Make 'lpc clean' somewhat safer. Add an 'lpc tclean' command, which allows
one to see what files would be removed *if* an 'lpc clean' is done. 'tclean' will remove no files, and is therefore not a privileged command. Also, both 'lpc clean' and 'lpc tclean' will now look for 'core' files in spool directories (but not remove them). They also print out an extra line of info when a datafile to be removed is a symlink (from 'lpr -s'), saying what file it is a symlink to. 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. Reviewed by: freebsd-print@bostonradio.org freebsd-audit@FreeBSD.org MFC after: 2 weeks
This commit is contained in:
parent
6ddb63cad6
commit
004c9c5da2
@ -79,22 +79,77 @@ static void startpr(struct printer *_pp, int _chgenable);
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 (argc == 2 && strcmp(argv[1], "all") == 0) {
|
||||
generic_qselect = QSEL_ALL;
|
||||
more = firstprinter(pp, &cmdstatus);
|
||||
if (cmdstatus)
|
||||
goto looperr;
|
||||
@ -115,10 +170,14 @@ generic(void (*specificrtn)(struct printer *_pp), int argc, char *argv[])
|
||||
}
|
||||
} 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 +195,12 @@ generic(void (*specificrtn)(struct printer *_pp), int argc, char *argv[])
|
||||
}
|
||||
(*specificrtn)(pp);
|
||||
}
|
||||
|
||||
wrapup:
|
||||
if (generic_wrapup) {
|
||||
(*generic_wrapup)(cmdstatus);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
@ -236,14 +301,34 @@ upstat(struct printer *pp, const char *msg)
|
||||
(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 +361,66 @@ sortq(const void *a, const void *b)
|
||||
* Or, perhaps:
|
||||
* Remove incomplete jobs from spooling area.
|
||||
*/
|
||||
void
|
||||
clean(struct printer *pp)
|
||||
{
|
||||
register int i, n;
|
||||
register char *cp, *cp1, *lp;
|
||||
struct dirent **queue;
|
||||
int nitems;
|
||||
|
||||
printf("%s:\n", pp->printer);
|
||||
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
|
||||
init_tclean(int argc, char *argv[])
|
||||
{
|
||||
|
||||
/* 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;
|
||||
size_t linerem;
|
||||
int didhead, i, n, nitems, rmcp;
|
||||
|
||||
cln_queuecnt++;
|
||||
|
||||
didhead = 0;
|
||||
if (generic_qselect == QSEL_BYNAME) {
|
||||
printf("%s:\n", pp->printer);
|
||||
didhead = 1;
|
||||
}
|
||||
|
||||
lp = line;
|
||||
cp = pp->spool_dir;
|
||||
@ -293,20 +429,46 @@ clean(struct printer *pp)
|
||||
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;
|
||||
rmcp = 0;
|
||||
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,134 @@ clean(struct printer *pp)
|
||||
n++;
|
||||
}
|
||||
if (n == 0) {
|
||||
strncpy(lp, cp, sizeof(line) - strlen(line) - 1);
|
||||
line[sizeof(line) - 1] = '\0';
|
||||
unlinkf(line);
|
||||
rmcp = 1;
|
||||
}
|
||||
} else if (*cp == 'e') {
|
||||
/*
|
||||
* Must be an errrs or email temp file.
|
||||
*/
|
||||
rmcp = 1;
|
||||
} 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';
|
||||
rmcp = 1;
|
||||
}
|
||||
if (rmcp) {
|
||||
if (strlen(cp) >= linerem) {
|
||||
printf("\t** internal error: 'line' overflow!\n");
|
||||
printf("\t** spooldir = %s\n", pp->spool_dir);
|
||||
printf("\t** cp = %s\n", cp);
|
||||
return;
|
||||
}
|
||||
strlcpy(lp, cp, linerem);
|
||||
unlinkf(line);
|
||||
}
|
||||
} while (++i < nitems);
|
||||
}
|
||||
|
||||
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;
|
||||
char linkbuf[BUFSIZ];
|
||||
|
||||
/*
|
||||
* We have to use lstat() instead of stat(), in case this is a df*
|
||||
* "file" which is really a symlink due to 'lpr -s' processing. In
|
||||
* that case, we need to check the last-mod time of the symlink, and
|
||||
* not the file that the symlink is pointed at.
|
||||
*/
|
||||
seteuid(euid);
|
||||
if (unlink(name) < 0)
|
||||
printf("\tcannot remove %s\n", name);
|
||||
else
|
||||
printf("\tremoved %s\n", name);
|
||||
res = lstat(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) {
|
||||
/* this debugging-aid probably is not needed any more... */
|
||||
printf("\t\t modify age=%g secs, stat age=%g secs\n",
|
||||
agemod, agestat);
|
||||
}
|
||||
if ((agemod <= cln_minage) && (agestat <= cln_minage))
|
||||
return;
|
||||
|
||||
/*
|
||||
* if this file is a symlink, then find out the target of the
|
||||
* symlink before unlink-ing the file itself
|
||||
*/
|
||||
if (S_ISLNK(stbuf.st_mode)) {
|
||||
seteuid(euid);
|
||||
res = readlink(name, linkbuf, sizeof(linkbuf));
|
||||
seteuid(uid);
|
||||
if (res < 0) {
|
||||
printf("\terror return from readlink(%s):\n", name);
|
||||
printf("\t %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
if (res == sizeof(linkbuf))
|
||||
res--;
|
||||
linkbuf[res] = '\0';
|
||||
}
|
||||
|
||||
cln_filecnt++;
|
||||
cln_sizecnt += stbuf.st_size;
|
||||
|
||||
if (cln_testonly) {
|
||||
printf("\twould remove %s\n", name);
|
||||
if (S_ISLNK(stbuf.st_mode)) {
|
||||
printf("\t (which is a symlink to %s)\n", linkbuf);
|
||||
}
|
||||
} else {
|
||||
seteuid(euid);
|
||||
res = unlink(name);
|
||||
seteuid(uid);
|
||||
if (res < 0)
|
||||
printf("\tcannot remove %s (!)\n", name);
|
||||
else
|
||||
printf("\tremoved %s\n", name);
|
||||
/* XXX
|
||||
* Note that for a df* file, this code should also check to see
|
||||
* if it is a symlink to some other file, and if the original
|
||||
* lpr command included '-r' ("remove file"). Of course, this
|
||||
* code would not be removing the df* file unless there was no
|
||||
* matching cf* file, and without the cf* file it is currently
|
||||
* impossible to determine if '-r' had been specified...
|
||||
*
|
||||
* As a result of this quandry, we may be leaving behind a
|
||||
* user's file that was supposed to have been removed after
|
||||
* being printed. This may effect services such as CAP or
|
||||
* samba, if they were configured to use 'lpr -r', and if
|
||||
* datafiles are not being properly removed.
|
||||
*/
|
||||
if (S_ISLNK(stbuf.st_mode)) {
|
||||
printf("\t (which was a symlink to %s)\n", linkbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -58,26 +58,30 @@ char restarthelp[] = "kill (if possible) and restart a spooling daemon";
|
||||
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]);
|
||||
|
@ -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);
|
||||
|
@ -32,7 +32,7 @@
|
||||
.\" @(#)lpc.8 8.5 (Berkeley) 4/28/95
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd April 28, 1995
|
||||
.Dd June 20, 2001
|
||||
.Dt LPC 8
|
||||
.Os BSD 4.2
|
||||
.Sh NAME
|
||||
@ -96,6 +96,11 @@ for the specified printers.
|
||||
Remove any temporary files, data files, and control files that cannot
|
||||
be printed (i.e., do not form a complete printer job)
|
||||
from the specified printer queue(s) on the local machine.
|
||||
This command will also look for 'core' files in spool directory
|
||||
for each printer queue, and list any that are found.
|
||||
It will not remove any 'core' files. See also the
|
||||
.Ic tclean
|
||||
command.
|
||||
.Pp
|
||||
.It Ic disable Xo
|
||||
.Es \&{ \&}
|
||||
@ -165,6 +170,24 @@ Display the status of daemons and queues on the local machine.
|
||||
Stop a spooling daemon after the current job completes and disable
|
||||
printing.
|
||||
.Pp
|
||||
.It Ic tclean Xo
|
||||
.Es \&{ \&}
|
||||
.En Cm all | Ar printer
|
||||
.Xc
|
||||
This will do a test-run of the
|
||||
.Ic clean
|
||||
command.
|
||||
All the same checking is done, but the command will only print out
|
||||
messages saying what a similar
|
||||
.Ic clean
|
||||
command would do if the user typed it in.
|
||||
It will not remove any files.
|
||||
Note that the
|
||||
.Ic clean
|
||||
command is a privileged command, while the
|
||||
.Ic tclean
|
||||
command is not restricted.
|
||||
.Pp
|
||||
.It Ic topq Ar printer Xo
|
||||
.Op Ar jobnum ...
|
||||
.Op Ar user ...
|
||||
|
@ -115,7 +115,7 @@ main(int argc, char *argv[])
|
||||
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 @@ cmdscanner(void)
|
||||
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);
|
||||
}
|
||||
|
@ -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 *);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user