+#
+# Read a directory and return the entries in sorted inode order.
+# This relies on the IO::Dirent module being installed. If not,
+# the inode data is empty and the default directory order is
+# returned.
+#
+# The returned data is a list of hashes with entries {name, type, inode, nlink}.
+# The returned data includes "." and "..".
+#
+# $need is a hash of file attributes we need: type, inode, or nlink.
+# If set, these parameters are added to the returned hash.
+#
+# To support browsing pre-3.0.0 backups where the charset encoding
+# is typically iso-8859-1, the charsetLegacy option can be set in
+# $need to convert the path from utf8 and convert the names to utf8.
+#
+# If IO::Dirent is successful if will get type and inode for free.
+# Otherwise, a stat is done on each file, which is more expensive.
+#
+sub dirRead
+{
+ my($bpc, $path, $need) = @_;
+ my(@entries, $addInode);
+
+ from_to($path, "utf8", $need->{charsetLegacy})
+ if ( $need->{charsetLegacy} ne "" );
+ return if ( !opendir(my $fh, $path) );
+ if ( $IODirentLoaded && !$IODirentOk ) {
+ #
+ # Make sure the IO::Dirent really works - some installs
+ # on certain file systems (eg: XFS) don't return a valid type.
+ #
+ if ( opendir(my $fh, $bpc->{TopDir}) ) {
+ my $dt_dir = eval("DT_DIR");
+ foreach my $e ( readdirent($fh) ) {
+ if ( $e->{name} eq "." && $e->{type} == $dt_dir ) {
+ $IODirentOk = 1;
+ last;
+ }
+ }
+ closedir($fh);
+ }
+ #
+ # if it isn't ok then don't check again.
+ #
+ $IODirentLoaded = 0 if ( !$IODirentOk );
+ }
+ if ( $IODirentOk ) {
+ @entries = sort({ $a->{inode} <=> $b->{inode} } readdirent($fh));
+ map { $_->{type} = 0 + $_->{type} } @entries; # make type numeric
+ } else {
+ @entries = map { { name => $_} } readdir($fh);
+ }
+ closedir($fh);
+ if ( defined($need) ) {
+ for ( my $i = 0 ; $i < @entries ; $i++ ) {
+ next if ( (!$need->{inode} || defined($entries[$i]{inode}))
+ && (!$need->{type} || defined($entries[$i]{type}))
+ && (!$need->{nlink} || defined($entries[$i]{nlink})) );
+ my @s = stat("$path/$entries[$i]{name}");
+ $entries[$i]{nlink} = $s[3] if ( $need->{nlink} );
+ if ( $need->{inode} && !defined($entries[$i]{inode}) ) {
+ $addInode = 1;
+ $entries[$i]{inode} = $s[1];
+ }
+ if ( $need->{type} && !defined($entries[$i]{type}) ) {
+ my $mode = S_IFMT($s[2]);
+ $entries[$i]{type} = BPC_DT_FIFO if ( S_ISFIFO($mode) );
+ $entries[$i]{type} = BPC_DT_CHR if ( S_ISCHR($mode) );
+ $entries[$i]{type} = BPC_DT_DIR if ( S_ISDIR($mode) );
+ $entries[$i]{type} = BPC_DT_BLK if ( S_ISBLK($mode) );
+ $entries[$i]{type} = BPC_DT_REG if ( S_ISREG($mode) );
+ $entries[$i]{type} = BPC_DT_LNK if ( S_ISLNK($mode) );
+ $entries[$i]{type} = BPC_DT_SOCK if ( S_ISSOCK($mode) );
+ }
+ }
+ }
+ #
+ # Sort the entries if inodes were added (the IO::Dirent case already
+ # sorted above)
+ #
+ @entries = sort({ $a->{inode} <=> $b->{inode} } @entries) if ( $addInode );
+ #
+ # for browing pre-3.0.0 backups, map iso-8859-1 to utf8 if requested
+ #
+ if ( $need->{charsetLegacy} ne "" ) {
+ for ( my $i = 0 ; $i < @entries ; $i++ ) {
+ from_to($entries[$i]{name}, $need->{charsetLegacy}, "utf8");
+ }
+ }
+ return \@entries;
+}
+
+#
+# Same as dirRead, but only returns the names (which will be sorted in
+# inode order if IO::Dirent is installed)
+#
+sub dirReadNames
+{
+ my($bpc, $path, $need) = @_;
+
+ my $entries = $bpc->dirRead($path, $need);
+ return if ( !defined($entries) );
+ my @names = map { $_->{name} } @$entries;
+ return \@names;
+}
+
+sub find
+{
+ my($bpc, $param, $dir, $dontDoCwd) = @_;
+
+ return if ( !chdir($dir) );
+ my $entries = $bpc->dirRead(".", {inode => 1, type => 1});
+ #print Dumper($entries);
+ foreach my $f ( @$entries ) {
+ next if ( $f->{name} eq ".." || $f->{name} eq "." && $dontDoCwd );
+ $param->{wanted}($f->{name}, "$dir/$f->{name}");
+ next if ( $f->{type} != BPC_DT_DIR || $f->{name} eq "." );
+ chdir($f->{name});
+ $bpc->find($param, "$dir/$f->{name}", 1);
+ return if ( !chdir("..") );
+ }
+}
+