Next Previous Contents

8. Using the Printcap Database

As described in the Print Spooling Overview, the heart of the LPRng system is information in the /etc/printcap file. The printcap information specifies:

  1. The print queues available to users.
  2. How client programs communicate with the lpc print server.
  3. The configuration, location, and other information for each print queue on the print server.
  4. How the lpd server processes jobs in each print queue.

In order to explain a complex subject, we will start with a set of simple printer configurations, and explain the purpose and effect of each entry in the printcap.

For details about individual printcap options, see the printcap(5) man page from the LPRng distribution, or use the Index To All The Configuration and Printcap Options to find a specific printcap option and its effects.

8.1 Simple Client Printcap Entry

Options used:

I'll use this simple example to explain the basics of the LPRng printcap format and introduce some of the LPRng network configuration options. Here is a simple printcap file used to provide client programs (lpr, lprm, etc) with remote printer and server information.

# printer lp1
lp1|printer1
  :rm=localhost
# printer lp2 with continuation
lp2:\
  :lp=pr@10.0.0.1:client
# printcap lp3, to printer pr, with overrides
lp3:rp=pr:rm=hpprinter.astart.com
  :force_localhost@
# Simplest possible printcap entry - defaults for everything
lp4
  1. Lines starting with a # sign are comments, and all leading and trailing whitespace, i.e. - spaces, tabs, etc, are ignored. Empty lines are ignored as well.
  2. A printcap entry starts with the printcap entry name, followed by one or more aliases, followed by one or more options. In the above example we have three printcap entries: lp1 with an alias printer1 and lp2, lp3, and lp4 with no aliases.
  3. Aliases start with the | character and options with the : character; tabs and spaces before and after the | or : characters and at the start and end of lines are ignored. You can use backslash (tt/\/) at the end of a line to create a multi-line value for an option. The backslash will cause the next line to be appended to the current line; watch out for comments and ends of printcap entries if you use this facility. As you can see from the example, there is no Name printcap entry - this is part of the cm option on the previous line.
  4. Options take the form of a keyword/value pair, i.e.-
    :option=value
    :option#value   (legacy, not advised for new systems)
    :option
    :option@
    
  5. Option names are case insensitive, but option values are not. While Ts and ts are the same option name, ts=Testing and ts=testing have their case preserved. A string or integer value is specified by option=value or option#value.
  6. The use of the legacy option#value form is NOT recommended as some preprocessors and database systems will treat # as the start of a comment and delete the remainder of the line. This has caused great consternation for sysadmins who wonder why their NIS distributed printcap entries have been mysteriously truncated.
  7. If you want to set a string option to empty value, use option=. The option will set it to 1. If an option value contains a colon, then use the C (or Perl or Tck/Tk) string escape \072 to represent the value.
  8. Boolean options are set TRUE (1) if no value follows the keyword and FALSE (0) by appending a @. For example sh will set sh to TRUE and sh@ to FALSE.

There may be multiple options on the same line, separated by colons. However, this does make the file less readable. The next tip was supplied by James H. Young <jhy@gsu.edu>:

My personal preference for readability is to always put each option on its own line. Putting each option on its own line is worth the trouble even though it detracts from the usability of certain grepping techniques when trying to maintain these types of files.

Now let's examine the first printcap entry in detail. It is reproduced here for convenience:

# printer lp1
lp1|printer1
  :rm=localhost
  1. We start with a comment, followed by the printcap entry name and and alias. Aliases are useful when you want to refer to a single printer or print queue by different names. This can be useful in advanced printcap and print queue setups. By default, the remote printer name is the printcap entry name.
  2. The rm (remote machine or host) option specifies the name or IP address of the lpd host running lpd. In this example the remote host is localhost or the machine that the client is running on and we assume that the lpd server is running on the localhost. Thus, we would communicate with printer lp1@localhost.

Let's look at the next printcap entry:

# printer lp2 with continuation
lp2:\
  :lp=pr@10.0.0.1:client
  1. The lp2 printcap entry illustrates the use (and abuse) of the \ continuation. If you think about this, we have really defined a printcap entry of the form:
    lp2: :lp=pr@10.0.0.1:client
    

    Luckily, LPRng ignores empty options like : :. While it is strongly recommended that \ be avoided it may be necessary for compatiblitye with other system utilities.

  2. The lp=pr@10.0.0.1 option is an alternate way to specify a remote queue and server. If the force_localhost default is being used, then the LPRng clients will ignore the 10.0.0.1 address and still connect to pr@localhost. There is further discussion about this in the next section.
  3. The client option explicitly labels client only printcap information. The lpd server will ignore any printcap with the client option. When constructing complex printcaps, this option is used to keep ensure that you have consistent printcap information.
The following printcap entry shows how to override the force_localhost default, and force the LPRng clients to connect directly to a remote server:
lp3:rp=pr:rm=hpprinter.astart.com
  :force_localhost@
  1. The rp= (remote printer) remote print queue name to used when sending commands to the lpd print server.
  2. The force_localhost@ option is an example of a flag option. The @ sets the option value to 0 (false). We set force_localhost to false, which now allows the LPRng clients to connect directly to the specified remote printer. In this example, the hpprinter.astart.com could be a HP LaserJet Printer with a JetDirect interface, which supports the RFC1179 protocol.
  3. One disadvantages of sending a job directly to a printer using the above method is that lpr program will not terminate or exit until all of the files have been transferred to the printer, and this may take a long time as the printer processes the files as they are received.

Now let's look at the last printcap entry:

# Simplest possible printcap entry - defaults for everything
lp4

