1 /*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1983, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
33 #include <sys/param.h>
34 #include <sys/stat.h>
35
36 #include <ctype.h>
37 #include <dirent.h>
38 #include <err.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <signal.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #define psignal foil_gcc_psignal
46 #define sys_siglist foil_gcc_siglist
47 #include <unistd.h>
48 #undef psignal
49 #undef sys_siglist
50
51 #include "lp.h"
52 #include "lp.local.h"
53 #include "pathnames.h"
54
55 /*
56 * Routines to display the state of the queue.
57 */
58 #define JOBCOL 40 /* column for job # in -l format */
59 #define OWNCOL 7 /* start of Owner column in normal */
60 #define SIZCOL 62 /* start of Size column in normal */
61
62 /*
63 * isprint() takes a parameter of 'int', but expect values in the range
64 * of unsigned char. Define a wrapper which takes a value of type 'char',
65 * whether signed or unsigned, and ensure it ends up in the right range.
66 */
67 #define isprintch(Anychar) isprint((u_char)(Anychar))
68
69 /*
70 * Stuff for handling job specifications
71 */
72 static int col; /* column on screen */
73 static char current[MAXNAMLEN+1]; /* current file being printed */
74 static char file[MAXNAMLEN+1]; /* print file name */
75 static int first; /* first file in ``files'' column? */
76 static int garbage; /* # of garbage cf files */
77 static int lflag; /* long output option */
78 static int rank; /* order to be printed (-1=none, 0=active) */
79 static long totsize; /* total print job size in bytes */
80
81 static const char *head0 = "Rank Owner Job Files";
82 static const char *head1 = "Total Size\n";
83
84 static void alarmhandler(int _signo);
85 static void filtered_write(char *_obuffer, int _wlen, FILE *_wstream);
86 static void daemonwarn(const struct printer *_pp);
87
88 /*
89 * Display the current state of the queue. Format = 1 if long format.
90 */
91 void
displayq(struct printer * pp,int format)92 displayq(struct printer *pp, int format)
93 {
94 register struct jobqueue *q;
95 register int i, nitems, fd, ret;
96 char *cp, *endp;
97 struct jobqueue **queue;
98 struct stat statb;
99 FILE *fp;
100 void (*savealrm)(int);
101
102 lflag = format;
103 totsize = 0;
104 rank = -1;
105
106 if ((cp = checkremote(pp))) {
107 printf("Warning: %s\n", cp);
108 free(cp);
109 }
110
111 /*
112 * Print out local queue
113 * Find all the control files in the spooling directory
114 */
115 PRIV_START
116 if (chdir(pp->spool_dir) < 0)
117 fatal(pp, "cannot chdir to spooling directory: %s",
118 strerror(errno));
119 PRIV_END
120 if ((nitems = getq(pp, &queue)) < 0)
121 fatal(pp, "cannot examine spooling area\n");
122 PRIV_START
123 ret = stat(pp->lock_file, &statb);
124 PRIV_END
125 if (ret >= 0) {
126 if (statb.st_mode & LFM_PRINT_DIS) {
127 if (pp->remote)
128 printf("%s: ", local_host);
129 printf("Warning: %s is down: ", pp->printer);
130 PRIV_START
131 fd = open(pp->status_file, O_RDONLY|O_SHLOCK);
132 PRIV_END
133 if (fd >= 0) {
134 while ((i = read(fd, line, sizeof(line))) > 0)
135 (void) fwrite(line, 1, i, stdout);
136 (void) close(fd); /* unlocks as well */
137 } else
138 putchar('\n');
139 }
140 if (statb.st_mode & LFM_QUEUE_DIS) {
141 if (pp->remote)
142 printf("%s: ", local_host);
143 printf("Warning: %s queue is turned off\n",
144 pp->printer);
145 }
146 }
147
148 if (nitems) {
149 PRIV_START
150 fp = fopen(pp->lock_file, "r");
151 PRIV_END
152 if (fp == NULL)
153 daemonwarn(pp);
154 else {
155 /* get daemon pid */
156 cp = current;
157 endp = cp + sizeof(current) - 1;
158 while ((i = getc(fp)) != EOF && i != '\n') {
159 if (cp < endp)
160 *cp++ = i;
161 }
162 *cp = '\0';
163 i = atoi(current);
164 if (i <= 0) {
165 ret = -1;
166 } else {
167 PRIV_START
168 ret = kill(i, 0);
169 PRIV_END
170 }
171 if (ret < 0) {
172 daemonwarn(pp);
173 } else {
174 /* read current file name */
175 cp = current;
176 endp = cp + sizeof(current) - 1;
177 while ((i = getc(fp)) != EOF && i != '\n') {
178 if (cp < endp)
179 *cp++ = i;
180 }
181 *cp = '\0';
182 /*
183 * Print the status file.
184 */
185 if (pp->remote)
186 printf("%s: ", local_host);
187 PRIV_START
188 fd = open(pp->status_file, O_RDONLY|O_SHLOCK);
189 PRIV_END
190 if (fd >= 0) {
191 while ((i = read(fd, line,
192 sizeof(line))) > 0)
193 fwrite(line, 1, i, stdout);
194 close(fd); /* unlocks as well */
195 } else
196 putchar('\n');
197 }
198 (void) fclose(fp);
199 }
200 /*
201 * Now, examine the control files and print out the jobs to
202 * be done for each user.
203 */
204 if (!lflag)
205 header();
206 for (i = 0; i < nitems; i++) {
207 q = queue[i];
208 inform(pp, q->job_cfname);
209 free(q);
210 }
211 free(queue);
212 }
213 if (!pp->remote) {
214 if (nitems == 0)
215 puts("no entries");
216 return;
217 }
218
219 /*
220 * Print foreign queue
221 * Note that a file in transit may show up in either queue.
222 */
223 if (nitems)
224 putchar('\n');
225 (void) snprintf(line, sizeof(line), "%c%s", format ? '\4' : '\3',
226 pp->remote_queue);
227 cp = line;
228 for (i = 0; i < requests && cp-line+10 < sizeof(line) - 1; i++) {
229 cp += strlen(cp);
230 (void) sprintf(cp, " %d", requ[i]);
231 }
232 for (i = 0; i < users && cp - line + 1 + strlen(user[i]) <
233 sizeof(line) - 1; i++) {
234 cp += strlen(cp);
235 *cp++ = ' ';
236 (void) strcpy(cp, user[i]);
237 }
238 strcat(line, "\n");
239 savealrm = signal(SIGALRM, alarmhandler);
240 alarm(pp->conn_timeout);
241 fd = getport(pp, pp->remote_host, 0);
242 alarm(0);
243 (void)signal(SIGALRM, savealrm);
244 if (fd < 0) {
245 if (from_host != local_host)
246 printf("%s: ", local_host);
247 printf("connection to %s is down\n", pp->remote_host);
248 }
249 else {
250 i = strlen(line);
251 if (write(fd, line, i) != i)
252 fatal(pp, "Lost connection");
253 while ((i = read(fd, line, sizeof(line))) > 0)
254 filtered_write(line, i, stdout);
255 filtered_write(NULL, -1, stdout);
256 (void) close(fd);
257 }
258 }
259
260 /*
261 * The lpq-info read from remote hosts may contain unprintable characters,
262 * or carriage-returns instead of line-feeds. Clean those up before echoing
263 * the lpq-info line(s) to stdout. The info may also be missing any kind of
264 * end-of-line character. This also turns CRLF and LFCR into a plain LF.
265 *
266 * This routine may be called multiple times to process a single set of
267 * information, and after a set is finished this routine must be called
268 * one extra time with NULL specified as the buffer address.
269 */
270 static void
filtered_write(char * wbuffer,int wlen,FILE * wstream)271 filtered_write(char *wbuffer, int wlen, FILE *wstream)
272 {
273 static char lastchar, savedchar;
274 char *chkptr, *dest_end, *dest_ch, *nxtptr, *w_end;
275 int destlen;
276 char destbuf[BUFSIZ];
277
278 if (wbuffer == NULL) {
279 if (savedchar != '\0') {
280 if (savedchar == '\r')
281 savedchar = '\n';
282 fputc(savedchar, wstream);
283 lastchar = savedchar;
284 savedchar = '\0';
285 }
286 if (lastchar != '\0' && lastchar != '\n')
287 fputc('\n', wstream);
288 lastchar = '\0';
289 return;
290 }
291
292 dest_ch = &destbuf[0];
293 dest_end = dest_ch + sizeof(destbuf);
294 chkptr = wbuffer;
295 w_end = wbuffer + wlen;
296 lastchar = '\0';
297 if (savedchar != '\0') {
298 chkptr = &savedchar;
299 nxtptr = wbuffer;
300 } else
301 nxtptr = chkptr + 1;
302
303 while (chkptr < w_end) {
304 if (nxtptr < w_end) {
305 if ((*chkptr == '\r' && *nxtptr == '\n') ||
306 (*chkptr == '\n' && *nxtptr == '\r')) {
307 *dest_ch++ = '\n';
308 /* want to skip past that second character */
309 nxtptr++;
310 goto check_next;
311 }
312 } else {
313 /* This is the last byte in the buffer given on this
314 * call, so check if it could be the first-byte of a
315 * significant two-byte sequence. If it is, then
316 * don't write it out now, but save for checking in
317 * the next call.
318 */
319 savedchar = '\0';
320 if (*chkptr == '\r' || *chkptr == '\n') {
321 savedchar = *chkptr;
322 break;
323 }
324 }
325 if (*chkptr == '\r')
326 *dest_ch++ = '\n';
327 #if 0 /* XXX - don't translate unprintable characters (yet) */
328 else if (*chkptr != '\t' && *chkptr != '\n' &&
329 !isprintch(*chkptr))
330 *dest_ch++ = '?';
331 #endif
332 else
333 *dest_ch++ = *chkptr;
334
335 check_next:
336 chkptr = nxtptr;
337 nxtptr = chkptr + 1;
338 if (dest_ch >= dest_end) {
339 destlen = dest_ch - &destbuf[0];
340 fwrite(destbuf, 1, destlen, wstream);
341 lastchar = destbuf[destlen - 1];
342 dest_ch = &destbuf[0];
343 }
344 }
345 destlen = dest_ch - &destbuf[0];
346 if (destlen > 0) {
347 fwrite(destbuf, 1, destlen, wstream);
348 lastchar = destbuf[destlen - 1];
349 }
350 }
351
352 /*
353 * Print a warning message if there is no daemon present.
354 */
355 static void
daemonwarn(const struct printer * pp)356 daemonwarn(const struct printer *pp)
357 {
358 if (pp->remote)
359 printf("%s: ", local_host);
360 puts("Warning: no daemon present");
361 current[0] = '\0';
362 }
363
364 /*
365 * Print the header for the short listing format
366 */
367 void
header(void)368 header(void)
369 {
370 printf("%s", head0);
371 col = strlen(head0)+1;
372 blankfill(SIZCOL);
373 printf("%s", head1);
374 }
375
376 void
inform(const struct printer * pp,char * cf)377 inform(const struct printer *pp, char *cf)
378 {
379 int copycnt, jnum;
380 char savedname[MAXPATHLEN+1];
381 FILE *cfp;
382
383 /*
384 * There's a chance the control file has gone away
385 * in the meantime; if this is the case just keep going
386 */
387 PRIV_START
388 if ((cfp = fopen(cf, "r")) == NULL)
389 return;
390 PRIV_END
391
392 if (rank < 0)
393 rank = 0;
394 if (pp->remote || garbage || strcmp(cf, current))
395 rank++;
396
397 /*
398 * The cf-file may include commands to print more than one datafile
399 * from the user. For each datafile, the cf-file contains at least
400 * one line which starts with some format-specifier ('a'-'z'), and
401 * a second line ('N'ame) which indicates the original name the user
402 * specified for that file. There can be multiple format-spec lines
403 * for a single Name-line, if the user requested multiple copies of
404 * that file. Standard lpr puts the format-spec line(s) before the
405 * Name-line, while lprNG puts the Name-line before the format-spec
406 * line(s). This section needs to handle the lines in either order.
407 */
408 copycnt = 0;
409 file[0] = '\0';
410 savedname[0] = '\0';
411 jnum = calc_jobnum(cf, NULL);
412 while (get_line(cfp)) {
413 switch (line[0]) {
414 case 'P': /* Was this file specified in the user's list? */
415 if (!inlist(line+1, cf)) {
416 fclose(cfp);
417 return;
418 }
419 if (lflag) {
420 printf("\n%s: ", line+1);
421 col = strlen(line+1) + 2;
422 prank(rank);
423 blankfill(JOBCOL);
424 printf(" [job %s]\n", cf+3);
425 } else {
426 col = 0;
427 prank(rank);
428 blankfill(OWNCOL);
429 printf("%-10s %-3d ", line+1, jnum);
430 col += 16;
431 first = 1;
432 }
433 continue;
434 default: /* some format specifer and file name? */
435 if (line[0] < 'a' || line[0] > 'z')
436 break;
437 if (copycnt == 0 || strcmp(file, line+1) != 0) {
438 strlcpy(file, line + 1, sizeof(file));
439 }
440 copycnt++;
441 /*
442 * deliberately 'continue' to another get_line(), so
443 * all format-spec lines for this datafile are read
444 * in and counted before calling show()
445 */
446 continue;
447 case 'N':
448 strlcpy(savedname, line + 1, sizeof(savedname));
449 break;
450 }
451 if ((file[0] != '\0') && (savedname[0] != '\0')) {
452 show(savedname, file, copycnt);
453 copycnt = 0;
454 file[0] = '\0';
455 savedname[0] = '\0';
456 }
457 }
458 fclose(cfp);
459 /* check for a file which hasn't been shown yet */
460 if (file[0] != '\0') {
461 if (savedname[0] == '\0') {
462 /* a safeguard in case the N-ame line is missing */
463 strlcpy(savedname, file, sizeof(savedname));
464 }
465 show(savedname, file, copycnt);
466 }
467 if (!lflag) {
468 blankfill(SIZCOL);
469 printf("%ld bytes\n", totsize);
470 totsize = 0;
471 }
472 }
473
474 int
inlist(char * uname,char * cfile)475 inlist(char *uname, char *cfile)
476 {
477 int *r, jnum;
478 char **u;
479 const char *cfhost;
480
481 if (users == 0 && requests == 0)
482 return(1);
483 /*
484 * Check to see if it's in the user list
485 */
486 for (u = user; u < &user[users]; u++)
487 if (!strcmp(*u, uname))
488 return(1);
489 /*
490 * Check the request list
491 */
492 jnum = calc_jobnum(cfile, &cfhost);
493 for (r = requ; r < &requ[requests]; r++)
494 if (*r == jnum && !strcmp(cfhost, from_host))
495 return(1);
496 return(0);
497 }
498
499 void
show(const char * nfile,const char * datafile,int copies)500 show(const char *nfile, const char *datafile, int copies)
501 {
502 if (strcmp(nfile, " ") == 0)
503 nfile = "(standard input)";
504 if (lflag)
505 ldump(nfile, datafile, copies);
506 else
507 dump(nfile, datafile, copies);
508 }
509
510 /*
511 * Fill the line with blanks to the specified column
512 */
513 void
blankfill(int tocol)514 blankfill(int tocol)
515 {
516 while (col++ < tocol)
517 putchar(' ');
518 }
519
520 /*
521 * Give the abbreviated dump of the file names
522 */
523 void
dump(const char * nfile,const char * datafile,int copies)524 dump(const char *nfile, const char *datafile, int copies)
525 {
526 struct stat lbuf;
527 const char etctmpl[] = ", ...";
528 char etc[sizeof(etctmpl)];
529 char *lastsep;
530 short fill, nlen;
531 short rem, remetc;
532
533 /*
534 * Print as many filenames as will fit
535 * (leaving room for the 'total size' field)
536 */
537 fill = first ? 0 : 2; /* fill space for ``, '' */
538 nlen = strlen(nfile);
539 rem = SIZCOL - 1 - col;
540 if (nlen + fill > rem) {
541 if (first) {
542 /* print the right-most part of the name */
543 printf("...%s ", &nfile[3+nlen-rem]);
544 col = SIZCOL;
545 } else if (rem > 0) {
546 /* fit as much of the etc-string as we can */
547 remetc = rem;
548 if (rem > strlen(etctmpl))
549 remetc = strlen(etctmpl);
550 etc[0] = '\0';
551 strncat(etc, etctmpl, remetc);
552 printf("%s", etc);
553 col += remetc;
554 rem -= remetc;
555 /* room for the last segment of this filename? */
556 lastsep = strrchr(nfile, '/');
557 if ((lastsep != NULL) && (rem > strlen(lastsep))) {
558 /* print the right-most part of this name */
559 printf("%s", lastsep);
560 col += strlen(lastsep);
561 } else {
562 /* do not pack any more names in here */
563 blankfill(SIZCOL);
564 }
565 }
566 } else {
567 if (!first)
568 printf(", ");
569 printf("%s", nfile);
570 col += nlen + fill;
571 }
572 first = 0;
573
574 PRIV_START
575 if (*datafile && !stat(datafile, &lbuf))
576 totsize += copies * lbuf.st_size;
577 PRIV_END
578 }
579
580 /*
581 * Print the long info about the file
582 */
583 void
ldump(const char * nfile,const char * datafile,int copies)584 ldump(const char *nfile, const char *datafile, int copies)
585 {
586 struct stat lbuf;
587
588 putchar('\t');
589 if (copies > 1)
590 printf("%-2d copies of %-19s", copies, nfile);
591 else
592 printf("%-32s", nfile);
593 if (*datafile && !stat(datafile, &lbuf))
594 printf(" %qd bytes", (long long) lbuf.st_size);
595 else
596 printf(" ??? bytes");
597 putchar('\n');
598 }
599
600 /*
601 * Print the job's rank in the queue,
602 * update col for screen management
603 */
604 void
prank(int n)605 prank(int n)
606 {
607 char rline[100];
608 static const char *r[] = {
609 "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"
610 };
611
612 if (n == 0) {
613 printf("active");
614 col += 6;
615 return;
616 }
617 if ((n/10)%10 == 1)
618 (void)snprintf(rline, sizeof(rline), "%dth", n);
619 else
620 (void)snprintf(rline, sizeof(rline), "%d%s", n, r[n%10]);
621 col += strlen(rline);
622 printf("%s", rline);
623 }
624
625 void
alarmhandler(int signo __unused)626 alarmhandler(int signo __unused)
627 {
628 /* the signal is ignored */
629 /* (the '__unused' is just to avoid a compile-time warning) */
630 }
631