Changes which rewrite 'lpc topq', and which add 'lpc bottomq'. These

reflect much valuable feedback from wollman.  More details on the new
'lpc topq' are in the log message for revision 1.2 of lpc/movejobs.c.

The previous implementation of 'lpc topq' is available as 'lpc xtopq',
in case there are any problems noticed in the new implementation.  If
there are no problems with this version, a later update will remove the
'lpc xtopq' command.

Reviewed by:	freebsd-print@bostonradio.org
MFC after:	6 days
This commit is contained in:
Garance A Drosehn 2002-07-17 00:51:19 +00:00
parent 689fee8724
commit dd8faa9ff2
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=100203
9 changed files with 1103 additions and 10 deletions

View File

@ -7,7 +7,7 @@
#
LIB= lpr
INTERNALLIB= YES
SRCS= common.c ctlinfo.c displayq.c net.c printcap.c request.c \
rmjob.c startdaemon.c
SRCS= common.c ctlinfo.c displayq.c matchjobs.c net.c \
printcap.c request.c rmjob.c startdaemon.c
.include <bsd.lib.mk>

View File

@ -0,0 +1,585 @@
/*
* ------+---------+---------+---------+---------+---------+---------+---------*
* Copyright (c) 2002 - Garance Alistair Drosehn <gad@FreeBSD.org>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation
* are those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of the FreeBSD Project
* or FreeBSD, Inc.
*
* ------+---------+---------+---------+---------+---------+---------+---------*
*/
#ifndef lint
static const char rcsid[] =
"$FreeBSD$";
#endif /* not lint */
/*
* movejobs.c - The lpc commands which move jobs around.
*/
#include <sys/file.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/time.h>
#include <dirent.h> /* for MAXNAMLEN, for job_cfname in lp.h! */
#include <ctype.h>
#include <errno.h>
#include <fnmatch.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "ctlinfo.h"
#include "lp.h"
#include "matchjobs.h"
#define DEBUG_PARSEJS 0 /* set to 1 when testing */
#define DEBUG_SCANJS 0 /* set to 1 when testing */
static int match_jobspec(struct jobqueue *_jq, struct jobspec *_jspec);
/*
* isdigit is defined to work on an 'int', in the range 0 to 255, plus EOF.
* Define a wrapper which can take 'char', either signed or unsigned.
*/
#define isdigitch(Anychar) isdigit(((int) Anychar) & 255)
/*
* Format a single jobspec into a string fit for printing.
*/
void
format_jobspec(struct jobspec *jspec, int fmt_wanted)
{
char rangestr[40], buildstr[200];
const char fromuser[] = "from user ";
const char fromhost[] = "from host ";
size_t strsize;
/*
* If the struct already has a fmtstring, then release it
* before building a new one.
*/
if (jspec->fmtoutput != NULL) {
free(jspec->fmtoutput);
jspec->fmtoutput = NULL;
}
jspec->pluralfmt = 1; /* assume a "plural result" */
rangestr[0] = '\0';
if (jspec->startnum >= 0) {
if (jspec->startnum != jspec->endrange)
snprintf(rangestr, sizeof(rangestr), "%ld-%ld",
jspec->startnum, jspec->endrange);
else {
jspec->pluralfmt = 0;
snprintf(rangestr, sizeof(rangestr), "%ld",
jspec->startnum);
}
}
strsize = sizeof(buildstr);
buildstr[0] = '\0';
switch (fmt_wanted) {
case FMTJS_TERSE:
/* Build everything but the hostname in a temp string. */
if (jspec->wanteduser != NULL)
strlcat(buildstr, jspec->wanteduser, strsize);
if (rangestr[0] != '\0') {
if (buildstr[0] != '\0')
strlcat(buildstr, ":", strsize);
strlcat(buildstr, rangestr, strsize);
}
if (jspec->wantedhost != NULL)
strlcat(buildstr, "@", strsize);
/* Get space for the final result, including hostname */
strsize = strlen(buildstr) + 1;
if (jspec->wantedhost != NULL)
strsize += strlen(jspec->wantedhost);
jspec->fmtoutput = malloc(strsize);
/* Put together the final result */
strlcpy(jspec->fmtoutput, buildstr, strsize);
if (jspec->wantedhost != NULL)
strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
break;
case FMTJS_VERBOSE:
default:
/* Build everything but the hostname in a temp string. */
strlcat(buildstr, rangestr, strsize);
if (jspec->wanteduser != NULL) {
if (rangestr[0] != '\0')
strlcat(buildstr, " ", strsize);
strlcat(buildstr, fromuser, strsize);
strlcat(buildstr, jspec->wanteduser, strsize);
}
if (jspec->wantedhost != NULL) {
if (jspec->wanteduser == NULL) {
if (rangestr[0] != '\0')
strlcat(buildstr, " ", strsize);
strlcat(buildstr, fromhost, strsize);
} else
strlcat(buildstr, "@", strsize);
}
/* Get space for the final result, including hostname */
strsize = strlen(buildstr) + 1;
if (jspec->wantedhost != NULL)
strsize += strlen(jspec->wantedhost);
jspec->fmtoutput = malloc(strsize);
/* Put together the final result */
strlcpy(jspec->fmtoutput, buildstr, strsize);
if (jspec->wantedhost != NULL)
strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
break;
}
}
/*
* Free all the jobspec-related information.
*/
void
free_jobspec(struct jobspec_hdr *js_hdr)
{
struct jobspec *jsinf;
while (!STAILQ_EMPTY(js_hdr)) {
jsinf = STAILQ_FIRST(js_hdr);
STAILQ_REMOVE_HEAD(js_hdr, nextjs);
if (jsinf->fmtoutput)
free(jsinf->fmtoutput);
if (jsinf->matcheduser)
free(jsinf->matcheduser);
free(jsinf);
}
}
/*
* This routine takes a string as typed in from the user, and parses it
* into a job-specification. A job specification would match one or more
* jobs in the queue of some single printer (the specification itself does
* not indicate which queue should be searched).
*
* This recognizes a job-number range by itself (all digits, or a range
* indicated by "digits-digits"), or a userid by itself. If a `:' is
* found, it is treated as a separator between a job-number range and
* a userid, where the job number range is the side which has a digit as
* the first character. If an `@' is found, everything to the right of
* it is treated as the hostname the job originated from.
*
* So, the user can specify:
* jobrange userid userid:jobrange jobrange:userid
* jobrange@hostname jobrange:userid@hostname
* userid@hostname userid:jobrange@hostname
*
* XXX - it would be nice to add "not options" too, such as ^user,
* ^jobrange, and @^hostname.
*
* This routine may modify the original input string if that input is
* valid. If the input was *not* valid, then this routine should return
* with the input string the same as when the routine was called.
*/
int
parse_jobspec(char *jobstr, struct jobspec_hdr *js_hdr)
{
struct jobspec *jsinfo;
char *atsign, *colon, *lhside, *numstr, *period, *rhside;
int jobnum;
#if DEBUG_PARSEJS
printf("\t [ pjs-input = %s ]\n", jobstr);
#endif
if ((jobstr == NULL) || (*jobstr == '\0'))
return (0);
jsinfo = malloc(sizeof(struct jobspec));
memset(jsinfo, 0, sizeof(struct jobspec));
jsinfo->startnum = jsinfo->endrange = -1;
/* Find the separator characters, and nullify them. */
numstr = NULL;
atsign = strchr(jobstr, '@');
colon = strchr(jobstr, ':');
if (atsign != NULL)
*atsign = '\0';
if (colon != NULL)
*colon = '\0';
/* The at-sign always indicates a hostname. */
if (atsign != NULL) {
rhside = atsign + 1;
if (*rhside != '\0')
jsinfo->wantedhost = rhside;
}
/* Finish splitting the input into three parts. */
rhside = NULL;
if (colon != NULL) {
rhside = colon + 1;
if (*rhside == '\0')
rhside = NULL;
}
lhside = NULL;
if (*jobstr != '\0')
lhside = jobstr;
/*
* If there is a `:' here, then it's either jobrange:userid,
* userid:jobrange, or (if @hostname was not given) perhaps it
* might be hostname:jobnum. The side which has a digit as the
* first character is assumed to be the jobrange. It is an
* input error if both sides start with a digit, or if neither
* side starts with a digit.
*/
if ((lhside != NULL) && (rhside != NULL)) {
if (isdigitch(*lhside)) {
if (isdigitch(*rhside))
goto bad_input;
numstr = lhside;
jsinfo->wanteduser = rhside;
} else if (isdigitch(*rhside)) {
numstr = rhside;
/*
* The original implementation of 'lpc topq' accepted
* hostname:jobnum. If the input did not include a
* @hostname, then assume the userid is a hostname if
* it includes a '.'.
*/
period = strchr(lhside, '.');
if ((atsign == NULL) && (period != NULL))
jsinfo->wantedhost = lhside;
else
jsinfo->wanteduser = lhside;
} else {
/* Neither side is a job number = user error */
goto bad_input;
}
} else if (lhside != NULL) {
if (isdigitch(*lhside))
numstr = lhside;
else
jsinfo->wanteduser = lhside;
} else if (rhside != NULL) {
if (isdigitch(*rhside))
numstr = rhside;
else
jsinfo->wanteduser = rhside;
}
/*
* Break down the numstr. It should be all digits, or a range
* specified as "\d+-\d+".
*/
if (numstr != NULL) {
errno = 0;
jobnum = strtol(numstr, &numstr, 10);
if (errno != 0) /* error in conversion */
goto bad_input;
if (jobnum < 0) /* a bogus value for this purpose */
goto bad_input;
if (jobnum > 99999) /* too large for job number */
goto bad_input;
jsinfo->startnum = jsinfo->endrange = jobnum;
/* Check for a range of numbers */
if ((*numstr == '-') && (isdigitch(*(numstr + 1)))) {
numstr++;
errno = 0;
jobnum = strtol(numstr, &numstr, 10);
if (errno != 0) /* error in conversion */
goto bad_input;
if (jobnum < jsinfo->startnum)
goto bad_input;
if (jobnum > 99999) /* too large for job number */
goto bad_input;
jsinfo->endrange = jobnum;
}
/*
* If there is anything left in the numstr, and if the
* original string did not include a userid or a hostname,
* then this might be the ancient form of '\d+hostname'
* (with no seperator between jobnum and hostname). Accept
* that for backwards compatibility, but otherwise any
* remaining characters mean a user-error. Note that the
* ancient form accepted only a single number, but this
* will also accept a range of numbers.
*/
if (*numstr != '\0') {
if (atsign != NULL)
goto bad_input;
if (jsinfo->wantedhost != NULL)
goto bad_input;
if (jsinfo->wanteduser != NULL)
goto bad_input;
/* Treat as the rest of the string as a hostname */
jsinfo->wantedhost = numstr;
}
}
if ((jsinfo->startnum < 0) && (jsinfo->wanteduser == NULL) &&
(jsinfo->wantedhost == NULL))
goto bad_input;
/*
* The input was valid, in the sense that it could be parsed
* into the individual parts. Add this jobspec to the list
* of jobspecs.
*/
STAILQ_INSERT_TAIL(js_hdr, jsinfo, nextjs);
#if DEBUG_PARSEJS
printf("\t [ will check for");
if (jsinfo->startnum >= 0) {
if (jsinfo->startnum == jsinfo->endrange)
printf(" jobnum = %ld", jsinfo->startnum);
else
printf(" jobrange = %ld to %ld", jsinfo->startnum,
jsinfo->endrange);
} else {
printf(" jobs");
}
if ((jsinfo->wanteduser != NULL) || (jsinfo->wantedhost != NULL)) {
printf(" from");
if (jsinfo->wanteduser != NULL)
printf(" user = %s", jsinfo->wanteduser);
if (jsinfo->wantedhost != NULL)
printf(" host = %s", jsinfo->wantedhost);
}
printf("]\n");
#endif
return (1);
bad_input:
/*
* Restore any `@' and `:', in case the calling routine wants to
* write an error message which includes the input string.
*/
if (atsign != NULL)
*atsign = '@';
if (colon != NULL)
*colon = ':';
if (jsinfo != NULL)
free(jsinfo);
return (0);
}
/*
* Check to see if a given job (specified by a jobqueue entry) matches
* all of the specifications in a given jobspec.
*
* Returns 0 if no match, 1 if the job does match.
*/
static int
match_jobspec(struct jobqueue *jq, struct jobspec *jspec)
{
struct cjobinfo *cfinf;
char *cp, *cf_numstr, *cf_hoststr;
int jnum, match;
#if DEBUG_SCANJS
printf("\t [ match-js checking %s ]\n", jq->job_cfname);
#endif
if (jspec == NULL || jq == NULL)
return (0);
/*
* Keep track of which jobs have already been matched by this
* routine, and thus (probably) already processed.
*/
if (jq->job_matched)
return (0);
/*
* The standard `cf' file has the job number start in position 4,
* but some implementations have that as an extra file-sequence
* letter, and start the job number in position 5. The job
* number is usually three bytes, but may be as many as five.
*
* XXX - All this nonsense should really be handled in a single
* place, like getq()...
*/
cf_numstr = jq->job_cfname + 3;
if (!isdigitch(*cf_numstr))
cf_numstr++;
jnum = 0;
for (cp = cf_numstr; (cp < cf_numstr + 5) && isdigitch(*cp); cp++)
jnum = jnum * 10 + (*cp - '0');
cf_hoststr = cp;
cfinf = NULL;
match = 0; /* assume the job will not match */
jspec->matcheduser = NULL;
/*
* Check the job-number range.
*/
if (jspec->startnum >= 0) {
if (jnum < jspec->startnum)
goto nomatch;
if (jnum > jspec->endrange)
goto nomatch;
}
/*
* Check the hostname. Strictly speaking this should be done by
* reading the control file, but it is less expensive to check
* the hostname-part of the control file name. Also, this value
* can be easily seen in 'lpq -l', while there is no easy way for
* a user/operator to see the hostname in the control file.
*/
if (jspec->wantedhost != NULL) {
if (fnmatch(jspec->wantedhost, cf_hoststr, 0) != 0)
goto nomatch;
}
/*
* Check for a match on the user name. This has to be done
* by reading the control file.
*/
if (jspec->wanteduser != NULL) {
cfinf = ctl_readcf("fakeq", jq->job_cfname);
if (cfinf == NULL)
goto nomatch;
if (fnmatch(jspec->wanteduser, cfinf->cji_username, 0) != 0)
goto nomatch;
}
/* This job matches all of the specified criteria. */
match = 1;
jq->job_matched = 1; /* avoid matching the job twice */
jspec->matchcnt++;
if (jspec->wanteduser != NULL) {
/*
* If the user specified a userid (which may have been a
* pattern), then the caller's "doentry()" routine might
* want to know the userid of this job that matched.
*/
jspec->matcheduser = strdup(cfinf->cji_username);
}
#if DEBUG_SCANJS
printf("\t [ job matched! ]\n");
#endif
nomatch:
if (cfinf != NULL)
ctl_freeinf(cfinf);
return (match);
}
/*
* Scan a queue for all jobs which match a jobspec. The queue is scanned
* from top to bottom.
*
* The caller can provide a routine which will be executed for each job
* that does match. Note that the processing routine might do anything
* to the matched job -- including the removal of it.
*
* This returns the number of jobs which were matched.
*/
int
scanq_jobspec(int qcount, struct jobqueue **squeue, int sopts, struct
jobspec_hdr *js_hdr, process_jqe doentry, void *doentryinfo)
{
struct jobqueue **qent;
struct jobspec *jspec;
int cnt, matched, total;
if (qcount < 1)
return (0);
if (js_hdr == NULL)
return (-1);
/* The caller must specify one of the scanning orders */
if ((sopts & (SCQ_JSORDER|SCQ_QORDER)) == 0)
return (-1);
total = 0;
if (sopts & SCQ_JSORDER) {
/*
* For each job specification, scan through the queue
* looking for every job that matches.
*/
STAILQ_FOREACH(jspec, js_hdr, nextjs) {
for (qent = squeue, cnt = 0; cnt < qcount;
qent++, cnt++) {
matched = match_jobspec(*qent, jspec);
if (!matched)
continue;
total++;
if (doentry != NULL)
doentry(doentryinfo, *qent, jspec);
if (jspec->matcheduser != NULL) {
free(jspec->matcheduser);
jspec->matcheduser = NULL;
}
}
/*
* The entire queue has been scanned for this
* jobspec. Call the user's routine again with
* a NULL queue-entry, so it can print out any
* kind of per-jobspec summary.
*/
if (doentry != NULL)
doentry(doentryinfo, NULL, jspec);
}
} else {
/*
* For each job in the queue, check all of the job
* specifications to see if any one of them matches
* that job.
*/
for (qent = squeue, cnt = 0; cnt < qcount;
qent++, cnt++) {
STAILQ_FOREACH(jspec, js_hdr, nextjs) {
matched = match_jobspec(*qent, jspec);
if (!matched)
continue;
total++;
if (doentry != NULL)
doentry(doentryinfo, *qent, jspec);
if (jspec->matcheduser != NULL) {
free(jspec->matcheduser);
jspec->matcheduser = NULL;
}
/*
* Once there is a match, then there is no
* point in checking this same job against
* all the other jobspec's.
*/
break;
}
}
}
return (total);
}

