4.9. Print Job Filters

A printer usually understands one or more Print Job Languages. Files sent to this printer must be in one of these languages and have the appropriate job format. The most common Print Job Languages are PostScript and PCL. Text files are PCL with no special PCL control sequences.

In order for a printer to reliably print a job it needs to be reset to a known configuration and then at the end of job having it flush all of the output to the printing device. This is done by sending it start of job and end of job commands. These commands differ from printer to printer and depend on the print job language as well. Some vintage line printers also have a set of proprietary escape sequences that are used to set up margins, form size, and other printing characteristics. Usually a setup string with these escape sequences must be sent to the printer before a file can be printed.

When sending a job to the printer the print spooler will first process the job using a print job filter or filter program. This program reads the job file and then produces output in the format required for the printer.

When a print job is created the files in the print job are assigned a format. This format was meant as a guide to the print spooler and was to be used to select the filter program for the files in the job. The format was a lower case letter; the f is the default format and indicates normal processing and the l format indicates a literal or binary file. Job files that are flagged as having literal or binary format are usually passed directly to the printer or have at the most a minimal amount of processing. See Print Job Formats for more information about formats and their use with filters.

There are two ways to specify filters: the default :filter=... option and the more specific Xf=... option. The X is a lower case letter corresponding to a format. Here is a sample printcap entry with a filter specification:

    lp:sd=/var/spool/lpd/%P
      :filter=/usr/local/lib/filters/ifhp
      :rf=/usr/local/lib/filters/rfilter
All jobs with formats other than r will be processed using the ifhp program while the jobs with the r format will be processed using the rfilter program.

We will set up a very simple filter and use it to demonstrate how filtering is done by the lpd print spooler. First, set up the /tmp/testf file as shown below.

    #!/bin/sh
    # /tmp/testf - test filter for LPRng
    PATH=/bin:/usr/bin; export PATH
    echo TESTF $0 "$@" >&2
    echo TESTF $0 "$@"
    echo ENV
    set
    echo LEADER
    /bin/cat
    echo TRAILER
    exit 0


Let us carefully examine the script line by line. The first couple of lines are boilerplate. You should always set the PATH value in a filter script or use full pathnames for executable programs. This is a good practice as it ensures that only the specified directories will be searched for commands.

The next lines echo the command line arguments to file descriptor 2 (STDERR) and to STDOUT. We will soon see how this information is displayed by the LPRng software. We then use the set command to list the shell variables to STDOUT, print LEADER to STDOUT, copy STDIN to STDOUT, and print TRAILER to STDOUT. We exit with a zero result code.

We can test our script, with the results shown below:

    h4: {163} % chmod 755 /tmp/testf
    h4: {164} % echo hi |/tmp/testf -a1
    TESTF /tmp/testf -a1
    TESTF /tmp/testf -a1
    ENV
    USER=papowell
    HOSTNAME=h4
    ...
    PATH=/bin:/usr/bin
    LEADER
    hi
    TRAILER


Let's now use this filter. Edit the lp printcap entry so it has contents indicated below, use checkpc -f to check the printcap, and then use lpc reread to restart the lpd server.

    lp:sd=/var/spool/lpd/%P
      :force_localhost
      :lp=/tmp/lp
      :filter=/tmp/testf


