use strict;
use warnings;
use warnings::register;
-our $VERSION = '1.28';
+our $VERSION = '1.29';
require Exporter;
require Cwd;
-#
-# Modified to ensure sub-directory traversal order is not inverted by stack
-# push and pops. That is remains in the same order as in the directory file,
-# or user pre-processing (EG:sorted).
-#
-
-=head1 NAME
-
-File::Find - Traverse a directory tree.
-
-=head1 SYNOPSIS
-
- use File::Find;
- find(\&wanted, @directories_to_search);
- sub wanted { ... }
-
- use File::Find;
- finddepth(\&wanted, @directories_to_search);
- sub wanted { ... }
+our @ISA = qw(Exporter);
+our @EXPORT = qw(find finddepth);
- use File::Find;
- find({ wanted => \&process, follow => 1 }, '.');
-=head1 DESCRIPTION
+use strict;
+my $Is_VMS;
+my $Is_Win32;
-These are functions for searching through directory trees doing work
-on each file found similar to the Unix I<find> command. File::Find
-exports two functions, C<find> and C<finddepth>. They work similarly
-but have subtle differences.
+require File::Basename;
+require File::Spec;
-=over 4
+# Should ideally be my() not our() but local() currently
+# refuses to operate on lexicals
-=item B<find>
+our %SLnkSeen;
+our ($wanted_callback, $avoid_nlink, $bydepth, $no_chdir, $follow,
+ $follow_skip, $full_check, $untaint, $untaint_skip, $untaint_pat,
+ $pre_process, $post_process, $dangling_symlinks);
- find(\&wanted, @directories);
- find(\%options, @directories);
+sub contract_name {
+ my ($cdir,$fn) = @_;
-C<find()> does a depth-first search over the given C<@directories> in
-the order they are given. For each file or directory found, it calls
-the C<&wanted> subroutine. (See below for details on how to use the
-C<&wanted> function). Additionally, for each directory found, it will
-C<chdir()> into that directory and continue the search, invoking the
-C<&wanted> function on each file or subdirectory in the directory.
+ return substr($cdir,0,rindex($cdir,'/')) if $fn eq $File::Find::current_dir;
-=item B<finddepth>
+ $cdir = substr($cdir,0,rindex($cdir,'/')+1);
- finddepth(\&wanted, @directories);
- finddepth(\%options, @directories);
+ $fn =~ s|^\./||;
-C<finddepth()> works just like C<find()> except that it invokes the
-C<&wanted> function for a directory I<after> invoking it for the
-directory's contents. It does a postorder traversal instead of a
-preorder traversal, working from the bottom of the directory tree up
-where C<find()> works from the top of the tree down.
+ my $abs_name= $cdir . $fn;
-=back
+ if (substr($fn,0,3) eq '../') {
+ 1 while $abs_name =~ s!/[^/]*/\.\./+!/!;
+ }
-=head2 %options
+ return $abs_name;
+}
-The first argument to C<find()> is either a code reference to your
-C<&wanted> function, or a hash reference describing the operations
-to be performed for each file. The
-code reference is described in L<The wanted function> below.
+sub PathCombine($$) {
+ my ($Base,$Name) = @_;
+ my $AbsName;
-Here are the possible keys for the hash:
+ if (substr($Name,0,1) eq '/') {
+ $AbsName= $Name;
+ }
+ else {
+ $AbsName= contract_name($Base,$Name);
+ }
-=over 3
+ # (simple) check for recursion
+ my $newlen= length($AbsName);
+ if ($newlen <= length($Base)) {
+ if (($newlen == length($Base) || substr($Base,$newlen,1) eq '/')
+ && $AbsName eq substr($Base,0,$newlen))
+ {
+ return undef;
+ }
+ }
+ return $AbsName;
+}
-=item C<wanted>
+sub Follow_SymLink($) {
+ my ($AbsName) = @_;
-The value should be a code reference. This code reference is
-described in L<The wanted function> below. The C<&wanted> subroutine is
-mandatory.
+ my ($NewName,$DEV, $INO);
+ ($DEV, $INO)= lstat $AbsName;
-=item C<bydepth>
+ while (-l _) {
+ if ($SLnkSeen{$DEV, $INO}++) {
+ if ($follow_skip < 2) {
+ die "$AbsName is encountered a second time";
+ }
+ else {
+ return undef;
+ }
+ }
+ $NewName= PathCombine($AbsName, readlink($AbsName));
+ unless(defined $NewName) {
+ if ($follow_skip < 2) {
+ die "$AbsName is a recursive symbolic link";
+ }
+ else {
+ return undef;
+ }
+ }
+ else {
+ $AbsName= $NewName;
+ }
+ ($DEV, $INO) = lstat($AbsName);
+ return undef unless defined $DEV; # dangling symbolic link
+ }
-Reports the name of a directory only AFTER all its entries
-have been reported. Entry point C<finddepth()> is a shortcut for
-specifying C<< { bydepth => 1 } >> in the first argument of C<find()>.
+ if ($full_check && defined $DEV && $SLnkSeen{$DEV, $INO}++) {
+ if ( ($follow_skip < 1) || ((-d _) && ($follow_skip < 2)) ) {
+ die "$AbsName encountered a second time";
+ }
+ else {
+ return undef;
+ }
+ }
-=item C<preprocess>
+ return $AbsName;
+}
-The value should be a code reference. This code reference is used to
-preprocess the current directory. The name of the currently processed
-directory is in C<$File::Find::dir>. Your preprocessing function is
-called after C<readdir()>, but before the loop that calls the C<wanted()>
-function. It is called with a list of strings (actually file/directory
-names) and is expected to return a list of strings. The code can be
-used to sort the file/directory names alphabetically, numerically,
-or to filter out directory entries based on their name alone. When
-I<follow> or I<follow_fast> are in effect, C<preprocess> is a no-op.
+our($dir, $name, $fullname, $prune);
+sub _find_dir_symlnk($$$);
+sub _find_dir($$$);
-=item C<postprocess>
+# check whether or not a scalar variable is tainted
+# (code straight from the Camel, 3rd ed., page 561)
+sub is_tainted_pp {
+ my $arg = shift;
+ my $nada = substr($arg, 0, 0); # zero-length
+ local $@;
+ eval { eval "# $nada" };
+ return length($@) != 0;
+}
-The value should be a code reference. It is invoked just before leaving
-the currently processed directory. It is called in void context with no
-arguments. The name of the current directory is in C<$File::Find::dir>. This
-hook is handy for summarizing a directory, such as calculating its disk
-usage. When I<follow> or I<follow_fast> are in effect, C<postprocess> is a
-no-op.
+sub _find_opt {
+ my $wanted = shift;
+ die "invalid top directory" unless defined $_[0];
-=item C<follow>
+ # This function must local()ize everything because callbacks may
+ # call find() or finddepth()
-Causes symbolic links to be followed. Since directory trees with symbolic
-links (followed) may contain files more than once and may even have
-cycles, a hash has to be built up with an entry for each file.
-This might be expensive both in space and time for a large
-directory tree. See L</follow_fast> and L</follow_skip> below.
-If either I<follow> or I<follow_fast> is in effect:
+ local %SLnkSeen;
+ local ($wanted_callback, $avoid_nlink, $bydepth, $no_chdir, $follow,
+ $follow_skip, $full_check, $untaint, $untaint_skip, $untaint_pat,
+ $pre_process, $post_process, $dangling_symlinks);
+ local($dir, $name, $fullname, $prune);
+ local *_ = \my $a;
-=over 6
+ my $cwd = $wanted->{bydepth} ? Cwd::fastcwd() : Cwd::getcwd();
+ if ($Is_VMS) {
+ # VMS returns this by default in VMS format which just doesn't
+ # work for the rest of this module.
+ $cwd = VMS::Filespec::unixpath($cwd);
-=item *
+ # Apparently this is not expected to have a trailing space.
+ # To attempt to make VMS/UNIX conversions mostly reversible,
+ # a trailing slash is needed. The run-time functions ignore the
+ # resulting double slash, but it causes the perl tests to fail.
+ $cwd =~ s#/\z##;
-It is guaranteed that an I<lstat> has been called before the user's
-C<wanted()> function is called. This enables fast file checks involving S<_>.
-Note that this guarantee no longer holds if I<follow> or I<follow_fast>
-are not set.
+ # This comes up in upper case now, but should be lower.
+ # In the future this could be exact case, no need to change.
+ }
+ my $cwd_untainted = $cwd;
+ my $check_t_cwd = 1;
+ $wanted_callback = $wanted->{wanted};
+ $bydepth = $wanted->{bydepth};
+ $pre_process = $wanted->{preprocess};
+ $post_process = $wanted->{postprocess};
+ $no_chdir = $wanted->{no_chdir};
+ $full_check = $Is_Win32 ? 0 : $wanted->{follow};
+ $follow = $Is_Win32 ? 0 :
+ $full_check || $wanted->{follow_fast};
+ $follow_skip = $wanted->{follow_skip};
+ $untaint = $wanted->{untaint};
+ $untaint_pat = $wanted->{untaint_pattern};
+ $untaint_skip = $wanted->{untaint_skip};
+ $dangling_symlinks = $wanted->{dangling_symlinks};
-=item *
+ # for compatibility reasons (find.pl, find2perl)
+ local our ($topdir, $topdev, $topino, $topmode, $topnlink);
-There is a variable C<$File::Find::fullname> which holds the absolute
-pathname of the file with all symbolic links resolved. If the link is
-a dangling symbolic link, then fullname will be set to C<undef>.
+ # a symbolic link to a directory doesn't increase the link count
+ $avoid_nlink = $follow || $File::Find::dont_use_nlink;
-=back
+ my ($abs_dir, $Is_Dir);
-This is a no-op on Win32.
+ Proc_Top_Item:
+ foreach my $TOP (@_) {
+ my $top_item = $TOP;
+ $top_item = VMS::Filespec::unixify($top_item) if $Is_VMS;
-=item C<follow_fast>
+ ($topdev,$topino,$topmode,$topnlink) = $follow ? stat $top_item : lstat $top_item;
-This is similar to I<follow> except that it may report some files more
-than once. It does detect cycles, however. Since only symbolic links
-have to be hashed, this is much cheaper both in space and time. If
-processing a file more than once (by the user's C<wanted()> function)
-is worse than just taking time, the option I<follow> should be used.
+ if ($Is_Win32) {
+ $top_item =~ s|[/\\]\z||
+ unless $top_item =~ m{^(?:\w:)?[/\\]$};
+ }
+ else {
+ $top_item =~ s|/\z|| unless $top_item eq '/';
+ }
-This is also a no-op on Win32.
+ $Is_Dir= 0;
-=item C<follow_skip>
+ if ($follow) {
-C<follow_skip==1>, which is the default, causes all files which are
-neither directories nor symbolic links to be ignored if they are about
-to be processed a second time. If a directory or a symbolic link
-are about to be processed a second time, File::Find dies.
+ if (substr($top_item,0,1) eq '/') {
+ $abs_dir = $top_item;
+ }
+ elsif ($top_item eq $File::Find::current_dir) {
+ $abs_dir = $cwd;
+ }
+ else { # care about any ../
+ $top_item =~ s/\.dir\z//i if $Is_VMS;
+ $abs_dir = contract_name("$cwd/",$top_item);
+ }
+ $abs_dir= Follow_SymLink($abs_dir);
+ unless (defined $abs_dir) {
+ if ($dangling_symlinks) {
+ if (ref $dangling_symlinks eq 'CODE') {
+ $dangling_symlinks->($top_item, $cwd);
+ } else {
+ warnings::warnif "$top_item is a dangling symbolic link\n";
+ }
+ }
+ next Proc_Top_Item;
+ }
-C<follow_skip==0> causes File::Find to die if any file is about to be
-processed a second time.
+ if (-d _) {
+ $top_item =~ s/\.dir\z//i if $Is_VMS;
+ _find_dir_symlnk($wanted, $abs_dir, $top_item);
+ $Is_Dir= 1;
+ }
+ }
+ else { # no follow
+ $topdir = $top_item;
+ unless (defined $topnlink) {
+ warnings::warnif "Can't stat $top_item: $!\n";
+ next Proc_Top_Item;
+ }
+ if (-d _) {
+ $top_item =~ s/\.dir\z//i if $Is_VMS;
+ _find_dir($wanted, $top_item, $topnlink);
+ $Is_Dir= 1;
+ }
+ else {
+ $abs_dir= $top_item;
+ }
+ }
-C<follow_skip==2> causes File::Find to ignore any duplicate files and
-directories but to proceed normally otherwise.
+ unless ($Is_Dir) {
+ unless (($_,$dir) = File::Basename::fileparse($abs_dir)) {
+ ($dir,$_) = ('./', $top_item);
+ }
-=item C<dangling_symlinks>
+ $abs_dir = $dir;
+ if (( $untaint ) && (is_tainted($dir) )) {
+ ( $abs_dir ) = $dir =~ m|$untaint_pat|;
+ unless (defined $abs_dir) {
+ if ($untaint_skip == 0) {
+ die "directory $dir is still tainted";
+ }
+ else {
+ next Proc_Top_Item;
+ }
+ }
+ }
-If true and a code reference, will be called with the symbolic link
-name and the directory it lives in as arguments. Otherwise, if true
-and warnings are on, warning "symbolic_link_name is a dangling
-symbolic link\n" will be issued. If false, the dangling symbolic link
-will be silently ignored.
+ unless ($no_chdir || chdir $abs_dir) {
+ warnings::warnif "Couldn't chdir $abs_dir: $!\n";
+ next Proc_Top_Item;
+ }
-=item C<no_chdir>
+ $name = $abs_dir . $_; # $File::Find::name
+ $_ = $name if $no_chdir;
-Does not C<chdir()> to each directory as it recurses. The C<wanted()>
-function will need to be aware of this, of course. In this case,
-C<$_> will be the same as C<$File::Find::name>.
+ { $wanted_callback->() }; # protect against wild "next"
-=item C<untaint>
+ }
-If find is used in taint-mode (-T command line switch or if EUID != UID
-or if EGID != GID) then internally directory names have to be untainted
-before they can be chdir'ed to. Therefore they are checked against a regular
-expression I<untaint_pattern>. Note that all names passed to the user's
-I<wanted()> function are still tainted. If this option is used while
-not in taint-mode, C<untaint> is a no-op.
+ unless ( $no_chdir ) {
+ if ( ($check_t_cwd) && (($untaint) && (is_tainted($cwd) )) ) {
+ ( $cwd_untainted ) = $cwd =~ m|$untaint_pat|;
+ unless (defined $cwd_untainted) {
+ die "insecure cwd in find(depth)";
+ }
+ $check_t_cwd = 0;
+ }
+ unless (chdir $cwd_untainted) {
+ die "Can't cd to $cwd: $!\n";
+ }
+ }
+ }
+}
-=item C<untaint_pattern>
+# API:
+# $wanted
+# $p_dir : "parent directory"
+# $nlink : what came back from the stat
+# preconditions:
+# chdir (if not no_chdir) to dir
-See above. This should be set using the C<qr> quoting operator.
-The default is set to C<qr|^([-+@\w./]+)$|>.
-Note that the parentheses are vital.
+sub _find_dir($$$) {
+ my ($wanted, $p_dir, $nlink) = @_;
+ my ($CdLvl,$Level) = (0,0);
+ my @Stack;
+ my @filenames;
+ my ($subcount,$sub_nlink);
+ my $SE= [];
+ my $dir_name= $p_dir;
+ my $dir_pref;
+ my $dir_rel = $File::Find::current_dir;
+ my $tainted = 0;
+ my $no_nlink;
-=item C<untaint_skip>
+ if ($Is_Win32) {
+ $dir_pref
+ = ($p_dir =~ m{^(?:\w:[/\\]?|[/\\])$} ? $p_dir : "$p_dir/" );
+ } elsif ($Is_VMS) {
-If set, a directory which fails the I<untaint_pattern> is skipped,
-including all its sub-directories. The default is to 'die' in such a case.
+ # VMS is returning trailing .dir on directories
+ # and trailing . on files and symbolic links
+ # in UNIX syntax.
+ #
-=back
+ $p_dir =~ s/\.(dir)?$//i unless $p_dir eq '.';
-=head2 The wanted function
+ $dir_pref = ($p_dir =~ m/[\]>]+$/ ? $p_dir : "$p_dir/" );
+ }
+ else {
+ $dir_pref= ( $p_dir eq '/' ? '/' : "$p_dir/" );
+ }
-The C<wanted()> function does whatever verifications you want on
-each file and directory. Note that despite its name, the C<wanted()>
-function is a generic callback function, and does B<not> tell
-File::Find if a file is "wanted" or not. In fact, its return value
-is ignored.
+ local ($dir, $name, $prune, *DIR);
-The wanted function takes no arguments but rather does its work
-through a collection of variables.
+ unless ( $no_chdir || ($p_dir eq $File::Find::current_dir)) {
+ my $udir = $p_dir;
+ if (( $untaint ) && (is_tainted($p_dir) )) {
+ ( $udir ) = $p_dir =~ m|$untaint_pat|;
+ unless (defined $udir) {
+ if ($untaint_skip == 0) {
+ die "directory $p_dir is still tainted";
+ }
+ else {
+ return;
+ }
+ }
+ }
+ unless (chdir ($Is_VMS && $udir !~ /[\/\[<]+/ ? "./$udir" : $udir)) {
+ warnings::warnif "Can't cd to $udir: $!\n";
+ return;
+ }
+ }
-=over 4
+ # push the starting directory
+ push @Stack,[$CdLvl,$p_dir,$dir_rel,-1] if $bydepth;
-=item C<$File::Find::dir> is the current directory name,
+ while (defined $SE) {
+ unless ($bydepth) {
+ $dir= $p_dir; # $File::Find::dir
+ $name= $dir_name; # $File::Find::name
+ $_= ($no_chdir ? $dir_name : $dir_rel ); # $_
+ # prune may happen here
+ $prune= 0;
+ { $wanted_callback->() }; # protect against wild "next"
+ next if $prune;
+ }
-=item C<$_> is the current filename within that directory
+ # change to that directory
+ unless ($no_chdir || ($dir_rel eq $File::Find::current_dir)) {
+ my $udir= $dir_rel;
+ if ( ($untaint) && (($tainted) || ($tainted = is_tainted($dir_rel) )) ) {
+ ( $udir ) = $dir_rel =~ m|$untaint_pat|;
+ unless (defined $udir) {
+ if ($untaint_skip == 0) {
+ die "directory (" . ($p_dir ne '/' ? $p_dir : '') . "/) $dir_rel is still tainted";
+ } else { # $untaint_skip == 1
+ next;
+ }
+ }
+ }
+ unless (chdir ($Is_VMS && $udir !~ /[\/\[<]+/ ? "./$udir" : $udir)) {
+ warnings::warnif "Can't cd to (" .
+ ($p_dir ne '/' ? $p_dir : '') . "/) $udir: $!\n";
+ next;
+ }
+ $CdLvl++;
+ }
-=item C<$File::Find::name> is the complete pathname to the file.
+ $dir= $dir_name; # $File::Find::dir
-=back
+ # Get the list of files in the current directory.
+ unless (opendir DIR, ($no_chdir ? $dir_name : $File::Find::current_dir)) {
+ warnings::warnif "Can't opendir($dir_name): $!\n";
+ next;
+ }
+ @filenames = readdir DIR;
+ closedir(DIR);
+ @filenames = $pre_process->(@filenames) if $pre_process;
+ push @Stack,[$CdLvl,$dir_name,"",-2] if $post_process;
-The above variables have all been localized and may be changed without
-affecting data outside of the wanted function.
+ # 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 example, when examining the file F</some/path/foo.ext> you will have:
+ if ($nlink == 2 && !$no_nlink) {
+ # This dir has no subdirectories.
+ for my $FN (@filenames) {
+ if ($Is_VMS) {
+ # Big hammer here - Compensate for VMS trailing . and .dir
+ # No win situation until this is changed, but this
+ # will handle the majority of the cases with breaking the fewest
- $File::Find::dir = /some/path/
- $_ = foo.ext
- $File::Find::name = /some/path/foo.ext
+ $FN =~ s/\.dir\z//i;
+ $FN =~ s#\.$## if ($FN ne '.');
+ }
+ next if $FN =~ $File::Find::skip_pattern;
+
+ $name = $dir_pref . $FN; # $File::Find::name
+ $_ = ($no_chdir ? $name : $FN); # $_
+ { $wanted_callback->() }; # protect against wild "next"
+ }
-You are chdir()'d to C<$File::Find::dir> when the function is called,
-unless C<no_chdir> was specified. Note that when changing to
-directories is in effect the root directory (F</>) is a somewhat
-special case inasmuch as the concatenation of C<$File::Find::dir>,
-C<'/'> and C<$_> is not literally equal to C<$File::Find::name>. The
-table below summarizes all variants:
+ }
+ else {
+ # This dir has subdirectories.
+ $subcount = $nlink - 2;
- $File::Find::name $File::Find::dir $_
- default / / .
- no_chdir=>0 /etc / etc
- /etc/x /etc x
+ # HACK: insert directories at this position. so as to preserve
+ # the user pre-processed ordering of files.
+ # EG: directory traversal is in user sorted order, not at random.
+ my $stack_top = @Stack;
- no_chdir=>1 / / /
- /etc / /etc
- /etc/x /etc /etc/x
+ for my $FN (@filenames) {
+ next if $FN =~ $File::Find::skip_pattern;
+ if ($subcount > 0 || $no_nlink) {
+ # Seen all the subdirs?
+ # check for directoriness.
+ # stat is faster for a file in the current directory
+ $sub_nlink = (lstat ($no_chdir ? $dir_pref . $FN : $FN))[3];
+ if (-d _) {
+ --$subcount;
+ $FN =~ s/\.dir\z//i if $Is_VMS;
+ # HACK: replace push to preserve dir traversal order
+ #push @Stack,[$CdLvl,$dir_name,$FN,$sub_nlink];
+ splice @Stack, $stack_top, 0,
+ [$CdLvl,$dir_name,$FN,$sub_nlink];
+ }
+ else {
+ $name = $dir_pref . $FN; # $File::Find::name
+ $_= ($no_chdir ? $name : $FN); # $_
+ { $wanted_callback->() }; # protect against wild "next"
+ }
+ }
+ else {
+ $name = $dir_pref . $FN; # $File::Find::name
+ $_= ($no_chdir ? $name : $FN); # $_
+ { $wanted_callback->() }; # protect against wild "next"
+ }
+ }
+ }
+ }
+ continue {
+ while ( defined ($SE = pop @Stack) ) {
+ ($Level, $p_dir, $dir_rel, $nlink) = @$SE;
+ if ($CdLvl > $Level && !$no_chdir) {
+ my $tmp;
+ if ($Is_VMS) {
+ $tmp = '[' . ('-' x ($CdLvl-$Level)) . ']';
+ }
+ else {
+ $tmp = join('/',('..') x ($CdLvl-$Level));
+ }
+ die "Can't cd to $tmp from $dir_name: $!"
+ unless chdir ($tmp);
+ $CdLvl = $Level;
+ }
-When C<follow> or C<follow_fast> are in effect, there is
-also a C<$File::Find::fullname>. The function may set
-C<$File::Find::prune> to prune the tree unless C<bydepth> was
-specified. Unless C<follow> or C<follow_fast> is specified, for
-compatibility reasons (find.pl, find2perl) there are in addition the
-following globals available: C<$File::Find::topdir>,
-C<$File::Find::topdev>, C<$File::Find::topino>,
-C<$File::Find::topmode> and C<$File::Find::topnlink>.
+ if ($Is_Win32) {
+ $dir_name = ($p_dir =~ m{^(?:\w:[/\\]?|[/\\])$}
+ ? "$p_dir$dir_rel" : "$p_dir/$dir_rel");
+ $dir_pref = "$dir_name/";
+ }
+ elsif ($^O eq 'VMS') {
+ if ($p_dir =~ m/[\]>]+$/) {
+ $dir_name = $p_dir;
+ $dir_name =~ s/([\]>]+)$/.$dir_rel$1/;
+ $dir_pref = $dir_name;
+ }
+ else {
+ $dir_name = "$p_dir/$dir_rel";
+ $dir_pref = "$dir_name/";
+ }
+ }
+ else {
+ $dir_name = ($p_dir eq '/' ? "/$dir_rel" : "$p_dir/$dir_rel");
+ $dir_pref = "$dir_name/";
+ }
-This library is useful for the C<find2perl> tool, which when fed,
+ if ( $nlink == -2 ) {
+ $name = $dir = $p_dir; # $File::Find::name / dir
+ $_ = $File::Find::current_dir;
+ $post_process->(); # End-of-directory processing
+ }
+ elsif ( $nlink < 0 ) { # must be finddepth, report dirname now
+ $name = $dir_name;
+ if ( substr($name,-2) eq '/.' ) {
+ substr($name, length($name) == 2 ? -1 : -2) = '';
+ }
+ $dir = $p_dir;
+ $_ = ($no_chdir ? $dir_name : $dir_rel );
+ if ( substr($_,-2) eq '/.' ) {
+ substr($_, length($_) == 2 ? -1 : -2) = '';
+ }
+ { $wanted_callback->() }; # protect against wild "next"
+ }
+ else {
+ push @Stack,[$CdLvl,$p_dir,$dir_rel,-1] if $bydepth;
+ last;
+ }
+ }
+ }
+}
- find2perl / -name .nfs\* -mtime +7 \
- -exec rm -f {} \; -o -fstype nfs -prune
-produces something like:
+# API:
+# $wanted
+# $dir_loc : absolute location of a dir
+# $p_dir : "parent directory"
+# preconditions:
+# chdir (if not no_chdir) to dir
- sub wanted {
- /^\.nfs.*\z/s &&
- (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_)) &&
- int(-M _) > 7 &&
- unlink($_)
- ||
- ($nlink || (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_))) &&
- $dev < 0 &&
- ($File::Find::prune = 1);
- }
+sub _find_dir_symlnk($$$) {
+ my ($wanted, $dir_loc, $p_dir) = @_; # $dir_loc is the absolute directory
+ my @Stack;
+ my @filenames;
+ my $new_loc;
+ my $updir_loc = $dir_loc; # untainted parent directory
+ my $SE = [];
+ my $dir_name = $p_dir;
+ my $dir_pref;
+ my $loc_pref;
+ my $dir_rel = $File::Find::current_dir;
+ my $byd_flag; # flag for pending stack entry if $bydepth
+ my $tainted = 0;
+ my $ok = 1;
-Notice the C<_> in the above C<int(-M _)>: the C<_> is a magical
-filehandle that caches the information from the preceding
-C<stat()>, C<lstat()>, or filetest.
+ $dir_pref = ( $p_dir eq '/' ? '/' : "$p_dir/" );
+ $loc_pref = ( $dir_loc eq '/' ? '/' : "$dir_loc/" );
-Here's another interesting wanted function. It will find all symbolic
-links that don't resolve:
+ local ($dir, $name, $fullname, $prune, *DIR);
- sub wanted {
- -l && !-e && print "bogus link: $File::Find::name\n";
+ unless ($no_chdir) {
+ # untaint the topdir
+ if (( $untaint ) && (is_tainted($dir_loc) )) {
+ ( $updir_loc ) = $dir_loc =~ m|$untaint_pat|; # parent dir, now untainted
+ # once untainted, $updir_loc is pushed on the stack (as parent directory);
+ # hence, we don't need to untaint the parent directory every time we chdir
+ # to it later
+ unless (defined $updir_loc) {
+ if ($untaint_skip == 0) {
+ die "directory $dir_loc is still tainted";
+ }
+ else {
+ return;
+ }
+ }
+ }
+ $ok = chdir($updir_loc) unless ($p_dir eq $File::Find::current_dir);
+ unless ($ok) {
+ warnings::warnif "Can't cd to $updir_loc: $!\n";
+ return;
+ }
}
-Note that you may mix directories and (non-directory) files in the list of
-directories to be searched by the C<wanted()> function.
+ push @Stack,[$dir_loc,$updir_loc,$p_dir,$dir_rel,-1] if $bydepth;
- find(\&wanted, "./foo", "./bar", "./baz/epsilon");
+ while (defined $SE) {
-In the example above, no file in F<./baz/> other than F<./baz/epsilon> will be
-evaluated by C<wanted()>.
+ unless ($bydepth) {
+ # change (back) to parent directory (always untainted)
+ unless ($no_chdir) {
+ unless (chdir $updir_loc) {
+ warnings::warnif "Can't cd to $updir_loc: $!\n";
+ next;
+ }
+ }
+ $dir= $p_dir; # $File::Find::dir
+ $name= $dir_name; # $File::Find::name
+ $_= ($no_chdir ? $dir_name : $dir_rel ); # $_
+ $fullname= $dir_loc; # $File::Find::fullname
+ # prune may happen here
+ $prune= 0;
+ lstat($_); # make sure file tests with '_' work
+ { $wanted_callback->() }; # protect against wild "next"
+ next if $prune;
+ }
-See also the script C<pfind> on CPAN for a nice application of this
-module.
+ # change to that directory
+ unless ($no_chdir || ($dir_rel eq $File::Find::current_dir)) {
+ $updir_loc = $dir_loc;
+ if ( ($untaint) && (($tainted) || ($tainted = is_tainted($dir_loc) )) ) {
+ # untaint $dir_loc, what will be pushed on the stack as (untainted) parent dir
+ ( $updir_loc ) = $dir_loc =~ m|$untaint_pat|;
+ unless (defined $updir_loc) {
+ if ($untaint_skip == 0) {
+ die "directory $dir_loc is still tainted";
+ }
+ else {
+ next;
+ }
+ }
+ }
+ unless (chdir $updir_loc) {
+ warnings::warnif "Can't cd to $updir_loc: $!\n";
+ next;
+ }
+ }
-=head1 WARNINGS
+ $dir = $dir_name; # $File::Find::dir
-If you run your program with the C<-w> switch, or if you use the
-C<warnings> pragma, File::Find will report warnings for several weird
-situations. You can disable these warnings by putting the statement
+ # Get the list of files in the current directory.
+ unless (opendir DIR, ($no_chdir ? $dir_loc : $File::Find::current_dir)) {
+ warnings::warnif "Can't opendir($dir_loc): $!\n";
+ next;
+ }
+ @filenames = readdir DIR;
+ closedir(DIR);
- no warnings 'File::Find';
+ for my $FN (@filenames) {
+ if ($Is_VMS) {
+ # Big hammer here - Compensate for VMS trailing . and .dir
+ # No win situation until this is changed, but this
+ # will handle the majority of the cases with breaking the fewest.
-in the appropriate scope. See L<warnings> for more info about lexical
-warnings.
+ $FN =~ s/\.dir\z//i;
+ $FN =~ s#\.$## if ($FN ne '.');
+ }
+ next if $FN =~ $File::Find::skip_pattern;
-=head1 CAVEAT
+ # follow symbolic links / do an lstat
+ $new_loc = Follow_SymLink($loc_pref.$FN);
-=over 2
+ # ignore if invalid symlink
+ unless (defined $new_loc) {
+ if (!defined -l _ && $dangling_symlinks) {
+ $fullname = undef;
+ if (ref $dangling_symlinks eq 'CODE') {
+ $dangling_symlinks->($FN, $dir_pref);
+ } else {
+ warnings::warnif "$dir_pref$FN is a dangling symbolic link\n";
+ }
+ }
+ else {
+ $fullname = $loc_pref . $FN;
+ }
+ $name = $dir_pref . $FN;
+ $_ = ($no_chdir ? $name : $FN);
+ { $wanted_callback->() };
+ next;
+ }
-=item $dont_use_nlink
+ if (-d _) {
+ if ($Is_VMS) {
+ $FN =~ s/\.dir\z//i;
+ $FN =~ s#\.$## if ($FN ne '.');
+ $new_loc =~ s/\.dir\z//i;
+ $new_loc =~ s#\.$## if ($new_loc ne '.');
+ }
+ push @Stack,[$new_loc,$updir_loc,$dir_name,$FN,1];
+ }
+ else {
+ $fullname = $new_loc; # $File::Find::fullname
+ $name = $dir_pref . $FN; # $File::Find::name
+ $_ = ($no_chdir ? $name : $FN); # $_
+ { $wanted_callback->() }; # protect against wild "next"
+ }
+ }
-You can set the variable C<$File::Find::dont_use_nlink> to 1, if you want to
-force File::Find to always stat directories. This was used for file systems
-that do not have an C<nlink> count matching the number of sub-directories.
-Examples are ISO-9660 (CD-ROM), AFS, HPFS (OS/2 file system), FAT (DOS file
-system) and a couple of others.
+ }
+ continue {
+ while (defined($SE = pop @Stack)) {
+ ($dir_loc, $updir_loc, $p_dir, $dir_rel, $byd_flag) = @$SE;
+ $dir_name = ($p_dir eq '/' ? "/$dir_rel" : "$p_dir/$dir_rel");
+ $dir_pref = "$dir_name/";
+ $loc_pref = "$dir_loc/";
+ if ( $byd_flag < 0 ) { # must be finddepth, report dirname now
+ unless ($no_chdir || ($dir_rel eq $File::Find::current_dir)) {
+ unless (chdir $updir_loc) { # $updir_loc (parent dir) is always untainted
+ warnings::warnif "Can't cd to $updir_loc: $!\n";
+ next;
+ }
+ }
+ $fullname = $dir_loc; # $File::Find::fullname
+ $name = $dir_name; # $File::Find::name
+ if ( substr($name,-2) eq '/.' ) {
+ substr($name, length($name) == 2 ? -1 : -2) = ''; # $File::Find::name
+ }
+ $dir = $p_dir; # $File::Find::dir
+ $_ = ($no_chdir ? $dir_name : $dir_rel); # $_
+ if ( substr($_,-2) eq '/.' ) {
+ substr($_, length($_) == 2 ? -1 : -2) = '';
+ }
-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.
+ lstat($_); # make sure file tests with '_' work
+ { $wanted_callback->() }; # protect against wild "next"
+ }
+ else {
+ push @Stack,[$dir_loc, $updir_loc, $p_dir, $dir_rel,-1] if $bydepth;
+ last;
+ }
+ }
+ }
+}
-If you do set C<$File::Find::dont_use_nlink> to 1, you will notice slow-downs.
-=item symlinks
+sub wrap_wanted {
+ my $wanted = shift;
+ if ( ref($wanted) eq 'HASH' ) {
+ # RT #122547
+ my %valid_options = map {$_ => 1} qw(
+ wanted
+ bydepth
+ preprocess
+ postprocess
+ follow
+ follow_fast
+ follow_skip
+ dangling_symlinks
+ no_chdir
+ untaint
+ untaint_pattern
+ untaint_skip
+ );
+ my @invalid_options = ();
+ for my $v (keys %{$wanted}) {
+ push @invalid_options, $v unless exists $valid_options{$v};
+ }
+ warn "Invalid option(s): @invalid_options" if @invalid_options;
+
+ unless( exists $wanted->{wanted} and ref( $wanted->{wanted} ) eq 'CODE' ) {
+ die 'no &wanted subroutine given';
+ }
+ if ( $wanted->{follow} || $wanted->{follow_fast}) {
+ $wanted->{follow_skip} = 1 unless defined $wanted->{follow_skip};
+ }
+ if ( $wanted->{untaint} ) {
+ $wanted->{untaint_pattern} = $File::Find::untaint_pattern
+ unless defined $wanted->{untaint_pattern};
+ $wanted->{untaint_skip} = 0 unless defined $wanted->{untaint_skip};
+ }
+ return $wanted;
+ }
+ elsif( ref( $wanted ) eq 'CODE' ) {
+ return { wanted => $wanted };
+ }
+ else {
+ die 'no &wanted subroutine given';
+ }
+}
-Be aware that the option to follow symbolic links can be dangerous.
-Depending on the structure of the directory tree (including symbolic
-links to directories) you might traverse a given (physical) directory
-more than once (only if C<follow_fast> is in effect).
-Furthermore, deleting or changing files in a symbolically linked directory
-might cause very unpleasant surprises, since you delete or change files
-in an unknown directory.
+sub find {
+ my $wanted = shift;
+ _find_opt(wrap_wanted($wanted), @_);
+}
-=back
+sub finddepth {
+ my $wanted = wrap_wanted(shift);
+ $wanted->{bydepth} = 1;
+ _find_opt($wanted, @_);
+}
-=head1 BUGS AND CAVEATS
+# default
+$File::Find::skip_pattern = qr/^\.{1,2}\z/;
+$File::Find::untaint_pattern = qr|^([-+@\w./]+)$|;
-Despite the name of the C<finddepth()> function, both C<find()> and
-C<finddepth()> perform a depth-first search of the directory
-hierarchy.
+# These are hard-coded for now, but may move to hint files.
+if ($^O eq 'VMS') {
+ $Is_VMS = 1;
+ $File::Find::dont_use_nlink = 1;
+}
+elsif ($^O eq 'MSWin32') {
+ $Is_Win32 = 1;
+}
-=head1 HISTORY
+# this _should_ work properly on all platforms
+# where File::Find can be expected to work
+$File::Find::current_dir = File::Spec->curdir || '.';
-File::Find used to produce incorrect results if called recursively.
-During the development of perl 5.8 this bug was fixed.
-The first fixed version of File::Find was 1.01.
+$File::Find::dont_use_nlink = 1
+ if $^O eq 'os2' || $^O eq 'dos' || $^O eq 'amigaos' || $Is_Win32 ||
+ $^O eq 'interix' || $^O eq 'cygwin' || $^O eq 'qnx' || $^O eq 'nto';
-=head1 SEE ALSO
+# Set dont_use_nlink in your hint file if your system's stat doesn't
+# report the number of links in a directory as an indication
+# of the number of files.
+# See, e.g. hints/machten.sh for MachTen 2.2.
+unless ($File::Find::dont_use_nlink) {
+ require Config;
+ $File::Find::dont_use_nlink = 1 if ($Config::Config{'dont_use_nlink'});
+}
-find, find2perl.
+# We need a function that checks if a scalar is tainted. Either use the
+# Scalar::Util module's tainted() function or our (slower) pure Perl
+# fallback is_tainted_pp()
+{
+ local $@;
+ eval { require Scalar::Util };
+ *is_tainted = $@ ? \&is_tainted_pp : \&Scalar::Util::tainted;
+}
-=cut
+1;
-our @ISA = qw(Exporter);
-our @EXPORT = qw(find finddepth);
+__END__
+#
+# Modified to ensure sub-directory traversal order is not inverted by stack
+# push and pops. That is remains in the same order as in the directory file,
+# or user pre-processing (EG:sorted).
+#
+=head1 NAME
-use strict;
-my $Is_VMS;
-my $Is_Win32;
+File::Find - Traverse a directory tree.
-require File::Basename;
-require File::Spec;
+=head1 SYNOPSIS
-# Should ideally be my() not our() but local() currently
-# refuses to operate on lexicals
+ use File::Find;
+ find(\&wanted, @directories_to_search);
+ sub wanted { ... }
-our %SLnkSeen;
-our ($wanted_callback, $avoid_nlink, $bydepth, $no_chdir, $follow,
- $follow_skip, $full_check, $untaint, $untaint_skip, $untaint_pat,
- $pre_process, $post_process, $dangling_symlinks);
+ use File::Find;
+ finddepth(\&wanted, @directories_to_search);
+ sub wanted { ... }
-sub contract_name {
- my ($cdir,$fn) = @_;
+ use File::Find;
+ find({ wanted => \&process, follow => 1 }, '.');
- return substr($cdir,0,rindex($cdir,'/')) if $fn eq $File::Find::current_dir;
+=head1 DESCRIPTION
- $cdir = substr($cdir,0,rindex($cdir,'/')+1);
+These are functions for searching through directory trees doing work
+on each file found similar to the Unix I<find> command. File::Find
+exports two functions, C<find> and C<finddepth>. They work similarly
+but have subtle differences.
- $fn =~ s|^\./||;
+=over 4
- my $abs_name= $cdir . $fn;
+=item B<find>
- if (substr($fn,0,3) eq '../') {
- 1 while $abs_name =~ s!/[^/]*/\.\./+!/!;
- }
+ find(\&wanted, @directories);
+ find(\%options, @directories);
- return $abs_name;
-}
+C<find()> does a depth-first search over the given C<@directories> in
+the order they are given. For each file or directory found, it calls
+the C<&wanted> subroutine. (See below for details on how to use the
+C<&wanted> function). Additionally, for each directory found, it will
+C<chdir()> into that directory and continue the search, invoking the
+C<&wanted> function on each file or subdirectory in the directory.
-sub PathCombine($$) {
- my ($Base,$Name) = @_;
- my $AbsName;
+=item B<finddepth>
- if (substr($Name,0,1) eq '/') {
- $AbsName= $Name;
- }
- else {
- $AbsName= contract_name($Base,$Name);
- }
+ finddepth(\&wanted, @directories);
+ finddepth(\%options, @directories);
- # (simple) check for recursion
- my $newlen= length($AbsName);
- if ($newlen <= length($Base)) {
- if (($newlen == length($Base) || substr($Base,$newlen,1) eq '/')
- && $AbsName eq substr($Base,0,$newlen))
- {
- return undef;
- }
- }
- return $AbsName;
-}
+C<finddepth()> works just like C<find()> except that it invokes the
+C<&wanted> function for a directory I<after> invoking it for the
+directory's contents. It does a postorder traversal instead of a
+preorder traversal, working from the bottom of the directory tree up
+where C<find()> works from the top of the tree down.
-sub Follow_SymLink($) {
- my ($AbsName) = @_;
+=back
- my ($NewName,$DEV, $INO);
- ($DEV, $INO)= lstat $AbsName;
+=head2 %options
- while (-l _) {
- if ($SLnkSeen{$DEV, $INO}++) {
- if ($follow_skip < 2) {
- die "$AbsName is encountered a second time";
- }
- else {
- return undef;
- }
- }
- $NewName= PathCombine($AbsName, readlink($AbsName));
- unless(defined $NewName) {
- if ($follow_skip < 2) {
- die "$AbsName is a recursive symbolic link";
- }
- else {
- return undef;
- }
- }
- else {
- $AbsName= $NewName;
- }
- ($DEV, $INO) = lstat($AbsName);
- return undef unless defined $DEV; # dangling symbolic link
- }
+The first argument to C<find()> is either a code reference to your
+C<&wanted> function, or a hash reference describing the operations
+to be performed for each file. The
+code reference is described in L<The wanted function> below.
- if ($full_check && defined $DEV && $SLnkSeen{$DEV, $INO}++) {
- if ( ($follow_skip < 1) || ((-d _) && ($follow_skip < 2)) ) {
- die "$AbsName encountered a second time";
- }
- else {
- return undef;
- }
- }
+Here are the possible keys for the hash:
- return $AbsName;
-}
+=over 3
-our($dir, $name, $fullname, $prune);
-sub _find_dir_symlnk($$$);
-sub _find_dir($$$);
+=item C<wanted>
-# check whether or not a scalar variable is tainted
-# (code straight from the Camel, 3rd ed., page 561)
-sub is_tainted_pp {
- my $arg = shift;
- my $nada = substr($arg, 0, 0); # zero-length
- local $@;
- eval { eval "# $nada" };
- return length($@) != 0;
-}
+The value should be a code reference. This code reference is
+described in L<The wanted function> below. The C<&wanted> subroutine is
+mandatory.
-sub _find_opt {
- my $wanted = shift;
- die "invalid top directory" unless defined $_[0];
+=item C<bydepth>
- # This function must local()ize everything because callbacks may
- # call find() or finddepth()
+Reports the name of a directory only AFTER all its entries
+have been reported. Entry point C<finddepth()> is a shortcut for
+specifying C<< { bydepth => 1 } >> in the first argument of C<find()>.
- local %SLnkSeen;
- local ($wanted_callback, $avoid_nlink, $bydepth, $no_chdir, $follow,
- $follow_skip, $full_check, $untaint, $untaint_skip, $untaint_pat,
- $pre_process, $post_process, $dangling_symlinks);
- local($dir, $name, $fullname, $prune);
- local *_ = \my $a;
+=item C<preprocess>
- my $cwd = $wanted->{bydepth} ? Cwd::fastcwd() : Cwd::getcwd();
- if ($Is_VMS) {
- # VMS returns this by default in VMS format which just doesn't
- # work for the rest of this module.
- $cwd = VMS::Filespec::unixpath($cwd);
+The value should be a code reference. This code reference is used to
+preprocess the current directory. The name of the currently processed
+directory is in C<$File::Find::dir>. Your preprocessing function is
+called after C<readdir()>, but before the loop that calls the C<wanted()>
+function. It is called with a list of strings (actually file/directory
+names) and is expected to return a list of strings. The code can be
+used to sort the file/directory names alphabetically, numerically,
+or to filter out directory entries based on their name alone. When
+I<follow> or I<follow_fast> are in effect, C<preprocess> is a no-op.
- # Apparently this is not expected to have a trailing space.
- # To attempt to make VMS/UNIX conversions mostly reversible,
- # a trailing slash is needed. The run-time functions ignore the
- # resulting double slash, but it causes the perl tests to fail.
- $cwd =~ s#/\z##;
+=item C<postprocess>
- # This comes up in upper case now, but should be lower.
- # In the future this could be exact case, no need to change.
- }
- my $cwd_untainted = $cwd;
- my $check_t_cwd = 1;
- $wanted_callback = $wanted->{wanted};
- $bydepth = $wanted->{bydepth};
- $pre_process = $wanted->{preprocess};
- $post_process = $wanted->{postprocess};
- $no_chdir = $wanted->{no_chdir};
- $full_check = $Is_Win32 ? 0 : $wanted->{follow};
- $follow = $Is_Win32 ? 0 :
- $full_check || $wanted->{follow_fast};
- $follow_skip = $wanted->{follow_skip};
- $untaint = $wanted->{untaint};
- $untaint_pat = $wanted->{untaint_pattern};
- $untaint_skip = $wanted->{untaint_skip};
- $dangling_symlinks = $wanted->{dangling_symlinks};
+The value should be a code reference. It is invoked just before leaving
+the currently processed directory. It is called in void context with no
+arguments. The name of the current directory is in C<$File::Find::dir>. This
+hook is handy for summarizing a directory, such as calculating its disk
+usage. When I<follow> or I<follow_fast> are in effect, C<postprocess> is a
+no-op.
- # for compatibility reasons (find.pl, find2perl)
- local our ($topdir, $topdev, $topino, $topmode, $topnlink);
+=item C<follow>
- # a symbolic link to a directory doesn't increase the link count
- $avoid_nlink = $follow || $File::Find::dont_use_nlink;
+Causes symbolic links to be followed. Since directory trees with symbolic
+links (followed) may contain files more than once and may even have
+cycles, a hash has to be built up with an entry for each file.
+This might be expensive both in space and time for a large
+directory tree. See L</follow_fast> and L</follow_skip> below.
+If either I<follow> or I<follow_fast> is in effect:
- my ($abs_dir, $Is_Dir);
+=over 6
- Proc_Top_Item:
- foreach my $TOP (@_) {
- my $top_item = $TOP;
- $top_item = VMS::Filespec::unixify($top_item) if $Is_VMS;
+=item *
- ($topdev,$topino,$topmode,$topnlink) = $follow ? stat $top_item : lstat $top_item;
+It is guaranteed that an I<lstat> has been called before the user's
+C<wanted()> function is called. This enables fast file checks involving S<_>.
+Note that this guarantee no longer holds if I<follow> or I<follow_fast>
+are not set.
- if ($Is_Win32) {
- $top_item =~ s|[/\\]\z||
- unless $top_item =~ m{^(?:\w:)?[/\\]$};
- }
- else {
- $top_item =~ s|/\z|| unless $top_item eq '/';
- }
+=item *
- $Is_Dir= 0;
+There is a variable C<$File::Find::fullname> which holds the absolute
+pathname of the file with all symbolic links resolved. If the link is
+a dangling symbolic link, then fullname will be set to C<undef>.
- if ($follow) {
+=back
- if (substr($top_item,0,1) eq '/') {
- $abs_dir = $top_item;
- }
- elsif ($top_item eq $File::Find::current_dir) {
- $abs_dir = $cwd;
- }
- else { # care about any ../
- $top_item =~ s/\.dir\z//i if $Is_VMS;
- $abs_dir = contract_name("$cwd/",$top_item);
- }
- $abs_dir= Follow_SymLink($abs_dir);
- unless (defined $abs_dir) {
- if ($dangling_symlinks) {
- if (ref $dangling_symlinks eq 'CODE') {
- $dangling_symlinks->($top_item, $cwd);
- } else {
- warnings::warnif "$top_item is a dangling symbolic link\n";
- }
- }
- next Proc_Top_Item;
- }
+This is a no-op on Win32.
- if (-d _) {
- $top_item =~ s/\.dir\z//i if $Is_VMS;
- _find_dir_symlnk($wanted, $abs_dir, $top_item);
- $Is_Dir= 1;
- }
- }
- else { # no follow
- $topdir = $top_item;
- unless (defined $topnlink) {
- warnings::warnif "Can't stat $top_item: $!\n";
- next Proc_Top_Item;
- }
- if (-d _) {
- $top_item =~ s/\.dir\z//i if $Is_VMS;
- _find_dir($wanted, $top_item, $topnlink);
- $Is_Dir= 1;
- }
- else {
- $abs_dir= $top_item;
- }
- }
+=item C<follow_fast>
- unless ($Is_Dir) {
- unless (($_,$dir) = File::Basename::fileparse($abs_dir)) {
- ($dir,$_) = ('./', $top_item);
- }
+This is similar to I<follow> except that it may report some files more
+than once. It does detect cycles, however. Since only symbolic links
+have to be hashed, this is much cheaper both in space and time. If
+processing a file more than once (by the user's C<wanted()> function)
+is worse than just taking time, the option I<follow> should be used.
- $abs_dir = $dir;
- if (( $untaint ) && (is_tainted($dir) )) {
- ( $abs_dir ) = $dir =~ m|$untaint_pat|;
- unless (defined $abs_dir) {
- if ($untaint_skip == 0) {
- die "directory $dir is still tainted";
- }
- else {
- next Proc_Top_Item;
- }
- }
- }
+This is also a no-op on Win32.
- unless ($no_chdir || chdir $abs_dir) {
- warnings::warnif "Couldn't chdir $abs_dir: $!\n";
- next Proc_Top_Item;
- }
+=item C<follow_skip>
- $name = $abs_dir . $_; # $File::Find::name
- $_ = $name if $no_chdir;
+C<follow_skip==1>, which is the default, causes all files which are
+neither directories nor symbolic links to be ignored if they are about
+to be processed a second time. If a directory or a symbolic link
+are about to be processed a second time, File::Find dies.
- { $wanted_callback->() }; # protect against wild "next"
+C<follow_skip==0> causes File::Find to die if any file is about to be
+processed a second time.
- }
+C<follow_skip==2> causes File::Find to ignore any duplicate files and
+directories but to proceed normally otherwise.
- unless ( $no_chdir ) {
- if ( ($check_t_cwd) && (($untaint) && (is_tainted($cwd) )) ) {
- ( $cwd_untainted ) = $cwd =~ m|$untaint_pat|;
- unless (defined $cwd_untainted) {
- die "insecure cwd in find(depth)";
- }
- $check_t_cwd = 0;
- }
- unless (chdir $cwd_untainted) {
- die "Can't cd to $cwd: $!\n";
- }
- }
- }
-}
+=item C<dangling_symlinks>
-# API:
-# $wanted
-# $p_dir : "parent directory"
-# $nlink : what came back from the stat
-# preconditions:
-# chdir (if not no_chdir) to dir
+If true and a code reference, will be called with the symbolic link
+name and the directory it lives in as arguments. Otherwise, if true
+and warnings are on, warning "symbolic_link_name is a dangling
+symbolic link\n" will be issued. If false, the dangling symbolic link
+will be silently ignored.
-sub _find_dir($$$) {
- my ($wanted, $p_dir, $nlink) = @_;
- my ($CdLvl,$Level) = (0,0);
- my @Stack;
- my @filenames;
- my ($subcount,$sub_nlink);
- my $SE= [];
- my $dir_name= $p_dir;
- my $dir_pref;
- my $dir_rel = $File::Find::current_dir;
- my $tainted = 0;
- my $no_nlink;
+=item C<no_chdir>
- if ($Is_Win32) {
- $dir_pref
- = ($p_dir =~ m{^(?:\w:[/\\]?|[/\\])$} ? $p_dir : "$p_dir/" );
- } elsif ($Is_VMS) {
+Does not C<chdir()> to each directory as it recurses. The C<wanted()>
+function will need to be aware of this, of course. In this case,
+C<$_> will be the same as C<$File::Find::name>.
- # VMS is returning trailing .dir on directories
- # and trailing . on files and symbolic links
- # in UNIX syntax.
- #
+=item C<untaint>
- $p_dir =~ s/\.(dir)?$//i unless $p_dir eq '.';
+If find is used in taint-mode (-T command line switch or if EUID != UID
+or if EGID != GID) then internally directory names have to be untainted
+before they can be chdir'ed to. Therefore they are checked against a regular
+expression I<untaint_pattern>. Note that all names passed to the user's
+I<wanted()> function are still tainted. If this option is used while
+not in taint-mode, C<untaint> is a no-op.
- $dir_pref = ($p_dir =~ m/[\]>]+$/ ? $p_dir : "$p_dir/" );
- }
- else {
- $dir_pref= ( $p_dir eq '/' ? '/' : "$p_dir/" );
- }
+=item C<untaint_pattern>
- local ($dir, $name, $prune, *DIR);
+See above. This should be set using the C<qr> quoting operator.
+The default is set to C<qr|^([-+@\w./]+)$|>.
+Note that the parentheses are vital.
- unless ( $no_chdir || ($p_dir eq $File::Find::current_dir)) {
- my $udir = $p_dir;
- if (( $untaint ) && (is_tainted($p_dir) )) {
- ( $udir ) = $p_dir =~ m|$untaint_pat|;
- unless (defined $udir) {
- if ($untaint_skip == 0) {
- die "directory $p_dir is still tainted";
- }
- else {
- return;
- }
- }
- }
- unless (chdir ($Is_VMS && $udir !~ /[\/\[<]+/ ? "./$udir" : $udir)) {
- warnings::warnif "Can't cd to $udir: $!\n";
- return;
- }
- }
+=item C<untaint_skip>
- # push the starting directory
- push @Stack,[$CdLvl,$p_dir,$dir_rel,-1] if $bydepth;
+If set, a directory which fails the I<untaint_pattern> is skipped,
+including all its sub-directories. The default is to 'die' in such a case.
- while (defined $SE) {
- unless ($bydepth) {
- $dir= $p_dir; # $File::Find::dir
- $name= $dir_name; # $File::Find::name
- $_= ($no_chdir ? $dir_name : $dir_rel ); # $_
- # prune may happen here
- $prune= 0;
- { $wanted_callback->() }; # protect against wild "next"
- next if $prune;
- }
+=back
+
+=head2 The wanted function
+
+The C<wanted()> function does whatever verifications you want on
+each file and directory. Note that despite its name, the C<wanted()>
+function is a generic callback function, and does B<not> tell
+File::Find if a file is "wanted" or not. In fact, its return value
+is ignored.
- # change to that directory
- unless ($no_chdir || ($dir_rel eq $File::Find::current_dir)) {
- my $udir= $dir_rel;
- if ( ($untaint) && (($tainted) || ($tainted = is_tainted($dir_rel) )) ) {
- ( $udir ) = $dir_rel =~ m|$untaint_pat|;
- unless (defined $udir) {
- if ($untaint_skip == 0) {
- die "directory (" . ($p_dir ne '/' ? $p_dir : '') . "/) $dir_rel is still tainted";
- } else { # $untaint_skip == 1
- next;
- }
- }
- }
- unless (chdir ($Is_VMS && $udir !~ /[\/\[<]+/ ? "./$udir" : $udir)) {
- warnings::warnif "Can't cd to (" .
- ($p_dir ne '/' ? $p_dir : '') . "/) $udir: $!\n";
- next;
- }
- $CdLvl++;
- }
+The wanted function takes no arguments but rather does its work
+through a collection of variables.
- $dir= $dir_name; # $File::Find::dir
+=over 4
- # Get the list of files in the current directory.
- unless (opendir DIR, ($no_chdir ? $dir_name : $File::Find::current_dir)) {
- warnings::warnif "Can't opendir($dir_name): $!\n";
- next;
- }
- @filenames = readdir DIR;
- closedir(DIR);
- @filenames = $pre_process->(@filenames) if $pre_process;
- push @Stack,[$CdLvl,$dir_name,"",-2] if $post_process;
+=item C<$File::Find::dir> is the current directory name,
- # 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);
+=item C<$_> is the current filename within that directory
- if ($nlink == 2 && !$no_nlink) {
- # This dir has no subdirectories.
- for my $FN (@filenames) {
- if ($Is_VMS) {
- # Big hammer here - Compensate for VMS trailing . and .dir
- # No win situation until this is changed, but this
- # will handle the majority of the cases with breaking the fewest
+=item C<$File::Find::name> is the complete pathname to the file.
- $FN =~ s/\.dir\z//i;
- $FN =~ s#\.$## if ($FN ne '.');
- }
- next if $FN =~ $File::Find::skip_pattern;
-
- $name = $dir_pref . $FN; # $File::Find::name
- $_ = ($no_chdir ? $name : $FN); # $_
- { $wanted_callback->() }; # protect against wild "next"
- }
+=back
- }
- else {
- # This dir has subdirectories.
- $subcount = $nlink - 2;
+The above variables have all been localized and may be changed without
+affecting data outside of the wanted function.
- # HACK: insert directories at this position. so as to preserve
- # the user pre-processed ordering of files.
- # EG: directory traversal is in user sorted order, not at random.
- my $stack_top = @Stack;
+For example, when examining the file F</some/path/foo.ext> you will have:
- for my $FN (@filenames) {
- next if $FN =~ $File::Find::skip_pattern;
- if ($subcount > 0 || $no_nlink) {
- # Seen all the subdirs?
- # check for directoriness.
- # stat is faster for a file in the current directory
- $sub_nlink = (lstat ($no_chdir ? $dir_pref . $FN : $FN))[3];
+ $File::Find::dir = /some/path/
+ $_ = foo.ext
+ $File::Find::name = /some/path/foo.ext
- if (-d _) {
- --$subcount;
- $FN =~ s/\.dir\z//i if $Is_VMS;
- # HACK: replace push to preserve dir traversal order
- #push @Stack,[$CdLvl,$dir_name,$FN,$sub_nlink];
- splice @Stack, $stack_top, 0,
- [$CdLvl,$dir_name,$FN,$sub_nlink];
- }
- else {
- $name = $dir_pref . $FN; # $File::Find::name
- $_= ($no_chdir ? $name : $FN); # $_
- { $wanted_callback->() }; # protect against wild "next"
- }
- }
- else {
- $name = $dir_pref . $FN; # $File::Find::name
- $_= ($no_chdir ? $name : $FN); # $_
- { $wanted_callback->() }; # protect against wild "next"
- }
- }
- }
- }
- continue {
- while ( defined ($SE = pop @Stack) ) {
- ($Level, $p_dir, $dir_rel, $nlink) = @$SE;
- if ($CdLvl > $Level && !$no_chdir) {
- my $tmp;
- if ($Is_VMS) {
- $tmp = '[' . ('-' x ($CdLvl-$Level)) . ']';
- }
- else {
- $tmp = join('/',('..') x ($CdLvl-$Level));
- }
- die "Can't cd to $tmp from $dir_name: $!"
- unless chdir ($tmp);
- $CdLvl = $Level;
- }
+You are chdir()'d to C<$File::Find::dir> when the function is called,
+unless C<no_chdir> was specified. Note that when changing to
+directories is in effect the root directory (F</>) is a somewhat
+special case inasmuch as the concatenation of C<$File::Find::dir>,
+C<'/'> and C<$_> is not literally equal to C<$File::Find::name>. The
+table below summarizes all variants:
- if ($Is_Win32) {
- $dir_name = ($p_dir =~ m{^(?:\w:[/\\]?|[/\\])$}
- ? "$p_dir$dir_rel" : "$p_dir/$dir_rel");
- $dir_pref = "$dir_name/";
- }
- elsif ($^O eq 'VMS') {
- if ($p_dir =~ m/[\]>]+$/) {
- $dir_name = $p_dir;
- $dir_name =~ s/([\]>]+)$/.$dir_rel$1/;
- $dir_pref = $dir_name;
- }
- else {
- $dir_name = "$p_dir/$dir_rel";
- $dir_pref = "$dir_name/";
- }
- }
- else {
- $dir_name = ($p_dir eq '/' ? "/$dir_rel" : "$p_dir/$dir_rel");
- $dir_pref = "$dir_name/";
- }
+ $File::Find::name $File::Find::dir $_
+ default / / .
+ no_chdir=>0 /etc / etc
+ /etc/x /etc x
- if ( $nlink == -2 ) {
- $name = $dir = $p_dir; # $File::Find::name / dir
- $_ = $File::Find::current_dir;
- $post_process->(); # End-of-directory processing
- }
- elsif ( $nlink < 0 ) { # must be finddepth, report dirname now
- $name = $dir_name;
- if ( substr($name,-2) eq '/.' ) {
- substr($name, length($name) == 2 ? -1 : -2) = '';
- }
- $dir = $p_dir;
- $_ = ($no_chdir ? $dir_name : $dir_rel );
- if ( substr($_,-2) eq '/.' ) {
- substr($_, length($_) == 2 ? -1 : -2) = '';
- }
- { $wanted_callback->() }; # protect against wild "next"
- }
- else {
- push @Stack,[$CdLvl,$p_dir,$dir_rel,-1] if $bydepth;
- last;
- }
- }
- }
-}
+ no_chdir=>1 / / /
+ /etc / /etc
+ /etc/x /etc /etc/x
-# API:
-# $wanted
-# $dir_loc : absolute location of a dir
-# $p_dir : "parent directory"
-# preconditions:
-# chdir (if not no_chdir) to dir
+When C<follow> or C<follow_fast> are in effect, there is
+also a C<$File::Find::fullname>. The function may set
+C<$File::Find::prune> to prune the tree unless C<bydepth> was
+specified. Unless C<follow> or C<follow_fast> is specified, for
+compatibility reasons (find.pl, find2perl) there are in addition the
+following globals available: C<$File::Find::topdir>,
+C<$File::Find::topdev>, C<$File::Find::topino>,
+C<$File::Find::topmode> and C<$File::Find::topnlink>.
-sub _find_dir_symlnk($$$) {
- my ($wanted, $dir_loc, $p_dir) = @_; # $dir_loc is the absolute directory
- my @Stack;
- my @filenames;
- my $new_loc;
- my $updir_loc = $dir_loc; # untainted parent directory
- my $SE = [];
- my $dir_name = $p_dir;
- my $dir_pref;
- my $loc_pref;
- my $dir_rel = $File::Find::current_dir;
- my $byd_flag; # flag for pending stack entry if $bydepth
- my $tainted = 0;
- my $ok = 1;
+This library is useful for the C<find2perl> tool, which when fed,
- $dir_pref = ( $p_dir eq '/' ? '/' : "$p_dir/" );
- $loc_pref = ( $dir_loc eq '/' ? '/' : "$dir_loc/" );
+ find2perl / -name .nfs\* -mtime +7 \
+ -exec rm -f {} \; -o -fstype nfs -prune
- local ($dir, $name, $fullname, $prune, *DIR);
+produces something like:
- unless ($no_chdir) {
- # untaint the topdir
- if (( $untaint ) && (is_tainted($dir_loc) )) {
- ( $updir_loc ) = $dir_loc =~ m|$untaint_pat|; # parent dir, now untainted
- # once untainted, $updir_loc is pushed on the stack (as parent directory);
- # hence, we don't need to untaint the parent directory every time we chdir
- # to it later
- unless (defined $updir_loc) {
- if ($untaint_skip == 0) {
- die "directory $dir_loc is still tainted";
- }
- else {
- return;
- }
- }
- }
- $ok = chdir($updir_loc) unless ($p_dir eq $File::Find::current_dir);
- unless ($ok) {
- warnings::warnif "Can't cd to $updir_loc: $!\n";
- return;
- }
+ sub wanted {
+ /^\.nfs.*\z/s &&
+ (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_)) &&
+ int(-M _) > 7 &&
+ unlink($_)
+ ||
+ ($nlink || (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_))) &&
+ $dev < 0 &&
+ ($File::Find::prune = 1);
}
- push @Stack,[$dir_loc,$updir_loc,$p_dir,$dir_rel,-1] if $bydepth;
+Notice the C<_> in the above C<int(-M _)>: the C<_> is a magical
+filehandle that caches the information from the preceding
+C<stat()>, C<lstat()>, or filetest.
- while (defined $SE) {
+Here's another interesting wanted function. It will find all symbolic
+links that don't resolve:
+
+ sub wanted {
+ -l && !-e && print "bogus link: $File::Find::name\n";
+ }
+
+Note that you may mix directories and (non-directory) files in the list of
+directories to be searched by the C<wanted()> function.
- unless ($bydepth) {
- # change (back) to parent directory (always untainted)
- unless ($no_chdir) {
- unless (chdir $updir_loc) {
- warnings::warnif "Can't cd to $updir_loc: $!\n";
- next;
- }
- }
- $dir= $p_dir; # $File::Find::dir
- $name= $dir_name; # $File::Find::name
- $_= ($no_chdir ? $dir_name : $dir_rel ); # $_
- $fullname= $dir_loc; # $File::Find::fullname
- # prune may happen here
- $prune= 0;
- lstat($_); # make sure file tests with '_' work
- { $wanted_callback->() }; # protect against wild "next"
- next if $prune;
- }
+ find(\&wanted, "./foo", "./bar", "./baz/epsilon");
- # change to that directory
- unless ($no_chdir || ($dir_rel eq $File::Find::current_dir)) {
- $updir_loc = $dir_loc;
- if ( ($untaint) && (($tainted) || ($tainted = is_tainted($dir_loc) )) ) {
- # untaint $dir_loc, what will be pushed on the stack as (untainted) parent dir
- ( $updir_loc ) = $dir_loc =~ m|$untaint_pat|;
- unless (defined $updir_loc) {
- if ($untaint_skip == 0) {
- die "directory $dir_loc is still tainted";
- }
- else {
- next;
- }
- }
- }
- unless (chdir $updir_loc) {
- warnings::warnif "Can't cd to $updir_loc: $!\n";
- next;
- }
- }
+In the example above, no file in F<./baz/> other than F<./baz/epsilon> will be
+evaluated by C<wanted()>.
- $dir = $dir_name; # $File::Find::dir
+See also the script C<pfind> on CPAN for a nice application of this
+module.
- # Get the list of files in the current directory.
- unless (opendir DIR, ($no_chdir ? $dir_loc : $File::Find::current_dir)) {
- warnings::warnif "Can't opendir($dir_loc): $!\n";
- next;
- }
- @filenames = readdir DIR;
- closedir(DIR);
+=head1 WARNINGS
- for my $FN (@filenames) {
- if ($Is_VMS) {
- # Big hammer here - Compensate for VMS trailing . and .dir
- # No win situation until this is changed, but this
- # will handle the majority of the cases with breaking the fewest.
+If you run your program with the C<-w> switch, or if you use the
+C<warnings> pragma, File::Find will report warnings for several weird
+situations. You can disable these warnings by putting the statement
- $FN =~ s/\.dir\z//i;
- $FN =~ s#\.$## if ($FN ne '.');
- }
- next if $FN =~ $File::Find::skip_pattern;
+ no warnings 'File::Find';
- # follow symbolic links / do an lstat
- $new_loc = Follow_SymLink($loc_pref.$FN);
+in the appropriate scope. See L<warnings> for more info about lexical
+warnings.
- # ignore if invalid symlink
- unless (defined $new_loc) {
- if (!defined -l _ && $dangling_symlinks) {
- $fullname = undef;
- if (ref $dangling_symlinks eq 'CODE') {
- $dangling_symlinks->($FN, $dir_pref);
- } else {
- warnings::warnif "$dir_pref$FN is a dangling symbolic link\n";
- }
- }
- else {
- $fullname = $loc_pref . $FN;
- }
- $name = $dir_pref . $FN;
- $_ = ($no_chdir ? $name : $FN);
- { $wanted_callback->() };
- next;
- }
+=head1 CAVEAT
- if (-d _) {
- if ($Is_VMS) {
- $FN =~ s/\.dir\z//i;
- $FN =~ s#\.$## if ($FN ne '.');
- $new_loc =~ s/\.dir\z//i;
- $new_loc =~ s#\.$## if ($new_loc ne '.');
- }
- push @Stack,[$new_loc,$updir_loc,$dir_name,$FN,1];
- }
- else {
- $fullname = $new_loc; # $File::Find::fullname
- $name = $dir_pref . $FN; # $File::Find::name
- $_ = ($no_chdir ? $name : $FN); # $_
- { $wanted_callback->() }; # protect against wild "next"
- }
- }
+=over 2
- }
- continue {
- while (defined($SE = pop @Stack)) {
- ($dir_loc, $updir_loc, $p_dir, $dir_rel, $byd_flag) = @$SE;
- $dir_name = ($p_dir eq '/' ? "/$dir_rel" : "$p_dir/$dir_rel");
- $dir_pref = "$dir_name/";
- $loc_pref = "$dir_loc/";
- if ( $byd_flag < 0 ) { # must be finddepth, report dirname now
- unless ($no_chdir || ($dir_rel eq $File::Find::current_dir)) {
- unless (chdir $updir_loc) { # $updir_loc (parent dir) is always untainted
- warnings::warnif "Can't cd to $updir_loc: $!\n";
- next;
- }
- }
- $fullname = $dir_loc; # $File::Find::fullname
- $name = $dir_name; # $File::Find::name
- if ( substr($name,-2) eq '/.' ) {
- substr($name, length($name) == 2 ? -1 : -2) = ''; # $File::Find::name
- }
- $dir = $p_dir; # $File::Find::dir
- $_ = ($no_chdir ? $dir_name : $dir_rel); # $_
- if ( substr($_,-2) eq '/.' ) {
- substr($_, length($_) == 2 ? -1 : -2) = '';
- }
+=item $dont_use_nlink
- lstat($_); # make sure file tests with '_' work
- { $wanted_callback->() }; # protect against wild "next"
- }
- else {
- push @Stack,[$dir_loc, $updir_loc, $p_dir, $dir_rel,-1] if $bydepth;
- last;
- }
- }
- }
-}
+You can set the variable C<$File::Find::dont_use_nlink> to 1, if you want to
+force File::Find to always stat directories. This was used for file systems
+that do not have an C<nlink> count matching the number of sub-directories.
+Examples are ISO-9660 (CD-ROM), AFS, HPFS (OS/2 file system), FAT (DOS file
+system) and a couple of others.
+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.
-sub wrap_wanted {
- my $wanted = shift;
- if ( ref($wanted) eq 'HASH' ) {
- # RT #122547
- my %valid_options = map {$_ => 1} qw(
- wanted
- bydepth
- preprocess
- postprocess
- follow
- follow_fast
- follow_skip
- dangling_symlinks
- no_chdir
- untaint
- untaint_pattern
- untaint_skip
- );
- my @invalid_options = ();
- for my $v (keys %{$wanted}) {
- push @invalid_options, $v unless exists $valid_options{$v};
- }
- warn "Invalid option(s): @invalid_options" if @invalid_options;
+If you do set C<$File::Find::dont_use_nlink> to 1, you will notice slow-downs.
- unless( exists $wanted->{wanted} and ref( $wanted->{wanted} ) eq 'CODE' ) {
- die 'no &wanted subroutine given';
- }
- if ( $wanted->{follow} || $wanted->{follow_fast}) {
- $wanted->{follow_skip} = 1 unless defined $wanted->{follow_skip};
- }
- if ( $wanted->{untaint} ) {
- $wanted->{untaint_pattern} = $File::Find::untaint_pattern
- unless defined $wanted->{untaint_pattern};
- $wanted->{untaint_skip} = 0 unless defined $wanted->{untaint_skip};
- }
- return $wanted;
- }
- elsif( ref( $wanted ) eq 'CODE' ) {
- return { wanted => $wanted };
- }
- else {
- die 'no &wanted subroutine given';
- }
-}
+=item symlinks
-sub find {
- my $wanted = shift;
- _find_opt(wrap_wanted($wanted), @_);
-}
+Be aware that the option to follow symbolic links can be dangerous.
+Depending on the structure of the directory tree (including symbolic
+links to directories) you might traverse a given (physical) directory
+more than once (only if C<follow_fast> is in effect).
+Furthermore, deleting or changing files in a symbolically linked directory
+might cause very unpleasant surprises, since you delete or change files
+in an unknown directory.
-sub finddepth {
- my $wanted = wrap_wanted(shift);
- $wanted->{bydepth} = 1;
- _find_opt($wanted, @_);
-}
+=back
-# default
-$File::Find::skip_pattern = qr/^\.{1,2}\z/;
-$File::Find::untaint_pattern = qr|^([-+@\w./]+)$|;
+=head1 BUGS AND CAVEATS
-# These are hard-coded for now, but may move to hint files.
-if ($^O eq 'VMS') {
- $Is_VMS = 1;
- $File::Find::dont_use_nlink = 1;
-}
-elsif ($^O eq 'MSWin32') {
- $Is_Win32 = 1;
-}
+Despite the name of the C<finddepth()> function, both C<find()> and
+C<finddepth()> perform a depth-first search of the directory
+hierarchy.
-# this _should_ work properly on all platforms
-# where File::Find can be expected to work
-$File::Find::current_dir = File::Spec->curdir || '.';
+=head1 HISTORY
-$File::Find::dont_use_nlink = 1
- if $^O eq 'os2' || $^O eq 'dos' || $^O eq 'amigaos' || $Is_Win32 ||
- $^O eq 'interix' || $^O eq 'cygwin' || $^O eq 'qnx' || $^O eq 'nto';
+File::Find used to produce incorrect results if called recursively.
+During the development of perl 5.8 this bug was fixed.
+The first fixed version of File::Find was 1.01.
-# Set dont_use_nlink in your hint file if your system's stat doesn't
-# report the number of links in a directory as an indication
-# of the number of files.
-# See, e.g. hints/machten.sh for MachTen 2.2.
-unless ($File::Find::dont_use_nlink) {
- require Config;
- $File::Find::dont_use_nlink = 1 if ($Config::Config{'dont_use_nlink'});
-}
+=head1 SEE ALSO
-# We need a function that checks if a scalar is tainted. Either use the
-# Scalar::Util module's tainted() function or our (slower) pure Perl
-# fallback is_tainted_pp()
-{
- local $@;
- eval { require Scalar::Util };
- *is_tainted = $@ ? \&is_tainted_pp : \&Scalar::Util::tainted;
-}
+find, find2perl.
-1;
+=cut