Line Printer Spooler
- Program
- Output
include
src
Makefile
# The following output is captured on two shell processes; client and server.
# Unfortunately, the historical lpr program has been succeeded by the CUPS program,
# hence the output on terminal instead of an actual printer (I don't have a printer regardless).
# Server
bash-5.3$ ./src/server/server
# Client
bash-5.3$ ./bin/main Makefile
# Server
[LOG] 192.168.1.68 request payload: lp
[4 bytes]
[DBEUG] Found control packet
[LOG] 192.168.1.68 request payload: 2226 dfA001Pranav-MacBook-Pro.local
[37 bytes]
[LOG] Got a data file
[LOG] data file received. Allocating sufficient storage and storing the buffer...
*********************DATA RECEIVE START*********************
BUILD ?= dev
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S), Linux)
CC=gcc
ifeq ($(BUILD), dev)
CFLAGS=-O1 -Wall -Wextra -fcommon -fsanitize=address -fsanitize=undefined
else
CFLAGS=-O2 -Wall -Wextra -fcommon
endif # BUILD
endif # UNAME_S
ifeq ($(UNAME_S), Darwin)
# CC = gcc can be done as well, but macOS alias's gcc to clang
CC=clang
ifeq ($(BUILD), dev)
CFLAGS=-O1 -Wall -Wextra -fsanitize=address -fsanitize=undefined
else
CFLAGS=-O2 -Wall -Wextra
endif # BUILD
endif # UNAME_S
BINDIR=bin
OBJDIR=obj
SRCDIR=src
INCDIR=include
OBJS=$(OBJDIR)/tcp_open.o $(OBJDIR)/writen.o $(OBJDIR)/readn.o \
$(OBJDIR)/readline.o $(OBJDIR)/err_routine.o $(OBJDIR)/get_seqno.o \
$(OBJDIR)/host_err_routine.o $(OBJDIR)/initvars.o $(OBJDIR)/lock.o \
$(OBJDIR)/main.o $(OBJDIR)/printbsd.o
BINS=$(BINDIR)/main
all: $(BINS)
$(BINDIR) $(OBJDIR):
mkdir -p $@
$(BINS): $(OBJS) | $(BINDIR)
$(CC) $(CFLAGS) -o $@ $^
$(OBJDIR)/err_routine.o: $(SRCDIR)/err_routine.c $(INCDIR)/err_routine.h $(INCDIR)/systype.h | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/get_seqno.o: $(SRCDIR)/get_seqno.c $(INCDIR)/defs.h $(INCDIR)/err_routine.h | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/host_err_routine.o: $(SRCDIR)/host_err_routine.c $(INCDIR)/host_err_routine.h | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/initvars.o: $(SRCDIR)/initvars.c $(INCDIR)/defs.h | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/lock.o: $(SRCDIR)/lock.c $(INCDIR)/err_routine.h | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/main.o: $(SRCDIR)/main.c $(INCDIR)/defs.h $(INCDIR)/err_routine.h | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/printbsd.o: $(SRCDIR)/printbsd.c $(INCDIR)/defs.h $(INCDIR)/err_routine.h $(INCDIR)/systype.h | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/readline.o: $(SRCDIR)/readline.c | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/readn.o: $(SRCDIR)/readn.c | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/tcp_open.o: $(SRCDIR)/tcp_open.c $(INCDIR)/err_routine.h $(INCDIR)/host_err_routine.h | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/writen.o: $(SRCDIR)/writen.c | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm $(OBJS) $(BINS)
rm -r $(OBJDIR) $(BINDIR)
**********************DATA RECEIVE END**********************
[LOG] Received data packet ACK. Sending reply...
[LOG] 192.168.1.68 request payload: 168 cfA001Pranav-MacBook-Pro.local
[36 bytes]
[DBEUG] Found control packet
[LOG] Got a control file
[LOG] control file received. Allocating sufficient storage and storing the buffer...
*********************CONTROL RECEIVE START*********************
HPranav-MacBook-Pro.local
Ppranavramjoshi
CPranav-MacBook-Pro.local
Lpranavramjoshi
JMakefile
fdfA001Pranav-MacBook-Pro.local
UdfA001Pranav-MacBook-Pro.local
NMakefile
**********************CONTROL RECEIVE END**********************
[LOG] Connection with 192.168.1.68 terminated successfully.
# Client
bash-5.3$ ./bin/main ./src/server/Makefile
# Server
[LOG] 192.168.1.68 request payload: lp
[4 bytes]
[DBEUG] Found control packet
[LOG] 192.168.1.68 request payload: 629 dfA002Pranav-MacBook-Pro.local
[36 bytes]
[LOG] Got a data file
[LOG] data file received. Allocating sufficient storage and storing the buffer...
*********************DATA RECEIVE START*********************
BUILD ?= dev
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S), Linux)
CC=gcc
ifeq ($(BUILD), dev)
CFLAGS=-O1 -Wall -Wextra -fcommon -fsanitize=address -fsanitize=undefined
else
CFLAGS=-O2 -Wall -Wextra -fcommon
endif # BUILD
endif # UNAME_S
ifeq ($(UNAME_S), Darwin)
# CC = gcc can be done as well, but macOS alias's gcc to clang
CC=clang
ifeq ($(BUILD), dev)
CFLAGS=-O1 -Wall -Wextra -fsanitize=address -fsanitize=undefined
else
CFLAGS=-O2 -Wall -Wextra
endif # BUILD
endif # UNAME_S
all: server
server: server.o
$(CC) $(CFLAGS) -o $@ $^
server.o: server.c
$(CC) $(CFLAGS) -c $<
clean:
rm server.o server
**********************DATA RECEIVE END**********************
[LOG] Received data packet ACK. Sending reply...
[LOG] 192.168.1.68 request payload: 168 cfA002Pranav-MacBook-Pro.local
[36 bytes]
[DBEUG] Found control packet
[LOG] Got a control file
[LOG] control file received. Allocating sufficient storage and storing the buffer...
*********************CONTROL RECEIVE START*********************
HPranav-MacBook-Pro.local
Ppranavramjoshi
CPranav-MacBook-Pro.local
Lpranavramjoshi
JMakefile
fdfA002Pranav-MacBook-Pro.local
UdfA002Pranav-MacBook-Pro.local
NMakefile
**********************CONTROL RECEIVE END**********************
[LOG] Connection with 192.168.1.68 terminated successfully.
# Client
bash-5.3$ ./bin/main ./src/lock.c ./src/readn.c
# Server
[LOG] 192.168.1.68 request payload: lp
[4 bytes]
[DBEUG] Found control packet
[LOG] 192.168.1.68 request payload: 384 dfA004Pranav-MacBook-Pro.local
[36 bytes]
[LOG] Got a data file
[LOG] data file received. Allocating sufficient storage and storing the buffer...
*********************DATA RECEIVE START*********************
#include <unistd.h>
#include "../include/err_routine.h"
void my_lock (int fd) {
lseek(fd, 0L, 0); /* rewind before lockf */
if (lockf(fd, F_LOCK, 0L) == -1) { /* 0L -> lock entire file */
err_sys("can't F_LOCK");
}
}
void my_unlock (int fd) {
lseek(fd, 0L, 0);
if (lockf(fd, F_ULOCK, 0L) == -1) {
err_sys("can't F_ULOCK");
}
}
**********************DATA RECEIVE END**********************
[LOG] Received data packet ACK. Sending reply...
[LOG] 192.168.1.68 request payload: 353 dfB004Pranav-MacBook-Pro.local
[36 bytes]
[LOG] Got a data file
[LOG] data file received. Allocating sufficient storage and storing the buffer...
*********************DATA RECEIVE START*********************
#include <unistd.h>
int readn (register int fd, register char *ptr, register int nbytes) {
int nleft, nread;
nleft = nbytes;
while (nleft > 0) {
nread = read(fd, ptr, nleft);
if (nread < 0) {
return nread;
} else if (nread == 0) {
break;
}
nleft -= nread;
ptr += nread;
}
return (nbytes - nleft);
}
**********************DATA RECEIVE END**********************
[LOG] Received data packet ACK. Sending reply...
[LOG] 192.168.1.68 request payload: 237 cfA004Pranav-MacBook-Pro.local
[36 bytes]
[DBEUG] Found control packet
[LOG] Got a control file
[LOG] control file received. Allocating sufficient storage and storing the buffer...
*********************CONTROL RECEIVE START*********************
HPranav-MacBook-Pro.local
Ppranavramjoshi
CPranav-MacBook-Pro.local
Lpranavramjoshi
Jlock.c
fdfA004Pranav-MacBook-Pro.local
UdfA004Pranav-MacBook-Pro.local
Nlock.c
fdfB004Pranav-MacBook-Pro.local
UdfB004Pranav-MacBook-Pro.local
Nreadn.c
**********************CONTROL RECEIVE END**********************
[LOG] Connection with 192.168.1.68 terminated successfully.
# Client
bash-5.3$ cat /tmp/seqno
005
Running the Program
-
Inside the
srcdirectory, there is another directory calledserver. Build the executable usingmake. Don't keep your expectations high. This server is a really dumb one created as a proof of concept and the things this doesn't do which alprdshould is listed in the source file. -
Locate back to the project directory and run
maketo build the client-side program. Do realize that client attempts to create a TCP/IP connection on port515. The server is created to run on this port. So if you don't have superuser access on your system, you should consider changing the server's port to something >1024. Also, make sure that the client'stcp_openfunction is not called with theservicefield asprinter. Leave the field as aNULLpointer and make sure the port you opened your server is included as the final argument (onsend_startfunction inprintbsd.cfile.) -
Before running the client side program, ensure that
/tmpcontains a file namedseqnowhich must contain a number. Also, you can use localhost or the network interface that you're connected to. The default name issroot, which is initialized ininitvars.cfile. You can: create an entry in/etc/hostsfile (if you have sufficient permissions) with your network interface's IPv4 address and hostname assroot, or you can use different name and change the name ininitvars.cfile. -
Steps that are done by client and server in order:
-
[server] Initialize the socket using
socketcall,bindthe socket to an network interface (INADDR_ANYin this case). As we're creating a TCP/IP socket, we need tolistenon the socket descriptor and spin up an iterative server. The server will now listen for connection. -
[client] Calls the
send_startfunction which opens a TCP/IP socket. Do some chores such as: setting the user ID of the program if the binary is set-user-id, get the name of host, checkpasswdentry for the effective user, open the/tmp/seqnofile and lock the file till the operation persists. Through theseqnofile, generate the datafile-name and controlfile-name and do some chores regarding the control file. We first send the name of the printer in the form:\002<printer-name>\nto the server. Then we wait for the server to respond with a 1-byte packet which contains a null-byte (\0). -
[server] Processes the message sent by client. As we've preceeded the message by the character
\002. The server will encounter two scenarios where the message will contain the preceeding\002character. One of them is sending the name of the printer, and the other one is when sending the control file. For the former case, upon receiving the message with this preceeding character, the server will send a 1-byte message with null-byte as the content. This is what the client is expecting. -
[client] Can now start sending the datafile(s). Each datafile which is supplied through the command-line argument is first opened and the content is then sent. Each datafile is processed using
send_filefunction where the actual file transfer is done throughxmit_filefunction. Before invokingxmit_file, some chores regarding control file is first done. Before sending the actual file, a message which contains the first character as\003is sent to indicate that datafile is about to be transmitted. This message is of the format:\003<file-size> <datafile-name>\n. Then it's waiting a response from the server. -
[server] Will get this message and will fetch the file-size and file-name from the message. Next, the size of file is checked to see if it's of considerable size as well as try to dynamically allocate storage to hold the file's content. After all this done, the server will
writea 1-byte message which contains the null byte, just as the client is anticipating. -
[client] Starts sending the datafile. As the size of the file is already sent, the server will just
readthe said amount of bytes. After sending all the file's content, the client willwritea 1-byte message that contains a null-byte. -
[server] Will
readexactly the number of bytes as the client previously sent. After reading that amount of bytes, as we are aware of the client sending a null byte, we willreadone character and if it's a null byte, we willwriteback a null-byte as a 1-byte message back. Also, it sets up the variableisctrlinforeadyto indicate that any later message with\002character at the beginning will be the control file. -
The communication between client and server from point # 4 through point # 7 is repeated for other data files that the client may have requested to be printed.
-
[client] After sending all the datafile(s), the
send_donefunction is invoked. We've been builidng up the control file throughout this procedure for this moment. We usexmit_fileto transfer the file to the server. As we're sending the control file, we will preceed the message with the character\002. The message sent before sending the control file is of the format:\002<file-size> <controlfile-name>\n. Now, we're awaiting for the server to respond with a 1-byte message containing a null-byte. -
[server] Will receive the message sent by client. Since the first character is
\002, we know it's a control character, and since we've previously received the datafile(s), the server knows the client is about to send the control file. The message is scanned to determine the file-size and the file-name that the client sent. Like previously with datafile(s), the server will check the file-size and allocate sufficient memory. Now, itwrites a 1-byte message containing the null-byte, as the client is expecting. -
[client] Upon receiving the null-byte message, will start sending the file content. After all the content of file has been written, the client
writes a 1-byte message containing the null-byte. It is now expecting the server to respond back a 1-byte message with null-byte. -
[server] Will start reading the file content. As we know the size of the file, we
readexactly that amount of bytes. Later we will check to see if the client has sent us the 1-byte message containing the null-byte. If that's the case, then wewritea 1-byte message which contains a null byte. If everything goes as expected, the server's function that deals with the clientreturns0to indicate safe error-free handling. The connection is closed by the server. -
[client] If the binary was invoked with
-dflag in the command-line, will not delete the control file. The connection is closed by the client as well.
Alternative to 4.3BSD Print Spooler
-
Modern Unix-Like systems have an successor to System V and 4.3BSD print spooler called Common Unix Printing System (CUPS).
-
Text showed a command to print files to a printer using the
lprcommand:
lpr -Plp main.c subr.c
The -P<var> specifies the symbolic name of the printer <var>. Text mentioned "the mapping of symbolic printer names into physical device names, along with a complete specification of the printer's capabilities, is specified by the system administrator in the file /etc/printcap."
The printcap file is no longer used for CUPS.
- The symbolic name for the printer can be given using the
lpadmincommand. The command used to give a symbolic name ofDummyPrinteris:
lpadmin -E -p DummyPrinter -v dummy:/tmp/cups/dummyprinter
Here, the -p flag specifies the symbolic name DummyPrinter for the printing device (which is a file /tmp/cups/dummyprinter). Here, the device being used for DummyPrinter is dummy.
dummyabove is a custom device created in the/usr/libexec/cups/backend/. Thedummyfile contains the following script:
#!/bin/sh
OUTPUT_FILE="/tmp/cups/dummyprinter"
cat - > "$OUTPUT_FILE"
exit 0
-
Although a "dummy" printer with a symbolic name of
DummyPrinteris created, assigning job to it--like using thelprcommand--will not properly work. The error log pager obtained using thesudo less -f /var/log/cups/error_logcommand showed that some operations were not permitted. This is likely due to creating our owndummy"protocol" rather than using the one supported. The supported ones are known using thelpinfo -vcommand. -
I won't go further into setting up a dummy printer (for now.) The main focus of this chapter is to understand how job control is done in a network environment.
Actions performed for lpr -Plp main.c subr.c
- You should probably understand the arguments to the command first. This is explained in the previous section.
- The two file
main.candsubr.care read by thelprprogram and copied to the spooling directory. The name of the spooling directory for a given printer is specified in the/etc/printcapentry for each printer. Assume that for the printerlpthe spooling directory is/usr/spool/lpd. Also assume that the symbolic name of the host isorange. Thelprcommand create three files in the spooling directory
dfA123orange
dfB123orange
cfA123orange
The first is a copy of the file main.c, the second a copy of the file subr.c, and the third file is a "control file" that speicifes the printing parameters for the program that prints the file. We show these spool files in Figure 13.1.
user process
+--------------+ +------------------+
+--------+ | |------->| data file | /usr/spool/lpd/dfA123orange
| main.c |------->| | | (copy of main.c) |
+--------+ | | +------------------+
| lpr |
+--------+ | | +------------------+
| subr.c |------->| |------->| data file | /usr/spool/lpd/dfB123orange
+--------+ | | | (copy of subr.c) |
+--------------+ +------------------+
|
| +------------------+
+-------->| control file | /usr/spool/lpd/cfA123orange
+------------------+
Figure 13.1 Spool files created by 4.3BSD lpr command
An example of what the control file, cfA123orange, looks like for the example given above is
Horange
Pstevens
Jmain.c
Corange
Lstevens
fdfA123orange
UdfA123orange
Nmain.c
fdfB123orange
UdfB123orange
Nsubr.c
Each line in the control file starts with a letter that specifies a parameter for the printer daemon that eventually prints the file. The characters and their meaning are
f filename of text file to print
C class name for banner page
H name of host system on which the lpr was executed
J job name for banner page
N name of file (used by `lpq`)
P person (login name of user)
U name of file to unlink after printing is complete
Note that in this example, the first five lines in the control file specify parameters for the entire job, and following these lines are three lines for every file to be printed.
- Once the data files and the control file are written to the spooling directory, the
lprprocess sends an IPC message to the line printer daemon,lpd, notifying it that there is a job to print. The message contains the name of the printer (lp), so the daemon knows which directory to look at for the control files and data files. We show this in Figure 13.2.
user process
+-------------------+
| lpr |
+-------------------+
|
| Unix domain IPC message
|
V
daemon process
+-------------------+
| lpd |
+-------------------+
Figure 13.2 IPC from lpr process to lpd daemon
The IPC message is sent using a Unix domain stream socket, since the daemon process is running on the same system as the lpr process.
-
The daemon process, which has been waiting for an IPC message on a Unix domain socket, does a
forkof itself. This provides another copy of itself that handles the request, while the originallpdprocess waits for another IPC message notifying it that there are jobs to print. This is an example of a concurrent server, as described in Section 1.6. -
The
lprprocess is now complete. It has copied the files to be printed to the spooling directory, created a control file for the spooling program, and notified the master print daemon that there is work to do. Theforked copy of the printer daemon now looks at the files in the spool directory and sends them to the actual printer. This is shown in Figure 13.3.
+-------------------+
| data file | /usr/spool/lpd/dfA123orange
| (copy of main.c) |
+-------------------+
|
| +-------------------+
| | data file | /usr/spool/lpd/dfB123orange
| | (copy of subr.c) |
| +-------------------+
| |
| | +---------------+
| | | control file | /usr/spool/lpd/cfA123orange
| | +---------------+
| | |
| | |
| | |
V V V
daemon process (parent) daemon process (child)
+---------------------+ (fork) +---------------------------------------+
| lpd |-------->| lpd |
+---------------------+ +---------------------------------------+
|
|
V
printer device
Figure 13.3 Processing of spool files by lpd daemon
- There are some fine points that were ignored in the description above. First, what if two different users send files to the same printer at almosst same time? This is where the 3-digit sequence number in the
dffilenames andcffilename is used. Each user's data files and control files have a different sequence number. The assignment of the sequence number is done by each client'slprprocess. A file is maintainted in each printer's spooling directory with a 4-character name ".seq". The pathname in the above example would be/usr/spool/lpd/.seq. This file itself has a length of 4 bytes--it contains the next 3 digit sequence number to be used, and a newline. Thelprprocessopens this file and then callsflockto obtain an "exclusive lock" on the file. The system doesn't return from the flock call until the process has an exclusive lock on the file. Othereise the process is blocked until it can get the lock. When thelprprocess obtains the exclusive lock, it reads the sequence number, and uses the value for its data files and control files. It immediately increments this number (cycling back to000if its value was999), writes it back to the file, and close the file. Theclosesystem call releases the exclusive lock that the process has on the file, in case anotherlprprocess is waiting to get a lock on the same sequence number file. The actual code fragment to implement this is
sprintf(buf, "%s/.seq", SPOOLDIR);
if ( (fd = open(buf, O_RDWR | O_CREAT, 0661)) < 0)
err_sys("cannot create %s", buf);
if (flock(fd, LOCK_EX))
err_sys("cannot lock %s", buf);
seq = 0;
if ( (len = read(fd, buf, sizeof(buf))) > 0) {
/*
file is 4-bytes. `len` will be 4.
Also, the last character--newline--is not in the range between the
ASCII code for numeric digits, so it will likely break out of the
loop before the loop itself terminates.
*/
for (cp = buf; len--; ) { /* convert ASCII to binary */
if (*cp < '0' || *cp > '9')
break;
seq = seq * 10 + (*cp++ - '0');
}
}
/* seq = sequence number to use */
seq = (seq + 1) % 1000; /* increment seq # modulo 1000 */
/* rewind the file offset that was modified by read */
if (lseek(fd, 0L, 0) < 0)
err_sys("lseek error");
sprintf(buf, "%03d\n", seq); /* convert binary to ASCII */
if (write(fd, buf, strlen(buf)) != strlen(buf))
err_sys("write error");
close(fd); /* unlocks the file too */
- The names of the data files and control files do not conflict for jobs that are destined for different printers, since each printer has its own spooling directory. For example, assume two different printers users executed the
lprprogram at the same time, one printer on the printer namedlpand the other on the printer namedlaser1. If the sequence numbers for the two printers just happened to be identical (345, for example), then the files are
/usr/spool/lpd/.seq
/usr/spool/lpd/dfA345orange
/usr/spool/lpd/cfA345orange
and
/usr/spool/laser1d/.seq
/usr/spool/laser1d/dfA345orange
/usr/spool/laser1d/cfA345orange
Note that the pathnames of all the files are unique. Note also that since the sequence number files are unique, neither process has to wait for the other to obtain a lock on its sequence number file.
- The second point that was ignored above is what happens if there is already a printer daemon process that is busy printing a file on the requested printer. In each printer's spooling directory a file named
lockis maintained. Whenever anlpdchild process is active for a given printer, that process holds an exclusive lock on itslockfile. When a copy of the lpd daemon is forked by the master lpd daemon, the first thing it does is try to obtain an exclusive lock on itslockfile. If this fails, and if the reason for the failure is that another process already holds an exclusive lock on the file, then this new copy oflpddaemon gracefully exits, since a copy of the daemon for this particular printer already exists. The actual code segment that implements this is
if ( (lfd = open(MASTERLOCK, O_WRONLY | O_CREAT, 0644)) < 0) {
/* MASTERLOCK probably a string literal which is `#define`d */
syslog(LOG_ERR, "%s: %m", MASTERLOCK);
/* check `syslog(3)` manual for the %m format specifier */
exit(1);
}
if (flock(lfd, LOCK_EX | LOCK_NB) < 0) {
if (errno == EWOULDBLOCK)
exit(0); /* active daemon present, normal exit */
syslog(LOG_ERR, "%s: %m", MASTERLOCK);
exit(1);
}
-
When a daemon is active for a given printer, it prints all files that appear in the spool directory for that printer. Even though the active copy of the daemon was invoked to print one specific job, when it is done it looks in its spooling directory to see if any other jobs have been placed there for printing.
-
While the
lpdchild process has the exclusive lock on its lock file, it maintains the file as an ASCII text file containing two lines. The first line is the process ID of thelpdchild process itself, and the second line is the name of the control file for the job currently being printed by the process. By keeping these two peices of information in a file, it is easy for thelpqprogram to determine if a daemon is active for a given printer, and if so, which job is currently being printed. The way to determine if a process exists, given its process ID, is to use thekillsystem call and send it a signal0.
Output Filters
-
In essence, the output filter alters the way
lpdhandles the print job. The four categories for filters:offilter,iffilter,/bin/pr, and one of seven possible remaining filter (discussed later) determine how thelpdprocess will handle the job and tranfer the printing to actual device. Theofoption is for "per-job" filter,ifoption for "per-file" accounting (same for one of seven remaining filter andbin/pr). ThefincfA123orangeexample above is used to describe the control character. Based on the invokation oflpr, the control character can be changed. -
The description [previous section] above is valid if there are no output filters being used for the print job. But the 4.3BSD spooliing system supports several different output filters. These handle different types of data files and do resource accounting. The invocation of these filters by the printer daemon provides some interesting uses of process control.
-
A particular filter is specified with the
lprcommand. For example, to print the fileplot.outthat was produced by the Unixplotcommand on the printer namedraster, we execute
lpr -Praster -g plot.out
The -g option causes lpr to change the first character of the control file from an f to a g. When the printer daemon reads the control file, this character tells it which output filter to use.
- Figure 13.4 shows the
lproptions that cause the different filters to be involved. The interaction of these different filters is not at all obvious from an inspection of Figure 13.4.
+--------+----------+--------------+------------------------------------------------+
| lpr | printcap | Control file | Description |
| option | keyword | character | |
+--------+----------+--------------+------------------------------------------------+
| | of | | per-job filter--see description below |
+--------+----------+--------------+------------------------------------------------+
| | if | f | default text filter for per-file accounting |
| -l | if | l | same as above with "literal" mode |
+--------+----------+--------------+------------------------------------------------+
| -p | | p | use /bin/pr command |
+--------+----------+--------------+------------------------------------------------+
| -c | cf | c | cifplot filter |
| -d | df | d | TEX filter |
| -g | gf | g | plot filter |
| -n | nf | n | ditroff filter |
| -f | rf | r | FORTRAN-style output filter |
| -t | tf | t | troff filter |
| -v | vf | v | raster filter |
+--------+----------+--------------+------------------------------------------------+
Figure 13.4 lpr options and filters
MYNOTE: The Control file character section describes the character which can be seen in the name of the control file. For instance, the one which we've been looking previously is cfA123orange. Not really sure what the printcap keyword is and it's significance.
-
First, group the filters into four categories, as shown above in Figure 13.4.
-
the
offilter -
the
iffilter -
the
/bin/prfilter -
the other seven data filters (the last seven entries in the table)
-
The
-loption tolprspecifies that the file is to be printed in literal mode--control characters are to be printed as is and the usual page breaks (form feeds by default) are not printed. All this option does is pass a special command line option to theiffilter, so we can consider it equivalent to theiffilter, for our purposes. -
The
iffilter is used by default. It is invoked for every file taht is printed. It usually does the printer accounting function, appending a line to an accounting file specifying the login name and the number of pages printed, for every line. -
We can consider the other seven data filters equivalent to the
iffilter. These seven filters handle special data formats, while theiffilter is for text files. -
If an
iffilter or one of the seven special data filters isn't specified for a file, theoffilter is used instead. But theoffilter is invoked only a single time for a print job, which might be comprised of multiple files. -
If the
iffilter or one of the seven special data filters is being used, theoffilter is bypassed, even if it is specified for the printer. -
The
-poption, to use the Unix/bin/prprogram before the file is printed, can be used with the other filters, or by itself. -
We can now consider six cases that handle all the different combinations of output filters. These cases are shown in Figure 13.5.
+---------------------+-----------+---------+-------+
| if or other | of filter | /bin/pr | Case |
| special data filter | | | below |
+---------------------+-----------+---------+-------+
| no | no | no | (1) |
| no | yes | no | (2) |
| yes | (ignored) | no | (3) |
| no | no | yes | (4) |
| no | yes | yes | (5) |
| yes | (ignored) | yes | (6) |
+---------------------+-----------+---------+-------+
Figure 13.5 Cases to consider for combinations of filters
-
[Case 1] This case was shown in the example earlier in this section. The
lpddaemon spawns a child process that prints all the data files on the printer device. No filters are involved. -
[Case 2] When only an
offilter is specified, thelpdchild process first creates a pipe and thenforks to create another process. This new child process uses thedupsystem call to assign the output end of the pipe to its standard input and to assign the printer device to its standard output. It thenexecs the filter program. Thelpdchild reads each data file and writes to the pipe for the filter process to print. We show this in Figure 13.6.
control file
data files
|
|
V
fork +-----------+ fork +-----------+ fd = 1
------->| lpd |-------------->| of |----------> printer device
| (child) | exec | filter |
+-----------+ +-----------+
| ^
| | fd = 0
| +-------+ |
+------>| pipe |---------------+
+-------+
Figure 13.6 Process and IPC for only an of filter
- [Case 3] When either an
iffilter or one of the seven special data filters is specified, the output filter is invoked for every file to print. Also, even if anoffilter was specified, it is not used. Before invoking the filter, thelpdchild uses thedupsystem call to attach the input data file to its standard input, and the printer device to its standard output. This is shown in Figure 13.7.
+---------------------------------------------------------------+
control file | |
data files | data files |
| | | |
| | | fd = 0 |
V | V |
fork +-----------+ | fork +-----------+ fd = 1 |
----------->| lpd |---|--------------->| filter |------------> printer device |
| (child) | | exec +-----------+ |
+-----------+ | |
| |
| |
+---------------------------------------------------------------+
once per data file
Figure 13.7 Processes and IPC for if or other data filter
- [Case 4] If the
/bin/prprogram is used as a filter, without anifor anoffilter, we have the same case as the previous, with the/bin/prprogram being used as the output filter. Figure 13.8 shows this case.
+---------------------------------------------------------------+
control file | |
data files | data files |
| | | |
| | | fd = 0 |
V | V |
fork +-----------+ | fork +-----------+ fd = 1 |
----------->| lpd |---|--------------->| /bin/pr |------------> printer device |
| (child) | | exec +-----------+ |
+-----------+ | |
| |
| |
+---------------------------------------------------------------+
once per data file
Figure 13.8 Processes and IPC for /bin/pr without other filters
Since the /bin/pr program reads from its standard input and writes to its standard output, it can be used just like the printer specific filters.
- [Case 5] When we have two filters between the
lpdchild process and printer device (the/bin/prfilter and theoffilter), the two filters must be connected with some form of IPC. The technique used is a pipe. The first step is for thelpdchild to create a pipe using thepipesystem call,forka copy of itself, and thenexectheoffilter. These are exactly the same steps used in case 2. This is shown in Figure 13.9. This first step is done only once by thelpdchild.
control file
data files
|
|
V
fork +-----------+ fork +-----------+ fd = 1
------->| lpd |-------------->| of |----------> printer device
| (child) | exec | filter |
+-----------+ +-----------+
| ^
| | fd = 0
| +-------+ |
+------>| pipe |---------------+
+-------+
Figure 13.9 First step when two filters are being used
Then, for every data to be printed, the /bin/pr filter is invoked. This is done by forking and execing the /bin/pr filter, having it use the previously created pipe for its standard output, and the data file as its standard input. We show this in Figure 13.10.
+---------------------------------------+
control file | |
data files | data files |
| | | |
| | | fd = 0 |
V | V |
fork +-----------+ | fork +-----------+ | +-----------+ fd = 1
----------->| lpd |---|--------------->| /bin/pr | | | of |----------> printer device
| (child) | | exec +-----------+ | | filter |
+-----------+ | | | +-----------+
| | fd = 1 | ^
| | | | fd = 0
+------------------------|--------------+ |
once per data file | |
| +----------+ |
+---------->| pipe |---------+
+----------+
Figure 13.10 Final arrangement when two filters are used
- [Case 6] The final case involves two filters between the
lpdchild and the printer device, but unlike the previous case, now both of these filters are invoked for every data file. Again, a pipeline is used as the form of IPC between the two filters. For each data file, the first step is to create a pipe and thenforkandexecthe/bin/prprogram, connecting the pipe from/bin/prback to thelpdchild process. Figure 13.11 shows this arrangement. Note that the data file to be printed is attached to the standard data input of the/bin/prfilter.
control file
data files data file
| |
| | fd = 0
V V
fork +-----------+ fork +-------------+
------->| lpd |-------------->| /bin/pr |
| (child) | exec | |
+-----------+ +-------------+
^ |
| fd = 0 | fd = 1
| +-------+ |
+-------| pipe |<--------------+
+-------+
Figure 13.11 First step when two filters are invoked for every data file
Next the lpd child process spawns the filter program, setting up the file descriptors before the exec so that the standard input for the filter is the pipe and the standard output is the printer device. This final arrangement is shown in Figure 13.12.
+--------------------------------------------------------------------------------+
control file | |
data files | data file |
| | | |
| | | fd = 0 |
V | V |
fork +-------------+ | fork +----------+ +---------+ |
--------->| lpd |--|------------>| filter | | /bin/pr | |
| (child) | | exec +----------+ +---------+ |
+-------------+ | | ^ | |
| fd = 1 | | fd = 0 | fd = 1 |
| | | +------+ | |
| | +----------| pipe |<-------------------+ |
| | +------+ |
| V |
| printer device |
| |
+--------------------------------------------------------------------------------+
once per data file
Figure 13.12 Final arrangement when two filters are invoked for every file
For simplicity, we do not show that the lpd process opens the printer's log file on its standard error before execing any of the filters. By doing this, any of the filters can write error messages to their standard error.
Remote Printing
- We now consider a request to print a file on a printer that resides on a remote host. The system administrator can set a flag for a printer in the
/etc/printcapfile that designates the printer as a remote printer and specifies the name of the remote host to send files to for printing. It is transparent to the users of thelprcommand that the actual printer is connected to a different computer system. If we assume that the name of the local system (the system on which thelprcommand is executed) is namedorangeand that the remote system (the one on which the files are printed) is namedapple, we can pick up the example from above at the pointer where the masterlpddaemon hasforked a copy of itself to handle the request. Thelpdchild process recognizes that the request is for a remote printer from the printer's entry in the/etc/printcapfile. Instead of sending it to the printer device, it opens a TCP connection to theprinterservice on the remote host, which is thelpddaemon on a 4.3BSD system. This gives us the processes shown in Figure 13.13. (The service nameprinteris converted to its TCP port number using thegetservbynamefunction, described in 8.2.).
+---------------------------------------+ +-------------------------------------------+
| | | |
| +-------+ | | |
| | lpr |---------> control file | | |
| +-------+ data files | | |
| | | | | |
| UNIX | domain IPC | | | |
| | | | | |
| V V | | |
| +-----------+ +-----------+ | Internet domain | +-------------+ fork +-------------+ |
| | local lpd | fork | local lpd |<--|-----------------------|-->| remote lpd |------->| remote lpd | |
| | (parent) |------>| (child) | | IPC | | daemon | | (child) | |
| +-----------+ +-----------+ | | +-------------+ +-------------+ |
| | | |
+---------------------------------------+ +-------------------------------------------+
local host orange remote host apple
Figure 13.13 Processes and IPC for remote printing
-
The master
lpddaemon on the remote host is waiting for one of two events to occur -
a local connection on a Unix domain socket from a local
lprcommand, as described previously. -
a remote connection on an Internet domain socket from a remote
lpddaemon, which we now describe. -
The
lpdprocess uses theselectsystem call to wait for either connection to occur. When a remote connection is received, the remotelpddaemon first verifies that the sending host has permission to send it print jobs, and thenforks a copy of itself. As before, it is the child process that does the actual work, allowing the parent to go back to its wait for a socket connection. This is another example of concurrent server as shown in Figure 13.14.
+-----------+ Internet domain IPC +------------+
| local lpd |<----------------------->| remote lpd |
| (child) | | (child) |
+-----------+ +------------+
Figure 13.14 Actual communication is between the two child process for remote printing
- The first communication across the socket occurs when the local process writes the following
4bytes to the socket
\002lp\n
This line consists of a byte containing the binary value 2 (shown above as \002), followed by the ASCII name of the printer (lp in this example), followed by a newline. This is the standard format for IPC messages to or from the daemon: a binary byte that specifies the message type, followed by the variable-length ASCII message, follwed by a newline. Acknowledgements usually consist of a single byte, with a byte of binary zero meaning all is OK, and other values indicating various error conditions. The first byte of this message, 2, specifies that the local host wants to send the remote daemon one or more files to print. This line is read by the remote process and it writes an acknowledgement message to the socket consisting of a single byte of zero. The local process now sends the data files to be printed, followed by the control file, to the remote process.
-
The actual data flow [for sending files to the server] is:
-
The local writes a message of the form
\003size filename\nto the remote process. The message type,
3, specifies that a data file is being sent. The first file transferred in our exmaple ismain.c. By specifying its size in bytes, the remote system can check that it has enough room for the file. Knowing the size before the file transfer also allows the receiving process to know when it has received the entire file. The filename in our example isdfA123orange. Note that the first line that was transferred across the socket between the two daemons specified the name of the destination printer (lp), so the receiving daemon can write the data file to the appropriate spool directory on the remote system. There is no need that the spool directory on the remote system be the same as on the remote system. The/etc/printcapfile on the remote system contains all the parameters for the remote spooling system.Note also that there cannot be filename conflicts in the remote spool directory with print files arriving from multiple hosts, each with their own sequence numbers. (Recall that the sequence number is assigned by the local host.) This is because the filename in the remote spool directory contains the host name.
-
If there is room on the remote system, the remote process responds with an acknowledgement consisting of a single byte of zero. If the file won't fit, the response is a single byte of two.
-
If there is room on the remote host, the local host now sends the actual data file. The entire file is transferred across the socket, followed by a byte of zero. Since the receiving host knows the size of the file, it knows when the entire file has been received. Note also that since the size of the file is known by the receiver before the file is transferred, there are no restrictions on what characters can be in the file. Either text files or binary files can be handled.
-
If the remote host receives the entire file and the final byte of zero, it responds with an acknowledgement of a byte of zero, otherwise it responds with a single byte of one.
-
Note that the network protocol being used by the two processes, TCP, is a reliable protocol with error handling built into it. The simple application protocol that we described above is between the two application processes (the two daemons). There are no checksums or block numbers specifically sent by the daemons in their messages--this is handled by TCP.
The four steps listed above (in previous point) are then repeated for the next data file, dfB123orange, which is a copy of the file subr.c, and then repeated again for the control file, cfA123orange. When the control file is transferred, the first byte of the message in step 1 is 2, instead of 3 (i.e., \002size <control-filename>\n), indicating that the file is a control file and not a data file. Once the files are successfully transferred to the remote system, they can be deleted from the local system. The local process goes through the control file and for every U line, unlinks the corresponding file. The control file is then deleted.
- Before closing the socket connection to the remote host, the local process first checks if any other print jobs have arrived that are to be sent to the remote host. If so, they are transferred. When there are no more jobs to transfer to the remote host, the local process terminates, which closes the socket. The remote process, which has executed a
readon the socket, waiting for some more files to be transferred, gets an end-of-file return from theread. It then goes through its spool directory and prints the files that have been queued (i.e., the jobs that it received from the local host, along with any other jobs that are queued for thelpprinter).
Two copies of the file are made when the file is printed on a remote printer. The first is made by the lpr process on the local host, and the other copy is made by the remote lpd daemon when it receives the file. Doing it this way makes it transparent to the local lpr process that the file is going to a remote host--the only process that needs to worry about sending the file to the remote host is the lpd process. Also, another benefit of the additional copy is that it is transparent to the local lpr process if the remote host is not available. The file is still spooled on the local system and can be transferred to the remote system by the lpd daemon as soon as the remote host is available. If we wanted to avoid the extra copy that is made, the lpr process would have to handle the transmission to the remote host and it would not be able to do the transfer if the remote host were down.
Security Issues for Remote Printing
-
We gave an overview of the network security used by 4.3BSD in Section 9.2. Figure 9.1 listed the steps taken by the
lpdserver. The steps are -
First, any request from a remote host to access a printer must arrive with a reserved TCP port number. (Reserved Internet ports were discussed in Section 6.8.)
-
Next, the remote host must appear in either the file
/etc/hosts.equivor the file/etc/hosts.lpd. If a host is not considered an equivalent host (probably talking about/etc/hosts.equiv) for thershdandrlogindservers (which we describe in Chapters 14 and 15), you can still give it access to your line printers by entering the host name in your/etc/hosts.lpdfile. This file is used only by the printer daemon, to allow printer access to nonequivalent hosts. -
Finally, the administrator has the option of specifying an option in the
/etc/printcapfile for any printer that restricts remote usage of the printer to users with accounts on the server's system. This option is handled by thelpddaemon. Recall from the comments in the_validuserfunction that we showed in Section 9.2, that the line printer server calls this function to validate a login name.
Ancillary Daemon Functions
- We described in detail one IPC message that the master
lpddaemon handles
\002printer\n
This line was read by the daemon on its Internet domain socket and asked the daemono to receive one or more files from a remote system for queueing on the local system.
-
The five message lines of this [above shown] form that the daemon handles are as follows:
-
check the queue for jobs and print any that are there,
-
receive a job from a remote system,
-
list the queue, short form,
-
list the queue, long form,
-
remove some jobs from the queue.
Step 2 from the first example of this chapter, when the lpr process sent a Unix domain message to the daemon after placing a job in the queue for a printer, sends the first of these messages to the daemon. The third and fourth messages are used by the lpq program to print the status of the queue for a given printer. The final message is used by the lprm program to remove a job from a print queue. The three programs, lpr, lpq, and lprm are the ones available for general users.
-
An additional program,
/etc/lpcis available for the system administrator for additional control over the spooling system. The commands provided by this program are -
abort: Kills an existing daemon (if one exists) and disables printing for one or more printers. The method used to disable printing is to turn on the owner-execute bit of thelockfile in the spooling directory for a given printer. -
start: Enables printing and starts a daemon for one or more printers. This command turns off the owner-execute bit of thelockfile in the spooling directory. -
clean: Remove any control files, data files, and temporary files that do not form a complete print job. -
enable: Enableslprto queue jobs for one or more printers. Queueing is enabled for a given printer if the group-execute bit of thelockfile in the printer's spool directory is off. -
disable: Disables queueing of print jobs bylprby turning on the group-execute bit of thelockfile in a printer's spool directory. -
restart: Attempts to start a newlpdchild process for a given printer. -
stop: Stops an lpd child process after the current job completes, then disables printing on the printer. -
up: Enables queueing and enables printing for a given printer. -
down: Turns queueing off and disables printing for a given printer. -
topq: Moves one or more print jobs to the top of the queue for a given printer. Since the processing of a printer's queue is done in a first-in, first-out order, based on the modification time of a job's control file, this command changes the modificaiton time of the specified job's control files. Additionally, the world-execute bit of the printer's lock file is turned on to tell thelpdchild process that the output queue for this printer must be rebuilt when it is finished with the current job. -
status: Displays the status of a given printer--queueing status, printing status, number of jobs to print, and whether anlpdchild process is active for the printer. -
The features that a system administrator needs control over--enabling and disabling the queue for a printer, and starting and stopping a printer--are controlled by the owner-execute bit and the group-execute bit of the printer's
lockfile. Recall that thelpdchild process for a given printer holds an exclusive lock on its lock file while it is active. Having the file locked by thelpdchild process does not prevent thelpcprocess from modifying these execute bits in the file's access control word. Hence, a printer'slockfile is used for six different purposes. -
The file is exclusively locked by an active
lpdchild process, to prevent multiple daemons from being invoked. -
If the owner-execute bit is on, printing is disabled.
-
If the group-execute bit is on, queueing is disabled.
-
If the world-execute bit is on, the
lpdchild process is to rebuild the queue after it is finished with the current job. -
The first line of the file is the ASCII process ID of the
lpdchild process, and is used bylpctokillthis process when turning a printer off. -
The second line is the name of the control file of the job being printed, and is used by the
lpqprocess.
System V Print Spooler
- There are some changes as compared to the 4.3BSD Line Printer Spooler. One of the changes is the command which is used by the client to print any file on a printer. The command used is
lpcommand which communicates with thelpscheddaemon found in System V. The command is of the form:
lp -plaser1 main.c subr.c
The -p flag is used to indicate the printer which is used for printing the files: main.c and subr.c.
Another thing that wasn't previously mentioned is, instead of using a specific printer like laser1 in the above example, one could also queue a print job using the printer class name. System V takes the naming one step further and allows names to be associated both with printers and with classes printers. For example, we can create a class of printers with the name laser that might include 3 different printers. We then specify either a specific printer, perhaps, laser1, laser2, or laser3, or the class name, in which case the system prints the job on the first avaiable printer in the class.
- The working of the
lpcommand is similar to thelprwe recently discussed. The text does mention that links are made to the files to be printed. That probably means that instead of copying the filename and its content, theln/linkcommand may be used to create either a symbolic link or a hard link (I think the former one is more appropriate). But if the link cannot be made (if the files are on a different filesystem from the/usr/spool/lpdirectory), then the full pathname of the two files is remembered bylp. Also, an option can be specified to havelpmake a copy of the files and print the copy. If a copy is not made, then any modifications made to the files before they are printed will appear in the printed output (my reason to belive that thelpcommands symbolically links the file rather than a hard link). For the example above, the spool files created can be represented as:
(Assuming links can be made to the user's files, three files are created by lp in the directory /usr/spool/lp/request/laser1).
+-------------------+
user process +------>| data file | /usr/spool/lp/request/laser1/d5-123
+--------+ +---------+ | | (link to main.c) |
| main.c |------->| |-----------+ +-------------------+
+--------+ | |
| | +-------------------+
| lp |------------------>| data file | /usr/spool/lp/request/laser1/d6-123
| | | (link to subr.c) |
+--------+ | | +-------------------+
| subr.c |------->| |-----------+
+--------+ +---------+ | +-------------------+
+------>| request file | /usr/spool/lp/request/laser1/r-123
+-------------------+
Figure 13.15 Spool files created by System V lp command
-
There exist some filesystem restriction on System V's
lpcommand as compared to 4.3BSD'slprcommand. The System V line printer spooler requires that all directories and files start at the/usr/spool/lpdirectory, while the 4.3BSD only requires the file/etc/printcapto be in a known location, with all spool directories pointed to by theprintcapfile. -
Similar to the "control" file for
lpr, thelpcommand works on the "request" file, which specifies the parameters for the print job. An example of what the request file looks like is:
T
C 1
O
F d5-123
F d6-123
Each line in the request file starts with a letter that specifies a parameter for the printer daemon.
C number of copies, -n option
F name of file to print
O printer-specific options, -o option
T optional title, -t option
-
As with the 4.3BSD spooler, a sequence number is chosen for each print job by the
lpcommand. Unlike the 4.3BSD system, the System V spooler uses a single sequence number file for all print jobs. The 4.3BSD system uses a separate sequence number file for each printer. A locking mechanism makes certain that multiple invocations oflpdon't interfere with each other in obtaining the next sequence number. In the example above we assume the sequence number is123. The names of the data files (the names of the links to the data files) are of the formdn-seqno where n is an integer that is incremented for every job that gets printed (the numbers 5 and 6 in our example) and seqno is the sequence number. -
An entry is appended to the file
/usr/spool/lp/outputq. This is a binary file whose function is described in page 560/561 of text. -
The System V line printer system uses a FIFO (named pipe) to commuincate between the user's
lpprocesses and the system's printer daemonlpsched. After creating a request file and any required data files, thelpprocess writes a single line to the file/usr/spool/lp/FIFOwhich is a FIFO. In our example the line could be
r laser1 123 stevens\n
The first character, r, specifies that this is a print request (the FIFO is used for other messages between other processes and the daemon) and it is followed by the destination name (laser1), the sequence number and the name of the user who executed the lp command. The second and third parameters allow the daemon to go to the file /usr/spool/lp/request/laser1/r-123 to find the request file for this job. We specifically show the newline character at the end, as these are used as the message delimiters for the data in the FIFO. The communication between the user process and the daemon is shown in Figure 13.16.
user process daemon process
+-----------------+ +--------------------+
| lp | | lpsched |
+-----------------+ +--------------------+
| ^
| |
| FIFO |
| +-----------+ |
+-------------->| |-----------------------+
+-----------+
/usr/spool/lp/FIFO
Figure 13.16 IPC through FIFO between lp and lpsched daemon
-
After writing to the FIFO the
lpprocess is finished. Note that multiplelpprocesses that might be executing at the same time have their writes to the FIFO handled correctly by the kernel. Recall from Section 3.5 that writes to a pipe or FIFO are guaranteed to be atomic, when the size of the write is less than the capacity of the FIFO, as it is here. -
We now follow the actions of the system printer daemon,
lpsched, when it receives a request to print a job. As with the 4.3BSD daemon, it does aforkto have a child process handle each job, but the actions of the System V daemon are more complicated. -
If the requested printer is not busy, and it is enabled for printing, the daemon
forks a copy of itself, and the "master" daemon (the parent) goes back to waiting for a request to be written to the FIFO. The parent remembers that the printer is now busy and it also remembers the process ID of the child process, so that it canwaitfor it later. -
The child process (we'll call it the first child) opens and reads the request file. It builds a command line that is used for executing the interface program for the specified printer. In our example the command line is
interface/laser1 laser1-123 stevens "" 1 "" /usr/spool/lp/request/laser1/d5-123 /usr/spool/lp/request/laser1/d6-123(The line above may be rendered as two lines, depending on the screen size.) The interface program expects its arguments to be as shown in Figure 13.17.
+----------+--------------------------------------------------------------------------------+
| Argument | Description |
+----------+--------------------------------------------------------------------------------+
| 0 | Name of the interface program. The interface program can obtain the name of |
| | the printer from the last portion of this argument, using the Unix `basename` |
| | command, for example. |
+----------+--------------------------------------------------------------------------------+
| 1 | Request name. |
+----------+--------------------------------------------------------------------------------+
| 2 | Login name. |
+----------+--------------------------------------------------------------------------------+
| 3 | Title. Shown above as an empty string. |
+----------+--------------------------------------------------------------------------------+
| 4 | Number of copies to print. |
+----------+--------------------------------------------------------------------------------+
| 5 | Options. Shown above as an empty string. This is where the `-o` options on |
| | the `lp` command are passed to the actual interface program. |
+----------+--------------------------------------------------------------------------------+
| 6 | First file to print. |
+----------+--------------------------------------------------------------------------------+
| 7 | Second file to print. |
+----------+--------------------------------------------------------------------------------+
| ... | |
+----------+--------------------------------------------------------------------------------+
Figure 13.17 Command line arguments to printer interface program(The first column in Figure 13.17, 0, 1, etc., is the index of the argument. In a C program the arguments would be
argv[0],argv[1], and so on.) The first child opens the actual printer device and attaches it to both standard ouput and standard error. The standard input is attached to the empty device/dev/null, in case the interface program attempts to read from standard input. If the device's file permission bit allows reading, then both standard output and standard error are opened for reading and writing. Otherwise, they are opened for writing only. The attempt to open the device for reading is for those devices that present status information that the interface program might want to read. (The 4.3BSD print spooler has a similar feature that is specified as a per-printer option in the/etc/printcapentry for each printer.)The first child does another
forkand it is the second child that does theexecof the interface program, using a command line as shown above. -
The second child does the
execand the first childwaits for the interface program to complete. It is the interface program that reads the files to be printed and prints them. Most interface programs are shell scripts, and you can look at them by perusing through the files in/usr/spool/lp/interface. Note that there is no assumption by anyone whether the interface program is a shell script, a C program, or any other type of program--if a program can beexeced and passed an argument list, it can be an interface program.When the second child has finished and calls
exit, the first child has itswaitreturn. The first child then deletes the request file and any data files that it created in the spool directory (the fileslaser1/d5-123andlaser1/d6-123from the example). It then sends an IPC message back to the master daemon (the parent) notifying it that the specified printer has finished and is ready for more output.The picture we have from these steps is shown in Figure 13.18.
+---------+
FIFO----------->| lpsched |---------+
+---------+ |
| fork
V
+---------------+
| lpsched |
| (first child) |
+---------------+
^ |
| |
| | fork
+-------------------+ |
| V
+--------------+ +----------------+
| request file | | lpsched |--------------+
+--------------+ | (second child) | |
/usr/spool/lp/request/laser1/r-123 +----------------+ |
| exec
V
+-----------------------+
| printer interface |
| program |
+-----------------------+
+-------------------+ ^ ^ |
| data file | | | |
| (link to main.c) |-----------------------------------+ | |
+-------------------+ | |
/usr/spool/lp/request/laser1/d5-123 | |
| |
+-------------------+ | |
| data file | | |
| (link to subr.c) |-------------------------------------------+ |
+-------------------+ |
/usr/spool/lp/request/laser1/d6-123 |
V
printer device
Figure 13.18 Processes and files used by System V print spoolerOne point to note is that there is no requirement that the interface program print anything. What is provided by the System V line printer spooler is a general purpose scheduler that could be used for purposes other than printing files on a line printer. Since it is easy to implement your own interface programs, you can have them do whatever you wish. Be aware that there are some basic limitations in the scheduling algorithms currently used by the spooler--for example, you cannot assign priorities to different requests, and you cannot have the spooler process the jobs in any order other than first-in, first-out.
When the master printer daemon is started, usually from the file
/etc/rcduring system initialization, it creates a file named/usr/spool/lp/SCHEDLOCKand locks this file. This is to prevent any additional copies of this daemon from starting, as long as the original daemon is still in existence. This same technique was used by the 4.3BSD daemon and it is a standard technique that programs can use to assure that only a single copy is running. -
The last paragraph from the previous points provides a good way for a programmar to create a daemon, or rather a "master" daemon. When the daemon process first runs, it can
opena file, like/path/to/process/MYDAEMON, and then obtain an exclusive lock for that file. This way, one can assure that multiple copies of master daemon cannot be run simultaneously. Normally, the call toopenwould be in a "blocking" state as we are opening a file with the exclusive lock. To avoid this, we can specify a non-blocking flag which allows the system call to immediately return if the file can't be opened. -
The
Ancillary Daemon FunctionsSection of the text is not presented here. Interested reader could refer to page 559 of the text. -
There are three exercises given in the text. These questions are similar, they ask for some implementation techniques that can be used for, say, a
lpclient to initiate a job on a printer attached to a 4.3BSD system. Also, another question, but forlprclient and printer attached to System V machine. The last question is about usinglpprogram to print a file on a printer attached to another System V machine using the TCP/IP link. The last question plays a crucial role as it drives the reader to implement alpdstyle daemon which is capable of listening to Internet domain sockets. Like how thelpdfunctions, we can make themy_lpsched_minidaemon to listen to a reserved port where the client can request for a print job. The client useslpcommand to make a print job and required files (data files and request files). The client can then send this information over the Internet socket and print the files. Of course there are a lot of details that has not been considered, but the answer only attempts to cover the focal point of the question. For the first two questions, we need some way to create appropriate control/request file, depending on which system the printer is attached to. For 4.3BSD system that is attached to the printer and the client useslpprogram, we can make some modifications such that data files and "request" files are compatible with thelpdthat the 4.3BSD uses. The name of the data files must be appropriately modified, as well as content of "request" file such that it resembles the "control" file thelpduses. Likewise, similar strategy can be used for alprclient which is used for communicating with thelpscheddaemon on a System V machine which is attached to a printer. There are no solutions provided for these exercises, as even if there was some solution, verification could not be done by me because: (1) lack of System V/4.3BSD machine and (2) I don't own a printer (and most printers seems to have their own application protocol,IPP).
Extras
printcapfile format manual: https://man.freebsd.org/cgi/man.cgi?query=printcap&sektion=5&format=htmlIPP: RFC 2565