Execute the following commands to print the /tmp/hi file and observe the results:

    h4: {165} % cp /dev/null /tmp/lp
    h4: {166} % lpr /tmp/hi
    h4: {167} % lpq -llll
    Printer: lp@h4
     Queue: no printable jobs in queue
     Status: lp@h4.private: job 'papowell@h4+26593' printed at 21:37:21.312
     Status: job 'papowell@h4+26593' removed at 21:37:21.323
     Status: subserver pid 26683 starting at 21:39:21.908
     Status: accounting at start at 21:39:21.908
     Status: opening device '/tmp/lp' at 21:39:21.909
     Status: printing job 'papowell@h4+26681' at 21:39:21.909
     Status: no banner at 21:39:21.909
     Status: printing data file 'dfA026681h4.private', size 3, \
        IF filter 'testf' at 21:39:21.909
     Status: IF filter msg - 'TESTF /tmp/testf -Apapowell@h4+26681 \
         -CA -D2000-04 -11-21:39:21.877 -Ff -Hh4.private -J/tmp/hi \
          -Lpapowell -Plp -Qlp -aacct -b3 -d/var/tmp/LPD/lp \
          -edfA026681h4.private -f/tmp/hi -hh4.private -j026681 \
          -kcfA026681h4.private -l66 -npapowell -sstatus \
          -t2000-04-11-21:39:21.000 -w80 -x0 -y0 acct' \
           at 21:39:21.914
     Status: IF filter finished at 21:39:22.070
     Status: printing done 'papowell@h4+26681' at 21:39:22.070
     Status: accounting at end at 21:39:22.070
     Status: finished 'papowell@h4+26681', status 'JSUCC' at 21:39:22.070
     Status: subserver pid 26683 exit status 'JSUCC' at 21:39:22.072
     Status: lp@h4.private: job 'papowell@h4+26681' printed at 21:39:22.072
     Status: job 'papowell@h4+26681' removed at 21:39:22.085
    h4: {168} % more /tmp/lp
    TESTF /tmp/testf -Apapowell@h4+26681 -CA -D2000-04-11-21:39:21.877 \
     -Ff -Hh4.private -J/tmp/hi -Lpapowell -Plp -Qlp -aacct -b3 \
     -d/var/tmp/LPD/lp -edfA026681h4.private -f/tmp/hi -hh4.private \
     -j026681 -kcfA026681h4.private -l66 -npapowell -sstatus \
     -t2000-04-11-21:39:21.000 -w80 -x0 -y0 acct
    ENV
    USER=papowell
    LD_LIBRARY_PATH=/lib:/usr/lib:/usr/5lib:/usr/ucblib
    HOME=/home/papowell
    PRINTCAP_ENTRY=lp
     :force_localhost
     :filter=/tmp/testf
     :lp=/var/tmp/lp
     :sd=/var/tmp/LPD/lp
    
    PS1=$
    OPTIND=1
    PS2=>
    SPOOL_DIR=/var/tmp/LPD/lp
    LOGNAME=papowell
    
    CONTROL=Hh4.private
     Ppapowell
     J/tmp/hi
     CA
     Lpapowell
     Apapowell@h4+15850
     D2000-04-26-18:13:55.505
     Qlp
     N/tmp/hi
     fdfA015850h4.private
     UdfA015850h4.private
    
    PATH=/bin:/usr/bin
    SHELL=/bin/sh
    LOGDIR=/home/papowell
    IFS=
    PRINTER=lp
    LEADER
    test Test
    TRAILER


The cp command clears out the /tmp/lp file we are using as a dummy output device. The lpr command prints the /tmp/hi file and the lpq -llll command shows the status information. The status information now contains the line that the testf script wrote to STDERR. The lpd server captures filter STDERR messages and puts it them in the spool queue status file.

As we see from the lpq status, lpd passes a large number of command line options to our filter. These options and their meanings are discussed in detail in Filter Command Line Options and Environment Variables. We will discuss these in more detail in the next section.

If we look at the /tmp/lp file, we see the command line options and values of the shell variables. For a full discussion of the environment variables passed to a filter see Filter Command Line Options and Environment Variables. The more interesting environment variables include PRINTCAP_ENTRY - the printcap entry for this printer, CONTROL - the control file, and HF - the hold file.

4.9.1. Control Files and Filter Options

When you transfer a print job the lpd print spooler will send the job as two or more files: control file that contains information about the job and data files that contain the information to be printed. Here is sample control file:

    Hh4.private
    Ppapowell
    J/tmp/hi
    CA
    Lpapowell
    Apapowell@h4+15850
    D2000-04-26-18:13:55.505
    Qlp
    N/tmp/hi
    fdfA015850h4.private
    UdfA015850h4.private


Lines starting with upper case letters contain job information such as the user who submitted the job. Lines starting with lower case letters indicate the data file to be printed and the corresponding format. For full details about the exact format of the control file see Job Files.

Table 4-1 shows the correspondence between lines in the control file and lpr command line options. The N values are the names of the files that are printed. The U indicates a data file is in a job and is present to meet RCF1179 and vintage print spooler requirements.

Table 4-1. Filter Options

Control File Filter Option Purpose or Value
  -PPrinter Print queue name - printcap information
H -HHost Host Name
P -nUser User Login Name of job originator
J -JJob name lpr -J option or file name
C -CClass Print class (lpr -C option)
L -LBanner Banner page request
A -AJobid Job Id
D -DDate Date or time information
Q -QQueue Original Print queue job was sent to
N -NFilename Filename
f,l,p,... -Ff Datafile format
U   Datafile (historical)

