xref: /freebsd/usr.sbin/lpr/lpc/cmds.c (revision 11f0b352e05306cf6f1f85e9087022c0a92624a3)
1 /*
2  * Copyright (c) 1983, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *	This product includes software developed by the University of
17  *	California, Berkeley and its contributors.
18  * 4. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #ifndef lint
36 static const char copyright[] =
37 "@(#) Copyright (c) 1983, 1993\n\
38 	The Regents of the University of California.  All rights reserved.\n";
39 #endif /* not lint */
40 
41 #ifndef lint
42 /*
43 static char sccsid[] = "@(#)cmds.c	8.2 (Berkeley) 4/28/95";
44 */
45 static const char rcsid[] =
46   "$FreeBSD$";
47 #endif /* not lint */
48 
49 /*
50  * lpc -- line printer control program -- commands:
51  */
52 
53 #include <sys/param.h>
54 #include <sys/time.h>
55 #include <sys/stat.h>
56 #include <sys/file.h>
57 
58 #include <signal.h>
59 #include <fcntl.h>
60 #include <errno.h>
61 #include <dirent.h>
62 #include <unistd.h>
63 #include <stdlib.h>
64 #include <stdio.h>
65 #include <ctype.h>
66 #include <string.h>
67 #include "lp.h"
68 #include "lp.local.h"
69 #include "lpc.h"
70 #include "extern.h"
71 #include "pathnames.h"
72 
73 /*
74  * Return values from kill_qtask().
75  */
76 #define KQT_LFERROR	-2
77 #define KQT_KILLFAIL	-1
78 #define KQT_NODAEMON	0
79 #define KQT_KILLOK	1
80 
81 static char	*args2line(int argc, char **argv);
82 static int	 doarg(char *_job);
83 static int	 doselect(struct dirent *_d);
84 static int	 kill_qtask(const char *lf);
85 static int	 sortq(const void *_a, const void *_b);
86 static int	 touch(struct jobqueue *_jq);
87 static void	 unlinkf(char *_name);
88 static void	 upstat(struct printer *_pp, const char *_msg, int _notify);
89 static void	 wrapup_clean(int _laststatus);
90 
91 /*
92  * generic framework for commands which operate on all or a specified
93  * set of printers
94  */
95 enum	qsel_val {			/* how a given ptr was selected */
96 	QSEL_UNKNOWN = -1,		/* ... not selected yet */
97 	QSEL_BYNAME = 0,		/* ... user specifed it by name */
98 	QSEL_ALL = 1			/* ... user wants "all" printers */
99 					/*     (with more to come)    */
100 };
101 
102 static enum qsel_val generic_qselect;	/* indicates how ptr was selected */
103 static int generic_initerr;		/* result of initrtn processing */
104 static char *generic_cmdname;
105 static char *generic_msg;		/* if a -msg was specified */
106 static char *generic_nullarg;
107 static void (*generic_wrapup)(int _last_status);   /* perform rtn wrap-up */
108 
109 void
110 generic(void (*specificrtn)(struct printer *_pp), int cmdopts,
111     void (*initrtn)(int _argc, char *_argv[]), int argc, char *argv[])
112 {
113 	int cmdstatus, more, targc;
114 	struct printer myprinter, *pp;
115 	char **margv, **targv;
116 
117 	if (argc == 1) {
118 		/*
119 		 * Usage needs a special case for 'down': The user must
120 		 * either include `-msg', or only the first parameter
121 		 * that they give will be processed as a printer name.
122 		 */
123 		printf("usage: %s  {all | printer ...}", argv[0]);
124 		if (strcmp(argv[0], "down") == 0) {
125 			printf(" -msg [<text> ...]\n");
126 			printf("   or: down  {all | printer} [<text> ...]");
127 		} else if (cmdopts & LPC_MSGOPT)
128 			printf(" [-msg <text> ...]");
129 		printf("\n");
130 		return;
131 	}
132 
133 	/* The first argument is the command name. */
134 	generic_cmdname = *argv++;
135 	argc--;
136 
137 	/*
138 	 * The initialization routine for a command might set a generic
139 	 * "wrapup" routine, which should be called after processing all
140 	 * the printers in the command.  This might print summary info.
141 	 *
142 	 * Note that the initialization routine may also parse (and
143 	 * nullify) some of the parameters given on the command, leaving
144 	 * only the parameters which have to do with printer names.
145 	 */
146 	pp = &myprinter;
147 	generic_wrapup = NULL;
148 	generic_qselect = QSEL_UNKNOWN;
149 	cmdstatus = 0;
150 	/* this just needs to be a distinct value of type 'char *' */
151 	if (generic_nullarg == NULL)
152 		generic_nullarg = strdup("");
153 
154 	/*
155 	 * Some commands accept a -msg argument, which indicates that
156 	 * all remaining arguments should be combined into a string.
157 	 */
158 	generic_msg = NULL;
159 	if (cmdopts & LPC_MSGOPT) {
160 		targc = argc;
161 		targv = argv;
162 		for (; targc > 0; targc--, targv++) {
163 			if (strcmp(*targv, "-msg") == 0) {
164 				argc -= targc;
165 				generic_msg = args2line(targc - 1, targv + 1);
166 				break;
167 			}
168 		}
169 	}
170 
171 	/* call initialization routine, if there is one for this cmd */
172 	if (initrtn != NULL) {
173 		generic_initerr = 0;
174 		(*initrtn)(argc, argv);
175 		if (generic_initerr)
176 			return;
177 		/*
178 		 * The initrtn may have null'ed out some of the parameters.
179 		 * Compact the parameter list to remove those nulls, and
180 		 * correct the arg-count.
181 		 */
182 		targc = argc;
183 		targv = argv;
184 		margv = argv;
185 		argc = 0;
186 		for (; targc > 0; targc--, targv++) {
187 			if (*targv != generic_nullarg) {
188 				if (targv != margv)
189 					*margv = *targv;
190 				margv++;
191 				argc++;
192 			}
193 		}
194 	}
195 
196 	if (argc == 1 && strcmp(*argv, "all") == 0) {
197 		generic_qselect = QSEL_ALL;
198 		more = firstprinter(pp, &cmdstatus);
199 		if (cmdstatus)
200 			goto looperr;
201 		while (more) {
202 			(*specificrtn)(pp);
203 			do {
204 				more = nextprinter(pp, &cmdstatus);
205 looperr:
206 				switch (cmdstatus) {
207 				case PCAPERR_TCOPEN:
208 					printf("warning: %s: unresolved "
209 					       "tc= reference(s) ",
210 					       pp->printer);
211 				case PCAPERR_SUCCESS:
212 					break;
213 				default:
214 					fatal(pp, "%s", pcaperr(cmdstatus));
215 				}
216 			} while (more && cmdstatus);
217 		}
218 		goto wrapup;
219 	}
220 
221 	generic_qselect = QSEL_BYNAME;		/* specifically-named ptrs */
222 	for (; argc > 0; argc--, argv++) {
223 		init_printer(pp);
224 		cmdstatus = getprintcap(*argv, pp);
225 		switch (cmdstatus) {
226 		default:
227 			fatal(pp, "%s", pcaperr(cmdstatus));
228 		case PCAPERR_NOTFOUND:
229 			printf("unknown printer %s\n", *argv);
230 			continue;
231 		case PCAPERR_TCOPEN:
232 			printf("warning: %s: unresolved tc= reference(s)\n",
233 			       *argv);
234 			break;
235 		case PCAPERR_SUCCESS:
236 			break;
237 		}
238 		(*specificrtn)(pp);
239 	}
240 
241 wrapup:
242 	if (generic_wrapup) {
243 		(*generic_wrapup)(cmdstatus);
244 	}
245 	if (generic_msg)
246 		free(generic_msg);
247 }
248 
249 /*
250  * Convert an argv-array of character strings into a single string.
251  */
252 static char *
253 args2line(int argc, char **argv)
254 {
255 	char *cp1, *cend;
256 	const char *cp2;
257 	char buf[1024];
258 
259 	if (argc <= 0)
260 		return strdup("\n");
261 
262 	cp1 = buf;
263 	cend = buf + sizeof(buf) - 1;		/* save room for '\0' */
264 	while (--argc >= 0) {
265 		cp2 = *argv++;
266 		while ((cp1 < cend) && (*cp1++ = *cp2++))
267 			;
268 		cp1[-1] = ' ';
269 	}
270 	cp1[-1] = '\n';
271 	*cp1 = '\0';
272 	return strdup(buf);
273 }
274 
275 /*
276  * Kill the current daemon, to stop printing of the active job.
277  */
278 static int
279 kill_qtask(const char *lf)
280 {
281 	FILE *fp;
282 	pid_t pid;
283 	int errsav, killres, lockres, res;
284 
285 	seteuid(euid);
286 	fp = fopen(lf, "r");
287 	errsav = errno;
288 	seteuid(uid);
289 	res = KQT_NODAEMON;
290 	if (fp == NULL) {
291 		/*
292 		 * If there is no lock file, then there is no daemon to
293 		 * kill.  Any other error return means there is some
294 		 * kind of problem with the lock file.
295 		 */
296 		if (errsav != ENOENT)
297 			res = KQT_LFERROR;
298 		goto killdone;
299 	}
300 
301 	/* If the lock file is empty, then there is no daemon to kill */
302 	if (getline(fp) == 0)
303 		goto killdone;
304 
305 	/*
306 	 * If the file can be locked without blocking, then there
307 	 * no daemon to kill, or we should not try to kill it.
308 	 *
309 	 * XXX - not sure I understand the reasoning behind this...
310 	 */
311 	lockres = flock(fileno(fp), LOCK_SH|LOCK_NB);
312 	(void) fclose(fp);
313 	if (lockres == 0)
314 		goto killdone;
315 
316 	pid = atoi(line);
317 	if (pid < 0) {
318 		/*
319 		 * If we got a negative pid, then the contents of the
320 		 * lock file is not valid.
321 		 */
322 		res = KQT_LFERROR;
323 		goto killdone;
324 	}
325 
326 	seteuid(uid);
327 	killres = kill(pid, SIGTERM);
328 	errsav = errno;
329 	seteuid(uid);
330 	if (killres == 0) {
331 		res = KQT_KILLOK;
332 		printf("\tdaemon (pid %d) killed\n", pid);
333 	} else if (errno == ESRCH) {
334 		res = KQT_NODAEMON;
335 	} else {
336 		res = KQT_KILLFAIL;
337 		printf("\tWarning: daemon (pid %d) not killed:\n", pid);
338 		printf("\t    %s\n", strerror(errsav));
339 	}
340 
341 killdone:
342 	switch (res) {
343 	case KQT_LFERROR:
344 		printf("\tcannot open lock file: %s\n",
345 		    strerror(errsav));
346 		break;
347 	case KQT_NODAEMON:
348 		printf("\tno daemon to abort\n");
349 		break;
350 	case KQT_KILLFAIL:
351 	case KQT_KILLOK:
352 		/* These two already printed messages to the user. */
353 		break;
354 	default:
355 		printf("\t<internal error in kill_qtask>\n");
356 		break;
357 	}
358 
359 	return (res);
360 }
361 
362 /*
363  * Write a message into the status file.
364  */
365 static void
366 upstat(struct printer *pp, const char *msg, int notifyuser)
367 {
368 	int fd;
369 	char statfile[MAXPATHLEN];
370 
371 	status_file_name(pp, statfile, sizeof statfile);
372 	umask(0);
373 	seteuid(euid);
374 	fd = open(statfile, O_WRONLY|O_CREAT|O_EXLOCK, STAT_FILE_MODE);
375 	seteuid(uid);
376 	if (fd < 0) {
377 		printf("\tcannot create status file: %s\n", strerror(errno));
378 		return;
379 	}
380 	(void) ftruncate(fd, 0);
381 	if (msg == (char *)NULL)
382 		(void) write(fd, "\n", 1);
383 	else
384 		(void) write(fd, msg, strlen(msg));
385 	(void) close(fd);
386 	if (notifyuser) {
387 		if ((msg == (char *)NULL) || (strcmp(msg, "\n") == 0))
388 			printf("\tstatus message is now set to nothing.\n");
389 		else
390 			printf("\tstatus message is now: %s", msg);
391 	}
392 }
393 
394 /*
395  * kill an existing daemon and disable printing.
396  */
397 void
398 abort_q(struct printer *pp)
399 {
400 	int killres, setres;
401 	char lf[MAXPATHLEN];
402 
403 	lock_file_name(pp, lf, sizeof lf);
404 	printf("%s:\n", pp->printer);
405 
406 	/*
407 	 * Turn on the owner execute bit of the lock file to disable printing.
408 	 */
409 	setres = set_qstate(SQS_STOPP, lf);
410 
411 	/*
412 	 * If set_qstate found that there already was a lock file, then
413 	 * call a routine which will read that lock file and kill the
414 	 * lpd-process which is listed in that lock file.  If the lock
415 	 * file did not exist, then either there is no daemon running
416 	 * for this queue, or there is one running but *it* could not
417 	 * write a lock file (which means we can not determine the
418 	 * process id of that lpd-process).
419 	 */
420 	switch (setres) {
421 	case SQS_CHGOK:
422 	case SQS_CHGFAIL:
423 		/* Kill the process */
424 		killres = kill_qtask(lf);
425 		break;
426 	case SQS_CREOK:
427 	case SQS_CREFAIL:
428 		printf("\tno daemon to abort\n");
429 		break;
430 	case SQS_STATFAIL:
431 		printf("\tassuming no daemon to abort\n");
432 		break;
433 	default:
434 		printf("\t<unexpected result (%d) from set_qstate>\n",
435 		    setres);
436 		break;
437 	}
438 
439 	if (setres >= 0)
440 		upstat(pp, "printing disabled\n", 0);
441 }
442 
443 /*
444  * "global" variables for all the routines related to 'clean' and 'tclean'
445  */
446 static time_t	 cln_now;		/* current time */
447 static double	 cln_minage;		/* minimum age before file is removed */
448 static long	 cln_sizecnt;		/* amount of space freed up */
449 static int 	 cln_debug;		/* print extra debugging msgs */
450 static int	 cln_filecnt;		/* number of files destroyed */
451 static int	 cln_foundcore;		/* found a core file! */
452 static int	 cln_queuecnt;		/* number of queues checked */
453 static int 	 cln_testonly;		/* remove-files vs just-print-info */
454 
455 static int
456 doselect(struct dirent *d)
457 {
458 	int c = d->d_name[0];
459 
460 	if ((c == 'c' || c == 'd' || c == 'r' || c == 't') &&
461 	    d->d_name[1] == 'f')
462 		return 1;
463 	if (c == 'c') {
464 		if (!strcmp(d->d_name, "core"))
465 			cln_foundcore = 1;
466 	}
467 	if (c == 'e') {
468 		if (!strncmp(d->d_name, "errs.", 5))
469 			return 1;
470 	}
471 	return 0;
472 }
473 
474 /*
475  * Comparison routine that clean_q() uses for scandir.
476  *
477  * The purpose of this sort is to have all `df' files end up immediately
478  * after the matching `cf' file.  For files matching `cf', `df', `rf', or
479  * `tf', it sorts by job number and machine, then by `cf', `df', `rf', or
480  * `tf', and then by the sequence letter (which is A-Z, or a-z).    This
481  * routine may also see filenames which do not start with `cf', `df', `rf',
482  * or `tf' (such as `errs.*'), and those are simply sorted by the full
483  * filename.
484  *
485  * XXX
486  *   This assumes that all control files start with `cfA*', and it turns
487  *   out there are a few implementations of lpr which will create `cfB*'
488  *   filenames (they will have datafile names which start with `dfB*').
489  */
490 static int
491 sortq(const void *a, const void *b)
492 {
493 	const int a_lt_b = -1, a_gt_b = 1, cat_other = 10;
494 	const char *fname_a, *fname_b, *jnum_a, *jnum_b;
495 	int cat_a, cat_b, ch, res, seq_a, seq_b;
496 
497 	fname_a = (*(const struct dirent * const *)a)->d_name;
498 	fname_b = (*(const struct dirent * const *)b)->d_name;
499 
500 	/*
501 	 * First separate filenames into cagatories.  Catagories are
502 	 * legitimate `cf', `df', `rf' & `tf' filenames, and "other" - in
503 	 * that order.  It is critical that the mapping be exactly the
504 	 * same for 'a' vs 'b', so define a macro for the job.
505 	 *
506 	 * [aside: the standard `cf' file has the jobnumber start in
507 	 * position 4, but some implementations have that as an extra
508 	 * file-sequence letter, and start the job number in position 5.]
509 	 */
510 #define MAP_TO_CAT(fname_X,cat_X,jnum_X,seq_X) do { \
511 	cat_X = cat_other;    \
512 	ch = *(fname_X + 2);  \
513 	jnum_X = fname_X + 3; \
514 	seq_X = 0;            \
515 	if ((*(fname_X + 1) == 'f') && (isalpha(ch))) { \
516 		seq_X = ch; \
517 		if (*fname_X == 'c') \
518 			cat_X = 1; \
519 		else if (*fname_X == 'd') \
520 			cat_X = 2; \
521 		else if (*fname_X == 'r') \
522 			cat_X = 3; \
523 		else if (*fname_X == 't') \
524 			cat_X = 4; \
525 		if (cat_X != cat_other) { \
526 			ch = *jnum_X; \
527 			if (!isdigit(ch)) { \
528 				if (isalpha(ch)) { \
529 					jnum_X++; \
530 					ch = *jnum_X; \
531 					seq_X = (seq_X << 8) + ch; \
532 				} \
533 				if (!isdigit(ch)) \
534 					cat_X = cat_other; \
535 			} \
536 		} \
537 	} \
538 } while (0)
539 
540 	MAP_TO_CAT(fname_a, cat_a, jnum_a, seq_a);
541 	MAP_TO_CAT(fname_b, cat_b, jnum_b, seq_b);
542 
543 #undef MAP_TO_CAT
544 
545 	/* First handle all cases which have "other" files */
546 	if ((cat_a >= cat_other) || (cat_b >= cat_other)) {
547 		/* for two "other" files, just compare the full name */
548 		if (cat_a == cat_b)
549 			res = strcmp(fname_a, fname_b);
550 		else if (cat_a < cat_b)
551 			res = a_lt_b;
552 		else
553 			res = a_gt_b;
554 		goto have_res;
555 	}
556 
557 	/*
558 	 * At this point, we know both files are legitimate `cf', `df', `rf',
559 	 * or `tf' files.  Compare them by job-number and machine name.
560 	 */
561 	res = strcmp(jnum_a, jnum_b);
562 	if (res != 0)
563 		goto have_res;
564 
565 	/*
566 	 * We have two files which belong to the same job.  Sort based
567 	 * on the catagory of file (`c' before `d', etc).
568 	 */
569 	if (cat_a < cat_b) {
570 		res = a_lt_b;
571 		goto have_res;
572 	} else if (cat_a > cat_b) {
573 		res = a_gt_b;
574 		goto have_res;
575 	}
576 
577 	/*
578 	 * Two files in the same catagory for a single job.  Sort based
579 	 * on the sequence letter(s).  (usually `A' thru `Z', etc).
580 	 */
581 	if (seq_a < seq_b) {
582 		res = a_lt_b;
583 		goto have_res;
584 	} else if (seq_a > seq_b) {
585 		res = a_gt_b;
586 		goto have_res;
587 	}
588 
589 	/*
590 	 * Given that the filenames in a directory are unique, this SHOULD
591 	 * never happen (unless there are logic errors in this routine).
592 	 * But if it does happen, we must return "is equal" or the caller
593 	 * might see inconsistent results in the sorting order, and that
594 	 * can trigger other problems.
595 	 */
596 	printf("\t*** Error in sortq: %s == %s !\n", fname_a, fname_b);
597 	printf("\t***       cat %d == %d ; seq = %d %d\n", cat_a, cat_b,
598 	    seq_a, seq_b);
599 	res = 0;
600 
601 have_res:
602 	return res;
603 }
604 
605 /*
606  * Remove all spool files and temporaries from the spooling area.
607  * Or, perhaps:
608  * Remove incomplete jobs from spooling area.
609  */
610 
611 void
612 clean_gi(int argc, char *argv[])
613 {
614 
615 	/* init some fields before 'clean' is called for each queue */
616 	cln_queuecnt = 0;
617 	cln_now = time(NULL);
618 	cln_minage = 3600.0;		/* only delete files >1h old */
619 	cln_filecnt = 0;
620 	cln_sizecnt = 0;
621 	cln_debug = 0;
622 	cln_testonly = 0;
623 	generic_wrapup = &wrapup_clean;
624 
625 	/* see if there are any options specified before the ptr list */
626 	for (; argc > 0; argc--, argv++) {
627 		if (**argv != '-')
628 			break;
629 		if (strcmp(*argv, "-d") == 0) {
630 			/* just an example of an option... */
631 			cln_debug++;
632 			*argv = generic_nullarg;	/* "erase" it */
633 		} else {
634 			printf("Invalid option '%s'\n", *argv);
635 			generic_initerr = 1;
636 		}
637 	}
638 
639 	return;
640 }
641 
642 void
643 tclean_gi(int argc, char *argv[])
644 {
645 
646 	/* only difference between 'clean' and 'tclean' is one value */
647 	/* (...and the fact that 'clean' is priv and 'tclean' is not) */
648 	clean_gi(argc, argv);
649 	cln_testonly = 1;
650 
651 	return;
652 }
653 
654 void
655 clean_q(struct printer *pp)
656 {
657 	char *cp, *cp1, *lp;
658 	struct dirent **queue;
659 	size_t linerem;
660 	int didhead, i, n, nitems, rmcp;
661 
662 	cln_queuecnt++;
663 
664 	didhead = 0;
665 	if (generic_qselect == QSEL_BYNAME) {
666 		printf("%s:\n", pp->printer);
667 		didhead = 1;
668 	}
669 
670 	lp = line;
671 	cp = pp->spool_dir;
672 	while (lp < &line[sizeof(line) - 1]) {
673 		if ((*lp++ = *cp++) == 0)
674 			break;
675 	}
676 	lp[-1] = '/';
677 	linerem = sizeof(line) - (lp - line);
678 
679 	cln_foundcore = 0;
680 	seteuid(euid);
681 	nitems = scandir(pp->spool_dir, &queue, doselect, sortq);
682 	seteuid(uid);
683 	if (nitems < 0) {
684 		if (!didhead) {
685 			printf("%s:\n", pp->printer);
686 			didhead = 1;
687 		}
688 		printf("\tcannot examine spool directory\n");
689 		return;
690 	}
691 	if (cln_foundcore) {
692 		if (!didhead) {
693 			printf("%s:\n", pp->printer);
694 			didhead = 1;
695 		}
696 		printf("\t** found a core file in %s !\n", pp->spool_dir);
697 	}
698 	if (nitems == 0)
699 		return;
700 	if (!didhead)
701 		printf("%s:\n", pp->printer);
702 	if (cln_debug) {
703 		printf("\t** ----- Sorted list of files being checked:\n");
704 		i = 0;
705 		do {
706 			cp = queue[i]->d_name;
707 			printf("\t** [%3d] = %s\n", i, cp);
708 		} while (++i < nitems);
709 		printf("\t** ----- end of sorted list\n");
710 	}
711 	i = 0;
712 	do {
713 		cp = queue[i]->d_name;
714 		rmcp = 0;
715 		if (*cp == 'c') {
716 			/*
717 			 * A control file.  Look for matching data-files.
718 			 */
719 			/* XXX
720 			 *  Note the logic here assumes that the hostname
721 			 *  part of cf-filenames match the hostname part
722 			 *  in df-filenames, and that is not necessarily
723 			 *  true (eg: for multi-homed hosts).  This needs
724 			 *  some further thought...
725 			 */
726 			n = 0;
727 			while (i + 1 < nitems) {
728 				cp1 = queue[i + 1]->d_name;
729 				if (*cp1 != 'd' || strcmp(cp + 3, cp1 + 3))
730 					break;
731 				i++;
732 				n++;
733 			}
734 			if (n == 0) {
735 				rmcp = 1;
736 			}
737 		} else if (*cp == 'e') {
738 			/*
739 			 * Must be an errrs or email temp file.
740 			 */
741 			rmcp = 1;
742 		} else {
743 			/*
744 			 * Must be a df with no cf (otherwise, it would have
745 			 * been skipped above) or an rf or tf file (which can
746 			 * always be removed if it is old enough).
747 			 */
748 			rmcp = 1;
749 		}
750 		if (rmcp) {
751 			if (strlen(cp) >= linerem) {
752 				printf("\t** internal error: 'line' overflow!\n");
753 				printf("\t**   spooldir = %s\n", pp->spool_dir);
754 				printf("\t**   cp = %s\n", cp);
755 				return;
756 			}
757 			strlcpy(lp, cp, linerem);
758 			unlinkf(line);
759 		}
760      	} while (++i < nitems);
761 }
762 
763 static void
764 wrapup_clean(int laststatus __unused)
765 {
766 
767 	printf("Checked %d queues, and ", cln_queuecnt);
768 	if (cln_filecnt < 1) {
769 		printf("no cruft was found\n");
770 		return;
771 	}
772 	if (cln_testonly) {
773 		printf("would have ");
774 	}
775 	printf("removed %d files (%ld bytes).\n", cln_filecnt, cln_sizecnt);
776 }
777 
778 static void
779 unlinkf(char *name)
780 {
781 	struct stat stbuf;
782 	double agemod, agestat;
783 	int res;
784 	char linkbuf[BUFSIZ];
785 
786 	/*
787 	 * We have to use lstat() instead of stat(), in case this is a df*
788 	 * "file" which is really a symlink due to 'lpr -s' processing.  In
789 	 * that case, we need to check the last-mod time of the symlink, and
790 	 * not the file that the symlink is pointed at.
791 	 */
792 	seteuid(euid);
793 	res = lstat(name, &stbuf);
794 	seteuid(uid);
795 	if (res < 0) {
796 		printf("\terror return from stat(%s):\n", name);
797 		printf("\t      %s\n", strerror(errno));
798 		return;
799 	}
800 
801 	agemod = difftime(cln_now, stbuf.st_mtime);
802 	agestat = difftime(cln_now,  stbuf.st_ctime);
803 	if (cln_debug > 1) {
804 		/* this debugging-aid probably is not needed any more... */
805 		printf("\t\t  modify age=%g secs, stat age=%g secs\n",
806 		    agemod, agestat);
807 	}
808 	if ((agemod <= cln_minage) && (agestat <= cln_minage))
809 		return;
810 
811 	/*
812 	 * if this file is a symlink, then find out the target of the
813 	 * symlink before unlink-ing the file itself
814 	 */
815 	if (S_ISLNK(stbuf.st_mode)) {
816 		seteuid(euid);
817 		res = readlink(name, linkbuf, sizeof(linkbuf));
818 		seteuid(uid);
819 		if (res < 0) {
820 			printf("\terror return from readlink(%s):\n", name);
821 			printf("\t      %s\n", strerror(errno));
822 			return;
823 		}
824 		if (res == sizeof(linkbuf))
825 			res--;
826 		linkbuf[res] = '\0';
827 	}
828 
829 	cln_filecnt++;
830 	cln_sizecnt += stbuf.st_size;
831 
832 	if (cln_testonly) {
833 		printf("\twould remove %s\n", name);
834 		if (S_ISLNK(stbuf.st_mode)) {
835 			printf("\t    (which is a symlink to %s)\n", linkbuf);
836 		}
837 	} else {
838 		seteuid(euid);
839 		res = unlink(name);
840 		seteuid(uid);
841 		if (res < 0)
842 			printf("\tcannot remove %s (!)\n", name);
843 		else
844 			printf("\tremoved %s\n", name);
845 		/* XXX
846 		 *  Note that for a df* file, this code should also check to see
847 		 *  if it is a symlink to some other file, and if the original
848 		 *  lpr command included '-r' ("remove file").  Of course, this
849 		 *  code would not be removing the df* file unless there was no
850 		 *  matching cf* file, and without the cf* file it is currently
851 		 *  impossible to determine if '-r' had been specified...
852 		 *
853 		 *  As a result of this quandry, we may be leaving behind a
854 		 *  user's file that was supposed to have been removed after
855 		 *  being printed.  This may effect services such as CAP or
856 		 *  samba, if they were configured to use 'lpr -r', and if
857 		 *  datafiles are not being properly removed.
858 		*/
859 		if (S_ISLNK(stbuf.st_mode)) {
860 			printf("\t    (which was a symlink to %s)\n", linkbuf);
861 		}
862 	}
863 }
864 
865 /*
866  * Enable queuing to the printer (allow lpr to add new jobs to the queue).
867  */
868 void
869 enable_q(struct printer *pp)
870 {
871 	int setres;
872 	char lf[MAXPATHLEN];
873 
874 	lock_file_name(pp, lf, sizeof lf);
875 	printf("%s:\n", pp->printer);
876 
877 	setres = set_qstate(SQS_ENABLEQ, lf);
878 }
879 
880 /*
881  * Disable queuing.
882  */
883 void
884 disable_q(struct printer *pp)
885 {
886 	int setres;
887 	char lf[MAXPATHLEN];
888 
889 	lock_file_name(pp, lf, sizeof lf);
890 	printf("%s:\n", pp->printer);
891 
892 	setres = set_qstate(SQS_DISABLEQ, lf);
893 }
894 
895 /*
896  * Disable queuing and printing and put a message into the status file
897  * (reason for being down).  If the user specified `-msg', then use
898  * everything after that as the message for the status file.  If the
899  * user did NOT specify `-msg', then the command should take the first
900  * parameter as the printer name, and all remaining parameters as the
901  * message for the status file.  (This is to be compatible with the
902  * original definition of 'down', which was implemented long before
903  * `-msg' was around).
904  */
905 void
906 down_gi(int argc, char *argv[])
907 {
908 
909 	/* If `-msg' was specified, then this routine has nothing to do. */
910 	if (generic_msg != NULL)
911 		return;
912 
913 	/*
914 	 * If the user only gave one parameter, then use a default msg.
915 	 * (if argc == 1 at this point, then *argv == name of printer).
916 	 */
917 	if (argc == 1) {
918 		generic_msg = strdup("printing disabled\n");
919 		return;
920 	}
921 
922 	/*
923 	 * The user specified multiple parameters, and did not specify
924 	 * `-msg'.  Build a message from all the parameters after the
925 	 * first one (and nullify those parameters so generic-processing
926 	 * will not process them as printer-queue names).
927 	 */
928 	argc--;
929 	argv++;
930 	generic_msg = args2line(argc, argv);
931 	for (; argc > 0; argc--, argv++)
932 		*argv = generic_nullarg;	/* "erase" it */
933 }
934 
935 void
936 down_q(struct printer *pp)
937 {
938 	int setres;
939 	char lf[MAXPATHLEN];
940 
941 	lock_file_name(pp, lf, sizeof lf);
942 	printf("%s:\n", pp->printer);
943 
944 	setres = set_qstate(SQS_DISABLEQ+SQS_STOPP, lf);
945 	if (setres >= 0)
946 		upstat(pp, generic_msg, 1);
947 }
948 
949 /*
950  * Exit lpc
951  */
952 void
953 quit(int argc __unused, char *argv[] __unused)
954 {
955 	exit(0);
956 }
957 
958 /*
959  * Kill and restart the daemon.
960  */
961 void
962 restart_q(struct printer *pp)
963 {
964 	int killres, setres, startok;
965 	char lf[MAXPATHLEN];
966 
967 	lock_file_name(pp, lf, sizeof lf);
968 	printf("%s:\n", pp->printer);
969 
970 	killres = kill_qtask(lf);
971 
972 	/*
973 	 * XXX - if the kill worked, we should probably sleep for
974 	 *      a second or so before trying to restart the queue.
975 	 */
976 
977 	/* make sure the queue is set to print jobs */
978 	setres = set_qstate(SQS_STARTP, lf);
979 
980 	seteuid(euid);
981 	startok = startdaemon(pp);
982 	seteuid(uid);
983 	if (!startok)
984 		printf("\tcouldn't restart daemon\n");
985 	else
986 		printf("\tdaemon restarted\n");
987 }
988 
989 /*
990  * Set the status message of each queue listed.  Requires a "-msg"
991  * parameter to indicate the end of the queue list and start of msg text.
992  */
993 void
994 setstatus_gi(int argc __unused, char *argv[] __unused)
995 {
996 
997 	if (generic_msg == NULL) {
998 		printf("You must specify '-msg' before the text of the new status message.\n");
999 		generic_initerr = 1;
1000 	}
1001 }
1002 
1003 void
1004 setstatus_q(struct printer *pp)
1005 {
1006 	char lf[MAXPATHLEN];
1007 
1008 	lock_file_name(pp, lf, sizeof lf);
1009 	printf("%s:\n", pp->printer);
1010 
1011 	upstat(pp, generic_msg, 1);
1012 }
1013 
1014 /*
1015  * Enable printing on the specified printer and startup the daemon.
1016  */
1017 void
1018 start_q(struct printer *pp)
1019 {
1020 	int setres, startok;
1021 	char lf[MAXPATHLEN];
1022 
1023 	lock_file_name(pp, lf, sizeof lf);
1024 	printf("%s:\n", pp->printer);
1025 
1026 	setres = set_qstate(SQS_STARTP, lf);
1027 
1028 	seteuid(euid);
1029 	startok = startdaemon(pp);
1030 	seteuid(uid);
1031 	if (!startok)
1032 		printf("\tcouldn't start daemon\n");
1033 	else
1034 		printf("\tdaemon started\n");
1035 	seteuid(uid);
1036 }
1037 
1038 /*
1039  * Print the status of the printer queue.
1040  */
1041 void
1042 status(struct printer *pp)
1043 {
1044 	struct stat stbuf;
1045 	register int fd, i;
1046 	register struct dirent *dp;
1047 	DIR *dirp;
1048 	char file[MAXPATHLEN];
1049 
1050 	printf("%s:\n", pp->printer);
1051 	lock_file_name(pp, file, sizeof file);
1052 	if (stat(file, &stbuf) >= 0) {
1053 		printf("\tqueuing is %s\n",
1054 		       ((stbuf.st_mode & LFM_QUEUE_DIS) ? "disabled"
1055 			: "enabled"));
1056 		printf("\tprinting is %s\n",
1057 		       ((stbuf.st_mode & LFM_PRINT_DIS) ? "disabled"
1058 			: "enabled"));
1059 	} else {
1060 		printf("\tqueuing is enabled\n");
1061 		printf("\tprinting is enabled\n");
1062 	}
1063 	if ((dirp = opendir(pp->spool_dir)) == NULL) {
1064 		printf("\tcannot examine spool directory\n");
1065 		return;
1066 	}
1067 	i = 0;
1068 	while ((dp = readdir(dirp)) != NULL) {
1069 		if (*dp->d_name == 'c' && dp->d_name[1] == 'f')
1070 			i++;
1071 	}
1072 	closedir(dirp);
1073 	if (i == 0)
1074 		printf("\tno entries in spool area\n");
1075 	else if (i == 1)
1076 		printf("\t1 entry in spool area\n");
1077 	else
1078 		printf("\t%d entries in spool area\n", i);
1079 	fd = open(file, O_RDONLY);
1080 	if (fd < 0 || flock(fd, LOCK_SH|LOCK_NB) == 0) {
1081 		(void) close(fd);	/* unlocks as well */
1082 		printf("\tprinter idle\n");
1083 		return;
1084 	}
1085 	(void) close(fd);
1086 	/* print out the contents of the status file, if it exists */
1087 	status_file_name(pp, file, sizeof file);
1088 	fd = open(file, O_RDONLY|O_SHLOCK);
1089 	if (fd >= 0) {
1090 		(void) fstat(fd, &stbuf);
1091 		if (stbuf.st_size > 0) {
1092 			putchar('\t');
1093 			while ((i = read(fd, line, sizeof(line))) > 0)
1094 				(void) fwrite(line, 1, i, stdout);
1095 		}
1096 		(void) close(fd);	/* unlocks as well */
1097 	}
1098 }
1099 
1100 /*
1101  * Stop the specified daemon after completing the current job and disable
1102  * printing.
1103  */
1104 void
1105 stop_q(struct printer *pp)
1106 {
1107 	int setres;
1108 	char lf[MAXPATHLEN];
1109 
1110 	lock_file_name(pp, lf, sizeof lf);
1111 	printf("%s:\n", pp->printer);
1112 
1113 	setres = set_qstate(SQS_STOPP, lf);
1114 
1115 	if (setres >= 0)
1116 		upstat(pp, "printing disabled\n", 0);
1117 }
1118 
1119 struct	jobqueue **queue;
1120 int	nitems;
1121 time_t	mtime;
1122 
1123 /*
1124  * Put the specified jobs at the top of printer queue.
1125  */
1126 void
1127 topq(int argc, char *argv[])
1128 {
1129 	register int i;
1130 	struct stat stbuf;
1131 	int cmdstatus, changed;
1132 	struct printer myprinter, *pp = &myprinter;
1133 
1134 	if (argc < 3) {
1135 		printf("usage: topq printer [jobnum ...] [user ...]\n");
1136 		return;
1137 	}
1138 
1139 	--argc;
1140 	++argv;
1141 	init_printer(pp);
1142 	cmdstatus = getprintcap(*argv, pp);
1143 	switch(cmdstatus) {
1144 	default:
1145 		fatal(pp, "%s", pcaperr(cmdstatus));
1146 	case PCAPERR_NOTFOUND:
1147 		printf("unknown printer %s\n", *argv);
1148 		return;
1149 	case PCAPERR_TCOPEN:
1150 		printf("warning: %s: unresolved tc= reference(s)", *argv);
1151 		break;
1152 	case PCAPERR_SUCCESS:
1153 		break;
1154 	}
1155 	printf("%s:\n", pp->printer);
1156 
1157 	seteuid(euid);
1158 	if (chdir(pp->spool_dir) < 0) {
1159 		printf("\tcannot chdir to %s\n", pp->spool_dir);
1160 		goto out;
1161 	}
1162 	seteuid(uid);
1163 	nitems = getq(pp, &queue);
1164 	if (nitems == 0)
1165 		return;
1166 	changed = 0;
1167 	mtime = queue[0]->job_time;
1168 	for (i = argc; --i; ) {
1169 		if (doarg(argv[i]) == 0) {
1170 			printf("\tjob %s is not in the queue\n", argv[i]);
1171 			continue;
1172 		} else
1173 			changed++;
1174 	}
1175 	for (i = 0; i < nitems; i++)
1176 		free(queue[i]);
1177 	free(queue);
1178 	if (!changed) {
1179 		printf("\tqueue order unchanged\n");
1180 		return;
1181 	}
1182 	/*
1183 	 * Turn on the public execute bit of the lock file to
1184 	 * get lpd to rebuild the queue after the current job.
1185 	 */
1186 	seteuid(euid);
1187 	if (changed && stat(pp->lock_file, &stbuf) >= 0)
1188 		(void) chmod(pp->lock_file, stbuf.st_mode | LFM_RESET_QUE);
1189 
1190 out:
1191 	seteuid(uid);
1192 }
1193 
1194 /*
1195  * Reposition the job by changing the modification time of
1196  * the control file.
1197  */
1198 static int
1199 touch(struct jobqueue *jq)
1200 {
1201 	struct timeval tvp[2];
1202 	int ret;
1203 
1204 	tvp[0].tv_sec = tvp[1].tv_sec = --mtime;
1205 	tvp[0].tv_usec = tvp[1].tv_usec = 0;
1206 	seteuid(euid);
1207 	ret = utimes(jq->job_cfname, tvp);
1208 	seteuid(uid);
1209 	return (ret);
1210 }
1211 
1212 /*
1213  * Checks if specified job name is in the printer's queue.
1214  * Returns:  negative (-1) if argument name is not in the queue.
1215  */
1216 static int
1217 doarg(char *job)
1218 {
1219 	register struct jobqueue **qq;
1220 	register int jobnum, n;
1221 	register char *cp, *machine;
1222 	int cnt = 0;
1223 	FILE *fp;
1224 
1225 	/*
1226 	 * Look for a job item consisting of system name, colon, number
1227 	 * (example: ucbarpa:114)
1228 	 */
1229 	if ((cp = strchr(job, ':')) != NULL) {
1230 		machine = job;
1231 		*cp++ = '\0';
1232 		job = cp;
1233 	} else
1234 		machine = NULL;
1235 
1236 	/*
1237 	 * Check for job specified by number (example: 112 or 235ucbarpa).
1238 	 */
1239 	if (isdigit(*job)) {
1240 		jobnum = 0;
1241 		do
1242 			jobnum = jobnum * 10 + (*job++ - '0');
1243 		while (isdigit(*job));
1244 		for (qq = queue + nitems; --qq >= queue; ) {
1245 			n = 0;
1246 			for (cp = (*qq)->job_cfname+3; isdigit(*cp); )
1247 				n = n * 10 + (*cp++ - '0');
1248 			if (jobnum != n)
1249 				continue;
1250 			if (*job && strcmp(job, cp) != 0)
1251 				continue;
1252 			if (machine != NULL && strcmp(machine, cp) != 0)
1253 				continue;
1254 			if (touch(*qq) == 0) {
1255 				printf("\tmoved %s\n", (*qq)->job_cfname);
1256 				cnt++;
1257 			}
1258 		}
1259 		return(cnt);
1260 	}
1261 	/*
1262 	 * Process item consisting of owner's name (example: henry).
1263 	 */
1264 	for (qq = queue + nitems; --qq >= queue; ) {
1265 		seteuid(euid);
1266 		fp = fopen((*qq)->job_cfname, "r");
1267 		seteuid(uid);
1268 		if (fp == NULL)
1269 			continue;
1270 		while (getline(fp) > 0)
1271 			if (line[0] == 'P')
1272 				break;
1273 		(void) fclose(fp);
1274 		if (line[0] != 'P' || strcmp(job, line+1) != 0)
1275 			continue;
1276 		if (touch(*qq) == 0) {
1277 			printf("\tmoved %s\n", (*qq)->job_cfname);
1278 			cnt++;
1279 		}
1280 	}
1281 	return(cnt);
1282 }
1283 
1284 /*
1285  * Enable both queuing & printing, and start printer (undo `down').
1286  */
1287 void
1288 up_q(struct printer *pp)
1289 {
1290 	int setres, startok;
1291 	char lf[MAXPATHLEN];
1292 
1293 	lock_file_name(pp, lf, sizeof lf);
1294 	printf("%s:\n", pp->printer);
1295 
1296 	setres = set_qstate(SQS_ENABLEQ+SQS_STARTP, lf);
1297 
1298 	seteuid(euid);
1299 	startok = startdaemon(pp);
1300 	seteuid(uid);
1301 	if (!startok)
1302 		printf("\tcouldn't start daemon\n");
1303 	else
1304 		printf("\tdaemon started\n");
1305 }
1306