1 /*
2  * Copyright (c) 1983, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * @(#)displayq.c	8.4 (Berkeley) 4/28/95
30  * $FreeBSD: src/usr.sbin/lpr/common_source/displayq.c,v 1.15.2.8 2001/08/30 09:27:41 kris Exp $
31  * $DragonFly: src/usr.sbin/lpr/common_source/displayq.c,v 1.5 2005/08/08 18:58:56 joerg Exp $
32  */
33 
34 #include <sys/param.h>
35 #include <sys/stat.h>
36 
37 #include <ctype.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <limits.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  * Stuff for handling job specifications
64  */
65 extern uid_t	uid, euid;
66 
67 static int	col;		/* column on screen */
68 static char	current[NAME_MAX+1];	/* current file being printed */
69 static char	file[NAME_MAX+1];	/* print file name */
70 static int	first;		/* first file in ``files'' column? */
71 static int	garbage;	/* # of garbage cf files */
72 static int	lflag;		/* long output option */
73 static int	rank;		/* order to be printed (-1=none, 0=active) */
74 static long	totsize;	/* total print job size in bytes */
75 
76 static const char  *head0 = "Rank   Owner      Job  Files";
77 static const char  *head1 = "Total Size\n";
78 
79 static void	alarmhandler(int _signo);
80 static void	warn(const struct printer *_pp);
81 
82 /*
83  * Display the current state of the queue. Format = 1 if long format.
84  */
85 void
86 displayq(struct printer *pp, int format)
87 {
88 	struct jobqueue *q;
89 	int i, nitems, fd, ret;
90 	char *cp, *endp;
91 	struct jobqueue **queue;
92 	struct stat statb;
93 	FILE *fp;
94 	void (*savealrm)(int);
95 
96 	lflag = format;
97 	totsize = 0;
98 	rank = -1;
99 
100 	if ((cp = checkremote(pp))) {
101 		printf("Warning: %s\n", cp);
102 		free(cp);
103 	}
104 
105 	/*
106 	 * Print out local queue
107 	 * Find all the control files in the spooling directory
108 	 */
109 	seteuid(euid);
110 	if (chdir(pp->spool_dir) < 0)
111 		fatal(pp, "cannot chdir to spooling directory: %s",
112 		      strerror(errno));
113 	seteuid(uid);
114 	if ((nitems = getq(pp, &queue)) < 0)
115 		fatal(pp, "cannot examine spooling area\n");
116 	seteuid(euid);
117 	ret = stat(pp->lock_file, &statb);
118 	seteuid(uid);
119 	if (ret >= 0) {
120 		if (statb.st_mode & LFM_PRINT_DIS) {
121 			if (pp->remote)
122 				printf("%s: ", local_host);
123 			printf("Warning: %s is down: ", pp->printer);
124 			seteuid(euid);
125 			fd = open(pp->status_file, O_RDONLY|O_SHLOCK);
126 			seteuid(uid);
127 			if (fd >= 0) {
128 				while ((i = read(fd, line, sizeof(line))) > 0)
129 					fwrite(line, 1, i, stdout);
130 				close(fd);	/* unlocks as well */
131 			} else
132 				putchar('\n');
133 		}
134 		if (statb.st_mode & LFM_QUEUE_DIS) {
135 			if (pp->remote)
136 				printf("%s: ", local_host);
137 			printf("Warning: %s queue is turned off\n",
138 			       pp->printer);
139 		}
140 	}
141 
142 	if (nitems) {
143 		seteuid(euid);
144 		fp = fopen(pp->lock_file, "r");
145 		seteuid(uid);
146 		if (fp == NULL)
147 			warn(pp);
148 		else {
149 			/* get daemon pid */
150 			cp = current;
151 			endp = cp + sizeof(current) - 1;
152 			while ((i = getc(fp)) != EOF && i != '\n') {
153 				if (cp < endp)
154 					*cp++ = i;
155 			}
156 			*cp = '\0';
157 			i = atoi(current);
158 			if (i <= 0) {
159 				ret = -1;
160 			} else {
161 				seteuid(euid);
162 				ret = kill(i, 0);
163 				seteuid(uid);
164 			}
165 			if (ret < 0) {
166 				warn(pp);
167 			} else {
168 				/* read current file name */
169 				cp = current;
170 				endp = cp + sizeof(current) - 1;
171 				while ((i = getc(fp)) != EOF && i != '\n') {
172 					if (cp < endp)
173 						*cp++ = i;
174 				}
175 				*cp = '\0';
176 				/*
177 				 * Print the status file.
178 				 */
179 				if (pp->remote)
180 					printf("%s: ", local_host);
181 				seteuid(euid);
182 				fd = open(pp->status_file, O_RDONLY|O_SHLOCK);
183 				seteuid(uid);
184 				if (fd >= 0) {
185 					while ((i = read(fd, line,
186 							 sizeof(line))) > 0)
187 						fwrite(line, 1, i, stdout);
188 					close(fd);	/* unlocks as well */
189 				} else
190 					putchar('\n');
191 			}
192 			fclose(fp);
193 		}
194 		/*
195 		 * Now, examine the control files and print out the jobs to
196 		 * be done for each user.
197 		 */
198 		if (!lflag)
199 			header();
200 		for (i = 0; i < nitems; i++) {
201 			q = queue[i];
202 			inform(pp, q->job_cfname);
203 			free(q);
204 		}
205 		free(queue);
206 	}
207 	if (!pp->remote) {
208 		if (nitems == 0)
209 			puts("no entries");
210 		return;
211 	}
212 
213 	/*
214 	 * Print foreign queue
215 	 * Note that a file in transit may show up in either queue.
216 	 */
217 	if (nitems)
218 		putchar('\n');
219 	snprintf(line, sizeof(line), "%c%s", format ? '\4' : '\3',
220 		 pp->remote_queue);
221 	cp = line;
222 	for (i = 0; i < requests && cp-line+10 < sizeof(line) - 1; i++) {
223 		cp += strlen(cp);
224 		sprintf(cp, " %d", requ[i]);
225 	}
226 	for (i = 0; i < users && cp - line + 1 + strlen(user[i]) <
227 		sizeof(line) - 1; i++) {
228 		cp += strlen(cp);
229 		*cp++ = ' ';
230 		strcpy(cp, user[i]);
231 	}
232 	strcat(line, "\n");
233 	savealrm = signal(SIGALRM, alarmhandler);
234 	alarm(pp->conn_timeout);
235 	fd = getport(pp, pp->remote_host, 0);
236 	alarm(0);
237 	signal(SIGALRM, savealrm);
238 	if (fd < 0) {
239 		if (from_host != local_host)
240 			printf("%s: ", local_host);
241 		printf("connection to %s is down\n", pp->remote_host);
242 	}
243 	else {
244 		i = strlen(line);
245 		if (write(fd, line, i) != i)
246 			fatal(pp, "Lost connection");
247 		while ((i = read(fd, line, sizeof(line))) > 0)
248 			fwrite(line, 1, i, stdout);
249 		close(fd);
250 	}
251 }
252 
253 /*
254  * Print a warning message if there is no daemon present.
255  */
256 static void
257 warn(const struct printer *pp)
258 {
259 	if (pp->remote)
260 		printf("%s: ", local_host);
261 	puts("Warning: no daemon present");
262 	current[0] = '\0';
263 }
264 
265 /*
266  * Print the header for the short listing format
267  */
268 void
269 header(void)
270 {
271 	printf("%s", head0);
272 	col = strlen(head0)+1;
273 	blankfill(SIZCOL);
274 	printf("%s", head1);
275 }
276 
277 void
278 inform(const struct printer *pp, char *cf)
279 {
280 	int copycnt;
281 	char savedname[MAXPATHLEN+1];
282 	FILE *cfp;
283 
284 	/*
285 	 * There's a chance the control file has gone away
286 	 * in the meantime; if this is the case just keep going
287 	 */
288 	seteuid(euid);
289 	if ((cfp = fopen(cf, "r")) == NULL)
290 		return;
291 	seteuid(uid);
292 
293 	if (rank < 0)
294 		rank = 0;
295 	if (pp->remote || garbage || strcmp(cf, current))
296 		rank++;
297 
298 	/*
299 	 * The cf-file may include commands to print more than one datafile
300 	 * from the user.  For each datafile, the cf-file contains at least
301 	 * one line which starts with some format-specifier ('a'-'z'), and
302 	 * a second line ('N'ame) which indicates the original name the user
303 	 * specified for that file.  There can be multiple format-spec lines
304 	 * for a single Name-line, if the user requested multiple copies of
305 	 * that file.  Standard lpr puts the format-spec line(s) before the
306 	 * Name-line, while lprNG puts the Name-line before the format-spec
307 	 * line(s).  This section needs to handle the lines in either order.
308 	 */
309 	copycnt = 0;
310 	file[0] = '\0';
311 	savedname[0] = '\0';
312 	while (get_line(cfp)) {
313 		switch (line[0]) {
314 		case 'P': /* Was this file specified in the user's list? */
315 			if (!inlist(line+1, cf)) {
316 				fclose(cfp);
317 				return;
318 			}
319 			if (lflag) {
320 				printf("\n%s: ", line+1);
321 				col = strlen(line+1) + 2;
322 				prank(rank);
323 				blankfill(JOBCOL);
324 				printf(" [job %s]\n", cf+3);
325 			} else {
326 				col = 0;
327 				prank(rank);
328 				blankfill(OWNCOL);
329 				printf("%-10s %-3d  ", line+1, atoi(cf+3));
330 				col += 16;
331 				first = 1;
332 			}
333 			continue;
334 		default: /* some format specifer and file name? */
335 			if (line[0] < 'a' || line[0] > 'z')
336 				break;
337 			if (copycnt == 0 || strcmp(file, line+1) != 0) {
338 				strlcpy(file, line + 1, sizeof(file));
339 			}
340 			copycnt++;
341 			/*
342 			 * deliberately 'continue' to another get_line(), so
343 			 * all format-spec lines for this datafile are read
344 			 * in and counted before calling show()
345 			 */
346 			continue;
347 		case 'N':
348 			strlcpy(savedname, line + 1, sizeof(savedname));
349 			break;
350 		}
351 		if ((file[0] != '\0') && (savedname[0] != '\0')) {
352 			show(savedname, file, copycnt);
353 			copycnt = 0;
354 			file[0] = '\0';
355 			savedname[0] = '\0';
356 		}
357 	}
358 	fclose(cfp);
359 	/* check for a file which hasn't been shown yet */
360 	if (file[0] != '\0') {
361 		if (savedname[0] == '\0') {
362 			/* a safeguard in case the N-ame line is missing */
363 			strlcpy(savedname, file, sizeof(savedname));
364 		}
365 		show(savedname, file, copycnt);
366 	}
367 	if (!lflag) {
368 		blankfill(SIZCOL);
369 		printf("%ld bytes\n", totsize);
370 		totsize = 0;
371 	}
372 }
373 
374 int
375 inlist(char *uname, char *cfile)
376 {
377 	int *r, n;
378 	char **u, *cp;
379 
380 	if (users == 0 && requests == 0)
381 		return(1);
382 	/*
383 	 * Check to see if it's in the user list
384 	 */
385 	for (u = user; u < &user[users]; u++)
386 		if (!strcmp(*u, uname))
387 			return(1);
388 	/*
389 	 * Check the request list
390 	 */
391 	for (n = 0, cp = cfile+3; isdigit(*cp); )
392 		n = n * 10 + (*cp++ - '0');
393 	for (r = requ; r < &requ[requests]; r++)
394 		if (*r == n && !strcmp(cp, from_host))
395 			return(1);
396 	return(0);
397 }
398 
399 void
400 show(const char *nfile, const char *datafile, int copies)
401 {
402 	if (strcmp(nfile, " ") == 0)
403 		nfile = "(standard input)";
404 	if (lflag)
405 		ldump(nfile, datafile, copies);
406 	else
407 		dump(nfile, datafile, copies);
408 }
409 
410 /*
411  * Fill the line with blanks to the specified column
412  */
413 void
414 blankfill(int tocol)
415 {
416 	while (col++ < tocol)
417 		putchar(' ');
418 }
419 
420 /*
421  * Give the abbreviated dump of the file names
422  */
423 void
424 dump(const char *nfile, const char *datafile, int copies)
425 {
426 	struct stat lbuf;
427 	const char etctmpl[] = ", ...";
428 	char	 etc[sizeof(etctmpl)];
429 	char	*lastsep;
430 	short	 fill, nlen;
431 	short	 rem, remetc;
432 
433 	/*
434 	 * Print as many filenames as will fit
435 	 *      (leaving room for the 'total size' field)
436 	 */
437 	fill = first ? 0 : 2;	/* fill space for ``, '' */
438 	nlen = strlen(nfile);
439 	rem = SIZCOL - 1 - col;
440 	if (nlen + fill > rem) {
441 		if (first) {
442 			/* print the right-most part of the name */
443 			printf("...%s ", &nfile[3+nlen-rem]);
444 			col = SIZCOL;
445 		} else if (rem > 0) {
446 			/* fit as much of the etc-string as we can */
447 			remetc = rem;
448 			if (rem > strlen(etctmpl))
449 				remetc = strlen(etctmpl);
450 			etc[0] = '\0';
451 			strncat(etc, etctmpl, remetc);
452 			printf("%s", etc);
453 			col += remetc;
454 			rem -= remetc;
455 			/* room for the last segment of this filename? */
456 			lastsep = strrchr(nfile, '/');
457 			if ((lastsep != NULL) && (rem > strlen(lastsep))) {
458 				/* print the right-most part of this name */
459 				printf("%s", lastsep);
460 				col += strlen(lastsep);
461 			} else {
462 				/* do not pack any more names in here */
463 				blankfill(SIZCOL);
464 			}
465 		}
466 	} else {
467 		if (!first)
468 			printf(", ");
469 		printf("%s", nfile);
470 		col += nlen + fill;
471 	}
472 	first = 0;
473 
474 	seteuid(euid);
475 	if (*datafile && !stat(datafile, &lbuf))
476 		totsize += copies * lbuf.st_size;
477 	seteuid(uid);
478 }
479 
480 /*
481  * Print the long info about the file
482  */
483 void
484 ldump(const char *nfile, const char *datafile, int copies)
485 {
486 	struct stat lbuf;
487 
488 	putchar('\t');
489 	if (copies > 1)
490 		printf("%-2d copies of %-19s", copies, nfile);
491 	else
492 		printf("%-32s", nfile);
493 	if (*datafile && !stat(datafile, &lbuf))
494 		printf(" %qd bytes", (long long) lbuf.st_size);
495 	else
496 		printf(" ??? bytes");
497 	putchar('\n');
498 }
499 
500 /*
501  * Print the job's rank in the queue,
502  *   update col for screen management
503  */
504 void
505 prank(int n)
506 {
507 	char rline[100];
508 	static const char *r[] = {
509 		"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"
510 	};
511 
512 	if (n == 0) {
513 		printf("active");
514 		col += 6;
515 		return;
516 	}
517 	if ((n/10)%10 == 1)
518 		snprintf(rline, sizeof(rline), "%dth", n);
519 	else
520 		snprintf(rline, sizeof(rline), "%d%s", n, r[n%10]);
521 	col += strlen(rline);
522 	printf("%s", rline);
523 }
524 
525 void
526 alarmhandler(int signo __unused)
527 {
528 	/* the signal is ignored */
529 	/* (the '__unused' is just to avoid a compile-time warning) */
530 }
531