When a print filter processes these jobs the values in the control file are passed on the command line as options starting with upper case letters:

    /tmp/testf -Apapowell@h4+26681 -CA \
       -D2000-04-11-21:39:21.877 -Ff -Hh4.private ....
Sometimes we want to pass only a small subset of these command line options to a filter or provide them in a specific order in order to be compatible with legacy print filters. LPRng provides several different ways to do this and we will explore how to control command line options.

If the filter entry starts with -$, this suppresses the automatic addition of command line options; we can then add our own options to the command line. Modify the printcap entry to have the following form:

    lp:sd=/var/spool/lpd/%P
      :force_localhost
      :lp=/tmp/lp
      :filter= -$ /tmp/testf '$P' $0P -X$-P ${lp} G\072 or \:


Lets print our /tmp/hi test file and then look at the lpq status:

    h4: {169} % cp /dev/null /tmp/lp
    h4: {170} % lpr /tmp/hi
    h4: {171} % lpq -llll
    Printer: lp@h4
     ....
     Status: IF filter msg - 'TESTF /tmp/testf -Plp -P lp -Xlp \
        -Ylp /tmp/lp G: or :' at 01:20:21.560


The -$ suppresses the adding the default literals to the filter command line. You can pass specific options using $X; if the option has a non-null value then it will be expanded in the following format:

    Option    Value Expansion
    $X        -X<value>
    $'X       -X'<value>'
    $0X       -X '<value>'
    $-X       <value>
    $'-X      '<value>'
    ${X}      control file X option value
    $'{X}     control file X option value (in quotes)
    ${name}   printcap option value if value nonzero length
    $'{name}  printcap option value if value nonzero length in quotes
    \nnn      single printable character
    $*        all options in control file expanded using $X


Command line options can be grouped and passed as a single argument by enclosing them in single or double quotes. You should be aware that LPRng has an extremely primitive way of handling quotes. When the /bin/sh -c parameter is not used, the the command line is broken on spaces and each unit is passed as an individual argument. If the first character after a space is a quote (single or double), the next quote is found, and then entire element is then used as a single parameter. Substitution of $X parameters is then done. As a special case, when you have a $0X, this causes a split and all of the string previous and including the -X flag is passed as a single option and all of the option value and following are passed as another option. If the result of the expansion is a zero length parameter then it is removed from the parameter list. When the /bin/sh -c is used the command line is not broken, and all non-empty option values are enclosed in single quotes.

The ${name} option is used to pass a printcap option value. For example, you can pass the value of the printcap option form as shown below. You can experiment with this by using the /tmp/testf filter and printcap shown below.

    printcap:
    lp:sd=/var/spool/lpd/%P
      :force_localhost
      :lp=/tmp/lp
      :filter=/tmp/testf -F ${form}
      :form=payroll
    
    h4: {172} % cp /dev/null /tmp/lp
    h4: {173} % lpr /tmp/hi
    h4: {174} % lpq -llll
    Printer: lp@h4
     ...
     Status: IF filter msg - 'TESTF /tmp/testf -F payroll' at 09:55:31.276
     ...


If we have a legacy print filter that was originally written for the BSD print spooler, then we may find that it requires a small number of command line options in a very specific order. We can use the :bkf (BSD Kompatible Filter or BacKwards compatible Filter) flag to pass suitable options. Modify the printcap entry to have the following form:

    lp:sd=/var/spool/lpd/%P
      :force_localhost
      :lp=/tmp/lp
      :filter=/tmp/testf
      :bk


Lets print our /tmp/hi test file and then look at the lpq status:

    h4: {175} % cp /dev/null /tmp/lp
    h4: {176} % lpr /tmp/hi
    h4: {177} % lpq -llll
    Printer: lp@h4
     ....
     Status: IF filter msg - 'TESTF /tmp/testf -Plp -w80 -l66 \
        -x0 -y0 -Ff -Lpapowell -J/tmp/hi -CA -n papowell \
        -h h4.private acct' at 08:07:46.583


