Writing an LP Interface Script

Norman A. Jacobs
Sun Microsystems, Inc.

What is an LP Interface Script ?

An LP inteface script is the component of the printing system that performs the final processing of a print job and actually sends it to the printer. It's sole purpose is to "print" the job. Upon completion, the script should either have completed the job and exited with a normal exit status, or have failed to complete, but exited with a non-normal exit status.

Interfaces

An interface script utilizes a number of interfaces to both gather intformation for it's operation, and return information about it's processing and results. These interfaces include the use of inbound command line arguments, inbound environment variables, file descriptors (for input and results), received signals, and exit status code.

Command Line Arguments

The command line arguments is the first place an interface script gathers information about what it is to do. The arguments provide information about the job, it's submitter, the features requested, and the data to handle.
 
$0 - .../{printer}
This argument is the name of the interface script that was called. On Solaris, this will alway be /etc/lp/interfaces/{printer}.
$1 - request-id
This is actually the first argument used in calling the interface script. It contains an identifier for the job. The intention is that this identifier is placed on any burst pages printed with the job. The format of the identifier is usually in the form of {printer}-{number}.
$2 - user
This the the user that submitted the print job. It is generally in the form of user@host and is to be used on any burst pages printed wit the job.
$3 - title
This is a title for the print job. Again, this is to be used on any burst pages printed with the job. A default title of the filename is normally supplied here, but lp -t or lpr -J will override the default.
$4 - copies
This is the number of copies of each document to print.
$5 - options (lp -o)
This contains a string of "transparent" options that were passed from the command line through the spooling system to this script. Options are generally used to turn on and off printer features like duplex printing, stapling, etc. It can also be used to pass additional required information like a fax # if the script is driving a faxmodem.
$6+ - file list
The rest of the arguements contain a list of all of the data files to send to the output device. This list of files is referenced through full path names.

Environment Variables

CHARSET - the character set to use
When this envronment variable is set, it contains the name of the character set to be used when printing the job. The value is generally used during printer initialization.
FILTER - fast filter to inline on the way to the printer
When set, this variable defines a "fast" filter stream to be used inline on hte way to the printer. All "good" interface scripts should heed this value when set.
LC_COLLATE - I18n
LC_CTYPE - I18n
LC_MESSAGES - I18n
LC_MONETARY - I18n
LC_NUMERIC - I18n
LC_TIME - I18n
Values to use when localizing any interface script derived ouput. These are primarily defined so that they can be passed to any programs called by the script.
PATH - default path (/usr/sbin:/usr/bin)
This should be obvious.
SPOOLER_KEY - used by lp.tell for backchannel to lpsched
This value is used by lp.tell(private interface) to uniquely specify the instance of the interface script running. lp.tell passes this value along with a fault message, or fault clear message to the scheduler when a problem occurs, or is resolved.
TERM - printer type
This value contains the printer type of the printer that this interface script is processing for. The type is defined with lpadmin -T.
TZ - our timezone
This should be obvious.

File Descriptors

0 - don't recall
1 - printer device (use /dev/null for network attached printers)
2 - backchannel to lpsched

Signals

Exit Codes

0 - success
If the interface script completed without any error, it should exit with this exit code.
1 - 127 - failure, don't restart
If the interface script had an error in processing the job and it's likely that the error is in the data, the script should probably exit with one of these codes.
128 - don't use
128 - failure, wait or restart
If the interface script encounters an error that is recoverable, the script should probably exit.

Effective UID

The interface script is run using one of two effective user ids. If the print job appears to the scheduler to have come from the local host, the euid will be that of the calling user. If the print job appears to have come from a remote source, the euid will be that of "lp". This should give the script the necessary privilege to access any of the files it needs to print.

If you are using a set-uid program inside of the interface script to perform some of the processing, you should make sure that you check for security hole that it may introduce while operating under the interface script, ass well as if called by hand.

Example