The last example is the simplest possible printcap entry. This will cause LPRng clients to use the default values for everything. The printer will be lp4, i.e. - the name of the printcap, and the server will be localhost if force_localhost is set, or the value of the default_remote_host configuration option if it is not.

8.2 Simple Server Printcap Example

Options used:

The previous section discussed printcap entries for use by the client programs. Now we will discuss printcap entries for use by the lpd server. In simple configurations or when we have the force_localhost option enabled we can use the same printcap for both LPRng clients and the lpd server.

# Local ASCII printer
lp1|printer
  :server
  :cm=Dumb printer
  :lp=/dev/lp1
  :sd=/var/spool/lpd/lp1
  :lf=log:af=acct
  :if=/usr/local/sbin/lpf
  :mx#0:sh:sf

  1. The printcap entry name is lp1. This information will be displayed when requesting status information using the lpq program.
  2. The printer alias. This allows a single spool queue to have multiple names.
  3. The server corresponds to the client flag and indicates this printcap entry is for the lpd server only.
  4. The cm field supplies a information field for lpq (printer status) output.
  5. The lp value specifies the destination file, device or remote spool queue to which data is sent. In this example it is the device /dev/lp1. By default, IO devices are opened for write-only operation.
  6. The sd specifies the spool directory where print job files are stored until they are printed.
  7. The lf and af options specify the names for the log and accounting files, respectively. These have the default values log and acct respectively, and if not absolute pathnames are relative to the spool queue directory. A log file is highly recommended. If these files don't exist, they will not be created, and no logging or accounting will be done. You will need to create them manually (e.g., by using touch) or by using the checkpc program.
  8. The if option specifies a filter program to be used for jobs with the f or default job format format. Filters and print formats are discussed in section Filters. The lpf filter translates LF (line feed) to CR/LF (carriage return/line feed) sequences, eliminating staircase output.
  9. mx indicates the maximum file size for a print job. Specifying 0 means that there is no limit.
  10. The sh suppress headers flag will suppress printing banner pages.
  11. The sf suppress form feeds flag will suppress form feeds between the files of a multi-file print job.

8.3 The Printcap Parsing Rules

In this section, we will discuss the remaining tricky parts of the LPRng printcap database: combined client and server printcaps, host specific printcap entries, and the tc include facility.

The following is a complete description of how a printcap file is processed:

  1. When processing a printcap file, the LPRng software reads and parses each entry individually. Leading whitespace is removed. Lines starting with # and blank lines are ignored.
  2. Lines ending with \ will have the \ discarded, and all lines of a printcap entry are joined by removing the line separators (\n) and replacing them with a space.
  3. The printcap entry is parsed, and the printcap name, aliases, and options are determined. Colons : act as option separators, and leading and trailing whitespaces are removed.
  4. Options are sorted and expect for the tc=... option only the last option setting is retained.
  5. Client programs will discard a printcap entry with a server option and server programs will discard a printcap entry with a client options.
  6. The oh (on this host) option specifies a list of IP addresses and mask pairs or glob strings which are used to determine if this printcap entry is valid for this host (see discussion below).
  7. After the above processing, if there is an existing termcap entry with the same name, the two sets of options are combined, with the last option setting retained except for the tc entries which are combined.
  8. When a printcap entry is actually used, the printcap entries listed by the tc include option are extracted and combined in order. (This allows include entries to appear after the referring printcap entry.) Then printcap options will be combined with the included ones. This has the effect that the options specified in the printcap entry will override the ones from the tc included entries.
  9. Finally, each string printcap option with a %X value has %X replaced by the following values. Unspecifed values will not be modified.
    %P           printcap name
    %h           short host name  (host)
    %H           fully qualified host name  (host.dns.whatever)
    %R           remote printer (rp value)
    %M           remote host (rm value)
    %D           date in YYYY-MM-DD format
    
  10. When parsing multiple printcap files, these are processed in order, and all of their printcap entries are combined according to the above procedures. The tc resolution and %X expansion is done after all the files have been processed.

The following examples show how to use the above rules to your advantage. You can combine both client and server printcap information in a single file as well as dividing a printcap entry into several parts. Here is an example:

# seen by both client and server
lp1:lp=lp@pr1:mx#100
lp1:sd=/usr/local/spool/lp1:mx#0
# seen only by client
lp2:lp=lp@pr2:client
# seen only by server
lp2:lp=/dev/lp:server
  1. Printcap entries with the same name are combined. The first printcap entry, lp1, the information is seen by both client and server. The next printcap entry, with the same name lp1, will be combined with the second one. The order of options is important - the entries are scanned in order and an option will have the last value set. Thus, after having read both the lp1 printcap entries, both client and server will have:
    lp1:lp=lp@pr1
      :mx#0
      :sd=/usr/local/spool/lp1P
    
  2. The lp2 has a client and server version. This is recommended when complex printcaps on multiple hosts and servers are used. Thus, the LPRng clients will see:
    lp1
      :lp=lp@pr1
      :mx#0
      :sd=/usr/local/spool/lp1P
    lp2
      :client
      :lp=lp@pr2
    
    and the server will see:
    lp1
      :lp=lp@pr1
      :mx#0
      :sd=/usr/local/spool/lp1P
    lp2
      :lp=/dev/lp
      :server
    

If you have multiple printers of the same type whose configuration is almost identical, then you can define a set of tc only printcap entries containing common information and use the tc include facility.

By convention, all printer names start with an alphanumeric character and contain only alphanumeric values, underscore (_) or hyphen (-). A printcap entry starting with punctuation such as period (.) or at (@) is processed by LPRng but can only be used with the tc include facility. For example:

.hp:
  :sd=/usr/local/spool/%P
  :mx#0:sf:sh
hp1:tc=.hp,.filter
  :lp=lp@10.0.0.1
