Printing in the Solaris Environment: Using the LP Subsystem
June 2000
This collection of tips and ideas is
written for developers of printing solutions for the SolarisTM operating environment and is made up of two parts:
Contents
Functions Provided by the LP Subsystem
Printing Integration Under the LP Service
LP Server Scalability
Application developers working with
printing functions generally want to perform two simple tasks:
- Query a list of available printers
- Submit a print job to a selected printer
Call Level Interfaces
Solaris contains no Application
Programmer Interfaces (APIs) for printing. It does, however, contain
three printing-related Call Level Interfaces (CLIs).
Lpstat(1) queries the status of a printer
lp(1) submits print jobs
cancel(1) cancels print jobs
The complete definitions for these CLIs
can be found in the corresponding Solaris man pages.
How to Query Available Printers
To generate a list of available
printers, use the lpstat(1)
interface.
There are many options available in
lpstat(1)
and, because they operate and report across all configured
printers, the list may take a long time to complete. The fastest way
to gather a simple list of printers is to use the /usr/bin/lpstat
v command, which performs a naming lookup and
returns the results without querying the servers.
Example
The following example generates a list
of printers by running the lpstat
command with the popen(3C)
function and then parsing the input stream.
/*
* Copyright (C) 1998, by Sun Microsystems, Inc.
* All Rights Reserved
*/
#pragma ident "@(#)popen_printer_list.c 1.2 98/05/11 SMI"
#include <stdio.h>
#include <strings.h>
main(int ac, char *av[])
{
FILE *fp;
if ((fp = popen("/usr/bin/lpstat -v", "r+")) != NULL) {
char buf[BUFSIZ];
while (fgets(buf, sizeof (buf), fp) != NULL) {
strtok(buf, ": "); /* eat system/device */
strtok(NULL, ": "); /* eat for */
printf("printer: %s\n", strtok(NULL, ": "));
}
fclose(fp);
}
}
How to Submit a Print Job
To submit a print job, use the lp(1)
interface.
Like the lpstat(1)
interface, lp(1)
also has many options, but only a few of them are important when
submitting a job from an application. When the lp
command is invoked, the instance of the command parses the command
line arguments and builds a print request. This request is actually
derived from the command line options, configuration data, and the
data to be printed. Many of the lp(1)
command line options set request characteristics, such as data type,
filter options, reporting actions, etc.
Job Data
By default, the job data is printed by
reference when an lp(1)
request is made. This means that when an application submits data to
print, the job data is kept in a temporary file, it must tell lp(1)
to copy the data before printing. If the data is piped to lp(1),
it will automatically be placed in a temporary file by lp(1).
PostScript versus Text
The LP server also checks to determine
if the print data is in PostScriptTM format or not.
If the data begins with %!
or ^d%!,
the print spooler considers the data to be PostScript. Otherwise,
the data is considered to be simple text.
To print PostScript data that does
not begin with %!
or ^d%!,
add the -T
postscript option to the command line. For example, %
lp -T postscript file.
To print PostScript data as simple
text, add the -T
simple option to the command line. For example,
% lp
-T simple file.
Example
The following example shows some ways
to submit print jobs programatically to the LP Subsystem in Solaris.
The example is complete enough to run, but should be tailored to your
needs. Please note that:
snprintf(3c)
is used to avoid buffer overflow problems. This interface is
available starting with the Solaris 2.6 release.
popen(3C)
can fail when file descriptors 0-255 are in use because of file
pointer limitation. If your application uses a large number of file
descriptors, you may want to run lp
using pipe(2),
fork(2),
and exec(2),
or simply using system(),
depending on your needs. Actually, popen(3C)
and system(3S)
should only be used with great care since they use a shell to
process the command that has been supplied.
/*
* Copyright (C) 1998, by Sun Microsystems, Inc.
* All Rights Reserved
*/
#pragma ident "@(#)popen_submit.c 1.2 98/05/11 SMI"
#include
#include
/*
* Assuming that the command accepts any user defined data, it is a good
* idea to replace any shell meta characters that might breakup the command
* and/or allow the user to run something of their own. This is particularly
* important if the program is SUID.
*/
void remove_shell_meta_characters(char *command)
{
char *ptr = command;
while ((ptr = strpbrk(ptr, " &;|>^$\\")) != NULL)
*ptr =
}
/*
* This function returns a file pointer that can be used to send output
* to. Once the file pointer is closed, the job is submitted.
*/
FILE * fp_submit(char *printer)
{
char command[BUFSIZ];
FILE *fp;
snprintf(command, sizeof (command), "/usr/bin/lp -s -d %s", printer);
remove_shell_meta_characters(command);
return (popen(command, "w+"));
}
/*
* This function submits a file to print
*/
int filename_submit(char *printer, char *file)
{
char command[BUFSIZ]
snprintf(command, sizeof (command), "/usr/bin/lp -s -c -d %s %s",
printer, file);
remove_shell_meta_characters(command);
return (system(command));
}
/*
* sample job submission from an application
*/
main(int ac, char *av[])
{
char *printer = av[1];
char *file = av[2];
FILE *fp;
filename_submit(printer, file);
if ((fp = fp_submit(printer)) != NULL) {
fprintf(fp, "this is printing via a file pointer\n");
fclose(fp);
}
}
This section provides a more detailed
description of LP server facilities, operation, and integration than
that found in the Solaris Administrator's Guide.
Background
The LP service is the core print
functionality in the Solaris operating environment. The LP service
changed considerably between the releases of Solaris 2.5.1 and
Solaris 2.6. The changes between these releases include a significant
architectural shift, as well as a philosophical shift, including the
reimplementation, replacement, or elimination of faulty services with
newer, more flexible and more efficient components. These changes are
primarily centered around client-side functionality and networking
support.
As of this writing, changes to the LP
server (lpsched)
have basically been completed. It is this server that is being
highlighted here.
LP Server Flow: Releases Prior to Solaris 2.6
Following is a flow diagram of lp
running under pre-Solaris 2.6 releases.

