Archive for the 'perl' Category

Scalar::Util and x86_64

Sunday, July 6th, 2008

Recently (28 June 08), my 64-bit CentOS machine started sending me error reports from cron jobs running perl scripts that had been running fine until now. There was a problem coming from Scalar::Util:

Use of uninitialized value in concatenation (.) or string at /usr/lib64/perl5/5.8.8/x86_64-linux-thread-multi/Scalar/Util.pm line 30

I’ve seen this problem before, as on this comp.lang.perl.misc post from Tim Boyer.

A force re-install of the Scalar::Util package in cpan does the trick:

cpan> force install Scalar::Util

However, while looking at Scalar/Util.pm around line 30, I found another bug which was hinted at by the first bug, but still only runs if there really is something to croak about:

Scalar/Util.pm version 1.19 lines 28-31:

if (grep { /^(dualvar|set_prototype)$/ } @_ ) {
    require Carp;
    Carp::croak("$1 is only available with the XS version");
}

If the array @_ contains either ‘dualvar’ or ‘set_prototype’, croak should be called with $1 filled with the string that matched. Unfortunately, this doesn’t happen. The ‘grep { /pattern/ } @_’ construct returns an array of values from @_ that matches the pattern. The if statement around it is true if the array returned from grep contains at least one element, meaning at least one element in @_ matched the /pattern/. Capturing within this construct does not work outside the { /pattern/ } block, and therefore the $1 will not get filled with the first (or any) string that matched the pattern.

This could be fixed by separating tests, as follows, although there is probably a much sexier way to do it:

if (grep { /^dualvar$/ } @_ {
    require Carp;
    Carp::croak("dualvar is only available with the XS version");
}
if (grep { /^set_prototype$/ } @_ {
    require Carp;
    Carp::croak("set_prototype is only available with the XS version");
}

I’ve emailed the author of Scalar::Util to see if this change can be made.

19 July 2008: Update
I found the proper place to report bugs in perl modules available on cpan, which is http://rt.cpan.org. Then I found a previous bug report that was for this exact problem.

It’s at: http://rt.cpan.org/Public/Bug/Display.html?id=31054

Archive::Zip reading from a scalar

Thursday, March 27th, 2008

Recently I spent a day trying to get Archive::Zip to open an existing zip file which was already loaded into memory (and stored in a scalar variable). It just wouldn’t work, so I tried the simplest possible example in a separate file, and it still wouldn’t work. Eventually I discovered a solution, though not perfect, it works for me, and I hope you can use it.

The simplest example of the functionality was included as one of the example files, readScalar.pl. This file uses IO::File to load a zip file into a scalar variable, and then uses IO::Scalar to open a seekable filehandle to that scalar which Archive::Zip can use with it’s readFromFileHandle method to load in the zip file.

Unfortunately, even this example wasn’t running on both my Mac OS X box or my CentOS box. I was getting an error that looked like this:

error: file not seekable
at /Library/Perl/5.8.6/Archive/Zip/Archive.pm line 437
Archive::Zip::Archive::readFromFileHandle('Archive::Zip::Archive=HASH(0x18e0700)', 'IO::Scalar=GLOB(0x1917dd8)') called at ./readScalar.pl line 20

After some Google searching, I came upon some bug reports on CPAN that dealt with this:

http://rt.cpan.org/Public/Bug/Display.html?id=7855
http://rt.cpan.org/Public/Bug/Display.html?id=7633

They both promised that the problem would be fixed in the next major release of Archive::Zip, but here we are, a couple years later, and it still doesn’t work.

The solution that worked for me was a combination of the user-submitted fixes. At the end of my perl script, I overrided Archive::Zip::Archive’s _isSeekable method with code suggested by NEDKONZ, and it looks like this:


# Override the _isSeekable function in Archive::Zip
no warnings 'redefine';
package Archive::Zip::Archive;
sub _isSeekable {
    my $fh = shift;
    if ( UNIVERSAL::isa( $fh, 'IO::Scalar' ) )
    {
        return $] >= 5.006;
    }
    elsif ( UNIVERSAL::isa( $fh, 'IO::String' ) )
    {
        return $] >= 5.006;
    }
    elsif ( UNIVERSAL::can( $fh, 'stat') )
    {
        return -f $fh;
    }
    return UNIVERSAL::can( $fh, 'seek');
}
use warnings 'all';

This function living in the bottom of my code ended the “error: file not seekable” problem. Of course, you could also just edit your Archive/Zip/Archive.pm file and fix it forever, rather than including this function in every file that needs to read a Zip file from memory.