hp2:tc=.hp,@filter
  :lp=lp@10.0.0.2
@filter
  :if=/usr/local/bin/ifhp
  :lpd_bounce
  1. The .hp and @filter printcap enties will not be used as real printcaps by LPRng, but can be referenced by the tc printcap include facility. After tc include processing is completed, the printcap information would resemble:
    hp1
      :lp=lp@10.0.0.1
      :if=/usr/local/bin/ifhp
      :lpd_bounce
      :mx#0
      :sd=/usr/local/spool/%P
      :sf
      :sh
    hp2
      :lp=lp@10.0.0.1
      :if=/usr/local/bin/ifhp
      :lpd_bounce
      :mx#0
      :sd=/usr/local/spool/%P
      :sf
      :sh
    
  2. The %X processing will replace %P with the printcap name, so we would have:
    hp1
      :lp=lp@10.0.0.1
      :mx#0
      :sd=/usr/local/spool/hp1
      :sf
      :sh
    hp2
      :lp=lp@10.0.0.1
      :mx#0
      :sd=/usr/local/spool/hp1
      :sf
      :sh
    

When administring a large number of printers over a large area, it is sometimes desireable to have a default printer for each host. This default printer may be different for each host, and can be selected by using the oh entry. The oh value is a list of the following entries

IP/n     - address + mask length    10.0.0.0/8
IP/IP    - address + mask           10.0.0.0/255.0.0.0
vvv      - glob for hostname        pc*.org.com    

The LPRng software will determine the hostnames and IP addresses assigned to the host and then check to see if there is a match in the listed hostnames or IP addresses. If there is a match, the printcap entry will be used. If not, then the entry will be discarded. For example:

lp:oh=*.admin.org.com,10.0.0.5,10.2.0.0/16:lp=pr1@server1
lp:oh=*.eng.org.com:lp=hp@server2
  1. In the above example, if our host name is booster.admin.org.com, then we would use lp=pr1@server1, as the *.admin.org.com glob pattern would match our host name.
  2. if our host name is booster.dev.org.com and our IP address is 10.2.0.1, then we would use lp=pr1@server1, as the 10.0.0.0/16 ip address would match.

8.4 Displaying Printcap Information

If you are generating complex printcap entries, you might need to find out exactly what the LPRng servers or clients will actually see. The LPRng software has several diagnostic tools to help you. The most simple to use is the lpc program.

The lpc client all and lpc server all commands will display the printcap information that the LPRng clients and lpc server would see when executing on the host. For example:

#> lpc client all
lp1
  :lp=lp@pr1
  :mx#0
  :sd=/usr/local/spool/lp1P
lp2
  :client
  :lp=/dev/lp

8.5 Remote Printer Using RFC1179

Options used:

You can have the lpd server forward jobs to another server or print which supports the RFC1179 procol by using the following printcap:

# Simplest
remote|Remote Printer
   :lp=raw@server
# historical
remote:
  rp=raw:rm=server
# Sometimes you have to connect to a non-standard port
special:lp=lp@server%2000
  1. If the lp printer entry is present, it will override the rm and rp printer entries.
  2. The lp=pr@host format specifies that the output device is actually a remote spool queue, and jobs should be transferred using RFC1179 protocol.
  3. By default, LPRng will attempt to sanitize all jobs that it originates or forwards. This sanitization will result in an RFC1179 compliant control file, and will not modify any of the job information.

8.6 Remote Printer Using Socket API

If the spool queue destination is a remote printer supporting the Socket API, then you can have LPRng open a connection directly to the printer. These include the older Apple printers with TCP/IP support and the HP JetDirect supported printers.

# Simplest
remote
   :lp=10.24.2.3%9100
   :sh:sf
  1. The lp=server%port or lp=IPaddr%port format specifies that lpd should open a TCP/IP connection to the remote host and simply transfer verbatum the files to be printed.
  2. The sh and sf will prevent lpd from trying to generate banner pages or put form feeds between jobs.

While this is the simplest printcap, it is also the most dangerous as there is nothing to prevent a malformed job from being sent to the printer. The next printcap example is much more robust:

# Simplest
remote
   :lp=10.24.2.3%9100
   :of=/usr/local/lib/ifhp
   :if=/usr/local/lib/ifhp
   :sh:sf
  1. This version will use the ifhp filter to precondition the printer and to process jobs. See IFHP Filter for details. The ifhp filter will perform the appropriate printer resets, translate job information, and ensure correct printer operation in the presence of errors. It will also produce voluminous error messages and logging information.

8.7 Parallel Printer

The parallel printer printcap is very simple.

# parallel printer
lp:
   :lp=/dev/lpr
   :sh:sf
  1. The lp=/dev/lpr specifies that lpd should open the device for APPEND and simply transfer job files to it.
  2. The sh and sf will prevent lpd from trying to generate banner pages or put form feeds between jobs.

If you discover that UNIX print jobs result in a staircase appearance, then you need to force your printer to do LF (linefeed) to CR/LF (carriage return/line feed) translation, or do the translation yourself.

# Simple parallel printer
lp:
   :lp=/dev/lpr
   :if=/usr/local/bin/lpf
   :sh:sf

By using the if=...lpf filter, the job will be passed through the lpf filter, which will do the LF to CR/LF translation.

If you have a more complex printer that handles PostScript, PCL, and PJL, then you will need to use the more powerful ifhp filter:

# Simple parallel printer
lp:
   :lp=/dev/lpr
   :ifhp=model=hp4,status@
   :of=/usr/local/bin/ifhp
   :if=/usr/local/bin/ifhp
   :sh:sf