Finally, there are times when we would like the print filter to be a simple shell command or to chain several programs together in a simple pipeline. While this is possible using a print filter, you can also do this in the filter specification. If your filter specification starts with a parenthesis (() or contains the IO redirection for pipeto (|), input redirection (<), or output redirection (>) then the lpd server will use the :shell configuration option value (default /bin/sh) and execute it using:

    ${shell} -c "( ${if} )"


If this is done, then no command line options are added to the command. However, expansion of $X parameters are still done. Modify the printcap entry to have the following form:

    lp:sd=/var/spool/lpd/%P
      :force_localhost
      :lp=/tmp/lp
      :filter=(echo "PREAMBLE"; /tmp/testf; echo "APPENDIX")


Lets print our /tmp/hi test file and then look at the lpq status:

    h4: {178} % cp /dev/null /tmp/lp
    h4: {179} % lpr /tmp/hi
    h4: {180} % lpq -llll
    Printer: lp@h4
     ....
     Status: printing data file 'dfA018881h4.private', size 3, \
       IF filter 'echo' at 09:22:11.476
     Status: IF filter msg - 'TESTF /tmp/testf' at 09:22:11.510
     Status: IF filter finished at 09:22:11.514


If we examine the /tmp/lp file we find:

    PREAMBLE
    TESTF /tmp/testf
    ENV
    USER=papowell
    LD_LIBRARY_PATH=/lib:/usr/lib:/usr/5lib:/usr/ucblib
    ...
    PRINTER=lp
    LEADER
    hi
    TRAILER
    APPENDIX


As we expected, no options were passed on the command line. If the printcap is modified to have the following contents, then you will see:

    lp:sd=/var/spool/lpd/%P
      :force_localhost
      :lp=/tmp/lp
      :filter=(echo "PREAMBLE"; /tmp/testf $*; echo "APPENDIX")
    
    h4: {181} % lpr /tmp/hi
    h4: {182} % lpq -llll
    Printer: lp@h4
     ....
     Status: IF filter msg - 'TESTF /tmp/testf -Apapowell@h4+18941 \
       -CA -D2000-04-29-09:27:30.700 -Ff -Hh4.private -J/tmp/hi \
       -Lpapowell -Plp -Qlp -aacct -b3 -d/var/tmp/LPD/lp \
       -edfA018941h4.private -f/tmp/hi -hh4.private -j018941 \
       -kcfA018941h4.private -l66 -npapowell -sstatus \
       -t2000-04-29-09:27:30.864 -w80 -x0 -y0 acct' at 09:27:30.879


Using the shell invocation is especially useful when you may have a parameter that has an empty string value, and need to pass this as a command line parameter. Modify the /tmp/testf filter, the printcap, and execute the following commands:

    printcap:
      lp:sd=/var/spool/lpd/%P
        :force_localhost
        :lp=/tmp/lp
        :filter=( /tmp/testf -F '${form}' )
        :form=
    
    #!/bin/sh
    # /tmp/testf - test filter for LPRng
    PATH=/bin:/usr/bin; export PATH
    echo TESTF $0 "$@" >&2
    echo TESTF $0 "$@"
    while test $# -gt 0 ; do
      echo "PARM '$1'";
      shift;
    done
    echo LEADER
    /bin/cat
    echo TRAILER
    exit 0
    
    h4: {183} % cp /dev/null /tmp/lp
    h4: {184} % lpr /tmp/hi
    h4: {185} % lpq -llll
    Printer: lp@h4
     ...
     Status: IF filter msg - 'TESTF /tmp/testf -F' at 09:59:27.365
    
    h4: {186} % more /tmp/lp
    TESTF /tmp/testf -F
    PARM '-F'
    PARM ''
    LEADER
    hi
    TRAILER


As you can see, there are empty parameters passed to the filter. This is due to the combination of the $'{form} and using the :filter=(...) form.

4.9.2. Filter Environment Variables

In this section we will look further at the environment variables passed to the filter. We printed the shell variable values for the filter at the start of the file:

    h4: {187} % cat /tmp/lp
    /tmp/testf -Plp -P lp -Xlp -Ylp /tmp/lp G:' at 01:20:21.560
    ENV
    CONTROL=Hh4.private
     Ppapowell
     J/tmp/hi
     CA
     Lpapowell
     Apapowell@h4+105
     D2000-04-12-15:27:26.662
     Qlp
     N/tmp/hi
     fdfA105h4.private
     UdfA105h4.private
    HF=H=h4.private
     P=papowell
     J=/tmp/hi
     C=A
     L=papowell
     A=papowell@h4+105
     D=2000-04-12-15:27:26.662
     Q=lp
     transfername=cfA105h4.private
     datafiles=N=/tmp/hi,transfername=fdfA105h4.private,
    HOME=/home/daemon
    LD_LIBRARY_PATH=/lib:/usr/lib:/usr/5lib:/usr/ucblib
    LOGDIR=/home/daemon
    LOGNAME=daemon
    OPTIND=1
    PATH=/bin:/usr/bin:/usr/local/bin
    PRINTCAP_ENTRY=lp
     :force_localhost
     :filter=/tmp/testf
     :lp=/tmp/lp
     :sd=/tmp/LPD/lp
    PRINTER=lp
    PS1=$
    PS2=>
    SHELL=/bin/sh
    SPOOL_DIR=/tmp/LPD/lp
    USER=daemon
    
    LEADER
    hi
    TRAILER


The HOME, USER, SHELL, PS1, and PS2 variables are usually set by the shell, and are reflect the information for the UID of the user running the shell.

The PATH and LP_LIBRARY_PATH are set by the lpd server to values specified in the printcap or configuration information. It is recommended that users set these to site specific values if the defaults are not suitable for their sites.

The lpd server sets the PRINTER, PRINTCAP_ENTRY, CONTROL and HF environment variables to the printer name, printcap entry, control file, and hold file for the print job. This information is very useful to filters that must make decisions based on values passed to the print server in the control file and which use parameters in the printcap entry to control their actions.

4.9.3. Using Command Line and Printcap Options In Filters

One of the problems commonly encountered problem in writing a filter is getting the command line values. The UNIX POSIX Standard provides a C Language getopt function that can be used for command line options, and some, but not all shell implementations have a corresponding shell getopt function. Also, many times it would be useful to get the values of the printcap options. These could be used to specify options or operations that are not easily done by passing command lines.



The decode_args_with_sh script which is in the UTILS directory of the LPRng distribution follows these conventions and sets the appropriate shell variables. We have also include a bit of code that will extract the control file control line values and put them into variables as well.

Save the current /tmp/testf filter file in /tmp/testf.old and replace it with the following:

    #!/bin/sh
    # this is an example of how to use /bin/sh and LPRng
    # to get the command line and printcap option values
    # and set shell variables from them
    #  Note that we use a couple of variables
    #PATH=/bin:/usr/bin
    Args=""
    vAr=""
    vAlue=""
    vAls=""
    iI=""
    Tf=""
    Debug=1
    if -n $Debug ; then
        set >/tmp/before
    fi
    Args="$@"
    if -n $Debug ; then
        echo "$@" >>/tmp/before
    fi
    while expr "$1" : '-.*' >/dev/null ; do
      vAr=`expr "$1" : '-\(.\).*'`;
      vAlue=`expr "$1" : '-.\(.*\)`;
      case "$vAr" in
        - ) break;;
        c ) c=1;;
        [a-zA-Z] )
          if test "X$vAlue" = "X" ; then shift; vAlue=$1; fi;
          eval $vAr='$vAlue';
          #setvar $vAr "$vAlue"
          ;;
      esac;
      shift;
    done
    
    # set shell variables to the printcap options
    #  flag   ->  flag=1
    #  flag@  ->  flag=0
    #  option=value ->  option='value'
    #
    setpcvals () {
        while test "$#" -gt 0 ; do
          iI=$1
          if expr "$iI" : " *\:" >/dev/null ; then
            vAr=`expr "$iI" : " *\:\([^=][^=]*\)=.*"`;
            vAlue=`expr "$iI" : " *\:[^=][^=]*=\(.*\)"`;
            if test "X$vAr" = "X" ; then
              vAr=`expr "$iI" : " *:\(.*\)@"`;
              vAlue=0;
            fi
            if test "X$vAr" = "X" ; then
              vAr=`expr "$iI" : " *:\(.*\)"`;
              vAlue=1;
            fi
            if test "X$vAr" != "X" ; then
              eval $vAr='$vAlue';
              #setvar $vAr "$vAlue"
            fi
          else
            vAr=`expr "$iI" : " *\([^|][^|]*\).*"`;
            if test "X$vAr" != "X" ; then
              eval Printer="$vAr"
            fi
          fi;
          shift
        done
    }
    
    # set shell variables to the printcap options
    #  flag   ->  flag=1
    #  flag@  ->  flag=0
    #  option=value ->  option='value'
    #
    setcontrolvals () {
        while test "$#" -gt 0 ; do
          iI=$1
          vAr=`expr "$iI" : " *\([A-Z]\).*"`;
          vAlue=`expr "$iI" : " *[A-Z]\(.*\)"`;
          if test "X$vAr" != "X" ; then
            eval $vAr='$vAlue';
            #setvar $vAr "$vAlue";
          fi;
          shift
        done
    }
    
    Tf=$IFS
    IFS="
    "
    setpcvals $PRINTCAP_ENTRY
    setcontrolvals $CONTROL
    IFS=$Tf
    
    #
    # restore argument list
    set -- $Args
    Args=""
    vAr=""
    vAlue=""
    vAls=""
    iI=""
    Tf=""
    
    if test -n "$Debug" ; then
        set >/tmp/after
        echo "$@" >>/tmp/after
        diff /tmp/before /tmp/after
    fi
    /bin/cat
    exit 0


