Parent Directory
|
Revision Log
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 |