#! /usr/bin/perl

# The above Perl path may vary on your system; fix it!!!

# This is directomatic, a filter script to print with free software
# printer drivers without needing a spooler. It is designed to be used
# together with a Foomatic printer definition file.

# Save it somewhere, mark it executable, download the
# LPD-O-Matic/Direct-O-Matic printer definition file for your
# printer/driver combo from the Linux Printing website, save it in the
# /etc/foomatic/direct/ directory, and print with

#    directomatic -P <printer def. file> -o option1=value1 -o option2=value2
#                 <file name>

# If you do not specify a file to print, standard input will be
# printed.  and if your printer definition file is in
# /etc/foomatic/direct or ~/.foomatic/direct you do not need to
# specify its path. Put a '$postpipe = "| ..."' line into the printer
# definition file (instructions in the file) to direct the output of
# directomatic to your printer instead of to standard output. To make
# normal users able to print this way make the appropriate printer
# device file in /dev/ world-writable.

# See http://www.linuxprinting.org/direct-doc.html

# Set this to a command you've got installed
my $enscriptcommand = "mpage -o -1 -P- -";

# my $enscriptcommand = "enscript args???";
# my $enscriptcommand = "nenscript args??";
# my $enscriptcommand = "a2ps args??";

# What path to use for filter programs and such.  Your printer driver
# must be in the path, as must be Ghostscript, $enscriptcommand, and
# possibly other stuff.  The default path is often fine on Linux, but
# may not be on other systems.
# 
#$ENV{'PATH'} = '/usr/local/bin:/usr/bin:/bin';

# Set debug to 1 to enable the debug logfile for this filter; it will
# appear as /tmp/prnlog It will contain status from this filter, plus
# Ghostscript stderr output.
#
# WARNING: This logfile is a security hole; do not use in production.

my $debug=0;

# Where to send debugging log output to
if ($debug) {
    # Grotesquely unsecure; use for debugging only
    open LOG, ">/tmp/prnlog";
    $logh = *LOG;

    use IO::Handle;
    $logh->autoflush(1);
} else {
    # Through away the debugging info
    open LOG, ">/dev/null";
    $logh = *LOG;
}

######### End interesting enduser options ##############

#
# directomatic Perl Foomatic filter script for spooler-less printing
#
# Copyright 2001 Grant Taylor <gtaylor@picante.com>
#              & Till Kamppeter <till.kamppeter@gmx.net>
#
#   This program is free software; you can redistribute it and/or modify it
#   under the terms of the GNU General Public License as published by the Free
#   Software Foundation; either version 2 of the License, or (at your option)
#   any later version.
#
#   This program is distributed in the hope that it will be useful, but
#   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
#   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
#   for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#



# Flush everything immediately.
$|=1;
$SIG{PIPE}='IGNORE';

my $domversion='$Revision: 1.3 $';
#'# Fix emacs syntax highlighting
print $logh "Directomatic backend version $domversion running...\n";
print $logh "$0: called with arguments: '",join("','",@ARGV),"'\n";

# Read the command line arguments

# We use the library Getopt::Long here, so that we can have more than
# one "-o" option on one command line.

use Getopt::Long;
Getopt::Long::Configure("no_ignore_case", "pass_through");
GetOptions("P=s" => \$opt_P,         # which Printer definition file?
           "d=s" => \$opt_d,         # which printer definition file
	                             # (Destination)?
           "o=s" => \@opt_o);        # printing Options

# We get the definition filename as the "-P" or "-d" argument.

if (!defined($opt_P) && (defined($opt_d))) {
    $opt_P = $opt_d;
}

if (!defined($opt_P)) {
    die "No printer definition (option \"-P <name>\") specified!";
}

my $def_file = $opt_P;
my @opts = @opt_o;

my $system_default_path = "/etc/foomatic/direct/";
my $user_default_path = "$ENV{'HOME'}/.foomatic/direct/";