See IFHP Filter for details. This entry will specify that the printer is an HP4, and that no status information is available. This is usually the case with a parallel port.

8.8 A serial printer queue

Options used:

The following is a typical printcap for a serial printer:

# Local Serial ASCII printer
lp2
  :lp=/dev/ttya
  :rw
  :cm=Serial printer
  :sd=/var/spool/lpd/lp2
  :stty=9600 -echo -crmod -raw -oddp -evenp pass8 cbreak ixon
  :if=/usr/local/sbin/lpf
  :mx#0:sh

Let's examine the new options:

  1. A serial port is usually bidirectional, and printers will report errors back to the host computer. The rw flag will cause the printer port to be openned read-write, and the lpd server will report status information.
  2. The sy option specifies the stty(1) flags and line speed needed to configure the serial line (See Serial Printers for details).
  3. The legacy br (bit rate) option can be used to specify the line speed as well.

8.9 Single Printcap File for Large Installation

One of the major problems faced by administrators of large sites is how to distribute printcap information. They would like to have a single printcap file either distributed by a file server (NFS) or by some other method such as rdist. By using the server and oh tags, information for the specific sites can be separated out. For example:

#/etc/printcap file
pr1:lp=pr1@serverhost1:oh=*.eng.site.com,130.191.12.0/24
pr2:lp=pr1@serverhost1:oh=*.eng.site.com,130.191.12.0/24
pr1:lp=pr2@serverhost2:oh=*.admin.site.com
pr2:lp=pr2@serverhost2:oh=*.admin.site.com
pr1:server:oh=serverhost1.eng.com:lp=/dev/lp:tc=.common
pr2:server:oh=serverhost2.admin.com:lp=/dev/lp:tc=.common
.common:sd=/usr/local/lpd/%P

The above example has some interesting effects. The pattern is used as a glob pattern and is applied to the fully qualified domain name (FQDN) of the host reading the printcap file. For example, *.eng.site.com would match host h1.eng.site.com but would not match h1.admin.site.com. Thus, the effects of the first couple of entries would be to specify that the pr1 and pr2 printers on the eng hosts would be pr1@serverhost1, and on the admin hosts would be pr2@serverhost2,

Also, the lpd daemons on serverhost1 and serverhost2 would extract the additional information for pr1 and pr2 respectively, overriding the common lp entries.

8.10 Bounce queues

Options used:

When the destination of a spool queue is another spool queue the job is simply forwarded without any modifications. However, sometimes it is essential that the job be modified before forwarding, as when the remote spool queue is actually a printer, and jobs need to be converted to the format acceptable by the remote printer or banner pages added.

The lpd_bounce flag marks a spool queue as a bounce queue. Lpd will perform all of the usually job processing steps, such as banner generation, filtering files, etc, but saves the output to a file. This file is then sent to the destination print queue for further processing.

# Simple example of a bounce queue
bounce:lp=bounce@bouncehost
bounce:server
    :lp=lp@remote
        :lpd_bounce
        # LEGACY
        #bq=lp@remote
    :sd=/usr/spool/lpd/%P
    :if=/usr/local/bin/lpf
    :vf=/usr/local/bin/lpf
    :bq_format=l
    # uncomment ab if you want banner
    #ab

Some comments:

  1. The lpd_bounce option marks the job as a bounce queue, and the lpd server will process the job through the appropriate filter programs.
  2. The legacy bq=host has the same effect as lpd_bounce. This option is retained for compatibility with previous versions of LPRng. It is recommended that this option not be used.
  3. The printcap has filter specifications for different job formats. These are the programs that will be used by LPRng to process the job.
  4. The bq_format specifies the job format for the output file sent to the remote spool queue. If not specified, it defaults to l (literal or binary).
  5. The ab (always print a banner) flag will force a banner to be added to the job. The banner generation is done as discussed in Banner Printing.

8.11 Changing Job Formats

Options used:

A rarely encountered problem is when a job is printed with one format but for compatiblity needs to be processed with another. The simple translate_format=vlxf option will rename format x files to f format.

This can be used to resolve problems with PC based sofware, which spools jobs using the v format. Unfortunately, many RCF1179 print spoolers do not understand the v format and mishandle the job. A simple forwarding queue with the following entries will rename v format to l (binary) format.

lp
  :sd=/usr/spool/lpd/%P
  :translate_format=vl
  :lp=lp@printerserver

8.12 LPR Filters Files

Options used:

Some users would like the advantages of the filtering and processing capabilities of a lpd daemon without running a lpd daemon on their system. By having the lpr program process the job by passing it through the various filters and then send the output of the filters as the print job you can get the desired effect.

# Simple example of an lpr_bounce entry
bounce
  :lpr_bounce
  :lp=lp@remote
  :if=/usr/local/bin/lpf

The lpr_bounce flag, if present in the printcap entry, will force lpr to process the job using the specified filters and send the outputs of the filters to the remote printer for further processing.

8.13 Dynamic Routing

Options used:

LPRng has the ability to route a job to one or more destinations in a dynamic manner. This is not the same as load balancing, as the destinations are hard coded and not able to be changed. This is accomplished by having a router filter generate a set of destinations. Here is a sample printcap entry:

t2|Test Printer 2
    :sd=/var/spool/LPD/t2
    :lf=log
    :destinations=t1@server1,t1@server2,t1@localhost
    :router=/usr/local/LPD/router

When a job arrives at the lpd server, the 'router' filter is invoked with the standard filter options which include the user, host, and other information obtained from the control file. STDIN is connected to a temporary copy of the control file, and the CONTROL environment variable is set to the value of the actual control file itself.

The routing filter exit status is used as follows:

The router filter writes to STDOUT a file specifying the destinations for the job. The destinations entries in this file file have the following format. Entry order is not important, but each destination must end with the 'end' tag.

