#! /usr/bin/perl
#
# lpdomatic Perl Foomatic filter script for LPD, LPRng, and GNUlpr
#
# Copyright 1997-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.
#

my $configpath = "/etc/foomatic";
my $enscriptcommand = "";
my $execpath = "/usr/local/bin:/usr/bin:/bin";
my $logfile = "/tmp/lpdomatic.log";
my $debug = 0;

# Read config file if present
%conf = readConfFile("$configpath/filter.conf");

# Determine which filter to use for non-PostScript files to be converted
# to PostScript

my @enscriptcommands = 
  ('a2ps -1 @@--medium=@@PAGESIZE@@ @@--center-title=@@JOBTITLE@@ -o - 2>/dev/null',
   'enscript -G @@-M @@PAGESIZE@@ @@-b "Page $%|@@JOBTITLE@@ -p- 2>/dev/null',
   'mpage -o -1 @@-b @@PAGESIZE@@ @@-H -h @@JOBTITLE@@ -P- -');

if (defined(%conf) and $conf{textfilter})
{
    $enscriptcommand = $conf{textfilter};
    $enscriptcommand eq 'a2ps' and $enscriptcommand = $enscriptcommands[0];
    $enscriptcommand eq 'enscript' and $enscriptcommand = $enscriptcommands[1];
    $enscriptcommand eq 'mpage' and $enscriptcommand = $enscriptcommands[2];
}

if ($execpath) {$ENV{'PATH'} = $execpath;}

# Set debug mode
$debug = $conf{debug} if defined(%conf) and defined $conf{debug};

# Where to send debugging log output to
if ($debug) {
    # Grotesquely unsecure; use for debugging only
    open LOG, ">$logfile";
    $logh = *LOG;
    use IO::Handle;
    $logh->autoflush(1);
} else {
    $logh=*STDERR;
}

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

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

# We get the definition filename as the accounting file argument,
# which is the last argument (in case of LPRng the last arguments are
# the free-formed arguments in the "filter_options" tag of
# /etc/printcap, so we do not need to abuse the accounting file).

my $af = $ARGV[$#ARGV];

print $logh "$0: af=$af\n";

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

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

$dat = $VAR1;

# Determine with which command non-PostScript files are converted to PostScript
if ($enscriptcommand eq "") {
    for my $c (@enscriptcommands) {
	($c =~ m/^\s*(\S+)\s+/) || ($c = m/^\s*(\S+)$/);
	$command = $1;
	for (split(':', $ENV{'PATH'})) {
	    if (-x "$_/$command") {
		$enscriptcommand = $c;
		last;
	    }
	}
	if ($enscriptcommand ne "") {
	    last;
	}
    }
    if ($enscriptcommand eq "") {
	$enscriptcommand = "echo \"Cannot convert file to PostScript!\" 1>&2";
    }
}

# 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.

# 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?
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:\s+\*?([^\s=]+)\s+(\S.*)$/) ||
		($line =~ m/\%\%\s*FoomaticOpt:\s*([^\s=]+)\s*=\s*(\S.*)$/)) {
		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));


# There is no good way to get the options to us easily on the
# commandline.	Some lpr's support -z to pass options, but not the
# usual one that everyone actually uses ;(
#
# We override the -J aka "Job Title" value (passed in as -j to this
# filter) for our own nefarious purposes.

# Get options

my $lprng = 0;
if ($ARGV[0] eq '--lprng') {
    $lprng=1;
    shift(@ARGV);
}

use Getopt::Std;
my ($optstr, $sep);
if ($lprng) {
    # LPRng
    getopts("cw:l:i:n:j:h:z:o:Z:J:");
    $optstr = $opt_Z;
    $sep = ',';
} else {
    # An LPD
    getopts("cw:l:i:n:j:h:z:o:Z:");
    $optstr = $opt_j;	    # classic LPD hack
    $sep = ' ';
    if (defined($ENV{'LPOPTS'})) {
	# Cool!	 We're running under a snazzy LPD that passes options this way
	$optstr = $ENV{'LPOPTS'};
	$gotlpopts = 1;
	$sep = ',';
    }
}