# If the file does not exist or if it is unreadable, try to add an
# extension
if (! -r $def_file) {
    if (-r "${def_file}.foo") { # current dir
	$def_file = "${def_file}.foo";
    } elsif (-r "${def_file}.lom") {
	$def_file = "${def_file}.lom";
    } elsif (-r "$user_default_path${def_file}.foo") { # user dir
	$def_file = "$user_default_path${def_file}.foo";
    } elsif (-r "$user_default_path${def_file}.lom") {
	$def_file = "$user_default_path${def_file}.lom";
    } elsif (-r "$system_default_path${def_file}.foo") { # system dir
	$def_file = "$system_default_path${def_file}.foo";
    } elsif (-r "$system_default_path${def_file}.lom") {
	$def_file = "$system_default_path${def_file}.lom";
    } else { 
	die "printer definition file $def_file does not exist or is unreadable!";
    }
}
print $logh "$0: printer def file=$def_file\n";

open PPD, "$def_file" || do {
    print $logh "$0: error opening $def_file.\n";
    die "unable to open printer definition file $def_file";
};
my @datablob = <PPD>;
close PPD;

# Which files do we want to print?
my $filelist;
if ($#ARGV < 0) {
    # No file specified, print fron standard input
    @filelist = ("<STDIN>");
} else {
    @filelist = @ARGV;
}

# Check file list
my $file;
for $file (@filelist) {
    if ($file ne "<STDIN>") {
	if ($file =~ /^-/) {
	    die "Invalid argument: $file";
	} elsif (! -r $file) {
	    die "File $file does not exist/is not readable";
	}
    }
}
	

# From here on we have to repeat all the rest of the program for every file
# to print

