Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 18 Jun 2001 20:53:23 -0400
From:      Garance A Drosihn <drosih@rpi.edu>
To:        freebsd-print@bostonradio.org
Cc:        freebsd-audit@freebsd.org
Subject:   Patch: smarter 'lpc clean', new 'lpc tclean'
Message-ID:  <p05100e12b754415778b5@[128.113.24.47]>

next in thread | raw e-mail | index | archive | help
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




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?p05100e12b754415778b5>