[system] / trunk / webwork-modperl / bin / pip Repository:
ViewVC logotype

View of /trunk/webwork-modperl/bin/pip

Parent Directory Parent Directory | Revision Log Revision Log


Revision 432 - (download) (annotate)
Fri Jul 19 02:39:16 2002 UTC (10 years, 10 months ago) by sh002i
File size: 7988 byte(s)
added: math2img and support files... probably shouldn't distribute pip
with WeBWorK, but no one's going to complain, since it's in the public
domain (and that's one less dependancy ;)
-sam

    1 #!/usr/bin/perl -w
    2 # 
    3 # pip
    4 # 
    5 # Wrapper for silly programs that insist on reading from / writing to
    6 # files instead of using stdin and stdout.
    7 # 
    8 # Usage:
    9 # 
   10 # pip [-i|-o]... PROG ARGS...
   11 # 
   12 # where '-i' or '-o' means input or output.  The ARGS should contain
   13 # one or more arguments beginning with '-'; the first occurrence will
   14 # be replaced by a filename used as stdin or stdout.  If the argument
   15 # has extra characters after the '-', these will be used as the
   16 # extension for the filename generated.  Any further '-i' or '-o'
   17 # arguments will be processed in the same way.  Without these flags,
   18 # the command is run as-is without substitution.
   19 # 
   20 # For example:
   21 # 
   22 # pip -i netscape -
   23 # 
   24 # will read a file from standard input, and give it to netscape to
   25 # display.  The final commandline might be 'netscape /tmp/pip123.tmp'.
   26 # 
   27 # pip -o ppmtogif in.gif -
   28 # 
   29 # will run ppmtogif with the input file 'in.gif' and output filename
   30 # something like '/tmp/pip124.tmp'.  Whatever it outputs will then be
   31 # printed to stdout.  (In the case of ppmtogif, this script is not
   32 # necessary, but you get the idea.)
   33 # 
   34 # pip -i -o ppmtogif - -
   35 # 
   36 # will get a file from stdin, run it through ppmtogif, and print the
   37 # results to stdout.  (Putting the switches the other way round will
   38 # not work.)
   39 # 
   40 # If the program you're running uses filename extensions to decide
   41 # what to do (such as the C compiler which does different things with
   42 # files ending in '.c' and '.cc'), then you can make pip generate a
   43 # filename with the desired extension, for example:
   44 # 
   45 # pip -i cc -.c
   46 # 
   47 # which will feed the C compiler with a file ending in '.c'.  This
   48 # works for filename extensions beginning with a dot; an argument
   49 # like '-x' will not be substituted.  Similarly for DOSish programs
   50 # which insist that their output should have a certain extension, you
   51 # can use something like '-.arc' as the placeholder for -o.
   52 # 
   53 # Note that in all these cases, you must wait for all input to be
   54 # consumed, or all output to be produced, before it feeds through.
   55 # You don't get smooth, gradual piping.  (This could be done using
   56 # named pipes instead of plain files, but that would be harder.)
   57 # 
   58 # We also handle 'bunched' arguments, eg '-io'.
   59 # 
   60 # I have never used CP/M, but vaguely remember a command called 'pip'
   61 # which was probably nothing like this.
   62 # 
   63 # BUGS
   64 # 
   65 # Because we unlink leftover temporary files before exiting, and we
   66 # exit as soon as we finish waiting for the command to exit, any
   67 # command which puts itself into the background before opening its
   68 # input files (eg emacs under Windows) will not work.
   69 # 
   70 # This program is in the public domain.  Use at your own risk.
   71 # <http://www.doc.ic.ac.uk/~epa98/work/apps/pip/>.
   72 # 
   73 # Version 0.2.2
   74 # 
   75 # -- Ed Avis, epa98@doc.ic.ac.uk, 2001-01-22
   76 # 
   77 
   78 use strict;
   79 use IO::Handle;
   80 sub tmpnam();
   81 
   82 if (@ARGV < 1) {
   83     print STDERR "usage: $0 [-i|-o]... PROG ARGS...\n";
   84     exit(1);
   85 }
   86 
   87 # Split the arguments into flags, and the rest.
   88 my $flags = '';
   89 my @rest = ();
   90 
   91 # Get the flags into a big lump.
   92 while (my $arg = shift @ARGV) {
   93     if ($arg =~ /^-(.*)/) {
   94 	local $_ = $1;
   95 	if (tr/io//c) {
   96 	    die "bad flag $_, expected -i or -o";
   97 	}
   98 	elsif ($_ eq '') {
   99 	    die "argument '-' must come after name of program to run";
  100 	}
  101 	else {
  102 	    $flags .= $_;
  103 	}
  104     }
  105     else {
  106 	# Not a flag, push it back and finish.
  107 	unshift @ARGV, $arg;
  108 	last;
  109     }
  110 }
  111 
  112 @rest = @ARGV;
  113 die "no program specified, usage: $0 [-i|-o]... PROG ARGS..."
  114     if @rest == 0;
  115 
  116 my ($prog, @args) = @rest;
  117 my @tmpfiles = ();
  118 my @infiles = ();
  119 my @outfiles = ();
  120 my %type = ();
  121 
  122 # Go through all the flags, substituting filenames for '-'
  123 # arguments.
  124 # 
  125 my $flag;
  126 foreach $flag (split(//, $flags)) {
  127     my $found = 0;
  128     my $n;
  129     foreach $n (0 .. $#args) {
  130 	if ($args[$n] =~ /^-(.*)/) {
  131 	    my $ext = $1;
  132 	    next if ($ext ne '' and $ext !~ /^\./);
  133 	    my $tmpfile = tmpnam() . $ext;
  134 	    push @tmpfiles, $tmpfile;
  135 	    if ($flag eq 'i') {
  136 		push @infiles, $tmpfile;
  137 	    }
  138 	    elsif ($flag eq 'o') {
  139 		push @outfiles, $tmpfile;
  140 	    }
  141 	    else { die }
  142 
  143 	    $args[$n] = $tmpfile;
  144 	    $found = 1;
  145 	    last;
  146 	}
  147     }
  148     die "no '-' argument found for flag $flag" if not $found;
  149 }
  150 
  151 # Get stdin if necessary.  Each input file gets the same data.
  152 if (@infiles) {
  153     my @handles = ();
  154     foreach (@infiles) {
  155 	my $fh = new IO::Handle;
  156 	open ($fh, ">$_") or die "can't write to $_: $!";
  157 	push @handles, $fh;
  158     }
  159     
  160     while (<STDIN>) {
  161 	my $handle;
  162 	foreach $handle (@handles) {
  163 	    print $handle $_;
  164 	}
  165     }
  166   
  167     foreach (@handles) {
  168 	close $_;
  169     }
  170 }
  171 
  172 # Run the program.
  173 system($prog, @args);
  174 
  175 # Remove input files.
  176 foreach (@infiles) {
  177     (not -e $_) or unlink or die "cannot unlink $_: $!";
  178 }
  179 
  180 # Print output if necessary, and remove files.
  181 my $outfile;
  182 foreach $outfile (@outfiles) {
  183     unless (open (OUTFILE, $outfile)) {
  184 	if ($! =~ /^No such file or directory/
  185 	    and $outfile !~ m!\.[^/]*$!)
  186         {
  187 	    # Sometimes DOSish programs add an extension to the output
  188 	    # filename without being asked.  Sniff around and see if
  189 	    # we can find any evidence of this.
  190 	    # 
  191 	    if (-e $outfile) {
  192 		die "open() said $outfile doesn't exist, but it does";
  193 	    }
  194 	    my @poss = <$outfile.*>;
  195 	    if (@poss == 0) {
  196 		# Nope, nothing.
  197 		die "cannot open $outfile: $!";
  198 	    }
  199 	    elsif (@poss == 1) {
  200 		# It looks like the program has indeed created an
  201 		# output file with a silly name.
  202 		# 
  203 		my $o = $poss[0];
  204 		$o =~ /^$outfile(\..*)$/ or die;
  205 		my $ext = $1;
  206 		warn <<END;
  207 $prog has created the file '$o' instead of '$outfile'.  Perhaps you
  208 should have given the output placeholder as '-$1' instead of '-'?
  209 END
  210                 open (OUTFILE, $o) or die "cannot open $o: $!";
  211 		$outfile = $o;
  212 	    }
  213 	    else {
  214 		my $s = "$prog did not create $outfile, "
  215 		  . "but it did create: " . join(', ', @poss) . "\n"
  216 		    . "I can't handle this sort of thing, giving up";
  217 		die $s;
  218 	    }
  219 	}
  220 	else {
  221 	    die "cannot open $outfile: $!";
  222 	}
  223     }
  224                 
  225     while (<OUTFILE>) {
  226 	print;
  227     }
  228     close OUTFILE;
  229     unlink $outfile or die "cannot unlink $outfile: $!";
  230 }
  231 
  232 
  233 # tmpnam()
  234 # 
  235 # Return a name for a temporary file.  I would use the tmpnam()
  236 # included with Perl's POSIX module, but some programs from MS-DOS
  237 # backgrounds truncate any leafname with more than eight characters
  238 # before the dot.
  239 # 
  240 # Now POSIX::tmpnam() is insecure, because an attacker might guess the
  241 # filename it will pick and use a symlink attack.  But I'm not sure
  242 # whether this version suffers from the same problem, since it creates
  243 # an 0700 directory.  Actually, it does, because it will use an
  244 # existing directory with the right name rather than insisting on
  245 # creating its own.  So this routine is insecure, don't use it.  I
  246 # will fix this sometime.
  247 # 
  248 sub tmpnam() {
  249     die 'usage: tmpnam()' if @_;
  250 
  251     # Max. tries to think of a filename before giving up.  The number
  252     # of digits in $MAX_TRIES-1 is the max. number of characters in
  253     # the leafname.
  254     # 
  255     my $MAX_TRIES = 1000;
  256     
  257     use vars '$tmpdir'; # Global var. for temp directory
  258     $tmpdir = $ENV{TMP} || $ENV{TMPDIR} || $ENV{TEMP} || '/tmp';
  259     die "bad temp directory $tmpdir" if not -d $tmpdir;
  260 
  261     # Because of the limited length, we might not be able to put our
  262     # PID into the leafname, so to 'guarantee' uniqueness we make a
  263     # directory under $tmpdir corresponding to our PID.
  264     # 
  265     (-d "$tmpdir/$$")
  266       or mkdir "$tmpdir/$$", 0700
  267 	or die "cannot mkdir $tmpdir/$$: $!";
  268 
  269     # Pick an unused filename in this directory.  Race condition here
  270     # if you have two threads with the same PID.
  271     # 
  272     for (my $n = 0; $n < $MAX_TRIES; $n++) {
  273 	my $try = "$tmpdir/$$/$n";
  274 	next if -e $try or <$try.*>;
  275 	return $try;
  276     }
  277     die "$tmpdir/$$ is full up";
  278 }
  279 # And here's some code to clear up the mess tmpnam() leaves behind.
  280 END {
  281     (not defined $tmpdir)
  282       or (not -e "$tmpdir/$$")
  283 	or (rmdir "$tmpdir/$$")
  284 	  or warn "cannot rmdir $tmpdir/$$: $!";
  285 }

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9