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

Brethren

I was running Pod::Find::pod_find() to list pod-containing files in C:/perl/bin (after I installed the pmtools ), and I expected to see several .bat files in the list. But there were none, although several of them contain pod (e.g. pod2html.bat).

I looked into Pod::Find, drilled down into _check_and_extract_name() and I found the code ...
sub _check_and_extract_name { my ($file, $verbose, $root_rx) = @_; # check extension or executable flag # this involves testing the .bat extension on Win32! unless(-f $file && -T _ && ($file =~ /\.(pod|pm|plx?)\z/i || -x _ +)) { return undef; } return undef unless contains_pod($file,$verbose); # ... proceed with adding the accepted file to the lost of pods .. +.
... that decides to accept or reject a file, based on the extension and on file tests. A .bat file being a -f, -T and -x, it should be accepted, from reading above unless clause.

But this is not what happens - .bat files are rejected.

After some experimenting I found two workarounds that fix the problem:
# change the order of terms in && expression unless(-f $file _ && ($file =~ /\.(pod|pm|plx?)\z/i || -x _ ) && - +T) { return undef; # replace the _ variable by $file unless(-f $file && -T _ && ($file =~ /\.(pod|pm|plx?)\z/i || -x $f +ile )) { return undef;
I experimented some more and found that the malfunction of -x _ occurs inside a logical expression, but not in a series of simple statements. The script below demonstrates these findings.

Can anyone confirm my findings or tell me if I am mistaken somehow?
If the bug is confirmed, should I ask the module author (marek@saftsack.fs.uni-bayreuth.de) to add the workaround, or report the bug to ActiveState, or to someone else?

Rudif

#!perl -w use strict; use Pod::Find; # # Test Pod::Find::pod_find on C:/perl/bin # my $dir = 'C:/perl/bin'; my %pods = Pod::Find::pod_find({ -verbose => 1}, $dir); my @pods = keys %pods; my $n = keys %pods; printf "Found $n files containing pods: @pods\n"; printf "NOTE 1: above finds .pl but not .bat files containing pods\n\n +"; # # Test using _ variable in filestests # my $bat = 'c:/perl/bin/pod2html.bat'; my $exe = 'c:/perl/bin/perl.exe'; filetest ($bat); filetest ($exe); printf "NOTE 2: above shows that -x _ basically works as advertized\n\ +n"; sub filetest { my $file = shift; print "$file: "; stat($file); print " Readable" if -r _; print " Writable" if -w _; print " Executable" if -x _; print " Text" if -T _; print " Binary" if -B _; print "\n"; } # # Demo the bug and workarounds # # simplified from Pod::Find, -x _ does not work printf "1 $bat ok for pod: %d\n", (-f $bat && -T _ && -x _); # 4 workarounds printf "2 $bat ok for pod: %d\n", (-f $bat && -x _ && -T _); printf "3 $bat ok for pod: %d\n", (-f $bat && -x $bat && -T _); printf "4 $bat ok for pod: %d\n", (-f $bat && -T _ && -x $bat); printf "5 $bat ok for pod: %d\n", (-f $bat && -T $bat && -x $bat); printf "NOTE 3: above shows that -x _ does not work as advertized in c +ase 1\n\n";

Replies are listed 'Best First'.
Re: Problem with filetest -x _ on Win2k AS Perl build 626
by bikeNomad (Priest) on Jun 25, 2001 at 00:44 UTC
    in POD::Find version 0.12, the file tests are in your suggested order:

    unless($file =~ /\.(pod|pm|plx?)\z/i || (-f $file && -x _ && -T _))

    And with these, it works fine on my system.

      >> in POD::Find version 0.12, the file tests are in your suggested order:

      Oops, I forgot to mention my version of Pod::Find - it is 0.21 and it came in the current distribution of Pod-Parser-1.18 on CPAN. The file Pod/Find.pm is dated 1-Sep-2000.

      Is your version 0.12 much older? And is it POD or Pod?

      Anyway, what is bugging me is that changing the order of evaluation of the 3 terms in the && expression can change the boolean result - this looks like a bug to me.

      What happens if you change your

      unless($file =~ /\.(pod|pm|plx?)\z/i || (-f $file && -x _ && -T _))
      to
      unless($file =~ /\.(pod|pm|plx?)\z/i || (-f $file && -T _ && -x _ ))
      - does it change the boolean result?

      Rudif

        Version 0.12 came with 5.6.0, apparently. Version 0.21 came with 5.6.1. And it's Pod::Find, except that dumb Windoze doesn't care. One of the differences between the two versions:

        # check extension or executable flag # this involves testing the .bat extension on Win32! - unless($file =~ /\.(pod|pm|plx?)\z/i || (-f $file && -x _ && -T _ +)) { - return undef; + unless(-f $file && -T _ && ($file =~ /\.(pod|pm|plx?)\z/i || -x _ + )) { + return undef; }

        When I ran your test code, I got a 0 for the first test, and a 1 for subsequent tests. I don't feel like firing up NT again to check it, but I think turning the test around would fix it.

Re: Problem with filetest -x _ on Win2k AS Perl build 626
by Rudif (Hermit) on Jun 26, 2001 at 17:31 UTC
    Update

    I investigated this problem some more. I ran the script below on Win2k (problem present) and on Linux (problem absent).

    Question to experienced monks: Where should I report the bug? ActiveState, or perl 5 porters?

    Rudif

    #! /usr/bin/perl -w use strict; # # This script checks the behavior of filetest -x _ # and demonstrates a problem in Win2k where filetest2() and filetest3( +) below # fail to classify the 2 test files (a script and the perl interpreter +) as executable. # By rudif@bluemail.ch 26 Jun 2001 # my ($script, $exe); if ($^O =~ /Win/) { # assume the usual directory - please edit if different on your ma +chine ($script, $exe) = qw ( c:/perl/bin/pod2html.bat c:/perl/bin/perl.e +xe ); } else { # assume *nix # assume the usual directory - please edit if different on your ma +chine ($script, $exe) = qw ( /usr/bin/pod2html /usr/bin/perl ); } die "no such file $script" unless -f $script; die "no such file $exe" unless -f $exe; printf "OS $^O, perl %vd\n\n", $^V; compare(filetest0($exe), filetest1($exe)); compare(filetest0($exe), filetest2($exe)); compare(filetest0($exe), filetest3($exe)); compare(filetest0($exe), filetest4($exe)); print "\n"; compare(filetest0($script), filetest1($script)); compare(filetest0($script), filetest2($script)); compare(filetest0($script), filetest3($script)); compare(filetest0($script), filetest4($script)); print "\n"; sub filetest0 { # reference - not using _ my $file = shift; my @props; push @props, "Readable" if -r $file; push @props, "Writable" if -w $file; push @props, "Executable" if -x $file; push @props, "Binary" if -B $file; push @props, "Text" if -T $file; join ' ', "filetest0 $file: ", sort @props; } sub filetest1 { my $file = shift; stat($file); my @props; push @props, "Readable" if -r _; push @props, "Writable" if -w _; push @props, "Executable" if -x _; # before -B and -T push @props, "Binary" if -B _; push @props, "Text" if -T _; join ' ', "filetest1 $file: ", sort @props; } sub filetest2 { my $file = shift; stat($file); my @props; push @props, "Readable" if -r _; push @props, "Writable" if -w _; push @props, "Text" if -T _; push @props, "Executable" if -x _; # after -T _ push @props, "Binary" if -B _; join ' ', "filetest2 $file: ", sort @props; } sub filetest3 { my $file = shift; stat($file); my @props; push @props, "Readable" if -r _; push @props, "Writable" if -w _; push @props, "Text" if -T $file; # after -T $file push @props, "Executable" if -x _; push @props, "Binary" if -B _; join ' ', "filetest3 $file: ", sort @props; } sub filetest4 { my $file = shift; stat($file); my @props; push @props, "Readable" if -r _; push @props, "Writable" if -w _; push @props, "Text" if -T _; push @props, "Executable" if -x $file; # not using _ push @props, "Binary" if -B _; join ' ', "filetest4 $file: ", sort @props; } sub compare { my ($ref, $other) = @_; (my $_ref = $ref) =~ s/.*://; (my $_other = $other) =~ s/.*://; if ($_ref eq $_other) { printf " ok $other\n", } else { printf "not ok $other\n", } } __END__ # output on Win2k OS MSWin32, perl 5.6.1 ok filetest1 c:/perl/bin/perl.exe: Binary Executable Readable Wri +table not ok filetest2 c:/perl/bin/perl.exe: Binary Readable Writable not ok filetest3 c:/perl/bin/perl.exe: Binary Readable Writable ok filetest4 c:/perl/bin/perl.exe: Binary Executable Readable Wri +table ok filetest1 c:/perl/bin/pod2html.bat: Executable Readable Text W +ritable not ok filetest2 c:/perl/bin/pod2html.bat: Readable Text Writable not ok filetest3 c:/perl/bin/pod2html.bat: Readable Text Writable ok filetest4 c:/perl/bin/pod2html.bat: Executable Readable Text W +ritable
      >> Question to experienced monks: Where should I report the bug? ActiveState, or perl 5 porters? Rudif mumbling to himself ...

      OK, I submitted a bug report to perlbug@perl.com, where it received ID 20010627.004.

      To investigate the problem further, I built the Perl locally (I had to tweak the makefile to get the symbols) and stepped in with a debugger (windbg). My conclusion: Microsoft implementation of C function _fstat() fails to set the 'Executable' bit correctly for an executable file, while their function _stat() does it correctly. The Perl implemetation of stat() calls _stat(), while the implementation of -T _ calls the rogue _fstat().

      Below is my followup report to perl5porters. I hope I won't get flamed for posting C code along with perl code.

      Rudif

      Re ID 20010627.004 : I have a diagnostic

      I investigated some more the bug 20010627.004 and I have a diagnostic.

      1. Summary

      I found that the failure of Perl filetest operator -x _ on Win2k to report an executable file as executable after a -T _ is due to discrepancy in st_mode values returned by Microsoft functions _stat() and _fstat(). Specifically, I found that _stat() sets the 3 Execute bits (mask 0111 octal) to 1 when it sees an executable file, while _fstat() sets these bits to 0 when looking at the same file.

      The C program below demonstrates.

      2. Simplified test case

      Here is my simplified perl script that demonstrates the problem, in Active Perl build 626 as well as in my local build of Perl from sources currently (Jun 2001) distributed by ActiveState:

      #!perl -w use strict; my $exe = '../perl.exe'; stat($exe); printf "BAD $exe text=%d, executable=%d\n", -T _, -x _; stat($exe); printf "OK $exe executable=%d, text=%d\n", -x _, -T _; stat($exe); printf "OK $exe text=%d, executable=%d\n", -T _, -x $exe; __END__ BAD ../perl.exe text=0, executable=0 OK ../perl.exe executable=1, text=0 OK ../perl.exe text=0, executable=1
      It demonstrates that doing -T _ after stat() and before -x _ produces erroneous -x result.

      3. Results of my investigation

      I looked into the implementation of Perl stat() and -T _. In fact, I ran the debugger windbg on my local build while running above test script.

      I found this:

      Perl_pp_stat() in pp_sys.c implements Perl stat() calls PerlLIONameStat() calls win32_stat() calls stat(path, sbuf) // Win32 library call sbuf.st_mode == 0100777 octal, for perl.exe, GOOD Perl_pp_fttext() in pp_sys.c implements Perl -T calls PerlLIOFileStat() calls win32_fstat() calls my_fstat() calls fstat(fd, sbuf) // Win32 library call sbuf.st_mode == 0100666 octal, for perl.exe, BAD
      which explains the misbehavior that I am complaining about.

      I wrote a C test program that demonstrates the misbehavior (IMO) of Win32 fstat():

      // _stattest.cpp : compare MS implementation of functions stat() and f +stat() // rudif@bluemail.ch 1 Jul 2001 #include <time.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <io.h> #include <fcntl.h> void main( void ) { const char *filename = "c:\\perl\\bin\\perl.exe"; printf( "File name : %s\n", filename ); { static struct stat buf; // initializes to 0 if ( stat( filename, &buf ) != 0 ) perror( "Problem with stat()" ); else { printf( "\nFrom MSDN page on _stat():\n" " Get status information on a file.\n" " st_mode\n" " Bit mask for file-mode information.\n" " The _S_IFDIR bit is set if path specifies a direc +tory;\n" " the _S_IFREG bit is set if path specifies an ordi +nary file or a device.\n" " User read/write bits are set according to the fil +e’s permission mode;\n" " user execute bits are set according to the filena +me extension.\n" ); printf( "Mode (oct) : 0%06o\n", (unsigned short)buf +.st_mode ); printf( "Mode (hex) : 0x%04x\n", (unsigned short)bu +f.st_mode ); } } { int fh; static struct stat buf; // initializes to 0 if ( (fh = open(filename, _O_RDONLY)) == -1 ) perror( "Problem with open()" ); else if ( fstat( fh, &buf ) != 0 ) perror( "Problem with fstat()" ); else { printf( "\nFrom MSDN page on _fstat():\n" " Get information about an open file.\n" " st_mode\n" " Bit mask for file-mode information.\n" " The _S_IFCHR bit is set if handle refers to a devi +ce.\n" " The _S_IFREG bit is set if handle refers to an ord +inary file.\n" " The read/write bits are set according to the file' +s permission mode.\n" " _S_IFCHR and other constants are defined in SYS\\S +TAT.H.\n" ); printf( "Mode (oct) : 0%06o\n", (unsigned short)buf +.st_mode ); printf( "Mode (hex) : 0x%04x\n", (unsigned short)bu +f.st_mode ); } } }

      I included wording from MSDN doc pages on _stat() and _fstat(). The _stat() doc says that "user execute bits are set according to the filename extension.", while the _fstat() doc does NOT mention the "user execute bits".

      My observation is that _stat() does what the doc says, while _fstat() silently sets the "user execute bits" to 0. OUCH.

      I have found that above code compiles and produces that same results when I replace

      stat() by _stat() fstat() by _fstat() struct stat by struct _stat
      The later forms are documented in MSDN, while the former are not AFAICS. I am not clear on how does this relate (or not) to the ANSI C standard - IANAL.

      HTH

      Rudi Farkas rudif@bluemail.ch