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