Lets print our /tmp/hi test file and then look at the results in /tmp/lp:

    h4: {188} % cp /dev/null /tmp/lp
    h4: {189} % lpr /tmp/hi
    h4: {190} % more /tmp/lp
    0a1
    > e=dfA021771h4.private
    2a4,6
    > l=66
    > s=status
    > L=papowell
    10a15,17
    > j=021771
    > C=A
    > J=/tmp/hi
    12a20
    > a=acct
    ...
    33a58
    > Printer=lp
    ...
    hi


As we see from the output, shell variables have the values of our command line and printcap options. It is left as an exercise for the reader to add the necessary export statements to cause these values to be exported to subshells. It is not recommended that a wholesale export of the shell variables be done, but only selected ones.

The paranoid and security minded reader will see some possible security problem with this script. The eval $vAr='$vAlue' command sets the value of the shell variable $vAr to the value $vAlue. The $vAr variable is always taken from either a single letter or is the name of an option in the printcap file. Clearly the printcap file must not be modifiable by users, and should have the same security considerations as any other system configuration file. The values of the $vAlue are taken directly from the control file, whose contents are under the control of the originator of the print job request.

For this reason LPRng takes the rather brutal step of sanitizing the control file. Only alphanumerics or a character in the list @/:()=,+-%_ are used in the control file; all others replaced by the underscore (_) character. In addition, all filters are run as the lpd user specified in the lpd.conf configuration file.

