Viewing file: foomatic-rip (199.44 KB) -rwxr-xr-x Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
#!/usr/bin/perl # The above Perl path may vary on your system; fix it!!! -*- perl -*-
use strict; use POSIX; use Cwd;
my $ripversion='$Revision: 3.43.2.15 $'; #'# Fix emacs syntax highlighting
# foomatic-rip is a spooler-independent filter script which takes # PostScript as standard input and generates the printer's page # description language (PDL)/raster format as standard output. This # kind of filter is usually called Raster Image Processor (RIP), # therefore the name "foomatic-rip".
# Save it in one of the directories of your $PATH, so that it gets # found when called from the command line (for spooler-less printing), # link it to spooler-specific directories when you use CUPS or PPR:
# ln -s /usr/bin/foomatic-rip /usr/lib/cups/filter/ # ln -s /usr/bin/foomatic-rip /usr/lib/ppr/lib/ # ln -s /usr/bin/foomatic-rip /usr/lib/ppr/interfaces/
# Mark this filter world-readable and world-executable (note that most # spoolers run the print filters as a special user, as "lp", not as # "root" or as the user who sent the job).
# See http://www.linuxprinting.org/cups-doc.html # http://www.linuxprinting.org/lpd-doc.html # http://www.linuxprinting.org/ppr-doc.html # http://www.linuxprinting.org/pdq-doc.html # http://www.linuxprinting.org/direct-doc.html # http://www.linuxprinting.org/ppd-doc.html
# ========================================================================== # # User-configurable settings, edit them if needed # # ==========================================================================
# What path to use for filter programs and such. Your printer driver # must be in the path, as must be the renderer, $enscriptcommand, and # possibly other stuff. The default path is often fine on Linux, but # may not be on other systems. # my $execpath = "/usr/bin:/usr/local/bin:/usr/bin:/bin";
# CUPS raster drivers are searched here my $cupsfilterpath = "/usr/lib/cups/filter:/usr/local/lib/cups/filter:/usr/local/libexec/cups/filter:/opt/cups/filter:/usr/lib/cups/filter";
# Location of the configuration file "filter.conf", this file can be # used to change the settings of foomatic-rip without editing # foomatic-rip. itself. This variable must contain the full pathname # of the directory which contains the configuration file, usually # "/etc/foomatic". # Some versions of configure do not fully expand $sysconfdir my $prefix = "/usr"; my $configpath = "/etc/foomatic";
# For the stuff below, the settings in the configuration file have priority.
# Set to 1 to insert postscript code for page accounting (CUPS only). my $ps_accounting = 1; my $accounting_prolog = "";
# Enter here your personal command for converting non-postscript files # (especially text) to PostScript. If you leave it blank, at first the # line "textfilter: ..." from /etc/foomatic/filter.conf is read and # then the commands given on the list below are tried, beginning with # the first one. # You can set this to "a2ps", "enscript" or "mpage" to select one of the # default command strings. my $fileconverter = "";
my($kid0,$kid1,$kid2,$kid3,$kid4); my($kidfailed,$kid3finished,$kid4finished); my($convkidfailed,$dockidfailed,$kid0finished,$kid1finished,$kid2finished); my($fileconverterpid,$rendererpid,$fileconverterhandle,$rendererhandle); my($jobhasjcl);
# What 'echo' program to use. It needs -e and -n. Linux's builtin # and regular echo work fine; non-GNU platforms may need to install # gnu echo and put gecho here or something. # my $myecho = 'echo';
# Set debug to 1 to enable the debug logfile for this filter; it will # appear as defined by $logfile. It will contain status from this # filter, plus the renderer's stderr output. You can also add a line # "debug: 1" to your /etc/foomatic/filter.conf to get all your # Foomatic filters into debug mode. # # WARNING: This logfile is a security hole; do not use in production. my $debug = 0;
# This is the location of the debug logfile (and also the copy of the # processed PostScript data) in case you have enabled debugging above. # The logfile will get the extension ".log", the PostScript data ".ps". my $logfile = "/tmp/foomatic-rip";
# End interesting enduser options
# ========================================================================== # # foomatic-rip spooler-independent PS->Printer filter (RIP) of Foomatic # # Copyright 2002 - 2004 Grant Taylor <gtaylor@picante.com> # & Till Kamppeter <till.kamppeter@gmx.net> # & Helge Blischke <h.blischke@srz.de> # # 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 $added_lf = "\n";
# Flush everything immediately. $|=1;
## Constants used by this filter
# Error codes, as some spooles behave different depending on the reason why # the RIP failed, we return an error code. As I have only found a table of # error codes for the PPR spooler. If our spooler is really PPR, these # definitions get overwritten by the ones of the PPR version currently in # use.
my $EXIT_PRINTED = 0; # file was printed normally my $EXIT_PRNERR = 1; # printer error occured my $EXIT_PRNERR_NORETRY = 2; # printer error with no hope of retry my $EXIT_JOBERR = 3; # job is defective my $EXIT_SIGNAL = 4; # terminated after catching signal my $EXIT_ENGAGED = 5; # printer is otherwise engaged (connection # refused) my $EXIT_STARVED = 6; # starved for system resources my $EXIT_PRNERR_NORETRY_ACCESS_DENIED = 7; # bad password? bad port # permissions? my $EXIT_PRNERR_NOT_RESPONDING = 8; # just doesn't answer at all # (turned off?) my $EXIT_PRNERR_NORETRY_BAD_SETTINGS = 9; # interface settings are invalid my $EXIT_PRNERR_NO_SUCH_ADDRESS = 10; # address lookup failed, may be # transient my $EXIT_PRNERR_NORETRY_NO_SUCH_ADDRESS = 11; # address lookup failed, not # transient my $EXIT_INCAPABLE = 50; # printer wants (lacks) features # or resources # Standard Unix signal names #my SIGHUP = 1; #my SIGINT = 2; #my SIGQUIT = 3; #my SIGKILL = 9; #my SIGTERM = 15; #my SIGUSR1 = 10; #my SIGUSR2 = 12; #my SIGTTIN = 21; #my SIGTTOU = 22;
my $ESPIPE = 29; # the errno value when seeking a pipe or socket
## Some important variables
# We don't know yet, which spooler will be used. If we don't detect # one. we assume that we do spooler-less printing. Supported spoolers # are currently:
# cups - CUPS - Common Unix Printing System # lpd - LPD - Line Printer Daemon # lprng - LPRng - LPR - New Generation # gnulpr - GNUlpr, an enhanced LPD (development stopped) # ppr - PPR (foomatic-rip runs as a PPR RIP) # ppr_int - PPR (foomatic-rip runs as an interface) # cps - CPS - Coherent Printing System # pdq - PDQ - Print, Don't Queue (development stopped) # direct - Direct, spooler-less printing
my $spooler = 'direct';
# PPD file name my $ppdfile = "";
# Printer model my $model = "";
# Printer queue name my $printer = "";
# Printing options my $optstr = "";
# Job ID my $jobid = "";
# User who sent job my $jobuser = ((getpwuid($<))[0] || `whoami` || ""); chomp $jobuser;
# Host from which job was sent my $jobhost = `hostname`; chomp $jobhost;
# Job title my $jobtitle = "$jobuser\@$jobhost";
# Number of copies my $copies = "1";
# Post pipe (command into which the output of this filter should be piped) my $postpipe = "";
# Files to be printed my @filelist = ();
# JCL prefix to put before the JCL options (Can be modified by a # "*JCLBegin:" keyword in the PPD file): my $jclbegin = "\033%-12345X\@PJL\n";
# JCL command to switch the printer to the PostScript interpreter (Can # be modified by a "*JCLToPSInterpreter:" keyword in the PPD file): my $jcltointerpreter = "";
# JCL command to close a print job (Can be modified by a "*JCLEnd:" # keyword in the PPD file): my $jclend = "\033%-12345X\@PJL RESET\n";
# Prefix for starting every JCL command (Can be modified by # "*FoomaticJCLPrefix:" keyword in the PPD file): my $jclprefix = "\@PJL ";
# Under which name were we called and in which directory do we reside $0 =~ m!^(.*/)([^/]+)$!; my $programdir = $1; my $programname = $2;
# Filters to convert non-PostScript files my @fileconverters = (# a2ps (converts also other files than text) 'a2ps -1 @@--medium=@@PAGESIZE@@ @@--center-title=@@JOBTITLE@@ -o -', # enscript 'enscript -G @@-M @@PAGESIZE@@ @@-b "Page $%|@@JOBTITLE@@ ' . '--margins=36:36:36:36 --mark-wrapped-lines=arrow --word-wrap -p-', # mpage 'mpage -o -1 @@-b @@PAGESIZE@@ @@-H -h @@JOBTITLE@@ -m36l36b36t36r ' . '-f -P- -');
# spooler-specific file converters, default for the specific spooler when # none of the converters above is chosen. Remove weird characters from the # command line arguments to enhance security my @fixed_args = (defined($ARGV[0])?removespecialchars($ARGV[0]):"", defined($ARGV[1])?removespecialchars($ARGV[1]):"", defined($ARGV[2])?removespecialchars($ARGV[2]):"", defined($ARGV[3])?removespecialchars($ARGV[3]):"", defined($ARGV[4])?removespecialchars($ARGV[4]):""); my $spoolerfileconverters = { 'cups' => "${programdir}texttops '$fixed_args[0]' '$fixed_args[1]' '$fixed_args[2]' " . "'$fixed_args[3]' '$fixed_args[4] page-top=36 page-bottom=36 " . "page-left=36 page-right=36 nolandscape cpi=12 lpi=7 " . "columns=1 wrap'" };
## Config file
# Read config file if present my %conf = readConfFile("$configpath/filter.conf");
# Get execution path from config file $execpath = $conf{execpath} if defined $conf{execpath}; $ENV{'PATH'} = $execpath;
# Get CUPS filter path from config file $cupsfilterpath = $conf{cupsfilterpath} if defined $conf{cupsfilterpath};
# Set debug mode $debug = $conf{debug} if defined $conf{debug};
# Determine which filter to use for non-PostScript files to be converted # to PostScript if (defined $conf{textfilter}) { $fileconverter = $conf{textfilter}; $fileconverter eq 'a2ps' and $fileconverter = $fileconverters[0]; $fileconverter eq 'enscript' and $fileconverter = $fileconverters[1]; $fileconverter eq 'mpage' and $fileconverter = $fileconverters[2]; }
## Environment variables;
# "PPD": PPD file name for CUPS or PPR (if we run as PPR RIP) if (defined($ENV{'PPD'})) { # Clean the file name from weird characters which could cause # unexpected behaviour $ppdfile = removespecialchars($ENV{'PPD'}); # CUPS and PPR (RIP filter) use the "PPD" environment variable to # make the PPD file name available (we set CUPS here preliminarily, # in the next step we check for PPR) $spooler = 'cups'; }
# "PPR_VERSION": PPR if (defined($ENV{'PPR_VERSION'})) { # We have PPR $spooler = 'ppr'; }
# "PPR_RIPOPTS": PPR if (defined($ENV{'PPR_RIPOPTS'})) { # PPR 1.5 allows the user to specify options for the PPR RIP with the # "--ripopts" option on the "ppr" command line. They are provided to # the RIP via the "PPR_RIPOPTS" environment variable. # Clean the option string from weird characters which could cause # unexpected behaviour $optstr .= removespecialchars("$ENV{'PPR_RIPOPTS'} "); # We have PPR $spooler = 'ppr'; }
# "LPOPTS": Option settings for some LPD implementations (ex: GNUlpr) if (defined($ENV{'LPOPTS'})) { my @lpopts = split(/,/, removespecialchars($ENV{'LPOPTS'})); foreach my $opt (@lpopts) { $opt =~ s/^\s+//; $opt =~ s/\s+$//; if ($opt =~ /\s+/) { $opt = "\"$opt\""; } $optstr .= "$opt "; } # We have an LPD which accepts "-o" for options $spooler = 'gnulpr'; }
## Named command line options
# We do not use Getopt::Long because it does not work when between the # option and the argument is no space ("-w80" instead of "-w 80"). This # happens in the command line of LPRng, but also users could type in # options this way when printing without spooler.
# Make one option string with a non-printable character as separator, # So we can parse it more easily.
# To avoid the separator to be in the options itselves, it is filters # out of the options. This does not break anything as having non # printable characters in the command line options does not make sense # nor is this needed. This way misinterpretation and even abuse is # prevented.
my $argstr = "\x01" . join("\x01", map { removeunprintables($_) } @ARGV) . "\x01";
# Debug mode activated via command line if ($argstr =~ s/\x01--debug\x01/\x01/) { $debug = 1; }
# Command line options for verbosity my $verbose = ($argstr =~ s/\x01-v\x01/\x01/); my $quiet = ($argstr =~ s/\x01-q\x01/\x01/); my $show_docs = ($argstr =~ s/\x01-d\x01/\x01/); my $do_docs; my $cupscolorprofile;
# Where to send debugging log output to my $logh;
if ($debug) { # Grotesquely unsecure; use for debugging only open LOG, "> ${logfile}.log"; $logh = *LOG;
use IO::Handle; $logh->autoflush(1); } elsif (($quiet) && (!$verbose)) { # Quiet mode, do not log open LOG, "> /dev/null"; $logh = *LOG;
use IO::Handle; $logh->autoflush(1); } else { # Default: log to STDERR $logh=*STDERR; }
## Start debug logging if ($debug) { # If we are not in debug mode, we do this later, as we must find out at # first which spooler is used. When printing without spooler we # suppress logging because foomatic-rip is called directly on the # command line and so we avoid logging onto the console. print $logh "foomatic-rip version $ripversion running...\n"; # Print the command line only in debug mode, Mac OS X adds very many # options so that CUPS cannot handle the output of the command line # in its log files. If CUPS encounters a line with more than 1024 # characters sent into its log files, it aborts the job with an error. if (($debug) || ($spooler ne 'cups')) { print $logh "called with arguments: '", join("', '",@ARGV), "'\n"; } }
## Continue with named options
# Check for LPRng first so we do not pick up bogus ppd files by the -p option if ($argstr =~ s/\x01--lprng\x01/\x01/) { # We have LPRng $spooler = 'lprng'; } # 'PRINTCAP_ENTRY' environment variable is : LPRng # the :ppd=/path/to/ppdfile printcap entry should be used if (defined($ENV{'PRINTCAP_ENTRY'})){ $spooler = 'lprng'; my( @pc); @pc = split( /\s*:\s*/, $ENV{'PRINTCAP_ENTRY'} ); shift @pc; foreach (@pc) { if( /^ppd=(.*)$/ or /^ppdfile=(.*)$/ ){ $ppdfile = removespecialchars($1) if $1; } } } elsif ($argstr =~ s/\x01--lprng\x01/\x01/g) { # We have LPRng $spooler = 'lprng'; }
# PPD file name given via the command line # allow duplicates, and use the last specified one while ( ($spooler ne 'lprng') and ($argstr =~ s/\x01-p(\x01|)([^\x01]+)\x01/\x01/)) { $ppdfile = removeshellescapes($2); } while ($argstr =~ s/\x01--ppd(\x01|=|)([^\x01]+)\x01/\x01/) { $ppdfile = removeshellescapes($2); }
# Check for LPD/GNUlpr by typical options which the spooler puts onto # the filter's command line (options "-w": text width, "-l": text # length, "-i": indent, "-x", "-y": graphics size, "-c": raw printing, # "-n": user name, "-h": host name) if ($argstr =~ s/\x01-h(\x01|)([^\x01]+)\x01/\x01/) { # We have LPD or GNUlpr if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) { $spooler = 'lpd'; } $jobhost = $2; } if ($argstr =~ s/\x01-n(\x01|)([^\x01]+)\x01/\x01/) { # We have LPD or GNUlpr if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) { $spooler = 'lpd'; } $jobuser = $2; } if (($argstr =~ s/\x01-w(\x01|)\d+\x01/\x01/) || ($argstr =~ s/\x01-l(\x01|)\d+\x01/\x01/) || ($argstr =~ s/\x01-x(\x01|)\d+\x01/\x01/) || ($argstr =~ s/\x01-y(\x01|)\d+\x01/\x01/) || ($argstr =~ s/\x01-i(\x01|)\d+\x01/\x01/) || ($argstr =~ s/\x01-c\x01/\x01/)) { # We have LPD or GNUlpr if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) { $spooler = 'lpd'; } }
# LPRng delivers the option settings via the "-Z" argument if ($argstr =~ s/\x01-Z(\x01|)([^\x01]+)\x01/\x01/) { my @lpopts = split(/,/, $2); foreach my $opt (@lpopts) { $opt =~ s/^\s+//; $opt =~ s/\s+$//; $opt = removeshellescapes($opt); if ($opt =~ /\s+/) { $opt = "\"$opt\""; } $optstr .= "$opt "; } # We have LPRng $spooler = 'lprng'; }
# Job title and options for stock LPD if ($argstr =~ s/\x01-[jJ](\x01|)([^\x01]+)\x01/\x01/) { # An LPD $jobtitle = removeshellescapes($2); # Classic LPD hack if ($spooler eq "lpd") { $optstr .= "$jobtitle "; } }
# Check for CPS if ($argstr =~ s/\x01--cps\x01/\x01/) { # We have cps $spooler = 'cps'; }
# Options for spooler-less printing, CPS, or PDQ while ($argstr =~ s/\x01-o(\x01|)([^\x01]+)\x01/\x01/) { my $opt = $2; $opt =~ s/^\s+//; $opt =~ s/\s+$//; $opt = removeshellescapes($opt); if ($opt =~ /\s+/) { $opt = "\"$opt\""; } $optstr .= "$opt "; # If we don't print as a PPR RIP or as a CPS filter, we print without # spooler (we check for PDQ later) if (($spooler ne 'ppr') && ($spooler ne 'cps')) { $spooler = 'direct'; } }
# Printer for spooler-less printing or PDQ if ($argstr =~ s/\x01-d(\x01|)([^\x01]+)\x01/\x01/) { $printer = removeshellescapes($2); } # Printer for spooler-less printing, PDQ, or LPRng if ($argstr =~ s/\x01-P(\x01|)([^\x01]+)\x01/\x01/) { $printer = removeshellescapes($2); }
# Were we called from a PDQ wrapper? if ($argstr =~ s/\x01--pdq\x01/\x01/) { # We have PDQ $spooler = 'pdq'; }
# Were we called to build the PDQ driver declaration file? # "--appendpdq=<file>" appends the data to the <file>, # "--genpdq=<file>" creates/overwrites <file> for the data, and # "--genpdq" writes to standard output my $genpdqfile = ""; if (($argstr =~ s/\x01--(gen)(raw|)pdq(\x01|=|)([^\x01]*)\x01/\x01/) || ($argstr =~ s/\x01--(append)(raw|)pdq(\x01|=|)([^\x01]+)\x01/\x01/)) { # Determine output file name if (!$4) { $genpdqfile = ">&STDOUT"; } else { if ($1 eq 'gen') { $genpdqfile = "> " . removeshellescapes($4); } else { $genpdqfile = ">> " . removeshellescapes($4); } } # Do we want to have a PDQ driver declaration for a raw printer? if ($2 eq 'raw') { my $time = time(); my @pdqfile = "driver \"Raw-Printer-$time\" { # This PDQ driver declaration file was generated automatically by # foomatic-rip to allow raw (filter-less) printing. language_driver all { # We accept all file types and pass them through without any changes filetype_regx \"\" convert_exec { ln -s \$INPUT \$OUTPUT } } filter_exec { ln -s \$INPUT \$OUTPUT } }"; open PDQFILE, $genpdqfile or rip_die("Cannot write PDQ driver declaration file", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); print PDQFILE join('', @pdqfile); close PDQFILE; exit $EXIT_PRINTED; } # We have PDQ $spooler = 'pdq'; }
# remove extra spacing if running as LPRng filter $added_lf = "" if $spooler eq 'lprng';
## Command line arguments without name
# Remaining arguments my @rargs = split(/\x01/, $argstr); shift @rargs;
# Load definitions for PPR error messages, check whether we run as # PPR interface or as PPR RIP my( $ppr_printer, $ppr_address, $ppr_options, $ppr_jobbreak, $ppr_feedback, $ppr_codes, $ppr_jobname, $ppr_routing, $ppr_for, $ppr_filetype, $ppr_filetoprint ); if ($spooler eq 'ppr') { # Read interface.sh so we will know the correct exit codes and # also signal.sh for the signal codes my $deffound = 0; # Did we find one of the definition files my @definitions; for my $file (("lib/interface.sh", "lib/signal.sh")) { open FILE, "< $file" || do { print $logh "error opening $file.\n"; next; }; $deffound = 1; while(my $line = <FILE>) { # Translate the shell script to Perl if (($line !~ m/^\s*$/) && ($line !~ m/^\s*\#/)) { $line =~ s/^\s*([^\#\s]*)/\$$1;/; push (@definitions, $line); } } close FILE; }
if ($deffound) { # Apply the definitions loaded from PPR eval join('',@definitions) || do { print $logh "unable to evaluate definitions\n"; rip_die ("Error in definitions evaluation", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); }; }
# Check whether we run as a PPR interface (if not, we run as a PPR RIP) if (($rargs[3] =~ /^\s*\d\d?\s*$/) && ($rargs[5] =~ /^\s*\d\d?\s*$/) && (($#rargs == 10) || ($#rargs == 9) || ($#rargs == 7))) { # PPR calls interfaces with many command line parameters, # where the forth and the sixth is a small integer # number. In addition, we have 8 (PPR <= 1.31), 10 # (PPR>=1.32), 11 (PPR >= 1.50) command line parameters. # We also check whether the current working directory is a # PPR directory. # Get all command line parameters $ppr_printer = removeshellescapes($rargs[0]); $ppr_address = $rargs[1]; $ppr_options = removeshellescapes($rargs[2]); $ppr_jobbreak = $rargs[3]; $ppr_feedback = $rargs[4]; $ppr_codes = $rargs[5]; $ppr_jobname = removeshellescapes($rargs[6]); $ppr_routing = removeshellescapes($rargs[7]); $ppr_for = $rargs[8]; $ppr_filetype = $rargs[9]; $ppr_filetoprint = removeshellescapes($rargs[10]); # Common job parameters $printer = $ppr_printer; $jobtitle = $ppr_jobname; if ((!$jobtitle) && ($ppr_filetoprint)) { $jobtitle = $ppr_filetoprint; } $optstr .= "$ppr_options $ppr_routing"; # Get the path of the PPD file from the queue configuration $ppdfile = `LANG=en_US; ppad show $ppr_printer | grep PPDFile`; $ppdfile = removeshellescapes($ppdfile); $ppdfile =~ s/PPDFile:\s+//; if ($ppdfile !~ m!^/!) { $ppdfile = "../../share/ppr/PPDFiles/$ppdfile"; } chomp($ppdfile); # We have PPR and run as an interface $spooler = 'ppr_int'; } }
# CUPS my( $cups_jobid, $cups_user, $cups_jobtitle, $cups_copies, $cups_options, $cups_filename ); if ($spooler eq 'cups') {
# Use CUPS font path ("FontPath" in /etc/cups/cupsd.conf) if ($ENV{'CUPS_FONTPATH'}) { $ENV{'GS_LIB'} = $ENV{'CUPS_FONTPATH'} . ($ENV{'GS_LIB'} ? ":$ENV{'GS_LIB'}" : ""); } else { if ($ENV{'CUPS_DATADIR'}) { $ENV{'GS_LIB'} = "$ENV{'CUPS_DATADIR'}/fonts" . ($ENV{'GS_LIB'} ? ":$ENV{'GS_LIB'}" : ""); } }
# Get all command line parameters $cups_jobid = removeshellescapes($rargs[0]); $cups_user = removeshellescapes($rargs[1]); $cups_jobtitle = removeshellescapes($rargs[2]); $cups_copies = removeshellescapes($rargs[3]); $cups_options = removeshellescapes($rargs[4]); $cups_filename = removeshellescapes($rargs[5]);
# Common job parameters #$printer = $cups_printer; $jobid = $cups_jobid; $jobtitle = $cups_jobtitle; $jobuser = $cups_user; $copies = $cups_copies; $optstr .= $cups_options;
# Check for and handle inputfile vs stdin if ((defined($cups_filename)) && ($cups_filename) && ($cups_filename ne '-')) { # We get the input from a file @filelist = ($cups_filename); print $logh "Getting input from file $cups_filename\n"; } }
# LPD/LPRng/GNUlpr if (($spooler eq 'lpd') || ($spooler eq 'lprng' and !$ppdfile) || ($spooler eq 'gnulpr')) {
# Get PPD file name as the last command line argument $ppdfile = removeshellescapes($rargs[$#rargs]);
}
# No spooler, CPS, or PDQ if (($spooler eq 'direct') || ($spooler eq 'cps') || ($spooler eq 'pdq')) { # Which files do we want to print? @filelist = map { removeshellescapes($_) } @rargs; }
## Additional spooler-specific preparations
# CUPS
if ($spooler eq 'cups') {
# This piece of PostScript code (initial idea 2001 by Michael # Allerhand (michael.allerhand at ed dot ac dot uk, vastly # improved by Till Kamppeter in 2002) lets GhostScript output # the page accounting information which CUPS needs on standard # error. # Redesign by Helge Blischke (2004-11-17): # - As the PostScript job itself may define BeginPage and/or EndPage # procedures, or the alternate pstops filter may have inserted # such procedures, we make sure that the accounting routine # will safely coexist with those. To achieve this, we force # - the accountint stuff to be inserted at the very end of the # PostScript job's setup section, # - the accounting stuff just using the return value of the # existing EndPage procedure, if any (and providing a default one # if not). # - As PostScript jobs may contain calls to setpagedevice "between" # pages, e.g. to change media type, do in-job stapling, etc., # we cannot rely on the "showpage count since last pagedevice # activation" but instead count the physical pages by ourselves # (in a global dictionary).
if (defined $conf{ps_accounting}) { $ps_accounting = $conf{ps_accounting}; } $accounting_prolog = $ps_accounting ? "[{ %% Code for writing CUPS accounting tags on standard error
/cupsPSLevel2 % Determine whether we can do PostScript level 2 or newer systemdict/languagelevel 2 copy known{get exec}{pop pop 1}ifelse 2 ge def
cupsPSLevel2 { % in case of level 2 or higher currentglobal true setglobal % define a dictioary foomaticDict globaldict begin % in global VM and establish a /foomaticDict % pages count key there << /PhysPages 0 >>def end setglobal }if
/cupsGetNumCopies { % Read the number of Copies requested for the current % page cupsPSLevel2 { % PS Level 2+: Get number of copies from Page Device dictionary currentpagedevice /NumCopies get } { % PS Level 1: Number of copies not in Page Device dictionary null } ifelse % Check whether the number is defined, if it is \"null\" use #copies % instead dup null eq { pop #copies } if % Check whether the number is defined now, if it is still \"null\" use 1 % instead dup null eq { pop 1 } if } bind def
/cupsWrite { % write a string onto standard error (%stderr) (w) file exch writestring } bind def
/cupsFlush % flush standard error to make it sort of unbuffered { (%stderr)(w)file flushfile }bind def
cupsPSLevel2 { % In language level 2, we try to do something reasonable << /EndPage [ % start the array that becomes the procedure currentpagedevice/EndPage 2 copy known {get} % get the existing EndPage procedure {pop pop {exch pop 2 ne}bind}ifelse % there is none, define the default /exec load % make sure it will be executed, whatever it is /dup load % duplicate the result value { % true: a sheet gets printed, do accounting currentglobal true setglobal % switch to global VM ... foomaticDict begin % ... and access our special dictionary PhysPages 1 add % count the sheets printed (including this one) dup /PhysPages exch def % and save the value end % leave our dict exch setglobal % return to previous VM (PAGE: )cupsWrite % assemble and print the accounting string ... 16 string cvs cupsWrite % ... the sheet count ... ( )cupsWrite % ... a space ... cupsGetNumCopies % ... the number of copies ... 16 string cvs cupsWrite % ... (\\n)cupsWrite % ... a newline cupsFlush }/if load % false: current page gets discarded; do nothing ]cvx bind % make the array executable and apply bind >>setpagedevice } { % In language level 1, we do no accounting currently, as there is no global VM % the contents of which are undesturbed by save and restore. % If we may be sure that showpage never gets called inside a page related save / restore pair % we might implement an hack with showpage similar to the one above. }ifelse
} stopped cleartomark " : "";
# On which queue are we printing? # CUPS gives the PPD file the same name as the printer queue, # so we can get the queue name from the name of the PPD file. $ppdfile =~ m!^(.*/)([^/]+)\.ppd$!; $printer = $2; }
# No spooler, CPS, or PDQ
if (($spooler eq 'direct') || ($spooler eq 'cps') || ($spooler eq 'pdq')) {
# Path for personal Foomatic configuration my $user_default_path = "$ENV{'HOME'}/.foomatic";
if (!$ppdfile) { if (!$printer) { # No printer definition file selected, check whether we have a # default printer defined. for my $conf_file (("./.directconfig", "./directconfig", "./.config", "$user_default_path/direct/.config", "$user_default_path/direct.conf", "$configpath/direct/.config", "$configpath/direct.conf")) { if (open CONFIG, "< $conf_file") { while (my $line = <CONFIG>) { chomp $line; if ($line =~ /^default\s*:\s*([^:\s]+)\s*$/) { $printer = $1; last; } } close CONFIG; } if ($printer) { last; } } }
# Neither in a config file nor on the command line a printer was # selected. if (!$printer) { rip_die("No printer definition (option \"-P <name>\") " . "specified!", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } # Search for the PPD file # Search also common spooler-specific locations, this way a printer # configured under a certain spooler can also be used without # spooler
if (-r $printer) { $ppdfile = $printer; # CPS can have the PPD in the spool directory } elsif (($spooler eq 'cps') && (-r "/var/spool/lpd/${printer}/${printer}.ppd")) { $ppdfile = "/var/spool/lpd/${printer}/${printer}.ppd"; } elsif (($spooler eq 'cps') && (-r "/var/local/spool/lpd/${printer}/${printer}.ppd")) { $ppdfile = "/var/local/spool/lpd/${printer}/${printer}.ppd"; } elsif (($spooler eq 'cps') && (-r "/var/local/lpd/${printer}/${printer}.ppd")) { $ppdfile = "/var/local/lpd/${printer}/${printer}.ppd"; } elsif (($spooler eq 'cps') && (-r "/var/spool/lpd/${printer}.ppd")) { $ppdfile = "/var/spool/lpd/${printer}.ppd"; } elsif (($spooler eq 'cps') && (-r "/var/local/spool/lpd/${printer}.ppd")) { $ppdfile = "/var/local/spool/lpd/${printer}.ppd"; } elsif (($spooler eq 'cps') && (-r "/var/local/lpd/${printer}.ppd")) { $ppdfile = "/var/local/lpd/${printer}.ppd"; } elsif (-r "${printer}.ppd") { # current dir $ppdfile = "${printer}.ppd"; } elsif (-r "$user_default_path/${printer}.ppd") { # user dir $ppdfile = "$user_default_path/${printer}.ppd"; } elsif (-r "$configpath/direct/${printer}.ppd") { # system dir $ppdfile = "$configpath/direct/${printer}.ppd"; } elsif (-r "$configpath/${printer}.ppd") { # system dir $ppdfile = "$configpath/${printer}.ppd"; } elsif (-r "/etc/cups/ppd/${printer}.ppd") { # CUPS config dir $ppdfile = "/etc/cups/ppd/${printer}.ppd"; } elsif (-r "/usr/local/etc/cups/ppd/${printer}.ppd") { $ppdfile = "/usr/local/etc/cups/ppd/${printer}.ppd"; } elsif (-r "/usr/share/ppr/PPDFiles/${printer}.ppd") { # PPR PPDs $ppdfile = "/usr/share/ppr/PPDFiles/${printer}.ppd"; } elsif (-r "/usr/local/share/ppr/PPDFiles/${printer}.ppd") { $ppdfile = "/usr/local/share/ppr/PPDFiles/${printer}.ppd"; } else { rip_die ("There is no readable PPD file for the printer " . "$printer, is it configured?", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } } }
## Files to be printed (can be more than one for spooler-less printing)
# Empty file list -> print STDIN if ($#filelist < 0) { @filelist = ("<STDIN>"); }
# Check file list my $file; my $filecnt = 0; for $file (@filelist) { if ($file ne "<STDIN>") { if ($file =~ /^-/) { rip_die ("Invalid argument: $file", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } elsif (! -r $file) { print $logh "File $file does not exist/is not readable\n"; splice(@filelist, $filecnt, 1); $filecnt --; } } $filecnt ++; }
## When we print without spooler or with CPS do not log onto STDERR unless ## the "-v" ('Verbose') is set or the debug mode is used if ((($spooler eq 'direct') || ($spooler eq 'cps') || ($genpdqfile)) && (!$verbose) && (!$debug)) { close $logh; open LOG, "> /dev/null"; $logh = *LOG;
use IO::Handle; $logh->autoflush(1); }
## Start logging if (!$debug) { # If we are in debug mode, we do this earlier. print $logh "foomatic-rip version $ripversion running...\n"; # Print the command line only in debug mode, Mac OS X adds very many # options so that CUPS cannot handle the output of the command line # in its log files. If CUPS encounters a line with more than 1024 # characters sent into its log files, it aborts the job with an error. if (($debug) || ($spooler ne 'cups')) { print $logh "called with arguments: '", join("', '",@ARGV), "'\n"; } }
## PPD file
# Load the PPD file and build a data structure for the renderer's # command line and the options open PPD, "< $ppdfile" || do { print $logh "error opening $ppdfile.\n"; rip_die ("Unable to open PPD file $ppdfile", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); };
print $logh "Parsing PPD file ...\n";
my $dat = {}; # data structure for the options my $currentargument = ""; # We are currently reading this argument
# If we have an old Foomatic 2.0.x PPD file, read its built-in Perl # data structure into @datablob and the default values in %ppddefaults # Then delete the $dat structure, replace it by the one "eval"ed from # @datablob, and correct the default settings according to the ones of # the main PPD structure my @datablob; my $jclprefixset = 0;
# Parse the PPD file sub undossify( $ ); while(<PPD>) { # foomatic-rip should also work with PPD file downloaded under Windows. $_ = undossify($_); # Parse keywords if (m!^\*NickName:\s*\"(.*)$!) { # "*NickName: <code>" my $line = $1; # Store the value # Code string can have multiple lines, read all of them my $cmd = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $cmd .= substr($line, 0, -2); } else { # line ends here $cmd .= "$line\n"; } # Read next line $line = <PPD>; chomp $line; } $line =~ m!^([^\"]*)\"!; $cmd .= $1; $model = unhtmlify($cmd); } elsif (m!^\*FoomaticIDs:\s*(\S+)\s+(\S+)\s*$!) { # "*FoomaticIDs: <printer ID> <driver ID>" my $id = $1; my $driver = $2; # Store the values $dat->{'id'} = $id; $dat->{'driver'} = $driver; } elsif (m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) { # "*FoomaticRIPPostPipe: <code>" my $line = $1; # Store the value # Code string can have multiple lines, read all of them my $cmd = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $cmd .= substr($line, 0, -2); } else { # line ends here $cmd .= "$line\n"; } # Read next line $line = <PPD>; chomp $line; } $line =~ m!^([^\"]*)\"!; $cmd .= $1; $postpipe = unhtmlify($cmd); } elsif (m!^\*FoomaticRIPCommandLine:\s*\"(.*)$!) { # "*FoomaticRIPCommandLine: <code>" my $line = $1; # Store the value # Code string can have multiple lines, read all of them my $cmd = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $cmd .= substr($line, 0, -2); } else { # line ends here $cmd .= "$line\n"; } # Read next line $line = <PPD>; chomp $line; } $line =~ m!^([^\"]*)\"!; $cmd .= $1; $dat->{'cmd'} = unhtmlify($cmd); } elsif (m!^\*cupsFilter:\s*\"(.*)$!) { # "*cupsFilter: <code>" my $line = $1; # Store the value # Code string can have multiple lines, read all of them my $cmd = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $cmd .= substr($line, 0, -2); } else { # line ends here $cmd .= "$line\n"; } # Read next line $line = <PPD>; chomp $line; } $line =~ m!^([^\"]*)\"!; $cmd .= $1; my $cupsfilterline = unhtmlify($cmd); if ($cupsfilterline =~ /^\s*(\S+)\s+\d+\s+(\S+)\s*$/) { print $logh "*cupsFilter: \"$cupsfilterline\"\n"; # Make a hash by mime type for all CUPS filters set in this PPD $dat->{'cupsfilter'}{$1} = $2; } } elsif (m!^\*CustomPageSize\s+True:\s*\"(.*)$!) { # "*CustomPageSize True: <code>" my $setting = "Custom"; my $translation = "Custom Size"; my $line = $1; # Make sure that the argument is in the data structure checkarg ($dat, "PageSize"); checkarg ($dat, "PageRegion"); # Make sure that the setting is in the data structure checksetting ($dat, "PageSize", $setting); checksetting ($dat, "PageRegion", $setting); $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'comment'} = $translation; $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'comment'} = $translation; # Store the value # Code string can have multiple lines, read all of them my $code = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $code .= substr($line, 0, -2); } else { # line ends here $code .= "$line\n"; } # Read next line $line = <PPD>; chomp $line; } $line =~ m!^([^\"]*)\"!; $code .= $1; if ($code !~ m!^%% FoomaticRIPOptionSetting!m) { $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'driverval'} = $code; $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'driverval'} = $code; } } elsif (m!^\*(JCL|)OpenUI\s+\*([^:]+):\s*(\S+)\s*$!) { # "*[JCL]OpenUI *<option>[/<translation>]: <type>" my $argnametrans = $2; my $argtype = $3; my $argname; my $translation = ""; if ($argnametrans =~ m!^([^:/\s]+)/([^:]*)$!) { $argname = $1; $translation = $2; } else { $argname = $argnametrans; } # Make sure that the argument is in the data structure checkarg ($dat, $argname); # Store the values $dat->{'args_byname'}{$argname}{'comment'} = $translation; # Set the argument type only if not defined yet, a # definition in "*FoomaticRIPOption" has priority if ( !($dat->{'args_byname'}{$argname}{'type'}) ) { if ($argtype eq "PickOne") { $dat->{'args_byname'}{$argname}{'type'} = 'enum'; } elsif ($argtype eq "PickMany") { $dat->{'args_byname'}{$argname}{'type'} = 'pickmany'; } elsif ($argtype eq "Boolean") { $dat->{'args_byname'}{$argname}{'type'} = 'bool'; } } # Mark in which argument we are currently, so that we can find # the entries for the choices $currentargument = $argname; } elsif (m!^\*(JCL|)CloseUI:\s+\*([^:/\s]+)\s*$!) { # "*[JCL]CloseUI *<option>" my $argname = $2; # Unmark the current argument to do not mis-interpret any keywords # as choices $currentargument = ""; } elsif ((m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s*$!) || (m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s+(\S+)\s*$!)){ # "*FoomaticRIPOption <option>: <type> <style> <spot> [<order>]" # <order> only used for 1-choice enum options my $argname = $1; my $argtype = $2; my $argstyle = $3; my $spot = $4; my $order = $5; # Make sure that the argument is in the data structure checkarg ($dat, $argname); # Store the values $dat->{'args_byname'}{$argname}{'type'} = $argtype; if ($argstyle eq "PS") { $dat->{'args_byname'}{$argname}{'style'} = 'G'; } elsif ($argstyle eq "CmdLine") { $dat->{'args_byname'}{$argname}{'style'} = 'C'; } elsif ($argstyle eq "JCL") { $dat->{'args_byname'}{$argname}{'style'} = 'J'; $dat->{'jcl'} = 1; } elsif ($argstyle eq "Composite") { $dat->{'args_byname'}{$argname}{'style'} = 'X'; } $dat->{'args_byname'}{$argname}{'spot'} = $spot; # $order only defined here for 1-choice enum options if ($order) { $dat->{'args_byname'}{$argname}{'order'} = $order; } } elsif (m!^\*FoomaticRIPOptionPrototype\s+([^/:\s]+):\s*\"(.*)$!) { # "*FoomaticRIPOptionPrototype <option>: <code>" # Used for numerical and string options only my $argname = $1; my $line = $2; # Make sure that the argument is in the data structure checkarg ($dat, $argname); # Store the value # Code string can have multiple lines, read all of them my $proto = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $proto .= substr($line, 0, -2); } else { # line ends here $proto .= "$line\n"; } # Read next line $line = <PPD>; chomp $line; } $line =~ m!^([^\"]*)\"!; $proto .= $1; $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($proto); } elsif (m!^\*FoomaticRIPOptionRange\s+([^/:\s]+):\s*(\S+)\s+(\S+)\s*$!) { # "*FoomaticRIPOptionRange <option>: <min> <max>" # Used for numerical options only my $argname = $1; my $min = $2; my $max = $3; # Make sure that the argument is in the data structure checkarg ($dat, $argname); # Store the values $dat->{'args_byname'}{$argname}{'min'} = $min; $dat->{'args_byname'}{$argname}{'max'} = $max; } elsif (m!^\*FoomaticRIPOptionMaxLength\s+([^/:\s]+):\s*(\S+)\s*$!) { # "*FoomaticRIPOptionMaxLength <option>: <length>" # Used for string options only my $argname = $1; my $maxlength = $2; # Make sure that the argument is in the data structure checkarg ($dat, $argname); # Store the value $dat->{'args_byname'}{$argname}{'maxlength'} = $maxlength; } elsif (m!^\*FoomaticRIPOptionAllowedChars\s+([^/:\s]+):\s*\"(.*)$!) { # "*FoomaticRIPOptionAllowedChars <option>: <code>" # Used for string options only my $argname = $1; my $line = $2; # Store the value # Code string can have multiple lines, read all of them my $code = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $code .= substr($line, 0, -2); } else { # line ends here $code .= "$line\n"; } # Read next line $line = <PPD>; chomp $line; } $line =~ m!^([^\"]*)\"!; $code .= $1; # Make sure that the argument is in the data structure checkarg ($dat, $argname); # Store the value $dat->{'args_byname'}{$argname}{'allowedchars'} = unhtmlify($code); } elsif (m!^\*FoomaticRIPOptionAllowedRegExp\s+([^/:\s]+):\s*\"(.*)$!) { # "*FoomaticRIPOptionAllowedRegExp <option>: <code>" # Used for string options only my $argname = $1; my $line = $2; # Store the value # Code string can have multiple lines, read all of them my $code = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $code .= substr($line, 0, -2); } else { # line ends here $code .= "$line\n"; } # Read next line $line = <PPD>; chomp $line; } $line =~ m!^([^\"]*)\"!; $code .= $1; # Make sure that the argument is in the data structure checkarg ($dat, $argname); # Store the value $dat->{'args_byname'}{$argname}{'allowedregexp'} = unhtmlify($code); } elsif (m!^\*OrderDependency:\s*(\S+)\s+(\S+)\s+\*([^:/\s]+)\s*$!) { # "*OrderDependency: <order> <section> *<option>" my $order = $1; my $section = $2; my $argname = $3; # Make sure that the argument is in the data structure checkarg ($dat, $argname); # Store the values $dat->{'args_byname'}{$argname}{'order'} = $order; $dat->{'args_byname'}{$argname}{'section'} = $section; } elsif (m!^\*Default([^/:\s]+):\s*([^/:\s]+)\s*$!) { # "*Default<option>: <value>" my $argname = $1; my $default = $2; # Make sure that the argument is in the data structure checkarg ($dat, $argname); # Store the value $dat->{'args_byname'}{$argname}{'default'} = $default; } elsif (m!^\*FoomaticRIPDefault([^/:\s]+):\s*([^/:\s]+)\s*$!) { # "*FoomaticRIPDefault<option>: <value>" # Used for numerical options only my $argname = $1; my $default = $2; # Make sure that the argument is in the data structure checkarg ($dat, $argname); # Store the value $dat->{'args_byname'}{$argname}{'fdefault'} = $default; } elsif (m!^\*$currentargument\s+([^:]+):\s*\"(.*)$!) { # "*<option> <choice>[/<translation>]: <code>" my $settingtrans = $1; my $line = $2; my $translation = ""; my $setting = ""; if ($settingtrans =~ m!^([^:/\s]+)/([^:]*)$!) { $setting = $1; $translation = $2; } else { $setting = $settingtrans; } # Make sure that the argument is in the data structure checkarg ($dat, $currentargument); # Make sure that the setting is in the data structure (enum options) my $bool = ($dat->{'args_byname'}{$currentargument}{'type'} eq 'bool'); if ($bool) { if (lc($setting) eq "true") { if (!$dat->{'args_byname'}{$currentargument}{'comment'}) { $dat->{'args_byname'}{$currentargument}{'comment'} = $translation; } $dat->{'args_byname'}{$currentargument}{'comment_true'} = $translation; } else { $dat->{'args_byname'}{$currentargument}{'comment_false'} = $translation; } } else { checksetting ($dat, $currentargument, $setting); # Make sure that this argument has a default setting, even if # none is defined in this PPD file if (!$dat->{'args_byname'}{$currentargument}{'default'}) { $dat->{'args_byname'}{$currentargument}{'default'} = $setting; } $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'comment'} = $translation; } # Store the value # Code string can have multiple lines, read all of them my $code = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $code .= substr($line, 0, -2); } else { # line ends here $code .= "$line\n"; } # Read next line $line = <PPD>; chomp $line; } $line =~ m!^([^\"]*)\"!; $code .= $1; if ($code !~ m!^%% FoomaticRIPOptionSetting!) { if ($bool) { if (lc($setting) eq "true") { $dat->{'args_byname'}{$currentargument}{'proto'} = $code; } else { $dat->{'args_byname'}{$currentargument}{'protof'} = $code; } } else { $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'driverval'} = $code; } } } elsif ((m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+)=([^/:=\s]+):\s*\"(.*)$!) || (m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+):\s*\"(.*)$!)) { # "*FoomaticRIPOptionSetting <option>[=<choice>]: <code>" # For boolean options <choice> is not given my $argname = $1; my $setting = $2; my $line = $3; my $bool = 0; if (!$line) { $line = $setting; $bool = 1; } # Make sure that the argument is in the data structure checkarg ($dat, $argname); # Make sure that the setting is in the data structure (enum options) if (!$bool) { checksetting ($dat, $argname, $setting); # Make sure that this argument has a default setting, even if # none is defined in this PPD file if (!$dat->{'args_byname'}{$argname}{'default'}) { $dat->{'args_byname'}{$argname}{'default'} = $setting; } } # Store the value # Code string can have multiple lines, read all of them my $code = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $code .= substr($line, 0, -2); } else { # line ends here $code .= "$line\n"; } # Read next line $line = <PPD>; chomp $line; } $line =~ m!^([^\"]*)\"!; $code .= $1; if ($bool) { $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($code); } else { $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting}{'driverval'} = unhtmlify($code); } } elsif (m!^\*(Foomatic|)JCL(Begin|ToPSInterpreter|End|Prefix):\s*\"(.*)$!) { # "*(Foomatic|)JCL(Begin|ToPSInterpreter|End|Prefix): <code>" # The printer supports PJL/JCL when there is such a line $dat->{'jcl'} = 1; my $item = $2; my $line = $3; # Store the value # Code string can have multiple lines, read all of them my $code = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $code .= substr($line, 0, -2); } else { # line ends here $code .= "$line\n"; } # Read next line $line = <PPD>; chomp $line; } $line =~ m!^([^\"]*)\"!; $code .= $1; if ($item eq 'Begin') { $jclbegin = unhexify($code); $jclprefix = "" if (!$jclprefixset) && ($jclbegin !~ /PJL/s); } elsif ($item eq 'ToPSInterpreter') { $jcltointerpreter = unhexify($code); } elsif ($item eq 'End') { $jclend = unhexify($code); } elsif ($item eq 'Prefix') { $jclprefix = unhexify($code); $jclprefixset = 1; } } elsif (m!^\*\% COMDATA \#(.*)$!) { # If we have an old Foomatic 2.0.x PPD file, collect its Perl data push (@datablob, $1); } } close PPD;
# If we have an old Foomatic 2.0.x PPD file use its Perl data structure if ($#datablob >= 0) { print $logh "${added_lf}You are using an old Foomatic 2.0 PPD file, consider " . "upgrading.${added_lf}\n"; my $VAR1; if (eval join('',@datablob)) { # Overtake default settings from the main structure of the PPD file for my $arg (@{$dat->{'args'}}) { if ($arg->{'default'}) { $VAR1->{'argsbyname'}{$arg->{'name'}}{'default'} = $arg->{'default'}; } } undef $dat; $dat = $VAR1; $dat->{'jcl'} = $dat->{'pjl'}; } else { # Perl structure broken print $logh "${added_lf}Unable to evaluate datablob, print job may come " . "out incorrectly or not at all.${added_lf}\n"; } }
## We do not need to parse the PostScript job when we don't have ## any options. If we have options, we must check whether the ## default settings from the PPD file are valid and correct them ## if nexessary.
my $dontparse = 0; if ((!defined(@{$dat->{'args'}})) || ($#{$dat->{'args'}} < 0)) { # We don't have any options, so we do not need to parse the # PostScript data $dontparse = 1; } else { # Let the default value of a boolean option being 0 or 1 instead of # "True" or "False", range-check the defaults of all options and # issue warnings if the values are not valid checkoptions($dat, 'default');
# Adobe's PPD specs do not support numerical # options. Therefore the numerical options are mapped to # enumerated options in the PPD file and their characteristics # as a numerical option are stored in "*Foomatic..." # keywords. A default must be between the enumerated # fixed values. The default # value must be given by a "*FoomaticRIPDefault<option>: # <value>" line in the PPD file. But this value is only valid # if the "official" default given by a "*Default<option>: # <value>" line (it must be one of the enumerated values) # points to the enumerated value which is closest to this # value. This way a user can select a default value with a # tool only supporting PPD files but not Foomatic extensions. # This tool only modifies the "*Default<option>: <value>" line # and if the "*FoomaticRIPDefault<option>: <value>" had always # priority, the user's change in "*Default<option>: <value>" # would have no effect.
for my $arg (@{$dat->{'args'}}) { if ($arg->{'fdefault'}) { if ($arg->{'default'}) { if ($arg->{'type'} =~ /^(int|float)$/) { if ($arg->{'fdefault'} < $arg->{'min'}) { $arg->{'fdefault'} = $arg->{'min'}; } if ($arg->{'fdefault'} > $arg->{'max'}) { $arg->{'fdefault'} = $arg->{'max'}; } my $mindiff = abs($arg->{'max'} - $arg->{'min'}); my $closestvalue; for my $val (@{$arg->{'vals'}}) { if (abs($arg->{'fdefault'} - $val->{'value'}) < $mindiff) { $mindiff = abs($arg->{'fdefault'} - $val->{'value'}); $closestvalue = $val->{'value'}; } } if (($arg->{'default'} == $closestvalue) || (abs($arg->{'default'} - $closestvalue) / $closestvalue < 0.001)) { $arg->{'default'} = $arg->{'fdefault'}; } } } else { $arg->{'default'} = $arg->{'fdefault'}; } } } }
# Is our PPD for a CUPS raster driver if (my $cupsfilter = $dat->{'cupsfilter'}{"application/vnd.cups-raster"}) {
# Search filter in cupsfilterpath # The %Y is a placeholder for the option settings my $havefilter = 0; for (split(':', $cupsfilterpath)) { if (-x "$_/$cupsfilter") { $havefilter=1; $cupsfilter = "$_/$cupsfilter 0 '' '' 0 '%Y%X'"; last; } }
if (!$havefilter) {
# We do not have the required filter, so we assume that # rendering this job is supposed to be done on a remote # server. So we do not define a renderer command line and # embed only the option settings (as we had a PostScript # printer). This way the settings are # taken into account # when the job is rendered on the server. print $logh "${added_lf}CUPS filter for this PPD file not found " . "assuming that job will be rendered on a remote server. Only " . "the PostScript of the options will be inserted into the " . "PostScript data stream.${added_lf}\n";
} else {
# use pstoraster script if available, otherwise run GhostScript # directly my $pstoraster = "pstoraster"; my $havepstoraster = 0; for (split(':', $cupsfilterpath)) { if (-x "$_/$pstoraster") { $havepstoraster=1; $pstoraster = "$_/$pstoraster 0 '' '' 0 '%X'"; last; } }
if (!$havepstoraster) {
# Build GhostScript command line $pstoraster = "gs -dQUIET -dDEBUG -dPARANOIDSAFER -dNOPAUSE -dBATCH -dNOMEDIAATTRS -sDEVICE=cups -sOutputFile=-%W -" }
# build GhostScript/CUPS driver command line $dat->{'cmd'} = "$pstoraster | $cupsfilter";
# Set environment variables $ENV{'PPD'} = $ppdfile; } }
# Was the RIP command line defined in the PPD file? If not, we assume a # PostScript printer and do not render/translate the input data if (!defined($dat->{'cmd'})) { $dat->{'cmd'} = "cat%A%B%C%D%E%F%G%H%I%J%K%L%M%Z"; if ($dontparse) { # No command line, no options, we have a raw queue, don't check # whether the input is PostScript and ignore the "docs" option, # simply pass the input data to the backend. $dontparse = 2; $model = "Raw queue"; } }
## Summary for debugging print $logh "${added_lf}Parameter Summary\n"; print $logh "-----------------${added_lf}\n"; print $logh "Spooler: $spooler\n"; print $logh "Printer: $printer\n"; print $logh "PPD file: $ppdfile\n"; print $logh "Printer model: $model\n"; # Print the options string only in debug mode, Mac OS X adds very many # options so that CUPS cannot handle the output of the option string # in its log files. If CUPS encounters a line with more than 1024 characters # sent into its log files, it aborts the job with an error. if (($debug) || ($spooler ne 'cups')) { print $logh "Options: $optstr\n"; } print $logh "Job title: $jobtitle\n"; print $logh "File(s) to be printed: ${added_lf}@filelist${added_lf}\n"; print $logh "GhostScript extra search path ('GS_LIB'): $ENV{'GS_LIB'}\n" if $ENV{'GS_LIB'};
## Parse options from command line ($optstr)
# Before we start, save the defaults for printing documentation pages
copyoptions($dat, 'default', 'userval');
# The options are "foo='bar nut'", "foo", "nofoo", "'bar nut'", or # "foo:'bar nut'" (when GPR was used) all with spaces between... # In addition they can be preceeded by page ranges, separated with a # colon.
my @opts;
# Variable for PPR's backend interface name (parallel, tcpip, atalk, ...)
my $backend = "";
# Array to collect unknown options so that they can get passed to the # backend interface of PPR. For other spoolers we ignore them.
my @backendoptions = ();
# "foo='bar nut'" while ($optstr =~ s!(((even|odd|[\d,-]+):|)\w+=[\'\"].*?[\'\"]) ?!!i) { push (@opts, $1); }
# "foo:'bar nut'" (GPR separates option and setting with a colon ":") while ($optstr =~ s!(((even|odd|[\d,-]+):|)\w+:[\'\"].*?[\'\"]) ?!!i) { #while ($optstr =~ s!(\w+=[\'\"].*?[\'\"])!!i) { push (@opts, $1); }
# "'bar nut'", "'foo=bar nut'", "'foo:bar nut'" while ($optstr =~ s!([\'\"].+?[\'\"]) ?!!) { my $opt = $1; $opt =~ s/[\'\"]//g; # Make only sure that we didn't quote # the option for a second time when we read # rge options from the command line or # environment variable push (@opts, $opt); }
# "foo", "nofoo" push(@opts, split(/ /,$optstr));
# Now actually process those pesky options...
for (@opts) { print $logh "Pondering option '$_'\n";
# "docs" option to print help page if ((lc($_) =~ /^\s*docs\s*$/) || (lc($_) =~ /^\s*docs\s*=\s*true\s*$/)) { # The second one is necessary becuase CUPS 1.1.15 or newer sees # "docs" as boolean option and modifies it to "docs=true" $do_docs = 1; next; }
# "profile" option to supply a color correction profile to a # CUPS raster driver if (lc($_) =~ /^\s*profile=(\S+)\s*$/) { $cupscolorprofile=$1; $dat->{'cmd'} =~ s!\%X!profile=$cupscolorprofile!g; $dat->{'cmd'} =~ s!\%W! -c\"<</cupsProfile($cupscolorprofile)>>setpagedevice\"!g; next; }
# Is the command line option limited to certain page ranges? If so, # mark the setting with a hash key containing the ranges my $optionset; if (s/^(even|odd|[\d,-]+)://i) { $optionset = "pages:$1"; } else { $optionset = 'userval'; }
my $arg; if ((m!([^=]+)=\'?(.*)\'?!) || (m!([^=:]+):\'?(.*)\'?!)) { my ($aname, $avalue) = ($1, $2);
if (($optionset =~ /pages/) && ($arg = argbyname($aname)) && ((!defined($arg->{'section'})) || ($arg->{'section'} !~ /^(Any|Page)Setup/))) { print $logh "This option is not a \"PageSetup\" or " . "\"AnySetup\" option, so it cannot be restricted to " . "a page range.\n"; next; }
# At first look for the "backend" option to determine the PPR # backend to use if (($aname =~ m!^backend$!i) && ($spooler eq 'ppr_int')) { # Backend interface name $backend = $avalue; } elsif ($aname =~ m!^media$!i) {
# Standard arguments? # media=x,y,z # sides=one|two-sided-long|short-edge
# 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.
my @values = split(',',$avalue); for (@values) { my $val; if ($dat->{'args_byname'}{'PageSize'} and $val=valbyname($dat->{'args_byname'}{'PageSize'},$_)) { $dat->{'args_byname'}{'PageSize'}{$optionset} = $val->{'value'}; # Keep "PageRegion" in sync if ($dat->{'args_byname'}{'PageRegion'} and $val=valbyname($dat->{'args_byname'}{'PageRegion'}, $_)) { $dat->{'args_byname'}{'PageRegion'}{$optionset} = $val->{'value'}; } } elsif ($dat->{'args_byname'}{'PageSize'} and /^Custom/) { $dat->{'args_byname'}{'PageSize'}{$optionset} = $_; # Keep "PageRegion" in sync if ($dat->{'args_byname'}{'PageRegion'}) { $dat->{'args_byname'}{'PageRegion'}{$optionset} = $_; } } elsif ($dat->{'args_byname'}{'MediaType'} and $val=valbyname($dat->{'args_byname'}{'MediaType'}, $_)) { $dat->{'args_byname'}{'MediaType'}{$optionset} = $val->{'value'}; } elsif ($dat->{'args_byname'}{'InputSlot'} and $val=valbyname($dat->{'args_byname'}{'InputSlot'}, $_)) { $dat->{'args_byname'}{'InputSlot'}{$optionset} = $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'}{$optionset} = 1; } } else { print $logh "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'})) { # We set "Duplex" to '1' here, the real argument setting # will be done later $dat->{'args_byname'}{'Duplex'}{$optionset} = '1'; # Check the binding: "long edge" or "short edge" if ($avalue =~ m!long-edge!i) { if (defined($dat->{'args_byname'}{'Binding'})) { $dat->{'args_byname'}{'Binding'}{$optionset} = $dat->{'args_byname'}{'Binding'}{'vals_byname'}{'LongEdge'}{'value'}; } else { $dat->{'args_byname'}{'Duplex'}{$optionset} = 'LongEdge'; } } elsif ($avalue =~ m!short-edge!i) { if (defined($dat->{'args_byname'}{'Binding'})) { $dat->{'args_byname'}{'Binding'}{$optionset} = $dat->{'args_byname'}{'Binding'}{'vals_byname'}{'ShortEdge'}{'value'}; } else { $dat->{'args_byname'}{'Duplex'}{$optionset} = 'ShortEdge'; } } } } elsif ($avalue =~ m!^one-sided!i) { if (defined($dat->{'args_byname'}{'Duplex'})) { # We set "Duplex" to '0' here, the real argument setting # will be done later $dat->{'args_byname'}{'Duplex'}{$optionset} = '0'; } }
# 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)) { if (defined(my $newvalue = checkoptionvalue($dat, $aname, $avalue, 0))) { # If the choice is valid, use it, otherwise # ignore it. $arg->{$optionset} = $newvalue; # If this argument is PageSize or PageRegion, # also set the other syncpagesize($dat, $aname, $avalue, $optionset); } else { # Invalid choice, make log entry print $logh "Invalid choice $aname=$avalue.\n"; } } elsif ($spooler eq 'ppr_int') { # Unknown option, pass it to PPR's backend interface push (@backendoptions, "$aname=$avalue"); } else { # Unknown option, make log entry print $logh "Unknown option $aname=$avalue.\n"; } } } elsif (m!^([\d\.]+)x([\d\.]+)([A-Za-z]*)$!) { my ($w, $h, $u) = ($1, $2, $3); # Custom paper size if (($w != 0) && ($h != 0) && ($arg=argbyname("PageSize")) && (defined($arg->{'vals_byname'}{'Custom'}))) { $arg->{$optionset} = "Custom.${w}x${h}${u}"; # Keep "PageRegion" in sync if ($dat->{'args_byname'}{'PageRegion'}) { $dat->{'args_byname'}{'PageRegion'}{$optionset} = $arg->{$optionset}; } } } elsif ((m!^\s*no(.+)\s*$!i) and ($arg=argbyname($1))) { # standard bool args: # landscape; what to do here? # duplex; we should just handle this one OK now? $arg->{$optionset} = 0; } elsif (m!^\s*(.+)\s*$!) { if ($arg=argbyname($1)) { $arg->{$optionset} = 1; } else { print $logh "Unknown boolean option \"$1\".\n"; } } } $do_docs = 1 if( $show_docs );
## Were we called to build the PDQ driver declaration file? my @pdqfile; if ($genpdqfile) { @pdqfile = buildpdqdriver($dat, 'userval'); open PDQFILE, $genpdqfile or rip_die("Cannot write PDQ driver declaration file", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); print PDQFILE join('', @pdqfile); close PDQFILE; exit $EXIT_PRINTED; }
## Set the $postpipe
# $postpipe when running as a PPR RIP if ($spooler eq 'ppr') { # The PPR RIP sends the data output to /dev/fd/3 instead of to STDOUT if (-w "/dev/fd/3") { $postpipe = "| cat - > /dev/fd/3"; } else { $postpipe = "| cat - >&3"; } }
# Set up PPR backend (if we run as a PPR interface). if ($spooler eq 'ppr_int') {
# Is the chosen backend installed and executable if (!-x "interfaces/$backend") { my $pwd = cwd; print $logh "The backend interface $pwd/interfaces/$backend " . "does not exist/is not executable!\n"; rip_die ("The backend interface $pwd/interfaces/$backend " . "does not exist/is not executable!", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); }
# foomatic-rip cannot use foomatic-rip as backend if ($backend eq "foomatic-rip") { print $logh "\"foomatic-rip\" cannot use itself as backend " . "interface!\n"; ppr_die ($ppr_printer, "\"foomatic-rip\" cannot use itself as backend interface!", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); }
# Put the backend interface into the $postpipe $postpipe = "| ( interfaces/$backend \"$ppr_printer\" ". "\"$ppr_address\" \"" . join(" ",@backendoptions) . "\" \"$ppr_jobbreak\" \"$ppr_feedback\" " . "\"$ppr_codes\" \"$ppr_jobname\" \"$ppr_routing\" " . "\"$ppr_for\" \"\" )";
}
# CUPS and PDQ have their own backends, they do not need a $postpipe if (($spooler eq 'cups') || ($spooler eq 'pdq')) { # No $postpipe for CUPS or PDQ, even if one is defined in the PPD file $postpipe = ""; }
# CPS needs always a $postpipe, set the default one for local printing # if none is set if (($spooler eq 'cps') && !$postpipe) { $postpipe = "| cat - > \$LPDDEV"; }
if ($postpipe) { print $logh "${added_lf}Output will be redirected to:\n$postpipe${added_lf}\n"; }
## Print documentation page when asked for my ($docgeneratorhandle, $docgeneratorpid,$retval); if ($do_docs) { # Don't print the supplied files, STDIN will be redirected to the # documentation page generator @filelist = ("<STDIN>"); # Start the documentation page generator ($docgeneratorhandle, $docgeneratorpid) = getdocgeneratorhandle($dat); if ($retval != $EXIT_PRINTED) { rip_die ("Error opening documentation page generator", $retval); } # Read the further data from the documentation page generator and # not from STDIN if (!close STDIN && $! != $ESPIPE) { rip_die ("Couldn't close STDIN", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if (!open (STDIN, "<&$docgeneratorhandle")) { rip_die ("Couldn't dup \$docgeneratorhandle", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if( $show_docs ){ while( <$docgeneratorhandle> ){ print; } exit(0); } }
## In debug mode save the data supposed to be fed into the ## renderer also into a file, reset the file here
if ($debug) { system("> ${logfile}.ps"); }
## From here on we have to repeat all the rest of the program for ## every file to print
for $file (@filelist) {
print $logh "${added_lf}================================================\n${added_lf}". "File: $file\n${added_lf}" . "================================================\n${added_lf}";
## If we do not print standard input, open the file to print if ($file ne "<STDIN>") { if (! -r $file) { print $logh "File $file missing or not readable, skipping.\n"; next; } close STDIN; open STDIN, "< $file" || do { print $logh "Cannot open $file, skipping.\n"; next; } }
## Do we have a raw queue if ($dontparse == 2) { # Raw queue, simply pass the input into the $postpipe (or to STDOUT # when there is no $postpipe) print $logh "Raw printing, executing \"cat $postpipe\"${added_lf}\n"; system("cat $postpipe"); next; }
## First, for arguments with a default, stick the default in as ## the initial value for the "header" option set, this option set ## consists of the PPD defaults, the options specified on the ## command line, and the options set in the header part of the ## PostScript file (all before the first page begins).
copyoptions($dat, 'userval', 'header');
## Next, examine the PostScript job for traces of command-line and ## JCL options. PPD-aware applications and spoolers stuff option ## settings directly into the file, they do not necessarily send ## PPD options by the command line. Also stuff in PostScript code ## to apply option settings given by the command line and to set ## the defaults given in the PPD file.
# Examination strategy: read lines from STDIN until the first # %%Page: comment appears and save them as @psheader. This is the # page-independent header part of the PostScript file. The # PostScript interpreter (renderer) must execute this part once # before rendering any assortment of pages. Then pages can be # printed in any arbitrary selection or order. All option # settings we find here will be collected in the default option # set for the RIP command line.
# Now the pages will be read and sent to the renderer, one after # the other. Every page is read into memory until the # %%EndPageSetup comment appears (or a certain amount of lines was # read). So we can get option settings only valid for this # page. If we have such settings we set them in the modified # command set for this page.
# If the renderer is not running yet (first page) we start it with # the command line built from the current modified command set and # send the first page to it, in the end we leave the renderer # running and keep input and output pipes open, so that it can # accept further pages. If the renderer is still running from # the previous page and the current modified command set is the # same as the one for the previous page, we send the page. If # the command set is different, we close the renderer, re-start # it with the command line built from the new modified command # set, send the header again, and then the page.
# After the last page the trailer (%%Trailer) is sent.
# The output pipe of this program stays open all the time so that # the spooler does not assume that the job has finished when the # renderer is re-started.
# Non DSC-conforming documents will be read until a certain line # number is reached. Command line or JCL options inserted later # will be ignored.
# If options are implemented by PostScript code supposed to be # stuffed into the job's PostScript data we stuff the code for all # these options into our job data, So all default settings made in # the PPD file (the user can have edited the PPD file to change # them) are taken care of and command line options get also # applied. To give priority to settings made by applications we # insert the options's code in the beginnings of their respective # sections, so that sommething, which is already inserted, gets # executed after our code. Missing sections are automatically # created. In non-DSC-conforming files we insert the option code # in the beginning of the file. This is the same policy as used by # the "pstops" filter of CUPS.
# If CUPS is the spooler, the option settings were already # inserted by the "pstops" filter, so we don't insert them # again. The only thing we do is correcting settings of numerical # options when they were set to a value not available as choice in # the PPD file, As "pstops" does not support "real" numerical # options, it sees these settings as an invalid choice and stays # with the default setting. In this case we correct the setting in # the first occurence of the option's code, as this one is the one # added by CUPS, later occurences come from applications and # should not be touched.
# If the input is not PostScript (if there is no "%!" after # $maxlinestopsstart lines) a file conversion filter will # automatically be applied to the incoming data, so that we will # process the resulting PostScript here. This way we have always # PostScript data here and so we can apply the printer/driver # features described in the PPD file.
# Supported file conversion filters are "a2ps", "enscript", # "mpage", and spooler-specific filters. All filters convert # plain text to PostScript, "a2ps" also other formats. The # conversion filter is always used when one prints the # documentation pages, as they are created as plain text, # when CUPS is the spooler "pstops" is executed after the # filter so that the default option settings from the PPD file # and CUPS-specific options as N-up get applied. On regular # printouts one gets always PostScript when CUPS or PPR is # the spooler, so the filter is only used for regular # printouts under LPD, LPRng, GNUlpr or without spooler.
my $maxlines = 1000; # Maximum number of lines to be read # when the documenent is not # DSC-conforming. "$maxlines = 0" # means that all will be read # and examined. If it is # discovered that the input file # is DSC-conforming, this will # be set to 0.
my $maxlinestopsstart = 200; # That many lines are allowed until the # "%!" indicating PS comes. These # additional lines in the # beginning are usually JCL # commands. The lines will be # ignored by our parsing but # passed through.
my $maxlinesforpageoptions=200; # Unfortunately, CUPS does not bracket # "PageSetup" option with # "%%BeginPageSetup" and # "%%EndPageSetup", so the options # can simply stand after the # page header and before the # page code, without special # marking. So buffer this amount # of lines before printing the # page to check for options.
my $maxnondsclinesinheader=1000; # If there is a block of more lines # than this in the document # header which is not in the # "%%BeginProlog...%%EndProlog" # or # "%%BeginSetup...%%EndSetup" # sections, the document is not # considered as DSC-conforming # and the rest gets passed # through to the renderer without # further parsing for options.
my $nondsclines = 0; # Amount of lines found which are not in # a section (see # $maxnondsclinesinheader).
my $nonpslines = 0; # lines before "%!" found yet.
my $more_stuff = 1; # there is more stuff in stdin.
my $linect = 0; # how many lines have we examined?
my $onelinebefore = ""; # The line before the current line # (Non-DSC comments are ignored)
my $twolinesbefore = ""; # The line two lines before the current # line (Non-DSC comments are ignored)
my @psheader = (); # The header of the PostScript file, # to be sent after each start of the # renderer
my @psfifo = (); # The input FIFO, data which we have # pulled from stdin for examination, # but not sent to the renderer yet.
my $passthru = 0; # 0: write data into @psfifo; 1: pass # data directly to the renderer
my $isdscjob = 0; # Is the job DSC conforming
my $inheader = 1; # Are we still in the header, before # first "%%Page:" comment?
my $optionset = 'header'; # Where do the option settings, which # we have found, go?
my $optionsalsointoheader = 0; # 1: We are in a "%%BeginSetup... # %%EndSetup" section after the first # "%%Page:..." line (OpenOffice.org # does this and intends the options here # apply to the whole document and not # only to the current page). We have to # add all lines also to the end of the # @psheader now and we have to set # non-PostScript options also in the # "header" optionset. 0: otherwise.
my $nestinglevel = 0; # Are we in the main document (0) or # in an embedded document bracketed by # "%%BeginDocument" and "%%EndDocument" # (>0) We do not parse the PostScript # in an embedded document.
my $inpageheader = 0; # Are we in the header of a page, # between "%%BeginPageSetup" and # "%%EndPageSetup" (1) or not (0).
my $lastpassthru = 0; # State of $passthru in previous line # (to allow debug output when $passthru # switches.
my $ignorepageheader = 0; # Will be set to 1 as soon as active # code (not between "%%BeginPageSetup" # and "%%EndPageSetup") appears after a # "%%Page:" comment. In this case # "%%BeginPageSetup" and # "%%EndPageSetup" is not allowed any # more on this page and will be ignored. # Will be set to 0 when a new "%%Page:" # comment appears.
my $printprevpage = 0; # We set this when encountering # "%%Page:" and the previous page is not # printed yet. Then it will be printed and # the new page will be prepared in the # next run of the loop (we don't read a # new line and don't increase the # $linect then).
$fileconverterhandle = undef; # File handle to the fileconverter process
$fileconverterpid = 0; # PID of the fileconverter process
$rendererhandle = undef; # File handle to the renderer process
$rendererpid = 0; # PID of the renderer process
my $prologfound = 0; # Did we find the # "%%BeginProlog...%%EndProlog" section?
my $setupfound = 0; # Did we find the # "%%BeginSetup...%%EndSetup" section?
my $pagesetupfound = 0; # special page setup handling needed
my $inprolog = 0; # We are between "%%BeginProlog" and # "%%EndProlog".
my $insetup = 0; # We are between "%%BeginSetup" and # "%%EndSetup".
my $infeature = 0; # We are between "%%BeginFeature" and # "%%EndFeature".
my $postscriptsection = 'jclsetup'; # In which section of the PostScript # file are we currently?
$nondsclines = 0; # Number of subsequent lines found which # are at a non-DSC-conforming place, # between the sections of the header.
my $optionreplaced = 0; # Will be set to 1 when we are in an # option ("%%BeginFeature... # %%EndFeature") which we have replaced.
$jobhasjcl = 0; # When the job does not start with # PostScript directly, but is a # PostScript job, we set this to 1 # to avoid adding the JCL options # for the second time.
my $insertoptions = 1; # If we find out that a file with # a DSC magic string # ("%!PS-Adobe-") is not really # DSC-conforming, we insert the # options directly after the line # with the magic string. We use # this variable to store the # number of the line with the # magic string.
my $currentpage = 0; # The page which we are currently # printing.
my $ooo110 = 0; # Flag to work around an application # bug.
if ($dontparse) { # We do not parse the PostScript to find Foomatic options, we check # only whether we have PostScript. $maxlines = 1; }
print $logh "Reading PostScript input ...\n";
my $line; # Line to be read from stdin do { my $ignoreline = 0; # Comment line to be ignored when # determining the last active line # and the one before the last
if (($printprevpage) || ($line=<STDIN>)) {
if ($linect == $nonpslines) { # In the beginning should be the postscript leader, # sometimes after some JCL commands if ($line !~ m/^.?%!/) { # There can be a Windows control # character before "%!" $nonpslines ++; if ($maxlines == $nonpslines) { $maxlines ++; } $jobhasjcl = 1; if ($nonpslines > $maxlinestopsstart) { # This is not a PostScript job, we must convert it print $logh "${added_lf}Job does not start with \"%!\", " . "is it PostScript?\n" . "Starting file converter\n"; # Reset all variables but conserve the data which # we have already read. $jobhasjcl = 0; $linect = 0; $nonpslines = 1; # Take into account that the line # of this run of the loop will be # put into @psheader, so the # first line read by the file # converter is already the second # line. $maxlines = 1001; $onelinebefore = ""; $twolinesbefore = ""; my $alreadyread = join('', @psheader, @psfifo) . $line; $line = ""; @psheader = (); @psfifo = (); # Start the file conversion filter if (!$fileconverterpid) { ($fileconverterhandle, $fileconverterpid) = getfileconverterhandle ($dat, $alreadyread); if ($retval != $EXIT_PRINTED) { rip_die ("Error opening file converter", $retval); } } else { rip_die("File conversion filter probably " . "crashed", $EXIT_JOBERR); } # Read the further data from the file converter and # not from STDIN if (!close STDIN && $! != $ESPIPE) { rip_die ("Couldn't close STDIN", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if (!open (STDIN, "<&$fileconverterhandle")) { rip_die ("Couldn't dup \$fileconverterhandle", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } } } else { # Do we have a DSC-conforming document? if ($line =~ m/^.?%!PS-Adobe-/) { # Do not stop parsing the document if (!$dontparse) { $maxlines = 0; $isdscjob = 1; $insertoptions = $linect + 1; # We have written into @psfifo before, # now we continue in @psheader and move # over the data which is already in @psfifo push (@psheader, @psfifo); @psfifo = (); } print $logh "--> This document is DSC-conforming!\n"; } else { # Job is not DSC-conforming, stick in all PostScript # option settings in the beginning $line .= makeprologsection($dat, $optionset, 1); $line .= makesetupsection($dat, $optionset, 1); $line .= makepagesetupsection($dat, $optionset, 1); $prologfound = 1; $setupfound = 1; $pagesetupfound = 1; } } } else { if ($line =~ m/^\s*\%\%BeginDocument[: ]/) { # Beginning of an embedded document # Note that Adobe Acrobat has a bug and so uses # "%%BeginDocument " instead of "%%BeginDocument:" $nestinglevel ++; print $logh "Embedded document, " . "nesting level now: $nestinglevel\n"; } elsif (($line =~ m/^\s*\%\%EndDocument/) && ($nestinglevel > 0)) { # End of an embedded document $nestinglevel --; print $logh "End of Embedded document, " . "nesting level now: $nestinglevel\n"; } elsif (($line =~ m/^\s*\%\%Creator[: ](.*)$/) && ($nestinglevel == 0)) { # Here we set flags to treat particular bugs of the # PostScript produced by certain applications my $creator = $1; if ($creator =~ /^\s*OpenOffice.org\s+1.1.\d+\s*$/) { # OpenOffice.org 1.1.x # The option settings supposed to affect the # whole document are put into the "%%PageSetup" # section of the first page print $logh "Document created with " . "OpenOffice.org 1.1.x\n"; $ooo110 = 1; } } elsif (($line =~ m/^\%\%BeginProlog/) && ($nestinglevel == 0)) { # Note: Below is another place where a "Prolog" # section start will be considered. There we assume # start of the "Prolog" if the job is DSC-Conformimg, # but an arbitrary comment starting with "%%Begin", but # not a comment explicitly treated here, is found. This # is done because many "dvips" (TeX/LaTeX) files miss # the "%%BeginProlog" comment. # Beginning of Prolog print $logh "${added_lf}-----------\nFound: \%\%BeginProlog\n"; $inprolog = 1; $postscriptsection = 'prolog' if $inheader; $nondsclines = 0; # Insert options for "Prolog" if (!$prologfound) { $line .= makeprologsection($dat, $optionset, 0); } $prologfound = 1; } elsif (($line =~ m/^\%\%EndProlog/) && ($nestinglevel == 0)) { # End of Prolog print $logh "Found: \%\%EndProlog\n"; $inprolog = 0; $insertoptions = $linect + 1; } elsif (($line =~ m/^\%\%BeginSetup/) && ($nestinglevel == 0)) { # Beginning of Setup print $logh "${added_lf}-----------\nFound: \%\%BeginSetup\n"; $insetup = 1; # We need to distinguish with the $inheader variable # here whether we are in the header or on a page, as # OpenOffice.org inserts a "%%BeginSetup...%%EndSetup" # section after the first "%%Page:..." line and assumes # this section to be valid for all pages. $postscriptsection = 'setup' if $inheader; $nondsclines = 0; if ($inheader) { # If there was no "Prolog" but there are # options for the "Prolog", push a "Prolog" # with these options onto the @psfifo here if (!$prologfound) { # "Prolog" missing, insert it here $line = makeprologsection($dat, $optionset, 1) . $line; # Now we have a "Prolog" $prologfound = 1; } # Insert options for "DocumentSetup" or "AnySetup" if ($spooler ne 'cups') { # For non-CUPS spoolers or no spooler at all, we leave # everything as it is. if (!$setupfound) { $line .= makesetupsection($dat, $optionset, 0); } $setupfound = 1; } } else { # Found option settings must be stuffed into both # the header and the currrent page now. They will # be written into both the "header" and the # "currentpage" optionsets and the PostScript code # lines of this section will not only go into the # output stream, but also added to the end of the # @psheader, so that they get repeated (to preserve # the embedded PostScript option settings) on a # restart of the renderer due to command line # option changes $optionsalsointoheader = 1; print $logh "\"%%BeginSetup\" in page header\n"; } } elsif (($line =~ m/^\%\%EndSetup/) && ($nestinglevel == 0)) { # End of Setup print $logh "Found: \%\%EndSetup\n"; $insetup = 0; if ($inheader) { if ($spooler eq 'cups') { # In case of CUPS, we must insert the # accounting stuff just before the # %%EndSetup comment in order to leave any # EndPage procedures that have been # defined by either the pstops filter or # the PostScript job itself fully # functional. if (!$setupfound) { $line = makesetupsection($dat, $optionset, 0) . $line; } $setupfound = 1; } $insertoptions = $linect + 1; } else { # The "%%BeginSetup...%%EndSetup" which # OpenOffice.org has inserted after the first # "%%Page:..." line ends here, so the following # options go only onto the current page again $optionsalsointoheader = 0; } } elsif (($line =~ m/^\%\%Page:(.*)$/) && ($nestinglevel == 0)) { if ((!$lastpassthru) && (!$inheader)) { # In the last line we were not in passthru mode, # so the last page is not printed. Prepare to do # it now. $printprevpage = 1; # Print the previous page $passthru = 1; print $logh "New page found but previous not " . "printed, print it now.\n"; } else { # The previous page is printed, so we can prepare # the current one $printprevpage = 0; print $logh "${added_lf}-----------\nNew page: $1\n"; # Count pages $currentpage ++; # We consider the beginning of the page already as # page setup section, as some apps do not use # "%%PageSetup" tags. $postscriptsection = 'pagesetup'; # Save PostScript state before beginning the page #$line .= "/foomatic-saved-state save def\n"; # Here begins a new page if ($inheader) { # Here we add some stuff which still belongs # into the header my $stillforheader; # If there was no "Setup" but there are # options for the "Setup", push a "Setup" # with these options onto the @psfifo here if (!$setupfound) { # "Setup" missing, insert it here $stillforheader = makesetupsection($dat, $optionset, 1) . $stillforheader; # Now we have a "Setup" $setupfound = 1; } # If there was no "Prolog" but there are # options for the "Prolog", push a "Prolog" # with these options onto the @psfifo here if (!$prologfound) { # "Prolog" missing, insert it here $stillforheader = makeprologsection($dat, $optionset, 1) . $stillforheader; # Now we have a "Prolog" $prologfound = 1; } # Now we push this onto the header push (@psheader, $stillforheader); # The first page starts, so the header ends $inheader = 0; $nondsclines = 0; # Option setting should go into the # page-specific option set now $optionset = 'currentpage'; } else { # Restore PostScript state after completing the # previous page: # # foomatic-saved-state restore # %%Page: ... # /foomatic-saved-state save def # # Print this directly, so that if we need to # restart the renderer for this page due to # a command line change this is done under the # old instance of the renderer #print $rendererhandle # "foomatic-saved-state restore\n";
# Save the option settings of the previous page copyoptions($dat, 'currentpage', 'previouspage'); deleteoptions($dat, 'currentpage'); } # Initialize the option set copyoptions($dat, 'header', 'currentpage'); # Set command line options which apply only # given pages setoptionsforpage($dat, 'currentpage', $currentpage); $pagesetupfound = 0; if ($spooler eq 'cups') { # Remove the "notfirst" flag from all options # forseen for the "PageSetup" section, because # when these are numerical options for CUPS. # they have to be set to the correct value # for every page for my $arg (@{$dat->{'args'}}) { if (($arg->{'section'} eq 'PageSetup') && (defined($arg->{'notfirst'}))) { delete($arg->{'notfirst'}); } } } # Insert PostScript option settings # (options for section "PageSetup". if ($isdscjob) { $line .= makepagesetupsection($dat, $optionset, 0); $pagesetupfound = 1; } # Now the page header comes, so buffer the data, # because we must perhaps shut down and restart # the renderer $passthru = 0; $ignorepageheader = 0; $optionsalsointoheader = 0; } } elsif (($line =~ m/^\%\%BeginPageSetup/) && ($nestinglevel == 0) && (!$ignorepageheader)) { # Start of the page header, up to %%EndPageSetup # nothing of the page will be drawn, page-specific # option settngs (as letter-head paper for page 1) # go here print $logh "${added_lf}Found: \%\%BeginPageSetup\n"; $passthru = 0; $inpageheader = 1; $postscriptsection = 'pagesetup'; if (($ooo110) && ($currentpage == 1)) { $optionsalsointoheader = 1; } else { $optionsalsointoheader = 0; } } elsif (($line =~ m/^\%\%EndPageSetup/) && ($nestinglevel == 0) && (!$ignorepageheader)) { # End of the page header, the page is ready to be # printed print $logh "Found: \%\%EndPageSetup\n"; print $logh "End of page header\n"; # We cannot for sure say that the page header ends here # OpenOffice.org puts (due to a bug) a "%%BeginSetup... # %%EndSetup" section after the first "%%Page:...". It # is possible that CUPS inserts a "%%BeginPageSetup... # %%EndPageSetup" before this section, which means that # the options in the "%%BeginSetup...%%EndSetup" section # are after the "%%EndPageSetup", so we continue for # searching options up to the buffer size limit # $maxlinesforpageoptions. $passthru = 0; $inpageheader = 0; $optionsalsointoheader = 0; } elsif ((($line =~ m/^\%\%(BeginFeature):\s*\*?([^\*\s=]+)\s+()(\S[^\r\n]*)\r?\n?$/) || ($line =~ m/^\s*\%\%\s*(FoomaticRIPOptionSetting):\s*([^\*\s=]+)\s*=\s*(\@?)([^\@\s][^\r\n]*)\r?\n?$/)) && ($nestinglevel == 0) && (!$optionreplaced) && ((!$passthru) || (!$isdscjob))) { my ($linetype, $option, $fromcomposite, $value) = ($1, $2, $3, $4);
# Mark that we are in a "Feature" section if ($linetype eq 'BeginFeature') { $infeature = 1; } # OK, we have an option. If it's not a # *ostscript-style option (ie, it's command-line or # JCL) then we should note that fact, since the # attribute-to-filter option passing in CUPS is kind of # funky, especially wrt boolean options.
print $logh "Found: $line"; if (my $arg=argbyname($option)) { print $logh " Option: $option=" . ($fromcomposite ? "From" : "") . $value; if (($spooler eq 'cups') && ($linetype eq 'BeginFeature') && (!defined($arg->{'notfirst'})) && ($arg->{$optionset} ne $value) && (($inheader) || ($arg->{section} eq 'PageSetup'))) { # We have the first occurence of an # option setting and the spooler is CUPS, # so this setting is inserted by "pstops". # The value from the command line was not # inserted by "pstops" so it seems to be # not under the choices in the PPD. # Possible reasons: # # - "pstops" ignores settings of numerical # or string options which are not one of # the choices in the PPD file, and inserts # the default value instead. # # - On the command line an option was applied # only to selected pages: # "-o <page ranges>:<option>=<values> # This is not supported by CUPS, so not # taken care of by "pstops". # # We must fix this here by replacing the setting # inserted by "pstops" with the exact setting # given on the command line. # $arg->{$optionset} is already # range-checked, so do not check again here # Insert DSC comment my $dest = ((($inheader) && ($isdscjob)) ? \@psheader : \@psfifo); push(@{$dest}, "%%BeginFeature: " . "*$option $arg->{$optionset}\n"); my $val; if ($arg->{'style'} eq 'G') { # PostScript option, insert the code if ($arg->{'type'} eq 'bool') { # Boolean option if (defined($arg->{$optionset}) && $arg->{$optionset} == 1) { push(@{$dest}, $arg->{'proto'} . "\n"); } elsif ($arg->{'protof'}) { push(@{$dest}, $arg->{'protof'}. "\n"); } } elsif ((($arg->{'type'} eq 'enum') || ($arg->{'type'} eq 'string') || ($arg->{'type'} eq 'password')) && (defined($val = $arg->{'vals_byname'}{$arg->{$optionset}}))) { # Enumerated choice of string or enum # option push(@{$dest}, $val->{'driverval'} . "\n"); } elsif ((($arg->{'type'} eq 'string') || ($arg->{'type'} eq 'password')) && ($arg->{$optionset} eq 'None')) { # 'None' is mapped to the empty string in # string options my $driverval = $arg->{'proto'}; $driverval =~ s/\%s//g; push(@{$dest}, $driverval . "\n"); } else { # Setting for numerical or string option # which is not under the enumerated choices my $sprintfproto = $arg->{'proto'}; $sprintfproto =~ s/\%(?!s)/\%\%/g; push(@{$dest}, sprintf($sprintfproto, $arg->{$optionset}) . "\n"); } } else { # Command line or JCL option push(@{$dest}, "%% FoomaticRIPOptionSetting: " . "$option=$arg->{$optionset}\n"); } print $logh " --> Correcting numerical/string " . "option to $option=$arg->{$optionset}" . " (Command line argument)\n"; # We have replaced this option on the # FIFO $optionreplaced = 1; } # Mark that we have already found this option $arg->{'notfirst'} = 1; if (!$optionreplaced) { if ($arg->{'style'} ne 'G') { # "Controlled by '<Composite>'" setting of # a member option of a composite option if ($fromcomposite) { $value = "From$value"; } # Non-PostScript option # Check whether it is valid if (defined(my $newvalue = checkoptionvalue($dat, $option, $value, 0))) { print $logh " --> Setting option\n"; # Valid choice, set it. $arg->{$optionset} = $newvalue; if ($optionsalsointoheader) { $arg->{'header'} = $newvalue; } if (($arg->{'type'} eq 'enum') && (($option eq 'PageSize') || ($option eq 'PageRegion')) && ($newvalue =~ /^Custom/) && ($linetype eq 'FoomaticRIPOptionSetting')) { # Custom page size $twolinesbefore =~ /^\s*([\d\.]+)\s+([\d\.]+)\s+([\d\.]+)\s+([\d\.]+)\s+([\d\.]+)\s*$/; my ($w, $h) = ($1, $2); if (($w) && ($h) && ($w != 0) && ($h != 0)) { $newvalue = "$newvalue.${w}x$h"; $arg->{$optionset} = $newvalue; if ($optionsalsointoheader) { $arg->{'header'} = $newvalue; } } } # For a composite option insert the # code from the member options with # current setting "From<composite>" # The code from the member options # is chosen according to the setting # of the composite option. if (($arg->{'style'} eq 'X') && ($linetype eq 'FoomaticRIPOptionSetting')) { buildcommandline($dat, $optionset); $line .= $arg->{$postscriptsection}; } # If this argument is PageSize or # PageRegion, also set the other syncpagesize($dat, $option, $newvalue, $optionset); if ($optionsalsointoheader) { syncpagesize($dat, $option, $newvalue, 'header'); } } else { # Invalid option, log it. print $logh " --> Invalid option " . "setting found in job\n"; } } elsif ($fromcomposite) { # PostScript option, but we have to look up # the PostScript code to be inserted from # the setting of a composite option, as this # option is set to "Controlled by # '<Composite>'". # Set the option if (defined(my $newvalue = checkoptionvalue ($dat, $option, "From$value", 0))) { print $logh " --> Looking up setting " . "in composite option '$value'\n"; # Valid choice, set it. $arg->{$optionset} = $newvalue; if ($optionsalsointoheader) { $arg->{'header'} = $newvalue; } # Update composite options buildcommandline($dat, $optionset); # Substitute PostScript comment by # the real code $line = $arg->{'compositesubst'}; } else { # Invalid option, log it. print $logh " --> Invalid option " . "setting found in job\n"; } } else { # it is a PostScript style option with # the code readily inserted, no option # for the renderer command line/JCL to set, # no lookup of a composite option needed, # so nothing to do here... print $logh " --> Option will be set by " . "PostScript interpreter\n"; } } } else { # This option is unknown to us. WTF? print $logh "Unknown option $option=$value found " . "in the job\n"; } } elsif (($line =~ m/^\%\%EndFeature/) && ($nestinglevel == 0)) { # End of Feature $infeature = 0; # If the option setting was replaced, it ends here, too, # end the next option is not necessarily also replaced. $optionreplaced = 0; } elsif (($line =~ m/^\%\%Begin/) && ($isdscjob) && (!$prologfound) && ($nestinglevel == 0)) { # In some PostScript files (especially when generated # by "dvips" of TeX/LaTeX) the "%%BeginProlog" is # missing, so assume that it was before the current # line (the first line starting with "%%Begin". print $logh "Job claims to be DSC-conforming, but " . "\"%%BeginProlog\" was missing before first " . "line with another \"%%Begin...\" comment " . "(is this a TeX/LaTeX/dvips-generated PostScript " . "file?). Assuming start of \"Prolog\" here.\n"; # Beginning of Prolog $inprolog = 1; $nondsclines = 0; # Insert options for "Prolog" before the current line if (!$prologfound) { $line = "%%BeginProlog\n" . makeprologsection($dat, $optionset, 0) . $line; } $prologfound = 1; } elsif (($line =~ m/^\s*\%/) || ($line =~ m/^\s*$/)) { # This is an unknown PostScript comment or a blank line, # no active code $ignoreline = 1; } else { # This line is active PostScript code if ($inheader) { if ((!$inprolog) && (!$insetup)) { # Outside the "Prolog" and "Setup" section # a correct DSC-conforming document has no # active PostScript code, so consider the # file as non-DSC-conforming when there are # too many of such lines. $nondsclines ++; if ($nondsclines > $maxnondsclinesinheader) { # Consider document as not DSC-conforming print $logh "This job seems not to be " . "DSC-conforming, DSC-comment for " . "next section not found, stopping " . "to parse the rest, passing it " . "directly to the renderer.\n"; # Stop scanning for further option settings $maxlines = 1; $isdscjob = 0; # Insert defaults and command line settings # in the beginning of the job or after the # last valid section splice(@psheader, $insertoptions, 0, ($prologfound ? () : makeprologsection($dat, $optionset, 1)), ($setupfound ? () : makesetupsection($dat, $optionset, 1)), ($pagesetupfound ? () : makepagesetupsection($dat, $optionset, 1))); $prologfound = 1; $setupfound = 1; $pagesetupfound = 1; } } } else { if (!$inpageheader) { # PostScript code inside a page, but not between # "%%BeginPageSetup" and "%%EndPageSetup", so # we are perhaps already drawing onto a page now if ($onelinebefore =~ m/^\%\%Page:/) { print $logh "No page header or page " . "header not DSC-conforming\n"; } # Stop buffering lines to search for options # placed not DSC-conforming if (scalar(@psfifo) >= $maxlinesforpageoptions) { print $logh "Stopping search for " . "page header options\n"; $passthru = 1; # If there comes a page header now, ignore # it $ignorepageheader = 1; $optionsalsointoheader = 0; } } } } } # Debug info if ($lastpassthru != $passthru) { if ($passthru) { print $logh "Found:\n $line" . " --> Output goes directly to the renderer now.\n${added_lf}"; } else { print $logh "Found:\n $line" . " --> Output goes to the FIFO buffer now.${added_lf}\n"; } }
# We are in an option which was replaced, do not output # the current line. if ($optionreplaced) { $line = ""; }
# If we are in a "%%BeginSetup...%%EndSetup" section after # the first "%%Page:..." and the current line belongs to # an option setting, we have to copy the line also to the # @psheader. if (($optionsalsointoheader) && (($infeature) || ($line =~ m/^\%\%EndFeature/))) { push (@psheader, $line); }
# Store or send the current line if (($inheader) && ($isdscjob)) { # We are still in the PostScript header, collect all lines # in @psheader push (@psheader, $line); } else { if (($passthru) && ($isdscjob)) { if (!$lastpassthru) { # We enter passthru mode with this line, so the # command line can have changed, check it and # close the renderer if needed if (($rendererpid) && (!optionsequal($dat, 'currentpage', 'previouspage', 0))) { print $logh "Command line/JCL options " . "changed, restarting renderer\n"; $retval = closerendererhandle ($rendererhandle, $rendererpid); if ($retval != $EXIT_PRINTED) { rip_die ("Error closing renderer", $retval); } $rendererpid = 0; } } # Flush @psfifo and send line directly to the renderer if (!$rendererpid) { # No renderer running, start it ($rendererhandle, $rendererpid) = getrendererhandle ($dat, join('', @psheader, @psfifo)); if ($retval != $EXIT_PRINTED) { rip_die ("Error opening renderer", $retval); } # @psfifo is sent out, flush it. @psfifo = (); } if ($#psfifo >= 0) { # Send @psfifo to renderer print $rendererhandle join('', @psfifo); # flush @psfifo @psfifo = (); } # Send line to renderer if (!$printprevpage) { print $rendererhandle $line; } } else { # Push the line onto the stack for later spitting up... push (@psfifo, $line); } } if (!$printprevpage) { $linect++; }
} else { # EOF! $more_stuff = 0; # No PostScript header in the whole file? Then it's not # PostScript, convert it. # We open the file converter here when the file has less # lines than the amount which we search for the PostScript # header ($maxlinestopsstart). if ($linect <= $nonpslines) { # This is not a PostScript job, we must convert it print $logh "${added_lf}Job does not start with \"%!\", " . "is it PostScript?\n" . "Starting file converter\n"; # Reset all variables but conserve the data which # we have already read. $jobhasjcl = 0; $linect = 0; $nonpslines = 0; $maxlines = 1000; $onelinebefore = ""; $twolinesbefore = ""; my $alreadyread = join('', @psheader, @psfifo); @psheader = (); @psfifo = (); $line = ""; # Start the file conversion filter if (!$fileconverterpid) { ($fileconverterhandle, $fileconverterpid) = getfileconverterhandle($dat, $alreadyread); if ( defined($retval) and $retval != $EXIT_PRINTED) { rip_die ("Error opening file converter", $retval); } } else { rip_die("File conversion filter probably " . "crashed", $EXIT_JOBERR); } # Read the further data from the file converter and # not from STDIN if (!close STDIN && $! != $ESPIPE) { rip_die ("Couldn't close STDIN", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if (!open (STDIN, "<&$fileconverterhandle")) { rip_die ("Couldn't dup \$fileconverterhandle", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } # Now we have new (converted) stuff in STDIN, so # continue in the loop $more_stuff = 1; } }
$lastpassthru = $passthru; if ((!$ignoreline) && (!$printprevpage)) { $twolinesbefore = $onelinebefore; $onelinebefore = $line; }
} while ((($maxlines == 0) or ($linect < $maxlines)) and ($more_stuff != 0));
# Some buffer still containing data? Send it out to the renderer. if (($more_stuff != 0) || ($inheader) || ($#psfifo >= 0)) { # Flush @psfifo and send the remaining data to the renderer, this # only happens with non-DSC-conforming jobs or non-Foomatic PPDs if ($more_stuff) { print $logh "Stopped parsing the PostScript data, ". "sending rest directly to renderer.\n"; } else { print $logh "Flushing FIFO.\n"; } if ($inheader) { # No page initialized yet? Copy the "header" option set into the # "currentpage" option set, so that the renderer will find the # options settings. copyoptions($dat, 'header', 'currentpage'); $optionset = 'currentpage'; # If not done yet, insert defaults and command line settings # in the beginning of the job or after the last valid section splice(@psheader, $insertoptions, 0, ($prologfound ? () : makeprologsection($dat, $optionset, 1)), ($setupfound ? () : makesetupsection($dat, $optionset, 1)), ($pagesetupfound ? () : makepagesetupsection($dat, $optionset, 1))); $prologfound = 1; $setupfound = 1; $pagesetupfound = 1; } if (($rendererpid) && (!optionsequal($dat, 'currentpage', 'previouspage', 0))) { print $logh "Command line/JCL options " . "changed, restarting renderer\n"; $retval = closerendererhandle ($rendererhandle, $rendererpid); if ($retval != $EXIT_PRINTED) { rip_die ("Error closing renderer", $retval); } $rendererpid = 0; } if (!$rendererpid) { ($rendererhandle, $rendererpid) = getrendererhandle($dat, join('', @psheader, @psfifo)); if ($retval != $EXIT_PRINTED) { rip_die ("Error opening renderer", $retval); } # We have sent @psfifo now @psfifo = (); } if ($#psfifo >= 0) { # Send @psfifo to renderer print $rendererhandle join('', @psfifo); # flush @psfifo @psfifo = (); } # Print the rest of the input data if ($more_stuff) { while (<STDIN>) { print $rendererhandle $_; } } }
# At every "%%Page:..." comment we have saved the PostScript state # and we have increased the page number. So if the page number is # non-zero we had at least one "%%Page:..." comment and so we have # to give a restore the PostScript state. #if ($currentpage > 0) { # print $rendererhandle "foomatic-saved-state restore\n"; #} # Close the renderer if ($rendererpid) { $retval = closerendererhandle ($rendererhandle, $rendererpid); if ($retval != $EXIT_PRINTED) { rip_die ("Error closing renderer", $retval); } $rendererpid = 0; }
# Close the file converter (if it was used) if ($fileconverterpid) { $retval = closefileconverterhandle ($fileconverterhandle, $fileconverterpid); if ($retval != $EXIT_PRINTED) { rip_die ("Error closing file converter", $retval); } $fileconverterpid = 0; } }
## Close the documentation page generator if ($docgeneratorpid) { $retval = closedocgeneratorhandle ($docgeneratorhandle, $docgeneratorpid); if ($retval != $EXIT_PRINTED) { rip_die ("Error closing documentation page generator", $retval); } $docgeneratorpid = 0; }
## Close last input file close STDIN;
## Only for debugging if ($debug && 1) { use Data::Dumper; local $Data::Dumper::Purity=1; local $Data::Dumper::Indent=1; print $logh Dumper($dat); }
## The End print $logh "${added_lf}Closing foomatic-rip.\n"; close $logh;
exit $retval;
## Functions to let foomatic-rip fork to do several tasks in parallel.
# To do the filtering without loading the whole file into memory we work # on a data stream, we read the data line by line analyse it to decide what # filters to use and start the filters if we have found out which we need. # We buffer the data only as long as we didn't determing which filters to # use for this piece of data and with which options. There are no temporary # files used.
# foomatic-rip splits into up to 6 parallel processes to do the whole # filtering (listed in the order of the data flow):
# KID0: Generate documentation pages (only jobs with "docs" option) # KID2: Put together already read data and current input stream for # feeding into the file conversion filter (only non-PostScript # and "docs" jobs) # KID1: Run the file conversion filter to convert non-PostScript # input into PostScript (only non-PostScript and "docs" jobs) # MAIN: Prepare the job auto-detecting the spooler, reading the PPD, # extracting the options from the command line, and parsing # the job data itself. It analyses the job data to check # whether it is PostScript and starts KID1/KID2 if not, it # also stuffs PostScript code from option settings into the # PostScript data stream. It starts the renderer (KID3/KID4) # as soon as it knows its command line and restarts it when # page-specific option settings need another command line # or different JCL commands. # KID3: The rendering process. In most cases GhostScript, "cat" # for native PostScript printers with their manufacturer's # PPD files. # KID4: Put together the JCL commands and the renderer's output # and send all that either to STDOUT or pipe it into the # command line defined with $postpipe.
## This function runs the renderer command line (and if defined also ## the postpipe) and returns a file handle for stuffing in the ## PostScript data. sub getrendererhandle {
my ($dat, $prepend) = @_;
print $logh "${added_lf}Starting renderer\n";
# Catch signals $retval = $EXIT_PRINTED; use sigtrap qw(handler set_exit_prnerr USR1 handler set_exit_prnerr_noretry USR2 handler set_exit_engaged TTIN);
# Variables for the kid processes reporting their state
# Set up a pipe for the kids to pass their exit stat to the main process pipe KID_MESSAGE, KID_MESSAGE_IN;
# When one kid fails put the exit stat here $kidfailed = 0;
# When a kid exits successfully, mark it here $kid3finished = 0; $kid4finished = 0;
# Build the command line and get the JCL commands buildcommandline($dat, 'currentpage'); my $commandline = $dat->{'currentcmd'}; my @jclprepend = @{$dat->{'jclprepend'}} if defined $dat->{'jclprepend'}; my @jclappend = @{$dat->{'jclappend'}} if defined $dat->{'jclappend'};
use IO::Handle; pipe KID3_IN, KID3; KID3->autoflush(1); $kid3 = fork(); if (!defined($kid3)) { close KID3; close KID3_IN; print $logh "$0: cannot fork for kid3!\n"; rip_die ("can't fork for kid3", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if ($kid3) {
# we are the parent; return a glob to the filehandle close KID3_IN;
# Feed in the PostScript header and the FIFO contents print KID3 $prepend;
KID3->flush(); return ( *KID3, $kid3 );
} else { close KID3;
pipe KID4_IN, KID4; KID4->autoflush(1); $kid4 = fork(); if (!defined($kid4)) { close KID4; close KID4_IN; print $logh "$0: cannot fork for kid4!\n"; close KID_MESSAGE; print KID_MESSAGE_IN "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_IN; rip_die ("can't fork for kid4", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if ($kid4) { # parent, child of primary task; we are |commandline| close KID4_IN;
print $logh "renderer PID kid4=$kid4\n"; print $logh "renderer command: $commandline\n"; if (!close STDIN && $! != $ESPIPE) { close KID3_IN; close KID4; close KID_MESSAGE; print KID_MESSAGE_IN "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_IN; rip_die ("Couldn't close STDIN in $kid4", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if (!open (STDIN, "<&KID3_IN")) { close KID3_IN; close KID4; close KID_MESSAGE; print KID_MESSAGE_IN "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_IN; rip_die ("Couldn't dup KID3_IN", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if (!close STDOUT) { close KID3_IN; close KID4; close KID_MESSAGE; print KID_MESSAGE_IN "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_IN; rip_die ("Couldn't close STDOUT in $kid4", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if (!open (STDOUT, ">&KID4")) { close KID3_IN; close KID4; close KID_MESSAGE; print KID_MESSAGE_IN "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_IN; rip_die ("Couldn't dup KID4", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if ($debug) { if (!open (STDERR, ">&$logh")) { close KID3_IN; close KID4; close KID_MESSAGE; print KID_MESSAGE_IN "3 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_IN; rip_die ("Couldn't dup logh to stderr", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } }
# 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\s!foomatic-gswrapper !g; $commandline =~ s!(\|\s*)gs\s!\|foomatic-gswrapper !g; $commandline =~ s!(;\s*)gs\s!; foomatic-gswrapper !g; }
# If the renderer command line contains the "echo" # command, replace the "echo" by the user-chosen $myecho # (important for non-GNU systems where GNU echo is in a # special path $commandline =~ s!^\s*echo\s!$myecho !g; $commandline =~ s!(\|\s*)echo\s!\|$myecho !g; $commandline =~ s!(;\s*)echo\s!; $myecho !g;
# In debug mode save the data supposed to be fed into the # renderer also into a file if ($debug) { $commandline = "tee -a ${logfile}.ps | ( $commandline )"; } # Actually run the thing... system("$commandline"); if ($? != 0) { my $rendererretval = $? >> 8; print $logh "renderer return value: $rendererretval\n"; my $renderersignal = $? & 127; print $logh "renderer received signal: $rendererretval\n"; close STDOUT; close KID4; close STDIN; close KID3_IN; # Handle signals if ($renderersignal == SIGUSR1) { $retval = $EXIT_PRNERR; } elsif ($renderersignal == SIGUSR2) { $retval = $EXIT_PRNERR_NORETRY; } elsif ($renderersignal == SIGTTIN) { $retval = $EXIT_ENGAGED; } if ($retval != $EXIT_PRINTED) { close KID_MESSAGE; print KID_MESSAGE_IN "3 $retval\n"; close KID_MESSAGE_IN; exit $retval; } # Evaluate renderer result if ($rendererretval == 0) { # Success, exit with 0 and inform main process close KID_MESSAGE; print KID_MESSAGE_IN "3 $EXIT_PRINTED\n"; close KID_MESSAGE_IN; exit $EXIT_PRINTED; } elsif ($rendererretval == 1) { # Syntax error? PostScript error? close KID_MESSAGE; print KID_MESSAGE_IN "3 $EXIT_JOBERR\n"; close KID_MESSAGE_IN; rip_die ("Possible error on renderer command line or PostScript error. Check options.", $EXIT_JOBERR); } elsif ($rendererretval == 139) { # Seems to indicate a core dump close KID_MESSAGE; print KID_MESSAGE_IN "3 $EXIT_JOBERR\n"; close KID_MESSAGE_IN; rip_die ("The renderer may have dumped core.", $EXIT_JOBERR); } elsif ($rendererretval == 141) { # Broken pipe, presumably additional filter interface # exited. close KID_MESSAGE; print KID_MESSAGE_IN "3 $EXIT_PRNERR\n"; close KID_MESSAGE_IN; rip_die ("A filter used in addition to the renderer" . " itself may have failed.", $EXIT_PRNERR); } elsif (($rendererretval == 243) || ($retval == 255)) { # PostScript error? close KID_MESSAGE; print KID_MESSAGE_IN "3 $EXIT_JOBERR\n"; close KID_MESSAGE_IN; exit $EXIT_JOBERR; } else { # Unknown error close KID_MESSAGE; print KID_MESSAGE_IN "3 $EXIT_PRNERR\n"; close KID_MESSAGE_IN; rip_die ("The renderer command line returned an" . " unrecognized error code $rendererretval.", $EXIT_PRNERR); } } close STDOUT; close KID4; close STDIN; close KID3_IN; # When arrived here the renderer command line was successful # So exit with zero exit value here and inform the main process close KID_MESSAGE; print KID_MESSAGE_IN "3 $EXIT_PRINTED\n"; close KID_MESSAGE_IN; # Wait for postpipe/output child waitpid($kid4, 0); print $logh "KID3 finished\n"; exit $EXIT_PRINTED; } else { # child, trailing task on the pipe; we write jcl stuff close KID4; close KID3_IN;
my $fileh = *STDOUT;
# Do we have a $postpipe, if yes, launch the command(s) and # point our output into it/them if ($postpipe) { if (!open PIPE,$postpipe) { close KID4_IN; close KID_MESSAGE; print KID_MESSAGE_IN "4 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_IN; rip_die ("cannot execute postpipe $postpipe", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } $fileh = *PIPE; }
# Debug output print $logh "JCL: " . join("", @jclprepend) . "<job data> ${added_lf}" . join("", @jclappend) . "\n";
# wrap the JCL around the job data, if there are any # options specified... # Should the driver already have inserted JCL commands we merge # our JCL header with the one from the driver my $driverjcl = 0; if ( @jclprepend > 1 ) { # JCL header read from renderer output my @jclheader = (); # Determine magic string of JCL in use (usually "@PJL") # For that we take the first part of the second JCL line up # to the first space if ($jclprepend[1] =~ /^(\S+)/) { my $jclstr = $1; # Read from the renderer output until the first non-JCL # line appears while (my $line = <KID4_IN>) { push(@jclheader, $line); last if ($line !~ /$jclstr/); } # If we had read at least two lines, at least one is # a JCL header, so do the merging if (@jclheader > 1) { $driverjcl = 1; # Discard the first and the last entry of the # @jclprepend array, we only need the option settings # to merge them in pop(@jclprepend); shift(@jclprepend); # Line after which we insert new JCL commands in the # JCL header of the job my $insert = 1; # Go through every JCL command in @jclprepend for my $line (@jclprepend) { # Search the command in the JCL header from the # driver. As search term use only the string from # the beginning of the line to the "=", so the # command will also be found when it has another # value $line =~ /^([^=]+)/; my $cmd = $1; my $cmdfound = 0; for (@jclheader) { # If the command is there, replace it $_ =~ s/$cmd\b.*(\r\n|\n|\r)/$line/ and $cmdfound = 1; } if (!$cmdfound) { # If the command is not found, insert it if (@jclheader > 2) { # @jclheader has more than one line, # insert the new command beginning # right after the first line and continuing # after the previous inserted command splice(@jclheader, $insert, 0, $line); $insert ++; } else { # If we have only one line of JCL it # is probably something like the # "@PJL ENTER LANGUAGE=..." line # which has to be in the end, but # it also contains the # "<esc>%-12345X" which has to be in the # beginning of the job. So we split the # line right before the $jclstr and # append our command to the end of the # first part and let the second part # be a second JCL line. $jclheader[0] =~ /^(.*?)($jclstr.*(\r\n|\n|\r))/; my $first = "$1$line"; my $second = "$2"; my $third = $jclheader[1]; @jclheader = ($first, $second, $third); } } } # Now pass on the merged JCL header print $fileh @jclheader; } else { # The driver didn't create a JCL header, simply # prepend ours and then pass on the line which we # already have read print $fileh @jclprepend, @jclheader; } } else { # No merging of JCL header possible, simply prepend it print $fileh @jclprepend; } }
# The rest of the job data while (<KID4_IN>) { print $fileh $_; }
# A JCL trailer if (( @jclprepend > 1 ) && (!$driverjcl)) { print $fileh @jclappend; } if (!close $fileh) { close KID4_IN; close KID_MESSAGE; print KID_MESSAGE_IN "4 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_IN; rip_die ("error closing $fileh", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } close KID4_IN;
print $logh "tail process done writing data to STDOUT\n";
# Handle signals of the backend interface if ($retval != $EXIT_PRINTED) { close KID_MESSAGE; print KID_MESSAGE_IN "4 $retval\n"; close KID_MESSAGE_IN; exit $retval; }
# Successful exit, inform main process close KID_MESSAGE; print KID_MESSAGE_IN "4 $EXIT_PRINTED\n"; close KID_MESSAGE_IN;
print $logh "KID4 finished\n"; exit($EXIT_PRINTED); } } }
## Close the renderer process and wait until all kid processes finish.
sub closerendererhandle {
my ($rendererhandle, $rendererpid) = @_;
print $logh "${added_lf}Closing renderer\n";
# Do it! close $rendererhandle;
# Wait for all kid processes to finish or one kid process to fail close KID_MESSAGE_IN; while ((!$kidfailed) && !(($kid3finished) && ($kid4finished))) { my $message = <KID_MESSAGE>; chomp $message; if ($message =~ /(\d+)\s+(\d+)/) { my $kid_id = $1; my $exitstat = $2; print $logh "KID$kid_id exited with status $exitstat\n"; if ($exitstat > 0) { $kidfailed = $exitstat; } elsif ($kid_id == 3) { $kid3finished = 1; } elsif ($kid_id == 4) { $kid4finished = 1; } } }
close KID_MESSAGE;
# If a kid failed, return the exit stat of this kid if ($kidfailed != 0) { $retval = $kidfailed; }
print $logh "Renderer exit stat: $retval\n"; # Wait for renderer child waitpid($rendererpid, 0); print $logh "Renderer process finished\n"; return ($retval); }
## This function is only used when the input data is not ## PostScript. Then it runs a filter which converts non-PostScript ## files into PostScript. The user can choose which filter he wants ## to use. The filter command line is provided by $fileconverter.
sub getfileconverterhandle {
# Already read data must be converted, too my ($dat, $alreadyread) = @_;
print $logh "${added_lf}Starting converter for non-PostScript files\n";
# Determine with which command non-PostScript files are converted # to PostScript if ($fileconverter eq "") { if ($spoolerfileconverters->{$spooler}) { $fileconverter = $spoolerfileconverters->{$spooler}; } else { for my $c (@fileconverters) { ($c =~ m/^\s*(\S+)\s+/) || ($c = m/^\s*(\S+)$/); my $command = $1; if( -x $command ){ $fileconverter = $command; } else { for (split(':', $ENV{'PATH'})) { if (-x "$_/$command") { $fileconverter = $c; last; } } } if ($fileconverter ne "") { last; } } } if ($fileconverter eq "") { $fileconverter = "echo \"Cannot convert file to " . "PostScript!\" 1>&2"; } }
# Insert the page size into the $fileconverter if ($fileconverter =~ /\@\@([^@]+)\@\@PAGESIZE\@\@/) { # We always use the "header" option swt here, with a # non-PostScript file we have no "currentpage" my $optstr = $1; my $arg; my $sizestr = (($arg = $dat->{'args_byname'}{'PageSize'}) ? $arg->{'header'} : ""); if ($sizestr) { # Use wider margins so that the pages come out completely on # every printer model (especially HP inkjets) if ($fileconverter =~ /^\s*(a2ps)\s+/) { if (lc($sizestr) eq "letter") { $sizestr = "Letterdj"; } elsif (lc($sizestr) eq "a4") { $sizestr = "A4dj"; } } $optstr .= $sizestr; } else { $optstr = ""; } $fileconverter =~ s/\@\@([^@]+)\@\@PAGESIZE\@\@/$optstr/; }
# Insert the job title into the $fileconverter if ($fileconverter =~ /\@\@([^@]+)\@\@JOBTITLE\@\@/) { if ($do_docs) { $jobtitle = "Documentation for the $model"; } my $titlearg = $1; my ($arg, $optstr); ($arg = $jobtitle) =~ s/\"/\\\"/g; if (($titlearg =~ /\"/) || $arg) { $optstr = $titlearg . ($titlearg =~ /\"/ ? '' : '"') . ($arg ? "$arg\"" : '"'); } else { $optstr = ""; } $fileconverter =~ s/\@\@([^@]+)\@\@JOBTITLE\@\@/$optstr/; }
# Apply "pstops" when having used a file converter under CUPS, so # CUPS can stuff the default settings into the PostScript output # of the file converter (so all CUPS settings get also applied when # one prints the documentation pages (all other files we get # already converted to PostScript by CUPS. if ($spooler eq 'cups') { $fileconverter .= " | ${programdir}pstops '$rargs[0]' '$rargs[1]' '$rargs[2]' " . "'$rargs[3]' '$rargs[4]'"; }
# Variables for the kid processes reporting their state
# Set up a pipe for the kids to pass their exit stat to the main process pipe KID_MESSAGE_CONV, KID_MESSAGE_CONV_IN;
# When one kid fails put the exit stat here $convkidfailed = 0;
# When a kid exits successfully, mark it here $kid1finished = 0; $kid2finished = 0;
use IO::Handle; pipe KID1_IN, KID1; KID1->autoflush(1); my $kid1 = fork(); if (!defined($kid1)) { close KID1; close KID1_IN; print $logh "$0: cannot fork for kid1!\n"; rip_die ("can't fork for kid1", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); }
if ($kid1) {
# we are the parent; return a glob to the filehandle close KID1;
return ( *KID1_IN, $kid1 );
} else { # We go on reading the job data and stuff it into the file # converter close KID1_IN;
pipe KID2_IN, KID2; KID2->autoflush(1); $kid2 = fork(); if (!defined($kid2)) { print $logh "$0: cannot fork for kid2!\n"; close KID1; close KID2; close KID2_IN; close KID_MESSAGE_CONV; print KID_MESSAGE_CONV_IN "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; rip_die ("can't fork for kid2", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if ($kid2) { # parent, child of primary task; we are |$fileconverter| close KID2;
print $logh "file converter PID kid2=$kid2\n"; if (($debug) || ($spooler ne 'cups')) { print $logh "file converter command: $fileconverter\n"; } if (!close STDIN && $! != $ESPIPE) { close KID1; close KID2_IN; close KID_MESSAGE_CONV; print KID_MESSAGE_CONV_IN "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_CONV_IN; rip_die ("Couldn't close STDIN in $kid2", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if (!open (STDIN, "<&KID2_IN")) { close KID1; close KID2_IN; close KID_MESSAGE_CONV; print KID_MESSAGE_CONV_IN "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_CONV_IN; rip_die ("Couldn't dup KID2_IN", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if (!close STDOUT) { close KID1; close KID2_IN; close KID_MESSAGE_CONV; print KID_MESSAGE_CONV_IN "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_CONV_IN; rip_die ("Couldn't close STDOUT in $kid2", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if (!open (STDOUT, ">&KID1")) { close KID1; close KID2_IN; close KID_MESSAGE_CONV; print KID_MESSAGE_CONV_IN "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_CONV_IN; rip_die ("Couldn't dup KID1", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } if ($debug) { if (!open (STDERR, ">&$logh")) { close KID1; close KID2_IN; close KID_MESSAGE_CONV; print KID_MESSAGE_CONV_IN "1 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_CONV_IN; rip_die ("Couldn't dup logh to stderr", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } }
# Actually run the thing... system("$fileconverter"); if ($? != 0) { my $fileconverterretval = $? >> 8; print $logh "file converter return value: " . "$fileconverterretval\n"; my $fileconvertersignal = $? & 127; print $logh "file converter received signal: ". "$fileconverterretval\n"; close STDOUT; close KID1; close STDIN; close KID2_IN; # Handle signals if ($fileconvertersignal == SIGUSR1) { $retval = $EXIT_PRNERR; } elsif ($fileconvertersignal == SIGUSR2) { $retval = $EXIT_PRNERR_NORETRY; } elsif ($fileconvertersignal == SIGTTIN) { $retval = $EXIT_ENGAGED; } if ($retval != $EXIT_PRINTED) { close KID_MESSAGE_CONV; print KID_MESSAGE_CONV_IN "1 $retval\n"; close KID_MESSAGE_CONV_IN; exit $retval; } # Evaluate fileconverter result if ($fileconverterretval == 0) { # Success, exit with 0 and inform main process close KID_MESSAGE_CONV; print KID_MESSAGE_CONV_IN "1 $EXIT_PRINTED\n"; close KID_MESSAGE_CONV_IN; exit $EXIT_PRINTED; } else { # Unknown error close KID_MESSAGE_CONV; print KID_MESSAGE_CONV_IN "1 $EXIT_PRNERR\n"; close KID_MESSAGE_CONV_IN; rip_die ("The file converter command line returned " . "an unrecognized error code " . "$fileconverterretval.", $EXIT_PRNERR); } } close STDOUT; close KID1; close STDIN; close KID2_IN; # When arrived here the fileconverter command line was # successful. # So exit with zero exit value here and inform the main process close KID_MESSAGE_CONV; print KID_MESSAGE_CONV_IN "1 $EXIT_PRINTED\n"; close KID_MESSAGE_CONV_IN; # Wait for input child waitpid($kid1, 0); print $logh "KID1 finished\n"; exit $EXIT_PRINTED; } else { # child, first part of the pipe, reading in the data from # standard input and stuffing it into the file converter # after putting in the already read data (in $alreadyread) close KID1; close KID2_IN;
# At first pass the data which we have already read to the # filter print KID2 $alreadyread; # Then read the rest from standard input while (<STDIN>) { print KID2 $_; }
if (!close STDIN && $! != $ESPIPE) { close KID2; close KID_MESSAGE_CONV; print KID_MESSAGE_CONV_IN "2 $EXIT_PRNERR_NORETRY_BAD_SETTINGS\n"; close KID_MESSAGE_CONV_IN; rip_die ("error closing STDIN", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } close KID2;
print $logh "tail process done reading data from STDIN\n";
# Successful exit, inform main process close KID_MESSAGE_CONV; print KID_MESSAGE_CONV_IN "2 $EXIT_PRINTED\n"; close KID_MESSAGE_CONV_IN;
print $logh "KID2 finished\n"; exit($EXIT_PRINTED); } } }
## Close the file conversion process and wait until all kid processes ## finish.
sub closefileconverterhandle {
my ($fileconverterhandle, $fileconverterpid) = @_;
print $logh "${added_lf}Closing file converter\n";
# Do it! close $fileconverterhandle;
# Wait for all kid processes to finish or one kid process to fail close KID_MESSAGE_CONV_IN; while ((!$convkidfailed) && !(($kid1finished) && ($kid2finished))) { my $message = <KID_MESSAGE_CONV>; chomp $message; if ($message =~ /(\d+)\s+(\d+)/) { my $kid_id = $1; my $exitstat = $2; print $logh "KID$kid_id exited with status $exitstat\n"; if ($exitstat > 0) { $convkidfailed = $exitstat; } elsif ($kid_id == 1) { $kid1finished = 1; } elsif ($kid_id == 2) { $kid2finished = 1; } } }
close KID_MESSAGE_CONV;
# If a kid failed, return the exit stat of this kid if ($convkidfailed != 0) { $retval = $convkidfailed; }
print $logh "File converter exit stat: $retval\n"; # Wait for fileconverter child waitpid($fileconverterpid, 0); print $logh "File converter process finished\n"; return ($retval); }
## Generate the documentation page and return a filehandle to get it
sub getdocgeneratorhandle {
# The data structure with the options my ($dat) = @_;
print $logh "${added_lf}Generating documentation page for the $model\n";
# Printer queue name my $printerstr; if ($printer) { $printerstr = $printer; } else { $printerstr = "<printer>"; } # Spooler-specific differences my ($command, $enumopt, $enumoptleft, $enumoptequal, $enumoptright, $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal, $booloptright, $numopt, $numoptleft, $numoptequal, $numoptright, $stropt, $stroptleft, $stroptequal, $stroptright, $optsep, $trailer, $custompagesize); if ($spooler eq 'cups') { ($command, $enumopt, $enumoptleft, $enumoptequal, $enumoptright, $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal, $booloptright, $numopt, $numoptleft, $numoptequal, $numoptright, $stropt, $stroptleft, $stroptequal, $stroptright, $optsep, $trailer, $custompagesize) = ("lpr -P $printerstr ", "-o ", "", "=", "", "-o ", "no", "", "=", "", "-o ", "", "=", "", "-o ", "", "=", "", " "," <file>", "\n Custom size: -o PageSize=Custom." . "<width>x<height>[<unit>]\n" . " Units: pt (default), in, cm, mm\n" . " Example: -o PageSize=Custom.4.0x6.0in\n"); } elsif ($spooler eq 'lpd') { ($command, $enumopt, $enumoptleft, $enumoptequal, $enumoptright, $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal, $booloptright, $numopt, $numoptleft, $numoptequal, $numoptright, $stropt, $stroptleft, $stroptequal, $stroptright, $optsep, $trailer, $custompagesize) = ("lpr -P $printerstr -J \"", "", "", "=", "", "", "", "", "=", "", "", "", "=", "", "", "", "=", "", " ", "\" <file>", "\n Custom size: PageSize=Custom." . "<width>x<height>[<unit>]\n" . " Units: pt (default), in, cm, mm\n" . " Example: PageSize=Custom.4.0x6.0in\n"); } elsif ($spooler eq 'gnulpr') { ($command, $enumopt, $enumoptleft, $enumoptequal, $enumoptright, $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal, $booloptright, $numopt, $numoptleft, $numoptequal, $numoptright, $stropt, $stroptleft, $stroptequal, $stroptright, $optsep, $trailer, $custompagesize) = ("lpr -P $printerstr ", "-o ", "", "=", "", "-o ", "", "", "=", "", "-o ", "", "=", "", "-o ", "", "=", "", " "," <file>", "\n Custom size: -o PageSize=Custom." . "<width>x<height>[<unit>]\n" . " Units: pt (default), in, cm, mm\n" . " Example: -o PageSize=Custom.4.0x6.0in\n"); } elsif ($spooler eq 'lprng') { ($command, $enumopt, $enumoptleft, $enumoptequal, $enumoptright, $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal, $booloptright, $numopt, $numoptleft, $numoptequal, $numoptright, $stropt, $stroptleft, $stroptequal, $stroptright, $optsep, $trailer, $custompagesize) = ("lpr -P $printerstr ", "-Z ", "", "=", "", "-Z ", "", "", "=", "", "-Z ", "", "=", "", "-Z ", "", "=", "", " "," <file>", "\n Custom size: -Z PageSize=Custom." . "<width>x<height>[<unit>]\n" . " Units: pt (default), in, cm, mm\n" . " Example: -Z PageSize=Custom.4.0x6.0in\n"); } elsif ($spooler eq 'ppr') { ($command, $enumopt, $enumoptleft, $enumoptequal, $enumoptright, $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal, $booloptright, $numopt, $numoptleft, $numoptequal, $numoptright, $stropt, $stroptleft, $stroptequal, $stroptright, $optsep, $trailer, $custompagesize) = ("ppr -d $printerstr --ripopts \"", "", "", "=", "", "", "", "", "=", "", "", "", "=", "", "", "", "=", "", " ","\" <file>", "\n Custom size: PageSize=Custom." . "<width>x<height>[<unit>]\n" . " Units: pt (default), in, cm, mm\n" . " Example: PageSize=Custom.4.0x6.0in\n"); } elsif ($spooler eq 'ppr-int') { ($command, $enumopt, $enumoptleft, $enumoptequal, $enumoptright, $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal, $booloptright, $numopt, $numoptleft, $numoptequal, $numoptright, $stropt, $stroptleft, $stroptequal, $stroptright, $optsep, $trailer, $custompagesize) = ("ppr -d $printerstr -i \"", "", "", "=", "", "", "", "", "=", "", "", "", "=", "", "", "", "=", "", " ","\" <file>", "\n Custom size: PageSize=Custom." . "<width>x<height>[<unit>]\n" . " Units: pt (default), in, cm, mm\n" . " Example: PageSize=Custom.4.0x6.0in\n"); } elsif ($spooler eq 'cps') { ($command, $enumopt, $enumoptleft, $enumoptequal, $enumoptright, $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal, $booloptright, $numopt, $numoptleft, $numoptequal, $numoptright, $stropt, $stroptleft, $stroptequal, $stroptright, $optsep, $trailer, $custompagesize) = ("lpr -P $printerstr ", "-o ", "", "=", "", "-o ", "", "", "=", "", "-o ", "", "=", "", "-o ", "", "=", "", " "," <file>", "\n Custom size: -o PageSize=Custom." . "<width>x<height>[<unit>]\n" . " Units: pt (default), in, cm, mm\n" . " Example: -o PageSize=Custom.4.0x6.0in\n"); } elsif ($spooler eq 'direct') { ($command, $enumopt, $enumoptleft, $enumoptequal, $enumoptright, $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal, $booloptright, $numopt, $numoptleft, $numoptequal, $numoptright, $stropt, $stroptleft, $stroptequal, $stroptright, $optsep, $trailer, $custompagesize) = ("$programname -P $printerstr ", "-o ", "", "=", "", "-o ", "", "", "=", "", "-o ", "", "=", "", "-o ", "", "=", "", " "," <file>", "\n Custom size: -o PageSize=Custom." . "<width>x<height>[<unit>]\n" . " Units: pt (default), in, cm, mm\n" . " Example: -o PageSize=Custom.4.0x6.0in\n"); } elsif ($spooler eq 'pdq') { ($command, $enumopt, $enumoptleft, $enumoptequal, $enumoptright, $boolopt, $booloptfalseprefix, $booloptleft, $booloptequal, $booloptright, $numopt, $numoptleft, $numoptequal, $numoptright, $stropt, $stroptleft, $stroptequal, $stroptright, $optsep, $trailer, $custompagesize) = ("pdq -P $printerstr ", "-o", "", "_", "", "-o", "no", "", "_", "", "-a", "", "=", "", "-a", "", "=", "", " "," <file>", "\n" . "Option 'PageWidth':\n". " Page Width (for \"Custom\" page size)\n" . " A floating point number argument\n" . " Range: 0 <= x <= 100000\n" . " Example: -aPageWidth=123.4\n" . "\n" . "Option 'PageHeight':\n" . " Page Height (for \"Custom\" page size)\n" . " A floating point number argument\n" . " Range: 0 <= x <= 100000\n" . " Example: -aPageHeight=234.5\n" . "\n" . "Option 'PageSizeUnit':\n" . " Unit (for \"Custom\" page size)\n" . " An enumerated choice argument\n" . " Possible choices:\n" . " o -oPageSizeUnit_pt: Points (1/72 inch)\n" . " o -oPageSizeUnit_in: Inches\n" . " o -oPageSizeUnit_cm: cm\n" . " o -oPageSizeUnit_mm: mm\n" . " Example: -oPageSizeUnit_mm\n"); }
# Variables for the kid processes reporting their state
# Set up a pipe for the kids to pass their exit stat to the main process pipe KID_MESSAGE_DOC, KID_MESSAGE_DOC_IN;
# When the kid fails put the exit stat here $dockidfailed = 0;
# When the kid exits successfully, mark it here $kid0finished = 0;
use IO::Handle; pipe KID0_IN, KID0; KID0->autoflush(1); my $kid0 = fork(); if (!defined($kid0)) { close KID0; close KID0_IN; print $logh "$0: cannot fork for kid0!\n"; rip_die ("can't fork for kid0", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); }
if ($kid0) { # we are the parent; return a glob to the filehandle close KID0; print $logh "Documentation page generator PID kid0=$kid0\n"; return ( *KID0_IN, $kid0 ); }
# we are the kid; we generate the documentation page
close KID0_IN;
# Kill data on STDIN to satisfy PPR if (($spooler eq 'ppr_int') || ($spooler eq 'ppr')) { while (my $dummy = <STDIN>) {}; } close STDIN or print $logh "Error closing STDIN for docs print\n";
# write the job into KID0 select KID0;
print "\nInvokation summary for the $model\n\n"; print "Use the following command line:\n\n"; if ($booloptfalseprefix) { # I think that what you want to indicate is that the prefix for a false # boolean has this form: xxx [no]<switch> or something similar print " ${command}${enumopt}${enumoptleft}<option>" . "${enumoptequal}<choice>${enumoptright}${optsep}" . "${boolopt}${booloptleft}\[${booloptfalseprefix}\]<switch>" . "${booloptright}${optsep}" . "${numopt}${numoptleft}<num. option>${numoptequal}" . "<value>${numoptright}${optsep}" . "${stropt}${stroptleft}<string option>${stroptequal}" . "<string>${stroptright}" . "${trailer}\n\n"; } else { print " ${command}${enumopt}${enumoptleft}<option>" . "${enumoptequal}<choice>${enumoptright}${optsep}" . "${boolopt}${booloptleft}<switch>${booloptequal}" . "<True/False>${booloptright}${optsep}" . "${numopt}${numoptleft}<num. option>${numoptequal}" . "<value>${numoptright}${optsep}" . "${stropt}${stroptleft}<string option>${stroptequal}" . "<string>${stroptright}" . "${trailer}\n\n"; } print "The following options are available for this printer:\n\n";
for my $arg (@{$dat->{'args'}}) { my ($name, $type, $comment, $spot, $default) = ($arg->{'name'}, $arg->{'type'}, $arg->{'comment'}, $arg->{'spot'}, $arg->{'default'});
# Is this really an option? Otherwise skip it. next if (!$type);
# We don't need "PageRegion", we have "PageSize" next if ($name eq "PageRegion");
# Skip enumerated choice options with only one choice next if (($type eq 'enum') && ($#{$arg->{'vals'}} < 1));
my $commentstr = ""; if ($comment) { $commentstr = " $comment\n"; }
my $typestr; if ($type eq "enum") { $typestr = "An enumerated choice"; } elsif ($type eq "bool") { $typestr = "A boolean"; } elsif ($type eq "int") { $typestr = "An integer number"; } elsif ($type eq "float") { $typestr = "A floating point number"; } elsif (($type eq "string") || ($type eq "password")) { $typestr = "A string"; }
print "Option '$name':\n$commentstr $typestr argument\n"; print " This options corresponds to a JCL command\n" if ($arg->{'style'} eq 'J'); if ($type eq 'bool') { print " Possible choices:\n"; if ($booloptfalseprefix) { print " o $name: $arg->{'comment_true'}\n"; print " o $booloptfalseprefix$name: " . "$arg->{'comment_false'}\n"; if (defined($default)) { my $defstr = ($default ? "" : "$booloptfalseprefix"); print " Default: $defstr$name\n"; } print " Example: ${boolopt}${booloptleft}${name}" . "${booloptright}\n"; } else { print " o True: $arg->{'comment_true'}\n"; print " o False: $arg->{'comment_false'}\n"; if (defined($default)) { my $defstr = ($default ? "True" : "False"); print " Default: $defstr\n"; } print " Example: ${boolopt}${booloptleft}${name}" . "${booloptequal}True${booloptright}\n"; } } elsif ($type eq 'enum') { print " Possible choices:\n"; my $exarg; my $havecustomsize = 0; for (@{$arg->{'vals'}}) { my ($choice, $comment) = ($_->{'value'}, $_->{'comment'}); print " o $choice: $comment\n"; if (($name eq "PageSize") && ($choice eq "Custom")) { $havecustomsize = 1; } $exarg=$choice; } if (defined($default)) { print " Default: $default\n"; } print " Example: ${enumopt}${enumoptleft}${name}" . "${enumoptequal}${exarg}${enumoptright}\n"; if ($havecustomsize) { print $custompagesize; } } 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: ${numopt}${numoptleft}${name}" . "${numoptequal}${exarg}${numoptright}\n"; } elsif ($type eq 'string' or $type eq 'password') { my $maxlength = $arg->{'maxlength'}; if (defined($maxlength)) { print " Maximum length: $maxlength characters\n"; } if (defined($default)) { print " Default: $default\n"; } print " Examples/special settings:\n"; for (@{$arg->{'vals'}}) { my ($value, $comment, $driverval, $proto) = ($_->{'value'}, $_->{'comment'}, $_->{'driverval'}, $arg->{'proto'}); # Retrieve the original string from the prototype # and the driverval my $string; if ($proto) { my $s = index($proto, '%s'); my $l = length($driverval) - length($proto) + 2; if (($s < 0) || ($l < 0)) { $string = $driverval; } else { $string = substr($driverval, $s, $l); } } else { $string = $driverval; } print " o ${stropt}${stroptleft}${name}" . "${stroptequal}${value}${stroptright}"; if (($value ne $string) || ($comment ne $value)) { print " ("; } if ($value ne $string) { if ($string eq '') { print "blank string"; } else { print "\"$string\""; } } if (($value ne $string) && ($comment ne $value)) { print ", "; } if ($value ne $comment) { print "$comment"; } if (($value ne $string) || ($comment ne $value)) { print ")"; } print "\n"; } }
print "\n"; } select STDOUT; close KID0 or print $logh "Error closing KID0 for docs print\n"; close STDOUT or print $logh "Error closing STDOUT for docs print\n";
# Finished successfully, inform main process close KID_MESSAGE_DOC; print KID_MESSAGE_DOC_IN "0 $EXIT_PRINTED\n"; close KID_MESSAGE_DOC_IN;
print $logh "KID0 finished\n"; exit($EXIT_PRINTED);
}
## Close the documentation page generation process and wait until the ## kid process finishes.
sub closedocgeneratorhandle {
my ($handle, $pid) = @_;
print $logh "${added_lf}Closing documentation page generator\n";
# Do it! close $handle;
# Wait for the kid process to finish or the kid process to fail close KID_MESSAGE_DOC_IN; while ((!$dockidfailed) && (!$kid0finished)) { my $message = <KID_MESSAGE_DOC>; chomp $message; if ($message =~ /(\d+)\s+(\d+)/) { my $kid_id = $1; my $exitstat = $2; print $logh "KID$kid_id exited with status $exitstat\n"; if ($exitstat > 0) { $dockidfailed = $exitstat; } elsif ($kid_id eq "0") { $kid0finished = 1; } } }
close KID_MESSAGE_DOC;
# If the kid failed, return the exit stat of the kid if ($dockidfailed != 0) { $retval = $dockidfailed; }
print $logh "Documentation page generator exit stat: $retval\n"; # Wait for fileconverter child waitpid($pid, 0); print $logh "Documentation page generator process finished\n"; return ($retval); }
# Find an argument by name in a case-insensitive way sub argbyname { my $name = $_[0];
for my $arg (@{$dat->{'args'}}) { return $arg if (lc($name) eq lc($arg->{'name'})); }
return undef; }
sub valbyname { my ($arg,$name) = @_;
for my $val (@{$arg->{'vals'}}) { return $val if (lc($name) eq lc($val->{'value'})); }
return undef; }
# Write a Good-Bye letter and clean up before committing suicide (send # error message to caller)
sub rip_die { my ($message, $exitstat) = @_; my $errmsg = "$!"; my $errcod = $! + 0;
# Close the documentation page generator (if it was used) if ($docgeneratorpid) { if ($kid0) { print $logh "Killing process $kid0 (KID0)\n"; kill(9, $kid0); } $docgeneratorpid = 0; }
# Close the file converter (if it was used) if ($fileconverterpid) { if ($kid2) { print $logh "Killing process $kid2 (KID2)\n"; kill(9, $kid2); } if ($kid1) { print $logh "Killing process $kid1 (KID1)\n"; kill(9, $kid1); } $fileconverterpid = 0; }
# Close the renderer if ($rendererpid) { if ($kid4) { print $logh "Killing process $kid4 (KID4)\n"; kill(9, $kid4); } if ($kid3) { print $logh "Killing process $kid3 (KID3)\n"; kill(9, $kid3); } $rendererpid = 0; }
print $logh "Process dying with \"$message\", exit stat: $exitstat\n\terror: $errmsg ($errcod)\n"; if ($spooler eq 'ppr_int') { # Special error handling for PPR intefaces $message =~ s/\\/\\\\/; $message =~ s/\"/\\\"/; my @messagelines = split("\n", $message); my $firstline = "TRUE"; for my $line (@messagelines) { system("lib/alert $printer $firstline \"$line\""); $firstline = "FALSE"; } } else { print STDERR $message . "\n"; } exit $exitstat; }
# Signal handling routines
sub set_exit_prnerr { $retval = $EXIT_PRNERR; }
sub set_exit_prnerr_noretry { $retval = $EXIT_PRNERR_NORETRY; }
sub set_exit_engaged { $retval = $EXIT_ENGAGED; }
# Read the config file
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; }
sub removeunprintables { # Remove unprintable characters my $str = $_[0]; $str =~ s/[\x00-\x1f]//g; return $str; }
sub removeshellescapes { # Remove shell escape characters my $str = $_[0]; $str =~ s/[\|<>&!\$\'\"\#\*\?\(\)\[\]\{\}]//g; return $str; }
sub removespecialchars { # Remove unprintable and shell escape characters return removeshellescapes(removeunprintables($_[0])); }
sub unhtmlify { my $str = $_[0];
# Replace HTML/XML entities by the original characters $str =~ s/\'/\'/g; $str =~ s/\"/\"/g; $str =~ s/\>/\>/g; $str =~ s/\</\</g; $str =~ s/\&/\&/g;
# Replace special entities by job data $str =~ s/\&job;/$jobid/g; $str =~ s/\&user;/$jobuser/g; $str =~ s/\&host;/$jobhost/g; $str =~ s/\&title;/$jobtitle/g; $str =~ s/\&copies;/$copies/g; $str =~ s/\&options;/$optstr/g; my ($sec, $min, $hour, $mday, $mon, $year) = (localtime)[0..5]; my $yearstr = sprintf("%04d", $year + 1900); my $monstr = sprintf("%02d", $mon + 1); my $mdaystr = sprintf("%02d", $mday); my $hourstr = sprintf("%02d", $hour); my $minstr = sprintf("%02d", $min); my $secstr = sprintf("%02d", $sec);
$str =~ s/\&year;/$yearstr/g; $str =~ s/\&month;/$monstr/g; $str =~ s/\&date;/$mdaystr/g; $str =~ s/\&hour;/$hourstr/g; $str =~ s/\&min;/$minstr/g; $str =~ s/\&sec;/$secstr/g; return $str; }
sub unhexify { # Replace hex notation for unprintable characters in PPD files # by the actual characters ex: "<0A>" --> chr(hex("0A")) my ($input) = @_; my $output = ""; my $hexmode = 0; my $firstdigit = ""; for (my $i = 0; $i < length($input); $i ++) { my $c = substr($input, $i, 1); if ($hexmode) { if ($c eq ">") { # End of hex string $hexmode = 0; } elsif ($c =~ /^[0-9a-fA-F]$/) { # Hexadecimal digit, two of them give a character if ($firstdigit ne "") { $output .= chr(hex("$firstdigit$c")); $firstdigit = ""; } else { $firstdigit = $c; } } } else { if ($c eq "<") { # Beginning of hex string $hexmode = 1; } else { # Normal character $output .= $c; } } } return $output; }
sub undossify( $ ) { # Remove "dossy" line ends ("\r\n") from a string my $str = $_[0]; $str =~ s/\r\n/\n/gs; $str =~ s/\r$//s; return( $str ); }
sub checkarg { # Check if there is already an argument record $argname in $dat, if not, # create one my ($dat, $argname) = @_; return if defined($dat->{'args_byname'}{$argname}); # argument record my $rec; $rec->{'name'} = $argname; # Insert record in 'args' array for browsing all arguments push(@{$dat->{'args'}}, $rec); # 'args_byname' hash for looking up arguments by name $dat->{'args_byname'}{$argname} = $dat->{'args'}[$#{$dat->{'args'}}]; # Default execution style is 'G' (PostScript) since all arguments for # which we don't find "*Foomatic..." keywords are usual PostScript # options $dat->{'args_byname'}{$argname}{'style'} = 'G'; # Default prototype for code to insert, used by enum options $dat->{'args_byname'}{$argname}{'proto'} = '%s'; # stop Perl nattering about undefined to string comparisons $dat->{'args_byname'}{$argname}{'type'} = ''; print $logh "Added option $argname\n"; }
sub checksetting { # Check if there is already an choice record $setting in the $argname # argument in $dat, if not, create one my ($dat, $argname, $setting) = @_; return if defined($dat->{'args_byname'}{$argname}{'vals_byname'}{$setting}); # setting record my $rec; $rec->{'value'} = $setting; # Insert record in 'vals' array for browsing all settings push(@{$dat->{'args_byname'}{$argname}{'vals'}}, $rec); # 'vals_byname' hash for looking up settings by name $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting} = $dat->{'args_byname'}{$argname}{'vals'}[$#{$dat->{'args_byname'}{$argname}{'vals'}}]; }
sub removearg { # remove the argument record $argname from $dat my ($dat, $argname) = @_; return if !defined($dat->{'args_byname'}{$argname}); # Remove 'args_byname' hash for looking up arguments by name delete $dat->{'args_byname'}{$argname}; # Remove argument itself for (my $i = 0; $i <= $#{$dat->{'args'}}; $i ++) { if ($dat->{'args'}[$i]{'name'} eq $argname) { print $logh "Removing option " . $argname . "\n"; splice(@{$dat->{'args'}}, $i, 1); last; } } }
sub removepsargs { # remove all records of PostScript arguments from $dat my ($dat) = @_; return if !defined($dat); for (my $i = 0; $i <= $#{$dat->{'args'}}; $i ++) { if ($dat->{'args'}[$i]{'style'} eq 'G') { print $logh "Removing PostScript option " . $dat->{'args'}[$i]{'name'} . "\n"; # Remove 'args_byname' hash for looking up arguments by name delete $dat->{'args_byname'}{$dat->{'args'}[$i]{'name'}}; # Remove argument itself splice(@{$dat->{'args'}}, $i, 1); $i --; } } }
sub checkoptionvalue {
## This function checks whether a given value is valid for a given ## option. If yes, it returns a cleaned value (e. g. always 0 or 1 ## for boolean options), otherwise "undef". If $forcevalue is set, ## we always determine a corrected value to insert (we never return ## "undef").
# Is $value valid for the option named $argname? my ($dat, $argname, $value, $forcevalue) = @_;
# Record for option $argname my $arg = $dat->{'args_byname'}{$argname}; $arg->{'type'} = '' if not defined $arg->{'type'};
if ($arg->{'type'} eq 'bool') { my $lcvalue = lc($value); if ((($lcvalue) eq 'true') || (($lcvalue) eq 'on') || (($lcvalue) eq 'yes') || (($lcvalue) eq '1')) { return 1; } elsif ((($lcvalue) eq 'false') || (($lcvalue) eq 'off') || (($lcvalue) eq 'no') || (($lcvalue) eq '0')) { return 0; } elsif ($forcevalue) { # This maps Unknown to mean False. Good? Bad? # It was done so in Foomatic 2.0.x, too. my $name = $arg->{'name'}; print $logh "The value $value for $name is not a " . "choice!\n" . " --> Using False instead!\n"; return 0; } } elsif ($arg->{'type'} eq 'enum') { if ($value =~ /^None$/i) { return 'None'; } elsif (defined($arg->{'vals_byname'}{$value})) { return $value; } elsif ((($arg->{'name'} eq "PageSize") || ($arg->{'name'} eq "PageRegion")) && (defined($arg->{'vals_byname'}{'Custom'})) && ($value =~ m!^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$!)) { # Custom paper size return $value; } elsif ($forcevalue) { # wtf!? that's not a choice! my $name = $arg->{'name'}; # Return the first entry of the list my $firstentry = $arg->{'vals'}[0]{'value'}; print $logh "The value $value for $name is not a " . "choice!\n" . " --> Using $firstentry instead!\n"; return $firstentry; } } elsif (($arg->{'type'} eq 'int') || ($arg->{'type'} eq 'float')) { if (($value <= $arg->{'max'}) && ($value >= $arg->{'min'})) { return $value; } elsif ($forcevalue) { my $name = $arg->{'name'}; my $newvalue; if ($value > $arg->{'max'}) { $newvalue = $arg->{'max'} } elsif ($value < $arg->{'min'}) { $newvalue = $arg->{'min'} } print $logh "The value $value for $name is out of " . "range!\n" . " --> Using $newvalue instead!\n"; return $newvalue; } } elsif (($arg->{'type'} eq 'string') || ($arg->{'type'} eq 'password')) { if (defined($arg->{'vals_byname'}{$value})) { my $name = $arg->{'name'}; print $logh "The value $value for $name is a predefined choice\n"; return $value; } elsif (stringvalid($dat, $argname, $value)) { # Check whether the string is one of the enumerated choices my $sprintfproto = $arg->{'proto'}; $sprintfproto =~ s/\%(?!s)/\%\%/g; my $driverval = sprintf($sprintfproto, $value); for my $val (@{$arg->{'vals'}}) { if (($val->{'driverval'} eq $driverval) || ($val->{'driverval'} eq $value)) { my $name = $arg->{'name'}; print $logh "The string $value for $name is the predefined " . "choice $val->{value}\n"; return $val->{value}; } } # "None" is mapped to the empty string if ($value eq 'None') { my $name = $arg->{'name'}; print $logh "Option $name: 'None' is the mapped to the " . "empty string\n"; return ''; } # No matching choice? Return the original string return $value; } elsif ($forcevalue) { my $name = $arg->{'name'}; my $str = substr($value, 0, $arg->{'maxlength'}); if (stringvalid($dat, $argname, $str)) { print $logh "The string $value for $name is longer than " . "$arg->{'maxlength'}, string shortened to $str\n"; return $str; } elsif ($#{$arg->{'vals'}} >= 0) { # First list item my $firstentry = $arg->{'vals'}[0]{'value'}; print $logh "The string $value for $name contains forbidden " . "characters or does not match the regular expression " . "defined for this option, using predefined choice " . "$firstentry instead\n"; return $firstentry; } else { # We should not get here rip_die("Option $name incorrectly defined in the " . "PPD file!\n", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } } } return undef; }
sub stringvalid {
## Checks whether a user-supplied value for a string option is valid ## It must be within the length limit, should only contain allowed ## characters and match the given regexp
# Option and string my ($dat, $argname, $value) = @_;
my $arg = $dat->{'args_byname'}{$argname};
# Maximum length return 0 if (defined($arg->{'maxlength'}) && (length($value) > $arg->{'maxlength'}));
# Allowed characters if ($arg->{'allowedchars'}) { my $chars = $arg->{'allowedchars'}; # Quote the slashes (if a slash is preceeded by an even number of # backslashes, it is not already quoted) $chars =~ s/(?<!\\)((\\\\)*)\//$2\\\//g; return 0 if $value !~ /^[$chars]*$/; }
# Regular expression if ($arg->{'allowedregexp'}) { my $regexp = $arg->{'allowedregexp'}; # Quote the slashes (if a slash is preceeded by an even number of # backslashes, it is not already quoted) $regexp =~ s/(?<!\\)((\\\\)*)\//$2\\\//g; return 0 if $value !~ /$regexp/; }
# All checks passed return 1; }
sub checkoptions {
## Let the values of a boolean option being 0 or 1 instead of ## "True" or "False", range-check the defaults of all options and ## issue warnings if the values are not valid
# Option set to be examined my ($dat, $optionset) = @_;
for my $arg (@{$dat->{'args'}}) { if (defined($arg->{$optionset})) { $arg->{$optionset} = checkoptionvalue ($dat, $arg->{'name'}, $arg->{$optionset}, 1); } }
# If the settings for "PageSize" and "PageRegion" are different, # set the one for "PageRegion" to the one for "PageSize" and issue # a warning. if ($dat->{'args_byname'}{'PageSize'}{$optionset} ne $dat->{'args_byname'}{'PageRegion'}{$optionset}) { print $logh "Settings for \"PageSize\" and \"PageRegion\" are " . "different:\n" . " PageSize: $dat->{'args_byname'}{'PageSize'}{$optionset}\n" . " PageRegion: ". "$dat->{'args_byname'}{'PageRegion'}{$optionset}\n" . "Using the \"PageSize\" value " . "\"$dat->{'args_byname'}{'PageSize'}{$optionset}\"," . " for both.\n"; $dat->{'args_byname'}{'PageRegion'}{$optionset} = $dat->{'args_byname'}{'PageSize'}{$optionset}; } }
# If the PageSize or PageRegion was changed, also change the other
sub syncpagesize { # Name and value of the option we set, and the option set where we # did the change my ($dat, $name, $value, $optionset) = @_;
# Don't do anything if we were called with an option other than # "PageSize" or "PageRegion" return if (($name ne "PageSize") && ($name ne "PageRegion")); # Don't do anything if not both "PageSize" and "PageRegion" exist return if ((!defined($dat->{'args_byname'}{'PageSize'})) || (!defined($dat->{'args_byname'}{'PageRegion'}))); my $dest; # "PageSize" --> "PageRegion" if ($name eq "PageSize") { $dest = "PageRegion"; } # "PageRegion" --> "PageSize" if ($name eq "PageRegion") { $dest = "PageSize"; } # Do it! my $val; if ($val=valbyname($dat->{'args_byname'}{$dest}, $value)) { # Standard paper size $dat->{'args_byname'}{$dest}{$optionset} = $val->{'value'}; } elsif ($val=valbyname($dat->{'args_byname'}{$dest}, "Custom")) { # Custom paper size $dat->{'args_byname'}{$dest}{$optionset} = $value; } }
sub copyoptions {
## Copy one option set into another one
# Source and destination option sets my ($dat, $srcoptionset, $destoptionset) = @_;
for my $arg (@{$dat->{'args'}}) { if (defined($arg->{$srcoptionset})) { $arg->{$destoptionset} = $arg->{$srcoptionset}; } } }
sub deleteoptions {
## Delete an option set
# option set to be removed my ($dat, $optionset) = @_;
for my $arg (@{$dat->{'args'}}) { if (defined($arg->{$optionset})) { delete($arg->{$optionset}); } } }
sub optionsequal {
## Compare two option sets, if they are equal, return 1, otherwise 0
# Option sets to be compared, flag to compare only command line and JCL # options my ($dat, $firstoptionset, $secondoptionset, $exceptPS) = @_;
for my $arg (@{$dat->{'args'}}) { next if ($exceptPS && ($arg->{'style'} eq 'G')); if ((defined($arg->{$firstoptionset})) && (defined($arg->{$secondoptionset}))) { # Both entries exist return 0 if $arg->{$firstoptionset} ne $arg->{$secondoptionset}; } elsif ((defined($arg->{$firstoptionset})) || (defined($arg->{$secondoptionset}))) { # One entry exists return 0; } # If no entry exists, the non-existing entries are considered as # equal } return 1; }
sub makeprologsection {
# option set to be used, # $comments = 1: Add "%%BeginProlog...%%EndProlog" my ($dat, $optionset, $comments) = @_; # Collect data to be inserted here my @output;
# Start comment if ($comments) { print $logh "\"Prolog\" section is missing, inserting it.\n"; push(@output, "%%BeginProlog\n"); }
# Generate the option code (not necessary when CUPS is spooler) if ($spooler ne 'cups') { print $logh "Inserting option code into \"Prolog\" section.\n"; buildcommandline ($dat, $optionset); push(@output, @{$dat->{'prologprepend'}}); }
# End comment if ($comments) { push(@output, "%%EndProlog\n"); }
return join('', @output); }
sub makesetupsection {
# option set to be used, $comments = 1: Add "%%BeginSetup...%%EndSetup" my ($dat, $optionset, $comments) = @_; # Collect data to be inserted here my @output;
# Start comment if ($comments) { print $logh "\"Setup\" section is missing, inserting it.\n"; push(@output, "%%BeginSetup\n"); }
# PostScript code to generate accounting messages for CUPS if ($spooler eq 'cups') { print $logh "Inserting PostScript code for CUPS' page accounting\n"; push(@output, $accounting_prolog); }
# Generate the option code (not necessary when CUPS is spooler) if ($spooler ne 'cups') { print $logh "Inserting option code into \"Setup\" section.\n"; buildcommandline ($dat, $optionset); push(@output, @{$dat->{'setupprepend'}}); }
# End comment if ($comments) { push(@output, "%%EndSetup\n"); }
return join('', @output); }
sub makepagesetupsection {
# option set to be used, # $comments = 1: Add "%%BeginPageSetup...%%EndPageSetup" my ($dat, $optionset, $comments) = @_; # Collect data to be inserted here my @output;
# Start comment if ($comments) { push(@output, "%%BeginPageSetup\n"); print $logh "\"PageSetup\" section is missing, inserting it.\n"; }
# Generate the option code (not necessary when CUPS is spooler) print $logh "Inserting option code into \"PageSetup\" section.\n"; buildcommandline ($dat, $optionset); if ($spooler ne 'cups') { push(@output, @{$dat->{'pagesetupprepend'}}); } else { push(@output, @{$dat->{'cupspagesetupprepend'}}); }
# End comment if ($comments) { push(@output, "%%EndPageSetup\n"); }
return join('', @output); }
sub parsepageranges {
## Parse a string containing page ranges and either check whether a ## given page is in the ranges or, if the given page number is zero, ## determine the score how specific this page range string is.
# String with page ranges and number of current page (0 for score) my ($ranges, $page) = @_; my $currentnumber = 0; my $rangestart = 0; ####### Question: is rangeend ever used? my $rangeend = 0; my $currentkeyword = ''; my $invalidrange = 0; my $totalscore = 0; my $pageinside = 0; my $currentrange = '';
my $evaluaterange = sub { # evaluate the current range: determine its score and whether the # current page is member of it. if ($invalidrange) { # Range is invalid, issue a warning print $logh " Invalid range: $currentrange\n"; } else { # We have a valid range, evaluate it if ($currentkeyword) { if ($currentkeyword =~ /^even/i) { # All even-numbered pages $totalscore += 50000; $pageinside = 1 if (($page % 2) == 0); } elsif ($currentkeyword =~ /^odd/i) { # All odd-numbered pages $totalscore += 50000; $pageinside = 1 if (($page % 2) == 1); } else { # Invalid range print $logh " Invalid range: $currentrange\n"; } } elsif (($rangestart == 0) && ($currentnumber > 0)) { # Page range is a single page $totalscore += 1; $pageinside = 1 if ($page == $currentnumber); } elsif (($rangestart > 0) && ($currentnumber > 0)) { # Page range is a sequence of pages $totalscore += (abs($currentnumber - $rangestart) + 1); if ($currentnumber < $rangestart) { my $tmp = $currentnumber; $currentnumber = $rangestart; $rangestart = $tmp; } $pageinside = 1 if (($page <= $currentnumber) && ($page >= $rangestart)); } elsif ($rangestart > 0) { # Page range goes to the end of the document $totalscore += 100000; $pageinside = 1 if ($page >= $rangestart); } else { # Invalid range print $logh " Invalid range: $currentrange\n"; } } # Range is evaluated, remove all recordings of the current range $rangestart = 0; $currentnumber = 0; $currentkeyword = ''; $invalidrange = 0; $currentrange = ''; };
for (my $i = 0; $i < length($ranges); $i ++) { my $c = substr($ranges, $i, 1); if (!$invalidrange) { if ($c =~ /\d/) { # Digit if ($currentkeyword) { # Add to keyword $currentkeyword .= $c; } else { # Build a page number $currentnumber *= 10; $currentnumber += $c; } } elsif ($c =~ /[a-z_]/i) { # Letter or underscore if (($rangestart > 0) || ($rangeend > 0) || ($currentnumber > 0)) { # Keyword not allowed after a page number or a # page range $invalidrange = 1; } else { # Build a keyword $currentkeyword .= $c; } } elsif ($c eq '-') { # Page range if (($rangestart > 0) || ($currentkeyword)) { # Keyword or two '-' not allowed in page range $invalidrange = 1; } else { # Save start of range, reset page number $rangestart = $currentnumber; if ($rangestart == 0) { $rangestart = 1; } $currentnumber = 0; } } } if ($c eq ',') { # End of a range &$evaluaterange(); } else { # Make a string of the current range, for warnings $currentrange .= $c; } } # End of input string &$evaluaterange(); # Return value if (($page == 0) || ($pageinside)) { return $totalscore; } else { return 0; } }
sub setoptionsforpage {
## Set the options for a given page
# Foomatic data, name of the option set where to apply the options, and # number of the page my ($dat, $optionset, $page) = @_;
my $bestscore = 10000000; my $value; for my $arg (@{$dat->{'args'}}) { $value = ''; for my $key (keys %{$arg}) { next if $key !~ /^pages:(.*)$/; my $pageranges = $1; if (my $score = parsepageranges($pageranges, $page)) { if ($score <= $bestscore) { $bestscore = $score; $value = $arg->{$key}; } } } if ($value) { $arg->{$optionset} = $value; } } }
sub buildcommandline {
## Build a renderer command line, based on the given option set
# Foomatic data and name of the option set to apply my ($dat, $optionset) = @_;
# Construct the proper command line. $dat->{'currentcmd'} = $dat->{'cmd'}; my @prologprepend; my @setupprepend; my @pagesetupprepend; my @cupspagesetupprepend; my @jclprepend; my @jclappend;
# At first search for composite options and determine how they # set their member options for my $arg (@{$dat->{'args'}}) { $arg->{'order'} = 0 if !defined $arg->{'order'}; } for my $arg (sort { $a->{'order'} <=> $b->{'order'} } @{$dat->{'args'}}) {
# Here we are only interested in composite options, skip the others next if $arg->{'style'} ne 'X'; my $name = $arg->{'name'}; # Check whether this composite option is controlled by another # composite option, so nested composite options are possible. my $userval = ($arg->{'fromcomposite'} ? $arg->{'fromcomposite'} : $arg->{$optionset});
# Get the current setting my $v = $arg->{'vals_byname'}{$userval}; my @settings = split(/\s+/s, $v->{'driverval'}); for my $s (@settings) { my ($key, $value); if ($s =~ /^([^=]+)=(.+)$/) { $key = $1; $value = $2; } elsif ($s =~ /^no([^=]+)$/) { $key = $1; $value = 0; } elsif ($s =~ /^([^=]+)$/) { $key = $1; $value = 1; } $a = $dat->{'args_byname'}{$key}; if ($a->{$optionset} eq "From$name") { # We must set this option according to the # composite option $a->{'fromcomposite'} = $value; # Mark the option telling by which composite option # it is controlled $a->{'controlledby'} = $name; } else { $a->{'fromcomposite'} = ""; } } # Remove PostScript code to be inserted after an appearance of the # Composite option in the PostScript code. undef $arg->{'jclsetup'}; undef $arg->{'prolog'}; undef $arg->{'setup'}; undef $arg->{'pagesetup'}; }
for my $arg (sort { $a->{'order'} <=> $b->{'order'} } @{$dat->{'args'}}) { # Composite options have no direct influence on the command # line, skip them here next if $arg->{'style'} eq 'X';
my $name = $arg->{'name'}; my $spot = $arg->{'spot'}; my $cmd = $arg->{'proto'}; my $cmdf = $arg->{'protof'}; my $type = ($arg->{'type'} || ""); my $section = $arg->{'section'}; my $userval = ($arg->{'fromcomposite'} ? $arg->{'fromcomposite'} : $arg->{$optionset}); my $cmdvar = "";
# If we have both "PageSize" and "PageRegion" options, we kept # them all the time in sync, so we don't need to insert the settings # of both options. So skip "PageRegion". next if (($name eq "PageRegion") && (defined($dat->{'args_byname'}{'PageSize'})) && (defined($dat->{'args_byname'}{'PageRegion'})));
# Build the command line snippet/PostScript/JCL code for the current # option if ($type eq 'bool') {
# If true, stick the proto into the command line, if false # and we have a proto for false, stick that in if (defined($userval) && $userval == 1) { $cmdvar = $cmd; } elsif ($cmdf) { $userval = 0; $cmdvar = $cmdf; }
} 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'}; # We have already range-checked, correct only # floating point inaccuricies here if ($userval < $min) { $userval = $min; } if ($userval > $max) { $userval = $max; } my $sprintfcmd = $cmd; $sprintfcmd =~ s/\%(?!s)/\%\%/g; $cmdvar = sprintf($sprintfcmd, ($type eq 'int' ? sprintf("%d", $userval) : sprintf("%f", $userval))); } else { $userval = 'None'; }
} elsif ($type eq 'enum') {
# If defined, stick the selected value into the proto and # thence into the commandline if (defined($userval)) { # CUPS assumes that options with the choices "Yes", "No", # "On", "Off", "True", or "False" are boolean options and # maps "-o Option=On" to "-o Option" and "-o Option=Off" # to "-o noOption", which foomatic-rip maps to "0" and "1". # So when "0" or "1" is unavailable in the option, we try # "Yes", "No", "On", "Off", "True", and "False". my $val; my $found = 0; if ($val=valbyname($arg,$userval)) { $found = 1; } elsif ($userval =~ /^Custom\.[\d\.]+x[\d\.]+[A-Za-z]*$/) { # Custom paper size $val = valbyname($arg,"Custom"); $found = 1; } elsif ($userval eq '0') { foreach (qw(No Off False None)) { if ($val=valbyname($arg,$_)) { $userval = $_; $arg->{$optionset} = $userval; $found = 1; last; } } } elsif ($userval eq '1') { foreach (qw(Yes On True)) { if ($val=valbyname($arg,$_)) { $userval = $_; $arg->{$optionset} = $userval; $found = 1; last; } } } elsif ($userval eq 'LongEdge') { # Handle different names for the choices of the # "Duplex" option foreach (qw(LongEdge DuplexNoTumble)) { if ($val=valbyname($arg,$_)) { $userval = $_; $arg->{$optionset} = $userval; $found = 1; last; } } } elsif ($userval eq 'ShortEdge') { foreach (qw(ShortEdge DuplexTumble)) { if ($val=valbyname($arg,$_)) { $userval = $_; $arg->{$optionset} = $userval; $found = 1; last; } } } if ($found) { my $sprintfcmd = $cmd; $sprintfcmd =~ s/\%(?!s)/\%\%/g; $cmdvar = sprintf($sprintfcmd, (defined($val->{'driverval'}) ? $val->{'driverval'} : $val->{'value'})); # Custom paper size if ($userval =~ /^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$/) { my $width = $1; my $height = $2; my $unit = $3; # convert width and height to PostScript points if (lc($unit) eq "in") { $width *= 72.0; $height *= 72.0; } elsif (lc($unit) eq "cm") { $width *= (72.0/2.54); $height *= (72.0/2.54); } elsif (lc($unit) eq "mm") { $width *= (72.0/25.4); $height *= (72.0/25.4); } # Round width and height $width =~ s/\.[0-4].*$// or $width =~ s/\.[5-9].*$// and $width += 1; $height =~ s/\.[0-4].*$// or $height =~ s/\.[5-9].*$// and $height += 1; # Insert width and height into the prototype if ($cmdvar =~ /^\s*pop\W/s) { # Custom page size for PostScript printers $cmdvar = "$width $height 0 0 0\n$cmdvar"; } else { # Custom page size for Foomatic/Gimp-Print $cmdvar =~ s/\%0/$width/ or $cmdvar =~ s/(\W)0(\W)/$1$width$2/ or $cmdvar =~ s/^0(\W)/$width$1/m or $cmdvar =~ s/(\W)0$/$1$width/m or $cmdvar =~ s/^0$/$width/m; $cmdvar =~ s/\%1/$height/ or $cmdvar =~ s/(\W)0(\W)/$1$height$2/ or $cmdvar =~ s/^0(\W)/$height$1/m or $cmdvar =~ s/(\W)0$/$1$height/m or $cmdvar =~ s/^0$/$height/m; } } } else { # User gave unknown value? $userval = 'None'; print $logh "Value $userval for $name is not a valid choice.\n"; } } else { $userval = 'None'; }
} elsif (($type eq 'string') || ($type eq 'password')) { # Stick the entered value into the proto and # thence into the commandline if (defined($userval)) { my $val; if ($val=valbyname($arg,$userval)) { $userval = $val->{'value'}; $cmdvar = (defined($val->{'driverval'}) ? $val->{'driverval'} : $val->{'value'}); } else { my $sprintfcmd = $cmd; $sprintfcmd =~ s/\%(?!s)/\%\%/g; $cmdvar = sprintf($sprintfcmd, $userval); } } else { $userval = 'None'; }
} else { # Ignore unknown option types silently } # Insert the built snippet at the correct place if ($arg->{'style'} eq 'G') { # Place this Postscript command onto the prepend queue # for the appropriate section. if ($cmdvar) { my $open = "[{\n%%BeginFeature: *$name $userval\n"; my $close = "\n%%EndFeature\n} stopped cleartomark\n"; if ($section eq "Prolog") { push (@prologprepend, "$open$cmdvar$close"); my $a = $arg; while ($a->{'controlledby'}) { # Collect option PostScript code to be inserted when # the composite option which controls this option # is found in the PostScript code $a = $dat->{'args_byname'}{$a->{'controlledby'}}; $a->{'prolog'} .= "$cmdvar\n"; } } elsif ($section eq "AnySetup") { if ($optionset ne 'currentpage') { push (@setupprepend, "$open$cmdvar$close"); } elsif ($arg->{'header'} ne $userval) { push (@pagesetupprepend, "$open$cmdvar$close"); push (@cupspagesetupprepend, "$open$cmdvar$close"); } my $a = $arg; while ($a->{'controlledby'}) { # Collect option PostScript code to be inserted when # the composite option which controls this option # is found in the PostScript code $a = $dat->{'args_byname'}{$a->{'controlledby'}}; $a->{'setup'} .= "$cmdvar\n"; $a->{'pagesetup'} .= "$cmdvar\n"; } } elsif ($section eq "DocumentSetup") { push (@setupprepend, "$open$cmdvar$close"); my $a = $arg; while ($a->{'controlledby'}) { # Collect option PostScript code to be inserted when # the composite option which controls this option # is found in the PostScript code $a = $dat->{'args_byname'}{$a->{'controlledby'}}; $a->{'setup'} .= "$cmdvar\n"; } } elsif ($section eq "PageSetup") { push (@pagesetupprepend, "$open$cmdvar$close"); my $a = $arg; while ($a->{'controlledby'}) { # Collect option PostScript code to be inserted when # the composite option which controls this option # is found in the PostScript code $a = $dat->{'args_byname'}{$a->{'controlledby'}}; $a->{'pagesetup'} .= "$cmdvar\n"; } } elsif ($section eq "JCLSetup") { # PJL/JCL argument $dat->{'jcl'} = 1; push (@jclprepend, unhexify($cmdvar)); my $a = $arg; while ($a->{'controlledby'}) { # Collect option PostScript code to be inserted when # the composite option which controls this option # is found in the PostScript code $a = $dat->{'args_byname'}{$a->{'controlledby'}}; $a->{'jclsetup'} .= "$cmdvar\n"; } } else { push (@setupprepend, "$open$cmdvar$close"); my $a = $arg; while ($a->{'controlledby'}) { # Collect option PostScript code to be inserted when # the composite option which controls this option # is found in the PostScript code $a = $dat->{'args_byname'}{$a->{'controlledby'}}; $a->{'setup'} .= "$cmdvar\n"; } } } # Do we have an option which is set to "Controlled by # '<Composite>'"? Then make PostScript code available # for substitution of "%% FoomaticRIPOptionSetting: ..." if ($arg->{'fromcomposite'}) { $arg->{'compositesubst'} = "$cmdvar\n"; } } elsif ($arg->{'style'} eq 'J') { # JCL argument $dat->{'jcl'} = 1; # put JCL commands onto JCL stack... push (@jclprepend, "$jclprefix$cmdvar\n") if $cmdvar; } elsif ($arg->{'style'} eq 'C') { # command-line argument
# Insert the processed argument in the commandline # just before every occurance of the spot marker. $dat->{'currentcmd'} =~ s!\%$spot!$cmdvar\%$spot!g; } # Insert option into command line of CUPS raster driver if ($dat->{'currentcmd'} =~ m!\%Y!) { next if !defined($userval) or $userval eq ""; $dat->{'currentcmd'} =~ s!\%Y!$name=$userval \%Y!g; } # Remove the marks telling that this option is currently controlled # by a composite option (setting "From<composite>") undef $arg->{'fromcomposite'}; undef $arg->{'controlledby'}; }
### 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 W X Y Z/; for my $spot (@letters) { # Remove the letter markers from the commandline $dat->{'currentcmd'} =~ s!\%$spot!!g; }
## J type finishing # Compute the proper stuff to say around the job
if ((defined($dat->{'jcl'})) && (!$jobhasjcl)) {
# Stick beginning of job cruft on the front of the jcl stuff... unshift (@jclprepend, $jclbegin);
# Command to switch to the interpreter push (@jclprepend, $jcltointerpreter); # Arrange for JCL RESET command at end of job push (@jclappend, $jclend);
# Put the JCL stuff into the data structure @{$dat->{'jclprepend'}} = @jclprepend; @{$dat->{'jclappend'}} = @jclappend; }
## G type finishing # Save PostScript options @{$dat->{'prologprepend'}} = @prologprepend; @{$dat->{'setupprepend'}} = @setupprepend; @{$dat->{'pagesetupprepend'}} = @pagesetupprepend; @{$dat->{'cupspagesetupprepend'}} = @cupspagesetupprepend; }
sub buildpdqdriver {
# Build a PDQ driver description file to use the given PPD file # together with foomatic-rip with the PDQ printing system
# Foomatic data and name of the option set for the default settings my ($dat, $optionset) = @_;
# Construct structure with driver information my @pdqdriver = ();
# Construct option list my @driveropts = ();
# Do we have a "Custom" setting for the page size? # Then we have to insert the following into the "filter_exec" script. my @setcustompagesize = ();
# Fata for a custom page size, to allow a custom size as default my $pagewidth = 612; my $pageheight = 792; my $pageunit = "pt";
## First, compute the various option/value clauses for my $arg (@{$dat->{'args'}}) {
if ($arg->{'type'} eq "enum") { # Option with only one choice, omit it, foomatic-rip will set # this choice anyway. next if ($#{$arg->{'vals'}} < 1);
my $nam = $arg->{'name'};
# Omit "PageRegion" option, it does the same as "PageSize". next if $nam eq "PageRegion";
my $com = $arg->{'comment'};
# Assure that the comment is not empty if (!$com) { $com = $nam; }
my $def = $arg->{$optionset}; $arg->{'varname'} = "$nam"; $arg->{'varname'} =~ s![\-\/\.]!\_!g; my $varn = $arg->{'varname'};
# 1, if setting "PageSize=Custom" was found # Then we must add options for page width and height my $custompagesize = 0;
# If the default is a custom size we have to set also # defaults for the width, height, and units of the page if (($nam eq "PageSize") && ($def =~ /^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$/)) { $def = "Custom"; $pagewidth = $1; $pageheight = $2; $pageunit = $3; }
# No quotes, thank you. $com =~ s!\"!\\\"!g; push(@driveropts, " option {\n", " var = \"$varn\"\n", " desc = \"$com\"\n"); # get enumeration values for each enum arg my ($ev, @vals, @valstmp); for $ev (@{$arg->{'vals'}}) { my $choiceshortname = $ev->{'value'}; my $choicename = "${nam}_${choiceshortname}"; my $val = " -o ${nam}=${choiceshortname}"; my $com = $ev->{'comment'};
# Assure that the comment is not empty if (!$com) { $com = $choiceshortname; }
# stick another choice on driveropts push(@valstmp, " choice \"$choicename\" {\n", " desc = \"$com\"\n", " value = \"$val\"\n", " }\n"); if (($nam eq "PageSize") && ($choiceshortname eq "Custom")) { $custompagesize = 1; if ($#setcustompagesize < 0) { push(@setcustompagesize, " # Custom page size settings\n", " # We aren't really checking for " . "legal vals.\n", " if [ \"x\${$varn}\" == 'x$val' ]; " . "then\n", " $varn=\"\${$varn}.\${PageWidth}" . "x\${PageHeight}\${PageSizeUnit}\"\n", " fi\n\n"); } } }
push(@driveropts, " default_choice \"" . $nam . "_" . $def . "\"\n", @valstmp, " }\n\n");
if ($custompagesize) { # Add options to set the custom page size push(@driveropts, " argument {\n", " var = \"PageWidth\"\n", " desc = \"Page Width (for \\\"Custom\\\" page " . "size)\"\n", " def_value \"$pagewidth\"\n", " help = \"Minimum value: 0, Maximum value: " . "100000\"\n", " }\n\n", " argument {\n", " var = \"PageHeight\"\n", " desc = \"Page Height (for \\\"Custom\\\" page " . "size)\"\n", " def_value \"$pageheight\"\n", " help = \"Minimum value: 0, Maximum value: " . "100000\"\n", " }\n\n", " option {\n", " var = \"PageSizeUnit\"\n", " desc = \"Unit (for \\\"Custom\\\" page size)\"\n", " default_choice \"PageSizeUnit_$pageunit\"\n", " choice \"PageSizeUnit_pt\" {\n", " desc = \"Points (1/72 inch)\"\n", " value = \"pt\"\n", " }\n", " choice \"PageSizeUnit_in\" {\n", " desc = \"Inches\"\n", " value = \"in\"\n", " }\n", " choice \"PageSizeUnit_cm\" {\n", " desc = \"cm\"\n", " value = \"cm\"\n", " }\n", " choice \"PageSizeUnit_mm\" {\n", " desc = \"mm\"\n", " value = \"mm\"\n", " }\n", " }\n\n"); } } elsif ($arg->{'type'} eq 'int' or $arg->{'type'} eq 'float') { my $nam = $arg->{'name'}; my $com = $arg->{'comment'};
# Assure that the comment is not empty if (!$com) { $com = $nam; }
my $def = $arg->{$optionset}; my $max = $arg->{'max'}; my $min = $arg->{'min'}; $arg->{'varname'} = "$nam"; $arg->{'varname'} =~ s![\-\/\.]!\_!g; my $varn = $arg->{'varname'}; my $legal = $arg->{'legal'} = "Minimum value: $min, Maximum value: $max"; my $defstr = ""; if ($def) { $defstr = sprintf(" def_value \"%s\"\n", $def); } push(@driveropts, " argument {\n", " var = \"$varn\"\n", " desc = \"$com\"\n", $defstr, " help = \"$legal\"\n", " }\n\n"); } elsif ($arg->{'type'} eq 'bool') { my $nam = $arg->{'name'}; my $com = $arg->{'comment'};
# Assure that the comment is not empty if (!$com) { $com = $nam; }
my $tcom = $arg->{'comment_true'}; my $fcom = $arg->{'comment_false'}; my $def = $arg->{$optionset}; $arg->{'legal'} = "Value is a boolean flag"; $arg->{'varname'} = "$nam"; $arg->{'varname'} =~ s![\-\/\.]!\_!g; my $varn = $arg->{'varname'}; my $defstr = ""; if ($def) { $defstr = sprintf(" default_choice \"%s\"\n", $def ? "$nam" : "no$nam"); } else { $defstr = sprintf(" default_choice \"%s\"\n", "no$nam"); } push(@driveropts, " option {\n", " var = \"$varn\"\n", " desc = \"$com\"\n", $defstr, " choice \"$nam\" {\n", " desc = \"$tcom\"\n", " value = \" -o $nam=True\"\n", " }\n", " choice \"no$nam\" {\n", " desc = \"$fcom\"\n", " value = \" -o $nam=False\"\n", " }\n", " }\n\n");
} elsif ($arg->{'type'} eq 'string' or $arg->{'type'} eq 'password') { my $nam = $arg->{'name'}; my $com = $arg->{'comment'};
# Assure that the comment is not empty if (!$com) { $com = $nam; }
my $def = $arg->{$optionset}; my $maxlength = $arg->{'maxlength'}; my $proto = $arg->{'proto'}; $arg->{'varname'} = "$nam"; $arg->{'varname'} =~ s![\-\/\.]!\_!g; my $varn = $arg->{'varname'};
my $legal; if (defined($maxlength)) { $legal .= "Maximum length: $maxlength characters, "; } $legal .= "Examples/special settings: "; for (@{$arg->{'vals'}}) { my ($value, $comment, $driverval) = ($_->{'value'}, $_->{'comment'}, $_->{'driverval'}); # Retrieve the original string from the prototype # and the driverval my $string; if ($proto) { my $s = index($proto, '%s'); my $l = length($driverval) - length($proto) + 2; if (($s < 0) || ($l < 0)) { $string = $driverval; } else { $string = substr($driverval, $s, $l); } } else { $string = $driverval; } if ($value ne $string) { $legal .= "${value}: \\\"$string\\\""; } else { $legal .= "\\\"$value\\\""; } if ($comment && ($value ne $comment) && ($string ne $comment) && (($value ne 'None') || ($comment ne '(None)'))) { $legal .= " ($comment)"; } $legal .= "; "; } $legal =~ s/; $//;
$arg->{'legal'} = $legal; my $defstr = ""; if ($def) { $defstr = sprintf(" def_value \"%s\"\n", $def); } push(@driveropts, " argument {\n", " var = \"$varn\"\n", " desc = \"$com\"\n", $defstr, " help = \"$legal\"\n", " }\n\n"); } }
## Define the "docs" option to print the driver documentation page
push(@driveropts, " option {\n", " var = \"DRIVERDOCS\"\n", " desc = \"Print driver usage information\"\n", " default_choice \"nodocs\"\n", " choice \"docs\" {\n", " desc = \"Yes\"\n", " value = \" -o docs\"\n", " }\n", " choice \"nodocs\" {\n", " desc = \"No\"\n", " value = \"\"\n", " }\n", " }\n\n");
## Build the "foomatic-rip" command line my $commandline = "foomatic-rip --pdq"; if ($printer) { $commandline .= " -P $printer"; } else { # Make sure that the PPD file is entered with an absolute path if ($ppdfile !~ m!^/!) { my $pwd = cwd; $ppdfile = "$pwd/$ppdfile"; } $commandline .= " --ppd=$ppdfile"; } for my $arg (@{$dat->{'args'}}) { if ($arg->{'varname'}) { $commandline .= "\${$arg->{'varname'}}"; } } $commandline .= "\${DRIVERDOCS} \$INPUT > \$OUTPUT";
## Now we generate code to build the command line snippets for the ## numerical options
my @psfilter; for my $arg (@{$dat->{'args'}}) { # Only numerical and string options need to be treated here next if (($arg->{'type'} ne 'int') && ($arg->{'type'} ne 'float') && ($arg->{'type'} ne 'string') && ($arg->{'type'} ne 'password'));
my $comment = $arg->{'comment'}; my $name = $arg->{'name'}; my $varname = $arg->{'varname'}; # If the option's variable is non-null, put in the # argument. Otherwise this option is the empty # string. Error checking? push(@psfilter, " # $comment\n", (($arg->{'type'} eq 'int') || ($arg->{'type'} eq 'float') ? (" # We aren't really checking for max/min,\n", " # this is done by foomatic-rip\n", " if [ \"x\${$varname}\" != 'x' ]; then\n ") : ""), #" $varname=`echo \${$varname} | perl -p -e \"s/'/'\\\\\\\\\\\\\\\\''/g\"`\n", " $varname=\" -o $name='\${$varname}'\"\n", (($arg->{'type'} eq 'int') || ($arg->{'type'} eq 'float') ? " fi\n" : ""), "\n"); }
# Command execution
push(@psfilter, " if ! test -e \$INPUT.ok; then\n", " sh -c \"$commandline\"\n", " if ! test -e \$OUTPUT; then \n", " echo 'Error running foomatic-rip; no output!'\n", " exit 1\n", " fi\n", " else\n", " ln -s \$INPUT \$OUTPUT\n", " fi\n\n"); my $version = time(); my $name = "$model-$version"; $name =~ s/\W/\-/g; $name =~ s/\-+/\-/g; my $pname = $model; push (@pdqdriver, "driver \"$name\" {\n\n", " # This PDQ driver declaration file was generated " . "automatically by\n", " # foomatic-rip from information in the file $ppdfile.\n", " # It allows printing with PDQ on the $pname.\n", "\n", " requires \"foomatic-rip\"\n\n", @driveropts, " language_driver all {\n", " # We accept all file types and pass them to foomatic-rip\n", " # (invoked in \"filter_exec {}\" section) without\n", " # pre-filtering\n", " filetype_regx \"\"\n", " convert_exec {\n", " ln -s \$INPUT \$OUTPUT\n", " }\n", " }\n\n", " filter_exec {\n", @setcustompagesize, @psfilter, " }\n", "}\n"); return @pdqdriver;
}
# Emacs tabulator/indentation
### Local Variables: ### tab-width: 8 ### perl-indent-level: 4 ### End:
|