Note: SAF = Service Access
Facility
LP Server Flow: Releases Starting with Solaris 2.6
Following is a flow diagram of lp
running under Solaris 2.6.
Notes:
- The client begins at
lp.
The server begins at inetd
(via RFC-1179+).
- The spooler begins at
bsd_lpsched,
which is a shared object.
In.lpd
is started from inetd.
lp.tell
is used to communicate with the lp
server.
- Solaris 2.6 does not contain a
BSD-style print system. It contains a SYSV-style
lp
server with software to take requests using lpd
protocol. The server is very much like the SYSV.
LP
Server Functions
The primary purpose of the Solaris LP
server is to provide print spooling to users for dedicated print
devices. This is done through the cooperation of a number of
facilities under a single process, including:
- A specialized queue manager
- A filtering (data transformation/matching) interface
- A back-end (printer/communication) interface
- A faulting interface
- An alerting/notification interface
- A media management interface.
To understand how the various LP server
components work together, look at the following example, which starts
with job submission.
When you make a print request, the
requester contacts the LP server (lpsched),
with a copy of the print request. The first thing the scheduler
determines is if the request can be handled by validating that the
requester has access to the destination and that the data contained
in the request can be transformed into a format that the destination
can handle. Once the request has been validated, the server accepts
the request and informs the requester that it has done so. From
there, the real work begins. If the request is already in a format
that is acceptable to the destination device, or if the
transformation to an acceptable format only requires fast filtering,
then the job is placed in the destination's queue for printing.
However, if the transformation requires a slow filter to perform the
transformation, the print job is placed in a filtering queue so it
can be transformed before being queued to the destination.
Once the job reaches the top of the
destinations print queue, the scheduler (lpsched)
starts a child process to manage the print job. This child process in
turn opens and locks the print device and starts a child process to
pass this open device and run the interface script. It is the
interface script that actually runs any fast filters and completes
processing of the job. At any time during this processing, the
interface script can detect and report error conditions and recovery
from error conditions. This information is reported back to the
scheduler using lp.tell.
Upon receipt of an error condition from the interface script, the
scheduler will use the configured alerting mechanism for the
destination to alert the administrator or user. Upon receipt of an
"all clear" from the interface script, the scheduler will
terminate its alerting and continue processing normally.
Some points to note about lp:
There is an lp parsing
problem when using the -o
option. The problem is that the -o
option is not parsed correctly when one of the arguments for the -o
option contains a space. The Sun BUGID is 4077583. The workaround is
to not allow spaces in arguments passed through the -o
option. The fix is in Solaris 7.
When adding a printer queue to
lpsched,
the printer type must be defined in the terminfo
database. The lpadmin
-p foo -T lex7000
command assumes that lex7000
is already defined in the terminfo
database. You should use tic(1M)
to define the type in the terminfo
database.
Printer Type is an implied Content
Type, but still needs a valid Content Type. Note that admintool
has been known to set Content Type to some default value for content
types that it does not recognize; this admintool
bug has been resolved in Solaris 8.
Unsupported Techniques
A developer who would like to have
greater control over lp
might be tempted to rename /usr/bin/lp
and have an lp_wrapper
script call it under certain conditions. This renders the printing
software unsupported.
For example, you might rename
/usr/bin/lp
to /usr/bin/lp/system_lp
and then create another shell script named /usr/bin/lp
that calls /usr/bin/lp/system_lp
in order to set job attributes dynamically at time of job submission.
This method is strongly discouraged since the binary contained in the
file /usr/bin/lp
looks at argv[0]
to determine how to parse arguments and function. Also, there are
other files in the file system that are symbolic links to this file
and expect the original binary to be in that file.
The preferred method is to use
supported interfaces, such as filters, or to write a wrapper and have
the user call the wrapper instead of lp(1).
LP
Server Interfaces
Filtering Interface
The Solaris LP server supports two
different types of print filters.
Slow filters are to be used when
the data transformation is likely to take longer than it will take
to print the data. This filtering is run "out-of-band"
before the print request is actually scheduled to print, so the
printer does not have to wait for the data to be transformed.
Fast filters are to be used when
direct interaction with the printer is required, or when the
transformation is not likely to be a bottleneck. Fast filters run
in-line, on the way to the printer.
Filters are configured under LP using
the lpfilter(1M)
command. This command takes in a filter description file and
configures a filter under lpsched.
The filter description file and its format are described in the man
pages.
When adding filters to a Solaris LP
server, there are a couple of important things to understand.
First and foremost, a filter will
only run if it is required to run for the job to print.
Second, filters may be required to
run either to convert data to a format acceptable to a printer or to
fold in special required options called modes.
Note: The online documentation
states that a print filter program cannot be used as a filter if it
references external files since the LP print service does not
recognize references to 'include' files from a filter program. This
statement applies to word processing programs such as troff
and nroff,
since they require other files or macros to complete their
processing. This does not apply to shared libraries.
For further information on writing and
integrating filters, see the lpfilter(1M)
man page or the System Administrators Guide.
Back-End Interface
One very important interface in the
Solaris LP server is its back-end interface. This interface
ultimately runs and communicates the print job to the printer. The
interface itself is simply a call-out to an external program. This
program can be written in any language, whether compiled or
interpreted. The most common method of implementing this program is
using a shell script, and because of this, the interface is generally
referred to as an interface script.
Interface Script Inputs
An interface script has a variety of
inputs. The first set of inputs is the command line arguments. These
arguments supply the printer name, request ID, user, title, number of
copies, an options list, and the list of files to print. With the
exception of the options list, each of these pieces of information is
supplied in a separate command line argument. The options list is
supplied as a single command line argument, but it can contain
multiple options.
Another input to the interface script
is the LP server's calling environment. The LP server takes great
care when constructing a calling environment for an interface script
to run in. This environment contains information about the character
set the printer is to use, any fast filter that should be run in-line
to the printer, localization information, printer type, time zone
information, and a token for communicating status back to the
scheduler. Each of these pieces of information is stored in an
environment variable that can be accessed using getenv(3C)
in C or $VARIABLE
in most shell languages.
A critical input to the interface
script is actually the set of open files descriptors. When lpsched
starts the interface script, it opens up the actual print device for
the script and passes the opened device to the script as standard
output. Standard error is actually an output. It is a back channel to
lpsched
that can be used to communicate error information back to the
scheduler.
The last input to the interface script
is an asynchronous input. That is, the input can be supplied to the
interface script at any time while it is running. This input is
supplied using signals. Actually, the only signal that will be
supplied from the scheduler is the SIGTERM
(15) signal. This signal is supplied when the scheduler
wants the interface script to stop what it is doing and terminate. It
is usually supplied when the scheduler is being shut down or someone
is attempting to perform maintenance on the printer.
Interface Script Outputs
In addition to the above inputs, the
interface script can communicate information back to the scheduler
using two outputs.
The first output is the ability to
send text messages back to the scheduler using standard error. These
messages are used, verbatim, as the printer status message for
lpstat.
The second output is the exit code
of the interface script. The exit status of the interface script
communicates whether the script completed successfully. If it
failed, the job should either be reprinted or tossed out.
For further information on interface scripts, how they work,
and how to write them, see
docs.sun.com,
and the System Administrators Guide. Sample interface scripts
are located in /usr/lib/lp/model/.
Faulting Interface
As mentioned above, when an error is
detected while passing a print job to an output device, the interface
script reports this error back to the scheduler. This is done through
the faulting interface. The faulting interface is largely made up of
a single program, lp.tell,
which is external to the scheduler.
The lp.tell program sends messages back from the interface
script to lpsched
using the scheduler's communication FIFO. The messages contain a type
(fault, clear fault), destination, key, and some amount of text.
These messages are what trigger the alerting/notification process in
the scheduler. As stated, lp.tell
is the program that passes these messages to the scheduler. lp.tell
is a relatively simple program. It is placed in the process pipeline
by an interface script while communicating with the output device.
It reads back the status messages from the communication program
(described below), makes a determination as to whether the message is
an error or not, and sends a message to the scheduler.
Alert/Notification Interface
Upon detecting an error condition or
when user interaction is required, the LP server will attempt to
perform a notification. This notification is a simple call-out to an
external program and is configured under LP using the lpadmin(1M)
command. Since alerting is done using a call-out, this program can
perform any task. Depending on the configuration, the notification
can occur a single time or at x minute intervals while the
error condition or requirement for user interaction persists.
Note: When lp.cat
sends a message to lp.tell
that the printer is okay and when lp.tell
sends a clear fault message to lpsched,
this acknowledges that the lp
alert message from the filter has been handled. This sequence shuts
down the alert.
For further information on writing and
integrating notification programs, see the lpadmin(1M)
man page, or the System Administrators Guide.
Forms (Preprinted Media) Interface
The LP server comes with built-in
support for arbitrary preprinted media. This media support, referred
to as form support, allows end users to select the media they require
to be loaded in the printer at the time a print job is printed. By
default, all print jobs have no media selection when submitted.
A user requesting a type of media must
supply either a media or form name by using the -f
option to lp
when submitting the job. Also, the server for the printer must have a
definition for the requested media, the destination must be allowed
to mount the media, and the user must be allowed to use the media. If
the media is not present on the destination at the time the job is
ready to print, the LP server will hold the job and request an
operator to mount the media on the destination. Once mounted, the
operator must use lpadmin(1M)
to notify the LP Server that the media has been placed in the
printer.
Some specific points to note on the use
of forms follows:
lpforms -f form-name -A alert-type [ -P paper-name [ -d ] ]
[ -Q requests ] [ -W minutes ]
In the form description, the
character set can be used to deal with changing ink cartridges for
different job types. For notification of changing empty cartridges,
the interface script should pass back a fault and the lp
alert mechanism (specified by the -A
option) should notify the user of the fault.
When a media change is requested
at the printer, lpsched
usually requires confirmation. This confirmation is passed via
lpadmin(1M).
For further information on integrating
media/forms support, see the lpadmin(1M)
man page, lpforms(1M)
man page, or the System Administrators Guide.
Communication Interfaces
Although communication with the printer
is handled via the back-end interface script, the interface scripts
supplied with Solaris run one of the three different programs
(postio, lp.cat, and netpr)
to do the actual communication. These three programs send the data to
be printed to the output device and gather status where possible. The
program that is selected to communicate with the printer depends on
the interface script being run and the type of printer being
communicated with.
postio
The first program, postio,
is used to communicate with PostScript (PS, PSR) printers attached to
a serial port or parallel port (BPP, SPIF, ECPP). The program is
configured under the standard interface script and reports errors
back to lpsched
using this same interface script.
Which errors it reports depends largely
on the connectivity to the printer. If the printer is connected to a
serial port, postio
will send a ^t
PostScript status request to the printer and retrieve the printer's
response before each block of data is sent to the printer. This has
the effect of providing for any error that the printer can generate.
If the printer is connected to a parallel port, postio
assumes that the connection to the printer is unidirectional. Since
the connection is believed to be unidirectional, the results of a ^t
status request cannot be retrieved. Instead, postio
checks the pin status from the parallel port itself. This allows
postio
to discern and return the following error conditions: power off,
off-line, paper out, and busy.
lp.cat
The lp.cat
program is used to communicate with all other locally attached
printers. It is also configured to run under the standard interface
script and report back errors using this script.
Again, the errors detected and reported
back depend on the type of connection to the printer. If the printer
is connected to a parallel port, it can return the same types of
errors that postio
does when it communicates with a printer on a parallel port. If the
printer is hooked up to a serial port, the reporting is essentially
limited to "can't communicate with printer". This is
because many serially connected printers don't support any form of
status request. With the exception of PostScript printers, those that
do support a status request do so in a vendor-specific manner.
There is one small anomaly to watch out
for. A serial port is not always a serial port. In some cases
developers build elaborate mechanisms to make their pie,
pty,
socket,
driver,
etc. appear to the software as a tty.
Some simply push the ldterm
streams modules on the stack. If the device responds correctly to
isatty(3C),
it is considered a serial port by postio
and lp.cat.
Both postio
and lp.cat
check the endpoint type using the following decision flow:
Is it a BPP parallel port?
Use the BPP port pin status.
Is it an ECPP parallel port?
Use the ECPP port status.
Is it a SPIF parallel port?
Use the SPIF port status.
Is it a tty?
Use ^t
status under postio
(act like a dumb connection under lp.cat).
Else...
It must be dumb.
Just send data in blocks. If it hangs, report an error.
netpr
The final program used to communicate
with printers is netpr,
which is available in releases of Solaris 2.6 and later, allows the
LP system to communicate with network attached printers. The
netpr
program is capable of sending job data to printers that either
implement the BSD Print protocol (RFC-1179) or taking a straight TCP
stream of print data. It is configured to run under the netstandard
interface script, which is also available in releases of Solaris 2.6
and later.
As with lp.cat's
ability to communicate over a serial port, this program's ability to
detect and report more than rudimentary error conditions is limited.
Again, status results returned from network-attached printers are
generally returned using vendor-specific mechanisms or formats, which
limits netprs
error reporting to transport-related errors only.
Using Solaris Private Printing Interfaces
As mentioned above, the only public
printing-related interfaces under Solaris are lp(1),
lpstat(1),
and cancel(1),
which are described in the SVID or XPG4. These interfaces include
client-side functionality only. That being said, any of the other
interfaces described here can be used to integrate support under the
LP system with the understanding that the interfaces will be changed
or removed eventually (as was first stated with the release of
Solaris 2.3).
The following examples illustrate the
use of the various LP components discussed above. Company pPrint is
working on a host-based routing information protocol (RIP) solution
for its inkjet printer products. A host-based RIP is a utility where
the input is a job (for example, a PostScript file) that gets
interpreted and converted into some binary form specific to the
target printer. The output from the host-based RIP program is then
submitted to the printer.
Double Queue Solution
You should strictly use supported
public interfaces and set up a front_filter
that will route the job on the fly. With the output from the RIP, you
might call lp
again to resubmit the job rather than putting the routed result back
into the lp
spool directory since the hierarchy of the lp
spool directory, /var/spool/lp/,
is considered private, and known only to the LP print service. The
design might look like the following figure.
The problem with this design is that
the print queue for the first lp
call is different from the print queue for the second lp
call. When the job leaves the first print queue, from an end-user
perspective, the job appears to have been printed as it no longer
appears in the first queue in which the job was submitted to. But,
the job may not necessarily have been printed as it could have gotten
stuck in the second queue and the end-user will not know this, nor
should they.
Single Queue Solution
One solution to avoid double queuing is
to have a fast_filter
that calls the RIP service via a pipe and have the routed data stored
back into lps
spool temp directory, /var/spool/lp/tmp.
This solution eliminates the need to call lp
again to requeue the job. The design might look like the following
figure.
The hierarchy of /var/spool/lp
is private, Sun's Solaris software group does not approve of its use.
However, when using the spool, precede the jobname with f
and have the RIP service daemon write the file and pass the name back
to the fast_filter.
A benefit to using /var/spool/lp
is that lp
will automatically cleanup when it is done printing. This solution
only works for local host-based RIP service spooling.
The number of printers the Solaris LP
server can support is scalable and is up to the amount of available
system resources (that is, virtual memory and process table space).
Prior to the release of Solaris 2.6,
the number of active printers that can be reliably supported was
effectively around 125, depending on a number of factors. The
printing system made heavy use of file pointers via fopen(3S)
and fdopen(3S)
and required file descriptors in the range of 0-255. In releases of
Solaris 2.6 and later, this dependency on file pointers has been
removed and a number of other improvements have been implemented.
For example in testing, an SS20 with
256Mb of VM was found to be able to support around 1000 printers
before reaching the limits of its system resources. In order to
support more than 2000 printers, it is necessary to use the -p
flag when starting lpsched(1M).
The number of remote printers
supported should be unlimited, since they only use system resources
when an active request (print/xfer job, status, cancel) is being
processed.
For further information, see topics on
configuring network-attached printers in the online documentation.
Using GhostScript
The Solaris Software Companion CD distributed freely with Solaris 8
contains a pre-compiled and pre-packaged version of GhostScript as a
convenience to Solaris users. This package also contains an interface
script that allows easy configuration of GhostScript under the LP
system.
June 2000