#!/usr/bin/perl # # print # # tool for spooling files # # Idea: Ready-to-print files are deposited in the spool directory # These must be in the right format. # The job of 'print' is to convert the file to the right format # and then copy it to the spool directory # use strict; use File::Copy; use Getopt::Std; use lib '/home/mjd/lib'; use MJD::Print; use vars qw($opt_t $opt_n $opt_v $opt_s $opt_b); umask 077; my @TMPFILES = (); # Options # -t TYPE assume file type is TYPE instead of heristic # -s do not spool or start printer daemon # -v verbose mode # -n number number of copies to print (default 1) # -b run in background, returning immediately getopts('n:vt:sb') or usage(); $opt_v ||= $opt_s; my $NCOPIES = $opt_n || 1; # my %CONF = load_conf("/etc/printd.conf"); my %CONF = load_conf($CONFIG); my $tempdir = mktempdir(); unless (@ARGV) { push @ARGV, save_stdin(); } if ($opt_b) { fork && exit; } 1; 1; 1; FILE: for my $file (@ARGV) { unless (-e $file) { warn "No such file: $file; skipping\n"; next; } my $type = defined($opt_t) ? $opt_t : file_type($file); while (is_compressed($type)) { warn "File is compressed\n" if $opt_v; $file = convert($file, 'uncompressed', decompress_command($type)); next FILE unless defined $file; $type = file_type($file); } if ($type eq '???') { die "I can't figure out what type of data is in that file.\n"; } my @path = conversion_path($type, $CONF{printable}); warn "Conversion path: @path\n" if $opt_v; unless (@path) { die "I don't know how to convert from file type $type to $CONF{printable}.\n"; } shift @path; # the first element is the source type for my $next_type (@path) { $file = convert($file, $next_type, $CONF{"$type\->$next_type"}); $type = $next_type; } spool($file) for 1 .. $NCOPIES; unlink @TMPFILES; @TMPFILES = (); } ################################################################ # # sub spool { if ($opt_s) { warn "(Not) spooling file\n" if $opt_v; return; } my $file = shift; my $spoolfile = make_spool_file_name(); my $dotfile = $spoolfile; unless (copy($file, "$CONF{spooldir}/$dotfile")) { die "Sorry, couldn't spool $file: $!; aborting"; } unless (rename "$CONF{spooldir}/$dotfile", "$CONF{spooldir}/$spoolfile.$CONF{printable}") { die "Sorry, couldn't finish spooling $file: $!; aborting"; } run_printd(); } my $Q; sub make_spool_file_name { join ".", $<, time(), $$, ++$Q; } sub run_printd { warn "*** Starting daemon: $CONF{daemon}\n" if $opt_v; fork && return; exec $CONF{daemon}; die "Couldn't exec daemon $CONF{daemon}: $!\n"; } sub tempfile { my $ext = shift; join ".", "$tempdir/tmp", int(rand(10000)), $ext; } sub mktempdir { my $Q; my $base_tempdir = "$CONF{temp}/$<.$$"; my $tempdir = $base_tempdir; until (mkdir $tempdir) { if ($! !~ /exists/) { die "Couldn't create temporary directory $tempdir: $!"; } $Q++; $tempdir = "$base_tempdir.$Q"; } return $tempdir; } sub save_stdin { my $output = tempfile(); local *F; open F, ">", $output or die "Couldn't open temporary file '$output' for saving stdin: $!"; my ($nb, $buf); while ($nb = read STDIN, $buf, 8192) { print F $buf; } unless (defined $nb) { die "Error reading from stdin: $!; aborting"; } close F or die "Error closing stdin savefile $output; $!; aborting"; push @TMPFILES, $output; return $output; } sub convert { my ($input, $new_type, $command) = @_; my $output = tempfile($new_type); local (*R, *W, *F); subst($command, 'I', $input); subst($command, 'O', $output); warn "*** Running command $command\n" if $opt_v; unless (system($command) == 0) { exit $? if $? & 255; # catch signals warn "command returned failure status $?; skipping"; return; } push @TMPFILES, $output; return $output; } =begin :later # This is only useful if the conversion command # reads stdin and writes stdout. Deal with it another time. # The program actually uses convert(), found above. sub convert_redirect { my ($input, $command) = @_; my $output = tempfile(); local (*R, *W, *F); unless (pipe(R,W)) { die "Couldn't make pipe: $!; aborting"; } open F, "< $input" or die "Couldn't read input file $input: $!; aborting"; my $pid = fork; unless (defined $pid) { die "Couldn't fork to run command '$command': $!; aborting"; } if ($pid==0) { # child open STDOUT, "> $output" or die "Couldn't redirect stdout to $output: $!.\n"; open STDIN, "<&=R" or die "Couldn't redirect stdin from pipe: $!.\n"; close F; close R; close W; exec $command; die "Couldn't exec command '$command': $!; aborting"; } # parent close R; my ($nb, $buf); while ($nb = read F, $buf, 8192) { print W $buf; } unless (defined $nb) { die "Error reading from $input: $!; aborting"; } close W or die "Error closing command $command; $!; aborting"; push @TMPFILES, $output; return $output; } =end :later =cut sub is_compressed { my $type = shift; return exists $CONF{"$type\->???"}; } sub decompress_command { my $type = shift; return $CONF{"$type\->???"}; } sub conversion_path { # Do BFS on configuration information my ($s, $d) = @_; return ($s) if $s eq $d; my @queue = ($s); my %conversions; for (keys %CONF) { push @{$conversions{$1}}, $2 if /(.*)->(.*)/; } my %good_path_to = ($s => []); while (@queue) { my ($here) = shift @queue; for my $next_type (@{$conversions{$here}}) { next if exists $good_path_to{$next_type}; # we've been here already my $p = [@{$good_path_to{$here}}, $here]; return @$p, $next_type if $next_type eq $d; $good_path_to{$next_type} = $p; push @queue, $next_type; } } # no path found return; } sub file_type { my $file = shift; local $_ = qx{file -Lb $file}; # -b: Do not prepend filename to output /JPEG/ && return 'jpg'; /GIF/ && return 'gif'; /HTML/ && return 'html'; /PostScript/ && return 'ps'; /HP PCL/ && return 'pcl'; /(ASCII( English| mail)?|news) text/ && return 'txt'; /PDF/ && return 'pdf'; /TeX DVI/ && return 'dvi'; /gzip/ && return 'gz'; /compress/ && return 'Z'; /troff/ && return '3'; /perl script/ && return 'pl'; /\w+ script/ && return 'txt'; /PPM/ && return 'ppm'; /Microsoft Office/ && return 'doc'; return '???'; } sub usage { exit 1; } sub subst { my ($string, $seq, $repl) = @_; # Look for %$seq or %$seq:.foo # If you see %$seq, replace it with $repl # If you see %$seq:foo, replace it with $repl:tfoo while ($_[0] =~ /%\Q$seq\E(?::(\.\S+))?/g) { my $len = length($&); my $pos = pos($_[0])-$len; my $repl = $repl; # warn "pos=$pos len=$len suff=$1 arg='$_[0]'\n"; if (defined $1) { my $suffix = $1; $repl =~ s/\.[^.]*$/$suffix/; } substr($_[0], $pos, $len) = $repl; } } END { unlink @TMPFILES; rmdir $tempdir; }