From owner-freebsd-audit Mon Jun 18 17:53:50 2001 Delivered-To: freebsd-audit@freebsd.org Received: from mail.rpi.edu (mail.rpi.edu [128.113.22.40]) by hub.freebsd.org (Postfix) with ESMTP id 6043537B401 for ; Mon, 18 Jun 2001 17:53:26 -0700 (PDT) (envelope-from drosih@rpi.edu) Received: from [128.113.24.47] (gilead.acs.rpi.edu [128.113.24.47]) by mail.rpi.edu (8.11.3/8.11.3) with ESMTP id f5J0rPx60162; Mon, 18 Jun 2001 20:53:25 -0400 Mime-Version: 1.0 X-Sender: drosih@mail.rpi.edu Message-Id: Date: Mon, 18 Jun 2001 20:53:23 -0400 To: freebsd-print@bostonradio.org From: Garance A Drosihn Subject: Patch: smarter 'lpc clean', new 'lpc tclean' Cc: freebsd-audit@freebsd.org Content-Type: text/plain; charset="us-ascii" ; format="flowed" Sender: owner-freebsd-audit@FreeBSD.ORG Precedence: bulk List-ID: List-Archive: (Web Archive) List-Help: (List Instructions) List-Subscribe: List-Unsubscribe: X-Loop: FreeBSD.ORG Main goals of this update to lpc (part of lpr & friends): 1) make 'lpc clean' somewhat safer 2) add an 'lpc tclean', which allows one to see what files would be removed *if* an 'lpc clean' is done. Note that 'lpc tclean' is not a privileged command, since it won't actually remove anything. 3) have 'lpc clean' and 'lpc tclean' also look for core files in spool directories. 4) make 'lpc clean' somewhat prettier and more informative. 5) get rid of some more compile-type warnings when BDECFLAGS is used (mainly by fixing up cmdtab.c). 6) as part of creating 'lpc clean' from 'lpc tclean', make it possible for "generic printer" routines to have an initialization routine which can set initial values of global variables, or even do some processing of command-line options. It can also set a "wrap-up" routine, which could be used to print summary information. This is cross-posted to freebsd-audit since the 'lpc' command is setgid daemon. (and at least at RPI, it's often run via 'sudo'). It wouldn't hurt to be a little extra paranoid while eyeing these changes... I haven't done any man-page changes yet. Some discussion: 'lpc' assumes that any control file (cfA*) will have the same hostname embedded in the filename as is embedded in the matching datafile names. This is often, but not always true. It is not true if lpd receives a job via an IP address where that IP address does not have the same hostname as what the original lpr-host uses for it's hostname. (think hosts with multiple ethernet cards, for instance). In those situations, it is possible for 'clean' to remove perfectly-valid jobs which are waiting in some queue. This adds an extra safety check, in that all files to be removed will also have to be at least one hour old. You're still able to remove perfectly-valid jobs, at least you'll only remove jobs which have been stuck in the queue for an hour. (I might increase this to two or four hours...). I hope to *really* fix this problem with other changes, sometime in "the indefinite future". More importantly, this patch provides the new 'lpc tclean' command, which will tell the user what files WOULD be removed if an 'lpc clean' command were done. Also, sometimes print-filters (or lpd itself) will die, and leave behind a multi-megabyte core file in some spool directory. As long as 'lpc clean' is looking at all files in all spool directories, have it also look for these core files. The 'lpc clean' commands also now print out a summary line saying how many queues were checked, how many files were removed (or "would be" removed, for tclean), and how much disk space is involved. For the benefit of those who have many print queues, 'lpc clean all' will only print out the names of print queues where some interesting files were found, instead of printing out a header-line for every queue in your printcap file. I'm thinking it would be good to move all the routines associated with 'clean' and 'tclean' into a separate source file. I could do that as part of this change if people thought it were reasonable, or I could do it as some later, separate update. I'd also like to move 'status'-related routines into a separate source file, and maybe the 'generic()' itself into a separate file. I'd probably leave all other currently-existing routines in lpc/cmds.c, but I also have at least one more new command in mind, and it seems like somewhere along the line cmds.c would be better if broken up. I guess the clever option-parsing ability for generic-printer commands seems a little odd, but it's meant to allow for a lot of flexibility without having to rewrite all the generic routines at the same time. At RPI, 'lpc status' also has some option-processing changes, and it's put to much more interesting use in that case. This patch has no source-files in common with my pending patch to lpd, so the patches can be committed to current in any order. That's probably more detail than anyone wanted... In any case, here's the patch: Index: lpc/cmds.c =================================================================== RCS file: /home/ncvs/src/usr.sbin/lpr/lpc/cmds.c,v retrieving revision 1.16 diff -u -r1.16 cmds.c --- lpc/cmds.c 2001/06/12 16:38:17 1.16 +++ lpc/cmds.c 2001/06/19 00:17:58 @@ -79,22 +79,78 @@ static int touch(struct jobqueue *_jq); static void unlinkf(char *_name); static void upstat(struct printer *_pp, const char *_msg); +static void wrapup_clean(int _laststatus); /* * generic framework for commands which operate on all or a specified * set of printers */ +enum qsel_val { /* how a given ptr was selected */ + QSEL_UNKNOWN = -1, /* ... not selected yet */ + QSEL_BYNAME = 0, /* ... user specifed it by name */ + QSEL_ALL = 1 /* ... user wants "all" printers */ + /* (with more to come) */ +}; + +static enum qsel_val generic_qselect; /* indicates how ptr was selected */ +static int generic_initerr; /* result of initrtn processing */ +static char *generic_nullarg; +static void (*generic_wrapup)(int _last_status); /* perform rtn wrap-up */ + void -generic(void (*specificrtn)(struct printer *_pp), int argc, char *argv[]) +generic(void (*specificrtn)(struct printer *_pp), + void (*initrtn)(int _argc, char *_argv[]), int argc, char *argv[]) { - int cmdstatus, more; - struct printer myprinter, *pp = &myprinter; + int cmdstatus, more, targc; + struct printer myprinter, *pp; + char **targv; if (argc == 1) { printf("Usage: %s {all | printer ...}\n", argv[0]); return; } - if (argc == 2 && strcmp(argv[1], "all") == 0) { + + /* + * The initialization routine for a command might set a generic + * "wrapup" routine, which should be called after processing all + * the printers in the command. This might print summary info. + * + * Note that the initialization routine may also parse (and + * nullify) some of the parameters given on the command, leaving + * only the parameters which have to do with printer names. + */ + pp = &myprinter; + generic_wrapup = NULL; + generic_qselect = QSEL_UNKNOWN; + cmdstatus = 0; + /* this just needs to be a distinct value of type 'char *' */ + if (generic_nullarg == NULL) + generic_nullarg = strdup(""); + + /* call initialization routine, if there is one for this cmd */ + if (initrtn != NULL) { + generic_initerr = 0; + (*initrtn)(argc, argv); + if (generic_initerr) + return; + /* skip any initial arguments null-ified by initrtn */ + targc = argc; + targv = argv; + while (--targc) { + if (targv[1] != generic_nullarg) + break; + ++targv; + } + if (targv != argv) { + targv[0] = argv[0]; /* copy the command-name */ + argv = targv; + argc = targc + 1; + } + } + + if (generic_qselect == QSEL_UNKNOWN && argc == 2 && strcmp(argv[1], + "all") == 0) { + generic_qselect = QSEL_ALL; more = firstprinter(pp, &cmdstatus); if (cmdstatus) goto looperr; @@ -115,10 +171,14 @@ } } while (more && cmdstatus); } - return; + goto wrapup; } + + generic_qselect = QSEL_BYNAME; /* specifically-named ptrs */ while (--argc) { ++argv; + if (*argv == generic_nullarg) + continue; init_printer(pp); cmdstatus = getprintcap(*argv, pp); switch (cmdstatus) { @@ -136,6 +196,12 @@ } (*specificrtn)(pp); } + +wrapup: + if (generic_wrapup) { + (*generic_wrapup)(cmdstatus); + } + } /* @@ -236,14 +302,34 @@ (void) close(fd); } +/* + * "global" variables for all the routines related to 'clean' and 'tclean' + */ +static time_t cln_now; /* current time */ +static double cln_minage; /* minimum age before file is removed */ +static long cln_sizecnt; /* amount of space freed up */ +static int cln_debug; /* print extra debugging msgs */ +static int cln_filecnt; /* number of files destroyed */ +static int cln_foundcore; /* found a core file! */ +static int cln_queuecnt; /* number of queues checked */ +static int cln_testonly; /* remove-files vs just-print-info */ + static int doselect(struct dirent *d) { int c = d->d_name[0]; if ((c == 't' || c == 'c' || c == 'd') && d->d_name[1] == 'f') - return(1); - return(0); + return 1; + if (c == 'c') { + if (!strcmp(d->d_name, "core")) + cln_foundcore = 1; + } + if (c == 'e') { + if (!strncmp(d->d_name, "errs.", 5)) + return 1; + } + return 0; } /* @@ -276,15 +362,66 @@ * Or, perhaps: * Remove incomplete jobs from spooling area. */ + +void +init_clean(int argc, char *argv[]) +{ + + /* init some fields before 'clean' is called for each queue */ + cln_queuecnt = 0; + cln_now = time(NULL); + cln_minage = 3600.0; /* only delete files >1h old */ + cln_filecnt = 0; + cln_sizecnt = 0; + cln_debug = 0; + cln_testonly = 0; + generic_wrapup = &wrapup_clean; + + /* see if there are any options specified before the ptr list */ + while (--argc) { + ++argv; + if (**argv != '-') + break; + if (strcmp(*argv, "-d") == 0) { + /* just an example of an option... */ + cln_debug = 1; + *argv = generic_nullarg; /* "erase" it */ + } else { + printf("Invalid option '%s'\n", *argv); + generic_initerr = 1; + } + } + + return; +} + void -clean(struct printer *pp) +init_tclean(int argc, char *argv[]) { - register int i, n; - register char *cp, *cp1, *lp; + + /* only difference between 'clean' and 'tclean' is one value */ + /* (...and the fact that 'clean' is priv and 'tclean' is not) */ + init_clean(argc, argv); + cln_testonly = 1; + + return; +} + +void +clean_q(struct printer *pp) +{ + char *cp, *cp1, *lp; struct dirent **queue; - int nitems; + size_t linerem; + int didhead, i, n, nitems; - printf("%s:\n", pp->printer); + cln_queuecnt++; + + didhead = 0; + if (generic_qselect == QSEL_BYNAME) { + printf("%s:\n", pp->printer); + didhead = 1; + } lp = line; cp = pp->spool_dir; @@ -293,20 +430,45 @@ break; } lp[-1] = '/'; + linerem = sizeof(line) - (lp - line); + cln_foundcore = 0; seteuid(euid); nitems = scandir(pp->spool_dir, &queue, doselect, sortq); seteuid(uid); if (nitems < 0) { + if (!didhead) { + printf("%s:\n", pp->printer); + didhead = 1; + } printf("\tcannot examine spool directory\n"); return; } + if (cln_foundcore) { + if (!didhead) { + printf("%s:\n", pp->printer); + didhead = 1; + } + printf("\t** found a core file in %s !\n", pp->spool_dir); + } if (nitems == 0) return; + if (!didhead) + printf("%s:\n", pp->printer); i = 0; do { cp = queue[i]->d_name; if (*cp == 'c') { + /* + * A control file. Look for matching data-files. + */ + /* XXX + * Note the logic here assumes that the hostname + * part of cf-filenames match the hostname part + * in df-filenames, and that is not necessarily + * true (eg: for multi-homed hosts). This needs + * some further thought... + */ n = 0; while (i + 1 < nitems) { cp1 = queue[i + 1]->d_name; @@ -316,32 +478,104 @@ n++; } if (n == 0) { - strncpy(lp, cp, sizeof(line) - strlen(line) - 1); - line[sizeof(line) - 1] = '\0'; + if (strlen(cp) >= linerem) + goto lineoflo; + strlcpy(lp, cp, linerem); unlinkf(line); } + } else if (*cp == 'e') { + /* + * Must be an errrs or email temp file. + */ + if (strlen(cp) >= linerem) + goto lineoflo; + strlcpy(lp, cp, linerem); + unlinkf(line); } else { /* * Must be a df with no cf (otherwise, it would have * been skipped above) or a tf file (which can always - * be removed). + * be removed if it's old enough). */ - strncpy(lp, cp, sizeof(line) - strlen(line) - 1); - line[sizeof(line) - 1] = '\0'; + /* XXX + * Note that this code really should check to see if + * a df file is a symbolic link to some other file, + * and if '-r' was specified on the lpr command for + * it. Of course, it's currently impossible to find + * out about any '-r' now that the cf file is gone... + */ + if (strlen(cp) >= linerem) + goto lineoflo; + strlcpy(lp, cp, linerem); unlinkf(line); } } while (++i < nitems); + return; + +lineoflo: + printf("\t** internal error: 'line' buffer overflow!"); + return; } + +static void +wrapup_clean(int laststatus __unused) +{ + + printf("Checked %d queues, and ", cln_queuecnt); + if (cln_filecnt < 1) { + printf("no cruft was found\n"); + return; + } + if (cln_testonly) { + printf("would have "); + } + printf("removed %d files (%ld bytes).\n", cln_filecnt, cln_sizecnt); +} static void unlinkf(char *name) { + struct stat stbuf; + double agemod, agestat; + int res; + + /* XXX + * Probably should use lstat instead of stat, but that opens up + * the issue of what SHOULD be done when removing a df* file + * which is a symbolic link... + */ seteuid(euid); - if (unlink(name) < 0) - printf("\tcannot remove %s\n", name); - else - printf("\tremoved %s\n", name); + res = stat(name, &stbuf); seteuid(uid); + if (res < 0) { + printf("\terror return from stat(%s):\n", name); + printf("\t %s\n", strerror(errno)); + return; + } + + agemod = difftime(cln_now, stbuf.st_mtime); + agestat = difftime(cln_now, stbuf.st_ctime); + if (cln_debug) { + printf("\t\t modify age=%g secs, stat age=%g secs\n", + agemod, agestat); + } + if ((agemod <= cln_minage) && (agestat <= cln_minage)) + return; + + cln_filecnt++; + cln_sizecnt += stbuf.st_size; + + if (cln_testonly) { + printf("\twould remove %s\n", name); + } else { + seteuid(euid); + res = unlink(name); + seteuid(uid); + if (res < 0) + printf("\tcannot remove %s (!)\n", name); + else + printf("\tremoved %s\n", name); + } } /* Index: lpc/cmdtab.c =================================================================== RCS file: /home/ncvs/src/usr.sbin/lpr/lpc/cmdtab.c,v retrieving revision 1.3 diff -u -r1.3 cmdtab.c --- lpc/cmdtab.c 1999/08/28 01:16:51 1.3 +++ lpc/cmdtab.c 2001/06/19 00:17:58 @@ -58,26 +58,30 @@ char starthelp[] = "enable printing and start a spooling daemon"; char statushelp[] = "show status of daemon and queue"; char stophelp[] = "stop a spooling daemon after current job completes and disable printing"; +char tcleanhelp[] = "test to see what files a clean cmd would remove"; char topqhelp[] = "put job at top of printer queue"; char uphelp[] = "enable everything and restart spooling daemon"; +#define PR 1 /* a privileged command */ + struct cmd cmdtab[] = { - { "abort", aborthelp, 0, 1, doabort }, - { "clean", cleanhelp, 0, 1, clean }, - { "enable", enablehelp, 0, 1, enable }, - { "exit", quithelp, quit, 0 }, - { "disable", disablehelp, 0, 1, disable }, - { "down", downhelp, down, 1 }, - { "help", helphelp, help, 0 }, - { "quit", quithelp, quit, 0 }, - { "restart", restarthelp, 0, 0, restart }, - { "start", starthelp, 0, 1, startcmd }, - { "status", statushelp, 0, 0, status }, - { "stop", stophelp, 0, 1, stop }, - { "topq", topqhelp, topq, 1 }, - { "up", uphelp, 0, 1, up }, - { "?", helphelp, help, 0 }, - { 0 }, + { "abort", aborthelp, PR, 0, doabort }, + { "clean", cleanhelp, PR, init_clean, clean_q }, + { "enable", enablehelp, PR, 0, enable }, + { "exit", quithelp, 0, quit, 0 }, + { "disable", disablehelp, PR, 0, disable }, + { "down", downhelp, PR, down, 0 }, + { "help", helphelp, 0, help, 0 }, + { "quit", quithelp, 0, quit, 0 }, + { "restart", restarthelp, 0, 0, restart }, + { "start", starthelp, PR, 0, startcmd }, + { "status", statushelp, 0, 0, status }, + { "stop", stophelp, PR, 0, stop }, + { "tclean", tcleanhelp, 0, init_tclean, clean_q }, + { "topq", topqhelp, PR, topq, 0 }, + { "up", uphelp, PR, 0, up }, + { "?", helphelp, 0, help, 0 }, + { 0, 0, 0, 0, 0}, }; int NCMDS = sizeof (cmdtab) / sizeof (cmdtab[0]); Index: lpc/extern.h =================================================================== RCS file: /home/ncvs/src/usr.sbin/lpr/lpc/extern.h,v retrieving revision 1.4 diff -u -r1.4 extern.h --- lpc/extern.h 2001/06/12 16:38:17 1.4 +++ lpc/extern.h 2001/06/19 00:17:58 @@ -42,14 +42,17 @@ __BEGIN_DECLS -void clean(struct printer *_pp); +void clean_q(struct printer *_pp); void disable(struct printer *_pp); void doabort(struct printer *_pp); void down(int _argc, char *_argv[]); void enable(struct printer *_pp); -void generic(void (*_specificrtn)(struct printer *_pp), int _argc, - char *_argv[]); +void generic(void (*_specificrtn)(struct printer *_pp), + void (*_initcmd)(int _argc, char *_argv[]), + int _argc, char *_argv[]); void help(int _argc, char *_argv[]); +void init_clean(int _argc, char *_argv[]); +void init_tclean(int _argc, char *_argv[]); void quit(int _argc, char *_argv[]); void restart(struct printer *_pp); void startcmd(struct printer *_pp); Index: lpc/lpc.c =================================================================== RCS file: /home/ncvs/src/usr.sbin/lpr/lpc/lpc.c,v retrieving revision 1.18 diff -u -r1.18 lpc.c --- lpc/lpc.c 2001/06/15 16:28:35 1.18 +++ lpc/lpc.c 2001/06/19 00:17:58 @@ -115,7 +115,7 @@ exit(1); } if (c->c_generic != 0) - generic(c->c_generic, argc, argv); + generic(c->c_generic, c->c_handler, argc, argv); else (*c->c_handler)(argc, argv); exit(0); @@ -200,8 +200,16 @@ printf("?Privileged command\n"); continue; } + + /* + * Two different commands might have the same generic rtn + * (eg: "clean" and "tclean"), and just use different + * handler routines for distinct command-setup. The handler + * routine might also be set on a generic routine for + * initial parameter processing. + */ if (c->c_generic != 0) - generic(c->c_generic, margc, margv); + generic(c->c_generic, c->c_handler, margc, margv); else (*c->c_handler)(margc, margv); } Index: lpc/lpc.h =================================================================== RCS file: /home/ncvs/src/usr.sbin/lpr/lpc/lpc.h,v retrieving revision 1.3 diff -u -r1.3 lpc.h --- lpc/lpc.h 2001/06/12 16:38:17 1.3 +++ lpc/lpc.h 2001/06/19 00:17:58 @@ -36,15 +36,17 @@ */ /* - * Line printer control program. + * Line Printer Control (lpc) program. */ struct printer; struct cmd { const char *c_name; /* command name */ const char *c_help; /* help message */ - /* routine to do the work */ + const int c_priv; /* privileged command */ + /* routine to do all the work for plain cmds, or + * initialization work for generic-printer cmds: */ void (*c_handler)(int, char *[]); - int c_priv; /* privileged command */ - void (*c_generic)(struct printer *); /* generic command */ + /* routine to do the work for generic-printer cmds: */ + void (*c_generic)(struct printer *); }; -- Garance Alistair Drosehn = gad@eclipse.acs.rpi.edu Senior Systems Programmer or gad@freebsd.org Rensselaer Polytechnic Institute or drosih@rpi.edu To Unsubscribe: send mail to majordomo@FreeBSD.org with "unsubscribe freebsd-audit" in the body of the message