The following is an example of how to extract the same information in Perl:

    #!/usr/bin/perl
    eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
        if $running_under_some_shell;
                # this emulates #! processing on NIH machines.
                # (remove #! line above if indigestible)
    
    use Getopt::Std;
    my(%args,%options);
    # get the arguments
    getopt(
        "a:b:cd:e:f:g:h:i:j:l:m:n:o:p:q:r:s:t:u:v:w:x:y:z:" .
        "A:B:C:D:E:F:G:H:I:J:L:M:N:O:P:Q:R:S:T:U:V:W:X:Y:Z:",
        \%args );
    
    # set :key=value  -> $option{$key}=$value
    # set :key@       -> $option{$key}="0"
    # set :key        -> $option{$key}="1"
     map {
        if( m/^\s*:([^=]+)=(.*)/ ){
         $options{$1}=$2;
        } elsif( m/^\s*:([^=]+)\@$/ ){
         $options{$1}="0";
        } elsif( m/^\s*:([^=]+)/ ){
         $options{$1}="1";
        } elsif( m/^\s*([^|]+)/ ){
         $options{"Printer"}=$1;
        }
     } split( "\n", $ENV{'PRINTCAP_ENTRY'});
    
    # get the control file entries
     map {
        if( m/^\s*([A-Z])(.*)/ ){
         $options{$1}=$2;
        } elsif( m/^\s*([a-z])/ ){
         $options{'Format'}=$1;
        }
     } split( "\n", $ENV{'CONTROL'});


The Perl Getopt::Std routine parses the command line options and puts their values in the %args hash variable where they can be accessed using $args{'x'}. Similarly, the map and split functions process the PRINTCAP_ENTRY and CONTROL environment variable and set %options with the printcap entry options and the values from the control file. The map function could be replaced by a foreach loop, but this is Perl: There is more than one way to do it and no tutorial would be complete without at least one mind stretching example that has the reader reaching for the reference manual.

4.9.4. Filter Exit Codes

The lpd server uses the exit code of the filter to determine if the filter was successful or unsuccessful. The Filter Exit Codes section discusses these values in detail, but here are the most important:

