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 * 4. 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
30#if 0
31#ifndef lint
32static char sccsid[] = "@(#)displayq.c	8.4 (Berkeley) 4/28/95";
33#endif /* not lint */
34#endif
35
36#include "lp.cdefs.h"		/* A cross-platform version of <sys/cdefs.h> */
37__FBSDID("$FreeBSD$");
38
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 */
78static int	col;		/* column on screen */
79static char	current[MAXNAMLEN+1];	/* current file being printed */
80static char	file[MAXNAMLEN+1];	/* print file name */
81static int	first;		/* first file in ``files'' column? */
82static int	garbage;	/* # of garbage cf files */
83static int	lflag;		/* long output option */
84static int	rank;		/* order to be printed (-1=none, 0=active) */
85static long	totsize;	/* total print job size in bytes */
86
87static const char  *head0 = "Rank   Owner      Job  Files";
88static const char  *head1 = "Total Size\n";
89
90static void	alarmhandler(int _signo);
91static void	filtered_write(char *_obuffer, int _wlen, FILE *_wstream);
92static void	daemonwarn(const struct printer *_pp);
93
94/*
95 * Display the current state of the queue. Format = 1 if long format.
96 */
97void
98displayq(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 */
276static void
277filtered_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
341check_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 */
361static void
362daemonwarn(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 */
373void
374header(void)
375{
376	printf("%s", head0);
377	col = strlen(head0)+1;
378	blankfill(SIZCOL);
379	printf("%s", head1);
380}
381
382void
383inform(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 (getline(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 getline(), 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
480int
481inlist(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
505void
506show(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 */
519void
520blankfill(int tocol)
521{
522	while (col++ < tocol)
523		putchar(' ');
524}
525
526/*
527 * Give the abbreviated dump of the file names
528 */
529void
530dump(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 */
589void
590ldump(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 */
610void
611prank(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
631void
632alarmhandler(int signo __unused)
633{
634	/* the signal is ignored */
635	/* (the '__unused' is just to avoid a compile-time warning) */
636}
637