2 * SPDX-License-Identifier: BSD-2-Clause
4 * ------+---------+---------+---------+---------+---------+---------+---------*
5 * Copyright (c) 2002,2011 - Garance Alistair Drosehn <gad@FreeBSD.org>.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * The views and conclusions contained in the software and documentation
30 * are those of the authors and should not be interpreted as representing
31 * official policies, either expressed or implied, of the FreeBSD Project
34 * ------+---------+---------+---------+---------+---------+---------+---------*
37 #include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
38 __FBSDID("$FreeBSD$");
41 * movejobs.c - The lpc commands which move jobs around.
45 #include <sys/param.h>
46 #include <sys/queue.h>
49 #include <dirent.h> /* for MAXNAMLEN, for job_cfname in lp.h! */
59 #include "matchjobs.h"
61 #define DEBUG_PARSEJS 0 /* set to 1 when testing */
62 #define DEBUG_SCANJS 0 /* set to 1 when testing */
64 static int match_jobspec(struct jobqueue *_jq, struct jobspec *_jspec);
67 * isdigit is defined to work on an 'int', in the range 0 to 255, plus EOF.
68 * Define a wrapper which can take 'char', either signed or unsigned.
70 #define isdigitch(Anychar) isdigit(((int) Anychar) & 255)
73 * Format a single jobspec into a string fit for printing.
76 format_jobspec(struct jobspec *jspec, int fmt_wanted)
78 char rangestr[40], buildstr[200];
79 const char fromuser[] = "from user ";
80 const char fromhost[] = "from host ";
84 * If the struct already has a fmtstring, then release it
85 * before building a new one.
87 if (jspec->fmtoutput != NULL) {
88 free(jspec->fmtoutput);
89 jspec->fmtoutput = NULL;
92 jspec->pluralfmt = 1; /* assume a "plural result" */
94 if (jspec->startnum >= 0) {
95 if (jspec->startnum != jspec->endrange)
96 snprintf(rangestr, sizeof(rangestr), "%ld-%ld",
97 jspec->startnum, jspec->endrange);
100 snprintf(rangestr, sizeof(rangestr), "%ld",
105 strsize = sizeof(buildstr);
107 switch (fmt_wanted) {
109 /* Build everything but the hostname in a temp string. */
110 if (jspec->wanteduser != NULL)
111 strlcat(buildstr, jspec->wanteduser, strsize);
112 if (rangestr[0] != '\0') {
113 if (buildstr[0] != '\0')
114 strlcat(buildstr, ":", strsize);
115 strlcat(buildstr, rangestr, strsize);
117 if (jspec->wantedhost != NULL)
118 strlcat(buildstr, "@", strsize);
120 /* Get space for the final result, including hostname */
121 strsize = strlen(buildstr) + 1;
122 if (jspec->wantedhost != NULL)
123 strsize += strlen(jspec->wantedhost);
124 jspec->fmtoutput = malloc(strsize);
126 /* Put together the final result */
127 strlcpy(jspec->fmtoutput, buildstr, strsize);
128 if (jspec->wantedhost != NULL)
129 strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
134 /* Build everything but the hostname in a temp string. */
135 strlcat(buildstr, rangestr, strsize);
136 if (jspec->wanteduser != NULL) {
137 if (rangestr[0] != '\0')
138 strlcat(buildstr, " ", strsize);
139 strlcat(buildstr, fromuser, strsize);
140 strlcat(buildstr, jspec->wanteduser, strsize);
142 if (jspec->wantedhost != NULL) {
143 if (jspec->wanteduser == NULL) {
144 if (rangestr[0] != '\0')
145 strlcat(buildstr, " ", strsize);
146 strlcat(buildstr, fromhost, strsize);
148 strlcat(buildstr, "@", strsize);
151 /* Get space for the final result, including hostname */
152 strsize = strlen(buildstr) + 1;
153 if (jspec->wantedhost != NULL)
154 strsize += strlen(jspec->wantedhost);
155 jspec->fmtoutput = malloc(strsize);
157 /* Put together the final result */
158 strlcpy(jspec->fmtoutput, buildstr, strsize);
159 if (jspec->wantedhost != NULL)
160 strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
166 * Free all the jobspec-related information.
169 free_jobspec(struct jobspec_hdr *js_hdr)
171 struct jobspec *jsinf;
173 while (!STAILQ_EMPTY(js_hdr)) {
174 jsinf = STAILQ_FIRST(js_hdr);
175 STAILQ_REMOVE_HEAD(js_hdr, nextjs);
176 if (jsinf->fmtoutput)
177 free(jsinf->fmtoutput);
178 if (jsinf->matcheduser)
179 free(jsinf->matcheduser);
185 * This routine takes a string as typed in from the user, and parses it
186 * into a job-specification. A job specification would match one or more
187 * jobs in the queue of some single printer (the specification itself does
188 * not indicate which queue should be searched).
190 * This recognizes a job-number range by itself (all digits, or a range
191 * indicated by "digits-digits"), or a userid by itself. If a `:' is
192 * found, it is treated as a separator between a job-number range and
193 * a userid, where the job number range is the side which has a digit as
194 * the first character. If an `@' is found, everything to the right of
195 * it is treated as the hostname the job originated from.
197 * So, the user can specify:
198 * jobrange userid userid:jobrange jobrange:userid
199 * jobrange@hostname jobrange:userid@hostname
200 * userid@hostname userid:jobrange@hostname
202 * XXX - it would be nice to add "not options" too, such as ^user,
203 * ^jobrange, and @^hostname.
205 * This routine may modify the original input string if that input is
206 * valid. If the input was *not* valid, then this routine should return
207 * with the input string the same as when the routine was called.
210 parse_jobspec(char *jobstr, struct jobspec_hdr *js_hdr)
212 struct jobspec *jsinfo;
213 char *atsign, *colon, *lhside, *numstr, *period, *rhside;
217 printf("\t [ pjs-input = %s ]\n", jobstr);
220 if ((jobstr == NULL) || (*jobstr == '\0'))
223 jsinfo = malloc(sizeof(struct jobspec));
224 memset(jsinfo, 0, sizeof(struct jobspec));
225 jsinfo->startnum = jsinfo->endrange = -1;
227 /* Find the separator characters, and nullify them. */
229 atsign = strchr(jobstr, '@');
230 colon = strchr(jobstr, ':');
236 /* The at-sign always indicates a hostname. */
237 if (atsign != NULL) {
240 jsinfo->wantedhost = rhside;
243 /* Finish splitting the input into three parts. */
255 * If there is a `:' here, then it's either jobrange:userid,
256 * userid:jobrange, or (if @hostname was not given) perhaps it
257 * might be hostname:jobnum. The side which has a digit as the
258 * first character is assumed to be the jobrange. It is an
259 * input error if both sides start with a digit, or if neither
260 * side starts with a digit.
262 if ((lhside != NULL) && (rhside != NULL)) {
263 if (isdigitch(*lhside)) {
264 if (isdigitch(*rhside))
267 jsinfo->wanteduser = rhside;
268 } else if (isdigitch(*rhside)) {
271 * The original implementation of 'lpc topq' accepted
272 * hostname:jobnum. If the input did not include a
273 * @hostname, then assume the userid is a hostname if
276 period = strchr(lhside, '.');
277 if ((atsign == NULL) && (period != NULL))
278 jsinfo->wantedhost = lhside;
280 jsinfo->wanteduser = lhside;
282 /* Neither side is a job number = user error */
285 } else if (lhside != NULL) {
286 if (isdigitch(*lhside))
289 jsinfo->wanteduser = lhside;
290 } else if (rhside != NULL) {
291 if (isdigitch(*rhside))
294 jsinfo->wanteduser = rhside;
298 * Break down the numstr. It should be all digits, or a range
299 * specified as "\d+-\d+".
301 if (numstr != NULL) {
303 jobnum = strtol(numstr, &numstr, 10);
304 if (errno != 0) /* error in conversion */
306 if (jobnum < 0) /* a bogus value for this purpose */
308 if (jobnum > 99999) /* too large for job number */
310 jsinfo->startnum = jsinfo->endrange = jobnum;
312 /* Check for a range of numbers */
313 if ((*numstr == '-') && (isdigitch(*(numstr + 1)))) {
316 jobnum = strtol(numstr, &numstr, 10);
317 if (errno != 0) /* error in conversion */
319 if (jobnum < jsinfo->startnum)
321 if (jobnum > 99999) /* too large for job number */
323 jsinfo->endrange = jobnum;
327 * If there is anything left in the numstr, and if the
328 * original string did not include a userid or a hostname,
329 * then this might be the ancient form of '\d+hostname'
330 * (with no separator between jobnum and hostname). Accept
331 * that for backwards compatibility, but otherwise any
332 * remaining characters mean a user-error. Note that the
333 * ancient form accepted only a single number, but this
334 * will also accept a range of numbers.
336 if (*numstr != '\0') {
339 if (jsinfo->wantedhost != NULL)
341 if (jsinfo->wanteduser != NULL)
343 /* Treat as the rest of the string as a hostname */
344 jsinfo->wantedhost = numstr;
348 if ((jsinfo->startnum < 0) && (jsinfo->wanteduser == NULL) &&
349 (jsinfo->wantedhost == NULL))
353 * The input was valid, in the sense that it could be parsed
354 * into the individual parts. Add this jobspec to the list
357 STAILQ_INSERT_TAIL(js_hdr, jsinfo, nextjs);
360 printf("\t [ will check for");
361 if (jsinfo->startnum >= 0) {
362 if (jsinfo->startnum == jsinfo->endrange)
363 printf(" jobnum = %ld", jsinfo->startnum);
365 printf(" jobrange = %ld to %ld", jsinfo->startnum,
370 if ((jsinfo->wanteduser != NULL) || (jsinfo->wantedhost != NULL)) {
372 if (jsinfo->wanteduser != NULL)
373 printf(" user = %s", jsinfo->wanteduser);
374 if (jsinfo->wantedhost != NULL)
375 printf(" host = %s", jsinfo->wantedhost);
384 * Restore any `@' and `:', in case the calling routine wants to
385 * write an error message which includes the input string.
397 * Check to see if a given job (specified by a jobqueue entry) matches
398 * all of the specifications in a given jobspec.
400 * Returns 0 if no match, 1 if the job does match.
403 match_jobspec(struct jobqueue *jq, struct jobspec *jspec)
405 struct cjobinfo *cfinf;
406 const char *cf_hoststr;
410 printf("\t [ match-js checking %s ]\n", jq->job_cfname);
413 if (jspec == NULL || jq == NULL)
417 * Keep track of which jobs have already been matched by this
418 * routine, and thus (probably) already processed.
423 jnum = calc_jobnum(jq->job_cfname, &cf_hoststr);
425 match = 0; /* assume the job will not match */
426 jspec->matcheduser = NULL;
429 * Check the job-number range.
431 if (jspec->startnum >= 0) {
432 if (jnum < jspec->startnum)
434 if (jnum > jspec->endrange)
439 * Check the hostname. Strictly speaking this should be done by
440 * reading the control file, but it is less expensive to check
441 * the hostname-part of the control file name. Also, this value
442 * can be easily seen in 'lpq -l', while there is no easy way for
443 * a user/operator to see the hostname in the control file.
445 if (jspec->wantedhost != NULL) {
446 if (fnmatch(jspec->wantedhost, cf_hoststr, 0) != 0)
451 * Check for a match on the user name. This has to be done
452 * by reading the control file.
454 if (jspec->wanteduser != NULL) {
455 cfinf = ctl_readcf("fakeq", jq->job_cfname);
458 if (fnmatch(jspec->wanteduser, cfinf->cji_acctuser, 0) != 0)
462 /* This job matches all of the specified criteria. */
464 jq->job_matched = 1; /* avoid matching the job twice */
466 if (jspec->wanteduser != NULL) {
468 * If the user specified a userid (which may have been a
469 * pattern), then the caller's "doentry()" routine might
470 * want to know the userid of this job that matched.
472 jspec->matcheduser = strdup(cfinf->cji_acctuser);
475 printf("\t [ job matched! ]\n");
485 * Scan a queue for all jobs which match a jobspec. The queue is scanned
486 * from top to bottom.
488 * The caller can provide a routine which will be executed for each job
489 * that does match. Note that the processing routine might do anything
490 * to the matched job -- including the removal of it.
492 * This returns the number of jobs which were matched.
495 scanq_jobspec(int qcount, struct jobqueue **squeue, int sopts, struct
496 jobspec_hdr *js_hdr, process_jqe doentry, void *doentryinfo)
498 struct jobqueue **qent;
499 struct jobspec *jspec;
500 int cnt, matched, total;
507 /* The caller must specify one of the scanning orders */
508 if ((sopts & (SCQ_JSORDER|SCQ_QORDER)) == 0)
512 if (sopts & SCQ_JSORDER) {
514 * For each job specification, scan through the queue
515 * looking for every job that matches.
517 STAILQ_FOREACH(jspec, js_hdr, nextjs) {
518 for (qent = squeue, cnt = 0; cnt < qcount;
520 matched = match_jobspec(*qent, jspec);
525 doentry(doentryinfo, *qent, jspec);
526 if (jspec->matcheduser != NULL) {
527 free(jspec->matcheduser);
528 jspec->matcheduser = NULL;
532 * The entire queue has been scanned for this
533 * jobspec. Call the user's routine again with
534 * a NULL queue-entry, so it can print out any
535 * kind of per-jobspec summary.
538 doentry(doentryinfo, NULL, jspec);
542 * For each job in the queue, check all of the job
543 * specifications to see if any one of them matches
546 for (qent = squeue, cnt = 0; cnt < qcount;
548 STAILQ_FOREACH(jspec, js_hdr, nextjs) {
549 matched = match_jobspec(*qent, jspec);
554 doentry(doentryinfo, *qent, jspec);
555 if (jspec->matcheduser != NULL) {
556 free(jspec->matcheduser);
557 jspec->matcheduser = NULL;
560 * Once there is a match, then there is no
561 * point in checking this same job against
562 * all the other jobspec's.