Print Job Filters

LPRng Reference Manual: 24 Sep 2004 (For LPRng-3.8.28)
Prev | Chapter 4. Print Spooling Tutorial | Next

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

- Observe that all the command line options are single letters. If we set the shell variables to the corresponding option value, then we could access them by using $x, where x is the option letter. There is an exception to this rule, which is the -c command line literal, which for various historical and compatibility reasons does not take a value. But if it is present, we might as well assign it the value 1.

- Observe that by convention all printcap options have lowercase names of two or more letters, and that all environment variables have all upper case letters. If we set shell variables with the corresponding printcap entry values, then we can access them using $literal. If we need to create a local shell variable for use, we can use mIxEd case and not have a conflict.

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 dfXnnn`h4.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.

Prev | Home | Next
Job Removal | Up | Job File Format Conversion with Filters