View File

@ -0,0 +1,101 @@
/*
* ------+---------+---------+---------+---------+---------+---------+---------*
* Copyright (c) 2002 - Garance Alistair Drosehn <gad@FreeBSD.org>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation
* are those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of the FreeBSD Project
* or FreeBSD, Inc.
*
* ------+---------+---------+---------+---------+---------+---------+---------*
* $FreeBSD$
* ------+---------+---------+---------+---------+---------+---------+---------*
*/
#include <sys/queue.h>
/*
* The "matcheduser" field is *only* valid during the call to the
* given "doentry()" routine, and is only set if the specification
* included a userid.
*/
struct jobspec {
STAILQ_ENTRY(jobspec) nextjs;
char *wantedhost;
char *wanteduser;
char *matcheduser; /* only valid for "doentry()" */
char *fmtoutput; /* set by format_jobspec() */
long startnum;
long endrange;
int pluralfmt; /* boolean set by format_jobspec() */
uint matchcnt;
};
STAILQ_HEAD(jobspec_hdr, jobspec);
/*
* Format options for format_jobspec.
*/
#define FMTJS_TERSE 1 /* user:jobrange@host */
#define FMTJS_VERBOSE 2 /* jobrange from user@host */
/*
* Options for scanq_jobspec.
*
* The caller must choose the order that entries should be scanned:
* 1) JSORDER: Matched jobs are processed (by calling the "doentry()"
* routine) in the order that the user specified those jobs.
* 2) QORDER: Matched jobs are processed in the order that the jobs are
* listed the queue. This guarantees that the "doentry()" routine
* will be called only once per job.
*
* There is a "job_matched" variable in struct jobqueue, which is used
* to make sure that the "doentry()" will only be called once for any
* given job in JSORDER processing. The "doentry()" routine can turn
* that off, if it does want to be called multiple times when the job
* is matched by multiple specifiers.
*
* The JSORDER processing will also call the "doentry()" routine once
* after each scan of the queue, with the jobqueue set to null. This
* provides a way for the caller to print out a summary message for
* each jobspec that was given.
*/
#define SCQ_JSORDER 0x0001 /* follow the user-specified order */
#define SCQ_QORDER 0x0002 /* the order of jobs in the queue */
#include <sys/cdefs.h>
__BEGIN_DECLS
struct jobqueue;
typedef int process_jqe(void *_myinfo, struct jobqueue *_jq,
struct jobspec *_jspec);
void format_jobspec(struct jobspec *_jspec, int _fmt_wanted);
void free_jobspec(struct jobspec_hdr *_js_hdr);
int scanq_jobspec(int _qitems, struct jobqueue **_squeue, int _sopts,
struct jobspec_hdr *_js_hdr, process_jqe _doentry,
void *_doentryinfo);
int parse_jobspec(char *_jobstr, struct jobspec_hdr *_js_hdr);
__END_DECLS

