Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I have two questions with the following code: The first questions is in regards to the error that is generated at the bottom of this post, does anyone know why this is, and or how to correct it?, and the second question, does anyone know how to exit the Find routine without exiting the program, I'm thinking about eval, but I'm not so sure how it will work with my sub-routine.
#!/C:/perl -w use strict; use File::Find; use Win32API::File qw( getLogicalDrives GetVolumeInformation ); my @drives; use vars qw/*name *dir *prune/; *name = *File::Find::name; *dir = *File::Find::dir; &LocalDrives; foreach my $drive ( @drives ) { File::Find::find(\&wanted, $drive); exit; sub wanted { if (/^VCVARS32.BAT\z/s) { $name =~ s/\///; $name =~ s/\//\\/g; print $name,"\n"; } } } sub LocalDrives { my @scan = getLogicalDrives(); foreach my $d ( @scan ) { my @x= (undef)x7; GetVolumeInformation( $d, @x ); if ($x[0] =~ /Local Disk/) { push @drives, $d; } } }
Error message: Can't opendir(C:\/System Volume Information): Invalid argument Thanks!

Replies are listed 'Best First'.
Re: Two questions (two birds with one stone)
by jryan (Vicar) on Jun 08, 2002 at 00:33 UTC

    Well, if I remember right, I think the system's volume info is restricted to administrator access, but I'm not sure. That section of your snippet worked on my computer; however, I'm running as Administrator so I'm not sure.

    Next, a few suggestions. In your wanted subroutine, you use an if statement like this:

    if (/^VCVARS32.BAT\z/s) { ... }

    I'm pretty sure this is not what you want. /s makes . match any character INCLUDING newlines, and there most certainly won't be a newline where the . is. Try rewriting it as so:

    if (/^VCVARS32\.BAT$/) { ... }

    Next, your drive checking if statement seems a bit shiesty. Instead of checking Volume name ($x[0]), you might want to check Volume Serial Number (in your case, will be $x[2]) instead for more reliable results.

    Since you are declaring the wanted subroutine inside the foreach loop, perl will scope the sub to that loop; that means it will be recreated at each pass. That will make it slow, so move it out. Finally, I assume the exit is there only for testing purposes...

Re: Two questions (two birds with one stone)
by braughing (Sexton) on Jun 08, 2002 at 00:16 UTC
    Access to 'C:\System Volume Information' is restricted to administrators. See Util's node below.
Leaving File::Find early
by Util (Priest) on Jun 10, 2002 at 02:59 UTC
    1. Under NTFS, the directory 'System Volume Information' is always open and locked on an mounted filesystem. Administrator privilege will not help. Mounting the NTFS volume as read-only on a Linux box *might* allow you to read it. Those monks not getting the error are perhaps running FAT32 volumes. When coding File::Find under Win32, I always start the &wanted subroutine with this line:
      $File::Find::prune = 1, return if $File::Find::name =~ m{^.:/System Volume Information$};
    2. You can get out of the Find loop quickly by setting a flag when you want to stop, and then using the flag to set $prune and return for all future files. It is not immediate, but you only continue to check the files (not sub-directories) in the current directory, and each of its parents. If you *must* have an immediate exit (due to a slow NFS-mounted search, perhaps), then you could use the dreaded goto. It works just fine for this.

    As an example, the code below finds the first file over 100MB in size, and then gets out quickly. On my NTFS volume (19489 files), $extras was just 19. Uncomment the line to test the goto version.

    use File::Find; my @drives = ( 'c:/', 'd:/' ); my $kill_find = 0; my $extras = 0; find( sub { $extras++ if $kill_find; $File::Find::prune = 1, return if $File::Find::name =~ m{^.:/System Volume Information$}; $File::Find::prune = 1, return if $kill_find; return if -d $_; # print "$File::Find::name\n" and goto End_F if -s(_) > 100_000_000; print "$File::Find::name\n" and $kill_find=1 if -s(_) > 100_000_000; }, @drives ); End_F: print "Extra files or directories checked by this method: $extras\n";

    Meditation on skiping to the next dir using File::Find; may provide further enlightenment.

    Lastly, GetVolumeInformation does not return 'Local Disk' unless that happens to be the volume name you gave your disk when you formatted it. FWIW, here is how I would write the code you posted:

    #!/usr/bin/perl -w use strict; use warnings 'all'; use File::Find; use Win32API::File qw( :Func :DRIVE_ ); my @drives = map { tr{\\}{/}s; $_ } grep { GetDriveType($_) == DRIVE_FIXED } getLogicalDrives(); find( sub { $File::Find::prune = 1, return if $File::Find::name =~ m{^.:/System Volume Information$}; return unless /^VCVARS32\.BAT$/i; for ($File::Find::name) { # s{\/}{}; # tr{/}{\\}; print "$_\n"; } }, @drives );

Re: Two questions (two birds with one stone)
by Juerd (Abbot) on Jun 08, 2002 at 15:47 UTC

    does anyone know how to exit the Find routine without exiting the program, I'm thinking about eval, but I'm not so sure how it will work with my sub-routine.

    Have die "Stop\n" in your "wanted" routine, and eval the find call:

    eval { find(...) }; die $@ if $@ and $@ ne "Stop\n";

    - Yes, I reinvent wheels.
    - Spam: Visit eurotraQ.