for $file (@filelist) {

    print $logh "
================================================

File: $file

================================================

";

    # If we do not print standard input, open the file to print
    if ($file ne "<STDIN>") {
	close STDIN;
	open STDIN, "< $file" ||
	    die "Cannot open $file";
    }

    # OK, we have the datablob
    eval join('',@datablob) || do {
	print $logh "$0: unable to evaluate datablob\n";
	die "error in datablob eval";
    };

    $dat = $VAR1;

    # First, for arguments with a default, stick the default in as the userval.
    for $arg (@{$dat->{'args'}}) {
	if ($arg->{'default'}) {
	    $arg->{'userval'} = $arg->{'default'};
	}
    }



    # Do we get options from within the job postscript?  We might from
    # a classical ppd-grokking postscript generating application (with
    # the # PPD-O-Matic PPD file).  In that case, we should have
    # stuffed # something we can extract into the postscript stream
    # (ie, the # standard PPD mechanism) and parsed it out here.
    # (Structured # comments are probably ideal for this purpose?)
    # When we get there, # be careful to let command-line options
    # override job contents.

    ## Next, examine the postscript job itself for traces of
    ## command-line and pjl options.  Sometimes these don't show up in
    ## the CUPS filter ## 'options' argument!

    # Examination strategy: read some lines from STDIN.  Put those
    # lines onto the stack @examined_stuff, which will be stuffed into
    # # Ghostscript/whatever later on.

    print $logh "Seaerching job for option settings ...\n";
    my $maxlines = 1000;            # how many lines to examine?
    my $more_stuff = 1;             # there is more stuff in stdin.
    my $linect = 0;                 # how many lines have we examined?
    @examined_stuff = ();           # Make sure that there does not
                                    # remain anything from the previous
                                    # file.
    do {
	my $line;
	if ($line=<STDIN>) {
	    if ($linect == 0) {
		# Line zero should be postscript leader
		if ($line !~ m/^.?%!/) { # There can be a Windows control 
		                         # char before "%!" 
		    # The input file is a text file!
		    $maxlines = 1; # Bail out of the option search loop
		}
	    } else {
		if (($line =~ m/\%\%BeginFeature: \*?(\w+) (\w+)/) ||
		    ($line =~ m/\%\%\s*FoomaticOpt:\s*(\w+)=(\w+)/)) {
		    my ($option, $value) = ($1, $2);

		    # OK, we have an option.  If it's not a
		    # *ostscript-style option (ie, it's command-line or
		    # PJL) then we should note that fact, since the
		    # attribute-to-filteroption passing in CUPS is kind of
		    # funky, especially wrt boolean options.

		    print $logh "Found: $line";
		    if ($arg=argbyname($option)) {
			print $logh "   Option: $option=$value";
			if ($arg->{'style'} ne 'G') {
			    print $logh " --> Setting option\n";
			    if ($arg->{'type'} eq 'bool') {
				# Boolean options are 1 or 0
				if ($value eq 'True') {
				    $arg->{'userval'} = 1;
				} elsif ($value eq 'False') {
				    $arg->{'userval'} = 0;
				} else {
				    warn "job contained boolean option",
				    " with neither True nor False value!?";
				}
			    } elsif (($arg->{'type'} eq 'enum') ||
				     ($arg->{'type'} eq 'int') ||
				     ($arg->{'type'} eq 'float')) {
				# enum options go as the value, unless 
				# they were Unknown...
				# Same with numerical options, they can 
				# appear here when the client has used the
				# Adobe-compliant PPD-O-MATIC PPD file.

				if (lc($value) eq 'unknown') {
				    $arg->{'userval'} = undef;
				} else {
				    $arg->{'userval'} = $value;
				}
			    }
			} else {
			    # it is a postscript style option, presuemably
			    # all applied for us and such...
			    print $logh " --> Option will be set by PostScript interpreter\n";
			}
		    } else {
			# This option is unknown to us.  WTF?
			warn "unknown option $option=$value found in the job";
		    }       
		    
		}
	    }

	    # Push the line onto the stack for later spitting up...
	    push (@examined_stuff, $line);
	    $linect++;

	} else {
	    # EOF!
	    $more_stuff = 0;
	}

    } while (($linect < $maxlines) and ($more_stuff != 0));

    # Get command line options

    print $logh "$0: options: '", join(" ", @opts), "'\n";

    # Everything below this point was once identical to cupsomatic.
    # Now it's subtly different and mangled.  I really ought to
    # combine # scripts, or modularize, or something...

    optionproc: for (@opts) {
	print $logh "$0: pondering option `$_'\n";
	
	if (lc($_) eq 'docs') {
	    $do_docs = 1;
	    last;
	}
	
	my $arg;
	if ((m!(.+)=(.+)!) || (m!(.+):(.+)!)) {
	    # "gpr" separates option and value with a ":" and not with a
	    # "=", seems that it is programmed against a special backend.
	    
	    my ($aname, $avalue) = ($1, $2);
	    
	    # Standard arguments?
	    # media=x,y,z
	    # sides=one|two-sided-long|short-edge
	    
	    # handled by cups for us?
	    # page-ranges=
	    # page-set=
	    # number-up=

	    # brightness= gamma= these probably collide with printer-
	    # specific options.  Hmm.  CUPS has a stupid design for option
	    # handling; everything gets all muddled together.

	    # Rummage around in the media= option for known media, source, etc types.
	    # We ought to do something sensible to make the common manual
	    # boolean option work when specified as a media= tray thing.
	    # 
	    # Note that this fails miserably when the option value is in
	    # fact a number; they all look alike.  It's unclear how many
	    # drivers do that.  We may have to standardize the verbose
	    # names to make them work as selections, too.

	    if ($aname =~ m!^media$!i) {
		my @values = split(',',$avalue);
		for (@values) {
		    if ($dat->{'args_byname'}{'PageSize'}
			and $val=valbyname($dat->{'args_byname'}{'PageSize'},$_)) {
			$dat->{'args_byname'}{'PageSize'}{'userval'} = 
			    $val->{'value'};
		    } elsif ($dat->{'args_byname'}{'MediaType'}
			     and $val=valbyname($dat->{'args_byname'}{'MediaType'},$_)) {
			$dat->{'args_byname'}{'MediaType'}{'userval'} =
			    $val->{'value'};
		    } elsif ($dat->{'args_byname'}{'InputSlot'}
			     and $val=valbyname($dat->{'args_byname'}{'InputSlot'},$_)) {
			$dat->{'args_byname'}{'InputSlot'}{'userval'} = 
			    $val->{'value'};
		    } elsif (lc($_) eq 'manualfeed') {
			# Special case for our typical boolean manual
			# feeder option if we didn't match an InputSlot above
			if (defined($dat->{'args_byname'}{'ManualFeed'})) {
			    $dat->{'args_byname'}{'ManualFeed'}{'userval'} = 1;
			}
		    } else {
			print $logh "$0: unknown media= component $_.\n";
		    }
		}
		
	    } elsif ($aname =~ m!^sides$!i) {
		# Handle the standard duplex option, mostly
		if ($avalue =~ m!^two-sided!i) {
		    if (defined($dat->{'args_byname'}{'Duplex'})) {
			$dat->{'args_byname'}{'Duplex'}{'userval'} = '1';
		    }
		}
		
		# We should handle the other half of this option - the
		# BindEdge bit.  Also, are there well-known ipp/cups
		# options for Collate and StapleLocation?  These may be
		# here...
		
	    } else {
		# Various non-standard printer-specific options
		if ($arg=argbyname($aname)) {
		    $arg->{'userval'} = $avalue;
		} else {
		    print $logh "$0: unknown option $aname\n";
		}
	    }
	} elsif (m!no(.+)!i) {
	    # standard bool args:
	    # landscape; what to do here?
	    # duplex; we should just handle this one OK now?
	    
	    if ($arg=argbyname($1)) {
		$arg->{'userval'} = 0;
	    } else {
		print $logh "$0: unknown option $1\n";
	    }
	} elsif (m!(.+)!) {
	    if ($arg=argbyname($1)) {
		$arg->{'userval'} = 1;
	    } else {
		print $logh "$0: unknown option $1\n";
	    }
	}
    }


    #### Everything below here ought to be generic for any printing
    #### system?  It just uses the $dat structure, with user values
    #### filled in, and turns postscript into printer data.


    # Construct the proper command line.
    $commandline = $dat->{'cmd'};
    @pjlprepend = ();
    @pjlappend = ();
  argument:
    for $arg (sort { $a->{'order'} <=> $b->{'order'} } 
              @{$dat->{'args'}}) {
        
        my $name = $arg->{'name'};
        my $spot = $arg->{'spot'};
        my $varname = $arg->{'varname'};
        my $cmd = $arg->{'proto'};
        my $comment = $arg->{'comment'};
        my $type = $arg->{'type'};
        my $cmdvar = "";
        my $userval = $arg->{'userval'};
        
        if ($type eq 'bool') {

            # If true, stick the proto into the command line
            if (defined($userval) && $userval == 1) {
                $cmdvar = $cmd;
            }

        } elsif ($type eq 'int' or $type eq 'float') {

            # If defined, process the proto and stick the result into
            # the command line or postscript queue.
            if (defined($userval)) {
                my $min = $arg->{'min'};
                my $max = $arg->{'max'};
                if ($userval >= $min and $userval <= $max) {
		    my $sprintfcmd = $cmd;
		    $sprintfcmd =~ s!\%([^s])!\%\%$1!g;
		    $cmdvar = sprintf($sprintfcmd,
                                      ($type eq 'int' 
                                       ? sprintf("%d", $userval)
                                       : sprintf("%f", $userval)));
                } else {
                    print $logh "Value $userval for $name is out of range $min<=x<=$max.\n";
                }
            }

        } elsif ($type eq 'enum') {

            # If defined, stick the selected value into the proto and
            # thence into the commandline
            if (defined($userval)) {
                my $val;
                if ($val=valbyname($arg,$userval)) {
		    my $sprintfcmd = $cmd;
		    $sprintfcmd =~ s!\%([^s])!\%\%$1!g;
		    $cmdvar = sprintf($sprintfcmd,
                                      (defined($val->{'driverval'})
                                       ? $val->{'driverval'}
                                       : $val->{'value'}));
                } else {
                    # User gave unknown value?
                    print $logh "Value $userval for $name is not a valid choice.\n";
                }
            }

        } else {
                    
            print $logh "unknown type for argument $name!?\n";
            # die "evil type!?";
                    
        }
        
        if ($arg->{'style'} eq 'G') {
            # Place this Postscript command onto the prepend queue.
            push (@prepend, "$cmdvar\n") if $cmdvar;
            print $logh "prepending: $cmdvar\n";

        } elsif ($arg->{'style'} eq 'J') {
	    # put PJL commands onto PJL stack...
	    if (defined($dat->{'pjl'})) {
		push (@pjlprepend, "\@PJL $cmdvar\n") if $cmdvar;
	    }
        } elsif ($arg->{'style'} eq 'C') {
            # command-line argument

            # Insert the processed argument in the commandline
            # just before the spot marker.
            $commandline =~ s!\%$spot!$cmdvar\%$spot!;
        }
        
    }


    ### Tidy up after computing option statements for all of P, J, and C 
    ### types:

    ## C type finishing
    # Pluck out all of the %n's from the command line prototype
    my @letters = qw/A B C D E F G H I J K L M Z/;
    for $spot (@letters) {
	# Remove the letter marker from the commandline
	$commandline =~ s!\%$spot!!;
    }

    ## J type finishing
    # Compute the proper stuff to say around the job
    if (defined($dat->{'pjl'})) {

	# Stick beginning of job cruft on the front of the pjl stuff...
	unshift (@pjlprepend,
		 "\033%-12345X\@PJL JOB NAME=\"DIRECTOMATIC\"\n");
	
	# Arrange for PJL EOJ command at end of job
	push (@pjlappend,
	      "\33%-12345X\@PJL RESET\n\@PJL EOJ\n");
	
	print $logh "PJL: ", @pjlprepend, "<job data>\n", @pjlappend;
    }


    # Debugging printout of all option values
    if ($debug) {
	for $arg (@{$dat->{'args'}}) {
	    my ($name, $val) = ($arg->{'name'}, $arg->{'userval'});
	    print $logh "Final value for option $name is $val\n";
	}
    }

    # Now print the darned thing!
    if (! $do_docs) {
	# Run the proper command line.
	print $logh "$0: running: $commandline\n";
	
	# OK.  Examine the input to see if it is text or Postscript
	if ($examined_stuff[0] =~ m/^(.?)%!/) { # optional stupid Windows control-char
	    # The job is Postscript...
	    print $logh "$0: postscript job line1=>$examined_stuff[0]<\n";
	    
	    # get a handle on | commandline | us pjlstuffing | postpipe
	    my ($driverhandle, $driverpid) = getdriverhandle();
        
	    # Now spew the job into the driver
	    print $driverhandle @examined_stuff;
	    if ($debug != 0) {
		open DRIVERINPUT, "> /tmp/prnjob"
		    or die "error opening /tmp/prnjob";
		print DRIVERINPUT @examined_stuff;
	    }
	    if ($more_stuff) {
		while (<STDIN>) { 
		    print $driverhandle $_;
		    if ($debug != 0) {
			print DRIVERINPUT $_;
		    }
		}
	    }

	    print $logh "closing $driverhandle\n";
	    close $driverhandle
		or die "Error closing pipe to $commandline";
	    print $logh "closed $driverhandle\n";
	    if ($debug != 0) {
		close DRIVERINPUT 
		    or die "error closing /tmp/prnjob";
	    }

	    # Wait for driver child
	    waitpid($driverpid, 0);
	    print $logh "\nFile $file finished\n\n\n";

	} else {
	    # The job is ascii, we guess.
	    print $logh "$0: ascii job\n";
	    
	    # Implemented:
	    # directomatic | $enscriptcommand | getdriverhandle()..
	    #       KID1^                  

	    # plus an optional | $postpipe on the end, handled by KID3

	    my $pid, $sleep_count=0;
	    do {
		$pid = open(KID1, "|-");
		unless (defined $pid) {
		    warn "cannot fork: $!";
		    die "bailing out" if $sleep_count++ > 6;
		    sleep 10;
		}
	    } until defined $pid;
        
	    if ($pid) {
		# parent; write the job data into KID1 aka $enscriptcommand
		
		print KID1 @examined_stuff;
		print $logh "printing: @examined_stuff";
		if ($more_stuff) {
		    while (<STDIN>) { 
			print KID1 $_; 
			print $logh "printing: $_";
		    }
		}
		close KID1;

		print $logh "root process done writing job data in\n";

		# Wait for enscript child
		waitpid($pid, 0);
		print $logh "\nFile $file finished\n\n\n";

	    } else {

		my ($driverhandle, $driverpid) = getdriverhandle();
		
		print $logh "setting STDOUT to be $driverhandle and spawning $enscriptcommand\n";
		
		open (STDOUT, ">&$driverhandle")
		    or die "Couldn't dup driverhandle";
		system "$enscriptcommand" 
		    and die "Couldn't exec $enscriptcommand";

		close STDOUT;
		close $driverhandle;

		# Wait for driver child
		waitpid($driverpid, 0);
		print $logh "KID1 finished\n";
		exit(0);
	    }
	}
    } else {
	print $logh "$0: printing docs\n";

	# Redirect STDERR to $logh, so that standard error of GhostScript
	# goes to /dev/null when not in debug mode
	close STDERR;
	open STDERR, ">&$logh" || do {
	    open STDERR;
	    die "Couldn't dup $logh";
	};
	$commandline = "| $enscriptcommand | ( $commandline ) $postpipe";
	# Put STDERR back to its old state
	close STDERR;
	open STDERR;
	print $logh "Piping doc page(s) into: $commandline\n";
	close $logh;
	open PRINTER, $commandline || die "unable to run $commandline";
	select PRINTER;

	my ($make, $model, $driver) 
	    = ($dat->{'make'}, $dat->{'model'}, $dat->{'driver'});

	# Read out the program name with which we were called, but discard the path
	
	$0 =~ m!/([^/]+)\s*$!;
	my $progname = $1;
	
	print "Invokation summary for your $make $model printer as driven by
the $driver driver.

Specify each option with a -o argument to $progname ie
% $progname -P $opt_P -o duplex -o two=2 -o three=3

The following options are available for this printer:

";

	for $arg (@{$dat->{'args'}}) {
	    my ($name,
		$required,
		$type,
		$comment,
		$spot,
		$default) = ($arg->{'name'},
			     $arg->{'required'},
			     $arg->{'type'},
			     $arg->{'comment'},
			     $arg->{'spot'},
			     $arg->{'default'});
        
	    my $reqstr = ($required ? " required" : "n optional");
	    print "Option `$name':\n  A$reqstr $type argument.\n  $comment\n";
	    print "  This option corresponds to a PJL command.\n" if ($arg->{'style'} eq 'J');
        
	    if ($type eq 'bool') {
		if (defined($default)) {
		    my $defstr = ($default ? "True" : "False");
		    print "  Default: $defstr\n";
		}
		print "  Example: `$name'\n";
	    } elsif ($type eq 'enum') {
		print "  Possible choices:\n";
		my $exarg;
		for (@{$arg->{'vals'}}) {
		    my ($choice, $comment) = ($_->{'value'}, $_->{'comment'});
		    print "   o $choice: $comment\n";
		    $exarg=$choice;
		}
		if (defined($default)) {
		    print "  Default: $default\n";
		}
		print "  Example: $name=$exarg\n";
	    } elsif ($type eq 'int' or $type eq 'float') {
		my ($max, $min) = ($arg->{'max'}, $arg->{'min'});
		my $exarg;
		if (defined($max)) {
		    print "  Range: $min <= x <= $max\n";
		    $exarg=$max;
		}
		if (defined($default)) {
		    print "  Default: $default\n";
		    $exarg=$default;
		}
		if (!$exarg) { $exarg=0; }
		print "  Example: $name=$exarg\n";
	    }
	    
	    print "\n";
	}
	
	select STDOUT;
	close PRINTER;

	# Print the documentation only once, evwn if there are more files
	# specified on the command line
	exit (0);
    }

    close STDIN;

}

