| Setting up a Printer Interface File
 
Larry Reznick 
The printer interface file is a filter that receives
all the data 
from the spooler and prepares it for the printer. The
interface is 
conceptually simple. It sees all its input from a set
of files named 
on its command line, and writes all its output to stdout,
expecting 
that whatever goes to stdout will go to the printer.
The interface 
file can be divided into three parts: preprocessing,
the data file 
output, and postprocessing. The preprocessing stage
sets up any special 
printer modes and may output a banner. The primary data
processing 
not only handles the data output, but may also deal
with special embedded 
codes in the data. The postprocessing handles cleanup,
including a 
formfeed for the print job. 
The interface file is a shell script. It does not have
to be, but 
with the power of shell script programming, why not
take advantage 
of something as easily maintained as shell script to
get the job done? 
On SVR4 all of the interface files are located in the
/usr/spool/lp/admins/lp/model 
directory. Your files may be in a slightly different
path depending 
on which vendor's UNIX you use. For instance, /usr/spool
may 
be /var/spool on SVR4, but /usr/spool is symbolically
linked to /var/spool for compatibility reasons. On SCO,
the 
path is /usr/spool/lp/model. If you are not sure where
yours 
is, try to find the "standard" interface file
by using: 
 
find /usr/spool -name standard -print 
 
This command should return the path where all the other
interface files are located. 
Specifying the Interface 
If you use lpadmin(1M) to set up a new printer, you
can specify 
one of three interface options. The -m option is used
to specify 
the model interface file; the -i option is used to install
a new interface file for a printer already in service;
and the -e 
option copies the interface file from an existing printer
already 
installed. These options are mutually exclusive. The
-m option 
takes as an argument the interface file's name without
a path -- 
the file is retrieved from the model directory. The
-i option 
wants the full path, while the -e option wants the name
of 
the existing printer. 
The simplest approach to setting up a new printer is
to find an existing 
model that comes close to the definitions of your printer,
copy it, 
and modify it to suit your needs. Why reinvent the wheel?
I have never 
needed to create a file completely from scratch, since
there has always 
been one to modify. If you cannot find a suitable model,
start with 
the file named standard -- that is, the one with the
-o 
options mentioned in the lp(1) man page: length, width,
lpi, cpi, and stty. The standard file always 
provides these options, but you should be aware that
some of the other 
interface files do not. 
The standard file is a large example of a Bourne shell
script. The 
file is a comprehensive example of what can be accomplished
with shell 
script programming because it contains interrupt handling,
functions, 
error handling, terminfo handling, and command-line
argument parsing. 
After you've studied this file, you should be able to
do almost anything 
with shell script. 
Defining Options 
In most cases, all you'll need to do is insert some
relevant options 
specific to the printer being installed. The argument
handling mechanism 
-- a function named parse and a comment showing the
command-line 
arguments -- can be found about one-third of the way
through the 
standard interface file. The arguments are generally
assigned to some 
variables: the first three (request ID, user name, and
title) are 
commonly used for the banner page; the fourth is the
number of copies 
requested of the print job coming in; and the fifth
is the option 
list combined into a single argument with spaces separating
each one. 
After that come the names of the files to be printed,
each in a separate 
argument. 
Once the command-line arguments have been absorbed into
appropriately 
named variables, the options variables in the fifth
argument are initialized. 
Typically, the modifications made to the standard interface
file consist 
of added options. There are three types of options:
simple options, 
value-setting options, and list options. The simple
options, such 
as 
 
-o nobanner 
 
use a single word to make something happen. The value
settings, such as 
 
-o cpi=17 
 
name a variable to be assigned a value. The list options,
such as 
 
-o stty='a b c d e' 
 
give multiple settings to a single command. If you are
adding your own and you want to be more specific, name
the variables 
after the facility they represent -- e.g., "spacing,"
"quality," 
"font," or "nlq," "compressed,"
"italic." 
The simple options usually come first, and are just
initialized to 
"no" or "yes" as appropriate. Later
on, they are tested 
by 
 
if [ "no" = varname ] 
 
syntax to determine whether other special operations
must be performed later to change the output format
on the printer. 
The value-setting options come next, and they should
be initialized 
to whatever default setting seems best. Value-setting
options usually 
take one of two forms: either a specific name is given
or a number 
is given. The name is used as an alias for a value,
such as "pica" 
for 10 cpi and "elite" for 12. For options
of this kind, the 
shell script case option simply checks to see if the
word has been 
used and sets the corresponding value. However, if a
specific value, 
such as "cpi=17", is given, the parse function
is used. 
This function is a marvelous little regular expression
using the expr(1) 
program and the colon (:) operator. 
 