0 - JSUCC

A JSUCC exit code indicates that the filter was successful in doing its work.

1 - JFAIL

A JFAIL exit code indicates that the filter was unsuccessful in doing its work, possibly due to a transient condition such as out of paper, printer jam, etc., but an additional attempt might be successful. Usually the lpd server will try at most send_try attempts before giving up.

2 - JABORT

A JABORT exit code indicates that the filter was unsuccessful in doing its work and has detected a condition that would make it impossible to print the job. In addition, the printer may require administrative attention, and the print queue operation may need to be suspended until the problem is rectified.

3 - JREMOVE

The JREMOVE exit code will cause the job to be removed from the print queue.

6 - JHOLD

The JHOLD exit code will cause the job to be temporarily prevented from printing until release by the lpc release command.

Other Values

Usually any other value, including exit due to a signal, is treated as a JABORT exit, and the same action is taken.



It should be obvious that the filter exit code is very important, and that care needs to be taken to return the correct value.

4.9.5. Job Formats and Filter Selection

In the previous sections we discussed how a print filter was executed and how it could be used. Now we will look at how the lpd spooler chooses a print filter program. Let us re-examine our example print job control file:

    Hh4.private
    Ppapowell
    J/tmp/hi
    CA
    Lpapowell
    Apapowell@h4+105
    D2000-04-12-15:27:26.662
    Qlp
    N/tmp/hi
    fdfA105h4.private
    UdfA105h4.private


Each data file for a print job has a name with the format dfXnnnh4.private. The df is used to indicate that the file is a data file, and the remainder is a unique name for the file in the job. The X part of the name must be an upper or lower case letter, setting a limit of 52 different files in a single print job.

The fdfA105h4.private line in the control file specifies that we are to print the job using the filter for the f format; the file printing information consists of the format assigned to the file and the name of the file. In the legacy BSD print spoolers, this format was used to select the print filter to be used. LPRng has expanded this by providing a default filter specification.

Table 4-2. Job Formats and Filter Selection

lpr command line option Control File Line Printcap Option For Filter Filter Command Line
(default) fdfAnnn :if=/path /path -Ff ...
-b or -l ldfAnnn :if=/path /path ... -Ff -c
-p pdfAnnn :if=/path pr | format f filter
-c, -d, -n, -r, -t, -v, -FX XdfAnnn :Xf=/path ... /path -FX
any format XdfAnnn :filter=/path ... /path -FX

Table 4-2 shows the rather baroque relationship between the format options specified by the lpr command and the way that lpd uses them. The reason for this complexity lies in the various implementations and variations that occurred during the development and deployment of the original BSD print spooling software.

Here is the complete, arcane, and baroque set of rules that are used to select and print filters. The default format used by lpr is f; unless some other format is specified this is used. The lpr -b and lpr -l (binary and literal literals) are a request to lpd to do as little processing as possible of this file before printing it. Lpd use the :if filter for formats f and l; the l literal causes the the -c filter command line flag to be used as well. The lpr -c, -d, -n, -r, -t, and -v options cause the corresponding format to be used, and for lpd to use the filter specified by the printcap option :Xf, where X is the specified format.

The lpr -p (pretty-print literal) selects p format, and lpd is supposed to use the program specified by the :pr printcap option to format the file and then process the output of this program according to format f. Unpredictable results may occur using this facility.

the lpr -FX allows you to explicitly specify format X where X is a lower case letter, and lpd will use the filter specified by printcap option :Xf, where X is the specified format. If there is no :Xf printcap literal value then the printcap :filter literal value will be used as the filter, and if this is undefined then the file will be passed without processing through to the printing device.

If a filter is not specified for the format then the default filter specified by :filter=/path filter is used, and if there is no default, then the output is sent directly to the output device.

If the :fx=formats is present in a printcap entry, it specifies the formats that are allowed. For example, :fx=lfv would allow only formats l, f, and v to be used on a particular spool queue.

Some Xf options have pre-assigned meanings and cannot be used for filter selection.

Printcap Option Purpose
Printcap Option Purpose
:af=/path Accounting File
:ff=formfeed Form Feed String
:if=/path filter (l,b,p,f formats)
:filter=/path Default filter
:lf=/path Log file
:of=/path OF filter
:sf suppress form feed between job files

The :of filter is a special case and is used for banner printing and accounting purposes. See OF Filter for details.