Here are a number of sample interface scripts that can be used as a starting point for implementing an interface script of your own.
GSinterface
This script is an simple example of using GhostScript to drive a PCL printer under LP.
SPARCprinter
This script is an simple example of using GhostScript to drive a SPARCprinter under LP.
NeWSprinterCL+
This script is an simple example of using GhostScript to drive a NeWSprinterCL+ printer under LP.
PSinterface
This script is an simple example of driving a PostScript printer under LP.
example
This script is a template example of an interface script.
Finally, here is a shell of an interface script to be used as a start to implementing your own.
#/bin/sh
#
#       Copyright (c) 1997, by Sun Microsystems Inc.
#       All Rights Reserved
#
#pragma ident   "@(#)interface.html     1.2     98/05/22 SM"
#
#       This is an example interface script to help you get an idea of how
# to integrate support for your printer under lp.  This is by no means the
# definitive source for information.  You should look at included interface
# scripts as examples and at the Solaris System Administrators Answerbook.
#
# This script is run as 'lp' on the print server if the job came in via the
# network.  It runs as the submitting user if the job is local.  You should
# also note that the data files are owned by the user running the script.
#
# Command Line Arguments:
#       $0      - .../{printer}
#       $1      - request-id
#       $2      - user
#       $3      - title
#       $4      - copies
#       $5      - options (lp -o)
#       $6+     - file list
# Environment Variables:
#       CHARSET         - the character set to use
#       FILTER          - fast filter to inline on the way to the printer
#       LC_COLLATE      - I18n
#       LC_CTYPE        - I18n
#       LC_MESSAGES     - I18n
#       LC_MONETARY     - I18n
#       LC_NUMERIC      - I18n
#       LC_TIME         - I18n
#       PATH            - default path (/usr/sbin:/usr/bin)
#       SPOOLER_KEY     - used by lp.tell for backchannel to lpsched
#       TERM            - printer type
#       TZ              - our timezone
# File Descriptors:
#       0       - don't recall
#       1       - printer device (use /dev/null for network attached printers)
#       2       - backchannel to lpsched
# Signals:
#
# Exit Codes:
#       0       - success
#       1 - 127 - failure, don't restart
#       128     - don't use
#       >128    - failure, wait or restart
#

if [ $# -lt 5 ] ; then
        echo "wrong number of arguments to interface program" 1>&2
        exit 1
fi


printer=`basename $0`
request_id=$1
user_name=$2
title=$3
copies=$4
option_list=$5

shift 5
files="$*"

stty=

parse () {
        echo "`expr \"$1\" : \"^[^=]*=\(.*\)\"`"
}

nobanner="no"
nofilebreak="no"
inlist=
for i in ${option_list}
do
        case "${inlist}${i}" in

        nobanner )
                nobanner="yes"
                ;;

        nofilebreak )
                nofilebreak="yes"
                ;;

# lp -o key
#       key )
#               key="whatever"
#               ;;

# lp -o key=value
#       key=* )
#               key=`parse ${i}`
#               ;;

# lp -o key='list ...'
#       stty=* )
#               inlist=`expr "${inlist}${i}" : "^\([^=]*=\)"`
#               case "${i}" in
#               ${inlist}\'*\' )
#                       item=`expr "${i}" : "^[^=]*='*\(.*\)'\$"`
#                       ;;
#               ${inlist}\' )
#                       continue
#                       ;;
#               ${inlist}\'* )
#                       item=`expr "${i}" : "^[^=]*='*\(.*\)\$"`
#                       ;;
#               ${inlist}* )
#                       item=`expr "${i}" : "^[^=]*=\(.*\)\$"`
#                       ;;
#               *\' )
#                       item=`expr "${i}" : "^\(.*\)'\$"`
#                       ;;
#               * )
#                       item="${i}"
#                       ;;
#               esac
#
#               #####
#               #
#               # We don't dare use "eval" because a clever user could
#               # put something in an option value that we'd end up
#               # exec'ing.
#               #####
#               case "${inlist}" in
#               key= )
#                       key="${key} ${item}"
#                       ;;
#               esac
#
#               case "${i}" in
#               ${inlist}\'*\' )
#                       inlist=
#                       ;;
#               ${inlist}\'* )
#                       ;;
#               *\' | ${inlist}* )
#                       inlist=
#                       ;;
#               esac
#               ;;
        * )
                echo "unrecognized \"-o ${i}\" option check the option, resubmit if necessary printing continues" 1>&2
                ;;
        esac
done

#
# should create a banner
#

#
# process the files
#

i=1
while [ $i -le $copies ]
do
        for file in $files
        do
                cat $file
        done
        i=`expr $i + 1`
done
exit 0