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