Perl cross-platform compatibility
Anyone that knows me knows that my generally preferred language is Perl. (I’ll wait for the jeering to die down.)
Generally, this isn’t a bad thing, as Perl is remarkably cross-platform compatible. Unfortunately, Windows is an utter disaster in terms of compatibility with Unix based systems.
Working on a few Git (Introduction) related tools, such as a replacement for “cvs annotate”/”svn blame”, we’ve learned that the current best practices for doing safe pipe opens aren’t very compatible.
For example, to call a program like “ls”, you might do something like this:
open(PIPE, “-|”, “ls”);
while(<PIPE>) { do_something($_); }
close(PIPE);
Unfortunately, that fails on Perl 5.6, and on ActiveState (Any version, I believe.)
So on Perl 5.6, the solution is to instead do the two steps by hand:
my $pid = open my $kid, “-|”;
defined $pid or die “Cannot fork: $!”;
unless ($pid) {
exec “ls”;
die “Cannot exec ls: $!”;
}
while(< $kid>) { do_something($_) }
close($kid);
But this fails on ActiveState, because ActiveState doesn’t handle the forked pipe open well (ok, at all). ActiveState doesn’t appear to have a good solution for this, at all, so at this point, we’re forced to fall back on something like backquotes (“) or the easier to read qx(). (qx() is just an alternate form of backquotes, that gives you some control of how interpolation happens.
The problem with using qx() is that it is a backquote form, so it return a list of lines, not a filehandle. Since that’s a very different way to do things, and potentially a big performance hit on systems that don’t need the qx() work arounds, I forced myself to find a way to just hide the complexity:
sub open_pipe {
if ($^O eq ‘##INSERT_ACTIVESTATE_STRING_HERE##’) {
return open_pipe_activestate(@_);
} else {
return open_pipe_normal(@_);
}
}sub open_pipe_activestate {
tie *fh, “Git::ActiveStatePipe”, @_;
return *fh;
}sub open_pipe_normal {
my (@execlist) = @_;my $pid = open my $kid, “-|”;
defined $pid or die “Cannot fork: $!”;unless ($pid) {
exec @execlist;
die “Cannot exec @execlist: $!”;
}return $kid;
}package Git::ActiveStatePipe;
use strict;sub TIEHANDLE {
my ($class, @params) = @_;
my $cmdline = join ” “, @params;
my @data = qx{$cmdline};
bless { i => 0, data => \@data }, $class;
}sub READLINE {
my $self = shift;
if ($self->{i} >= scalar @{$self->{data}}) {
return undef;
}
return $self->{’data’}->[ $self->{i}++ ];
}sub CLOSE {
my $self = shift;
delete $self->{data};
delete $self->{i};
}sub EOF {
my $self = shift;
return ($self->{i} >= scalar @{$self->{data}});
}
It should, hopefully, be fairly obvious what is going on there.
For a normal system, we use the Perl 5.6 compatible method, and return a filehandle that works normally.
For an ActiveState system, we tie a glob to a special object that provides some very basic emulation of a filehandle, and internally, calls qx() and indexes across an array returning the data.
So, that’s how “git annotate” is going to be cross-platform compatible, at least, as of today, that is my plan.
Thanks to Randal Schwartz (merlyn) for providing part of the inspiration for this, and the rest of the people on the Git list that helped hash out various approaches to this that didn’t quite seem as clean or as nice as this one, in the long run.
I think this method keeps all the security advantages of the argument list forms, while actually still managing to work on crippled systems, so I’m fairly pleased with it.
No Comments »
No comments yet.
RSS feed for comments on this post. TrackBack URI
Leave a comment
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>