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