dest (destination queue)
copies (number of copies to be made)
priority (priority letter)
X(controlfile modifications)
end

Example of router output:

dest t1@localhost
copies 2
CA
priority B
end
dest t2@localhost
CZ
priority Z
end

In this example, two copies of the job will be sent to the t1 and t2 spool queue servers. The Class (C letter value) and job priority information will be rewritten with the indicated values.

If routing information is specified by the router filter the job will be sent to the default destination.

LPQ will display job information in a slightly different format for multiple destination jobs. For example:

Printer: t2@astart2 'Test Printer 2' (routed/bounce queue to 't1@astart2.astart.com')
  Queue: 1 printable jobs in queue
 Rank  Owner/ID        Class Job Files                           Size Time
active  papowell@astart2+707 A 707  /tmp/hi                         3 10:04:49
 - actv papowell@astart2+707.1 A 707 ->t1@localhost <cpy 1/2>       3 10:04:49
 -      papowell@astart2+707.2 A 707 ->t2@localhost                 3 10:04:49

The routing information is displayed below the main job information. Each destination will have its transfer status displayed as it is transferred. By convention, the job identifier of the routed jobs will have a suffix of the form .N added; copies will have CN added as well. For example, papowell@astart2+707.1C2 will be the job sent to the first destination, copy two.

Routed jobs can be held, removed, etc., just as normal jobs. In addition, the individual destination jobs can be manipulated as well. The LPC functionality has been extended to recognize destination jobids as well as the main job id for control and/or selection operations.

The optional destinations entry specifies the possible set of destinations that the job can be sent to, and is for informational purposes only. In order for LPQ/LPRM to find the job once it has passed through LPD, LPQ/LPRM uses the list of printers in the destinations, and loop over all the names in the list looking for the "job" that you are interested in. If there is no destinations information, the bq information will be usued.

Lars Anderson <lsa@business.auc.dk> supplied this example (slightly edited):

This script will attempt to distribute print jobs evenly on 2 printers hpl5a and hpl5b when sending to hpl5bounce.

hpl5bounce|for PLP/LPRng software - network based HP Jetdirect card:
        :lpd_bounce
        #default
        :rp=hpl5b
        :destinations=hp5a,hp5b
        :router=/usr/local/admscripts/bouncer.pl
hpl5a|for PLP/LPRng software - network based HP Jetdirect card:
        :lp=hpl5a%9100
        :tc=@hplcommon
hpl5b|for PLP/LPRng software - network based HP Jetdirect card:
        :lp=hpl5b%9100
        :tc=@hplcommon
# Common settings
@hplcommon:
        :sd=/var/spool/lpd/%P
        :rw:sh:ps=status
        :fx=flp
        :if=/usr/local/lib/filters/ifhp -Tbanner=on
        :of=/usr/local/lib/filters/ofhp -Tbanner=on

The perl script bouncer.pl looks like this:

#!/usr/bin/perl
#
# Script for printjob loadsharing
#   This is static, not dynamic balancing
#
# Printqueues to check
$printer1="hpl5a\@localhost";
$printer2="hpl5b\@localhost";
# obtain number of jobs in each printqueue
$lpq1=`/usr/local/bin/lpq -s -P$printer1`;
$lpq2=`/usr/local/bin/lpq -s -P$printer2`;
$lpq1=~ (/(\d+) jobs?/); $numjobs1=$1;
$lpq2=~ (/(\d+) jobs?/); $numjobs2=$1;
if ($numjobs1 == 0) {
    print "dest $printer1\nCA\nend\n";
    exit;
}
if ($numjobs1 > $numjobs2) {
    print "dest $printer2\nCA\nend\n";
    exit;
}
print "dest $printer1\nCA\nend\n"; 

8.14 Printer Load Balancing

In a large site, you could have several equivalent printers, which will be used by many people. The reason for this is, of course, to increase the printer output by enabling several jobs to be printed at once.

