Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File::Find::find has incorrect results when operating on a foreign filesystem without nlink support on an OS that has nlink support #14920

Open
p5pRT opened this issue Sep 23, 2015 · 6 comments

Comments

@p5pRT
Copy link

p5pRT commented Sep 23, 2015

Migrated from rt.perl.org#126144 (status was 'open')

Searchable as RT126144$

@p5pRT
Copy link
Author

p5pRT commented Sep 23, 2015

From rmah@pobox.com

Created by rmah@pobox.com

Summary
-------
If File​::Find​::find is executed on a directory that is a foreign mount
which does not support nlink (e.g. CIFS) while the host OS does support
nlink (e.g. Linux), it will silently fail to recurse into subdirectories
leading to incorrect results.

Background
----------
File​::Find currently uses the flag $dont_use_nlink to decide if nlink
should be used to determine if a directory has subdirectories. However,
it guesses the value for the flag by checking to see which OS perl is
running on.

When a foreign filesystems that do not support nlink is mounted on a
host OS which does supports nlink, executing File​::Find​::find on a dir
of that foreign filesystem will cause silent and mysterious failures.
The most common example would probably be Windows shares mounted on
Linux or MacOSX via CIFS. CIFS does not support nlinks (directories
always report 2) while perl thinks Linux does. Thus, if you execute
find() on a directory that is on the CIFS, you get silent failure and
bad reults.

Possible Resolution
-------------------
I suggest that a single check is done for the top level target directory
argument of find() to see if $dont_use_nlink is set correctly. Checking
could be done by iterating over the files and counting -d dirs and
comparing vs nlink. This check would improve reliability while having
only minimal impact on overall performance.

Negatives
---------
It is true that if a subdir of the top level target argument to find is
a foreign mount, that you will still get failures, but you will get
failures there anyway with the current code.

I understand that one can set $dont_use_nlink manually, but doing so
would impact performance for ALL users regardless of platform.

Perl Info

Flags:
     category=library
     severity=low
     module=File::Find

Site configuration information for perl 5.20.2:

Configured by Debian Project at Tue Mar  3 11:42:20 UTC 2015.

Summary of my perl5 (revision 5 version 20 subversion 2) configuration:

   Platform:
     osname=linux, osvers=3.2.0-58-generic, 
archname=x86_64-linux-gnu-thread-multi
     uname='linux kissel 3.2.0-58-generic #88-ubuntu smp tue dec 3 
17:37:58 utc 2013 x86_64 x86_64 x86_64 gnulinux '
     config_args='-Dusethreads -Duselargefiles -Dccflags=-DDEBIAN 
-D_FORTIFY_SOURCE=2 -g -O2 -fstack-protector-strong -Wformat 
-Werror=format-security -Dldflags= -Wl,-Bsymbolic-functions -Wl,-z,relro 
-Dlddlflags=-shared -Wl,-Bsymbolic-functions -Wl,-z,relro 
-Dcccdlflags=-fPIC -Darchname=x86_64-linux-gnu -Dprefix=/usr 
-Dprivlib=/usr/share/perl/5.20 
-Darchlib=/usr/lib/x86_64-linux-gnu/perl/5.20 -Dvendorprefix=/usr 
-Dvendorlib=/usr/share/perl5 
-Dvendorarch=/usr/lib/x86_64-linux-gnu/perl5/5.20 
-Dsiteprefix=/usr/local -Dsitelib=/usr/local/share/perl/5.20.2 
-Dsitearch=/usr/local/lib/x86_64-linux-gnu/perl/5.20.2 
-Dman1dir=/usr/share/man/man1 -Dman3dir=/usr/share/man/man3 
-Dsiteman1dir=/usr/local/man/man1 -Dsiteman3dir=/usr/local/man/man3 
-Duse64bitint -Dman1ext=1 -Dman3ext=3perl 
-Dpager=/usr/bin/sensible-pager -Uafs -Ud_csh -Ud_ualarm -Uusesfio 
-Uusenm -Ui_libutil -Uversiononly -DDEBUGGING=-g -Doptimize=-O2 
-Duseshrplib -Dlibperl=libperl.so.5.20.2 -des'
     hint=recommended, useposix=true, d_sigaction=define
     useithreads=define, usemultiplicity=define
     use64bitint=define, use64bitall=define, uselongdouble=undef
     usemymalloc=n, bincompat5005=undef
   Compiler:
     cc='cc', ccflags ='-D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fwrapv 
-fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE 
-D_FILE_OFFSET_BITS=64',
     optimize='-O2 -g',
     cppflags='-D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fwrapv 
-fno-strict-aliasing -pipe -I/usr/local/include'
     ccversion='', gccversion='4.9.2', gccosandvers=''
     intsize=4, longsize=8, ptrsize=8, doublesize=8, byteorder=12345678
     d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=16
     ivtype='long', ivsize=8, nvtype='double', nvsize=8, Off_t='off_t', 
lseeksize=8
     alignbytes=8, prototype=define
   Linker and Libraries:
     ld='cc', ldflags =' -fstack-protector -L/usr/local/lib'
     libpth=/usr/local/lib 
/usr/lib/gcc/x86_64-linux-gnu/4.9/include-fixed 
/usr/include/x86_64-linux-gnu /usr/lib /lib/x86_64-linux-gnu /lib/../lib 
/usr/lib/x86_64-linux-gnu /usr/lib/../lib /lib
     libs=-lgdbm -lgdbm_compat -ldb -ldl -lm -lpthread -lc -lcrypt
     perllibs=-ldl -lm -lpthread -lc -lcrypt
     libc=libc-2.19.so, so=so, useshrplib=true, libperl=libperl.so.5.20
     gnulibc_version='2.19'
   Dynamic Linking:
     dlsrc=dl_dlopen.xs, dlext=so, d_dlsymun=undef, ccdlflags='-Wl,-E'
     cccdlflags='-fPIC', lddlflags='-shared -L/usr/local/lib 
-fstack-protector'

Locally applied patches:
     DEBPKG:debian/cpan_definstalldirs - Provide a sensible INSTALLDIRS 
default for modules installed from CPAN.
     DEBPKG:debian/db_file_ver - http://bugs.debian.org/340047 Remove 
overly restrictive DB_File version check.
     DEBPKG:debian/doc_info - Replace generic man(1) instructions with 
Debian-specific information.
     DEBPKG:debian/enc2xs_inc - http://bugs.debian.org/290336 Tweak 
enc2xs to follow symlinks and ignore missing @INC directories.
     DEBPKG:debian/errno_ver - http://bugs.debian.org/343351 Remove 
Errno version check due to upgrade problems with long-running processes.
     DEBPKG:debian/libperl_embed_doc - http://bugs.debian.org/186778 
Note that libperl-dev package is required for embedded linking
     DEBPKG:fixes/respect_umask - Respect umask during installation
     DEBPKG:debian/writable_site_dirs - Set umask approproately for site 
install directories
     DEBPKG:debian/extutils_set_libperl_path - EU:MM: set location of 
libperl.a under /usr/lib
     DEBPKG:debian/no_packlist_perllocal - Don't install .packlist or 
perllocal.pod for perl or vendor
     DEBPKG:debian/prefix_changes - Fiddle with *PREFIX and variables 
written to the makefile
     DEBPKG:debian/fakeroot - Postpone LD_LIBRARY_PATH evaluation to the 
binary targets.
     DEBPKG:debian/instmodsh_doc - Debian policy doesn't install 
.packlist files for core or vendor.
     DEBPKG:debian/ld_run_path - Remove standard libs from LD_RUN_PATH 
as per Debian policy.
     DEBPKG:debian/libnet_config_path - Set location of libnet.cfg to 
/etc/perl/Net as /usr may not be writable.
     DEBPKG:debian/mod_paths - Tweak @INC ordering for Debian
     DEBPKG:debian/module_build_man_extensions - 
http://bugs.debian.org/479460 Adjust Module::Build manual page 
extensions for the Debian Perl policy
     DEBPKG:debian/prune_libs - http://bugs.debian.org/128355 Prune the 
