xref: /freebsd/usr.sbin/lpr/lpc/cmds.c (revision 5b31cc94b10d4bb7109c6b27940a0fc76a44a331)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1983, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #ifndef lint
34 static const char copyright[] =
35 "@(#) Copyright (c) 1983, 1993\n\
36 	The Regents of the University of California.  All rights reserved.\n";
37 #endif /* not lint */
38 
39 #if 0
40 #endif
41 
42 #include "lp.cdefs.h"		/* A cross-platform version of <sys/cdefs.h> */
43 /*
44  * lpc -- line printer control program -- commands:
45  */
46 
47 #include <sys/param.h>
48 #include <sys/time.h>
49 #include <sys/stat.h>
50 #include <sys/file.h>
51 
52 #include <signal.h>
53 #include <fcntl.h>
54 #include <err.h>
55 #include <errno.h>
56 #include <dirent.h>
57 #include <unistd.h>
58 #include <stdlib.h>
59 #include <stdio.h>
60 #include <ctype.h>
61 #include <string.h>
62 #include "lp.h"
63 #include "lp.local.h"
64 #include "lpc.h"
65 #include "extern.h"
66 #include "pathnames.h"
67 
68 /*
69  * Return values from kill_qtask().
70  */
71 #define KQT_LFERROR	-2
72 #define KQT_KILLFAIL	-1
73 #define KQT_NODAEMON	0
74 #define KQT_KILLOK	1
75 
76 static char	*args2line(int argc, char **argv);
77 static int	 doarg(char *_job);
78 static int	 doselect(const struct dirent *_d);
79 static int	 kill_qtask(const char *lf);
80 static int	 sortq(const struct dirent **a, const struct dirent **b);
81 static int	 touch(struct jobqueue *_jq);
82 static void	 unlinkf(char *_name);
83 static void	 upstat(struct printer *_pp, const char *_msg, int _notify);
84 static void	 wrapup_clean(int _laststatus);
85 
86 /*
87  * generic framework for commands which operate on all or a specified
88  * set of printers
89  */
90 enum	qsel_val {			/* how a given ptr was selected */
91 	QSEL_UNKNOWN = -1,		/* ... not selected yet */
92 	QSEL_BYNAME = 0,		/* ... user specified it by name */
93 	QSEL_ALL = 1			/* ... user wants "all" printers */
94 					/*     (with more to come)    */
95 };
96 
97 static enum qsel_val generic_qselect;	/* indicates how ptr was selected */
98 static int generic_initerr;		/* result of initrtn processing */
99 static char *generic_cmdname;
100 static char *generic_msg;		/* if a -msg was specified */
101 static char *generic_nullarg;
102 static void (*generic_wrapup)(int _last_status);   /* perform rtn wrap-up */
103 
104 void
105 generic(void (*specificrtn)(struct printer *_pp), int cmdopts,
106     void (*initrtn)(int _argc, char *_argv[]), int argc, char *argv[])
107 {
108 	int cmdstatus, more, targc;
109 	struct printer myprinter, *pp;
110 	char **margv, **targv;
111 
112 	if (argc == 1) {
113 		/*
114 		 * Usage needs a special case for 'down': The user must
115 		 * either include `-msg', or only the first parameter
116 		 * that they give will be processed as a printer name.
117 		 */
118 		printf("usage: %s  {all | printer ...}", argv[0]);
119 		if (strcmp(argv[0], "down") == 0) {
120 			printf(" -msg [<text> ...]\n");
121 			printf("   or: down  {all | printer} [<text> ...]");
122 		} else if (cmdopts & LPC_MSGOPT)
123 			printf(" [-msg <text> ...]");
124 		printf("\n");
125 		return;
126 	}
127 
128 	/* The first argument is the command name. */
129 	generic_cmdname = *argv++;
130 	argc--;
131 
132 	/*
133 	 * The initialization routine for a command might set a generic
134 	 * "wrapup" routine, which should be called after processing all
135 	 * the printers in the command.  This might print summary info.
136 	 *
137 	 * Note that the initialization routine may also parse (and
138 	 * nullify) some of the parameters given on the command, leaving
139 	 * only the parameters which have to do with printer names.
140 	 */
141 	pp = &myprinter;
142 	generic_wrapup = NULL;
143 	generic_qselect = QSEL_UNKNOWN;
144 	cmdstatus = 0;
145 	/* this just needs to be a distinct value of type 'char *' */
146 	if (generic_nullarg == NULL)
147 		generic_nullarg = strdup("");
148 
149 	/*
150 	 * Some commands accept a -msg argument, which indicates that
151 	 * all remaining arguments should be combined into a string.
152 	 */
153 	generic_msg = NULL;
154 	if (cmdopts & LPC_MSGOPT) {
155 		targc = argc;
156 		targv = argv;
157 		for (; targc > 0; targc--, targv++) {
158 			if (strcmp(*targv, "-msg") == 0) {
159 				argc -= targc;
160 				generic_msg = args2line(targc - 1, targv + 1);
161 				break;
162 			}
163 		}
164 		if (argc < 1) {
165 			printf("error: No printer name(s) specified before"
166 			    " '-msg'.\n");
167 			printf("usage: %s  {all | printer ...}",
168 			    generic_cmdname);
169 			printf(" [-msg <text> ...]\n");
170 			return;
171 		}
172 	}
173 
174 	/* call initialization routine, if there is one for this cmd */
175 	if (initrtn != NULL) {
176 		generic_initerr = 0;
177 		(*initrtn)(argc, argv);
178 		if (generic_initerr)
179 			return;
180 		/*
181 		 * The initrtn may have null'ed out some of the parameters.
182 		 * Compact the parameter list to remove those nulls, and
183 		 * correct the arg-count.
184 		 */
185 		targc = argc;
186 		targv = argv;
187 		margv = argv;
188 		argc = 0;
189 		for (; targc > 0; targc--, targv++) {
190 			if (*targv != generic_nullarg) {
191 				if (targv != margv)
192 					*margv = *targv;
193 				margv++;
194 				argc++;
195 			}
196 		}
197 	}
198 
199 	if (argc == 1 && strcmp(*argv, "all") == 0) {
200 		generic_qselect = QSEL_ALL;
201 		more = firstprinter(pp, &cmdstatus);
202 		if (cmdstatus)
203 			goto looperr;
204 		while (more) {
205 			(*specificrtn)(pp);
206 			do {
207 				more = nextprinter(pp, &cmdstatus);
208 looperr:
209 				switch (cmdstatus) {
210 				case PCAPERR_TCOPEN:
211 					printf("warning: %s: unresolved "
212 					       "tc= reference(s) ",
213 					       pp->printer);
214 				case PCAPERR_SUCCESS:
215 					break;
216 				default:
217 					fatal(pp, "%s", pcaperr(cmdstatus));
218 				}
219 			} while (more && cmdstatus);
220 		}
221 		goto wrapup;
222 	}
223 
224 	generic_qselect = QSEL_BYNAME;		/* specifically-named ptrs */
225 	for (; argc > 0; argc--, argv++) {
226 		init_printer(pp);
227 		cmdstatus = getprintcap(*argv, pp);
228 		switch (cmdstatus) {
229 		default:
230 			fatal(pp, "%s", pcaperr(cmdstatus));
231 		case PCAPERR_NOTFOUND:
232 			printf("unknown printer %s\n", *argv);
233 			continue;
234 		case PCAPERR_TCOPEN:
235 			printf("warning: %s: unresolved tc= reference(s)\n",
236 			       *argv);
237 			break;
238 		case PCAPERR_SUCCESS:
239 			break;
240 		}
241 		(*specificrtn)(pp);
242 	}
243 
244 wrapup:
245 	if (generic_wrapup) {
246 		(*generic_wrapup)(cmdstatus);
247 	}
248 	free_printer(pp);
249 	if (generic_msg)
250 		free(generic_msg);
251 }
252 
253 /*
254  * Convert an argv-array of character strings into a single string.
255  */
256 static char *
257 args2line(int argc, char **argv)
258 {
259 	char *cp1, *cend;
260 	const char *cp2;
261 	char buf[1024];
262 
263 	if (argc <= 0)
264 		return strdup("\n");
265 
266 	cp1 = buf;
267 	cend = buf + sizeof(buf) - 1;		/* save room for '\0' */
268 	while (--argc >= 0) {
269 		cp2 = *argv++;
270 		while ((cp1 < cend) && (*cp1++ = *cp2++))
271 			;
272 		cp1[-1] = ' ';
273 	}
274 	cp1[-1] = '\n';
275 	*cp1 = '\0';
276 	return strdup(buf);
277 }
278 
279 /*
280  * Kill the current daemon, to stop printing of the active job.
281  */
282 static int
283 kill_qtask(const char *lf)
284 {
285 	FILE *fp;
286 	pid_t pid;
287 	int errsav, killres, lockres, res;
288 
289 	PRIV_START
290 	fp = fopen(lf, "r");
291 	errsav = errno;
292 	PRIV_END
293 	res = KQT_NODAEMON;
294 	if (fp == NULL) {
295 		/*
296 		 * If there is no lock file, then there is no daemon to
297 		 * kill.  Any other error return means there is some
298 		 * kind of problem with the lock file.
299 		 */
300 		if (errsav != ENOENT)
301 			res = KQT_LFERROR;
302 		goto killdone;
303 	}
304 
305 	/* If the lock file is empty, then there is no daemon to kill */
306 	if (get_line(fp) == 0)
307 		goto killdone;
308 
309 	/*
310 	 * If the file can be locked without blocking, then there
311 	 * no daemon to kill, or we should not try to kill it.
312 	 *
313 	 * XXX - not sure I understand the reasoning behind this...
314 	 */
315 	lockres = flock(fileno(fp), LOCK_SH|LOCK_NB);
316 	(void) fclose(fp);
317 	if (lockres == 0)
318 		goto killdone;
319 
320 	pid = atoi(line);
321 	if (pid < 0) {
322 		/*
323 		 * If we got a negative pid, then the contents of the
324 		 * lock file is not valid.
325 		 */
326 		res = KQT_LFERROR;
327 		goto killdone;
328 	}
329 
330 	PRIV_END
331 	killres = kill(pid, SIGTERM);
332 	errsav = errno;
333 	PRIV_END
334 	if (killres == 0) {
335 		res = KQT_KILLOK;
336 		printf("\tdaemon (pid %d) killed\n", pid);
337 	} else if (errno == ESRCH) {
338 		res = KQT_NODAEMON;
339 	} else {
340 		res = KQT_KILLFAIL;
341 		printf("\tWarning: daemon (pid %d) not killed:\n", pid);
342 		printf("\t    %s\n", strerror(errsav));
343 	}
344 
345 killdone:
346 	switch (res) {
347 	case KQT_LFERROR:
348 		printf("\tcannot open lock file: %s\n",
349 		    strerror(errsav));
350 		break;
351 	case KQT_NODAEMON:
352 		printf("\tno daemon to abort\n");
353 		break;
354 	case KQT_KILLFAIL:
355 	case KQT_KILLOK:
356 		/* These two already printed messages to the user. */
357 		break;
358 	default:
359 		printf("\t<internal error in kill_qtask>\n");
360 		break;
361 	}
362 
363 	return (res);
364 }
365 
366 /*
367  * Write a message into the status file.
368  */
369 static void
370 upstat(struct printer *pp, const char *msg, int notifyuser)
371 {
372 	int fd;
373 	char statfile[MAXPATHLEN];
374 
375 	status_file_name(pp, statfile, sizeof statfile);
376 	umask(0);
377 	PRIV_START
378 	fd = open(statfile, O_WRONLY|O_CREAT|O_EXLOCK, STAT_FILE_MODE);
379 	PRIV_END
380 	if (fd < 0) {
381 		printf("\tcannot create status file: %s\n", strerror(errno));
382 		return;
383 	}
384 	(void) ftruncate(fd, 0);
385 	if (msg == NULL)
386 		(void) write(fd, "\n", 1);
387 	else
388 		(void) write(fd, msg, strlen(msg));
389 	(void) close(fd);
390 	if (notifyuser) {
391 		if ((msg == (char *)NULL) || (strcmp(msg, "\n") == 0))
392 			printf("\tstatus message is now set to nothing.\n");
393 		else
394 			printf("\tstatus message is now: %s", msg);
395 	}
396 }
397 
398 /*
399  * kill an existing daemon and disable printing.
400  */
401 void
402 abort_q(struct printer *pp)
403 {
404 	int killres, setres;
405 	char lf[MAXPATHLEN];
406 
407 	lock_file_name(pp, lf, sizeof lf);
408 	printf("%s:\n", pp->printer);
409 
410 	/*
411 	 * Turn on the owner execute bit of the lock file to disable printing.
412 	 */
413 	setres = set_qstate(SQS_STOPP, lf);
414 
415 	/*
416 	 * If set_qstate found that there already was a lock file, then
417 	 * call a routine which will read that lock file and kill the
418 	 * lpd-process which is listed in that lock file.  If the lock
419 	 * file did not exist, then either there is no daemon running
420 	 * for this queue, or there is one running but *it* could not
421 	 * write a lock file (which means we can not determine the
422 	 * process id of that lpd-process).
423 	 */
424 	switch (setres) {
425 	case SQS_CHGOK:
426 	case SQS_CHGFAIL:
427 		/* Kill the process */
428 		killres = kill_qtask(lf);
429 		break;
430 	case SQS_CREOK:
431 	case SQS_CREFAIL:
432 		printf("\tno daemon to abort\n");
433 		break;
434 	case SQS_STATFAIL:
435 		printf("\tassuming no daemon to abort\n");
436 		break;
437 	default:
438 		printf("\t<unexpected result (%d) from set_qstate>\n",
439 		    setres);
440 		break;
441 	}
442 
443 	if (setres >= 0)
444 		upstat(pp, "printing disabled\n", 0);
445 }
446 
447 /*
448  * "global" variables for all the routines related to 'clean' and 'tclean'
449  */
450 static time_t	 cln_now;		/* current time */
451 static double	 cln_minage;		/* minimum age before file is removed */
452 static long	 cln_sizecnt;		/* amount of space freed up */
453 static int 	 cln_debug;		/* print extra debugging msgs */
454 static int	 cln_filecnt;		/* number of files destroyed */
455 static int	 cln_foundcore;		/* found a core file! */
456 static int	 cln_queuecnt;		/* number of queues checked */
457 static int 	 cln_testonly;		/* remove-files vs just-print-info */
458 
459 static int
460 doselect(const struct dirent *d)
461 {
462 	int c = d->d_name[0];
463 
464 	if ((c == 'c' || c == 'd' || c == 'r' || c == 't') &&
465 	    d->d_name[1] == 'f')
466 		return 1;
467 	if (c == 'c') {
468 		if (!strcmp(d->d_name, "core"))
469 			cln_foundcore = 1;
470 	}
471 	if (c == 'e') {
472 		if (!strncmp(d->d_name, "errs.", 5))
473 			return 1;
474 	}
475 	return 0;
476 }
477 
478 /*
479  * Comparison routine that clean_q() uses for scandir.
480  *
481  * The purpose of this sort is to have all `df' files end up immediately
482  * after the matching `cf' file.  For files matching `cf', `df', `rf', or
483  * `tf', it sorts by job number and machine, then by `cf', `df', `rf', or
484  * `tf', and then by the sequence letter (which is A-Z, or a-z).    This
485  * routine may also see filenames which do not start with `cf', `df', `rf',
486  * or `tf' (such as `errs.*'), and those are simply sorted by the full
487  * filename.
488  *
489  * XXX
490  *   This assumes that all control files start with `cfA*', and it turns
491  *   out there are a few implementations of lpr which will create `cfB*'
492  *   filenames (they will have datafile names which start with `dfB*').
493  */
494 static int
495 sortq(const struct dirent **a, const struct dirent **b)
496 {
497 	const int a_lt_b = -1, a_gt_b = 1, cat_other = 10;
498 	const char *fname_a, *fname_b, *jnum_a, *jnum_b;
499 	int cat_a, cat_b, ch, res, seq_a, seq_b;
500 
501 	fname_a = (*a)->d_name;
502 	fname_b = (*b)->d_name;
503 
504 	/*
505 	 * First separate filenames into categories.  Categories are
506 	 * legitimate `cf', `df', `rf' & `tf' filenames, and "other" - in
507 	 * that order.  It is critical that the mapping be exactly the
508 	 * same for 'a' vs 'b', so define a macro for the job.
509 	 *
510 	 * [aside: the standard `cf' file has the jobnumber start in
511 	 * position 4, but some implementations have that as an extra
512 	 * file-sequence letter, and start the job number in position 5.]
513 	 */
514 #define MAP_TO_CAT(fname_X,cat_X,jnum_X,seq_X) do { \
515 	cat_X = cat_other;    \
516 	ch = *(fname_X + 2);  \
517 	jnum_X = fname_X + 3; \
518 	seq_X = 0;            \
519 	if ((*(fname_X + 1) == 'f') && (isalpha(ch))) { \
520 		seq_X = ch; \
521 		if (*fname_X == 'c') \
522 			cat_X = 1; \
523 		else if (*fname_X == 'd') \
524 			cat_X = 2; \
525 		else if (*fname_X == 'r') \
526 			cat_X = 3; \
527 		else if (*fname_X == 't') \
528 			cat_X = 4; \
529 		if (cat_X != cat_other) { \
530 			ch = *jnum_X; \
531 			if (!isdigit(ch)) { \
532 				if (isalpha(ch)) { \
533 					jnum_X++; \
534 					ch = *jnum_X; \
535 					seq_X = (seq_X << 8) + ch; \
536 				} \
537 				if (!isdigit(ch)) \
538 					cat_X = cat_other; \
539 			} \
540 		} \
541 	} \
542 } while (0)
543 
544 	MAP_TO_CAT(fname_a, cat_a, jnum_a, seq_a);
545 	MAP_TO_CAT(fname_b, cat_b, jnum_b, seq_b);
546 
547 #undef MAP_TO_CAT
548 
549 	/* First handle all cases which have "other" files */
550 	if ((cat_a >= cat_other) || (cat_b >= cat_other)) {
551 		/* for two "other" files, just compare the full name */
552 		if (cat_a == cat_b)
553 			res = strcmp(fname_a, fname_b);
554 		else if (cat_a < cat_b)
555 			res = a_lt_b;
556 		else
557 			res = a_gt_b;
558 		goto have_res;
559 	}
560 
561 	/*
562 	 * At this point, we know both files are legitimate `cf', `df', `rf',
563 	 * or `tf' files.  Compare them by job-number and machine name.
564 	 */
565 	res = strcmp(jnum_a, jnum_b);
566 	if (res != 0)
567 		goto have_res;
568 
569 	/*
570 	 * We have two files which belong to the same job.  Sort based
571 	 * on the category of file (`c' before `d', etc).
572 	 */
573 	if (cat_a < cat_b) {
574 		res = a_lt_b;
575 		goto have_res;
576 	} else if (cat_a > cat_b) {
577 		res = a_gt_b;
578 		goto have_res;
579 	}
580 
581 	/*
582 	 * Two files in the same category for a single job.  Sort based
583 	 * on the sequence letter(s).  (usually `A' through `Z', etc).
584 	 */
585 	if (seq_a < seq_b) {
586 		res = a_lt_b;
587 		goto have_res;
588 	} else if (seq_a > seq_b) {
589 		res = a_gt_b;
590 		goto have_res;
591 	}
592 
593 	/*
594 	 * Given that the filenames in a directory are unique, this SHOULD
595 	 * never happen (unless there are logic errors in this routine).
596 	 * But if it does happen, we must return "is equal" or the caller
597 	 * might see inconsistent results in the sorting order, and that
598 	 * can trigger other problems.
599 	 */
600 	printf("\t*** Error in sortq: %s == %s !\n", fname_a, fname_b);
601 	printf("\t***       cat %d == %d ; seq = %d %d\n", cat_a, cat_b,
602 	    seq_a, seq_b);
603 	res = 0;
604 
605 have_res:
606 	return res;
607 }
608 
609 /*
610  * Remove all spool files and temporaries from the spooling area.
611  * Or, perhaps:
612  * Remove incomplete jobs from spooling area.
613  */
614 
615 void
616 clean_gi(int argc, char *argv[])
617 {
618 
619 	/* init some fields before 'clean' is called for each queue */
620 	cln_queuecnt = 0;
621 	cln_now = time(NULL);
622 	cln_minage = 3600.0;		/* only delete files >1h old */
623 	cln_filecnt = 0;
624 	cln_sizecnt = 0;
625 	cln_debug = 0;
626 	cln_testonly = 0;
627 	generic_wrapup = &wrapup_clean;
628 
629 	/* see if there are any options specified before the ptr list */
630 	for (; argc > 0; argc--, argv++) {
631 		if (**argv != '-')
632 			break;
633 		if (strcmp(*argv, "-d") == 0) {
634 			/* just an example of an option... */
635 			cln_debug++;
636 			*argv = generic_nullarg;	/* "erase" it */
637 		} else {
638 			printf("Invalid option '%s'\n", *argv);
639 			generic_initerr = 1;
640 		}
641 	}
642 }
643 
644 void
645 tclean_gi(int argc, char *argv[])
646 {
647 
648 	/* only difference between 'clean' and 'tclean' is one value */
649 	/* (...and the fact that 'clean' is priv and 'tclean' is not) */
650 	clean_gi(argc, argv);
651 	cln_testonly = 1;
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 	PRIV_START
681 	nitems = scandir(pp->spool_dir, &queue, doselect, sortq);
682 	PRIV_END
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 	PRIV_START
793 	res = lstat(name, &stbuf);
794 	PRIV_END
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 		PRIV_START
817 		res = readlink(name, linkbuf, sizeof(linkbuf));
818 		PRIV_END
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 		PRIV_START
839 		res = unlink(name);
840 		PRIV_END
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 	PRIV_START
981 	startok = startdaemon(pp);
982 	PRIV_END
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 	struct stat stbuf;
1007 	int not_shown;
1008 	char lf[MAXPATHLEN];
1009 
1010 	lock_file_name(pp, lf, sizeof lf);
1011 	printf("%s:\n", pp->printer);
1012 
1013 	upstat(pp, generic_msg, 1);
1014 
1015 	/*
1016 	 * Warn the user if 'lpq' will not display this new status-message.
1017 	 * Note that if lock file does not exist, then the queue is enabled
1018 	 * for both queuing and printing.
1019 	 */
1020 	not_shown = 1;
1021 	if (stat(lf, &stbuf) >= 0) {
1022 		if (stbuf.st_mode & LFM_PRINT_DIS)
1023 			not_shown = 0;
1024 	}
1025 	if (not_shown) {
1026 		printf("\tnote: This queue currently has printing enabled,\n");
1027 		printf("\t    so this -msg will only be shown by 'lpq' if\n");
1028 		printf("\t    a job is actively printing on it.\n");
1029 	}
1030 }
1031 
1032 /*
1033  * Enable printing on the specified printer and startup the daemon.
1034  */
1035 void
1036 start_q(struct printer *pp)
1037 {
1038 	int setres, startok;
1039 	char lf[MAXPATHLEN];
1040 
1041 	lock_file_name(pp, lf, sizeof lf);
1042 	printf("%s:\n", pp->printer);
1043 
1044 	setres = set_qstate(SQS_STARTP, lf);
1045 
1046 	PRIV_START
1047 	startok = startdaemon(pp);
1048 	PRIV_END
1049 	if (!startok)
1050 		printf("\tcouldn't start daemon\n");
1051 	else
1052 		printf("\tdaemon started\n");
1053 	PRIV_END
1054 }
1055 
1056 /*
1057  * Print the status of the printer queue.
1058  */
1059 void
1060 status(struct printer *pp)
1061 {
1062 	struct stat stbuf;
1063 	register int fd, i;
1064 	register struct dirent *dp;
1065 	DIR *dirp;
1066 	char file[MAXPATHLEN];
1067 
1068 	printf("%s:\n", pp->printer);
1069 	lock_file_name(pp, file, sizeof file);
1070 	if (stat(file, &stbuf) >= 0) {
1071 		printf("\tqueuing is %s\n",
1072 		       ((stbuf.st_mode & LFM_QUEUE_DIS) ? "disabled"
1073 			: "enabled"));
1074 		printf("\tprinting is %s\n",
1075 		       ((stbuf.st_mode & LFM_PRINT_DIS) ? "disabled"
1076 			: "enabled"));
1077 	} else {
1078 		printf("\tqueuing is enabled\n");
1079 		printf("\tprinting is enabled\n");
1080 	}
1081 	if ((dirp = opendir(pp->spool_dir)) == NULL) {
1082 		printf("\tcannot examine spool directory\n");
1083 		return;
1084 	}
1085 	i = 0;
1086 	while ((dp = readdir(dirp)) != NULL) {
1087 		if (*dp->d_name == 'c' && dp->d_name[1] == 'f')
1088 			i++;
1089 	}
1090 	closedir(dirp);
1091 	if (i == 0)
1092 		printf("\tno entries in spool area\n");
1093 	else if (i == 1)
1094 		printf("\t1 entry in spool area\n");
1095 	else
1096 		printf("\t%d entries in spool area\n", i);
1097 	fd = open(file, O_RDONLY);
1098 	if (fd < 0 || flock(fd, LOCK_SH|LOCK_NB) == 0) {
1099 		(void) close(fd);	/* unlocks as well */
1100 		printf("\tprinter idle\n");
1101 		return;
1102 	}
1103 	(void) close(fd);
1104 	/* print out the contents of the status file, if it exists */
1105 	status_file_name(pp, file, sizeof file);
1106 	fd = open(file, O_RDONLY|O_SHLOCK);
1107 	if (fd >= 0) {
1108 		(void) fstat(fd, &stbuf);
1109 		if (stbuf.st_size > 0) {
1110 			putchar('\t');
1111 			while ((i = read(fd, line, sizeof(line))) > 0)
1112 				(void) fwrite(line, 1, i, stdout);
1113 		}
1114 		(void) close(fd);	/* unlocks as well */
1115 	}
1116 }
1117 
1118 /*
1119  * Stop the specified daemon after completing the current job and disable
1120  * printing.
1121  */
1122 void
1123 stop_q(struct printer *pp)
1124 {
1125 	int setres;
1126 	char lf[MAXPATHLEN];
1127 
1128 	lock_file_name(pp, lf, sizeof lf);
1129 	printf("%s:\n", pp->printer);
1130 
1131 	setres = set_qstate(SQS_STOPP, lf);
1132 
1133 	if (setres >= 0)
1134 		upstat(pp, "printing disabled\n", 0);
1135 }
1136 
1137 struct	jobqueue **queue;
1138 int	nitems;
1139 time_t	mtime;
1140 
1141 /*
1142  * Put the specified jobs at the top of printer queue.
1143  */
1144 void
1145 topq(int argc, char *argv[])
1146 {
1147 	register int i;
1148 	struct stat stbuf;
1149 	int cmdstatus, changed;
1150 	struct printer myprinter, *pp = &myprinter;
1151 
1152 	if (argc < 3) {
1153 		printf("usage: topq printer [jobnum ...] [user ...]\n");
1154 		return;
1155 	}
1156 
1157 	--argc;
1158 	++argv;
1159 	init_printer(pp);
1160 	cmdstatus = getprintcap(*argv, pp);
1161 	switch(cmdstatus) {
1162 	default:
1163 		fatal(pp, "%s", pcaperr(cmdstatus));
1164 	case PCAPERR_NOTFOUND:
1165 		printf("unknown printer %s\n", *argv);
1166 		return;
1167 	case PCAPERR_TCOPEN:
1168 		printf("warning: %s: unresolved tc= reference(s)", *argv);
1169 		break;
1170 	case PCAPERR_SUCCESS:
1171 		break;
1172 	}
1173 	printf("%s:\n", pp->printer);
1174 
1175 	PRIV_START
1176 	if (chdir(pp->spool_dir) < 0) {
1177 		printf("\tcannot chdir to %s\n", pp->spool_dir);
1178 		goto out;
1179 	}
1180 	PRIV_END
1181 	nitems = getq(pp, &queue);
1182 	if (nitems == 0)
1183 		return;
1184 	changed = 0;
1185 	mtime = queue[0]->job_time;
1186 	for (i = argc; --i; ) {
1187 		if (doarg(argv[i]) == 0) {
1188 			printf("\tjob %s is not in the queue\n", argv[i]);
1189 			continue;
1190 		} else
1191 			changed++;
1192 	}
1193 	for (i = 0; i < nitems; i++)
1194 		free(queue[i]);
1195 	free(queue);
1196 	if (!changed) {
1197 		printf("\tqueue order unchanged\n");
1198 		return;
1199 	}
1200 	/*
1201 	 * Turn on the public execute bit of the lock file to
1202 	 * get lpd to rebuild the queue after the current job.
1203 	 */
1204 	PRIV_START
1205 	if (changed && stat(pp->lock_file, &stbuf) >= 0)
1206 		(void) chmod(pp->lock_file, stbuf.st_mode | LFM_RESET_QUE);
1207 
1208 out:
1209 	PRIV_END
1210 }
1211 
1212 /*
1213  * Reposition the job by changing the modification time of
1214  * the control file.
1215  */
1216 static int
1217 touch(struct jobqueue *jq)
1218 {
1219 	struct timeval tvp[2];
1220 	int ret;
1221 
1222 	tvp[0].tv_sec = tvp[1].tv_sec = --mtime;
1223 	tvp[0].tv_usec = tvp[1].tv_usec = 0;
1224 	PRIV_START
1225 	ret = utimes(jq->job_cfname, tvp);
1226 	PRIV_END
1227 	return (ret);
1228 }
1229 
1230 /*
1231  * Checks if specified job name is in the printer's queue.
1232  * Returns:  negative (-1) if argument name is not in the queue.
1233  */
1234 static int
1235 doarg(char *job)
1236 {
1237 	register struct jobqueue **qq;
1238 	register int jobnum, n;
1239 	register char *cp, *machine;
1240 	int cnt = 0;
1241 	FILE *fp;
1242 
1243 	/*
1244 	 * Look for a job item consisting of system name, colon, number
1245 	 * (example: ucbarpa:114)
1246 	 */
1247 	if ((cp = strchr(job, ':')) != NULL) {
1248 		machine = job;
1249 		*cp++ = '\0';
1250 		job = cp;
1251 	} else
1252 		machine = NULL;
1253 
1254 	/*
1255 	 * Check for job specified by number (example: 112 or 235ucbarpa).
1256 	 */
1257 	if (isdigit(*job)) {
1258 		jobnum = 0;
1259 		do
1260 			jobnum = jobnum * 10 + (*job++ - '0');
1261 		while (isdigit(*job));
1262 		for (qq = queue + nitems; --qq >= queue; ) {
1263 			n = 0;
1264 			for (cp = (*qq)->job_cfname+3; isdigit(*cp); )
1265 				n = n * 10 + (*cp++ - '0');
1266 			if (jobnum != n)
1267 				continue;
1268 			if (*job && strcmp(job, cp) != 0)
1269 				continue;
1270 			if (machine != NULL && strcmp(machine, cp) != 0)
1271 				continue;
1272 			if (touch(*qq) == 0) {
1273 				printf("\tmoved %s\n", (*qq)->job_cfname);
1274 				cnt++;
1275 			}
1276 		}
1277 		return(cnt);
1278 	}
1279 	/*
1280 	 * Process item consisting of owner's name (example: henry).
1281 	 */
1282 	for (qq = queue + nitems; --qq >= queue; ) {
1283 		PRIV_START
1284 		fp = fopen((*qq)->job_cfname, "r");
1285 		PRIV_END
1286 		if (fp == NULL)
1287 			continue;
1288 		while (get_line(fp) > 0)
1289 			if (line[0] == 'P')
1290 				break;
1291 		(void) fclose(fp);
1292 		if (line[0] != 'P' || strcmp(job, line+1) != 0)
1293 			continue;
1294 		if (touch(*qq) == 0) {
1295 			printf("\tmoved %s\n", (*qq)->job_cfname);
1296 			cnt++;
1297 		}
1298 	}
1299 	return(cnt);
1300 }
1301 
1302 /*
1303  * Enable both queuing & printing, and start printer (undo `down').
1304  */
1305 void
1306 up_q(struct printer *pp)
1307 {
1308 	int setres, startok;
1309 	char lf[MAXPATHLEN];
1310 
1311 	lock_file_name(pp, lf, sizeof lf);
1312 	printf("%s:\n", pp->printer);
1313 
1314 	setres = set_qstate(SQS_ENABLEQ+SQS_STARTP, lf);
1315 
1316 	PRIV_START
1317 	startok = startdaemon(pp);
1318 	PRIV_END
1319 	if (!startok)
1320 		printf("\tcouldn't start daemon\n");
1321 	else
1322 		printf("\tdaemon started\n");
1323 }
1324