A print spooler is a program that accepts print jobs (which are usually one or more files) from a program or network interface, stores them in a spool queue, and then sends them to a printer or another print spooler. Usually there are facilities to submit jobs, check on the current job status, remove jobs from spool queues, and perform administrative functions such as starting or stopping printing.
A print spooler is a client/server application. The client programs are used to submit jobs to the print spooler program which performs the actual printing operations. In order to carry out these operations, the server may need to use other programs to convert print job files into a format acceptable to a printer, or perform various accounting or administrative functions.
+---------+ +-----+ +-----+ +--------+ +---------+
| program | -> | lpr | -> | lpd | -> | filter | -> | printer |
+---------+ +-----+ * +-----+ +--------+ +---------+
* * |
printcap V
+-----+ +--------+ +---------+
| lpd | -> | filter | -> | printer |
+-----+ +--------+ +---------+
Figure 1
Figure 1 shows the flow of data between the individual components of the
LPRng print spooling system.
A program (or user) will use the lpr program to send a file
to the lpd server over a TCP/IP connection.
The lpd server will store the file temporarily in a
spool queue directory.
The information needed by the lpr and lpd programs to carry
out this activity is stored in the
printcap (usually called the /etc/printcap) database file.
The lpd server sorts the queue entries and determines the print order.
It will select a job to be printed,
open a connection to the printer,
and then use a filter program to convert the file contents into a
format suitable for the printer.
If the file does not need conversion,
then the lpd server will send the file directly to the printer.
The lpd server can also forward jobs to another print server
over a network connection,
optionally sending them through a filter as well.
The destination server can in turn forward the job or send it to a printer.
The protocol or commands used to do this job forward and transfer are
specified by
RFC1179.
This protocol specifies how the lpr client program sends a job
to the lpd server,
as well as how the lpd server forwards jobs to another server.
In addition to job submission,
RFC1179 specifies commands to obtain queue status,
to remove jobs from the queue,
and to start and stop print queues.
As described in the
Print Spooling Overview,
the information in the printcap database is used control printing
operations.
While there is no RFC specifying its format or content,
there is a strong de facto standard for its format.
For a complete description of the printcap database see
Using the Printcap Database.
For a list of all of the printcap and configuration options see
Index To All The Configuration and Printcap Options.
Here is a sample printcap suitable for use by the LPRng clients:
# LPRng
lp:lp=psqueue@printserver.astart.com
# Classical BSD
lp:rp=psqueue:rm=printserver.astart.com
The printcap information tells the client programs
that jobs for the lp printer
are sent to the
psqueue print queue queue on host printerserver.astart.com.
The classical BSD printcap is also shown.
The rp value is the remote printer (actually a spool queue)
and the rm value is the server.
When both
lp and the
rp/rm are present the lp value has precedence.
On the printserver
the following is a sample printcap entry
suitable for the lpd server:
psqueue:server
:lp=/dev/lp0
:sd=/var/spool/lpd/psqueue
:if=/usr/local/libexec/filters/ifhp
The sd (spool queue directory) entry specifies the directory where
print jobs received from the lpr program will be placed.
The lp value is the output device
the lpd server will use to print the job.
The if (input file filter) is a program that
translates the input file to a format compatible with the printer.
If there is not need for a conversion filter then
this entry is left out.
The previous sections have given a very high level view of printing operations. In order to do printing the following programs and information must be established:
The following sections will cover each of these topics in turn. The initial sections assume that most users are setting up printers on small systems and require an extremely simple print capability. Later sections explore the various configurations that can be used to support large networks of print spoolers as would be found in large academic institutions or businesses.
In order to simplify operation and not waste lots of paper, we will use a very simple printcap entry during this tutorial. During the tutorial we will also show how to modify the printcap to add additional capabilities. You should run these tests as a nonprivileged (non-ROOT) user except where specifically noted.
By default,
the LPRng system uses the printcap file in
/etc/printcap
or
/usr/local/etc/printcap.
Save the existing file and then create a new printcap file
as shown below.
# -- obtain root permissions. You will need to entry the password
su
cd /etc OR cd /usr/local/etc
mv printcap printcap.orig
vi printcap
# -- put the following lines into the printcap file:
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
lp2:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp2
# -- save the printcap file and set permissions so programs
# can read it
chmod 644 printcap
First,
we use the su command to get SuperUser (ROOT) privileges.
We then save the original printcap file,
and create a new simple one.
This printcap file has two entries: lp and lp2.
Each print queue on the server needs a spool file to hold print jobs,
jobs,
and the :sd value specifies its location.
The %P value is replaced with the name of the printer
when it is used.
The force_localhost
is a very powerful option used by LPRng to provide
classical BSD
or
lightweight
operation.
In classical BSD operation
each host has an lpd print spooler running on the local host
(we use localhost in this manual for simplicity).
Files were copied to spool directories on the localhost and then then
print spooler would send them to the destination,
which could be another print spooler.
This meant that each localhost machine had to have a print spooler
and spool queue directory structure.
Management of this becomes very difficult in large organizations.
The LPRng lightweight operation allows the client programs
to communicate directly with an lpd server that is not
running on the localhost.
While this would appear to be the most desirable default mode of
operation,
it turns out in practice that all existing UNIX documentation
for print spooler configuration
assumes the
classical BSD
configuration.
When the behavior of LPRng is different
new users become extremely frustrated.
In this tutorial we will show how to use both
modes and the advantages and disadvantages of both modes.
The print device (:lp=) will be a file
so that we can see what is happening during these tests
(and also to save trees).
# -- create the dummy printing devices
cp /dev/null /var/tmp/lp
cp /dev/null /var/tmp/lp2
chown daemon /var/tmp/lp /var/tmp/lp2
chgrp daemon /var/tmp/lp /var/tmp/lp2
# we want to read and write the dummy device
chmod 666 /var/tmp/lp /var/tmp/lp2
echo hi >/tmp/hi
echo there >/tmp/there
Next we create the files that will act as our printing devices. We give them world read and write permissions so we can observe their contents. We also create a couple of files containing some information to print.
# we modify the lpd.perms to allow an ordinary user to control
# the print queues
mv lpd.perms lpd.perms.orig
vi lpd.perms
# put the following into the lpd.perms file:
DEFAULT ACCEPT
The next set of commands creates a permissions database that will allow all actions by default. This is useful for testing, but dangerous in a working environment.
# -- checkpc create directories and files for the print queue
# as well as make sure permissions are correct
checkpc -f -V
# -- output will contain information similar to:
# Checking printer 'lp'
# Checking directory: '/var/spool/lp'
# directory '/var'
# directory '/var/spool'
# directory '/var/spool/lp'
# Warning - changing ownership '/var/spool/lp' to 1/1
# checking 'control.lp' file
# checking 'status.lp' file
# checking 'status' file
# cleaning 'status' file, 0K bytes: no truncation
# checking 'log' file
# cleaning 'log' file, 0K bytes: no truncation
# checking 'acct' file
# cleaning 'acct' file, 0K bytes: no truncation
# Checking printer 'lp2'
# Checking directory: '/var/spool/lp2'
# directory '/var'
# ....
# -- signal the lpd server to use the new configuration
lpc reread
# -- if no server was running you can start the server
lpd
# -- do the next steps as ordinary user
# we first exit from su
exit
# -- check to see if the server is active and the queue is
# present
lpq
# example of output:
# Printer: lp@h4
# Queue: no printable jobs in queue
# -- check to see that the reread command is accepted
lpc reread
The
checkpc
performs consistency checks on the printcap file and spool queue entries.
The
checkpc -f (fix) option will change permissions and create
directories and can only be executed by ROOT.
The
checkpc
has other functions as well - you can view printcap information,
see default configuration values,
and
remove junk files from spool queues with it.
The lpc reread
command sends a request to the LPD server to reread the configuration
and printcap information.
The lpd command is added as insurance in case your lpd
server is not running.
The exit command restores ordinary user privileges,
and the lpq command is used to check that the server is running.
Finally,
we check to see that the lpc reread command is accepted
from an ordinary user.
To restore the original configuration,
you simply need to restore the original printcap
and lpd.perms file and then restart the lpd server.
<tscreen>
<verb>
# -- obtain root permissions. You will need to entry the password
su
cd /etc OR cd /usr/local/etc
mv printcap.orig printcap
mv lpd.perms.orig lpd.perms
checkpc -f
lpc reread
rm -rf /var/spool/lpd/lp /var/spool/lpd/lp2
rm -f /var/tmp/lp /var/tmp/lp2
exit
Try the following commands. The commands appear after the prompt, and sample output that you might see is shown.
h4: {168} % lpr -V /tmp/hi
Version LPRng-3.6.14
sending job 'papowell@h4+238' to lp@localhost
connecting to 'localhost', attempt 1
connected to 'localhost'
requesting printer lp@localhost
sending control file 'cfA238h4.private' to lp@localhost
completed sending 'cfA238h4.private' to lp@localhost
sending data file 'dfA238h4.private' to lp@localhost
completed sending 'dfA238h4.private' to lp@localhost
done job 'papowell@h4+238' transfer to lp@localhost
The lpr -V (Verbose) option causes LPR to print
status output.
As you can see from the above lines,
it first tries to connect to the lpd server on host
localhost,
then sends a print prequest (which is accepted),
then sends a control file
containing information about the job
and a data file or files which are copies of the files to be
printed.
If you check the /var/tmp/lp file
and you will find that a copy of /tmp/hi
has been written to it.
By default,
the lpd print spooler acts as a store and forward system,
accepting files to be printed,
holding them in the print queue,
and then forwarding them to the destination system or output device.
You can use the lpq command to view the status of the print job.
h4: {169} % lpq
Printer: lp@h4
Queue: no printable jobs in queue
Status: job 'papowell@h4+238' removed at 09:39:03.256
If you want to see more status information,
use lpq -l, lpq -ll, or even lpq -L.
The -L provides alL the status.
h4: {170} % lpq -l
Printer: lp@h4
Queue: no printable jobs in queue
Status: lp@h4.private: job 'papowell@h4+238' printed at 09:39:03.112
Status: job 'papowell@h4+238' removed at 09:39:03.256
h4: {171} % lpq -ll
Printer: lp@h4
Queue: no printable jobs in queue
Status: finished 'papowell@h4+238', status 'JSUCC' at 09:39:03.108
Status: subserver pid 8240 exit status 'JSUCC' at 09:39:03.110
Status: lp@h4.private: job 'papowell@h4+238' printed at 09:39:03.112
Status: job 'papowell@h4+238' removed at 09:39:03.256
h4: {172} % lpq -L
Printer: lp@h4
Queue: no printable jobs in queue
Status: subserver pid 8240 starting at 09:39:03.105
Status: accounting at start at 09:39:03.105
Status: opening device '/var/tmp/lp' at 09:39:03.105
Status: printing job 'papowell@h4+238' at 09:39:03.106
Status: no banner at 09:39:03.107
Status: printing data file 'dfA238h4.private', size 3 at 09:39:03.107
Status: printing done 'papowell@h4+238' at 09:39:03.107
Status: accounting at end at 09:39:03.108
Status: finished 'papowell@h4+238', status 'JSUCC' at 09:39:03.108
Status: subserver pid 8240 exit status 'JSUCC' at 09:39:03.110
Status: lp@h4.private: job 'papowell@h4+238' printed at 09:39:03.112
Status: job 'papowell@h4+238' removed at 09:39:03.256
There are different status formats available as well.
The lpq -s (summary) produces a single line of status per spool queue,
while the lpq -v (verbose)
produces output that is very suitable for processing with
programs such as Perl or awk:
h4: {173} % lpq -s
lp@h4 0 jobs
h4: {174} % lpq -v
Printer: lp@h4
Printing: no
Aborted: no
Spooling: no
Queue: no printable jobs in queue
SPOOLCONTROL=
Status: subserver pid 8240 starting at 09:39:03.105
Status: accounting at start at 09:39:03.105
Status: opening device '/var/tmp/lp' at 09:39:03.105
Status: printing job 'papowell@h4+238' at 09:39:03.106
Status: no banner at 09:39:03.107
Status: printing data file 'dfA238h4.private', size 3 at 09:39:03.107
Status: printing done 'papowell@h4+238' at 09:39:03.107
Status: accounting at end at 09:39:03.108
Status: finished 'papowell@h4+238', status 'JSUCC' at 09:39:03.108
Status: subserver pid 8240 exit status 'JSUCC' at 09:39:03.110
Status: lp@h4.private: job 'papowell@h4+238' printed at 09:39:03.112
Status: job 'papowell@h4+238' removed at 09:39:03.256
If you check the /var/tmp/lp file
and you will find that a copy of /tmp/hi
has been written to it.
By default,
the lpd print spooler acts as a store and forward system,
accepting files to be printed,
holding them in the print queue,
and then forwarding them to the destination system or output device.
In the previous section we used the default print queue.
How does LPRng determine what print queue to use?
First,
you can explicitly specify the printer using the lpq -Pprintqueue
option
and the lpq -a or lpq -Pall to select all print queues:
h4: {160} % lpq -Plp
Printer: lp@h4
Queue: no printable jobs in queue
h4: {161} % lpq -Plp2
Printer: lp2@h4
Queue: no printable jobs in queue
h4: {162} % lpq -a
Printer: lp@h4
Queue: no printable jobs in queue
Printer: lp2@h4
Queue: no printable jobs in queue
You can combine the lpq -a with the lpq -s option for a summary listing:
h4: {162} % lpq -a
Printer: lp@h4
Queue: no printable jobs in queue
Printer: lp2@h4
Queue: no printable jobs in queue
h4: {163} % lpq -s -a
lp@h4 0 jobs
lp2@h4 0 jobs
There is another way to explicitly specify the printqueues
listed by lpq -a;
see the
ALL Printcap Entry
for details.
Users can set their default printer by using the
PRINTER (highest priority),
LPDEST (next),
and
NGPRINTER (lowest priority),
environment variables.
For example:
h4: {164} % setenv PRINTER lp2
h4: {165} % lpq
Printer: lp2@h4
Queue: no printable jobs in queue
h4: {164} % unsetenv PRINTER
h4: {165} % lpq
Printer: lp@h4
Queue: no printable jobs in queue
Finally,
if you do not use the -P printqueue or have an environment variable set,
then the default printer chosen by the system administrator will be used.
Please see
Printer Name and Server IP Address
for details.
You can control the input and output functions of the print queue.
The
lpc enable and
lpc disable commands
control spooling to the print queue and the
lpc stop and
lpc start commands
control printing (or transfers) from the print queue.
There is also a lpc status
command that displays administrative status for a print queue.
Let's look at the status displayed when we use these commands:
h4: {30} % lpc disable
Printer: lp@h4
lp@h4.private: disabled
h4: {31} % lpq
Printer: lp@h4 (spooling disabled)
Queue: no printable jobs in queue
h4: {32} % lpc enable
Printer: lp@h4
lp@h4.private: enabled
h4: {33} % lpq
Printer: lp@h4
Queue: no printable jobs in queue
h4: {34} % lpc stop
Printer: lp@h4
lp@h4.private: stopped
h4: {35} % lpq
Printer: lp@h4 (printing disabled)
Queue: no printable jobs in queue
h4: {36} % lpc start
Printer: lp@h4
lp@h4.private: started
h4: {37} % lpq
Printer: lp@h4
Queue: no printable jobs in queue
h4: {38} % lpc status
Printer Printing Spooling Jobs Server Subserver Redirect Status/(Debug)
lp@h4 enabled enabled 0 none none
Let's see what happens when we print to a stopped queue:
h4: {79} % lpc stop
Printer: lp@h4
lp@h4.private: stopped
h4: {80} % lpr /tmp/hi
h4: {81} % lpr /tmp/hi /tmp/there
h4: {82} % lpq
Printer: lp@h4 (printing disabled)
Queue: 2 printable jobs
Server: no server active
Rank Owner/ID Class Job Files Size Time
1 papowell@h4+17920 A 17920 /tmp/hi 3 18:14:22
2 papowell@h4+17922 A 17922 /tmp/hi,/tmp/there 9 18:14:30
h4: {83} % lpc status
Printer Printing Spooling Jobs Server Subserver Redirect Status/(Debug)
lp@h4 disable enabled 2 none none
The status shows that we have two jobs spooled.
The Rank field shows the order,
the Owner/ID shows the unique job ID that is assigned to the
job
and
the Class field is the job class (this may be changed with the lpr -C class
option).
The Job field shows the job number assigned to this job in this
particular spool queue.
While the ID value never changes as a job moves through the LPRng system,
the job number is specific to a particular spool queue and may change
if a job is forwarded to another spool queue that has a job with the
same job number.
The Size field is the total number of printable bytes in the job,
and the Time field shows the timestamp associated with the job.
The lpq -s output is very succinct, and does not show the state of the spool queue:
h4: {51} % lpq -s
lp@h4 2 jobs
Now let's start the print queue and watch what happens.
h4: {83} % lpc start
Printer: lp@h4
lp@h4.private: started
h4: {84} % lpq
Printer: lp@h4
Queue: 2 printable jobs
Server: pid 17928 active
Unspooler: pid 17929 active
Status: opening device '/var/tmp/lp' at 18:14:43.921
Rank Owner/ID Class Job Files Size Time
active papowell@h4+17920 A 17920 /tmp/hi 3 18:14:22
2 papowell@h4+17922 A 17922 /tmp/hi,/tmp/there 9 18:14:30
h4: {85} % lpq -ll
Printer: lp@h4
Queue: 2 printable jobs
Server: pid 17928 active
Unspooler: pid 17929 active
Status: printing job 'papowell@h4+17920' at 18:14:43.921
Status: no banner at 18:14:43.921
Status: printing data file 'dfA017920h4.private', size 57 at 18:14:43.922
Rank Owner/ID Class Job Files Size Time
active papowell@h4+17920 A 17920 /tmp/hi 3 18:14:22
2 papowell@h4+17922 A 17922 /tmp/hi,/tmp/there 9 18:14:30
The Rank value of the first job has been changed to active
and there is new Status
information.
If we use lpq -ll we can see the times that the various print operations
are carried out,
and details of their success or failure.
We can also use the lpq command to see the status of a particular job.
We can select jobs by the user name, the ID, or the job number.
For example:
h4: {88} % lpc stop
Printer: lp@h4
lp@h4.private: stopped
h4: {89} % echo hi |lpr
h4: {90} % echo there | lpr
h4: {91} % echo test |lpr
h4: {92} % lpq
Printer: lp@h4 (printing disabled)
Queue: 3 printable jobs
Server: no server active
Status: job 'papowell@h4+17922' removed at 18:15:13.981
Rank Owner/ID Class Job Files Size Time
1 papowell@h4+17959 A 17959 (stdin) 3 18:23:24
2 papowell@h4+17962 A 17962 (stdin) 6 18:23:30
3 papowell@h4+17970 A 17970 (stdin) 5 18:23:35
h4: {93} % lpq 17970
Printer: lp@h4 (printing disabled)
Queue: 3 printable jobs
Server: no server active
Status: job 'papowell@h4+17922' removed at 18:15:13.981
Rank Owner/ID Class Job Files Size Time
3 papowell@h4+17970 A 17970 (stdin) 5 18:23:35
h4: {94} % lpq papowell
Printer: lp@h4 (printing disabled)
Queue: 3 printable jobs
Server: no server active
Status: job 'papowell@h4+17922' removed at 18:15:13.981
Rank Owner/ID Class Job Files Size Time
1 papowell@h4+17959 A 17959 (stdin) 3 18:23:24
2 papowell@h4+17962 A 17962 (stdin) 6 18:23:30
3 papowell@h4+17970 A 17970 (stdin) 5 18:23:35
h4: {94} % lpq -s 17970
lp@h4 1 jobs
h4: {95} % lpq -s papowell
lp@h4 3 jobs
h4: {96} % lpq -s nobody
lp@h4 0 jobs
Just as we used the lpq -Pqueuename to select a specific
print queue,
and the lpq -a or lpq -Pall to select all queues:
h4: {167} % lpc -a stop
Printer: lp@h4
lp@h4.private: stopped
Printer: lp2@h4
lp2@h4.private: stopped
h4: {168} % lpc -Pall start
Printer: lp@h4
lp@h4.private: started
Printer: lp2@h4
lp2@h4.private: started
Finally,
you can use the lpc command in interactive mode:
h4: {170} % lpc
lpc>status
Printer Printing Spooling Jobs Server Subserver Redirect Status/(Debug)
lp@h4 enabled enabled 0 none none
lpc>status all
Printer Printing Spooling Jobs Server Subserver Redirect Status/(Debug)
lp@h4 enabled enabled 0 none none
lp2@h4 enabled enabled 0 none none
lpc>stop lp
Printer: lp@h4
lp@h4.private: stopped
lpc>start lp
Printer: lp@h4
lp@h4.private: started
lpc>quit
In the interactive or extended command mode, command syntax has the form operation [printqueue] operands. This syntax can also be used on command lines, but is less common:
h4: {171} % lpc stop all
Printer: lp@h4
lp@h4.private: stopped
Printer: lp2@h4
lp2@h4.private: stopped
Occasionally we print a file
and then change our mind and want to cancel the job.
The lprm command allows us to do this.
h4: {126} % lpq
Printer: lp@h4 (printing disabled)
Queue: 3 printable jobs
Server: no server active
Status: job 'papowell@h4+17922' removed at 18:15:13.981
Rank Owner/ID Class Job Files Size Time
1 papowell@h4+17959 A 17959 (stdin) 3 18:23:24
2 papowell@h4+17962 A 17962 (stdin) 6 18:23:30
3 papowell@h4+17970 A 17970 (stdin) 5 18:23:35
h4: {127} % lprm
Printer lp@h4:
checking perms 'papowell@h4+17959'
dequeued 'papowell@h4+17959'
h4: {128} % lpq
Printer: lp@h4 (printing disabled)
Queue: 2 printable jobs
Server: no server active
Status: job 'papowell@h4+17922' removed at 18:15:13.981
Rank Owner/ID Class Job Files Size Time
1 papowell@h4+17962 A 17962 (stdin) 6 18:23:30
2 papowell@h4+17970 A 17970 (stdin) 5 18:23:35
h4: {129} % lprm 17970
Printer lp@h4:
checking perms 'papowell@h4+17970'
dequeued 'papowell@h4+17970'
h4: {130} % lpq
Printer: lp@h4 (printing disabled)
Queue: 1 printable job
Server: no server active
Status: job 'papowell@h4+17922' removed at 18:15:13.981
Rank Owner/ID Class Job Files Size Time
1 papowell@h4+17962 A 17962 (stdin) 6 18:23:30
By default,
the lprm
command removes the first job in the queue that the user has permission to
remove.
Also,
as shown in the example,
you can remove a job by specifying the job ID or the job number.
If you specify a user name,
you remove all of the user's jobs.
This can be dangerous:
h4: {137} % lpq
Printer: lp@h4 (printing disabled)
Queue: 3 printable jobs
Server: no server active
Status: job 'papowell@h4+17922' removed at 18:15:13.981
Rank Owner/ID Class Job Files Size Time
1 papowell@h4+17962 A 17962 (stdin) 6 18:23:30
2 papowell@h4+18499 A 18499 /tmp/hi 3 18:56:00
3 papowell@h4+18501 A 18501 /tmp/there 6 18:56:02
h4: {138} % lprm papowell
Printer lp@h4:
checking perms 'papowell@h4+17962'
dequeued 'papowell@h4+17962'
checking perms 'papowell@h4+18499'
dequeued 'papowell@h4+18499'
checking perms 'papowell@h4+18501'
dequeued 'papowell@h4+18501'
h4: {139} % lpq
Printer: lp@h4 (printing disabled)
Queue: no printable jobs in queue
Status: job 'papowell@h4+17922' removed at 18:15:13.981
The special user all matches all jobs in a print queue.
Clearly you should be careful not to specify lprm all by accident.
Even more dangerous is the following command:
h4: {139} % lprm -a all
As you might surmise, this removes all print jobs in all queues, which is an excellent way to purge print queues of all jobs.
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, control characters, or other information. The most common Print Job Languages are PostScript and PCL.
In order for a printer to reliably print a job, it needs to be reset to a known configuration. This is usually done by sending it a special start of job and end of job command. These commands differ from printer to printer, and even depend on the type of print job language that the print job is in. 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 of these escape sequences needs to be sent to the printer before the file can be printed.
In order to handle these problems,
the LPRng system uses a filter
program to provide the set of escape sequences or carry
out the initialization required for a printer.
The files in a print job are assigned a
lower case letter
format
using the lpr format options;
the default format is f.
The l (literal or binary) format is used to indicate
that the file should be 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.
We will set up a very simple filter and use it to demonstrate
how the lpd spooler uses it.
First,
set up the /var/tmp/testf file as shown below.
#!/bin/sh
# /var/tmp/testf - test filter for LPRng
PATH=/bin:/usr/bin
echo TESTF $0 "$@" >&2
while expr "$1" : '-.*' >/dev/null ; do
n=` expr $1 : '-\(.\).*' `;
v=` expr $1 : '-.\(.*\)' `;
shift;
if [ "$n" != 'c' -a -z "$v" ] ; then
v=$1;
shift;
fi
eval "$n='$v'";
done
echo ENV
printenv
echo LEADER
/bin/cat
echo TRAILER
exit 0
Let us carefully examine the script line by line.
The first couple of lines are standard boilerplate.
You should
always
set the PATH
value in a filter script or use full pathnames.
This is a good practice
as it ensures that only the specified directories will be
searched for commands.
The next line echos the arguments to file descriptor 2 (STDERR); by convention the filter STDERR output is used for error messages or important status information. We will soon see how this information is displayed by the LPRng software.
The while loop is used to extract the option flags and their values.
All of these but the c flag have an argument.
Finally we echo LEADER to STDOUT,
then copy STDIN to STDOUT,
and then echo TRAILER to STDOUT.
We then exit with a zero result code.
Now execute the commands below to test the script:
h4: {50} % chmod 755 /var/tmp/testf
h4: {51} % echo hi |/var/tmp/testf -a1
TESTF /var/tmp/testf -a1
LEADER
hi
TRAILER
Let us put this filter into one of our printcap entries.
Edit the lp printcap entry so it has the following form,
use checkpc -f to check the printcap,
and then use lpc reread to restart the lpd server
as we did in previous exercises.
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
:if=/var/tmp/testf
Execute the following commands
to print our /tmp/hi test file file
and see what changes have occurred in the printing process:
h4: {47} % cp /dev/null /var/tmp/lp
h4: {48} % lpr /tmp/hi
h4: {49} % 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 '/var/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 /var/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/v
ar/tmp/LPD/lp -edfA026681h4.private -f/tmp/hi -hh4.private -j026681 -kcfA026681h
4.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
The cp command will clear out the /var/tmp/lp
file we are using as a dummy output device.
Next we use lpr to print the /tmp/hi file
and then use lpq -llll to see the status information.
If we look at the status,
we see that
the line containing IF filter msg is the output that
testf 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 message,
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 Flags,
but for now we will ignore them.
Sometimes we want to pass only a small subset of these options, or provide them in a specific manner. Modify the printcap entry to have the following form:
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
:if= -$ /var/tmp/testf '$P' $0P -X$-P ${lp} G$%3a
If you redo the previous commands to print our test file and examine the status, you will discover that the options now are:
Status: IF filter msg - 'TESTF /var/tmp/testf -Plp -P lp -Xlp \
-Ylp /var/tmp/lp G:' at 01:20:21.560
The -$ suppresses appending the default options to the end
of the filter command line.
You can add specific options using the $X format;
if the option has a non-null value then it will be expanded in the following
format:
Option Value Expansion
$X -X<value>
$0X -X <value>
$-X <value>
${name} printcap option value
$%XX character corresponding to 0xXX
You can group options and other values using single or double quotes. These are used only for grouping and will be removed when the command is executed.
The ${name} format is very handy for passing a filter
an option value which is set in the printcap.
For example,
you may decide to create a new printcap option called form,
which will set the form type to be used with this spool queue.
You can pass it to the filter
which will then generate the necessary control codes for the printer
to set the form.
For example:
Example 1:
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
:if=-$ /var/tmp/testf -F${form},
:form=payroll
....
Status: IF filter msg - 'TESTF /var/tmp/testf -Fpayroll, ' at 01:20:21.560
....
Example 2:
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
:if=-$ /var/tmp/testf -F '${form}'
:form=
....
Status: IF filter msg - 'TESTF /var/tmp/testf -F, ' at 01:20:21.560
....
In the second example the form
printcap option has a null value,
which is expanded to a empty string.
Notice that we have added a comma at the end of the parameter;
this will protect the parsing of command line options from failure
if the form value is null.
Clearly the testf filter must be ready to deal with
a comma at the end of the -F parameter.
Now lets look at what is in the output file:
h4: {50} % cat /var/tmp/lp
ENV
LD_LIBRARY_PATH=/lib:/usr/lib:/usr/5lib:/usr/ucblib
HOME=/home/daemon
PRINTCAP_ENTRY=lp
:force_localhost
:if=/var/tmp/testf
:lp=/var/tmp/lp
:sd=/var/tmp/LPD/lp
USER=daemon
PS1=$
OPTIND=1
PS2=>
SPOOL_DIR=/var/tmp/LPD/lp
LOGNAME=daemon
CONTROL=Hh4.private
Ppapowell
J/tmp/hi
CA
Lpapowell
Apapowell@h4+105
D2000-04-12-15:27:26.662
Qlp
N/tmp/hi
fdfA000105h4.private
UdfA000105h4.private
PATH=/bin:/usr/bin:/usr/local/bin
SHELL=/bin/sh
LOGDIR=/home/daemon
PRINTER=lp
LEADER
hi
TRAILER
The lpd server sets the
PRINTER,
PRINTCAP_ENTRY,
and
CONTROL
environment variables to the printer name,
printcap entry, and the control 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 can also use parameters in the printcap entry
to control their actions.
We will see how the
ifhp filter makes use of this in a later section.
When printing to vintage hard copy devices or to printers that support a text mode, many UNIX users discover that their output suffers from a case of the jaggies.
Input file:
This is
a nice day
Output:
This is
a nice day
UNIX systems terminate lines with a single NL (new line) character.
This causes the printer to move down one line on the printing page
but does not change its horizontal position
and print the next character at the left margin.
This is done by using the CR (carriage return) character.
You need to convert the single NL to a CR-LF
combination and the lpf filter supplied with LPRng does this.
First,
locate the lpf filter.
It is usually in /usr/local/libexec/filters directory,
but this may vary from installation to installation.
You can find it by using the command:
find / -type f -name lpf -print
We will first see what the output is like without
lpf,
and then see what it does.
Modify the lp printcap entry as shown below
and then use lpc restart
to restart the lpd server.
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
Print a file and view the output using the following
commands.
If you do not have the od (octal dump) program,
try using hexdump or some other appropriate program
that displays the numerical contents of the file.
h4: {134} % cp /dev/null /var/tmp/lp
h4: {135} % lpr /tmp/hi
h4: {136} % od -bc /var/tmp/lp
0000000 150 151 012
h i \n
0000003
Now we will use the lpf filter.
Modify the printcap as shown below
and use lpc lpd to restart lpd.
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
# modify the path to lpf appropriately
:if=/usr/local/libexec/filters/lpf
Now reprint the file:
h4: {141} % cp /dev/null /var/tmp/lp
h4: {142} % lpr /tmp/hi
h4: {143} % od -bc /var/tmp/lp
od -bc /var/tmp/lp
0000000 150 151 015 012
h i \r \n
0000004
As you see,
lpf changes the LF to a CR-LF sequence.
The companion
ifhp print filter is used with the LPRng to provide
hardware level support for PostScript, PCL, text, and other printers.
It provides diagnostic and error information as well as
accounting information.
Obtain the latest or stable version of the ifhp filter source code from a
LPRng FTP Site.
Normally,
ifhp is installed together with the LPRng software.
The PostScript and PCL printer job languages are supported by most printer manufacturers. However, in order to have a job printed correctly the following steps must be taken.
ifhp filter sends the appropriate commands to the
printer to select these options.The ifhp filter uses the ifhp.conf database file
to determine the actions and commands
appropriate for various models of printers.
See the ifhp documentation for details about the
format and contents of this file.
The default printer used by the
ifhp filter is the HP LaserJet 4M Plus,
which supports PostScript, PCL, and PJL.
These defaults will also support a very wide variety of printers
that support both PostScript and PCL.
Now we will use the ifhp filter.
Find the path to the ifhp filter using the
find command as we did in the previous exercise.
Modify the printcap as shown below
and use lpc lpd to restart lpd.
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
# modify the path to ifhp appropriately
:if=/usr/local/libexec/filters/ifhp
Now reprint the file,
and then display /var/tmp/lp
using a text editor such as vi or emacs that shows
control characters:
h4: {141} % cp /dev/null /var/tmp/lp
h4: {142} % lpr /tmp/hi
h4: {143} % vi /var/tmp/lp
^[%-12345X@PJL
@PJL JOB NAME = "PID 405" DISPLAY = "papowell"
@PJL RDYMSG DISPLAY = "papowell"
@PJL USTATUSOFF
@PJL USTATUS JOB = ON
@PJL USTATUS DEVICE = ON
@PJL USTATUS PAGE = ON
@PJL USTATUS TIMED = 10
@PJL ENTER LANGUAGE = PCL
^]E^]&^]&k2G^]&s0C^]&l0O^]9^](s0P^](s10.00H^](s4099Thi
^]E^]%-12345X@PJL
@PJL RDYMSG DISPLAY = "papowell"
@PJL EOJ NAME = "PID 405"
@PJL USTATUSOFF
@PJL USTATUS JOB = ON
@PJL USTATUS DEVICE = ON
@PJL USTATUS PAGE = ON
@PJL USTATUS TIMED = 10
@PJL RDYMSG DISPLAY = ""
^[%-12345X
The output now contains all of the control sequences and setup codes needed to print a text file on the printer. For details on the exact meaning of each of these entries, see the PCL and PJL documentation.
There are some printers that only support PostScript.
We will show how to configure the ifhp
filter to recognize this.
First,
create the /tmp/one.ps PostScript file:
%!PS-Adobe-3.0
%% one page (i.e. - a page with a 1 on it)
/Courier
findfont 200 scalefont setfont
72 300 moveto
(1) show
showpage
This PostScript file starts with the standard PostScript
header -
%! or the acceptible alternative
\004%!.
If a PostScript file does not start with either of these
two character sequences,
the printer may behave in unexpected ways.
The ifhp filter will check to make sure that
the file starts with these sequences.
Modify the printcap as shown below
and use lpc lpd to restart lpd.
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
# modify the path to ifhp appropriately
:ifhp=model=ps
:if=/usr/local/libexec/filters/ifhp
Now print the file /tmp/one.ps,
and then display the output in /var/tmp/lp
using a text editor such as vi or emacs that shows
control characters:
h4: {141} % cp /dev/null /var/tmp/lp
h4: {142} % lpr /tmp/one.ps
h4: {143} % vi /var/tmp/lp
^D%!
%!PS-Adobe-3.0
%% one page (i.e. - a page with a 1 on it)
%%/Times-Roman
/Courier
findfont 200 scalefont setfont
72 300 moveto
(1) show
showpage
^D
As you can see,
the PostScript
^D (Control-D or \004) End of Job characters have been
prefixed and appended to the file.
This will ensure that the job will be handled properly by
the printer.
While PostScript is the most widely support Print Job Language,
there are large number of printers that support only PCL.
The ifhp filter has support for a PCL only printer.
Modify the printcap as shown below
and use lpc lpd to restart lpd.
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
# modify the path to ifhp appropriately
:ifhp=model=pclonly
:if=/usr/local/libexec/filters/ifhp
Now print the file /tmp/hi,
and then display the output in /var/tmp/lp
using a text editor such as vi or emacs that shows
control characters:
h4: {141} % cp /dev/null /var/tmp/lp
h4: {142} % lpr /tmp/hi
h4: {143} % vi /var/tmp/lp
^]E^]&^]&k2G^]&s0C^]&l0O^]9^](s0P^](s10.00H^](s4099Thi
^]E
The output now contains the necessary PCL start of job, format setup, and end of job sequences needed to print on a PCL printer.
A major cost in a low end printer printer is the microprocessor and memory which converts PCL or PostScript to a raster image. Most low end printers have the rasterization done on a users computer by a print driver program or utility, and then the raster image is transferred to the printer. The raster image is usually in a proprietary format.
If you have a PostScript file and want to print it on
one of these printers then you will need to use
GhostScript
or a similar program to do the conversion.
The following example shows one way to use GhostScript
with LPRng and is presented for instructional purposes.
It is not recommended method;
please consult the ifhp
documentation for a more robust method that
handles text files and other non-PostScript files correctly.
Suppose that we have an HP DeskJet 660C printer and want to print PostScript files. Enter the following command to see what devices your version of GhostScript is configured for.
h4: {349} % gs --help
GNU Ghostscript 5.10 (1998-12-17)
Copyright (C) 1997 Aladdin Enterprises, Menlo Park, CA. All rights reserved.
Usage: gs [switches] [file1.ps file2.ps ...]
Most frequently used switches: (you can use # in place of =)
-dNOPAUSE no pause after page | -q `quiet', fewer messages
-g<width>x<height> page size in pixels | -r<res> pixels/inch resolution
-sDEVICE=<devname> select device | -dBATCH exit after last file
-sOutputFile=<file> select output file: - for stdout, |command for pipe,
embed %d or %ld for page #
Input formats: PostScript PostScriptLevel1 PostScriptLevel2 PDF
Available devices:
x11 x11alpha x11cmyk x11mono sxlcrt ap3250 appledmp bj10e bj200 bjc600
bjc800 cdeskjet cdjcolor cdjmono cdj500 cdj550 cp50 declj250 deskjet
djet500c dnj650c epson eps9mid eps9high epsonc ibmpro imagen iwhi iwlo
iwlq jetp3852 laserjet la50 la70 la75 la75plus lbp8 lips3 ln03 lj250
ljet2p ljet3 ljet3d ljet4 lj4dith ljetplus lp2563 m8510 necp6 oce9050
oki182 paintjet pj pjetxl pjxl pjxl300 r4081 sj48 st800 stcolor t4693d2
t4693d4 t4693d8 tek4696 xes dfaxhigh dfaxlow faxg3 faxg32d faxg4 tiffcrle
tiffg3 tiffg32d tiffg4 bmpmono bmp16 bmp256 bmp16m cgmmono cgm8 cgm24 cif
mgrmono mgrgray2 mgrgray4 mgrgray8 mgr4 mgr8 pcxmono pcxgray pcx16 pcx256
pcx24b psmono pbm pbmraw pgm pgmraw pgnm pgnmraw pnm pnmraw ppm ppmraw
sgirgb tifflzw tiffpack tiff12nc tiff24nc bit bitrgb bitcmyk pngmono
pnggray png16 png256 png16m pdfwrite nullpage
Search path:
. : /usr/share/ghostscript/5.10 :
/usr/share/ghostscript/fonts
For more information, see /usr/share/ghostscript/5.10/doc/use.txt.
Report bugs to ghost@aladdin.com; use the form in bug-form.txt.
Next we consult the GhostScript printer support documentation
at
http://www.cs.wisc.edu/~ghost/printer.html
or the documentation shipped with the GhostScript distribution
and find that the cd550 driver supports
a wide range of printers:
cdj550
HP DeskJet 550C (3.53)
HP DeskJet 560C (3.53)
HP DeskJet 600 (5.10) black (1bit/pixel) and colour (32bit/pixel)
HP DeskJet 660C (3.53)
HP DeskJet 660C (5.10)
HP DeskJet 682C (4.01) Use gamma=0.3
HP DeskJet 683C (3.33, 4.03)
HP DeskJet 693C (4.03) 24bit/pixel Is this a CMY or a CMYK printer?
Probably CMYK in which case 32bit/pixel can be used.
HP DeskJet 694C (5.03) Works in colour. 690C, 692C, 693C and 694C
are all software variations.
HP DeskJet 695C (5.50) Works in colour.
HP DeskJet 850 (3.53) 300dpi CMYK (32bit/pixel) See also cdj850.
HP DeskJet 870Cse (4.03) (16 or 32 bits/pixel) Use ljet4 device for B/W.
See also cdj850.
HP DeskJet 895Cxi (5.03, 5.50) Does not work with cdj850.
HP DeskJet 970 (5.50) Some pages contain small black boxes,
lines or missing characters.
HP OfficeJet 590 (5.50)
Olivetti jp450 (5.50)
Xerox XJ6C (5.50)
May work with other HP Colour DeskJet printers that use a CMYK ink cartridge.
Any printer that works with this device will probably work in black and white
with djet500 and cdjmono.
Create the /tmp/testgs file with the following contents:
#!/bin/sh
# /tmp/testgs - set the path to gs (GhostScript) appropriately
exec /usr/bin/gs -q -dSAFER -dBATCH -sDEVICE=cdj550 -sOutputFile=- -
Run the following commands to convert the /tmp/one.ps
file
and view the output with a text editor:
h4: {351} % /tmp/testgs </tmp/one.ps >/tmp/out.img
h4: {352} % vi /tmp/out.img
^[*rbC^[*t300R^[&l2aolE^[*o1d2Q....
Modify the lp printcap as shown below,
and use lpc reread to restart the server.
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
:if=/tmp/testgs
Print the file /tmp/one.ps,
and then display the output in /var/tmp/lp
using a text editor such as vi or emacs that shows
control characters:
h4: {141} % cp /dev/null /var/tmp/lp
h4: {142} % lpr /tmp/one.ps
h4: {143} % vi /var/tmp/lp
^[*rbC^[*t300R^[&l2aolE^[*o1d2Q....
In the previous section we saw how to set up a
filter shell script
so that we could convert PostScript files.
We can modify this script to extract information from the
PRINTCAP environment variable,
and make our script much more powerful.
Modify the /tmp/testgs script from the previous
example so that it contains:
#!/bin/sh
# /tmp/testgs - set the path to gs (GhostScript) appropriately
device=`echo $PRINTCAP_ENTRY \
|/usr/bin/sed -n 's/.*:device=\(.*\)/\1/p' `
if [ "X$device" = "X" ] ; then device=epson; fi
# print the device value for status information
echo DEVICE $device >&2
exec /usr/bin/gs -q -dSAFER -dBATCH -sDEVICE=$device -sOutputFile=- -
Modify the lp printcap as shown below,
and use lpc reread to restart the server.
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
:if=/tmp/testgs
Print the file /tmp/one.ps,
display the lpq status as shown below,
and then display the output in /var/tmp/lp
using a text editor such as vi or emacs that shows
control characters:
h4: {146} % cp /dev/null /var/tmp/lp
h4: {147} % lpr /tmp/one.ps
h4: {148} % lpq
....
Status: IF filter msg - 'DEVICE cdj550' at 20:31:34.386
h4: {143} % vi /var/tmp/lp
^[*rbC^[*t300R^[&l2aolE^[*o1d2Q....
Using this technique you can use one filter script
to handle a wide variety of printers.
This is essentially the method used by the
ifhp filter to determine the printer type.
While the previous method is useful when only PostScript files
are being printed,
it is not very robust.
The ifhp filter provides the
ghostscript printer configuration
which can be used as shown below.
The ifhp filter will invoke GhostScript with the
indicated options
and handle errors and setup in an appropriate manner.
The a2ps program is used to do text to PostScript conversion
in this configuration.
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
:ifhp=model=ghostscript,device=epson,resolution=-r240x72
# gs ... -sDEVICE=$device $resolution ...
:if=/usr/local/libexec/filters/ifhp
Modify the lp printcap as shown below,
and use lpc reread to restart the server.
lp:sd=/var/spool/lpd/%P
:force_localhost
:lp=/var/tmp/lp
:ifhp=model=ghostscript,device=cdj550
:if=/usr/local/libexec/filters/ifhp
Print the file /tmp/one.ps,
display the lpq status as shown below,
and then display the output in /var/tmp/lp
using a text editor such as vi or emacs that shows
control characters:
h4: {146} % cp /dev/null /var/tmp/lp
h4: {147} % lpr /tmp/one.ps
h4: {148} % lpq
....
Filter_status: initial job type 'POSTSCRIPT' at 03:50:38.141
Filter_status: job type 'raw', converter '/usr/bin/gs
-dSAFER -dBATCH -q -sDEVICE=cdj550 -sOutputFile=- - ' at 03:50:38.141
Filter_status: started converter '/usr/bin/gs
-dSAFER -dBATCH -q -sDEVICE=cdj550 -sOutputFile=- - ' at 03:50:38.146
Filter_status: converter done, output 1251 bytes at 03:50:38.790
h4: {143} % vi /var/tmp/lp
^[*rbC^[*t300R^[&l2aolE^[*o1d2Q....
Next, print the file /tmp/hi,
display the lpq status as shown below,
and then display the output in /var/tmp/lp
using a text editor such as vi or emacs that shows
control characters:
h4: {146} % cp /dev/null /var/tmp/lp
h4: {147} % lpr /tmp/hi
h4: {148} % lpq
....
Filter_status: job type 'raw', converter
'/usr/bin/a2ps -q -B -1 -M Letter --borders=no -o-
| /usr/bin/gs -dSAFER -dBATCH -q
-sDEVICE=cdj550 -sOutputFile=- - ' at 03:52:12.423
Filter_status: started converter
'/usr/bin/a2ps -q -B -1 -M Letter --borders=no -o-
| /usr/bin/gs -dSAFER -dBATCH -q
-sDEVICE=cdj550 -sOutputFile=- - ' at 03:52:12.426
Filter_status: converter done, output 183 bytes at 03:52:13.206
h4: {143} % vi /var/tmp/lp
^[*rbC^[*t300R^[&l2aolE^[*o1d2Q....
As shown by the lpq status above,
the a2ps program was invoked to convert a text file
into PostScript,
which was then rasterized by GhostScript.