#!/bin/sh #****************************************************************************** # File: @(#)$Id: if-pcl3,v 2.6 2001/07/07 10:05:34 Martin Rel $ # Contents: Example of a pcl3-based input filter for a Berkeley spooler (lpr) # driving a PCL-3 printer # Call: if-pcl3 # Author: Martin Lottermoser, Greifswaldstrasse 28, 38124 Braunschweig, # Germany. E-mail: Martin.Lottermoser@t-online.de. # #****************************************************************************** # * # Copyright (C) 1998, 2000, 2001 by Martin Lottermoser * # All rights reserved * # * #****************************************************************************** # # This input filter has the following interesting because unusual features: # - If an error occurs, the user is notified by mail. # - The filter is configurable based on the spool directory and permits in # particular # - the configuration of a "transparent" queue and # - the inclusion of a file with PostScript configuration commands. # - The filter does pac(8) accounting if this is requested in the printcap file. # # This is not the best of all possible filters. However, all the others I've # seen so far -- excepting those I've modified myself :-) -- lack several # important though elementary features. Much of this is admittedly due to the # inflexibility of the Berkeley spool system (compared to the AT&T spooler) # but, speaking entirely subjectively, I've also often had the impression that # the filters' authors are not as familiar with PCL, ghostscript or the # Berkeley spooler as one should be when trying to write universally useful # software. You should form your own opinion on this point and, if necessary, # ruthlessly modify whatever filter you determine to use until it satisfies # your requirements. # #****************************************************************************** # # Installation # ============ # # printcap entry # -------------- # Read the man page printcap(5). You should set at least 'if' (absolute path # name of this filter), 'lp' (printer device), 'sd' (absolute path name of the # spool directory, choose it to be queue-specific), and 'sh' (suppress header, # should be true). Here's an example for a queue named "draft": # # draft:if=/var/spool/lpd/if-pcl3:lp=/dev/lp0:sd=/var/spool/lpd/draft:sh: # # Other entries which are worth considering are 'af' (this must usually be a # file named 'acct' in the spool directory, see pac(8)), 'lf', and 'mx'. # # You must create the spool directory if it does not exist. Usually, it should # be owned by "daemon", group "lp", and have the file mode 770. # # # Filter configuration # -------------------- # This filter looks for a file if-pcl3.cfg in the spool directory. If it exists # and is readable it is sourced as a shell script. The file is intended for # setting shell variables used by this filter, redefining the two accounting # routines, and for modifying the environment. # # Here follows a description of shell variables used by this filter. Note that # you can also add other command line arguments to the gs call by setting # GS_OPTIONS in the environment. # # - GS: This is normally the path name of the ghostscript executable. But if # the configuration file sets it to the empty string, this filter will not # call ghostscript but simply 'cat'. This is intended for setting up a # "transparent" queue which passes files through to the printer without # modifications. # - INIT_TRANS: This must be empty or a parameter-less format string for the # 'printf' command. If $GS is empty, this string will be sent before the file # to be printed. Use it for PCL commands under the assumption that the user # wishes to print a text file. In particular, this is the place to set the # media size to ISO A4 if that is your default paper size. My recommendation # for A4 and ISO 8859-1 is: # INIT_TRANS='\033&l26A\033(0N\033&a10L\033(s12H\033&l6.5D\033&l5E\033&l66F' # For US Letter size, replace the "26" near the beginning by "2". # Setting INIT_TRANS should have no influence on PCL files submitted to such # a queue because all PCL-generating programs should start their output with # the "Printer Reset" command (pcl3 does this) which resets all parameters to # their defaults. # - RESOLUTION: This must be empty or an acceptable argument for ghostscript's # option '-r'. In the first case, pcl3's default resolution will be used # unless the document overrides it. Remember that "-r" sets also # "-dFIXEDRESOLUTION". # - SUBDEVICE, COLOURMODEL, QUALITY: These must contain values for pcl3's # options "Subdevice", "ColourModel" and "PrintQuality". # - PSCONFIGFILE: If this variable contains the name of an existing and # readable file it will be included in the call to ghostscript. This file is # intended to contain PostScript configuration commands, for example setting # the 'PageOffset' array, defining transfer functions, setting the halftone # screen or configuring 'InputAttributes'. # - PAGECOUNTFILE: If pcl3 has been compiled without EPRN_NO_PAGECOUNTFILE # defined, it has the ability to update a page count file with the number of # pages printed. If this variable is non-empty (and that is the default), # page counting will be enabled using the file specified in the variable and # the count will be accumulated across jobs. # - USE_INTERMEDIATE_FILE: When printing via ghostscript, this variable should # be "yes" or "no". It controls whether ghostscript's output is sent directly # to a printer or first to a file. Using "yes" needs substantial temporary # disk space but prevents wrong output in some situations and is therefore # the default. # # The defaults for these variables can be found below before the point where # the configuration file is sourced. # # The two accounting functions, 'acct_start' and 'acct_stop', are always called, # respectively, before and after the print command (cat or gs). 'acct_start' # should return a string on standard output which will be passed as an argument # to the later call to 'acct_stop'. The latter function should then return on # standard output a string representation of an integer or a floating point # number. If accounting has been enabled by an 'af' entry in the printcap file # and the print command was successful, this value will be recorded together # with the job owner's user name in the accounting file for later evaluation by # 'pac'. The usual interpretation is that this value represents pages printed # or feet of paper consumed. # # The default implementation of the accounting functions counts the number of # jobs submitted for a transparent queue and the number of pages printed for a # pcl3-based one. If PAGECOUNTFILE is empty or page counting manifestly does # not work, the filter counts jobs also in the latter case. # # # # Restrictions # ============ # A spool queue based on this filter processes only PostScript files correctly # except when configured as a transparent queue in which case only text or PCL # files should be sent. This is not a so-called "intelligent filter" which # adapts its behaviour to the type of file received! # #****************************************************************************** name=`basename "$0"` # Reset umask if 0. Otherwise an ordinary user could modify the accounting # information if this is the first time accounting is used. This does not work # on systems not supporting the old octal notation but is harmless there. expr "x`umask`" : 'x00*$' > /dev/null && umask 002 # Berkeley input filters are called in the spool directory. This is used to # locate configuration files and it usually identifies the spool queue. spool_directory=`pwd` #****************************************************************************** # In order to notify the user of errors occurring in the backend I'm copying # standard error into a temporary file and mail it to the user if it is # non-empty when the filter terminates. errlog="${TMPDIR:-/tmp}/$$-1.tmp" rm -f "$errlog" notify_user=root # This will be overwritten after we've checked the notify_host=`uname -n` # arguments in the call. finish() { if [ -s "$errlog" ]; then ${MAILX:-mailx} -s "$name: Error while printing" \ "$notify_user"@"$notify_host" << --- The following error occurred while running $0 in $spool_directory: `cat $errlog` --- cat "$errlog" >&3 # for the log file rm -f "$errlog" exit 2 fi rm -f "$errlog" exit 0 } # Set up a trap for finish() on exit and SIGINT trap finish 0 2 # Copy standard error into a file. We duplicate file descriptor 2 as 3 because # stderr can be appended to a log file by the 'lf' field in printcap and we # should like to be able to have both, a cumulative log file for the # administrator and a job-specific mail message to the user. exec 3>&2 exec 2> "$errlog" #****************************************************************************** # Process options. Most are irrelevant for gs and this filter. print_controls=no # print control characters instead of interpreting them host= # host name of job owner user= # user name of job owner length=0 # page length in lines indentation=0 # amount of indentation in characters width=0 # page width in characters while getopts ch:i:l:n:w: option; do case "$option" in c) print_controls=yes;; h) host="$OPTARG";; i) indentation="$OPTARG";; l) length="$OPTARG";; n) user="$OPTARG";; w) width="$OPTARG";; *) echo "? $name"": Illegal option specification. The call was:" >&2 printf ' %s\n' "$*" >&2 exit 2;; esac done test 1 = "$OPTIND" || shift `expr $OPTIND - 1` # Deal with non-option arguments if [ $# -gt 1 ]; then echo "? $name"": More than one non-option argument in the call:" >&2 printf ' %s\n' "$*" >&2 exit 2 fi if [ $# -gt 0 ]; then accounting_file="$1" else accounting_file= fi # Starting here, any errors occurring are sent to the job's owner if [ '' != "$user" ]; then notify_user="$user" test '' = "$host" || notify_host="$host" fi #****************************************************************************** # Set up default values. # These are the parameters which can be redefined in the configuration file. # You might also have to use GS_OPTIONS, for example for the media type. GS=gs SUBDEVICE=unspec COLOURMODEL=Gray # Americans note the "U"! Britons note the "a"! :-) RESOLUTION= QUALITY=normal PSCONFIGFILE=if-pcl3.ps PAGECOUNTFILE=gs-pages.count INIT_TRANS= USE_INTERMEDIATE_FILE=yes # Default accounting: count the number of files printed or, if supported, the # number of pages. acct_start() { test '' = "$PAGECOUNTFILE" -o ! -f "$PAGECOUNTFILE" || cat "$PAGECOUNTFILE" return } acct_stop() { pages_before="$1" test '' != "$pages_before" || pages_before=0 if [ '' != "$PAGECOUNTFILE" -a -f "$PAGECOUNTFILE" ]; then pages_after=`cat "$PAGECOUNTFILE"` pages=`expr $pages_after - $pages_before` if [ '' = "$pages" -o 0 -ge "$pages" ]; then test 0 -ne "$rc" || echo "? $name"": Wrong value for number of pages" \ "printed: \`$pages'." >&2 pages=1 # count files instead fi else pages=1 # count files fi echo $pages return } #****************************************************************************** # Read the configuration file for the filter if present in the spool directory true cfg=if-pcl3.cfg test ! -f $cfg -o ! -r $cfg || . ./$cfg test $? -eq 0 || exit 2 # RESOLUTION and PSCONFIGFILE must not contain field separators if expr "x$RESOLUTION$PSCONFIGFILE" : "x.*[$IFS]" > /dev/null; then printf '? %s: Shell field separator(s) in RESOLUTION (%s)\n' \ "$name" "$RESOLUTION" >&2 printf ' or PSCONFIGFILE (%s).\n' "$PSCONFIGFILE" >&2 exit 2 fi #****************************************************************************** # Initialize accounting LC_NUMERIC=C; export LC_NUMERIC acct_saved=`acct_start` # stdin is the input file, stdout the printer. rc=0 if [ '' = "$GS" ]; then # Transparent queue printf '\033E\033&k2G' # PCL: Printer Reset, Line Termination (LF -> CR+LF) test '' = "$INIT_TRANS" || printf "$INIT_TRANS" cat rc=$? else # An empty specifications for RESOLUTION means "use the default". test '' = "$RESOLUTION" || RESOLUTION="-r$RESOLUTION" # Remove the PostScript configuration file from the command line if it does # not exist or is not readable test '' != "$PSCONFIGFILE" -a -f "$PSCONFIGFILE" -a -r "$PSCONFIGFILE" || \ PSCONFIGFILE= # If PAGECOUNTFILE is non-empty, insert an option with it in the call. pcf_option= test '' = "$PAGECOUNTFILE" || pcf_option=-sPageCountFile="$PAGECOUNTFILE" # We have to pass the resulting PCL file to standard output. Unfortunately, # if one does this directly from ghostscript (via "-sOutputFile=-"), the # output can contain unwanted data: error messages and data written to what # is standard output for PostScript. (I consider this behaviour to be a bug # in gs.) The latter feature is for example used by some MS Windows drivers # for PostScript printers in order to report the current processing state # back to the host. # If we want neither error messages nor statements like "%%[ Page: 1 ]%%" to # be intermixed with what is to be printed, we have to create a file first # and copy it to stdout in a second step. This might require substantial disk # space. # Hence I provide both solutions. Choose the one you like best by setting # USE_INTERMEDIATE_FILE. if [ no = "$USE_INTERMEDIATE_FILE" ]; then # Directly to stdout, hoping that other data will not appear. However, # in order not to loose the error message text, the following command is # used to ensure that this output will be printed properly and in # particular without a "staircase effect" provided the error occurs on the # first page. # This is for example what happens if the input file is not PostScript. printf '\033E\033&k2G\033&s0C' # PCL: Printer Reset, Line Termination (LF -> CR+LF), End-of-Line-Wrap (ON). # Call ghostscript ${GS:-gs} -q -dNOPAUSE -dSAFER -sDEVICE=pcl3 -sSubdevice="$SUBDEVICE" \ -sColourModel="$COLOURMODEL" $RESOLUTION -sPrintQuality="$QUALITY" \ $pcf_option -sOutputFile=- $PSCONFIGFILE - rc=$? test 0 -eq $rc || \ printf '\n? %s: %s returned an exit code of %s.\n' "$name" "$GS" "$rc" >&2 else # Two-step process with an intermediate file tmp="${TMPDIR:-/tmp}/$$-2.tmp" rm -f "$tmp" # Call ghostscript, redirecting stdout to stderr. Note that permissible # output (e.g., from "=") will be redirected to stderr as well, hence # we can't use the size of the resulting file as the sole indication that # an error has occurred. prev_umask=`umask` umask 027 # Ensure privacy. The group should be lp. ${GS:-gs} -q -dNOPAUSE -dSAFER -sDEVICE=pcl3 -sSubdevice="$SUBDEVICE" \ -sColourModel="$COLOURMODEL" $RESOLUTION -sPrintQuality="$QUALITY" \ $pcf_option -sOutputFile="$tmp" $PSCONFIGFILE - >&2 rc=$? test 0 -eq $rc || \ printf '\n? %s: %s returned an exit code of %s.\n' "$name" "$GS" "$rc" >&2 umask "$prev_umask" # If an error occurred, don't bother printing. if [ 0 -eq $rc ]; then cat "$tmp" rc=$? test $rc -eq 0 || printf \ '? %s: Error copying the generated file to stdout.\n' "$name" >&2 fi rm -f "$tmp" fi fi # Reset the printer to its default state. This also ejects any unfinished pages # which is needed if the transparent queue is used for printing ordinary text. # It is superfluous when printing with pcl3 because pcl3 generates this at the # end of its output already, but a second reset is harmless (unless the printer # knows more than one command language and PCL-3+ is not the default) and it # works even if gs crashes. printf '\033E' # PCL: Printer Reset # Terminate accounting pages=`acct_stop "$acct_saved"` # Exit on error test 0 -eq $rc || exit 2 #****************************************************************************** # User-specific accounting on success if [ '' != "$accounting_file" ]; then printf '%7.2f\t%s:%s\n' "$pages" "$host" "$user" >> "$accounting_file" fi # Remove the trap for finish() in case the stderr log is non-empty although # everything worked. trap - 0 2 exit 0