list of libraries wanted to what we actually need.
     DEBPKG:fixes/net_smtp_docs - [rt.cpan.org #36038] 
http://bugs.debian.org/100195 Document the Net::SMTP 'Port' option
     DEBPKG:debian/perlivp - http://bugs.debian.org/510895 Make perlivp 
skip include directories in /usr/local
     DEBPKG:debian/deprecate-with-apt - http://bugs.debian.org/747628 
Point users to Debian packages of deprecated core modules
     DEBPKG:debian/squelch-locale-warnings - 
http://bugs.debian.org/508764 Squelch locale warnings in Debian package 
maintainer scripts
     DEBPKG:debian/skip-upstream-git-tests - Skip tests specific to the 
upstream Git repository
     DEBPKG:debian/patchlevel - http://bugs.debian.org/567489 List 
packaged patches for 5.20.2-2 in patchlevel.h
     DEBPKG:debian/skip-kfreebsd-crash - http://bugs.debian.org/628493 
[perl #96272] Skip a crashing test case in t/op/threads.t on GNU/kFreeBSD
     DEBPKG:fixes/document_makemaker_ccflags - 
http://bugs.debian.org/628522 [rt.cpan.org #68613] Document that CCFLAGS 
should include $Config{ccflags}
     DEBPKG:debian/find_html2text - http://bugs.debian.org/640479 
Configure CPAN::Distribution with correct name of html2text
     DEBPKG:debian/perl5db-x-terminal-emulator.patch - 
http://bugs.debian.org/668490 Invoke x-terminal-emulator rather than 
xterm in perl5db.pl
     DEBPKG:debian/cpan-missing-site-dirs - 
http://bugs.debian.org/688842 Fix CPAN::FirstTime defaults with 
nonexisting site dirs if a parent is writable
     DEBPKG:fixes/memoize_storable_nstore - [rt.cpan.org #77790] 
http://bugs.debian.org/587650 Memoize::Storable: respect 'nstore' option 
not respected
     DEBPKG:debian/regen-skip - Skip a regeneration check in unrelated 
git repositories
     DEBPKG:fixes/regcomp-mips-optim - [perl #122817] 
http://bugs.debian.org/754054 Downgrade the optimization of regcomp.c on 
mips and mipsel due to a gcc-4.9 bug
     DEBPKG:debian/makemaker-pasthru - http://bugs.debian.org/758471 
Pass LD settings through to subdirectories
     DEBPKG:fixes/perldoc-less-R - [rt.cpan.org #98636] 
http://bugs.debian.org/758689 Tell the 'less' pager to allow terminal 
escape sequences
     DEBPKG:fixes/pod_man_reproducible_date - 
http://bugs.debian.org/759405 Support POD_MAN_DATE in Pod::Man for the 
left-hand footer
     DEBPKG:fixes/io_uncompress_gunzip_inmemory - 
http://bugs.debian.org/747363 [rt.cpan.org #95494] Fix gunzip to 
in-memory file handle
     DEBPKG:fixes/socket_test_recv_fix - http://bugs.debian.org/758718 
[perl #122657] Compare recv return value to peername in socket test
     DEBPKG:fixes/hurd_socket_recv_todo - http://bugs.debian.org/758718 
[perl #122657] TODO checking the result of recv() on hurd
     DEBPKG:fixes/regexp-performance - [0fa70a0] 
http://bugs.debian.org/777556 [perl #123743] simpify and speed up 
/.*.../ handling


@INC for perl 5.20.2:
     /etc/perl
     /usr/local/lib/x86_64-linux-gnu/perl/5.20.2
     /usr/local/share/perl/5.20.2
     /usr/lib/x86_64-linux-gnu/perl5/5.20
     /usr/share/perl5
     /usr/lib/x86_64-linux-gnu/perl/5.20
     /usr/share/perl/5.20
     /usr/local/lib/site_perl
     .


Environment for perl 5.20.2:
     HOME=/home/rmah
     LANG=en_US.UTF-8
     LANGUAGE=en_US:
     LD_LIBRARY_PATH (unset)
     LOGDIR (unset)
 
PATH=/home/rmah/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
     PERL_BADLANG (unset)
     SHELL=/bin/bash

-- 
Robert S. Mah
rmah@pobox.com


@p5pRT
Copy link
Author

p5pRT commented Sep 24, 2015

From @jkeenan

On Wed Sep 23 09​:26​:31 2015, rmah@​pobox.com wrote​:

This is a bug report for perl from rmah@​pobox.com,
generated with the help of perlbug 1.40 running under perl 5.20.2.

-----------------------------------------------------------------
[Please describe your issue here]

Summary
-------
If File​::Find​::find is executed on a directory that is a foreign mount
which does not support nlink (e.g. CIFS) while the host OS does
support
nlink (e.g. Linux), it will silently fail to recurse into
subdirectories
leading to incorrect results.

Background
----------
File​::Find currently uses the flag $dont_use_nlink to decide if nlink
should be used to determine if a directory has subdirectories.
However,
it guesses the value for the flag by checking to see which OS perl is
running on.

When a foreign filesystems that do not support nlink is mounted on a
host OS which does supports nlink, executing File​::Find​::find on a dir
of that foreign filesystem will cause silent and mysterious failures.
The most common example would probably be Windows shares mounted on
Linux or MacOSX via CIFS. CIFS does not support nlinks (directories
always report 2) while perl thinks Linux does. Thus, if you execute
find() on a directory that is on the CIFS, you get silent failure and
bad results.

Would you be able to provide an example?

Thank you very much.

--
James E Keenan (jkeenan@​cpan.org)

@p5pRT
Copy link
Author

p5pRT commented Sep 24, 2015

The RT System itself - Status changed from 'new' to 'open'

@p5pRT
Copy link
Author

p5pRT commented Sep 24, 2015

From @iabyn

On Wed, Sep 23, 2015 at 05​:07​:40PM -0700, James E Keenan via RT wrote​:

If File​::Find​::find is executed on a directory that is a foreign mount
which does not support nlink (e.g. CIFS) while the host OS does
support
nlink (e.g. Linux), it will silently fail to recurse into
subdirectories
leading to incorrect results.

Well, File​::Find is *supposed* to detect this on the fly, e.g. when
chdiring to a CDROM mount point. (This was added by 5fa2bf2; see
the code starting with "default​: use whatever was specified" in File.pm).
but it seems to work by detecting nlink < 2. If a CIFS mount point is
faking up nlink == 2 it will defeat this auto-detection.

Possible Resolution
-------------------
I suggest that a single check is done for the top level target directory
argument of find() to see if $dont_use_nlink is set correctly. Checking
could be done by iterating over the files and counting -d dirs and
comparing vs nlink. This check would improve reliability while having only
minimal impact on overall performance.

This wouldn't detect cases where find crosses a mount point boundary,
but might be better than nothing.

--
A power surge on the Bridge is rapidly and correctly diagnosed as a faulty
capacitor by the highly-trained and competent engineering staff.
  -- Things That Never Happen in "Star Trek" #9

@p5pRT
Copy link
Author

p5pRT commented Sep 24, 2015

From rmah@pobox.com

James E Keenan via RT wrote​:

Would you be able to provide an example?

Thank you very much.

Well... sort of hard to do since it depends on one's environment, but,
I'll try...

Background


I found this problem by tracking down an issue I had building a module.
  On a Ubuntu 15.04 Linux box I have a home directory, "/home/rmah". In
that directory, I have mounted a Windows share at "/home/rmah/src" from
a Windows 10 box as a CIFS filesystem.

If you take a module's source archive (that uses ExtUtils​::MakeMaker)
and run Makefile.PL, it cannot find most of the files in the MANIFEST
and will report​:

rmah@​hillside​:~/src/modules/Stdlog$ perl Makefile.PL
Checking if your kit is complete...
Warning​: the following files are missing in your kit​:
  etc/bench-basics.pl
  etc/bench-formatter.pl
  etc/sample.pl
  lib/Stdlog.pm
  lib/Stdlog/Config.pm
  lib/Stdlog/Criteria.pm
  ... [many more files]...

If I move it to my home directory (so that it's on a local ext4
filesystem rather than on a remote CIFS filesystem), it works fine.

I tracked down the problem to the File​::Find​::find function and how it
checks to see if a dir has subdirs. It does this by calling stat() and
seeing how many hard links it has (nlink). Dirs with subdirs will have

2 on most OS's. However, some remotely filesystems (e.g. Windows
shares) will always report 2, and thus will not be detected as having
sub-directories even when they do.

NOTE​: I found that if you explicitly set $File​::Find​::dont_use_nlink=1
in the Makefile.PL, the build and test will work but the install still
fails to install all the files. Not sure why, but it's not directly
related to this problem, just a note that the easy "hack" does not fix
thing for this particular use case completely.

Alternative


Not sure how to give an small example, but you can do run this bash
script where WIN_SHARE refers to a directory that is a windows share
mounted on a Linux box​:

--
#!/bin/bash

WIN_SHARE=/path/to/windows/share/mount
TEST_DIR=${WIN_SHARE}/test_find

mkdir $TEST_DIR $TEST_DIR/foo
touch $TEST_DIR/test_one $TEST_DIR/foo/test_two
perl -MFile​::Find -le'find(sub{print $_}, "'${TEST_DIR}'")'
--

If you run that, the output *should* be​:
  .
  foo
  test_one
  test_two

but instead you just get​:
  .
  foo
  test_one

because File​::Find​::find does not recurse into subdirectories.

But Wait...


Upon rereading docs, I note that it says that File​::Find automatically
detects this issue...

  "You shouldn't need to set this variable, since File​::Find should
  now detect such file systems on-the-fly and switch itself to using
  stat. This works even for parts of your file system, like a mounted
  CD-ROM."

...but it does not. Perhaps this bug is a regression? In the _find_opt
sub, it simply checks the value set at startup (via OS checks) at line 518​:

  # a symbolic link to a directory doesn't increase the link count
  $avoid_nlink = $follow || $File​::Find​::dont_use_nlink;

But... after digging a bit more, perhaps the problem lies in the
_find_dir sub at line 741​:

  # default​: use whatever was specified
  # (if $nlink >= 2, and $avoid_nlink == 0, this will switch back)
  $no_nlink = $avoid_nlink;
  # if dir has wrong nlink count, force switch to slower stat method
  $no_nlink = 1 if ($nlink < 2);

For directories from a Windows share, nlink will be 2. To verify, just do​:

  perl -le'@​i=stat("dir/in/windows/share"); print $i[3]'

on directory within a Windows share. BTW, I verified that perl's stat
is returning the same value as the C stat(2).

Perhaps just changing that line that tests $nlink < 2 to <= 2 will fix this?

  $no_nlink = 1 if ($nlink <= 2);

Problem is, if you do this, I think it will always skip that optimized
block for when the find() start dir has no sub-dirs. Not sure how
important that is.

Anyway, sorry, this went on so long. Hope this helps.

Rob

P.S. Thanks for all the hard work you and everyone else as been doing on
perl5! I'm very impressed with all the progress over the last decade
and deeply indebted to you guys.

--
Robert S. Mah
rmah@​pobox.com

@p5pRT
Copy link
Author

p5pRT commented Sep 24, 2015

From rmah@pobox.com

Dave Mitchell via RT wrote​:

On Wed, Sep 23, 2015 at 05​:07​:40PM -0700, James E Keenan via RT wrote​:

If File​::Find​::find is executed on a directory that is a foreign mount
which does not support nlink (e.g. CIFS) while the host OS does
support
nlink (e.g. Linux), it will silently fail to recurse into
subdirectories
leading to incorrect results.

Well, File​::Find is *supposed* to detect this on the fly, e.g. when
chdiring to a CDROM mount point. (This was added by 5fa2bf2; see
the code starting with "default​: use whatever was specified" in File.pm).
but it seems to work by detecting nlink< 2. If a CIFS mount point is
faking up nlink == 2 it will defeat this auto-detection.

Yes, I discovered this as well on a re-read of the docs and code.

This is getting into an area I have next to no knowledge of but I did a
bit more research this morning and found that...

1) this problem (bad nlink counts for dirs) has been encountered by
other linux utilities (c.f. the find util and some sub dirs in /proc,
http​://lists.gnu.org/archive/html/bug-findutils/2005-04/msg00005.html)

2) It seems that some recommend FUSE filesystems be implemented with a
dir nlink count == 2. See
http​://www.slideshare.net/adorepump/building-file-systems-with-fuse and
refer to slides 22 and 31.

So it's probably not limited to some weird peculiarity in my environment :-)

Cheers,
Rob

--
Robert S. Mah
rmah@​pobox.com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants