kernel - acpi - fix thermal thread loop
[dragonfly.git] / usr.sbin / lpr / common_source / matchjobs.c
blobd4ae03149bfcc32912f1e39cc9bc1a183b2ea93e
1 /*
2 * ------+---------+---------+---------+---------+---------+---------+---------*
3 * Copyright (c) 2002 - Garance Alistair Drosehn <gad@FreeBSD.org>.
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
27 * The views and conclusions contained in the software and documentation
28 * are those of the authors and should not be interpreted as representing
29 * official policies, either expressed or implied, of the FreeBSD Project
30 * or FreeBSD, Inc.
32 * ------+---------+---------+---------+---------+---------+---------+---------*
34 * $FreeBSD: src/usr.sbin/lpr/common_source/matchjobs.c,v 1.2.2.2 2002/08/15 18:53:17 schweikh Exp $
35 * $DragonFly: src/usr.sbin/lpr/common_source/matchjobs.c,v 1.3 2005/08/08 18:58:56 joerg Exp $
39 * movejobs.c - The lpc commands which move jobs around.
42 #include <sys/file.h>
43 #include <sys/param.h>
44 #include <sys/queue.h>
45 #include <sys/time.h>
47 #include <ctype.h>
48 #include <errno.h>
49 #include <fnmatch.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <unistd.h>
54 #include "ctlinfo.h"
55 #include "lp.h"
56 #include "matchjobs.h"
58 #define DEBUG_PARSEJS 0 /* set to 1 when testing */
59 #define DEBUG_SCANJS 0 /* set to 1 when testing */
61 static int match_jobspec(struct jobqueue *_jq, struct jobspec *_jspec);
64 * isdigit is defined to work on an 'int', in the range 0 to 255, plus EOF.
65 * Define a wrapper which can take 'char', either signed or unsigned.
67 #define isdigitch(Anychar) isdigit(((int) Anychar) & 255)
70 * Format a single jobspec into a string fit for printing.
72 void
73 format_jobspec(struct jobspec *jspec, int fmt_wanted)
75 char rangestr[40], buildstr[200];
76 const char fromuser[] = "from user ";
77 const char fromhost[] = "from host ";
78 size_t strsize;
81 * If the struct already has a fmtstring, then release it
82 * before building a new one.
84 if (jspec->fmtoutput != NULL) {
85 free(jspec->fmtoutput);
86 jspec->fmtoutput = NULL;
89 jspec->pluralfmt = 1; /* assume a "plural result" */
90 rangestr[0] = '\0';
91 if (jspec->startnum >= 0) {
92 if (jspec->startnum != jspec->endrange)
93 snprintf(rangestr, sizeof(rangestr), "%ld-%ld",
94 jspec->startnum, jspec->endrange);
95 else {
96 jspec->pluralfmt = 0;
97 snprintf(rangestr, sizeof(rangestr), "%ld",
98 jspec->startnum);
102 strsize = sizeof(buildstr);
103 buildstr[0] = '\0';
104 switch (fmt_wanted) {
105 case FMTJS_TERSE:
106 /* Build everything but the hostname in a temp string. */
107 if (jspec->wanteduser != NULL)
108 strlcat(buildstr, jspec->wanteduser, strsize);
109 if (rangestr[0] != '\0') {
110 if (buildstr[0] != '\0')
111 strlcat(buildstr, ":", strsize);
112 strlcat(buildstr, rangestr, strsize);
114 if (jspec->wantedhost != NULL)
115 strlcat(buildstr, "@", strsize);
117 /* Get space for the final result, including hostname */
118 strsize = strlen(buildstr) + 1;
119 if (jspec->wantedhost != NULL)
120 strsize += strlen(jspec->wantedhost);
121 jspec->fmtoutput = malloc(strsize);
123 /* Put together the final result */
124 strlcpy(jspec->fmtoutput, buildstr, strsize);
125 if (jspec->wantedhost != NULL)
126 strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
127 break;
129 case FMTJS_VERBOSE:
130 default:
131 /* Build everything but the hostname in a temp string. */
132 strlcat(buildstr, rangestr, strsize);
133 if (jspec->wanteduser != NULL) {
134 if (rangestr[0] != '\0')
135 strlcat(buildstr, " ", strsize);
136 strlcat(buildstr, fromuser, strsize);
137 strlcat(buildstr, jspec->wanteduser, strsize);
139 if (jspec->wantedhost != NULL) {
140 if (jspec->wanteduser == NULL) {
141 if (rangestr[0] != '\0')
142 strlcat(buildstr, " ", strsize);
143 strlcat(buildstr, fromhost, strsize);
144 } else
145 strlcat(buildstr, "@", strsize);
148 /* Get space for the final result, including hostname */
149 strsize = strlen(buildstr) + 1;
150 if (jspec->wantedhost != NULL)
151 strsize += strlen(jspec->wantedhost);
152 jspec->fmtoutput = malloc(strsize);
154 /* Put together the final result */
155 strlcpy(jspec->fmtoutput, buildstr, strsize);
156 if (jspec->wantedhost != NULL)
157 strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
158 break;
163 * Free all the jobspec-related information.
165 void
166 free_jobspec(struct jobspec_hdr *js_hdr)
168 struct jobspec *jsinf;
170 while (!STAILQ_EMPTY(js_hdr)) {
171 jsinf = STAILQ_FIRST(js_hdr);
172 STAILQ_REMOVE_HEAD(js_hdr, nextjs);
173 if (jsinf->fmtoutput)
174 free(jsinf->fmtoutput);
175 if (jsinf->matcheduser)
176 free(jsinf->matcheduser);
177 free(jsinf);
182 * This routine takes a string as typed in from the user, and parses it
183 * into a job-specification. A job specification would match one or more
184 * jobs in the queue of some single printer (the specification itself does
185 * not indicate which queue should be searched).
187 * This recognizes a job-number range by itself (all digits, or a range
188 * indicated by "digits-digits"), or a userid by itself. If a `:' is
189 * found, it is treated as a separator between a job-number range and
190 * a userid, where the job number range is the side which has a digit as
191 * the first character. If an `@' is found, everything to the right of
192 * it is treated as the hostname the job originated from.
194 * So, the user can specify:
195 * jobrange userid userid:jobrange jobrange:userid
196 * jobrange@hostname jobrange:userid@hostname
197 * userid@hostname userid:jobrange@hostname
199 * XXX - it would be nice to add "not options" too, such as ^user,
200 * ^jobrange, and @^hostname.
202 * This routine may modify the original input string if that input is
203 * valid. If the input was *not* valid, then this routine should return
204 * with the input string the same as when the routine was called.
207 parse_jobspec(char *jobstr, struct jobspec_hdr *js_hdr)
209 struct jobspec *jsinfo;
210 char *atsign, *colon, *lhside, *numstr, *period, *rhside;
211 int jobnum;
213 #if DEBUG_PARSEJS
214 printf("\t [ pjs-input = %s ]\n", jobstr);
215 #endif
217 if ((jobstr == NULL) || (*jobstr == '\0'))
218 return (0);
220 jsinfo = malloc(sizeof(struct jobspec));
221 memset(jsinfo, 0, sizeof(struct jobspec));
222 jsinfo->startnum = jsinfo->endrange = -1;
224 /* Find the separator characters, and nullify them. */
225 numstr = NULL;
226 atsign = strchr(jobstr, '@');
227 colon = strchr(jobstr, ':');
228 if (atsign != NULL)
229 *atsign = '\0';
230 if (colon != NULL)
231 *colon = '\0';
233 /* The at-sign always indicates a hostname. */
234 if (atsign != NULL) {
235 rhside = atsign + 1;
236 if (*rhside != '\0')
237 jsinfo->wantedhost = rhside;
240 /* Finish splitting the input into three parts. */
241 rhside = NULL;
242 if (colon != NULL) {
243 rhside = colon + 1;
244 if (*rhside == '\0')
245 rhside = NULL;
247 lhside = NULL;
248 if (*jobstr != '\0')
249 lhside = jobstr;
252 * If there is a `:' here, then it's either jobrange:userid,
253 * userid:jobrange, or (if @hostname was not given) perhaps it
254 * might be hostname:jobnum. The side which has a digit as the
255 * first character is assumed to be the jobrange. It is an
256 * input error if both sides start with a digit, or if neither
257 * side starts with a digit.
259 if ((lhside != NULL) && (rhside != NULL)) {
260 if (isdigitch(*lhside)) {
261 if (isdigitch(*rhside))
262 goto bad_input;
263 numstr = lhside;
264 jsinfo->wanteduser = rhside;
265 } else if (isdigitch(*rhside)) {
266 numstr = rhside;
268 * The original implementation of 'lpc topq' accepted
269 * hostname:jobnum. If the input did not include a
270 * @hostname, then assume the userid is a hostname if
271 * it includes a '.'.
273 period = strchr(lhside, '.');
274 if ((atsign == NULL) && (period != NULL))
275 jsinfo->wantedhost = lhside;
276 else
277 jsinfo->wanteduser = lhside;
278 } else {
279 /* Neither side is a job number = user error */
280 goto bad_input;
282 } else if (lhside != NULL) {
283 if (isdigitch(*lhside))
284 numstr = lhside;
285 else
286 jsinfo->wanteduser = lhside;
287 } else if (rhside != NULL) {
288 if (isdigitch(*rhside))
289 numstr = rhside;
290 else
291 jsinfo->wanteduser = rhside;
295 * Break down the numstr. It should be all digits, or a range
296 * specified as "\d+-\d+".
298 if (numstr != NULL) {
299 errno = 0;
300 jobnum = strtol(numstr, &numstr, 10);
301 if (errno != 0) /* error in conversion */
302 goto bad_input;
303 if (jobnum < 0) /* a bogus value for this purpose */
304 goto bad_input;
305 if (jobnum > 99999) /* too large for job number */
306 goto bad_input;
307 jsinfo->startnum = jsinfo->endrange = jobnum;
309 /* Check for a range of numbers */
310 if ((*numstr == '-') && (isdigitch(*(numstr + 1)))) {
311 numstr++;
312 errno = 0;
313 jobnum = strtol(numstr, &numstr, 10);
314 if (errno != 0) /* error in conversion */
315 goto bad_input;
316 if (jobnum < jsinfo->startnum)
317 goto bad_input;
318 if (jobnum > 99999) /* too large for job number */
319 goto bad_input;
320 jsinfo->endrange = jobnum;
324 * If there is anything left in the numstr, and if the
325 * original string did not include a userid or a hostname,
326 * then this might be the ancient form of '\d+hostname'
327 * (with no separator between jobnum and hostname). Accept
328 * that for backwards compatibility, but otherwise any
329 * remaining characters mean a user-error. Note that the
330 * ancient form accepted only a single number, but this
331 * will also accept a range of numbers.
333 if (*numstr != '\0') {
334 if (atsign != NULL)
335 goto bad_input;
336 if (jsinfo->wantedhost != NULL)
337 goto bad_input;
338 if (jsinfo->wanteduser != NULL)
339 goto bad_input;
340 /* Treat as the rest of the string as a hostname */
341 jsinfo->wantedhost = numstr;
345 if ((jsinfo->startnum < 0) && (jsinfo->wanteduser == NULL) &&
346 (jsinfo->wantedhost == NULL))
347 goto bad_input;
350 * The input was valid, in the sense that it could be parsed
351 * into the individual parts. Add this jobspec to the list
352 * of jobspecs.
354 STAILQ_INSERT_TAIL(js_hdr, jsinfo, nextjs);
356 #if DEBUG_PARSEJS
357 printf("\t [ will check for");
358 if (jsinfo->startnum >= 0) {
359 if (jsinfo->startnum == jsinfo->endrange)
360 printf(" jobnum = %ld", jsinfo->startnum);
361 else
362 printf(" jobrange = %ld to %ld", jsinfo->startnum,
363 jsinfo->endrange);
364 } else {
365 printf(" jobs");
367 if ((jsinfo->wanteduser != NULL) || (jsinfo->wantedhost != NULL)) {
368 printf(" from");
369 if (jsinfo->wanteduser != NULL)
370 printf(" user = %s", jsinfo->wanteduser);
371 if (jsinfo->wantedhost != NULL)
372 printf(" host = %s", jsinfo->wantedhost);
374 printf("]\n");
375 #endif
377 return (1);
379 bad_input:
381 * Restore any `@' and `:', in case the calling routine wants to
382 * write an error message which includes the input string.
384 if (atsign != NULL)
385 *atsign = '@';
386 if (colon != NULL)
387 *colon = ':';
388 if (jsinfo != NULL)
389 free(jsinfo);
390 return (0);
394 * Check to see if a given job (specified by a jobqueue entry) matches
395 * all of the specifications in a given jobspec.
397 * Returns 0 if no match, 1 if the job does match.
399 static int
400 match_jobspec(struct jobqueue *jq, struct jobspec *jspec)
402 struct cjobinfo *cfinf;
403 char *cp, *cf_numstr, *cf_hoststr;
404 int jnum, match;
406 #if DEBUG_SCANJS
407 printf("\t [ match-js checking %s ]\n", jq->job_cfname);
408 #endif
410 if (jspec == NULL || jq == NULL)
411 return (0);
414 * Keep track of which jobs have already been matched by this
415 * routine, and thus (probably) already processed.
417 if (jq->job_matched)
418 return (0);
421 * The standard `cf' file has the job number start in position 4,
422 * but some implementations have that as an extra file-sequence
423 * letter, and start the job number in position 5. The job
424 * number is usually three bytes, but may be as many as five.
426 * XXX - All this nonsense should really be handled in a single
427 * place, like getq()...
429 cf_numstr = jq->job_cfname + 3;
430 if (!isdigitch(*cf_numstr))
431 cf_numstr++;
432 jnum = 0;
433 for (cp = cf_numstr; (cp < cf_numstr + 5) && isdigitch(*cp); cp++)
434 jnum = jnum * 10 + (*cp - '0');
435 cf_hoststr = cp;
436 cfinf = NULL;
437 match = 0; /* assume the job will not match */
438 jspec->matcheduser = NULL;
441 * Check the job-number range.
443 if (jspec->startnum >= 0) {
444 if (jnum < jspec->startnum)
445 goto nomatch;
446 if (jnum > jspec->endrange)
447 goto nomatch;
451 * Check the hostname. Strictly speaking this should be done by
452 * reading the control file, but it is less expensive to check
453 * the hostname-part of the control file name. Also, this value
454 * can be easily seen in 'lpq -l', while there is no easy way for
455 * a user/operator to see the hostname in the control file.
457 if (jspec->wantedhost != NULL) {
458 if (fnmatch(jspec->wantedhost, cf_hoststr, 0) != 0)
459 goto nomatch;
463 * Check for a match on the user name. This has to be done
464 * by reading the control file.
466 if (jspec->wanteduser != NULL) {
467 cfinf = ctl_readcf("fakeq", jq->job_cfname);
468 if (cfinf == NULL)
469 goto nomatch;
470 if (fnmatch(jspec->wanteduser, cfinf->cji_username, 0) != 0)
471 goto nomatch;
474 /* This job matches all of the specified criteria. */
475 match = 1;
476 jq->job_matched = 1; /* avoid matching the job twice */
477 jspec->matchcnt++;
478 if (jspec->wanteduser != NULL) {
480 * If the user specified a userid (which may have been a
481 * pattern), then the caller's "doentry()" routine might
482 * want to know the userid of this job that matched.
484 jspec->matcheduser = strdup(cfinf->cji_username);
486 #if DEBUG_SCANJS
487 printf("\t [ job matched! ]\n");
488 #endif
490 nomatch:
491 if (cfinf != NULL)
492 ctl_freeinf(cfinf);
493 return (match);
497 * Scan a queue for all jobs which match a jobspec. The queue is scanned
498 * from top to bottom.
500 * The caller can provide a routine which will be executed for each job
501 * that does match. Note that the processing routine might do anything
502 * to the matched job -- including the removal of it.
504 * This returns the number of jobs which were matched.
507 scanq_jobspec(int qcount, struct jobqueue **squeue, int sopts, struct
508 jobspec_hdr *js_hdr, process_jqe doentry, void *doentryinfo)
510 struct jobqueue **qent;
511 struct jobspec *jspec;
512 int cnt, matched, total;
514 if (qcount < 1)
515 return (0);
516 if (js_hdr == NULL)
517 return (-1);
519 /* The caller must specify one of the scanning orders */
520 if ((sopts & (SCQ_JSORDER|SCQ_QORDER)) == 0)
521 return (-1);
523 total = 0;
524 if (sopts & SCQ_JSORDER) {
526 * For each job specification, scan through the queue
527 * looking for every job that matches.
529 STAILQ_FOREACH(jspec, js_hdr, nextjs) {
530 for (qent = squeue, cnt = 0; cnt < qcount;
531 qent++, cnt++) {
532 matched = match_jobspec(*qent, jspec);
533 if (!matched)
534 continue;
535 total++;
536 if (doentry != NULL)
537 doentry(doentryinfo, *qent, jspec);
538 if (jspec->matcheduser != NULL) {
539 free(jspec->matcheduser);
540 jspec->matcheduser = NULL;
544 * The entire queue has been scanned for this
545 * jobspec. Call the user's routine again with
546 * a NULL queue-entry, so it can print out any
547 * kind of per-jobspec summary.
549 if (doentry != NULL)
550 doentry(doentryinfo, NULL, jspec);
552 } else {
554 * For each job in the queue, check all of the job
555 * specifications to see if any one of them matches
556 * that job.
558 for (qent = squeue, cnt = 0; cnt < qcount;
559 qent++, cnt++) {
560 STAILQ_FOREACH(jspec, js_hdr, nextjs) {
561 matched = match_jobspec(*qent, jspec);
562 if (!matched)
563 continue;
564 total++;
565 if (doentry != NULL)
566 doentry(doentryinfo, *qent, jspec);
567 if (jspec->matcheduser != NULL) {
568 free(jspec->matcheduser);
569 jspec->matcheduser = NULL;
572 * Once there is a match, then there is no
573 * point in checking this same job against
574 * all the other jobspec's.
576 break;
581 return (total);