# All files printed

exit(0);

# return glob ref to "| commandline | self(pjlstuffer) | $postpipe"
# also return driver pid.  must wait on diver pid
# ugly, we use $commandline, $postpipe, @prepend, @pjlprepend, @pjlappend globals
sub getdriverhandle {

    use IO::Handle;
    pipe KID3_IN, KID3;
    KID3->autoflush(1);
    
    my $pid3 = fork();
    if (!defined($pid3)) {
        print $logh "$0: cannot fork for kid3!\n";
        die "can't fork for kid3\n";
    }
    if ($pid3) {

        # we are the parent; return a glob to the filehandle
        close KID3_IN;
        print KID3 @prepend;
        print $logh "$0: prepended:\n", @prepend;

        KID3->flush();
        return ( *KID3, $pid3 );

    } else {
        close KID3;

        pipe KID4_IN, KID4;
	KID4->autoflush(1);
        my $pid2 = fork();
        if (!defined($pid2)) {
            print $logh "$0: cannot fork for kid4!\n";
            die "can't fork for kid4\n";
        }
        
        if ($pid2) {
            # parent, child of primary task; we are |commandline|
            close KID4_IN;

            print $logh "gs  PID pid2=$pid2\n";
            
            close STDIN                or die "Couldn't close STDIN in $pid2";
            open (STDIN, "<&KID3_IN")  or die "Couldn't dup KID3_IN";
            close STDOUT               or die "Couldn't close STDOUT in $pid2";
            open (STDOUT, ">&KID4")    or die "Couldn't dup KID4";
	    if ($debug) {
		open (STDERR, ">&$logh") 
		    or die "Couldn't dup logh to stderr";
	    }

	    # Massage commandline to execute foomatic-gswrapper
	    my $havewrapper = 0;
	    for (split(':', $ENV{'PATH'})) {
		if (-x "$_/foomatic-gswrapper") {
		    $havewrapper=1;
		    last;
		}
	    }
	    if ($havewrapper) {
		$commandline =~ s!^\s*gs !foomatic-gswrapper !;
		$commandline =~ s!(\|\s*)gs !\|foomatic-gswrapper !;
	    }
	    
	    # Redirect STDERR to $logh, so that standard error of
	    # GhostScript goes to /dev/null when not in debug mode
	    close STDERR;
	    open STDERR, ">&$logh" || do {
		open STDERR;
		die "Couldn't dup $logh";
	    };
	    # Actually run the thing...
            if (system "$commandline") {
		open STDERR;
		die "Couldn't exec $commandline";
	    };
	    # Put STDERR back to its old state
	    close STDERR;
	    open STDERR;
	    close STDOUT;
	    close KID4;
	    close STDIN;
	    close KID3_IN;
	    # Wait for postpipe/output child
	    waitpid($pid2, 0);
	    print $logh "KID3 finished\n";
	    exit(0);
        } else {
            # child, trailing task on the pipe; we write pjl stuff
            close KID4;
            close KID3_IN;

            my $fileh = *STDOUT;
            if ($postpipe) {
                open PIPE,$postpipe
                    or "die cannot open postpipe $postpipe";
                $fileh = *PIPE;
            }           

            # wrap the PJL around the job data, if there are any
            # options specified...
	    if ( @pjlprepend > 1 ) {
		print $fileh @pjlprepend;
	    }
            while (<KID4_IN>) {
                print $fileh $_;
            }
	    if ( @pjlprepend > 1 ) {
		print $fileh @pjlappend;
	    }

            close $fileh or die "error closing $fileh";
	    close KID4_IN;
                
            print $logh "tail process done writing data to $fileh\n";

	    print $logh "KID4 finished\n";
	    exit(0);
        }
    }
}

# Find an argument by name in a case-insensitive way
sub argbyname {
    my $name = @_[0];

    my $arg;
    for $arg (@{$dat->{'args'}}) {
        return $arg if (lc($name) eq lc($arg->{'name'}));
    }

    return undef;
}

sub valbyname {
    my ($arg,$name) = @_;

    my $val;
    for $val (@{$arg->{'vals'}}) {
        return $val if (lc($name) eq lc($val->{'value'}));
    }

    return undef;
}
