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