parse () {
echo "`expr"$1\" : \"^[^=]*=\(.*\)\"`"
}
 
The expr colon operator says that whatever is 
on the left side of the colon (the value passed to parse)
will 
be matched against the argument on the right side of
the colon. expr 
returns the number of characters matched, but when the
\( and 
\) operators are used, it returns the characters from
first 
argument referred to by that part of the regular expression.
Before 
looking at the regular expression, notice that echo's
argument 
is quoted, so embedded quotation marks must be escaped
with backslashes. 
Thus,"$1\" puts quotation marks around expr's first 
argument, and there are similar quotation marks around
the regular 
expression. If you ignore the quotation marks around
the regular expression, 
what's left is: 
 
^[^=]*=\(.*\) 
 
which means, "find a string starting at the beginning
of the argument composed of zero or more of any characters
except 
the = symbol, followed by an = symbol, followed by zero
or more of 
any character." The value returned is the "zero
or more of 
any character." So, if the argument in "$1"
is "cpi=17", 
the result would be "17" since only the part
on the right 
side of the = symbol would be returned by expr. 
What you usually get in the value-setting part of the
option list 
case is something like: 
 
cpi=* )
cpi=`parse ${i}`
;;
 
where ${i} is the current option setting (the 
entire "cpi=17" value) being given to the
parse function, 
which will evaluate as the "$1" inside that
function. Thus, 
a variable named cpi will be set to the value 17, which
the 
parse function has extracted from the "cpi=17"
option string 
of the command line. You could set up your own variables
in this case 
operation, too. There are places later in the interface
file, where 
actual initialization occurs, where these settings would
fit in. 
Finally, there are the list options settings, such as
stty. 
If you use these options, you must add them in two places.
You first 
add them to the main case list, which uses the vertical
bar, '|', 
to OR them all together. This qualifies them for further
parsing, 
which occurs within a secondary case. This operation
seems a bit complicated, 
but essentially, it separates the command name value
(such as "stty") 
from the item list being assigned to it. The tests that
separate the 
item list look for items of the following forms: 
anything preceded by an = sign surrounded by apostrophes
anything preceded by an = sign and an apostrophe but
no closing apostrophe
anything preceded by an = sign whether or not there
are apostrophes
anything followed by an apostrophe
anything else. 
After the command is separated from the item list, the
command name 
is handled with another case and the item list is combined
with the 
appropriate real command. Your own local option commands
would be 
added to both of these cases. 
How the Filter Works 
These options are usually used to initialize the printer
before any 
of the data actually goes to the printer. Generally,
the banner is 
kept separate from any initialization performed on the
main print 
job, so that all banners always look the same. The initialization
options that you insert are usually applied only to
the actual print 
job data, and they apply to the entire print job. In
the standard 
file, the best place to put these changes is right after
the second 
appearance of the internal_lpset function call, which
comes 
after the banner, but just before the print job files
are output. 
If you use echo to output the codes, they will get to
the printer, 
just as the banner output does. If more detail is needed,
another 
filter program should be used. 
The actual printing attaches each file by redirection
to the stdin 
of a filter program specified for the printer. If you
have specified 
a filter using the lpfilter(1M) command, its name will
be set 
in a FILTER variable and the interface file will use
it. If there 
is no filter, the interface file will use either a special
file named 
/usr/spool/lp/bin/lp.cat or, if lp.cat does not exist,
the simple cat(1) program. Whichever of these is available
becomes the value of the FILTER variable. To do the
printing, 
the data is passed through the filter to the printer.
If additional 
processing on a file-by-file basis is required, the
filter program 
must do it. 
Unless -o nofilebreak was specified, a formfeed will
be output 
when each print job is finished. The last print job
gets a formfeed 
even if nofilebreak was specified. (Strictly speaking,
the 
formfeed goes to the printer only if the printer's terminfo
definition 
has a formfeed (FF) variable set.) The interface file
then waits for 
the printer to finish receiving all the data and quits. 
Conclusion 
Modifying a copy of the printer interface file is the
simplest way 
to add special features to the printer's handling of
entire print 
jobs, such as printing an entire document in near-letter-quality
vs 
draft mode or producing program listings in compressed
type to get 
132-character lines on 80-column paper. Since these
options apply 
to entire print jobs, the next step is to create a filter
program 
that can process the special codes within a single document
that affect only portions of that document.  
 
 About the Author
 
Larry Reznick has been programming professionally since
1978. He is currently 
working on systems programming in UNIX and DOS. He teaches
C language courses
at American River College in Sacramento. He can be reached
via email at: 
rezbook!reznick@csusac.ecs.csus.ed. 
 
 
 |