LPRng supplies mechanisms to define a `virtual' printer for such a set of real printers. If properly set up, print jobs will be distributed evenly over all printers.

Multi-server print queue

Options used:

A multi-server print queue is one that feeds jobs to other queues. The main queue sv=q1,q2,... printcap entry specifies the names of the printers that will be sent jobs. These printers must have their spool queues on this LPD server.

Servers that are fed jobs have a ss=mainqueue printcap entry. This informs the lpd server that the queue operates under the control of the mainqueue print queue, and is fed jobs from it.

During normal operation, when the lpd server has a job to print in the mainqueue, it will check to see if there is an idle service queue. If there is, it will transfer the job to the service queue spooling directory and start the service queue printing activities.

Users can send jobs directly to the individual printers serving a queue.

The next example (and the comments underneath) was supplied by John Perkins <john@cs.wisc.edu> (slightly edited).

Here's how I've set up a bounce queue that feeds 6 LaserWriters:

laser|pi|Room 1359 LaserWriters
    :lp=laser@server.com
laser|pi|Room 1359 LaserWriters
    :server
    :lf=/usr/adm/laser-log
    :sv=laser1,laser2,laser3,laser4,laser5,laser6
    :sd=/usr/spool/laser
@commonlaser
    :sd=/usr/spool/%P
    :rw:mx#0:sh
    :lf=/usr/adm/laser1-log
    :if=/s/lprng/lib/filters/cappsif
    :of=/s/lprng/depend/cap/bin/papof
    :ss=laser
    :fx=fdginpt
laser1|pi1|Room 1359 LaserWriter #1
    :lp=laser1@server.com
laser1|pi1|Room 1359 LaserWriter #1
    :server
    :lp=/dev/laser1
    :tc=@commonlaser
laser2|pi2|Room 1359 LaserWriter #1
    :lp=laser2@server.com
laser2|pi2|Room 1359 LaserWriter #2
    :server
    :lp=/dev/laser2
    :tc=@commonlaser

and so on for the other 4 laserN queues.

This will forward a job from laser to laserN, once one of those queues is available. It will hold jobs in the ``laser'' queue until one of the other queues is empty.

Even though the queues are not meant for direct use, people can print directly to individual queues. This allows a specific load sharing printer to be used. If you wanted to hide the load sharing printers, i.e. - not allow direct spooling to them, then you would simply remove the non-server entries from the printcap.

Checking Busy Status of Server Queues

Options used:

The previous section outlined how LPRng uses the sv and ss flags to indicate that the server spool queue has multiple destination queues. However, there is a problem when the actual printer being served by the destination queue is a remote device, and can be busy or offline.

The check_idle option specifies a program that is invoked by the lpd server to determine if the spool queue device is available.

The program is invoked with the standard filter options, STDIN and STDOUT connected to /dev/null, and STDERR to the error log.

The program should make a connection to the remote device or system and should determine that the remote device is available for use, and then exit with the following status.

Key      Value   Meaning
JSUCC    0       Successful - printer is idle
JABORT   non-zero Printer is not accepting jobs

If the printer is accepting jobs but is temporarily busy, the program should poll the printer until it becomes free, only exiting when it is available for use. If the printer is not accepting jobs, the program should exit with a non-zero exit code.

The following is a sample printcap entry, showing how the check_idle facility can be used.

pr:
  :lp=laserjet%9100
  :check_idle=/usr/local/filters/remote_check lp@laserjet
  :if=/usr/local/filters/ifhp

The following perl program shows how to generate a query to the remote printer by simulating an lpq query and checking for returned status.

#!/usr/local/bin/perl
# Usage:
#  remote_check printer@host[%port] [-options]
#   -Tflag[,flags]*
#  flag
#    debug  - turns debugging on
#    long   - use long status format
#
# query the remote printer whose name is passed on the command line
# 
# Note that -Txxx options are passed AFTER the printer
use English;
use IO::Socket;

my $JSUCC = 0;
my $JABORT = 33;
my $JNOSPOOL = 38;
my $JNOPRINT = 39;

my $debug = 0;
my $optind;

# pull out the options
my($key,$value,$opt,$long,$opt_c);

$printer = $ARGV[0];

for( $i = 1; $i < @ARGV; ++$i ){
    $opt = $ARGV[$i];
    print STDERR "XX opt= $opt\n" if $debug;
    if( $opt eq '-c' ){
        $opt_c = 1;
    } elsif( ($key, $value) = ($opt =~ /^-(.)(.*)/) ){
        if( $value eq "" ){
            $value = $ARGV[++$i];
        }
        ${"opt_$key"} = $value;
        print STDERR "XX opt_$key = " . ${"opt_$key"} . "\n" if $debug;
    } else {
        $optind = $i;
        last;
    }
    print STDERR "XX opt_P = $opt_P\n" if $debug;
}

$long = 0;  # short

if( defined($opt_T) ){
    print STDERR "XX CHECK_REMOTE opt_T=$opt_T\n" if $debug;
    if( $opt_T =~ /debug/ ){
        $debug = 1;
    }
    if( $opt_T =~ /short/ ){
        $long = 1;
    }
    if( $opt_T =~ /long/ ){
        $long = 0;
    }
}

print STDERR "XX CHECK_REMOTE " . join(" ",@ARGV) . "\n" if $debug;

if( !defined($printer) or $printer =~ /^-/ ){
    print STDERR "$0: no printer value\n";
    exit( $JABORT );
}

while( checkstatus( $printer, $long ) ){
    print STDERR "XX CHECK_REMOTE sleeping\n" if $debug;
    sleep(10);
}

exit $JSUCC;

sub checkstatus {
    my ($printer,$long) = @_;
    my ($remote,$port);
    my ($count, $socket, $line);

    if( $long ){
        $long = 4;
    } else {
        $long = 3;
    }
    if( $printer =~ /@/ ){
        ($printer,$remote) = $printer =~ m/(.*)@(.*)/;
    }
    $remote="localhost" unless $remote;

    if( $remote =~ /%/ ){
        ($remote,$port) = $remote =~ m/(.*)%(.*)/;
    }
    $port = 515 unless $port;
    print STDERR "XX CHECK_REMOTE remote='$remote',"
        . " port='$port', pr='$printer', op='$long'\n" if $debug;

    $socket = getconnection( $remote, $port );

    $count = -1;
    # send the command
    printf $socket "%c%s\n", $long, $printer;

    while ( defined( $line = <$socket>) && $count < 0 ){
        chomp $line;
        print STDERR "XX CHECKREMOTE '$line'\n" if $debug;
        if( $line =~ /printing disa/ ){
            print STDERR "XX CHECKREMOTE printing disable\n" if $debug;
            exit $JNOPRINT;
        } elsif( $line =~ /spooling disa/ ){
            print STDERR "XX CHECKREMOTE printing disable\n" if $debug;
            exit $JNOSPOOL;
        } elsif( $line =~ /([0-9]*)\s+job.?$/ ){
            $count = $1;
            print STDERR "XX CHECKREMOTE $count jobs\n" if $debug;
        }
    }
    close $socket;
    if( $count < 0 ){
        print STDERR "CHECKREMOTE cannot decode status\n";
        exit $JABORT;
    }
    return $count;
}

sub getconnection {
    my ($remote,$port) = @_;
    my ($socket);
    print STDERR "XX CHECK_REMOTE remote='$remote', port=$port\n" if $debug;
    $socket = IO::Socket::INET->new(
        Proto => "tcp",
        PeerAddr => $remote,
        PeerPort => $port,
        );
    if( !$socket ){
        print STDERR "CHECK_REMOTE IO::Socket::INET failed - $!\n";
        exit $JABORT;
    }
    $socket->autoflush(1);
    $socket;
}

The example of the previous section can be modified now so that it uses the check_idle facility. The master queue will send jobs only to the server queue queues which report idle status.

laser1|pi1|Room 1359 LaserWriter #1
    :server:check_idle=/usr/local/lib/filters/remote_check pr@laser1
    :lp=laser1%9100
    :tc=@commonlaser
laser2|pi2|Room 1359 LaserWriter #2
    :server:check_idle=/usr/local/lib/filters/remote_check pr@laser1
    :lp=laser2%9100
    :tc=@commonlaser

8.15 Locations of Printcap Files

Options used:

The printcap_path and lpd_printcap_path configuration options (see lpd.conf(5)) specify a set of paths for the printcap information. Client programs use only printcap_path and the lpd server uses both printcap_path and lpd_printcap_path. The path names can be separated with whitespace, commas, semicolons, or colons. The default values are:

printcap_path      /etc/printcap /usr/etc/printcap
lpd_printcap_path  /etc/lpd_printcap /usr/etc/lpd_printcap

Separate Server and Client Printcap Files

Since only the LPD server uses the printcap file specified by the lpd_printcap_path, you can place server specific information there. This allows you to have a common printcap file for clients and an additional one for the lpd servers.

ALL Printcap Entry

The all printcap name and all option is reserved to provide a list of printers available for use by the spooling software. This is a desperation, last ditch, back to the wall option for administrators with systems that do not have ways to provide a list of printcap entries. The 'all' printcap entry has the form:

all:all=pr1,pr2,...

The value of the all option should be a list of printcap names whose values will then be extracted.

8.16 Management Strategies for Large Installations

One very effective way to organize print spooling is to have a small number of print servers running a lpd daemon, and to have all the other systems send their jobs directly to them. By using the above methods of specifying the printer and server host you eliminate the need for more complex management strategies.

However, you still need to inform users of the names and existence of these printers, and how to contact them. One method is to use a common /etc/printcap file which is periodically updated and transfered to all sites. Another method is to distribute the information using the NIS or some other database. LPRng has provided a very flexible method of obtaining and distributing database information: see Using Programs To Get Printcap Information for details.

8.17 Using Programs To Get Printcap Information

In the lpd.conf file you can specify:

printcap_path=|program
This will cause the LPRng software to execute the specified program, which should then provide the printcap information. The program is invoked with the standard filter options, and has the name of the printcap entry provided on STDIN. The filter should supply the printcap information on stdout and exit with a 0 (success) error code. By convention, the printcap name 'all' requests a printcap entry that lists all printers.

This technique has been used to interface to the Sun Microsystem NIS and NIS+ databases with great success. By having the invoked program a simple shell script or front end to the nismatch or ypmatch programs, the complexity of incorporating vendor specific code is avoided.

How to use NIS and LPRng

This note is based on material sent to the lprng@lprng.org mailing list by Paul Haldane <paul@ucs.ed.ac.uk>.

 # From: Paul Haldane <paul@ucs.ed.ac.uk>
 # To: lprng@lprng.org
 # Subject: Re: Problem using plp with NIS
 # 

We generally don't use NIS for printcap files (we've moved to hesiod) but I can show you what we've done in the past.

The input to NIS is a normal printcap file:

# Classical printcap entry
lp23a|lp23|lp|main printhost printer - KB, EUCS front Door:\
        :lp=lp23a@printhost:\
        :sd=/usr/spool/lpr/lp23a:
 
#lprng printcap entry
lplabel|lpl|TEST - Labels printer:
        :lp=:rm=printhost:rp=lplabel:
        :sd=/usr/spool/lpr/lplabel:
        :rg=lpadm:mx#1:

To build the NIS printcap.byname map we add the following to the NIS makefile (along the other bits and pieces that the makefile needs to know about a new map).

PRINTCAP=$(DIR)/printcap
#PRINTCAP=/etc/printcap
# warning : [  ] is actually [<space><tab>] in the script
printcap.time: $(PRINTCAP) Makefile
  if [ -f $(PRINTCAP) ]; then \
    sed < $(PRINTCAP) \
      -e 's/[   ][  ]*$$//' -e '/\\$$/s/\\$$/ /' \
    | awk '$$1 ~ /^#/{next;} $$1 ~ /^[:|]/ {printf "%s", $$0; next;} \
        {printf "\n%s", $$0 }' \
    | sed -e 's/[   ]*:[  ]*:/:/g' -e 's/[  ]*|[  ]*/|/g' \
      -e '/^[   ]*$$/d' > .printcap.$$$$; \
    cat .printcap.$$$$; \
    if [ $$? = 0 -a -s .printcap.$$$$ ]; then \
      awk <.printcap.$$$$ '{ FS=":"; OFS="\t"; } { \
          n = split($$1, names, "|"); \
          for (i=1; i<=n; i++) \
              if (length(names[i]) > 0 \
              && names[i] !~ /[ \t]/) \
                  print names[i], $$0; \
      }' | $(MAKEDBM) - $(YPDBDIR)/$(DOM)/printcap.byname; \
      awk <.printcap.$$$$ '{ FS=":"; OFS="\t"; } { \
          n = split($$1, names, "|"); \
          if (n && length(names[1]) > 0 && names[1] !~ /[ \t]/) \
              print names[1], $$0; \
      }' | $(MAKEDBM) - $(YPDBDIR)/$(DOM)/printcap.bykey; \
      rm -f .printcap.$$$$; \
      touch printcap.time; echo "updated printcap"; \
    fi \
  fi
  @if [ ! $(NOPUSH) -a -f $(PRINTCAP) ]; then \
      $(YPPUSH) printcap.byname; \
      $(YPPUSH) printcap.bykey; \
      touch printcap.time; echo "pushed printcap"; \
  fi

To specify that you want YP database rather than file access, use the following entry in your /etc/lpd.conf file:

printcap_path |/usr/local/lib/pcfilter

Put the following shell script in /usr/local/lib/pcfilter

#!/bin/sh
#/usr/local/lib/pcfilter
read key
ypmatch "$key" printcap.byname

How to use NIS and LPRng - Sven Rudolph

 Date: Wed, 11 Sep 1996 00:11:02 +0200
 From: Sven Rudolph <sr1@os.inf.tu-dresden.de>
 To: lprng@lprng.org
 Subject: Using :oh=server: with NIS

When I use a cluster-wide printcap, two entries for each printer will appear, e. g.:

---------- start of /etc/printcap snippet
lp1
 :lp=lp1@server
lp2
 :lp=lp2@server
lp1
 :server:oh=servername
 :sd=/var/spool/lpd/lp1
 :lp=/dev/lp1
 :sh:mx#0
---------- end of /etc/printcap snippet

When I create a NIS map out of this, the printer name is used as a key and must be unique. So NIS' makedbm decides to drop all but the last entry for each printer. This makes the printer on the clients unavailable. I solved this by a hack where the second entry is called lp1.server and the NIS client script has to request the right entry.

  1. Assumptions

    Perl is available at the YP server in /usr/bin/perl . A Bourne Shell is available at all clients in /bin/sh The printcap that is to be exported is in /etc/printcap . The printcap is written in the new format.

    In the examples the printer is called lp1 .

  2. Add the following to your YP Makefile (/var/yp/Makefile) on the YP server :
    ---------- start of /var/yp/Makefile snippet
    PRINTCAP  = /etc/printcap
    printcap: $(PRINTCAP)
        @echo "Updating $@..."
        $(CAT) $(PRINTCAP) | \
            /usr/lib/yp/normalize_printcap | $(DBLOAD) -i $(PRINTCAP) \
            -o $(YPMAPDIR)/$@ - $@
        @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
        @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
    ---------- end of /var/yp/Makefile snippet
    

    (These lines are for Debian GNU/Linux, other systems might require other modifications)

  3. Install the programs match_printcap and normalize_printcap to /usr/lib/yp. normalize_printcap is only required on the YP server. The normalize_printcap processes only the LPRng printcap format.
    ---------- start of /usr/lib/yp/normalize_printcap
    #! /usr/bin/perl
    $debug = 0;
    $line = "";
    $new = "";
    while (<>) {
        chomp;
        next if ( /^\s*\#.*/ );
        s/^\s*$//;
        next if ( $_ eq '' );
        print "new: " . $_ . "\n" if $debug;;
        if (/^\s/) { # continuation line
            $line = $line.$_;
            print "continued: $line\n" if $debug;
            next;
        } else {
            $line =~ s/\s+\:/:/g;
            $line =~ s/\:\s+/:/g;
            $line =~ s/\:\s*\:/:/g;
            print "line: $line\n" if $debug;
            push(@lines, $line) if $line;
            $line = $_;
        }
    }
    $line =~ s/\s+\:/:/g;
    $line =~ s/\:\s+/:/g;
    $line =~ s/\:\s*\:/:/g;
    push(@lines,$line) if $line;
    @lines = sort(@lines);
    foreach $line (@lines) {
        ($printers) = split(/\:/,$line);
        @printers = split(/\|/,$printers);
        foreach $printer (@printers) {
          $num{$printer}++;
          push(@allprinters,$printer);
          print "allprinters: @allprinters\n" if $debug;
          print $printer."_".$num{$printer}."\t$line\n";
        }
    }
    @pr = keys %num;
    print "printers @pr\n" if $debug;
    if ($#allprinters >=0) {
        print "all_1\tall:all=".join(",",@pr)."\n";
    }
    ---------- end of /usr/lib/yp/normalize_printcap
    

    The result of processing the sample printcap file is:

    lp1_1 lp1:lp=lp1@server
    lp1_2 lp1:server:oh=servername:sd=/var/spool/lpd/lp1:lp=/dev/lp1:sh:mx#0
    lp2_1 lp2:lp=lp2@server
    all_1 all:all=lp1,lp2
    

    Observe that each of the real printer entries has a key consisting of the printer name with a numerical suffix. This leads to the following method of extracting the printcap information using ypmatch:

    ---------- start of /usr/lib/yp/match_printcap
    #!/bin/sh
    read p
    n=1
    while ypmatch "${p}_${n}" printcap 2>/dev/null; do
        n=`expr $n + 1`
    done
    ---------- end of /usr/lib/yp/match_printcap
    
  4. Now test the YP arrangement:
    $ cd /var/yp; make # this should create the printcap map
    $ ypcat printcap # should provide the whole normalized printcap
    $ echo lp1 |/usr/lib/yp/match_printcap # yields lp1 printcap
    
  5. Add the printcap_path entry to /etc/lpd.conf:
    printcap_path=|/usr/lib/yp/match_printcap
    
  6. Test the use of the printcap path entry:
    $ lpq -Plp1 # shows the status of lp1
    
  7. Restart the lpd server and check to see that it accesses the right printcap information. Use the same lpq command, and then try lpc printcap lp1.

Next Previous Contents