xref: /dragonfly/usr.sbin/lpr/lpc/cmds.c (revision 984263bc)
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: src/usr.sbin/lpr/lpc/cmds.c,v 1.14.2.16 2002/07/25 23:29:39 gad Exp $";
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 	free_printer(pp);
246 	if (generic_msg)
247 		free(generic_msg);
248 }
249 
250 /*
251  * Convert an argv-array of character strings into a single string.
252  */
253 static char *
254 args2line(int argc, char **argv)
255 {
256 	char *cp1, *cend;
257 	const char *cp2;
258 	char buf[1024];
259 
260 	if (argc <= 0)
261 		return strdup("\n");
262 
263 	cp1 = buf;
264 	cend = buf + sizeof(buf) - 1;		/* save room for '\0' */
265 	while (--argc >= 0) {
266 		cp2 = *argv++;
267 		while ((cp1 < cend) && (*cp1++ = *cp2++))
268 			;
269 		cp1[-1] = ' ';
270 	}
271 	cp1[-1] = '\n';
272 	*cp1 = '\0';
273 	return strdup(buf);
274 }
275 
276 /*
277  * Kill the current daemon, to stop printing of the active job.
278  */
279 static int
280 kill_qtask(const char *lf)
281 {
282 	FILE *fp;
283 	pid_t pid;
284 	int errsav, killres, lockres, res;
285 
286 	seteuid(euid);
287 	fp = fopen(lf, "r");
288 	errsav = errno;
289 	seteuid(uid);
290 	res = KQT_NODAEMON;
291 	if (fp == NULL) {
292 		/*
293 		 * If there is no lock file, then there is no daemon to
294 		 * kill.  Any other error return means there is some
295 		 * kind of problem with the lock file.
296 		 */
297 		if (errsav != ENOENT)
298 			res = KQT_LFERROR;
299 		goto killdone;
300 	}
301 
302 	/* If the lock file is empty, then there is no daemon to kill */
303 	if (getline(fp) == 0)
304 		goto killdone;
305 
306 	/*
307 	 * If the file can be locked without blocking, then there
308 	 * no daemon to kill, or we should not try to kill it.
309 	 *
310 	 * XXX - not sure I understand the reasoning behind this...
311 	 */
312 	lockres = flock(fileno(fp), LOCK_SH|LOCK_NB);
313 	(void) fclose(fp);
314 	if (lockres == 0)
315 		goto killdone;
316 
317 	pid = atoi(line);
318 	if (pid < 0) {
319 		/*
320 		 * If we got a negative pid, then the contents of the
321 		 * lock file is not valid.
322 		 */
323 		res = KQT_LFERROR;
324 		goto killdone;
325 	}
326 
327 	seteuid(uid);
328 	killres = kill(pid, SIGTERM);
329 	errsav = errno;
330 	seteuid(uid);
331 	if (killres == 0) {
332 		res = KQT_KILLOK;
333 		printf("\tdaemon (pid %d) killed\n", pid);
334 	} else if (errno == ESRCH) {
335 		res = KQT_NODAEMON;
336 	} else {
337 		res = KQT_KILLFAIL;
338 		printf("\tWarning: daemon (pid %d) not killed:\n", pid);
339 		printf("\t    %s\n", strerror(errsav));
340 	}
341 
342 killdone:
343 	switch (res) {
344 	case KQT_LFERROR:
345 		printf("\tcannot open lock file: %s\n",
346 		    strerror(errsav));
347 		break;
348 	case KQT_NODAEMON:
349 		printf("\tno daemon to abort\n");
350 		break;
351 	case KQT_KILLFAIL:
352 	case KQT_KILLOK:
353 		/* These two already printed messages to the user. */
354 		break;
355 	default:
356 		printf("\t<internal error in kill_qtask>\n");
357 		break;
358 	}
359 
360 	return (res);
361 }
362 
363 /*
364  * Write a message into the status file.
365  */
366 static void
367 upstat(struct printer *pp, const char *msg, int notifyuser)
368 {
369 	int fd;
370 	char statfile[MAXPATHLEN];
371 
372 	status_file_name(pp, statfile, sizeof statfile);
373 	umask(0);
374 	seteuid(euid);
375 	fd = open(statfile, O_WRONLY|O_CREAT|O_EXLOCK, STAT_FILE_MODE);
376 	seteuid(uid);
377 	if (fd < 0) {
378 		printf("\tcannot create status file: %s\n", strerror(errno));
379 		return;
380 	}
381 	(void) ftruncate(fd, 0);
382 	if (msg == (char *)NULL)
383 		(void) write(fd, "\n", 1);
384 	else
385 		(void) write(fd, msg, strlen(msg));
386 	(void) close(fd);
387 	if (notifyuser) {
388 		if ((msg == (char *)NULL) || (strcmp(msg, "\n") == 0))
389 			printf("\tstatus message is now set to nothing.\n");
390 		else
391 			printf("\tstatus message is now: %s", msg);
392 	}
393 }
394 
395 /*
396  * kill an existing daemon and disable printing.
397  */
398 void
399 abort_q(struct printer *pp)
400 {
401 	int killres, setres;
402 	char lf[MAXPATHLEN];
403 
404 	lock_file_name(pp, lf, sizeof lf);
405 	printf("%s:\n", pp->printer);
406 
407 	/*
408 	 * Turn on the owner execute bit of the lock file to disable printing.
409 	 */
410 	setres = set_qstate(SQS_STOPP, lf);
411 
412 	/*
413 	 * If set_qstate found that there already was a lock file, then
414 	 * call a routine which will read that lock file and kill the
415 	 * lpd-process which is listed in that lock file.  If the lock
416 	 * file did not exist, then either there is no daemon running
417 	 * for this queue, or there is one running but *it* could not
418 	 * write a lock file (which means we can not determine the
419 	 * process id of that lpd-process).
420 	 */
421 	switch (setres) {
422 	case SQS_CHGOK:
423 	case SQS_CHGFAIL:
424 		/* Kill the process */
425 		killres = kill_qtask(lf);
426 		break;
427 	case SQS_CREOK:
428 	case SQS_CREFAIL:
429 		printf("\tno daemon to abort\n");
430 		break;
431 	case SQS_STATFAIL:
432 		printf("\tassuming no daemon to abort\n");
433 		break;
434 	default:
435 		printf("\t<unexpected result (%d) from set_qstate>\n",
436 		    setres);
437 		break;
438 	}
439 
440 	if (setres >= 0)
441 		upstat(pp, "printing disabled\n", 0);
442 }
443 
444 /*
445  * "global" variables for all the routines related to 'clean' and 'tclean'
446  */
447 static time_t	 cln_now;		/* current time */
448 static double	 cln_minage;		/* minimum age before file is removed */
449 static long	 cln_sizecnt;		/* amount of space freed up */
450 static int 	 cln_debug;		/* print extra debugging msgs */
451 static int	 cln_filecnt;		/* number of files destroyed */
452 static int	 cln_foundcore;		/* found a core file! */
453 static int	 cln_queuecnt;		/* number of queues checked */
454 static int 	 cln_testonly;		/* remove-files vs just-print-info */
455 
456 static int
457 doselect(struct dirent *d)
458 {
459 	int c = d->d_name[0];
460 
461 	if ((c == 'c' || c == 'd' || c == 'r' || c == 't') &&
462 	    d->d_name[1] == 'f')
463 		return 1;
464 	if (c == 'c') {
465 		if (!strcmp(d->d_name, "core"))
466 			cln_foundcore = 1;
467 	}
468 	if (c == 'e') {
469 		if (!strncmp(d->d_name, "errs.", 5))
470 			return 1;
471 	}
472 	return 0;
473 }
474 
475 /*
476  * Comparison routine that clean_q() uses for scandir.
477  *
478  * The purpose of this sort is to have all `df' files end up immediately
479  * after the matching `cf' file.  For files matching `cf', `df', `rf', or
480  * `tf', it sorts by job number and machine, then by `cf', `df', `rf', or
481  * `tf', and then by the sequence letter (which is A-Z, or a-z).    This
482  * routine may also see filenames which do not start with `cf', `df', `rf',
483  * or `tf' (such as `errs.*'), and those are simply sorted by the full
484  * filename.
485  *
486  * XXX
487  *   This assumes that all control files start with `cfA*', and it turns
488  *   out there are a few implementations of lpr which will create `cfB*'
489  *   filenames (they will have datafile names which start with `dfB*').
490  */
491 static int
492 sortq(const void *a, const void *b)
493 {
494 	const int a_lt_b = -1, a_gt_b = 1, cat_other = 10;
495 	const char *fname_a, *fname_b, *jnum_a, *jnum_b;
496 	int cat_a, cat_b, ch, res, seq_a, seq_b;
497 
498 	fname_a = (*(const struct dirent * const *)a)->d_name;
499 	fname_b = (*(const struct dirent * const *)b)->d_name;
500 
501 	/*
502 	 * First separate filenames into cagatories.  Catagories are
503 	 * legitimate `cf', `df', `rf' & `tf' filenames, and "other" - in
504 	 * that order.  It is critical that the mapping be exactly the
505 	 * same for 'a' vs 'b', so define a macro for the job.
506 	 *
507 	 * [aside: the standard `cf' file has the jobnumber start in
508 	 * position 4, but some implementations have that as an extra
509 	 * file-sequence letter, and start the job number in position 5.]
510 	 */
511 #define MAP_TO_CAT(fname_X,cat_X,jnum_X,seq_X) do { \
512 	cat_X = cat_other;    \
513 	ch = *(fname_X + 2);  \
514 	jnum_X = fname_X + 3; \
515 	seq_X = 0;            \
516 	if ((*(fname_X + 1) == 'f') && (isalpha(ch))) { \
517 		seq_X = ch; \
518 		if (*fname_X == 'c') \
519 			cat_X = 1; \
520 		else if (*fname_X == 'd') \
521 			cat_X = 2; \
522 		else if (*fname_X == 'r') \
523 			cat_X = 3; \
524 		else if (*fname_X == 't') \
525 			cat_X = 4; \
526 		if (cat_X != cat_other) { \
527 			ch = *jnum_X; \
528 			if (!isdigit(ch)) { \
529 				if (isalpha(ch)) { \
530 					jnum_X++; \
531 					ch = *jnum_X; \
532 					seq_X = (seq_X << 8) + ch; \
533 				} \
534 				if (!isdigit(ch)) \
535 					cat_X = cat_other; \
536 			} \
537 		} \
538 	} \
539 } while (0)
540 
541 	MAP_TO_CAT(fname_a, cat_a, jnum_a, seq_a);
542 	MAP_TO_CAT(fname_b, cat_b, jnum_b, seq_b);
543 
544 #undef MAP_TO_CAT
545 
546 	/* First handle all cases which have "other" files */
547 	if ((cat_a >= cat_other) || (cat_b >= cat_other)) {
548 		/* for two "other" files, just compare the full name */
549 		if (cat_a == cat_b)
550 			res = strcmp(fname_a, fname_b);
551 		else if (cat_a < cat_b)
552 			res = a_lt_b;
553 		else
554 			res = a_gt_b;
555 		goto have_res;
556 	}
557 
558 	/*
559 	 * At this point, we know both files are legitimate `cf', `df', `rf',
560 	 * or `tf' files.  Compare them by job-number and machine name.
561 	 */
562 	res = strcmp(jnum_a, jnum_b);
563 	if (res != 0)
564 		goto have_res;
565 
566 	/*
567 	 * We have two files which belong to the same job.  Sort based
568 	 * on the catagory of file (`c' before `d', etc).
569 	 */
570 	if (cat_a < cat_b) {
571 		res = a_lt_b;
572 		goto have_res;
573 	} else if (cat_a > cat_b) {
574 		res = a_gt_b;
575 		goto have_res;
576 	}
577 
578 	/*
579 	 * Two files in the same catagory for a single job.  Sort based
580 	 * on the sequence letter(s).  (usually `A' thru `Z', etc).
581 	 */
582 	if (seq_a < seq_b) {
583 		res = a_lt_b;
584 		goto have_res;
585 	} else if (seq_a > seq_b) {
586 		res = a_gt_b;
587 		goto have_res;
588 	}
589 
590 	/*
591 	 * Given that the filenames in a directory are unique, this SHOULD
592 	 * never happen (unless there are logic errors in this routine).
593 	 * But if it does happen, we must return "is equal" or the caller
594 	 * might see inconsistent results in the sorting order, and that
595 	 * can trigger other problems.
596 	 */
597 	printf("\t*** Error in sortq: %s == %s !\n", fname_a, fname_b);
598 	printf("\t***       cat %d == %d ; seq = %d %d\n", cat_a, cat_b,
599 	    seq_a, seq_b);
600 	res = 0;
601 
602 have_res:
603 	return res;
604 }
605 
606 /*
607  * Remove all spool files and temporaries from the spooling area.
608  * Or, perhaps:
609  * Remove incomplete jobs from spooling area.
610  */
611 
612 void
613 clean_gi(int argc, char *argv[])
614 {
615 
616 	/* init some fields before 'clean' is called for each queue */
617 	cln_queuecnt = 0;
618 	cln_now = time(NULL);
619 	cln_minage = 3600.0;		/* only delete files >1h old */
620 	cln_filecnt = 0;
621 	cln_sizecnt = 0;
622 	cln_debug = 0;
623 	cln_testonly = 0;
624 	generic_wrapup = &wrapup_clean;
625 
626 	/* see if there are any options specified before the ptr list */
627 	for (; argc > 0; argc--, argv++) {
628 		if (**argv != '-')
629 			break;
630 		if (strcmp(*argv, "-d") == 0) {
631 			/* just an example of an option... */
632 			cln_debug++;
633 			*argv = generic_nullarg;	/* "erase" it */
634 		} else {
635 			printf("Invalid option '%s'\n", *argv);
636 			generic_initerr = 1;
637 		}
638 	}
639 
640 	return;
641 }
642 
643 void
644 tclean_gi(int argc, char *argv[])
645 {
646 
647 	/* only difference between 'clean' and 'tclean' is one value */
648 	/* (...and the fact that 'clean' is priv and 'tclean' is not) */
649 	clean_gi(argc, argv);
650 	cln_testonly = 1;
651 
652 	return;
653 }
654 
655 void
656 clean_q(struct printer *pp)
657 {
658 	char *cp, *cp1, *lp;
659 	struct dirent **queue;
660 	size_t linerem;
661 	int didhead, i, n, nitems, rmcp;
662 
663 	cln_queuecnt++;
664 
665 	didhead = 0;
666 	if (generic_qselect == QSEL_BYNAME) {
667 		printf("%s:\n", pp->printer);
668 		didhead = 1;
669 	}
670 
671 	lp = line;
672 	cp = pp->spool_dir;
673 	while (lp < &line[sizeof(line) - 1]) {
674 		if ((*lp++ = *cp++) == 0)
675 			break;
676 	}
677 	lp[-1] = '/';
678 	linerem = sizeof(line) - (lp - line);
679 
680 	cln_foundcore = 0;
681 	seteuid(euid);
682 	nitems = scandir(pp->spool_dir, &queue, doselect, sortq);
683 	seteuid(uid);
684 	if (nitems < 0) {
685 		if (!didhead) {
686 			printf("%s:\n", pp->printer);
687 			didhead = 1;
688 		}
689 		printf("\tcannot examine spool directory\n");
690 		return;
691 	}
692 	if (cln_foundcore) {
693 		if (!didhead) {
694 			printf("%s:\n", pp->printer);
695 			didhead = 1;
696 		}
697 		printf("\t** found a core file in %s !\n", pp->spool_dir);
698 	}
699 	if (nitems == 0)
700 		return;
701 	if (!didhead)
702 		printf("%s:\n", pp->printer);
703 	if (cln_debug) {
704 		printf("\t** ----- Sorted list of files being checked:\n");
705 		i = 0;
706 		do {
707 			cp = queue[i]->d_name;
708 			printf("\t** [%3d] = %s\n", i, cp);
709 		} while (++i < nitems);
710 		printf("\t** ----- end of sorted list\n");
711 	}
712 	i = 0;
713 	do {
714 		cp = queue[i]->d_name;
715 		rmcp = 0;
716 		if (*cp == 'c') {
717 			/*
718 			 * A control file.  Look for matching data-files.
719 			 */
720 			/* XXX
721 			 *  Note the logic here assumes that the hostname
722 			 *  part of cf-filenames match the hostname part
723 			 *  in df-filenames, and that is not necessarily
724 			 *  true (eg: for multi-homed hosts).  This needs
725 			 *  some further thought...
726 			 */
727 			n = 0;
728 			while (i + 1 < nitems) {
729 				cp1 = queue[i + 1]->d_name;
730 				if (*cp1 != 'd' || strcmp(cp + 3, cp1 + 3))
731 					break;
732 				i++;
733 				n++;
734 			}
735 			if (n == 0) {
736 				rmcp = 1;
737 			}
738 		} else if (*cp == 'e') {
739 			/*
740 			 * Must be an errrs or email temp file.
741 			 */
742 			rmcp = 1;
743 		} else {
744 			/*
745 			 * Must be a df with no cf (otherwise, it would have
746 			 * been skipped above) or an rf or tf file (which can
747 			 * always be removed if it is old enough).
748 			 */
749 			rmcp = 1;
750 		}
751 		if (rmcp) {
752 			if (strlen(cp) >= linerem) {
753 				printf("\t** internal error: 'line' overflow!\n");
754 				printf("\t**   spooldir = %s\n", pp->spool_dir);
755 				printf("\t**   cp = %s\n", cp);
756 				return;
757 			}
758 			strlcpy(lp, cp, linerem);
759 			unlinkf(line);
760 		}
761      	} while (++i < nitems);
762 }
763 
764 static void
765 wrapup_clean(int laststatus __unused)
766 {
767 
768 	printf("Checked %d queues, and ", cln_queuecnt);
769 	if (cln_filecnt < 1) {
770 		printf("no cruft was found\n");
771 		return;
772 	}
773 	if (cln_testonly) {
774 		printf("would have ");
775 	}
776 	printf("removed %d files (%ld bytes).\n", cln_filecnt, cln_sizecnt);
777 }
778 
779 static void
780 unlinkf(char *name)
781 {
782 	struct stat stbuf;
783 	double agemod, agestat;
784 	int res;
785 	char linkbuf[BUFSIZ];
786 
787 	/*
788 	 * We have to use lstat() instead of stat(), in case this is a df*
789 	 * "file" which is really a symlink due to 'lpr -s' processing.  In
790 	 * that case, we need to check the last-mod time of the symlink, and
791 	 * not the file that the symlink is pointed at.
792 	 */
793 	seteuid(euid);
794 	res = lstat(name, &stbuf);
795 	seteuid(uid);
796 	if (res < 0) {
797 		printf("\terror return from stat(%s):\n", name);
798 		printf("\t      %s\n", strerror(errno));
799 		return;
800 	}
801 
802 	agemod = difftime(cln_now, stbuf.st_mtime);
803 	agestat = difftime(cln_now,  stbuf.st_ctime);
804 	if (cln_debug > 1) {
805 		/* this debugging-aid probably is not needed any more... */
806 		printf("\t\t  modify age=%g secs, stat age=%g secs\n",
807 		    agemod, agestat);
808 	}
809 	if ((agemod <= cln_minage) && (agestat <= cln_minage))
810 		return;
811 
812 	/*
813 	 * if this file is a symlink, then find out the target of the
814 	 * symlink before unlink-ing the file itself
815 	 */
816 	if (S_ISLNK(stbuf.st_mode)) {
817 		seteuid(euid);
818 		res = readlink(name, linkbuf, sizeof(linkbuf));
819 		seteuid(uid);
820 		if (res < 0) {
821 			printf("\terror return from readlink(%s):\n", name);
822 			printf("\t      %s\n", strerror(errno));
823 			return;
824 		}
825 		if (res == sizeof(linkbuf))
826 			res--;
827 		linkbuf[res] = '\0';
828 	}
829 
830 	cln_filecnt++;
831 	cln_sizecnt += stbuf.st_size;
832 
833 	if (cln_testonly) {
834 		printf("\twould remove %s\n", name);
835 		if (S_ISLNK(stbuf.st_mode)) {
836 			printf("\t    (which is a symlink to %s)\n", linkbuf);
837 		}
838 	} else {
839 		seteuid(euid);
840 		res = unlink(name);
841 		seteuid(uid);
842 		if (res < 0)
843 			printf("\tcannot remove %s (!)\n", name);
844 		else
845 			printf("\tremoved %s\n", name);
846 		/* XXX
847 		 *  Note that for a df* file, this code should also check to see
848 		 *  if it is a symlink to some other file, and if the original
849 		 *  lpr command included '-r' ("remove file").  Of course, this
850 		 *  code would not be removing the df* file unless there was no
851 		 *  matching cf* file, and without the cf* file it is currently
852 		 *  impossible to determine if '-r' had been specified...
853 		 *
854 		 *  As a result of this quandry, we may be leaving behind a
855 		 *  user's file that was supposed to have been removed after
856 		 *  being printed.  This may effect services such as CAP or
857 		 *  samba, if they were configured to use 'lpr -r', and if
858 		 *  datafiles are not being properly removed.
859 		*/
860 		if (S_ISLNK(stbuf.st_mode)) {
861 			printf("\t    (which was a symlink to %s)\n", linkbuf);
862 		}
863 	}
864 }
865 
866 /*
867  * Enable queuing to the printer (allow lpr to add new jobs to the queue).
868  */
869 void
870 enable_q(struct printer *pp)
871 {
872 	int setres;
873 	char lf[MAXPATHLEN];
874 
875 	lock_file_name(pp, lf, sizeof lf);
876 	printf("%s:\n", pp->printer);
877 
878 	setres = set_qstate(SQS_ENABLEQ, lf);
879 }
880 
881 /*
882  * Disable queuing.
883  */
884 void
885 disable_q(struct printer *pp)
886 {
887 	int setres;
888 	char lf[MAXPATHLEN];
889 
890 	lock_file_name(pp, lf, sizeof lf);
891 	printf("%s:\n", pp->printer);
892 
893 	setres = set_qstate(SQS_DISABLEQ, lf);
894 }
895 
896 /*
897  * Disable queuing and printing and put a message into the status file
898  * (reason for being down).  If the user specified `-msg', then use
899  * everything after that as the message for the status file.  If the
900  * user did NOT specify `-msg', then the command should take the first
901  * parameter as the printer name, and all remaining parameters as the
902  * message for the status file.  (This is to be compatible with the
903  * original definition of 'down', which was implemented long before
904  * `-msg' was around).
905  */
906 void
907 down_gi(int argc, char *argv[])
908 {
909 
910 	/* If `-msg' was specified, then this routine has nothing to do. */
911 	if (generic_msg != NULL)
912 		return;
913 
914 	/*
915 	 * If the user only gave one parameter, then use a default msg.
916 	 * (if argc == 1 at this point, then *argv == name of printer).
917 	 */
918 	if (argc == 1) {
919 		generic_msg = strdup("printing disabled\n");
920 		return;
921 	}
922 
923 	/*
924 	 * The user specified multiple parameters, and did not specify
925 	 * `-msg'.  Build a message from all the parameters after the
926 	 * first one (and nullify those parameters so generic-processing
927 	 * will not process them as printer-queue names).
928 	 */
929 	argc--;
930 	argv++;
931 	generic_msg = args2line(argc, argv);
932 	for (; argc > 0; argc--, argv++)
933 		*argv = generic_nullarg;	/* "erase" it */
934 }
935 
936 void
937 down_q(struct printer *pp)
938 {
939 	int setres;
940 	char lf[MAXPATHLEN];
941 
942 	lock_file_name(pp, lf, sizeof lf);
943 	printf("%s:\n", pp->printer);
944 
945 	setres = set_qstate(SQS_DISABLEQ+SQS_STOPP, lf);
946 	if (setres >= 0)
947 		upstat(pp, generic_msg, 1);
948 }
949 
950 /*
951  * Exit lpc
952  */
953 void
954 quit(int argc __unused, char *argv[] __unused)
955 {
956 	exit(0);
957 }
958 
959 /*
960  * Kill and restart the daemon.
961  */
962 void
963 restart_q(struct printer *pp)
964 {
965 	int killres, setres, startok;
966 	char lf[MAXPATHLEN];
967 
968 	lock_file_name(pp, lf, sizeof lf);
969 	printf("%s:\n", pp->printer);
970 
971 	killres = kill_qtask(lf);
972 
973 	/*
974 	 * XXX - if the kill worked, we should probably sleep for
975 	 *      a second or so before trying to restart the queue.
976 	 */
977 
978 	/* make sure the queue is set to print jobs */
979 	setres = set_qstate(SQS_STARTP, lf);
980 
981 	seteuid(euid);
982 	startok = startdaemon(pp);
983 	seteuid(uid);
984 	if (!startok)
985 		printf("\tcouldn't restart daemon\n");
986 	else
987 		printf("\tdaemon restarted\n");
988 }
989 
990 /*
991  * Set the status message of each queue listed.  Requires a "-msg"
992  * parameter to indicate the end of the queue list and start of msg text.
993  */
994 void
995 setstatus_gi(int argc __unused, char *argv[] __unused)
996 {
997 
998 	if (generic_msg == NULL) {
999 		printf("You must specify '-msg' before the text of the new status message.\n");
1000 		generic_initerr = 1;
1001 	}
1002 }
1003 
1004 void
1005 setstatus_q(struct printer *pp)
1006 {
1007 	char lf[MAXPATHLEN];
1008 
1009 	lock_file_name(pp, lf, sizeof lf);
1010 	printf("%s:\n", pp->printer);
1011 
1012 	upstat(pp, generic_msg, 1);
1013 }
1014 
1015 /*
1016  * Enable printing on the specified printer and startup the daemon.
1017  */
1018 void
1019 start_q(struct printer *pp)
1020 {
1021 	int setres, startok;
1022 	char lf[MAXPATHLEN];
1023 
1024 	lock_file_name(pp, lf, sizeof lf);
1025 	printf("%s:\n", pp->printer);
1026 
1027 	setres = set_qstate(SQS_STARTP, lf);
1028 
1029 	seteuid(euid);
1030 	startok = startdaemon(pp);
1031 	seteuid(uid);
1032 	if (!startok)
1033 		printf("\tcouldn't start daemon\n");
1034 	else
1035 		printf("\tdaemon started\n");
1036 	seteuid(uid);
1037 }
1038 
1039 /*
1040  * Print the status of the printer queue.
1041  */
1042 void
1043 status(struct printer *pp)
1044 {
1045 	struct stat stbuf;
1046 	register int fd, i;
1047 	register struct dirent *dp;
1048 	DIR *dirp;
1049 	char file[MAXPATHLEN];
1050 
1051 	printf("%s:\n", pp->printer);
1052 	lock_file_name(pp, file, sizeof file);
1053 	if (stat(file, &stbuf) >= 0) {
1054 		printf("\tqueuing is %s\n",
1055 		       ((stbuf.st_mode & LFM_QUEUE_DIS) ? "disabled"
1056 			: "enabled"));
1057 		printf("\tprinting is %s\n",
1058 		       ((stbuf.st_mode & LFM_PRINT_DIS) ? "disabled"
1059 			: "enabled"));
1060 	} else {
1061 		printf("\tqueuing is enabled\n");
1062 		printf("\tprinting is enabled\n");
1063 	}
1064 	if ((dirp = opendir(pp->spool_dir)) == NULL) {
1065 		printf("\tcannot examine spool directory\n");
1066 		return;
1067 	}
1068 	i = 0;
1069 	while ((dp = readdir(dirp)) != NULL) {
1070 		if (*dp->d_name == 'c' && dp->d_name[1] == 'f')
1071 			i++;
1072 	}
1073 	closedir(dirp);
1074 	if (i == 0)
1075 		printf("\tno entries in spool area\n");
1076 	else if (i == 1)
1077 		printf("\t1 entry in spool area\n");
1078 	else
1079 		printf("\t%d entries in spool area\n", i);
1080 	fd = open(file, O_RDONLY);
1081 	if (fd < 0 || flock(fd, LOCK_SH|LOCK_NB) == 0) {
1082 		(void) close(fd);	/* unlocks as well */
1083 		printf("\tprinter idle\n");
1084 		return;
1085 	}
1086 	(void) close(fd);
1087 	/* print out the contents of the status file, if it exists */
1088 	status_file_name(pp, file, sizeof file);
1089 	fd = open(file, O_RDONLY|O_SHLOCK);
1090 	if (fd >= 0) {
1091 		(void) fstat(fd, &stbuf);
1092 		if (stbuf.st_size > 0) {
1093 			putchar('\t');
1094 			while ((i = read(fd, line, sizeof(line))) > 0)
1095 				(void) fwrite(line, 1, i, stdout);
1096 		}
1097 		(void) close(fd);	/* unlocks as well */
1098 	}
1099 }
1100 
1101 /*
1102  * Stop the specified daemon after completing the current job and disable
1103  * printing.
1104  */
1105 void
1106 stop_q(struct printer *pp)
1107 {
1108 	int setres;
1109 	char lf[MAXPATHLEN];
1110 
1111 	lock_file_name(pp, lf, sizeof lf);
1112 	printf("%s:\n", pp->printer);
1113 
1114 	setres = set_qstate(SQS_STOPP, lf);
1115 
1116 	if (setres >= 0)
1117 		upstat(pp, "printing disabled\n", 0);
1118 }
1119 
1120 struct	jobqueue **queue;
1121 int	nitems;
1122 time_t	mtime;
1123 
1124 /*
1125  * Put the specified jobs at the top of printer queue.
1126  */
1127 void
1128 topq(int argc, char *argv[])
1129 {
1130 	register int i;
1131 	struct stat stbuf;
1132 	int cmdstatus, changed;
1133 	struct printer myprinter, *pp = &myprinter;
1134 
1135 	if (argc < 3) {
1136 		printf("usage: topq printer [jobnum ...] [user ...]\n");
1137 		return;
1138 	}
1139 
1140 	--argc;
1141 	++argv;
1142 	init_printer(pp);
1143 	cmdstatus = getprintcap(*argv, pp);
1144 	switch(cmdstatus) {
1145 	default:
1146 		fatal(pp, "%s", pcaperr(cmdstatus));
1147 	case PCAPERR_NOTFOUND:
1148 		printf("unknown printer %s\n", *argv);
1149 		return;
1150 	case PCAPERR_TCOPEN:
1151 		printf("warning: %s: unresolved tc= reference(s)", *argv);
1152 		break;
1153 	case PCAPERR_SUCCESS:
1154 		break;
1155 	}
1156 	printf("%s:\n", pp->printer);
1157 
1158 	seteuid(euid);
1159 	if (chdir(pp->spool_dir) < 0) {
1160 		printf("\tcannot chdir to %s\n", pp->spool_dir);
1161 		goto out;
1162 	}
1163 	seteuid(uid);
1164 	nitems = getq(pp, &queue);
1165 	if (nitems == 0)
1166 		return;
1167 	changed = 0;
1168 	mtime = queue[0]->job_time;
1169 	for (i = argc; --i; ) {
1170 		if (doarg(argv[i]) == 0) {
1171 			printf("\tjob %s is not in the queue\n", argv[i]);
1172 			continue;
1173 		} else
1174 			changed++;
1175 	}
1176 	for (i = 0; i < nitems; i++)
1177 		free(queue[i]);
1178 	free(queue);
1179 	if (!changed) {
1180 		printf("\tqueue order unchanged\n");
1181 		return;
1182 	}
1183 	/*
1184 	 * Turn on the public execute bit of the lock file to
1185 	 * get lpd to rebuild the queue after the current job.
1186 	 */
1187 	seteuid(euid);
1188 	if (changed && stat(pp->lock_file, &stbuf) >= 0)
1189 		(void) chmod(pp->lock_file, stbuf.st_mode | LFM_RESET_QUE);
1190 
1191 out:
1192 	seteuid(uid);
1193 }
1194 
1195 /*
1196  * Reposition the job by changing the modification time of
1197  * the control file.
1198  */
1199 static int
1200 touch(struct jobqueue *jq)
1201 {
1202 	struct timeval tvp[2];
1203 	int ret;
1204 
1205 	tvp[0].tv_sec = tvp[1].tv_sec = --mtime;
1206 	tvp[0].tv_usec = tvp[1].tv_usec = 0;
1207 	seteuid(euid);
1208 	ret = utimes(jq->job_cfname, tvp);
1209 	seteuid(uid);
1210 	return (ret);
1211 }
1212 
1213 /*
1214  * Checks if specified job name is in the printer's queue.
1215  * Returns:  negative (-1) if argument name is not in the queue.
1216  */
1217 static int
1218 doarg(char *job)
1219 {
1220 	register struct jobqueue **qq;
1221 	register int jobnum, n;
1222 	register char *cp, *machine;
1223 	int cnt = 0;
1224 	FILE *fp;
1225 
1226 	/*
1227 	 * Look for a job item consisting of system name, colon, number
1228 	 * (example: ucbarpa:114)
1229 	 */
1230 	if ((cp = strchr(job, ':')) != NULL) {
1231 		machine = job;
1232 		*cp++ = '\0';
1233 		job = cp;
1234 	} else
1235 		machine = NULL;
1236 
1237 	/*
1238 	 * Check for job specified by number (example: 112 or 235ucbarpa).
1239 	 */
1240 	if (isdigit(*job)) {
1241 		jobnum = 0;
1242 		do
1243 			jobnum = jobnum * 10 + (*job++ - '0');
1244 		while (isdigit(*job));
1245 		for (qq = queue + nitems; --qq >= queue; ) {
1246 			n = 0;
1247 			for (cp = (*qq)->job_cfname+3; isdigit(*cp); )
1248 				n = n * 10 + (*cp++ - '0');
1249 			if (jobnum != n)
1250 				continue;
1251 			if (*job && strcmp(job, cp) != 0)
1252 				continue;
1253 			if (machine != NULL && strcmp(machine, cp) != 0)
1254 				continue;
1255 			if (touch(*qq) == 0) {
1256 				printf("\tmoved %s\n", (*qq)->job_cfname);
1257 				cnt++;
1258 			}
1259 		}
1260 		return(cnt);
1261 	}
1262 	/*
1263 	 * Process item consisting of owner's name (example: henry).
1264 	 */
1265 	for (qq = queue + nitems; --qq >= queue; ) {
1266 		seteuid(euid);
1267 		fp = fopen((*qq)->job_cfname, "r");
1268 		seteuid(uid);
1269 		if (fp == NULL)
1270 			continue;
1271 		while (getline(fp) > 0)
1272 			if (line[0] == 'P')
1273 				break;
1274 		(void) fclose(fp);
1275 		if (line[0] != 'P' || strcmp(job, line+1) != 0)
1276 			continue;
1277 		if (touch(*qq) == 0) {
1278 			printf("\tmoved %s\n", (*qq)->job_cfname);
1279 			cnt++;
1280 		}
1281 	}
1282 	return(cnt);
1283 }
1284 
1285 /*
1286  * Enable both queuing & printing, and start printer (undo `down').
1287  */
1288 void
1289 up_q(struct printer *pp)
1290 {
1291 	int setres, startok;
1292 	char lf[MAXPATHLEN];
1293 
1294 	lock_file_name(pp, lf, sizeof lf);
1295 	printf("%s:\n", pp->printer);
1296 
1297 	setres = set_qstate(SQS_ENABLEQ+SQS_STARTP, lf);
1298 
1299 	seteuid(euid);
1300 	startok = startdaemon(pp);
1301 	seteuid(uid);
1302 	if (!startok)
1303 		printf("\tcouldn't start daemon\n");
1304 	else
1305 		printf("\tdaemon started\n");
1306 }
1307