21.5. Source Code
The source code for this chapter comprises five files, not including some of the common library routines we've used in earlier chapters:
ipp.h | Header file containing IPP definitions | print.h | Header containing common constants, data structure definitions, and utility routine declarations | util.c | Utility routines used by the two programs | print.c | The C source file for the command used to print a file | printd.c | The C source file for the printer spooling daemon |
We will study each file in the order listed.
We start with the ipp.h header file.
1 #ifndef _IPP_H
2 #define _IPP_H
3 /*
4 * Defines parts of the IPP protocol between the scheduler
5 * and the printer. Based on RFC2911 and RFC2910.
6 */
7 /*
8 * Status code classes.
9 */
10 #define STATCLASS_OK(x) ((x) >= 0x0000 && (x) <= 0x00ff)
11 #define STATCLASS_INFO(x) ((x) >= 0x0100 && (x) <= 0x01ff)
12 #define STATCLASS_REDIR(x) ((x) >= 0x0200 && (x) <= 0x02ff)
13 #define STATCLASS_CLIERR(x)((x) >= 0x0400 && (x) <= 0x04ff)
14 #define STATCLASS_SRVERR(x)((x) >= 0x0500 && (x) <= 0x05ff)
15 /*
16 * Status codes.
17 */
18 #define STAT_OK 0x0000 /* success */
19 #define STAT_OK_ATTRIGN 0x0001 /* OK; some attrs ignored */
20 #define STAT_OK_ATTRCON 0x0002 /* OK; some attrs conflicted */
21 #define STAT_CLI_BADREQ 0x0400 /* invalid client request */
22 #define STAT_CLI_FORBID 0x0401 /* request is forbidden */
23 #define STAT_CLI_NOAUTH 0x0402 /* authentication required */
24 #define STAT_CLI_NOPERM 0x0403 /* client not authorized */
25 #define STAT_CLI_NOTPOS 0x0404 /* request not possible */
26 #define STAT_CLI_TIMOUT 0x0405 /* client too slow */
27 #define STAT_CLI_NOTFND 0x0406 /* no object found for URI */
28 #define STAT_CLI_OBJGONE 0x0407 /* object no longer available */
29 #define STAT_CLI_TOOBIG 0x0408 /* requested entity too big */
30 #define STAT_CLI_TOOLNG 0x0409 /* attribute value too large */
31 #define STAT_CLI_BADFMT 0x040a /* unsupported doc format */
32 #define STAT_CLI_NOTSUP 0x040b /* attributes not supported */
33 #define STAT_CLI_NOSCHM 0x040c /* URI scheme not supported */
34 #define STAT_CLI_NOCHAR 0x040d /* charset not supported */
35 #define STAT_CLI_ATTRCON 0x040e /* attributes conflicted */
36 #define STAT_CLI_NOCOMP 0x040f /* compression not supported */
37 #define STAT_CLI_COMPERR 0x0410 /* data can't be decompressed */
38 #define STAT_CLI_FMTERR 0x0411 /* document format error */
39 #define STAT_CLI_ACCERR 0x0412 /* error accessing data */
[114] | We start the ipp.h header with the standard #ifdef to prevent errors when it is included twice in the same file. Then we define the classes of IPP status codes (see Section 13 in RFC 2911). | [1539] | We define specific status codes based on RFC 2911. We don't use these codes in the program shown here; their use is left as an exercise (See Exercise 21.1). |
40 #define STAT_SRV_INTERN 0x0500 /* unexpected internal error */
41 #define STAT_SRV_NOTSUP 0x0501 /* operation not supported */
42 #define STAT_SRV_UNAVAIL 0x0502 /* service unavailable */
43 #define STAT_SRV_BADVER 0x0503 /* version not supported */
44 #define STAT_SRV_DEVERR 0x0504 /* device error */
45 #define STAT_SRV_TMPERR 0x0505 /* temporary error */
46 #define STAT_SRV_REJECT 0x0506 /* server not accepting jobs */
47 #define STAT_SRV_TOOBUSY 0x0507 /* server too busy */
48 #define STAT_SRV_CANCEL 0x0508 /* job has been canceled */
49 #define STAT_SRV_NOMULTI 0x0509 /* multi-doc jobs unsupported */
50 /*
51 * Operation IDs
52 */
53 #define OP_PRINT_JOB 0x02
54 #define OP_PRINT_URI 0x03
55 #define OP_VALIDATE_JOB 0x04
56 #define OP_CREATE_JOB 0x05
57 #define OP_SEND_DOC 0x06
58 #define OP_SEND_URI 0x07
59 #define OP_CANCEL_JOB 0x08
60 #define OP_GET_JOB_ATTR 0x09
61 #define OP_GET_JOBS 0x0a
62 #define OP_GET_PRINTER_ATTR 0x0b
63 #define OP_HOLD_JOB 0x0c
64 #define OP_RELEASE_JOB 0x0d
65 #define OP_RESTART_JOB 0x0e
66 #define OP_PAUSE_PRINTER 0x10
67 #define OP_RESUME_PRINTER 0x11
68 #define OP_PURGE_JOBS 0x12
69 /*
70 * Attribute Tags.
71 */
72 #define TAG_OPERATION_ATTR 0x01 /* operation attributes tag */
73 #define TAG_JOB_ATTR 0x02 /* job attributes tag */
74 #define TAG_END_OF_ATTR 0x03 /* end of attributes tag */
75 #define TAG_PRINTER_ATTR 0x04 /* printer attributes tag */
76 #define TAG_UNSUPP_ATTR 0x05 /* unsupported attributes tag */
[4049] | We continue to define status codes. The ones in the range 0x500 to 0x5ff are server error codes. All codes are described in Sections 13.1.1 through 13.1.5 in RFC 2911. | [5068] | We define the various operation IDs next. There is one ID for each task defined by IPP (see Section 4.4.15 in RFC 2911). In our example, we will use only the print-job operation. | [6976] | The attribute tags delimit the attribute groups in the IPP request and response messages. The tag values are defined in Section 3.5.1 of RFC 2910. |
77 /*
78 * Value Tags.
79 */
80 #define TAG_UNSUPPORTED 0x10 /* unsupported value */
81 #define TAG_UNKNOWN 0x12 /* unknown value */
82 #define TAG_NONE 0x13 /* no value */
83 #define TAG_INTEGER 0x21 /* integer */
84 #define TAG_BOOLEAN 0x22 /* boolean */
85 #define TAG_ENUM 0x23 /* enumeration */
86 #define TAG_OCTSTR 0x30 /* octetString */
87 #define TAG_DATETIME 0x31 /* dateTime */
88 #define TAG_RESOLUTION 0x32 /* resolution */
89 #define TAG_INTRANGE 0x33 /* rangeOfInteger */
90 #define TAG_TEXTWLANG 0x35 /* textWithLanguage */
91 #define TAG_NAMEWLANG 0x36 /* nameWithLanguage */
92 #define TAG_TEXTWOLANG 0x41 /* textWithoutLanguage */
93 #define TAG_NAMEWOLANG 0x42 /* nameWithoutLanguage */
94 #define TAG_KEYWORD 0x44 /* keyword */
95 #define TAG_URI 0x45 /* URI */
96 #define TAG_URISCHEME 0x46 /* uriScheme */
97 #define TAG_CHARSET 0x47 /* charset */
98 #define TAG_NATULANG 0x48 /* naturalLanguage */
99 #define TAG_MIMETYPE 0x49 /* mimeMediaType */
100 struct ipp_hdr {
101 int8_t major_version; /* always 1 */
102 int8_t minor_version; /* always 1 */
103 union {
104 int16_t op; /* operation ID */
105 int16_t st; /* status */
106 } u;
107 int32_t request_id; /* request ID */
108 char attr_group[1]; /* start of optional attributes group */
109 /* optional data follows */
110 };
111 #define operation u.op
112 #define status u.st
113 #endif /* _IPP_H */
[7799] | The value tags indicate the format of individual attributes and parameters. They are defined in Section 3.5.2 of RFC 2910. | [100113] | We define the structure of an IPP header. Request messages start with the same header as response messages, except that the operation ID in the request is replaced by a status code in the response. | | We end the header file with a #endif to match the #ifdef at the start of the file. |
The next file is the print.h header.
1 #ifndef _PRINT_H
2 #define _PRINT_H
3 /*
4 * Print server header file.
5 */
6 #include <sys/socket.h>
7 #include <arpa/inet.h>
8 #if defined(BSD) || defined(MACOS)
9 #include <netinet/in.h>
10 #endif
11 #include <netdb.h>
12 #include <errno.h>
13 #define CONFIG_FILE "/etc/printer.conf"
14 #define SPOOLDIR "/var/spool/printer"
15 #define JOBFILE "jobno"
16 #define DATADIR "data"
17 #define REQDIR "reqs"
18 #define FILENMSZ 64
19 #define FILEPERM (S_IRUSR|S_IWUSR)
20 #define USERNM_MAX 64
21 #define JOBNM_MAX 256
22 #define MSGLEN_MAX 512
23 #ifndef HOST_NAME_MAX
24 #define HOST_NAME_MAX 256
25 #endif
26 #define IPP_PORT 631
27 #define QLEN 10
28 #define IBUFSZ 512 /* IPP header buffer size */
29 #define HBUFSZ 512 /* HTTP header buffer size */
30 #define IOBUFSZ 8192 /* data buffer size */
[112] | We include all header files that an application might need if it included this header. This makes it easy for applications to include print.h without having to track down all the header dependencies. | [1317] | We define the files and directories for the implementation. Copies of the files to be printed will be stored in the directory /var/spool/printer/data; control information for each request will be stored in the directory /var/spool/printer/reqs. The file containing the next job number is /var/spool/printer/jobno. | [1830] | Next, we define limits and constants. FILEPERM is the permissions used when creating copies of files submitted to be printed. The permissions are restrictive because we don't want ordinary users to be able to read one another's files while they are waiting to be printed. IPP is defined to use port 631. The QLEN is the backlog parameter we pass to listen (see Section 16.4 for details). |
31 #ifndef ETIME
32 #define ETIME ETIMEDOUT
33 #endif
34 extern int getaddrlist(const char *, const char *,
35 struct addrinfo **);
36 extern char *get_printserver(void);
37 extern struct addrinfo *get_printaddr(void);
38 extern ssize_t tread(int, void *, size_t, unsigned int);
39 extern ssize_t treadn(int, void *, size_t, unsigned int);
40 extern int connect_retry(int, const struct sockaddr *, socklen_t);
41 extern int initserver(int, struct sockaddr *, socklen_t, int);
42 /*
43 * Structure describing a print request.
44 */
45 struct printreq {
46 long size; /* size in bytes */
47 long flags; /* see below */
48 char usernm[USERNM_MAX]; /* user's name */
49 char jobnm[JOBNM_MAX]; /* job's name */
50 };
51 /*
52 * Request flags.
53 */
54 #define PR_TEXT 0x01 /* treat file as plain text */
55 /*
56 * The response from the spooling daemon to the print command.
57 */
58 struct printresp {
59 long retcode; /* 0=success, !0=error code */
60 long jobid; /* job ID */
61 char msg[MSGLEN_MAX]; /* error message */
62 };
63 #endif /* _PRINT_H */
[3133] | Some platforms don't define the error ETIME, so we define it to an alternate error code that makes sense for these systems. | [3441] | Next, we declare all the public routines contained in util.c (we'll look at these next). Note that the connect_retry function, from Figure 16.9, and the initserver function, from Figure 16.20, are not included in util.c. | [4263] | The printreq and printresp structures define the protocol between the print command and the printer spooling daemon. The print command sends the printreq structure defining the user name, job name, and file size to the printer spooling daemon. The spooling daemon responds with a printresp structure consisting of a return code, a job ID, and an error message if the request failed. |
The next file we will look at is util.c, the file containing utility routines.
1 #include "apue.h"
2 #include "print.h"
3 #include <ctype.h>
4 #include <sys/select.h>
5 #define MAXCFGLINE 512
6 #define MAXKWLEN 16
7 #define MAXFMTLEN 16
8 /*
9 * Get the address list for the given host and service and
10 * return through ailistpp. Returns 0 on success or an error
11 * code on failure. Note that we do not set errno if we
12 * encounter an error.
13 *
14 * LOCKING: none.
15 */
16 int
17 getaddrlist(const char *host, const char *service,
18 struct addrinfo **ailistpp)
19 {
20 int err;
21 struct addrinfo hint;
22 hint.ai_flags = AI_CANONNAME;
23 hint.ai_family = AF_INET;
24 hint.ai_socktype = SOCK_STREAM;
25 hint.ai_protocol = 0;
26 hint.ai_addrlen = 0;
27 hint.ai_canonname = NULL;
28 hint.ai_addr = NULL;
29 hint.ai_next = NULL;
30 err = getaddrinfo(host, service, &hint, ailistpp);
31 return(err);
32 }
[17] | We first define the limits needed by the functions in this file. MAXCFGLINE is the maximum size of a line in the printer configuration file, MAXKWLEN is the maximum size of a keyword in the configuration file, and MAXFMTLEN is the maximum size of the format string we pass to sscanf. | [832] | The first function is getaddrlist. It is a wrapper for getaddrinfo (Section 16.3.3), since we always call getaddrinfo with the same hint structure. Note that we need no mutex locking in this function. The LOCKING comment at the beginning of each function is intended only for documenting multithreaded locking. This comment lists the assumptions, if any, that are made regarding the locking, tells which locks the function might acquire or release, and tells which locks must be held to call the function. |
33 /*
34 * Given a keyword, scan the configuration file for a match
35 * and return the string value corresponding to the keyword.
36 *
37 * LOCKING: none.
38 */
39 static char *
40 scan_configfile(char *keyword)
41 {
42 int n, match;
43 FILE *fp;
44 char keybuf[MAXKWLEN], pattern[MAXFMTLEN];
45 char line[MAXCFGLINE];
46 static char valbuf[MAXCFGLINE];
47 if ((fp = fopen(CONFIG_FILE, "r")) == NULL)
48 log_sys("can't open %s", CONFIG_FILE);
49 sprintf(pattern, "%%%ds %%%ds", MAXKWLEN-1, MAXCFGLINE-1);
50 match = 0;
51 while (fgets(line, MAXCFGLINE, fp) != NULL) {
52 n = sscanf(line, pattern, keybuf, valbuf);
53 if (n == 2 && strcmp(keyword, keybuf) == 0) {
54 match = 1;
55 break;
56 }
57 }
58 fclose(fp);
59 if (match != 0)
60 return(valbuf);
61 else
62 return(NULL);
63 }
[3346] | The scan_configfile function searches through the printer configuration file for the specified keyword. | [4763] | We open the configuration file for reading and build the format string corresponding to the search pattern. The notation %%%ds builds a format specifier that limits the string size so we don't overrun the buffers used to store the strings on the stack. We read the file one line at a time and scan for two strings separated by white space; if we find them, we compare the first string with the keyword. If we find a match or we reach the end of the file, the loop ends and we close the file. If the keyword matches, we return a pointer to the buffer containing the string after the keyword; otherwise, we return NULL. | | The string returned is stored in a static buffer (valbuf), which can be overwritten on successive calls. Thus, scan_configfile can't be called by a multithreaded application unless we take care to avoid calling it from multiple threads at the same time. |
64 /*
65 * Return the host name running the print server or NULL on error.
66 *
67 * LOCKING: none.
68 */
69 char *
70 get_printserver(void)
71 {
72 return(scan_configfile("printserver"));
73 }
74 /*
75 * Return the address of the network printer or NULL on error.
76 *
77 * LOCKING: none.
78 */
79 struct addrinfo *
80 get_printaddr(void)
81 {
82 int err;
83 char *p;
84 struct addrinfo *ailist;
85 if ((p = scan_configfile("printer")) != NULL) {
86 if ((err = getaddrlist(p, "ipp", &ailist)) != 0) {
87 log_msg("no address information for %s", p);
88 return(NULL);
89 }
90 return(ailist);
91 }
92 log_msg("no printer address specified");
93 return(NULL);
94 }
[6473] | The get_printserver function is simply a wrapper function that calls scan_configfile to find the name of the computer system where the printer spooling daemon is running. | [7494] | We use the get_printaddr function to get the address of the network printer. It is similar to the previous function except that when we find the name of the printer in the configuration file, we use the name to find the corresponding network address. | | Both get_printserver and get_printaddr call scan_configfile. If it can't open the printer configuration file, scan_configfile calls log_sys to print an error message and exit. Although get_printserver is meant to be called from a client command and get_printaddr is meant to be called from the daemon, having both call log_sys is OK, because we can arrange for the log functions to print to the standard error instead of to the log file by setting a global variable. |
95 /*
96 * "Timed" read - timout specifies the # of seconds to wait before
97 * giving up (5th argument to select controls how long to wait for
98 * data to be readable). Returns # of bytes read or -1 on error.
99 *
100 * LOCKING: none.
101 */
102 ssize_t
103 tread(int fd, void *buf, size_t nbytes, unsigned int timout)
104 {
105 int nfds;
106 fd_set readfds;
107 struct timeval tv;
108 tv.tv_sec = timout;
109 tv.tv_usec = 0;
110 FD_ZERO(&readfds);
111 FD_SET(fd, &readfds);
112 nfds = select(fd+1, &readfds, NULL, NULL, &tv);
113 if (nfds <= 0) {
114 if (nfds == 0)
115 errno = ETIME;
116 return(-1);
117 }
118 return(read(fd, buf, nbytes));
119 }
[95107] | We provide a function called TRead to read a specified number of bytes, but block for at most timout seconds before giving up. This function is useful when reading from a socket or a pipe. If we don't receive data before the specified time limit, we return 1 with errno set to ETIME. If data is available within the time limit, we return at most nbytes bytes of data, but we can return less than requested if all the data doesn't arrive in time. | | We will use TRead to prevent denial-of-service attacks on the printer spooling daemon. A malicious user might repeatedly try to connect to the daemon without sending it data, just to prevent other users from being able to submit print jobs. By giving up after a reasonable amount of time, we prevent this from happening. The tricky part is selecting a suitable timeout value that is large enough to prevent premature failures when the system is under load and tasks are taking longer to complete. If we choose a value too large, however, we might enable denial-of-service attacks by allowing the daemon to consume too many resources to process the pending requests. | [108119] | We use select to wait for the specified file descriptor to be readable. If the time limit expires before data is available to be read, select returns 0, so we set errno to ETIME in this case. If select fails or times out, we return 1. Otherwise, we return whatever data is available. |
120 /*
121 * "Timed" read - timout specifies the number of seconds to wait
122 * per read call before giving up, but read exactly nbytes bytes.
123 * Returns number of bytes read or -1 on error.
124 *
125 * LOCKING: none.
126 */
127 ssize_t
128 treadn(int fd, void *buf, size_t nbytes, unsigned int timout)
129 {
130 size_t nleft;
131 ssize_t nread;
132 nleft = nbytes;
133 while (nleft > 0) {
134 if ((nread = tread(fd, buf, nleft, timout)) < 0) {
135 if (nleft == nbytes)
136 return(-1); /* error, return -1 */
137 else
138 break; /* error, return amount read so far */
139 } else if (nread == 0) {
140 break; /* EOF */
141 }
142 nleft -= nread;
143 buf += nread;
144 }
145 return(nbytes - nleft); /* return >= 0 */
146 }
[120146] | We also provide a variation of TRead, called treadn, that reads exactly the number of bytes requested. This is similar to the readn function described in Section 14.8, but with the addition of the timeout parameter. | | To read exactly nbytes bytes, we have to be prepared to make multiple calls to read. The difficult part is trying to apply a single timeout value to multiple calls to read. We don't want to use an alarm, because signals can be messy to deal with in multithreaded applications. We can't rely on the system updating the timeval structure on return from select to indicate the amount of time left, because many platforms do not support this (Section 14.5.1). Thus, we compromise and define the timeout value in this case to apply to an individual read call. Instead of limiting the total amount of time we wait, it limits the amount of time we'll wait in every iteration of the loop. The maximum time we can wait is bounded by (nbytes x timout) seconds (worst case, we'll receive only 1 byte at a time). | | We use nleft to record the number of bytes remaining to be read. If TRead fails and we have received data in a previous iteration, we break out of the while loop and return the number of bytes read; otherwise, we return 1. |
The command used to submit a print job is shown next. The C source file is print.c.
1 /*
2 * The client command for printing documents. Opens the file
3 * and sends it to the printer spooling daemon. Usage:
4 * print [-t] filename
5 */
6 #include "apue.h"
7 #include "print.h"
8 #include <fcntl.h>
9 #include <pwd.h>
10 /*
11 * Needed for logging funtions.
12 */
13 int log_to_stderr = 1;
14 void submit_file(int, int, const char *, size_t, int);
15 int
16 main(int argc, char *argv[])
17 {
18 int fd, sockfd, err, text, c;
19 struct stat sbuf;
20 char *host;
21 struct addrinfo *ailist, *aip;
22 err = 0;
23 text = 0;
24 while ((c = getopt(argc, argv, "t")) != -1) {
25 switch (c) {
26 case 't':
27 text = 1;
28 break;
29 case '?':
30 err = 1;
31 break;
32 }
33 }
[114] | We need to define an integer called log_to_stderr to be able to use the log functions in our library. If set to a nonzero value, error messages will be sent to the standard error stream instead of to a log file. Although we don't use any logging functions in print.c, we do link util.o with print.o to build the executable print command, and util.c contains functions for both user commands and daemons. | [1533] | We support one option, -t, to force the file to be printed as text (instead of as a PostScript program, for example). We use the getopt(3) function to process the command options. |
34 if (err || (optind != argc - 1))
35 err_quit("usage: print [-t] filename");
36 if ((fd = open(argv[optind], O_RDONLY)) < 0)
37 err_sys("print: can't open %s", argv[1]);
38 if (fstat(fd, &sbuf) < 0)
39 err_sys("print: can't stat %s", argv[1]);
40 if (!S_ISREG(sbuf.st_mode))
41 err_quit("print: %s must be a regular file\n", argv[1]);
42 /*
43 * Get the hostname of the host acting as the print server.
44 */
45 if ((host = get_printserver()) == NULL)
46 err_quit("print: no print server defined");
47 if ((err = getaddrlist(host, "print", &ailist)) != 0)
48 err_quit("print: getaddrinfo error: %s", gai_strerror(err));
49 for (aip = ailist; aip != NULL; aip = aip->ai_next) {
50 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
51 err = errno;
52 } else if (connect_retry(sockfd, aip->ai_addr,
53 aip->ai_addrlen) < 0) {
54 err = errno;
[3441] | When getopt completes processing the command options, it leaves the variable optind set to the index of the first nonoptional argument. If this is any value other than the index of the last argument, then the wrong number of arguments was specified (we support only one nonoptional argument). Our error processing includes checks to ensure that we can open the file to be printed and that it is a regular file (as opposed to a directory or other type of file). | [4248] | We get the name of the printer spooling daemon by calling the get_printserver function from util.c and then translate the host name into a network address by calling getaddrlist (also from util.c). | | Note that we specify the service as "print." As part of installing the printer spooling daemon on a system, we need to make sure that /etc/services (or the equivalent database) has an entry for the printer service. When we select a port number for the daemon, it would be a good idea to select one that is privileged, to prevent malicious users from writing applications that pretend to be a printer spooling daemon but instead steal copies of the files we try to print. This means that the port number should be less than 1,024 (recall Section 16.3.4) and that our daemon will have to run with superuser privileges to allow it to bind to a reserved port. | [4954] | We try to connect to the daemon using one address at a time from the list returned by getaddrinfo. We will try to send the file to the daemon using the first address to which we can connect. |
55 } else {
56 submit_file(fd, sockfd, argv[1], sbuf.st_size, text);
57 exit(0);
58 }
59 }
60 errno = err;
61 err_ret("print: can't contact %s", host);
62 exit(1);
63 }
64 /*
65 * Send a file to the printer daemon.
66 */
67 void
68 submit_file(int fd, int sockfd, const char *fname, size_t nbytes,
69 int text)
70 {
71 int nr, nw, len;
72 struct passwd *pwd;
73 struct printreq req;
74 struct printresp res;
75 char buf[IOBUFSZ];
76 /*
77 * First build the header.
78 */
79 if ((pwd = getpwuid(geteuid())) == NULL)
80 strcpy(req.usernm, "unknown");
81 else
82 strcpy(req.usernm, pwd->pw_name);
83 req.size = htonl(nbytes);
84 if (text)
85 req.flags = htonl(PR_TEXT);
86 else
87 req.flags = 0;
[5563] | If we can make a connection, we call submit_file to transmit the file to the printer spooling daemon. If we can't connect to any of the addresses, we print an error message and exit. We use err_ret and exit instead of making a single call to err_sys to avoid a compiler warning, because the last line in main wouldn't be a return statement or a call to exit. | [6487] | submit_file sends a print request to the daemon and reads the response.First, we build the printreq request header. We use geteuid to get the caller's effective user ID and pass this to getpwuid to look for the user in the system's password file. We copy the user's name to the request header or use the string unknown if we can't identify the user. We store the size of the file to be printed in the header after converting it to network byte order. Then we do the same with the PR_TEXT flag if the file is to be printed as plaintext. |
88 if ((len = strlen(fname)) >= JOBNM_MAX) {
89 /*
90 * Truncate the filename (+-5 accounts for the leading
91 * four characters and the terminating null).
92 */
93 strcpy(req.jobnm, "... ");
94 strncat(req.jobnm, &fname[len-JOBNM_MAX+5], JOBNM_MAX-5);
95 } else {
96 strcpy(req.jobnm, fname);
97 }
98 /*
99 * Send the header to the server.
100 */
101 nw = writen(sockfd, &req, sizeof(struct printreq));
102 if (nw != sizeof(struct printreq)) {
103 if (nw < 0)
104 err_sys("can't write to print server");
105 else
106 err_quit("short write (%d/%d) to print server",
107 nw, sizeof(struct printreq));
108 }
109 /*
110 * Now send the file.
111 */
112 while ((nr = read(fd, buf, IOBUFSZ)) != 0) {
113 nw = writen(sockfd, buf, nr);
114 if (nw != nr) {
115 if (nw < 0)
116 err_sys("can't write to print server");
117 else
118 err_quit("short write (%d/%d) to print server",
119 nw, nr);
120 }
121 }
[88108] | We set the job name to the name of the file being printed. If the name is longer than will fit in the message, we truncate the beginning portion of the name and prepend an ellipsis to indicate that there were more characters than would fit in the field. Then we send the request header to the daemon using writen. If the write fails or if we transmit less than we expect, we print an error message and exit. | [109121] | After sending the header to the daemon, we send the file to be printed. We read the file IOBUFSZ bytes at a time and use writen to send the data to the daemon. As with the header, if the write fails or we write less than we expect, we print an error message and exit. |
122 /*
123 * Read the response.
124 */
125 if ((nr = readn(sockfd, &res, sizeof(struct printresp))) !=
126 sizeof(struct printresp))
127 err_sys("can't read response from server");
128 if (res.retcode != 0) {
129 printf("rejected: %s\n", res.msg);
130 exit(1);
131 } else {
132 printf("job ID %ld\n", ntohl(res.jobid));
133 }
134 exit(0);
135 }
[122135] | After we send the file to be printed to the daemon, we read the daemon's response. If the request failed, the return code (retcode) will be nonzero, so we print the textual error message included in the response. If the request succeeded, we print the job ID so that the user knows how to refer to the request in the future. (Writing a command to cancel the print request is left as an exercise; the job ID can be used in the cancellation request to identify the job to be removed from the print queue.) | | Note that a successful response from the daemon does not mean that the printer was able to print the file. It merely means that the daemon successfully added the print job to the queue. |
Most of what we have seen in print.c was discussed in earlier chapters. The only topic that we haven't covered is the getopt function, although we saw it earlier in the pty program from Chapter 19.
It is important that all commands on a system follow the same conventions, because this makes them easier to use. If someone is familiar with the way command-line options are formed with one command, it would create more chances for mistakes if another command followed different conventions.
This problem is sometimes visible when dealing with white space on the command line. Some commands require that an option be separated from its argument by white space, but other commands require the argument to follow immediate after its option, without any intervening spaces. Without a consistent set of rules to follow, users either have to memorize the syntax of all commands or resort to a trial-and-error process when invoking them.
The Single UNIX Specification includes a set of conventions and guidelines that promote consistent command-line syntax. They include such suggestions as "Restrict each command-line option to a single alphanumeric character" and "All options should be preceded by a - character."
Luckily, the getopt function exists to help command developers process command-line options in a consistent manner.
#include <fcntl.h>
int getopt(int argc, const * const argv[], const
char *options);
extern int optind, opterr, optopt;
extern char *optarg;
| Returns: the next option character, or 1 when all options have been processed |
The argc and argv arguments are the same ones passed to the main function of the program. The options argument is a string containing the option characters supported by the command. If an option character is followed by a colon, then the option takes an argument. Otherwise, the option exists by itself. For example, if the usage statement for a command was
command [-i] [-u username] [-z] filename
we would pass "iu:z" as the options string to getopt.
The normal use of getopt is in a loop that terminates when getopt returns 1. During each iteration of the loop, getopt will return the next option processed. It is up to the application to sort out any conflict in options, however; getopt simply parses the options and enforces a standard format.
When it encounters an invalid option, getopt returns a question mark instead of the character. If an option's argument is missing, getopt will also return a question mark, but if the first character in the options string is a colon, getopt returns a colon instead. The special pattern -- will cause getopt to stop processing options and return 1. This allows users to provide command arguments that start with a minus sign but aren't options. For example, if you have a file named -bar, you can't remove it by typing
rm -bar
because rm will try to interpret -bar as options. The way to remove the file is to type
rm -- -bar
The getopt function supports four external variables.
optarg | If an option takes an argument, getopt sets optarg to point to the option's argument string when an option is processed. | opterr | If an option error is encountered, getopt will print an error message by default. To disable this behavior, applications can set opterr to 0. | optind | The index in the argv array of the next string to be processed. It starts out at 1 and is incremented for each argument processed by getopt. | optopt | If an error is encountered during options processing, getopt will set optopt to point to the option string that caused the error. |
The last file we will look at is the C source file for the printer spooling daemon.
1 /*
2 * Print server daemon.
3 */
4 #include "apue.h"
5 #include "print.h"
6 #include "ipp.h"
7 #include <fcntl.h>
8 #include <dirent.h>
9 #include <ctype.h>
10 #include <pwd.h>
11 #include <pthread.h>
12 #include <strings.h>
13 #include <sys/select.h>
14 #include <sys/uio.h>
15 /*
16 * These are for the HTTP response from the printer.
17 */
18 #define HTTP_INFO(x) ((x) >= 100 && (x) <= 199)
19 #define HTTP_SUCCESS(x) ((x) >= 200 && (x) <= 299)
20 /*
21 * Describes a print job.
22 */
23 struct job {
24 struct job *next; /* next in list */
25 struct job *prev; /* previous in list */
26 long jobid; /* job ID */
27 struct printreq req; /* copy of print request */
28 };
29 /*
30 * Describes a thread processing a client request.
31 */
32 struct worker_thread {
33 struct worker_thread *next; /* next in list */
34 struct worker_thread *prev; /* previous in list */
35 pthread_t tid; /* thread ID */
36 int sockfd; /* socket */
37 };
[119] | The printer spooling daemon includes the IPP header file that we saw earlier, because the daemon needs to communicate with the printer using this protocol. The HTTP_INFO and HTTP_SUCCESS macros define the status of the HTTP request (recall that IPP is built on top of HTTP). | [2037] | The job and worker_thread structures are used by the spooling daemon to keep track of print jobs and threads accepting print requests, respectively. |
38 /*
39 * Needed for logging.
40 */
41 int log_to_stderr = 0;
42 /*
43 * Printer-related stuff.
44 */
45 struct addrinfo *printer;
46 char *printer_name;
47 pthread_mutex_t configlock = PTHREAD_MUTEX_INITIALIZER;
48 int reread;
49 /*
50 * Thread-related stuff.
51 */
52 struct worker_thread *workers;
53 pthread_mutex_t workerlock = PTHREAD_MUTEX_INITIALIZER;
54 sigset_t mask;
55 /*
56 * Job-related stuff.
57 */
58 struct job *jobhead, *jobtail;
59 int jobfd;
[3841] | Our logging functions require that we define the log_to_stderr variable and set it to 0 to force log messages to be sent to the system log instead of to the standard error. In print.c, we defined log_to_stderr and set it to 1, even though we don't use the log functions in the user command. We could have avoided this by splitting the utility functions into two separate files: one for the server and one for the client commands. | [4248] | We use the global variable printer to hold the network address of the printer.We store the host name of the printer in printer_name. The configlock mutex protects access to the reread variable, which is used to indicate that the daemon needs to reread the configuration file, presumably because an administrator changed the printer or its network address. | [4954] | Next, we define the thread-related variables. We use workers as the head of a doubly-linked list of threads that are receiving files from clients. This list is protected by the mutex workerlock. The signal mask used by the threads is held in the variable mask. | [5559] | For the list of pending jobs, we define jobhead to be the start of the list and jobtail to be the tail of the list. This list is also doubly linked, but we need to add jobs to the end of the list, so we need to remember a pointer to the list tail. With the list of worker threads, the order doesn't matter, so we can add them to the head of the list and don't need to remember the tail pointer. jobfd is the file descriptor for the job file. |
60 long nextjob;
61 pthread_mutex_t joblock = PTHREAD_MUTEX_INITIALIZER;
62 pthread_cond_t jobwait = PTHREAD_COND_INITIALIZER;
63 /*
64 * Function prototypes.
65 */
66 void init_request(void);
67 void init_printer(void);
68 void update_jobno(void);
69 long get_newjobno(void);
70 void add_job(struct printreq *, long);
71 void replace_job(struct job *);
72 void remove_job(struct job *);
73 void build_qonstart(void);
74 void *client_thread(void *);
75 void *printer_thread(void *);
76 void *signal_thread(void *);
77 ssize_t readmore(int, char **, int, int *);
78 int printer_status(int, struct job *);
79 void add_worker(pthread_t, int);
80 void kill_workers(void);
81 void client_cleanup(void *);
82 /*
83 * Main print server thread. Accepts connect requests from
84 * clients and spawns additional threads to service requests.
85 *
86 * LOCKING: none.
87 */
88 int
89 main(int argc, char *argv[])
90 {
91 pthread_t tid;
92 struct addrinfo *ailist, *aip;
93 int sockfd, err, i, n, maxfd;
94 char *host;
95 fd_set rendezvous, rset;
96 struct sigaction sa;
97 struct passwd *pwdp;
[6062] | nextjob is the ID of the next print job to be received. The joblock mutex protects the linked list of jobs, as well as the condition represented by the jobwait condition variable. | [6381] | We declare the function prototypes for the remaining functions in this file. Doing this up front allows us to place the functions in the file without worrying about the order in which each is called. | [8297] | The main function for the printer spooling daemon has two tasks to perform: initialize the daemon and then process connect requests from clients. |
98 if (argc != 1)
99 err_quit("usage: printd");
100 daemonize("printd");
101 sigemptyset(&sa.sa_mask);
102 sa.sa_flags = 0;
103 sa.sa_handler = SIG_IGN;
104 if (sigaction(SIGPIPE, &sa, NULL) < 0)
105 log_sys("sigaction failed");
106 sigemptyset(&mask);
107 sigaddset(&mask, SIGHUP);
108 sigaddset(&mask, SIGTERM);
109 if ((err = pthread_sigmask(SIG_BLOCK, &mask, NULL)) != 0)
110 log_sys("pthread_sigmask failed");
111 init_request();
112 init_printer();
113 #ifdef _SC_HOST_NAME_MAX
114 n = sysconf(_SC_HOST_NAME_MAX);
115 if (n < 0) /* best guess */
116 #endif
117 n = HOST_NAME_MAX;
118 if ((host = malloc(n)) == NULL)
119 log_sys("malloc error");
120 if (gethostname(host, n) < 0)
121 log_sys("gethostname error");
[98100] | The daemon doesn't have any options, so if argc is not 1, we call err_quit to print an error message and exit. We call the daemonize function from Figure 13.1 to become a daemon. After this point, we can't print error messages to standard error; we need to log them instead. | [101112] | We arrange to ignore SIGPIPE. We will be writing to socket file descriptors, and we don't want a write error to trigger SIGPIPE, because the default action is to kill the process. Next, we set the signal mask of the thread to include SIGHUP and SIGTERM. All threads we create will inherit this signal mask. We'll use SIGHUP to tell the daemon to reread the configuration file and SIGTERM to tell the daemon to clean up and exit gracefully. We call init_request to initialize the job requests and ensure that only one copy of the daemon is running, and we call init_printer to initialize the printer information (we'll see both of these functions shortly). | [113121] | If the platform defines the _SC_HOST_NAME_MAX symbol, we call sysconf to get the maximum size of a host name. If sysconf fails or the limit is undefined, we use HOST_NAME_MAX as a best guess. Sometimes, this is defined for us by the platform, but if it isn't, we chose our own value in print.h. We allocate memory to hold the host name and call gethostname to retrieve it. |
122 if ((err = getaddrlist(host, "print", &ailist)) != 0) {
123 log_quit("getaddrinfo error: %s", gai_strerror(err));
124 exit(1);
125 }
126 FD_ZERO(&rendezvous);
127 maxfd = -1;
128 for (aip = ailist; aip != NULL; aip = aip->ai_next) {
129 if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr,
130 aip->ai_addrlen, QLEN)) >= 0) {
131 FD_SET(sockfd, &rendezvous);
132 if (sockfd > maxfd)
133 maxfd = sockfd;
134 }
135 }
136 if (maxfd == -1)
137 log_quit("service not enabled");
138 pwdp = getpwnam("lp");
139 if (pwdp == NULL)
140 log_sys("can't find user lp");
141 if (pwdp->pw_uid == 0)
142 log_quit("user lp is privileged");
143 if (setuid(pwdp->pw_uid) < 0)
144 log_sys("can't change IDs to user lp");
[122135] | Next, we try to find the network address that the daemon is supposed to use to provide printer spooling service. We clear the rendezvous fd_set variable that we will use with select to wait for client connect requests. We initialize the maximum file descriptor to 1 so that the first file descriptor we allocate is sure to be greater than maxfd. For each network address on which we need to provide service, we call initserver (from Figure 16.20) to allocate and initialize a socket. If initserver succeeds, we add the file descriptor to the fd_set; if it is greater than the maximum, we set maxfd equal to the socket file descriptor. | [136137] | If maxfd is still 1 after stepping through the list of addrinfo structures, we can't enable the printer spooling service, so we log a message and exit. | [138144] | Our daemon needs superuser privileges to bind a socket to a reserved port number. Now that this is done, we can lower its privileges by changing its user ID to the one associated with user lp (recall the security discussion in Section 21.4). We want to follow the principles of least privilege to avoid exposing the system to any potential vulnerabilities in the daemon. We call getpwnam to find the password entry associated with user lp. If no such user account exists, or if it exists with the same user ID as the superuser, we log a message and exit. Otherwise, we call setuid to change both the real and effective user IDs to the user ID for lp. To avoid exposing our system, we choose to provide no service at all if we can't reduce our privileges. |
145 pthread_create(&tid, NULL, printer_thread, NULL);
146 pthread_create(&tid, NULL, signal_thread, NULL);
147 build_qonstart();
148 log_msg("daemon initialized");
149 for (;;) {
150 rset = rendezvous;
151 if (select(maxfd+1, &rset, NULL, NULL, NULL) < 0)
152 log_sys("select failed");
153 for (i = 0; i <= maxfd; i++) {
154 if (FD_ISSET(i, &rset)) {
155 /*
156 * Accept the connection and handle
157 * the request.
158 */
159 sockfd = accept(i, NULL, NULL);
160 if (sockfd < 0)
161 log_ret("accept failed");
162 pthread_create(&tid, NULL, client_thread,
163 (void *)sockfd);
164 }
165 }
166 }
167 exit(1);
168 }
[145148] | We call pthread_create twice to create one thread to handle signals and one thread to communicate with the printer. (By restricting printer communication to one thread, we can simplify the locking of the printer-related data structures.) Then we call build_qonstart to search the directories in /var/spool/printer for any pending jobs. For each job that we find on disk, we will create a structure to let the printer thread know that it should send the file to the printer. At this point, we are done setting up the daemon, so we log a message to indicate that the daemon has initialized successfully. | [149168] | We copy the rendezvous fd_set structure to rset and call select to wait for one of the file descriptors to become readable. We have to copy rendezvous, because select will modify the fd_set structure that we pass to it to include only those file descriptors that satisfy the event. Since the sockets have been initialized for use by a server, a readable file descriptor means that a connect request is pending. After select returns, we check rset for a readable file descriptor. If we find one, we call accept to accept the connection. If this fails, we log a message and continue checking for more readable file descriptors. Otherwise, we create a thread to handle the client connection. The main thread loops, farming requests out to other threads for processing, and should never reach the exit statement. |
169 /*
170 * Initialize the job ID file. Use a record lock to prevent
171 * more than one printer daemon from running at a time.
172 *
173 * LOCKING: none, except for record-lock on job ID file.
174 */
175 void
176 init_request(void)
177 {
178 int n;
179 char name[FILENMSZ];
180 sprintf(name, "%s/%s", SPOOLDIR, JOBFILE);
181 jobfd = open(name, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
182 if (write_lock(jobfd, 0, SEEK_SET, 0) < 0)
183 log_quit("daemon already running");
184 /*
185 * Reuse the name buffer for the job counter.
186 */
187 if ((n = read(jobfd, name, FILENMSZ)) < 0)
188 log_sys("can't read job file");
189 if (n == 0)
190 nextjob = 1;
191 else
192 nextjob = atol(name);
193 }
[169183] | The init_request function does two things: it places a record lock on the job file, /var/spool/printer/jobno, and it reads the file to determine the next job number to assign. We place a write lock on the entire file to indicate that the daemon is running. If someone tries to start additional copies of the printer spooling daemon while one is already running, these additional daemons will fail to obtain the write lock and will exit. Thus, only one copy of the daemon can be running at a time. (Recall that we used this technique in Figure 13.6; we discussed the write_lock macro in Section 14.3.) | [184193] | The job file contains an ASCII integer string representing the next job number. If the file was just created and therefore is empty, we set nextjob to 1. Otherwise, we use atol to convert the string to an integer and use this as the next job number. We leave jobfd open to the job file so that we can update the job number as jobs are created. We can't close the file, because this would release the write lock that we've placed on it. | | On a system where a long integer is 64 bits wide, we need a buffer at least 21 bytes in size to fit a string representing the largest possible long integer. We are safe reusing the filename buffer, because FILENMSZ is defined to be 64 in print.h. |
194 /*
195 * Initialize printer information.
196 *
197 * LOCKING: none.
198 */
199 void
200 init_printer(void)
201 {
202 printer = get_printaddr();
203 if (printer == NULL) {
204 log_msg("no printer device registered");
205 exit(1);
206 }
207 printer_name = printer->ai_canonname;
208 if (printer_name == NULL)
209 printer_name = "printer";
210 log_msg("printer is %s", printer_name);
211 }
212 /*
213 * Update the job ID file with the next job number.
214 *
215 * LOCKING: none.
216 */
217 void
218 update_jobno(void)
219 {
220 char buf[32];
221 lseek(jobfd, 0, SEEK_SET);
222 sprintf(buf, "%ld", nextjob);
223 if (write(jobfd, buf, strlen(buf)) < 0)
224 log_sys("can't update job file");
225 }
[194211] | The init_printer function is used to set the printer name and address. We get the printer address by calling get_printaddr (from util.c). If this fails, we log a message and exit. We can't do this by calling log_sys, because get_printaddr can fail without setting errno. When it fails and does set errno, however, get_printaddr logs its own error message. We set the printer name to the ai_canonname field in the addrinfo structure. If this field is null, we set the printer name to a default value of printer. Note that we log the name of the printer we are using to aid administrators in diagnosing problems with the spooling system. | [212225] | The update_jobno function is used to write the next job number to the job file, /var/spool/printer/jobno. First, we seek to the beginning of the file. Then we convert the integer job number into a string and write it to the file. If the write fails, we log an error message and exit. |
226 /*
227 * Get the next job number.
228 *
229 * LOCKING: acquires and releases joblock.
230 */
231 long
232 get_newjobno(void)
233 {
234 long jobid;
235 pthread_mutex_lock(&joblock);
236 jobid = nextjob++;
237 if (nextjob <= 0)
238 nextjob = 1;
239 pthread_mutex_unlock(&joblock);
240 return(jobid);
241 }
242 /*
243 * Add a new job to the list of pending jobs. Then signal
244 * the printer thread that a job is pending.
245 *
246 * LOCKING: acquires and releases joblock.
247 */
248 void
249 add_job(struct printreq *reqp, long jobid)
250 {
251 struct job *jp;
252 if ((jp = malloc(sizeof(struct job))) == NULL)
253 log_sys("malloc failed");
254 memcpy(&jp->req, reqp, sizeof(struct printreq));
[226241] | The get_newjobno function is used to get the next job number. We first lock the joblock mutex. We increment the nextjob variable and handle the case where it wraps around. Then we unlock the mutex and return the value nextjob had before we incremented it. Multiple threads can call get_newjobno at the same time; we need to serialize access to the next job number so that each thread gets a unique job number. (Refer to Figure 11.9 to see what could happen if we don't serialize the threads in this case.) | [242254] | The add_job function is used to add a new print request to the end of the list of pending print jobs. We start by allocating space for the job structure. If this fails, we log a message and exit. At this point, the print request is stored safely on disk; when the printer spooling daemon is restarted, it will pick the request up. After we allocate memory for the new job, we copy the request structure from the client into the job structure. Recall from print.h that a job structure consists of a pair of list pointers, a job ID, and a copy of the printreq structure sent to us by the client print command. |
255 jp->jobid = jobid;
256 jp->next = NULL;
257 pthread_mutex_lock(&joblock);
258 jp->prev = jobtail;
259 if (jobtail == NULL)
260 jobhead = jp;
261 else
262 jobtail->next = jp;
263 jobtail = jp;
264 pthread_mutex_unlock(&joblock);
265 pthread_cond_signal(&jobwait);
266 }
267 /*
268 * Replace a job back on the head of the list.
269 *
270 * LOCKING: acquires and releases joblock.
271 */
272 void
273 replace_job(struct job *jp)
274 {
275 pthread_mutex_lock(&joblock);
276 jp->prev = NULL;
277 jp->next = jobhead;
278 if (jobhead == NULL)
279 jobtail = jp;
280 else
281 jobhead->prev = jp;
282 jobhead = jp;
283 pthread_mutex_unlock(&joblock);
284 }
[255266] | We save the job ID and lock the joblock mutex to gain exclusive access to the linked list of print jobs. We are about to add the new job structure to the end of the list. We set the new structure's previous pointer to the last job on the list. If the list is empty, we set jobhead to point to the new structure. Otherwise, we set the next pointer in the last entry on the list to point to the new structure. Then we set jobtail to point to the new structure. We unlock the mutex and signal the printer thread that another job is available. | [267284] | The replace_job function is used to insert a job at the head of the pending job list. We acquire the joblock mutex, set the previous pointer in the job structure to null, and set the next pointer in the job structure to point to the head of the list. If the list is empty, we set jobtail to point to the job structure we are replacing. Otherwise, we set the previous pointer in the first job structure on the list to point to the job structure we are replacing. Then we set the jobhead pointer to the job structure we are replacing. Finally, we release the joblock mutex. |
285 /*
286 * Remove a job from the list of pending jobs.
287 *
288 * LOCKING: caller must hold joblock.
289 */
290 void
291 remove_job(struct job *target)
292 {
293 if (target->next != NULL)
294 target->next->prev = target->prev;
295 else
296 jobtail = target->prev;
297 if (target->prev != NULL)
298 target->prev->next = target->next;
299 else
300 jobhead = target->next;
301 }
302 /*
303 * Check the spool directory for pending jobs on start-up.
304 *
305 * LOCKING: none.
306 */
307 void
308 build_qonstart(void)
309 {
310 int fd, err, nr;
311 long jobid;
312 DIR *dirp;
313 struct dirent *entp;
314 struct printreq req;
315 char dname[FILENMSZ], fname[FILENMSZ];
316 sprintf(dname, "%s/%s", SPOOLDIR, REQDIR);
317 if ((dirp = opendir(dname)) == NULL)
318 return;
[285301] | remove_job removes a job from the list of pending jobs given a pointer to the job to be removed. The caller must already hold the joblock mutex. If the next pointer is non-null, we set the next entry's previous pointer to the target's previous pointer. Otherwise, the entry is the last one on the list, so we set jobtail to the target's previous pointer. If the target's previous pointer is non-null, we set the previous entry's next pointer equal to the target's next pointer. Otherwise, this is the first entry in the list, so we set jobhead to point to the next entry in the list after the target. | [302318] | When the daemon starts, it calls build_qonstart to build an in-memory list of print jobs from the disk files stored in /var/spool/printer/reqs. If we can't open the directory, no print jobs are pending, so we return. |
319 while ((entp = readdir(dirp)) != NULL) {
320 /*
321 * Skip "." and ".."
322 */
323 if (strcmp(entp->d_name, ".") == 0 ||
324 strcmp(entp->d_name, "..") == 0)
325 continue;
326 /*
327 * Read the request structure.
328 */
329 sprintf(fname, "%s/%s/%s", SPOOLDIR, REQDIR, entp->d_name);
330 if ((fd = open(fname, O_RDONLY)) < 0)
331 continue;
332 nr = read(fd, &req, sizeof(struct printreq));
333 if (nr != sizeof(struct printreq)) {
334 if (nr < 0)
335 err = errno;
336 else
337 err = EIO;
338 close(fd);
339 log_msg("build_qonstart: can't read %s: %s",
340 fname, strerror(err));
341 unlink(fname);
342 sprintf(fname, "%s/%s/%s", SPOOLDIR, DATADIR,
343 entp->d_name);
344 unlink(fname);
345 continue;
346 }
347 jobid = atol(entp->d_name);
348 log_msg("adding job %ld to queue", jobid);
349 add_job(&req, jobid);
350 }
351 closedir(dirp);
352 }
[319325] | We read each entry in the directory, one at a time. We skip the entries for dot and dot-dot. | [326346] | For each entry, we create the full pathname of the file and open it for reading. If the open call fails, we just skip the file. Otherwise, we read the printreq structure stored in it. If we don't read the entire structure, we close the file, log a message, and unlink the file. Then we create the full pathname of the corresponding data file and unlink it, too. | [347352] | If we were able to read a complete printreq structure, we convert the filename into a job ID (the name of the file is its job ID), log a message, and then add the request to the list of pending print jobs. When we are done reading the directory, readdir will return NULL, and we close the directory and return. |
353 /*
354 * Accept a print job from a client.
355 *
356 * LOCKING: none.
357 */
358 void *
359 client_thread(void *arg)
360 {
361 int n, fd, sockfd, nr, nw, first;
362 long jobid;
363 pthread_t tid;
364 struct printreq req;
365 struct printresp res;
366 char name[FILENMSZ];
367 char buf[IOBUFSZ];
368 tid = pthread_self();
369 pthread_cleanup_push(client_cleanup, (void *)tid);
370 sockfd = (int)arg;
371 add_worker(tid, sockfd);
372 /*
373 * Read the request header.
374 */
375 if ((n = treadn(sockfd, &req, sizeof(struct printreq), 10)) !=
376 sizeof(struct printreq)) {
377 res.jobid = 0;
378 if (n < 0)
379 res.retcode = htonl(errno);
380 else
381 res.retcode = htonl(EIO);
382 strncpy(res.msg, strerror(res.retcode), MSGLEN_MAX);
383 writen(sockfd, &res, sizeof(struct printresp));
384 pthread_exit((void *)1);
385 }
[353371] | The client_thread is spawned from the main tHRead when a connect request is accepted. Its job is to receive the file to be printed from the client print command. We create a separate thread for each client print request. | | The first thing we do is install a thread cleanup handler (see Section 11.5 for a discussion of thread cleanup handlers). The cleanup handler is client_cleanup, which we will see later. It takes a single argument: our thread ID. Then we call add_worker to create a worker_thread structure and add it to the list of active client threads. | [372385] | At this point, we are done with the thread's initialization tasks, so we read the request header from the client. If the client sends less than we expect or we encounter an error, we respond with a message indicating the reason for the error and call pthread_exit to terminate the thread. |
386 req.size = ntohl(req.size);
387 req.flags = ntohl(req.flags);
388 /*
389 * Create the data file.
390 */
391 jobid = get_newjobno();
392 sprintf(name, "%s/%s/%ld", SPOOLDIR, DATADIR, jobid);
393 if ((fd = creat(name, FILEPERM)) < 0) {
394 res.jobid = 0;
395 if (n < 0)
396 res.retcode = htonl(errno);
397 else
398 res.retcode = htonl(EIO);
399 log_msg("client_thread: can't create %s: %s", name,
400 strerror(res.retcode));
401 strncpy(res.msg, strerror(res.retcode), MSGLEN_MAX);
402 writen(sockfd, &res, sizeof(struct printresp));
403 pthread_exit((void *)1);
404 }
405 /*
406 * Read the file and store it in the spool directory.
407 */
408 first = 1;
409 while ((nr = tread(sockfd, buf, IOBUFSZ, 20)) > 0) {
410 if (first) {
411 first = 0;
412 if (strncmp(buf, "%!PS", 4) != 0)
413 req.flags |= PR_TEXT;
414 }
[386404] | We convert the integer fields in the request header to host byte order and call get_newjobno to reserve the next job ID for this print request. We create the job data file, named /var/spool/printer/data/jobid, where jobid is the request's job ID. We use permissions that prevent others from being able read the files (FILEPERM is defined as S_IRUSR|S_IWUSR in print.h). If we can't create the file, we log an error message, send a failure response back to the client, and terminate the thread by calling pthread_exit. | [405414] | We read the file contents from the client, with the intent of writing the contents out to our private copy of the data file. But before we write anything, we need to check if this is a PostScript file the first time through the loop. If the file doesn't begin with the pattern %!PS, we can assume that the file is plaintext, so we set the PR_TEXT flag in the request header in this case. (Recall that the client can also set this flag if the -t flag is included when the print command is executed.) Although PostScript programs are not required to start with the pattern %!PS, the document formatting guidelines (Adobe Systems [1999]) strongly recommends that they do. |
415 nw = write(fd, buf, nr);
416 if (nw != nr) {
417 if (nw < 0)
418 res.retcode = htonl(errno);
419 else
420 res.retcode = htonl(EIO);
421 log_msg("client_thread: can't write %s: %s", name,
422 strerror(res.retcode));
423 close(fd);
424 strncpy(res.msg, strerror(res.retcode), MSGLEN_MAX);
425 writen(sockfd, &res, sizeof(struct printresp));
426 unlink(name);
427 pthread_exit((void *)1);
428 }
429 }
430 close(fd);
431 /*
432 * Create the control file.
433 */
434 sprintf(name, "%s/%s/%ld", SPOOLDIR, REQDIR, jobid);
435 fd = creat(name, FILEPERM);
436 if (fd < 0) {
437 res.jobid = 0;
438 if (n < 0)
439 res.retcode = htonl(errno);
440 else
441 res.retcode = htonl(EIO);
442 log_msg("client_thread: can't create %s: %s", name,
443 strerror(res.retcode));
444 strncpy(res.msg, strerror(res.retcode), MSGLEN_MAX);
445 writen(sockfd, &res, sizeof(struct printresp));
446 sprintf(name, "%s/%s/%ld", SPOOLDIR, DATADIR, jobid);
447 unlink(name);
448 pthread_exit((void *)1);
449 }
[415430] | We write the data that we read from the client to the data file. If write fails, we log an error message, close the file descriptor for the data file, send an error message back to the client, delete the data file, and terminate the thread by calling pthread_exit. Note that we do not explicitly close the socket file descriptor. This is done for us by our thread cleanup handler as part of the processing that occurs when we call pthread_exit. | | When we receive all the data to be printed, we close the file descriptor for the data file. | [431449] | Next, we create a file, /var/spool/printer/reqs/jobid, to remember the print request. If this fails, we log an error message, send an error response to the client, remove the data file, and terminate the thread. |
450 nw = write(fd, &req, sizeof(struct printreq));
451 if (nw != sizeof(struct printreq)) {
452 res.jobid = 0;
453 if (nw < 0)
454 res.retcode = htonl(errno);
455 else
456 res.retcode = htonl(EIO);
457 log_msg("client_thread: can't write %s: %s", name,
458 strerror(res.retcode));
459 close(fd);
460 strncpy(res.msg, strerror(res.retcode), MSGLEN_MAX);
461 writen(sockfd, &res, sizeof(struct printresp));
462 unlink(name);
463 sprintf(name, "%s/%s/%ld", SPOOLDIR, DATADIR, jobid);
464 unlink(name);
465 pthread_exit((void *)1);
466 }
467 close(fd);
468 /*
469 * Send response to client.
470 */
471 res.retcode = 0;
472 res.jobid = htonl(jobid);
473 sprintf(res.msg, "request ID %ld", jobid);
474 writen(sockfd, &res, sizeof(struct printresp));
475 /*
476 * Notify the printer thread, clean up, and exit.
477 */
478 log_msg("adding job %ld to queue", jobid);
479 add_job(&req, jobid);
480 pthread_cleanup_pop(1);
481 return((void *)0);
482 }
[450466] | We write the printreq structure to the control file. On error, we log a message, close the descriptor for the control file, send a failure response back to the client, remove the data and control files, and terminate the thread. | [467474] | We close the file descriptor for the control file and send a message containing the job ID and a successful status (retcode set to 0) back to the client. | [475482] | We call add_job to add the received job to the list of pending print jobs and call pthread_cleanup_pop to complete the cleanup processing. The thread terminates when we return. | | Note that before the thread exits, we must close any file descriptors we no longer need. Unlike process termination, file descriptors are not closed automatically when a thread ends if other threads exist in the process. If we didn't close unneeded file descriptors, we'd eventually run out of resources. |
483 /*
484 * Add a worker to the list of worker threads.
485 *
486 * LOCKING: acquires and releases workerlock.
487 */
488 void
489 add_worker(pthread_t tid, int sockfd)
490 {
491 struct worker_thread *wtp;
492 if ((wtp = malloc(sizeof(struct worker_thread))) == NULL) {
493 log_ret("add_worker: can't malloc");
494 pthread_exit((void *)1);
495 }
496 wtp->tid = tid;
497 wtp->sockfd = sockfd;
498 pthread_mutex_lock(&workerlock);
499 wtp->prev = NULL;
500 wtp->next = workers;
501 if (workers == NULL)
502 workers = wtp;
503 else
504 workers->prev = wtp;
505 pthread_mutex_unlock(&workerlock);
506 }
507 /*
508 * Cancel (kill) all outstanding workers.
509 *
510 * LOCKING: acquires and releases workerlock.
511 */
512 void
513 kill_workers(void)
514 {
515 struct worker_thread *wtp;
516 pthread_mutex_lock(&workerlock);
517 for (wtp = workers; wtp != NULL; wtp = wtp->next)
518 pthread_cancel(wtp->tid);
519 pthread_mutex_unlock(&workerlock);
520 }
[483506] | add_worker adds a worker_thread structure to the list of active threads. We allocate memory for the structure, initialize it, lock the workerlock mutex, add the structure to the head of the list, and unlock the mutex. | [507520] | The kill_workers function walks the list of worker threads and cancels each one. We hold the workerlock mutex while we walk the list. Recall that pthread_cancel merely schedules a thread for cancellation; actual cancellation happens when each thread reaches the next cancellation point. |
521 /*
522 * Cancellation routine for the worker thread.
523 *
524 * LOCKING: acquires and releases workerlock.
525 */
526 void
527 client_cleanup(void *arg)
528 {
529 struct worker_thread *wtp;
530 pthread_t tid;
531 tid = (pthread_t)arg;
532 pthread_mutex_lock(&workerlock);
533 for (wtp = workers; wtp != NULL; wtp = wtp->next) {
534 if (wtp->tid == tid) {
535 if (wtp->next != NULL)
536 wtp->next->prev = wtp->prev;
537 if (wtp->prev != NULL)
538 wtp->prev->next = wtp->next;
539 else
540 workers = wtp->next;
541 break;
542 }
543 }
544 pthread_mutex_unlock(&workerlock);
545 if (wtp != NULL) {
546 close(wtp->sockfd);
547 free(wtp);
548 }
549 }
[521543] | The client_cleanup function is the thread cleanup handler for the worker threads that communicate with client commands. This function is called when the thread calls pthread_exit, calls pthread_cleanup_pop with a nonzero argument, or responds to a cancellation request. The argument is the thread ID of the thread terminating. | | We lock the workerlock mutex and search the list of worker threads until we find a matching thread ID. When we find a match, we remove the worker thread structure from the list and stop the search. | [544549] | We unlock the workerlock mutex, close the socket file descriptor used by the thread to communicate with the client, and free the memory backing the worker_thread structure. | | Since we try to acquire the workerlock mutex, if a thread reaches a cancellation point while the kill_workers function is still walking the list, we will have to wait until kill_workers releases the mutex before we can proceed. |
550 /*
551 * Deal with signals.
552 *
553 * LOCKING: acquires and releases configlock.
554 */
555 void *
556 signal_thread(void *arg)
557 {
558 int err, signo;
559 for (;;) {
560 err = sigwait(&mask, &signo);
561 if (err != 0)
562 log_quit("sigwait failed: %s", strerror(err));
563 switch (signo) {
564 case SIGHUP:
565 /*
566 * Schedule to re-read the configuration file.
567 */
568 pthread_mutex_lock(&configlock);
569 reread = 1;
570 pthread_mutex_unlock(&configlock);
571 break;
572 case SIGTERM:
573 kill_workers();
574 log_msg("terminate with signal %s", strsignal(signo));
575 exit(0);
576 default:
577 kill_workers();
578 log_quit("unexpected signal %d", signo);
579 }
580 }
581 }
[550563] | The signal_thread function is run by the thread that is responsible for handling signals. In the main function, we initialized the signal mask to include SIGHUP and SIGTERM. Here, we call sigwait to wait for one of these signals to occur. If sigwait fails, we log an error message and exit. | [564571] | If we receive SIGHUP, we acquire the configlock mutex, set the reread variable to 1, and release the mutex. This tells the printer daemon to reread the configuration file on the next iteration in its processing loop. | [572575] | If we receive SIGTERM, we call kill_workers to kill all the worker threads, log a message, and call exit to terminate the process. | [576581] | If we receive a signal we are not expecting, we kill the worker threads and call log_quit to log a message and exit. |
582 /*
583 * Add an option to the IPP header.
584 *
585 * LOCKING: none.
586 */
587 char *
588 add_option(char *cp, int tag, char *optname, char *optval)
589 {
590 int n;
591 union {
592 int16_t s;
593 char c[2];
594 } u;
595 *cp++ = tag;
596 n = strlen(optname);
597 u.s = htons(n);
598 *cp++ = u.c[0];
599 *cp++ = u.c[1];
600 strcpy(cp, optname);
601 cp += n;
602 n = strlen(optval);
603 u.s = htons(n);
604 *cp++ = u.c[0];
605 *cp++ = u.c[1];
606 strcpy(cp, optval);
607 return(cp + n);
608 }
[582594] | The add_option function is used to add an option to the IPP header that we build to send to the printer. Recall from Figure 21.3 that the format of an attribute is a 1-byte tag describing the type of the attribute, followed by the length of the attribute name stored in binary as a 2-byte integer, followed by the name, the size of the attribute value, and finally the value itself. | | IPP makes no attempt to control the alignment of the binary integers embedded in the header. Some processor architectures, such as the SPARC, can't load an integer from an arbitrary address. This means that we can't store the integers in the header by casting a pointer to an int16_t to the address in the header where the integer is to be stored. Instead, we need to copy the integer 1 byte at a time. This is why we define the union containing a 16-bit integer and 2 bytes. | [595608] | We store the tag in the header and convert the length of the attribute name to network byte order. We copy the length 1 byte at a time to the header. Then we copy the attribute name. We repeat this process for the attribute value and return the address in the header where the next part of the header should begin. |
609 /*
610 * Single thread to communicate with the printer.
611 *
612 * LOCKING: acquires and releases joblock and configlock.
613 */
614 void *
615 printer_thread(void *arg)
616 {
617 struct job *jp;
618 int hlen, ilen, sockfd, fd, nr, nw;
619 char *icp, *hcp;
620 struct ipp_hdr *hp;
621 struct stat sbuf;
622 struct iovec iov[2];
623 char name[FILENMSZ];
624 char hbuf[HBUFSZ];
625 char ibuf[IBUFSZ];
626 char buf[IOBUFSZ];
627 char str[64];
628 for (;;) {
629 /*
630 * Get a job to print.
631 */
632 pthread_mutex_lock(&joblock);
633 while (jobhead == NULL) {
634 log_msg("printer_thread: waiting...");
635 pthread_cond_wait(&jobwait, &joblock);
636 }
637 remove_job(jp = jobhead);
638 log_msg("printer_thread: picked up job %ld", jp->jobid);
639 pthread_mutex_unlock(&joblock);
640 update_jobno();
[609627] | The printer_thread function is run by the thread that communicates with the network printer. We'll use icp and ibuf to build the IPP header. We'll use hcp and hbuf to build the HTTP header. We need to build the headers in separate buffers. The HTTP header includes a length field in ASCII, and we won't know how much space to reserve for it until we assemble the IPP header. We'll use writev to write these two headers in one call. | [628640] | The printer thread runs in an infinite loop that waits for jobs to transmit to the printer. We use the joblock mutex to protect the list of jobs. If a job is not pending, we use pthread_cond_wait to wait for one to arrive. When a job is ready, we remove it from the list by calling remove_job. We still hold the mutex at this point, so we release it and call update_jobno to write the next job number to /var/spool/printer/jobno. |
641 /*
642 * Check for a change in the config file.
643 */
644 pthread_mutex_lock(&configlock);
645 if (reread) {
646 freeaddrinfo(printer);
647 printer = NULL;
648 printer_name = NULL;
649 reread = 0;
650 pthread_mutex_unlock(&configlock);
651 init_printer();
652 } else {
653 pthread_mutex_unlock(&configlock);
654 }
655 /*
656 * Send job to printer.
657 */
658 sprintf(name, "%s/%s/%ld", SPOOLDIR, DATADIR, jp->jobid);
659 if ((fd = open(name, O_RDONLY)) < 0) {
660 log_msg("job %ld canceled - can't open %s: %s",
661 jp->jobid, name, strerror(errno));
662 free(jp);
663 continue;
664 }
665 if (fstat(fd, &sbuf) < 0) {
666 log_msg("job %ld canceled - can't fstat %s: %s",
667 jp->jobid, name, strerror(errno));
668 free(jp);
669 close(fd);
670 continue;
671 }
[641654] | Now that we have a job to print, we check for a change in the configuration file. We lock the configlock mutex and check the reread variable. If it is nonzero, then we free the old printer addrinfo list, clear the pointers, unlock the mutex, and call init_printer to reinitialize the printer information. Since only this context looks at and potentially changes the printer information after the main thread initialized it, we don't need any synchronization other than using the configlock mutex to protect the state of the reread flag. | | Note that although we acquire and release two different mutex locks in this function, we never hold both at the same time, so we don't need to establish a lock hierarchy (Section 11.6). | [655671] | If we can't open the data file, we log a message, free the job structure, and continue. After opening the file, we call fstat to find the size of the file. If this fails, we log a message, clean up, and continue. |
672 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
673 log_msg("job %ld deferred - can't create socket: %s",
674 jp->jobid, strerror(errno));
675 goto defer;
676 }
677 if (connect_retry(sockfd, printer->ai_addr,
678 printer->ai_addrlen) < 0) {
679 log_msg("job %ld deferred - can't contact printer: %s",
680 jp->jobid, strerror(errno));
681 goto defer;
682 }
683 /*
684 * Set up the IPP header.
685 */
686 icp = ibuf;
687 hp = (struct ipp_hdr *)icp;
688 hp->major_version = 1;
689 hp->minor_version = 1;
690 hp->operation = htons(OP_PRINT_JOB);
691 hp->request_id = htonl(jp->jobid);
692 icp += offsetof(struct ipp_hdr, attr_group);
693 *icp++ = TAG_OPERATION_ATTR;
694 icp = add_option(icp, TAG_CHARSET, "attributes-charset",
695 "utf-8");
696 icp = add_option(icp, TAG_NATULANG,
697 "attributes-natural-language", "en-us");
698 sprintf(str, "http://%s:%d", printer_name, IPP_PORT);
699 icp = add_option(icp, TAG_URI, "printer-uri", str);
[672682] | We open a stream socket to communicate with the printer. If the socket call fails, we jump down to defer, where we will clean up, delay, and try again later. If we can create a socket, we call connect_retry to connect to the printer. | [683699] | Next, we set up the IPP header. The operation is a print-job request. We use htons to convert the 2-byte operation ID from host to network byte order and htonl to convert the 4-byte job ID from host to network byte order. After the initial portion of the header, we set the tag value to indicate that operation attributes follow. We call add_option to add attributes to the message. Figure 21.4 lists the required and optional attributes for print-job requests. The first three are required. We specify the character set to be UTF-8, which the printer must support. We specify the language as en-us, which represents U.S. English. Another required attribute is the printer Universal Resource Identifier (URI). We set it to http://printer_name:631. (We really should ask the printer for a list of supported URIs and select one from that list, but that would complicate this example without adding much value.) |
700 icp = add_option(icp, TAG_NAMEWOLANG,
701 "requesting-user-name", jp->req.usernm);
702 icp = add_option(icp, TAG_NAMEWOLANG, "job-name",
703 jp->req.jobnm);
704 if (jp->req.flags & PR_TEXT) {
705 icp = add_option(icp, TAG_MIMETYPE, "document-format",
706 "text/plain");
707 } else {
708 icp = add_option(icp, TAG_MIMETYPE, "document-format",
709 "application/postscript");
710 }
711 *icp++ = TAG_END_OF_ATTR;
712 ilen = icp - ibuf;
713 /*
714 * Set up the HTTP header.
715 */
716 hcp = hbuf;
717 sprintf(hcp, "POST /%s/ipp HTTP/1.1\r\n", printer_name);
718 hcp += strlen(hcp);
719 sprintf(hcp, "Content-Length: %ld\r\n",
720 (long)sbuf.st_size + ilen);
721 hcp += strlen(hcp);
722 strcpy(hcp, "Content-Type: application/ipp\r\n");
723 hcp += strlen(hcp);
724 sprintf(hcp, "Host: %s:%d\r\n", printer_name, IPP_PORT);
725 hcp += strlen(hcp);
726 *hcp++ = '\r';
727 *hcp++ = '\n';
728 hlen = hcp - hbuf;
[700712] | The requesting-user-name attribute is recommended, but not required. The job-name attribute is optional. Recall that the print command sends the name of the file being printed as the job name, which can help users distinguish among multiple pending jobs. The last attribute we supply is the document-format. If we omit it, the printer will assume that the file conforms to the printer's default format. For a PostScript printer, this is probably PostScript, but some printers can autosense the format and choose between PostScript and text or PostScript and PCL (HP's Printer Command Language). If the PR_TEXT flag is set, we specify the document format as text/plain. Otherwise, we set it to application/postscript. Then we delimit the end of the attributes portion of the header with an end-of-attributes tag and calculate the size of the IPP header. | [713728] | Now that we know the IPP header size, we can set up the HTTP header. We set the Context-Length to the size in bytes of the IPP header plus the size of the file to be printed. The Content-Type is application/ipp. We mark the end of the HTTP header with a carriage return and a line feed. |
729 /*
730 * Write the headers first. Then send the file.
731 */
732 iov[0].iov_base = hbuf;
733 iov[0].iov_len = hlen;
734 iov[1].iov_base = ibuf;
735 iov[1].iov_len = ilen;
736 if ((nw = writev(sockfd, iov, 2)) != hlen + ilen) {
737 log_ret("can't write to printer");
738 goto defer;
739 }
740 while ((nr = read(fd, buf, IOBUFSZ)) > 0) {
741 if ((nw = write(sockfd, buf, nr)) != nr) {
742 if (nw < 0)
743 log_ret("can't write to printer");
744 else
745 log_msg("short write (%d/%d) to printer", nw, nr);
746 goto defer;
747 }
748 }
749 if (nr < 0) {
750 log_ret("can't read %s", name);
751 goto defer;
752 }
753 /*
754 * Read the response from the printer.
755 */
756 if (printer_status(sockfd, jp)) {
757 unlink(name);
758 sprintf(name, "%s/%s/%ld", SPOOLDIR, REQDIR, jp->jobid);
759 unlink(name);
760 free(jp);
761 jp = NULL;
762 }
[729739] | We set the first element of the iovec array to refer to the HTTP header and the second element to refer to the IPP header. Then we use writev to send both headers to the printer. If the write fails, we log a message and jump to defer, where we will clean up and delay before trying again. | [740752] | Next, we send the data file to the printer. We read the data file in IOBUFSZ chunks and write it to the socket connected to the printer. If either read or write fails, we log a message and jump to defer. | [753762] | After sending the entire file to be printed, we call printer_status to receive the printer's response to our print request. If printer_status succeeds, it returns a positive value, and we delete the data and control files. Then we free the job structure, set its pointer to NULL, and fall through to the defer label. |
763 defer:
764 close(fd);
765 if (sockfd >= 0)
766 close(sockfd);
767 if (jp != NULL) {
768 replace_job(jp);
769 sleep(60);
770 }
771 }
772 }
773 /*
774 * Read data from the printer, possibly increasing the buffer.
775 * Returns offset of end of data in buffer or -1 on failure.
776 *
777 * LOCKING: none.
778 */
779 ssize_t
780 readmore(int sockfd, char **bpp, int off, int *bszp)
781 {
782 ssize_t nr;
783 char *bp = *bpp;
784 int bsz = *bszp;
785 if (off >= bsz) {
786 bsz += IOBUFSZ;
787 if ((bp = realloc(*bpp, bsz)) == NULL)
788 log_sys("readmore: can't allocate bigger read buffer");
789 *bszp = bsz;
790 *bpp = bp;
791 }
792 if ((nr = tread(sockfd, &bp[off], bsz-off, 1)) > 0)
793 return(off+nr);
794 else
795 return(-1);
796 }
[763772] | At the defer label, we close the file descriptor for the open data file. If the socket descriptor is valid, we close it. On error, we place the job back on the head of the pending job list and delay for 1 minute. On success, jp is NULL, so we simply go back to the top of the loop to get the next job to print. | [773796] | The readmore function is used to read part of the response message from the printer. If we're at the end of the buffer, we reallocate a bigger buffer and return the new starting buffer address and buffer size through the bpp and bszp parameters, respectively. In either case, we read as much as the buffer will hold, starting at the end of the data already in the buffer. We return the new offset in the buffer corresponding to the end of the data read. If the read fails or the timeout expires, we return 1. |
797 /*
798 * Read and parse the response from the printer. Return 1
799 * if the request was successful, and 0 otherwise.
800 *
801 * LOCKING: none.
802 */
803 int
804 printer_status(int sockfd, struct job *jp)
805 {
806 int i, success, code, len, found, bufsz;
807 long jobid;
808 ssize_t nr;
809 char *statcode, *reason, *cp, *contentlen;
810 struct ipp_hdr *hp;
811 char *bp;
812 /*
813 * Read the HTTP header followed by the IPP response header.
814 * They can be returned in multiple read attempts. Use the
815 * Content-Length specifier to determine how much to read.
816 */
817 success = 0;
818 bufsz = IOBUFSZ;
819 if ((bp = malloc(IOBUFSZ)) == NULL)
820 log_sys("printer_status: can't allocate read buffer");
821 while ((nr = tread(sockfd, bp, IOBUFSZ, 5)) > 0) {
822 /*
823 * Find the status. Response starts with "HTTP/x.y"
824 * so we can skip the first 8 characters.
825 */
826 cp = bp + 8;
827 while (isspace((int)*cp))
828 cp++;
829 statcode = cp;
830 while (isdigit((int)*cp))
831 cp++;
832 if (cp == statcode) { /* Bad format; log it and move on */
833 log_msg(bp);
[797811] | The printer_status function reads the printer's response to a print-job request. We don't know how the printer will respond; it might send a response in multiple messages, send the complete response in one message, or include intermediate acknowledgements, such as HTTP 100 Continue messages. We need to handle all these possibilities. | [812833] | We allocate a buffer and read from the printer, expecting a response to be available within about 5 seconds. We skip the HTTP/1.1 and any white space that starts the message. The numeric status code should follow. If it doesn't, we log the contents of the message. |
834 } else {
835 *cp++ = '\0';
836 reason = cp;
837 while (*cp != '\r' && *cp != '\n')
838 cp++;
839 *cp = '\0';
840 code = atoi(statcode);
841 if (HTTP_INFO(code))
842 continue;
843 if (!HTTP_SUCCESS(code)) { /* probable error: log it */
844 bp[nr] = '\0';
845 log_msg("error: %s", reason);
846 break;
847 }
848 /*
849 * The HTTP request was okay, but we still
850 * need to check the IPP status. First
851 * search for the Content-Length specifier.
852 */
853 i = cp - bp;
854 for (;;) {
855 while (*cp != 'C' && *cp != 'c' && i < nr) {
856 cp++;
857 i++;
858 }
859 if (i >= nr && /* get more header */
860 ((nr = readmore(sockfd, &bp, i, &bufsz)) < 0))
861 goto out;
862 cp = &bp[i];
[834839] | If we have found a numeric status code in the response, we convert the first nondigit character to a null byte. The reason string (a text message) should follow. We search for the terminating carriage return or line feed, also terminating the text string with a null byte. | [840847] | We convert the code to an integer. If this is an informational message only, we ignore it and continue the loop so we end up reading more. We expect to see either a success message or an error message. If we get an error message, we log the error and break out of the loop. | [848862] | If the HTTP request was successful, we need to check the IPP status. We search through the message until we find the Content-Length attribute, so we look for a C or c. HTTP header keywords are case-insensitive, so we need to check both lowercase and uppercase characters. | | If we run out of buffer space, we read some more. Since readmore calls realloc, which might change the address of the buffer, we need to reset cp to point to the correct place in the buffer. |
863 if (strncasecmp(cp, "Content-Length:", 15) == 0) {
864 cp += 15;
865 while (isspace((int)*cp))
866 cp++;
867 contentlen = cp;
868 while (isdigit((int)*cp))
869 cp++;
870 *cp++ = '\0';
871 i = cp - bp;
872 len = atoi(contentlen);
873 break;
874 } else {
875 cp++;
876 i++;
877 }
878 }
879 if (i >= nr && /* get more header */
880 ((nr = readmore(sockfd, &bp, i, &bufsz)) < 0))
881 goto out;
882 cp = &bp[i];
883 found = 0;
884 while (!found) { /* look for end of HTTP header */
885 while (i < nr - 2) {
886 if (*cp == '\n' && *(cp + 1) == '\r' &&
887 *(cp + 2) == '\n') {
888 found = 1;
889 cp += 3;
890 i += 3;
891 break;
892 }
893 cp++;
894 i++;
895 }
896 if (i >= nr && /* get more header */
897 ((nr = readmore(sockfd, &bp, i, &bufsz)) < 0))
898 goto out;
899 cp = &bp[i];
900 }
[863882] | If we find the Content-Length attribute string, we search for its value. We convert this numeric string into an integer, break out of the for loop, and read more from the printer if we've exhausted the contents of the buffer. If we reach the end of the buffer without finding the Content-Length attribute, we continue in the loop and read some more from the printer. | [883900] | Once we get the length of the message as specified by the Content-Length attribute, we search for the end of the HTTP header (a blank line). If we find it, we set the found flag and skip past the blank line in the message. |
901 if (nr - i < len && /* get more header */
902 ((nr = readmore(sockfd, &bp, i, &bufsz)) < 0))
903 goto out;
904 cp = &bp[i];
905 hp = (struct ipp_hdr *)cp;
906 i = ntohs(hp->status);
907 jobid = ntohl(hp->request_id);
908 if (jobid != jp->jobid) {
909 /*
910 * Different jobs. Ignore it.
911 */
912 log_msg("jobid %ld status code %d", jobid, i);
913 break;
914 }
915 if (STATCLASS_OK(i))
916 success = 1;
917 break;
918 }
919 }
920 out:
921 free(bp);
922 if (nr < 0) {
923 log_msg("jobid %ld: error reading printer response: %s",
924 jobid, strerror(errno));
925 }
926 return(success);
927 }
[901904] | We continue searching for the end of the HTTP header. If we run out of space in the buffer, we read more. When we find the end of the HTTP header, we calculate the number of bytes that the HTTP header consumed. If the amount we've read minus the size of the HTTP header is not equal to the amount of data in the IPP message (the value we calculated from the content length), then we read some more. | [905927] | We get the status and job ID from the IPP header in the message. Both are stored as integers in network byte order, so we need to convert them to the host byte order by calling ntohs and ntohl. If the job IDs don't match, then this is not our response, so we log a message and break out of the outer while loop. If the IPP status indicates success, then we save the return value and break out of the loop. We return 1 if the print request was successful and 0 if it failed. |
This concludes our look at the extended example in this chapter. The programs in this chapter were tested with a Xerox Phaser 860 network-attached PostScript printer. Unfortunately, this printer doesn't recognize the text/plain document format, but it does support the ability to autosense between plaintext and PostScript. Therefore, with this printer, we can print PostScript files and text files, but we cannot print the source to a PostScript program as plaintext unless we use some other utility, such as a2ps(1) to encapsulate the PostScript program.
|