View File

@ -5,7 +5,7 @@
PROG= lpc
MAN= lpc.8
SRCS= lpc.c cmds.c cmdtab.c
SRCS= lpc.c cmds.c cmdtab.c movejobs.c
BINGRP= daemon
BINMODE= 2555

View File

@ -48,6 +48,7 @@ static const char rcsid[] =
* lpc -- command tables
*/
char aborthelp[] = "terminate a spooling daemon immediately and disable printing";
char botmqhelp[] = "move job(s) to the bottom of printer queue";
char cleanhelp[] = "remove cruft files from a queue";
char enablehelp[] = "turn a spooling queue on";
char disablehelp[] = "turn a spooling queue off";
@ -61,7 +62,7 @@ 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 topqhelp[] = "move job(s) to the top of printer queue";
char uphelp[] = "enable everything and restart spooling daemon";
/* Use some abbreviations so entries won't need to wrap */
@ -70,6 +71,7 @@ char uphelp[] = "enable everything and restart spooling daemon";
struct cmd cmdtab[] = {
{ "abort", aborthelp, PR, 0, abort_q },
{ "bottomq", botmqhelp, PR, bottomq_cmd, 0 },
{ "clean", cleanhelp, PR, clean_gi, clean_q },
{ "enable", enablehelp, PR, 0, enable_q },
{ "exit", quithelp, 0, quit, 0 },
@ -83,9 +85,10 @@ struct cmd cmdtab[] = {
{ "setstatus", setstatushelp, PR|M, setstatus_gi, setstatus_q },
{ "stop", stophelp, PR, 0, stop_q },
{ "tclean", tcleanhelp, 0, tclean_gi, clean_q },
{ "topq", topqhelp, PR, topq, 0 },
{ "topq", topqhelp, PR, topq_cmd, 0 },
{ "up", uphelp, PR, 0, up_q },
{ "?", helphelp, 0, help, 0 },
{ "xtopq", topqhelp, PR, topq, 0 },
{ 0, 0, 0, 0, 0},
};

View File

@ -40,9 +40,15 @@
#include <sys/types.h>
#include <sys/cdefs.h>
/*
* Options for setup_myprinter().
*/
#define SUMP_NOHEADER 0x0001 /* Do not print a header line */
#define SUMP_CHDIR_SD 0x0002 /* chdir into the spool directory */
__BEGIN_DECLS
void abort_q(struct printer *_pp);
void bottomq_cmd(int _argc, char *_argv[]);
void clean_gi(int _argc, char *_argv[]);
void clean_q(struct printer *_pp);
void disable_q(struct printer *_pp);
@ -61,8 +67,13 @@ void start_q(struct printer *_pp);
void status(struct printer *_pp);
void stop_q(struct printer *_pp);
void tclean_gi(int _argc, char *_argv[]);
void topq(int _argc, char *_argv[]);
void topq_cmd(int _argc, char *_argv[]);
void up_q(struct printer *_pp);
void topq(int _argc, char *_argv[]); /* X-version */
/* from lpc.c: */
struct printer *setup_myprinter(char *_pwanted, struct printer *_pp,
int _sump_opts);
__END_DECLS
extern int NCMDS;

View File

@ -32,7 +32,7 @@
.\" @(#)lpc.8 8.5 (Berkeley) 4/28/95
.\" $FreeBSD$
.\"
.Dd June 15, 2002
.Dd July 16, 2002
.Dt LPC 8
.Os
.Sh NAME
@ -92,6 +92,18 @@ then disable printing (preventing new daemons from being started by
.Xr lpr 1 )
for the specified printers.
.Pp
.It Ic bottomq Ar printer Xo
.Op Ar jobspec ...
.Xc
Take the specified jobs in the order specified and move them to the
bottom of the printer queue.
Each
.Ar jobspec
can match multiple print jobs.
The full description of a
.Ar jobspec
is given below.
.Pp
.It Ic clean Brq Cm all | Ar printer
Remove any temporary files, data files, and control files that cannot
be printed (i.e., do not form a complete printer job)
@ -188,16 +200,83 @@ command is a privileged command, while the
command is not restricted.
.Pp
.It Ic topq Ar printer Xo
.Op Ar jobnum ...
.Op Ar user ...
.Op Ar jobspec ...
.Xc
Place the jobs in the order listed at the top of the printer queue.
Take the specified jobs in the order specified and move them to the
top of the printer queue.
Each
.Ar jobspec
can match multiple print jobs.
The full description of a
.Ar jobspec
is given below.
.Pp
.It Ic up Brq Cm all | Ar printer
Enable everything and start a new printer daemon.
Undoes the effects of
.Ic down .
.El
.Pp
Commands such as
.Ic topq
and
.Ic bottomq
can take one or more
.Ar jobspec
to specify which jobs the command should operate on.
A
.Ar jobspec
can be:
.Bl -bullet
.It
a single job number, which will match all jobs in the printer's queue
which have the same job number. Eg:
.Ar 17 ,
.It
a range of job numbers, which will match all jobs with a number between
the starting and ending job numbers, inclusive. Eg:
.Ar 21-32 ,
.It
a specific userid, which will match all jobs which were sent by that
user. Eg:
.Ar jones ,
.It
a host name, when prefixed by an `@', which will match all jobs in
the queue which were sent from the given host. Eg:
.Ar @freebsd.org ,
.It
a job range and a userid, separated by a `:', which will match all jobs
which both match the job range and were sent by the specified user. Eg:
.Ar jones:17
or
.Ar 21-32:jones ,
.It
a job range and/or a userid, followed by a host name, which will match
all jobs which match all the specified criteria. Eg:
.Ar jones@freebsd.org
or
.Ar 21-32@freebsd.org
or
.Ar jones:17@freebsd.org .
.El
.Pp
The values for userid and host name can also include pattern-matching
characters, similar to the pattern matching done for filenames in
most command shells.
Note that if you enter a
.Ic topq
or
.Ic bottomq
command as parameters on the initial
.Nm
command, then the shell will expand any pattern-matching characters
that it can (based on what files in finds in the current directory)
before
.Nm
processes the command.
In that case, any parameters which include pattern-matching characters
should be enclosed in quotes, so that the shell will not try to
expand them.
.Sh FILES
.Bl -tag -width /var/spool/*/lockx -compact
.It Pa /etc/printcap

View File

@ -374,3 +374,45 @@ ingroup(const char *grname)
return(1);
return(0);
}
/*
* Routine to get the information for a single printer (which will be
* called by the routines which implement individual commands).
* Note: This is for commands operating on a *single* printer.
*/
struct printer *
setup_myprinter(char *pwanted, struct printer *pp, int sump_opts)
{
int cdres, cmdstatus;
init_printer(pp);
cmdstatus = getprintcap(pwanted, pp);
switch (cmdstatus) {
default:
fatal(pp, "%s", pcaperr(cmdstatus));
/* NOTREACHED */
case PCAPERR_NOTFOUND:
printf("unknown printer %s\n", pwanted);
return (NULL);
case PCAPERR_TCOPEN:
printf("warning: %s: unresolved tc= reference(s)", pwanted);
break;
case PCAPERR_SUCCESS:
break;
}
if ((sump_opts & SUMP_NOHEADER) == 0)
printf("%s:\n", pp->printer);
if (sump_opts & SUMP_CHDIR_SD) {
seteuid(euid);
cdres = chdir(pp->spool_dir);
seteuid(uid);
if (cdres < 0) {
printf("\tcannot chdir to %s\n", pp->spool_dir);
free_printer(pp);
return (NULL);
}
}
return (pp);
}

272
usr.sbin/lpr/lpc/movejobs.c Normal file
View File

@ -0,0 +1,272 @@
/*
* ------+---------+---------+---------+---------+---------+---------+---------*
* Copyright (c) 2002 - Garance Alistair Drosehn <gad@FreeBSD.org>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation
* are those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of the FreeBSD Project
* or FreeBSD, Inc.
*
* ------+---------+---------+---------+---------+---------+---------+---------*
*/
#ifndef lint
static const char rcsid[] =
"$FreeBSD$";
#endif /* not lint */
/*
* movejobs.c - The lpc commands which move jobs around.
*/
#include <sys/file.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <ctype.h>
#include <dirent.h> /* just for MAXNAMLEN, for job_cfname in lp.h! */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "lp.h"
#include "lpc.h"
#include "matchjobs.h"
#include "extern.h"
/* Values for origcmd in tqbq_common() */
#define IS_TOPQ 1
#define IS_BOTQ 2
static int process_jobs(int _argc, char *_argv[], process_jqe
_process_rtn, void *myinfo);
static process_jqe touch_jqe;
static void tqbq_common(int _argc, char *_argv[], int _origcmd);
/*
* isdigit is defined to work on an 'int', in the range 0 to 255, plus EOF.
* Define a wrapper which can take 'char', either signed or unsigned.
*/
#define isdigitch(Anychar) isdigit(((int) Anychar) & 255)
struct touchjqe_info { /* for topq/bottomq */
time_t newtime;
};
static int nitems;
static struct jobqueue **queue;
/*
* Process all the jobs, as specified by the user.
*/
static int
process_jobs(int argc, char *argv[], process_jqe process_rtn, void *myinfo)
{
struct jobspec_hdr jobs_wanted;
int i, matchcnt, pjres;
STAILQ_INIT(&jobs_wanted);
for (i = 0; i < argc; i++) {
pjres = parse_jobspec(argv[i], &jobs_wanted);
if (pjres == 0) {
printf("\tinvalid job specifier: %s\n", argv[i]);
continue;
}
}
matchcnt = scanq_jobspec(nitems, queue, SCQ_JSORDER, &jobs_wanted,
process_rtn, myinfo);
free_jobspec(&jobs_wanted);
return (matchcnt);
}
/*
* Reposition the job by changing the modification time of the
* control file.
*/
static int
touch_jqe(void *myinfo, struct jobqueue *jq, struct jobspec *jspec)
{
struct timeval tvp[2];
struct touchjqe_info *touch_info;
int ret;
/*
* If the entire queue has been scanned for the current jobspec,
* then let the user know if there were no jobs matched by that
* specification.
*/
if (jq == NULL) {
if (jspec->matchcnt == 0) {
format_jobspec(jspec, FMTJS_VERBOSE);
if (jspec->pluralfmt)
printf("\tjobs %s are not in the queue\n",
jspec->fmtoutput);
else
printf("\tjob %s is not in the queue\n",
jspec->fmtoutput);
}
return (1);
}
/*
* Do a little juggling with "matched" vs "processed", so a single
* job can be matched by multiple specifications, and yet it will
* be moved only once. This is so, eg, 'topq lp 7 7' will not
* complain "job 7 is not in queue" for the second specification.
*/
jq->job_matched = 0;
if (jq->job_processed) {
printf("\tmoved %s earlier\n", jq->job_cfname);
return (1);
}
jq->job_processed = 1;
touch_info = myinfo;
tvp[0].tv_sec = tvp[1].tv_sec = ++touch_info->newtime;
tvp[0].tv_usec = tvp[1].tv_usec = 0;
seteuid(euid);
ret = utimes(jq->job_cfname, tvp);
seteuid(uid);
if (ret == 0) {
if (jspec->matcheduser)
printf("\tmoved %s (user %s)\n", jq->job_cfname,
jspec->matcheduser);
else
printf("\tmoved %s\n", jq->job_cfname);
}
return (ret);
}
/*
* Put the specified jobs at the bottom of printer queue.
*/
void
bottomq_cmd(int argc, char *argv[])
{
if (argc < 3) {
printf("usage: bottomq printer [jobspec ...]\n");
return;
}
--argc; /* First argv was the command name */
++argv;
tqbq_common(argc, argv, IS_BOTQ);
}
/*
* Put the specified jobs at the top of printer queue.
*/
void
topq_cmd(int argc, char *argv[])
{
if (argc < 3) {
printf("usage: topq printer [jobspec ...]\n");
return;
}
--argc; /* First argv was the command name */
++argv;
tqbq_common(argc, argv, IS_TOPQ);
}
/*
* Processing in common between topq and bottomq commands.
*/
void
tqbq_common(int argc, char *argv[], int origcmd)
{
struct printer myprinter, *pp;
struct touchjqe_info touch_info;
int i, movecnt, setres;
pp = setup_myprinter(*argv, &myprinter, SUMP_CHDIR_SD);
if (pp == NULL)
return;
--argc; /* Second argv was the printer name */
++argv;
nitems = getq(pp, &queue);
if (nitems == 0) {
printf("\tthere are no jobs in the queue\n");
free_printer(pp);
return;
}
/*
* The only real difference between topq and bottomq is the
* initial value used for newtime.
*/
switch (origcmd) {
case IS_BOTQ:
/*
* When moving jobs to the bottom of the queue, pick a
* starting value which is one second after the last job
* in the queue.
*/
touch_info.newtime = queue[nitems - 1]->job_time + 1;
break;
case IS_TOPQ:
/*
* When moving jobs to the top of the queue, the greatest
* number of jobs which could be moved is all the jobs
* that are in the queue. Pick a starting value which
* leaves plenty of room for all existing jobs.
*/
touch_info.newtime = queue[0]->job_time - nitems - 5;
break;
default:
printf("\ninternal error in topq/bottomq processing.\n");
return;
}
movecnt = process_jobs(argc, argv, touch_jqe, &touch_info);
/*
* If any jobs were moved, then chmod the lock file to notify any
* active process for this queue that the queue has changed, so
* it will rescan the queue to find out the new job order.
*/
if (movecnt == 0)
printf("\tqueue order unchanged\n");
else {
setres = set_qstate(SQS_QCHANGED, pp->lock_file);
if (setres < 0)
printf("\t* queue order changed for %s, but the\n"
"\t* attempt to set_qstate() failed [%d]!\n",
pp->printer, setres);
}
for (i = 0; i < nitems; i++)
free(queue[i]);
free(queue);
free_printer(pp);
}