print $logh "$0: options: '", $optstr, "'\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...

my @opts = split(/$sep/,$optstr);
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.
my $commandline = $dat->{'cmd'};
my $arg;
 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=\"LPDOMATIC\"\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;
}

# Insert the page size into the $enscriptcommand
if ($enscriptcommand =~ /\@\@([^@]+)\@\@PAGESIZE\@\@/) {
    my $optstr = ((($arg = argbyname('PageSize')))
		  ? $1 . $arg->{'userval'}
		  : "");
    $enscriptcommand =~ s/\@\@([^@]+)\@\@PAGESIZE\@\@/$optstr/;
}

# Insert the job title into the $enscriptcommand
if ($enscriptcommand =~ /\@\@([^@]+)\@\@JOBTITLE\@\@/) {
    if ($do_docs) {
	$opt_J = "Documentation for the $dat->{'make'} $dat->{'model'}";
    }
    my $titlearg = $1;
    my ($arg, $optstr);
    ($arg = $opt_J) =~ s/\"/\\\"/g;
    if (($titlearg =~ /\"/) || $arg) {
	$optstr = $titlearg . ($titlearg =~ /\"/ ? '' : '"') .
	    ($arg ? "$arg\"" : '"');
    } else {
	$optstr = "";
    }
    $enscriptcommand =~ s/\@\@([^@]+)\@\@JOBTITLE\@\@/$optstr/;
}

# 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) {
	open DRIVERINPUT, "> /tmp/prnjob"
	  or die "error opening /tmp/prnjob";
	print DRIVERINPUT @examined_stuff;
    }
    if ($more_stuff) {
	while (<STDIN>) {
	    print $driverhandle $_;
	    if ($debug) {
		print DRIVERINPUT $_;
	    }
	}
    }

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

    # Wait for driver child
    waitpid($driverpid, 0);
    print $logh "Main process finished\n";
    exit(0);

} else {
    # The job is ascii, we guess.
    print $logh "$0: ascii job\n";

    # Implement:
    # lpdomatic | $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 "Main process finished\n";
	exit(0);

    } 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);
    }
}


die "shouldn't get here...";


} else {
  print $logh "$0: printing docs\n";

$commandline = "| $enscriptcommand | ( echo \"@prepend\"; cat ) | ( $commandline ) $postpipe";
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'});

my $optstr;
if ($lprng == 1) {
    $optstr = "Specify each option with a -Z argument to lpr ie\n% lpr -Z duplex -Z two=2 -Z three=3";
} else {
    $optstr = ($gotlpopts
	       ? "Specify each option with a -o argument to lpr ie\n% lpr -o duplex -o two=2 -o three=3"
	       : "Specify space-separated options to lpr with the -J flag ie\n% lpr -J'duplex two=2 three=3'");
}

print "Invokation summary for your $make $model printer as driven by
the $driver driver.

$optstr

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;
exit (0);
}

# WTF?!
die "unable to run command '$commandline'\n";

# 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 !;
		$commandline =~ s!(;\s*)gs !; foomatic-gswrapper !;
	    }

	    # Actually run the thing...
	    system "$commandline"
	      and die "Couldn't exec $commandline";
	    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;
}

sub readConfFile {
    my ($file) = @_;

    my %conf;
    # Read config file if present
    if (open CONF, "< $file") {
	while (<CONF>)
	{
	    $conf{$1}="$2" if (m/^\s*([^\#\s]\S*)\s*:\s*(.*)\s*$/);
	}
	close CONF;
    }

    return %conf;
}

# Emacs tabulator/indentation

### Local Variables:
### tab-width: 8
### perl-indent-level: 4
### End:
