matchjobs.c revision 101677
1100203Sgad/*
2100203Sgad * ------+---------+---------+---------+---------+---------+---------+---------*
3100203Sgad * Copyright (c) 2002   - Garance Alistair Drosehn <gad@FreeBSD.org>.
4100203Sgad * All rights reserved.
5100203Sgad *
6100203Sgad * Redistribution and use in source and binary forms, with or without
7100203Sgad * modification, are permitted provided that the following conditions
8100203Sgad * are met:
9100203Sgad *   1. Redistributions of source code must retain the above copyright
10100203Sgad *      notice, this list of conditions and the following disclaimer.
11100203Sgad *   2. Redistributions in binary form must reproduce the above copyright
12100203Sgad *      notice, this list of conditions and the following disclaimer in the
13100203Sgad *      documentation and/or other materials provided with the distribution.
14100203Sgad *
15100203Sgad * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16100203Sgad * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17100203Sgad * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18100203Sgad * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19100203Sgad * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20100203Sgad * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21100203Sgad * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22100203Sgad * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23100203Sgad * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24100203Sgad * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25100203Sgad * SUCH DAMAGE.
26100203Sgad *
27100203Sgad * The views and conclusions contained in the software and documentation
28100203Sgad * are those of the authors and should not be interpreted as representing
29100203Sgad * official policies, either expressed or implied, of the FreeBSD Project
30100203Sgad * or FreeBSD, Inc.
31100203Sgad *
32100203Sgad * ------+---------+---------+---------+---------+---------+---------+---------*
33100203Sgad */
34100203Sgad
35100203Sgad#ifndef lint
36100203Sgadstatic const char rcsid[] =
37100203Sgad  "$FreeBSD: head/usr.sbin/lpr/common_source/matchjobs.c 101677 2002-08-11 13:05:30Z schweikh $";
38100203Sgad#endif /* not lint */
39100203Sgad
40100203Sgad/*
41100203Sgad * movejobs.c - The lpc commands which move jobs around.
42100203Sgad */
43100203Sgad
44100203Sgad#include <sys/file.h>
45100203Sgad#include <sys/param.h>
46100203Sgad#include <sys/queue.h>
47100203Sgad#include <sys/time.h>
48100203Sgad
49100203Sgad#include <dirent.h>	/* for MAXNAMLEN, for job_cfname in lp.h! */
50100203Sgad#include <ctype.h>
51100203Sgad#include <errno.h>
52100203Sgad#include <fnmatch.h>
53100203Sgad#include <stdio.h>
54100203Sgad#include <stdlib.h>
55100203Sgad#include <string.h>
56100203Sgad#include <unistd.h>
57100203Sgad#include "ctlinfo.h"
58100203Sgad#include "lp.h"
59100203Sgad#include "matchjobs.h"
60100203Sgad
61100203Sgad#define DEBUG_PARSEJS	0	/* set to 1 when testing */
62100203Sgad#define DEBUG_SCANJS	0	/* set to 1 when testing */
63100203Sgad
64100203Sgadstatic int	 match_jobspec(struct jobqueue *_jq, struct jobspec *_jspec);
65100203Sgad
66100203Sgad/*
67100203Sgad * isdigit is defined to work on an 'int', in the range 0 to 255, plus EOF.
68100203Sgad * Define a wrapper which can take 'char', either signed or unsigned.
69100203Sgad */
70100203Sgad#define isdigitch(Anychar)    isdigit(((int) Anychar) & 255)
71100203Sgad
72100203Sgad/*
73100203Sgad * Format a single jobspec into a string fit for printing.
74100203Sgad */
75100203Sgadvoid
76100203Sgadformat_jobspec(struct jobspec *jspec, int fmt_wanted)
77100203Sgad{
78100203Sgad	char rangestr[40], buildstr[200];
79100203Sgad	const char fromuser[] = "from user ";
80100203Sgad	const char fromhost[] = "from host ";
81100203Sgad	size_t strsize;
82100203Sgad
83100203Sgad	/*
84100203Sgad	 * If the struct already has a fmtstring, then release it
85100203Sgad	 * before building a new one.
86100203Sgad	 */
87100203Sgad	if (jspec->fmtoutput != NULL) {
88100203Sgad		free(jspec->fmtoutput);
89100203Sgad		jspec->fmtoutput = NULL;
90100203Sgad	}
91100203Sgad
92100203Sgad	jspec->pluralfmt = 1;		/* assume a "plural result" */
93100203Sgad	rangestr[0] = '\0';
94100203Sgad	if (jspec->startnum >= 0) {
95100203Sgad		if (jspec->startnum != jspec->endrange)
96100203Sgad			snprintf(rangestr, sizeof(rangestr), "%ld-%ld",
97100203Sgad			    jspec->startnum, jspec->endrange);
98100203Sgad		else {
99100203Sgad			jspec->pluralfmt = 0;
100100203Sgad			snprintf(rangestr, sizeof(rangestr), "%ld",
101100203Sgad			    jspec->startnum);
102100203Sgad		}
103100203Sgad	}
104100203Sgad
105100203Sgad	strsize = sizeof(buildstr);
106100203Sgad	buildstr[0] = '\0';
107100203Sgad	switch (fmt_wanted) {
108100203Sgad	case FMTJS_TERSE:
109100203Sgad		/* Build everything but the hostname in a temp string. */
110100203Sgad		if (jspec->wanteduser != NULL)
111100203Sgad			strlcat(buildstr, jspec->wanteduser, strsize);
112100203Sgad		if (rangestr[0] != '\0') {
113100203Sgad			if (buildstr[0] != '\0')
114100203Sgad				strlcat(buildstr, ":", strsize);
115100203Sgad			strlcat(buildstr, rangestr, strsize);
116100203Sgad		}
117100203Sgad		if (jspec->wantedhost != NULL)
118100203Sgad				strlcat(buildstr, "@", strsize);
119100203Sgad
120100203Sgad		/* Get space for the final result, including hostname */
121100203Sgad		strsize = strlen(buildstr) + 1;
122100203Sgad		if (jspec->wantedhost != NULL)
123100203Sgad			strsize += strlen(jspec->wantedhost);
124100203Sgad		jspec->fmtoutput = malloc(strsize);
125100203Sgad
126100203Sgad		/* Put together the final result */
127100203Sgad		strlcpy(jspec->fmtoutput, buildstr, strsize);
128100203Sgad		if (jspec->wantedhost != NULL)
129100203Sgad			strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
130100203Sgad		break;
131100203Sgad
132100203Sgad	case FMTJS_VERBOSE:
133100203Sgad	default:
134100203Sgad		/* Build everything but the hostname in a temp string. */
135100203Sgad		strlcat(buildstr, rangestr, strsize);
136100203Sgad		if (jspec->wanteduser != NULL) {
137100203Sgad			if (rangestr[0] != '\0')
138100203Sgad				strlcat(buildstr, " ", strsize);
139100203Sgad			strlcat(buildstr, fromuser, strsize);
140100203Sgad			strlcat(buildstr, jspec->wanteduser, strsize);
141100203Sgad		}
142100203Sgad		if (jspec->wantedhost != NULL) {
143100203Sgad			if (jspec->wanteduser == NULL) {
144100203Sgad				if (rangestr[0] != '\0')
145100203Sgad					strlcat(buildstr, " ", strsize);
146100203Sgad				strlcat(buildstr, fromhost, strsize);
147100203Sgad			} else
148100203Sgad				strlcat(buildstr, "@", strsize);
149100203Sgad		}
150100203Sgad
151100203Sgad		/* Get space for the final result, including hostname */
152100203Sgad		strsize = strlen(buildstr) + 1;
153100203Sgad		if (jspec->wantedhost != NULL)
154100203Sgad			strsize += strlen(jspec->wantedhost);
155100203Sgad		jspec->fmtoutput = malloc(strsize);
156100203Sgad
157100203Sgad		/* Put together the final result */
158100203Sgad		strlcpy(jspec->fmtoutput, buildstr, strsize);
159100203Sgad		if (jspec->wantedhost != NULL)
160100203Sgad			strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
161100203Sgad		break;
162100203Sgad	}
163100203Sgad}
164100203Sgad
165100203Sgad/*
166100203Sgad * Free all the jobspec-related information.
167100203Sgad */
168100203Sgadvoid
169100203Sgadfree_jobspec(struct jobspec_hdr *js_hdr)
170100203Sgad{
171100203Sgad	struct jobspec *jsinf;
172100203Sgad
173100203Sgad	while (!STAILQ_EMPTY(js_hdr)) {
174100203Sgad		jsinf = STAILQ_FIRST(js_hdr);
175100203Sgad		STAILQ_REMOVE_HEAD(js_hdr, nextjs);
176100203Sgad		if (jsinf->fmtoutput)
177100203Sgad			free(jsinf->fmtoutput);
178100203Sgad		if (jsinf->matcheduser)
179100203Sgad			free(jsinf->matcheduser);
180100203Sgad		free(jsinf);
181100203Sgad	}
182100203Sgad}
183100203Sgad
184100203Sgad/*
185100203Sgad * This routine takes a string as typed in from the user, and parses it
186100203Sgad * into a job-specification.  A job specification would match one or more
187100203Sgad * jobs in the queue of some single printer (the specification itself does
188100203Sgad * not indicate which queue should be searched).
189100203Sgad *
190100203Sgad * This recognizes a job-number range by itself (all digits, or a range
191100203Sgad * indicated by "digits-digits"), or a userid by itself.  If a `:' is
192100203Sgad * found, it is treated as a separator between a job-number range and
193100203Sgad * a userid, where the job number range is the side which has a digit as
194100203Sgad * the first character.  If an `@' is found, everything to the right of
195100203Sgad * it is treated as the hostname the job originated from.
196100203Sgad *
197100203Sgad * So, the user can specify:
198100203Sgad *	jobrange       userid     userid:jobrange    jobrange:userid
199100203Sgad *	jobrange@hostname   jobrange:userid@hostname
200100203Sgad *	userid@hostname     userid:jobrange@hostname
201100203Sgad *
202100203Sgad * XXX - it would be nice to add "not options" too, such as ^user,
203100203Sgad *	^jobrange, and @^hostname.
204100203Sgad *
205100203Sgad * This routine may modify the original input string if that input is
206100203Sgad * valid.  If the input was *not* valid, then this routine should return
207100203Sgad * with the input string the same as when the routine was called.
208100203Sgad */
209100203Sgadint
210100203Sgadparse_jobspec(char *jobstr, struct jobspec_hdr *js_hdr)
211100203Sgad{
212100203Sgad	struct jobspec *jsinfo;
213100203Sgad	char *atsign, *colon, *lhside, *numstr, *period, *rhside;
214100203Sgad	int jobnum;
215100203Sgad
216100203Sgad#if DEBUG_PARSEJS
217100203Sgad	printf("\t [ pjs-input = %s ]\n", jobstr);
218100203Sgad#endif
219100203Sgad
220100203Sgad	if ((jobstr == NULL) || (*jobstr == '\0'))
221100203Sgad		return (0);
222100203Sgad
223100203Sgad	jsinfo = malloc(sizeof(struct jobspec));
224100203Sgad	memset(jsinfo, 0, sizeof(struct jobspec));
225100203Sgad	jsinfo->startnum = jsinfo->endrange = -1;
226100203Sgad
227100203Sgad	/* Find the separator characters, and nullify them. */
228100203Sgad	numstr = NULL;
229100203Sgad	atsign = strchr(jobstr, '@');
230100203Sgad	colon = strchr(jobstr, ':');
231100203Sgad	if (atsign != NULL)
232100203Sgad		*atsign = '\0';
233100203Sgad	if (colon != NULL)
234100203Sgad		*colon = '\0';
235100203Sgad
236100203Sgad	/* The at-sign always indicates a hostname. */
237100203Sgad	if (atsign != NULL) {
238100203Sgad		rhside = atsign + 1;
239100203Sgad		if (*rhside != '\0')
240100203Sgad			jsinfo->wantedhost = rhside;
241100203Sgad	}
242100203Sgad
243100203Sgad	/* Finish splitting the input into three parts. */
244100203Sgad	rhside = NULL;
245100203Sgad	if (colon != NULL) {
246100203Sgad		rhside = colon + 1;
247100203Sgad		if (*rhside == '\0')
248100203Sgad			rhside = NULL;
249100203Sgad	}
250100203Sgad	lhside = NULL;
251100203Sgad	if (*jobstr != '\0')
252100203Sgad		lhside = jobstr;
253100203Sgad
254100203Sgad	/*
255100203Sgad	 * If there is a `:' here, then it's either jobrange:userid,
256100203Sgad	 * userid:jobrange, or (if @hostname was not given) perhaps it
257100203Sgad	 * might be hostname:jobnum.  The side which has a digit as the
258100203Sgad	 * first character is assumed to be the jobrange.  It is an
259100203Sgad	 * input error if both sides start with a digit, or if neither
260100203Sgad	 * side starts with a digit.
261100203Sgad	 */
262100203Sgad	if ((lhside != NULL) && (rhside != NULL)) {
263100203Sgad		if (isdigitch(*lhside)) {
264100203Sgad			if (isdigitch(*rhside))
265100203Sgad				goto bad_input;
266100203Sgad			numstr = lhside;
267100203Sgad			jsinfo->wanteduser = rhside;
268100203Sgad		} else if (isdigitch(*rhside)) {
269100203Sgad			numstr = rhside;
270100203Sgad			/*
271100203Sgad			 * The original implementation of 'lpc topq' accepted
272100203Sgad			 * hostname:jobnum.  If the input did not include a
273100203Sgad			 * @hostname, then assume the userid is a hostname if
274100203Sgad			 * it includes a '.'.
275100203Sgad			 */
276100203Sgad			period = strchr(lhside, '.');
277100203Sgad			if ((atsign == NULL) && (period != NULL))
278100203Sgad				jsinfo->wantedhost = lhside;
279100203Sgad			else
280100203Sgad				jsinfo->wanteduser = lhside;
281100203Sgad		} else {
282100203Sgad			/* Neither side is a job number = user error */
283100203Sgad			goto bad_input;
284100203Sgad		}
285100203Sgad	} else if (lhside != NULL) {
286100203Sgad		if (isdigitch(*lhside))
287100203Sgad			numstr = lhside;
288100203Sgad		else
289100203Sgad			jsinfo->wanteduser = lhside;
290100203Sgad	} else if (rhside != NULL) {
291100203Sgad		if (isdigitch(*rhside))
292100203Sgad			numstr = rhside;
293100203Sgad		else
294100203Sgad			jsinfo->wanteduser = rhside;
295100203Sgad	}
296100203Sgad
297100203Sgad	/*
298100203Sgad	 * Break down the numstr.  It should be all digits, or a range
299100203Sgad	 * specified as "\d+-\d+".
300100203Sgad	 */
301100203Sgad	if (numstr != NULL) {
302100203Sgad		errno = 0;
303100203Sgad		jobnum = strtol(numstr, &numstr, 10);
304100203Sgad		if (errno != 0)		/* error in conversion */
305100203Sgad			goto bad_input;
306100203Sgad		if (jobnum < 0)		/* a bogus value for this purpose */
307100203Sgad			goto bad_input;
308100203Sgad		if (jobnum > 99999)	/* too large for job number */
309100203Sgad			goto bad_input;
310100203Sgad		jsinfo->startnum = jsinfo->endrange = jobnum;
311100203Sgad
312100203Sgad		/* Check for a range of numbers */
313100203Sgad		if ((*numstr == '-') && (isdigitch(*(numstr + 1)))) {
314100203Sgad			numstr++;
315100203Sgad			errno = 0;
316100203Sgad			jobnum = strtol(numstr, &numstr, 10);
317100203Sgad			if (errno != 0)		/* error in conversion */
318100203Sgad				goto bad_input;
319100203Sgad			if (jobnum < jsinfo->startnum)
320100203Sgad				goto bad_input;
321100203Sgad			if (jobnum > 99999)	/* too large for job number */
322100203Sgad				goto bad_input;
323100203Sgad			jsinfo->endrange = jobnum;
324100203Sgad		}
325100203Sgad
326100203Sgad		/*
327100203Sgad		 * If there is anything left in the numstr, and if the
328100203Sgad		 * original string did not include a userid or a hostname,
329100203Sgad		 * then this might be the ancient form of '\d+hostname'
330101677Sschweikh		 * (with no separator between jobnum and hostname).  Accept
331100203Sgad		 * that for backwards compatibility, but otherwise any
332100203Sgad		 * remaining characters mean a user-error.  Note that the
333100203Sgad		 * ancient form accepted only a single number, but this
334100203Sgad		 * will also accept a range of numbers.
335100203Sgad		 */
336100203Sgad		if (*numstr != '\0') {
337100203Sgad			if (atsign != NULL)
338100203Sgad				goto bad_input;
339100203Sgad			if (jsinfo->wantedhost != NULL)
340100203Sgad				goto bad_input;
341100203Sgad			if (jsinfo->wanteduser != NULL)
342100203Sgad				goto bad_input;
343100203Sgad			/* Treat as the rest of the string as a hostname */
344100203Sgad			jsinfo->wantedhost = numstr;
345100203Sgad		}
346100203Sgad	}
347100203Sgad
348100203Sgad	if ((jsinfo->startnum < 0) && (jsinfo->wanteduser == NULL) &&
349100203Sgad	    (jsinfo->wantedhost == NULL))
350100203Sgad		goto bad_input;
351100203Sgad
352100203Sgad	/*
353100203Sgad	 * The input was valid, in the sense that it could be parsed
354100203Sgad	 * into the individual parts.  Add this jobspec to the list
355100203Sgad	 * of jobspecs.
356100203Sgad	 */
357100203Sgad	STAILQ_INSERT_TAIL(js_hdr, jsinfo, nextjs);
358100203Sgad
359100203Sgad#if DEBUG_PARSEJS
360100203Sgad	printf("\t [   will check for");
361100203Sgad	if (jsinfo->startnum >= 0) {
362100203Sgad		if (jsinfo->startnum == jsinfo->endrange)
363100203Sgad			printf(" jobnum = %ld", jsinfo->startnum);
364100203Sgad		else
365100203Sgad			printf(" jobrange = %ld to %ld", jsinfo->startnum,
366100203Sgad			    jsinfo->endrange);
367100203Sgad	} else {
368100203Sgad		printf(" jobs");
369100203Sgad	}
370100203Sgad	if ((jsinfo->wanteduser != NULL) || (jsinfo->wantedhost != NULL)) {
371100203Sgad		printf(" from");
372100203Sgad		if (jsinfo->wanteduser != NULL)
373100203Sgad			printf(" user = %s", jsinfo->wanteduser);
374100203Sgad		if (jsinfo->wantedhost != NULL)
375100203Sgad			printf(" host = %s", jsinfo->wantedhost);
376100203Sgad	}
377100203Sgad	printf("]\n");
378100203Sgad#endif
379100203Sgad
380100203Sgad	return (1);
381100203Sgad
382100203Sgadbad_input:
383100203Sgad	/*
384100203Sgad	 * Restore any `@' and `:', in case the calling routine wants to
385100203Sgad	 * write an error message which includes the input string.
386100203Sgad	 */
387100203Sgad	if (atsign != NULL)
388100203Sgad		*atsign = '@';
389100203Sgad	if (colon != NULL)
390100203Sgad		*colon = ':';
391100203Sgad	if (jsinfo != NULL)
392100203Sgad		free(jsinfo);
393100203Sgad	return (0);
394100203Sgad}
395100203Sgad
396100203Sgad/*
397100203Sgad * Check to see if a given job (specified by a jobqueue entry) matches
398100203Sgad * all of the specifications in a given jobspec.
399100203Sgad *
400100203Sgad * Returns 0 if no match, 1 if the job does match.
401100203Sgad */
402100203Sgadstatic int
403100203Sgadmatch_jobspec(struct jobqueue *jq, struct jobspec *jspec)
404100203Sgad{
405100203Sgad	struct cjobinfo *cfinf;
406100203Sgad	char *cp, *cf_numstr, *cf_hoststr;
407100203Sgad	int jnum, match;
408100203Sgad
409100203Sgad#if DEBUG_SCANJS
410100203Sgad	printf("\t [ match-js checking %s ]\n", jq->job_cfname);
411100203Sgad#endif
412100203Sgad
413100203Sgad	if (jspec == NULL || jq == NULL)
414100203Sgad		return (0);
415100203Sgad
416100203Sgad	/*
417100203Sgad	 * Keep track of which jobs have already been matched by this
418100203Sgad	 * routine, and thus (probably) already processed.
419100203Sgad	 */
420100203Sgad	if (jq->job_matched)
421100203Sgad		return (0);
422100203Sgad
423100203Sgad	/*
424100203Sgad	 * The standard `cf' file has the job number start in position 4,
425100203Sgad	 * but some implementations have that as an extra file-sequence
426100203Sgad	 * letter, and start the job number in position 5.  The job
427100203Sgad	 * number is usually three bytes, but may be as many as five.
428100203Sgad	 *
429100203Sgad	 * XXX - All this nonsense should really be handled in a single
430100203Sgad	 *	place, like getq()...
431100203Sgad	 */
432100203Sgad	cf_numstr = jq->job_cfname + 3;
433100203Sgad	if (!isdigitch(*cf_numstr))
434100203Sgad		cf_numstr++;
435100203Sgad	jnum = 0;
436100203Sgad	for (cp = cf_numstr; (cp < cf_numstr + 5) && isdigitch(*cp); cp++)
437100203Sgad		jnum = jnum * 10 + (*cp - '0');
438100203Sgad	cf_hoststr = cp;
439100203Sgad	cfinf = NULL;
440100203Sgad	match = 0;			/* assume the job will not match */
441100203Sgad	jspec->matcheduser = NULL;
442100203Sgad
443100203Sgad	/*
444100203Sgad	 * Check the job-number range.
445100203Sgad	 */
446100203Sgad	if (jspec->startnum >= 0) {
447100203Sgad		if (jnum < jspec->startnum)
448100203Sgad			goto nomatch;
449100203Sgad		if (jnum > jspec->endrange)
450100203Sgad			goto nomatch;
451100203Sgad	}
452100203Sgad
453100203Sgad	/*
454100203Sgad	 * Check the hostname.  Strictly speaking this should be done by
455100203Sgad	 * reading the control file, but it is less expensive to check
456100203Sgad	 * the hostname-part of the control file name.  Also, this value
457100203Sgad	 * can be easily seen in 'lpq -l', while there is no easy way for
458100203Sgad	 * a user/operator to see the hostname in the control file.
459100203Sgad	 */
460100203Sgad	if (jspec->wantedhost != NULL) {
461100203Sgad		if (fnmatch(jspec->wantedhost, cf_hoststr, 0) != 0)
462100203Sgad			goto nomatch;
463100203Sgad	}
464100203Sgad
465100203Sgad	/*
466100203Sgad	 * Check for a match on the user name.  This has to be done
467100203Sgad	 * by reading the control file.
468100203Sgad	 */
469100203Sgad	if (jspec->wanteduser != NULL) {
470100203Sgad		cfinf = ctl_readcf("fakeq", jq->job_cfname);
471100203Sgad		if (cfinf == NULL)
472100203Sgad			goto nomatch;
473100203Sgad		if (fnmatch(jspec->wanteduser, cfinf->cji_username, 0) != 0)
474100203Sgad			goto nomatch;
475100203Sgad	}
476100203Sgad
477100203Sgad	/* This job matches all of the specified criteria. */
478100203Sgad	match = 1;
479100203Sgad	jq->job_matched = 1;		/* avoid matching the job twice */
480100203Sgad	jspec->matchcnt++;
481100203Sgad	if (jspec->wanteduser != NULL) {
482100203Sgad		/*
483100203Sgad		 * If the user specified a userid (which may have been a
484100203Sgad		 * pattern), then the caller's "doentry()" routine might
485100203Sgad		 * want to know the userid of this job that matched.
486100203Sgad		 */
487100203Sgad		jspec->matcheduser = strdup(cfinf->cji_username);
488100203Sgad	}
489100203Sgad#if DEBUG_SCANJS
490100203Sgad	printf("\t [ job matched! ]\n");
491100203Sgad#endif
492100203Sgad
493100203Sgadnomatch:
494100203Sgad	if (cfinf != NULL)
495100203Sgad		ctl_freeinf(cfinf);
496100203Sgad	return (match);
497100203Sgad}
498100203Sgad
499100203Sgad/*
500100203Sgad * Scan a queue for all jobs which match a jobspec.  The queue is scanned
501100203Sgad * from top to bottom.
502100203Sgad *
503100203Sgad * The caller can provide a routine which will be executed for each job
504100203Sgad * that does match.  Note that the processing routine might do anything
505100203Sgad * to the matched job -- including the removal of it.
506100203Sgad *
507100203Sgad * This returns the number of jobs which were matched.
508100203Sgad */
509100203Sgadint
510100203Sgadscanq_jobspec(int qcount, struct jobqueue **squeue, int sopts, struct
511100203Sgad    jobspec_hdr *js_hdr, process_jqe doentry, void *doentryinfo)
512100203Sgad{
513100203Sgad	struct jobqueue **qent;
514100203Sgad	struct jobspec *jspec;
515100203Sgad	int cnt, matched, total;
516100203Sgad
517100203Sgad	if (qcount < 1)
518100203Sgad		return (0);
519100203Sgad	if (js_hdr == NULL)
520100203Sgad		return (-1);
521100203Sgad
522100203Sgad	/* The caller must specify one of the scanning orders */
523100203Sgad	if ((sopts & (SCQ_JSORDER|SCQ_QORDER)) == 0)
524100203Sgad		return (-1);
525100203Sgad
526100203Sgad	total = 0;
527100203Sgad	if (sopts & SCQ_JSORDER) {
528100203Sgad		/*
529100203Sgad		 * For each job specification, scan through the queue
530100203Sgad		 * looking for every job that matches.
531100203Sgad		 */
532100203Sgad		STAILQ_FOREACH(jspec, js_hdr, nextjs) {
533100203Sgad			for (qent = squeue, cnt = 0; cnt < qcount;
534100203Sgad			    qent++, cnt++) {
535100203Sgad				matched = match_jobspec(*qent, jspec);
536100203Sgad				if (!matched)
537100203Sgad					continue;
538100203Sgad				total++;
539100203Sgad				if (doentry != NULL)
540100203Sgad					doentry(doentryinfo, *qent, jspec);
541100203Sgad				if (jspec->matcheduser != NULL) {
542100203Sgad					free(jspec->matcheduser);
543100203Sgad					jspec->matcheduser = NULL;
544100203Sgad				}
545100203Sgad			}
546100203Sgad			/*
547100203Sgad			 * The entire queue has been scanned for this
548100203Sgad			 * jobspec.  Call the user's routine again with
549100203Sgad			 * a NULL queue-entry, so it can print out any
550100203Sgad			 * kind of per-jobspec summary.
551100203Sgad			 */
552100203Sgad			if (doentry != NULL)
553100203Sgad				doentry(doentryinfo, NULL, jspec);
554100203Sgad		}
555100203Sgad	} else {
556100203Sgad		/*
557100203Sgad		 * For each job in the queue, check all of the job
558100203Sgad		 * specifications to see if any one of them matches
559100203Sgad		 * that job.
560100203Sgad		 */
561100203Sgad		for (qent = squeue, cnt = 0; cnt < qcount;
562100203Sgad		    qent++, cnt++) {
563100203Sgad			STAILQ_FOREACH(jspec, js_hdr, nextjs) {
564100203Sgad				matched = match_jobspec(*qent, jspec);
565100203Sgad				if (!matched)
566100203Sgad					continue;
567100203Sgad				total++;
568100203Sgad				if (doentry != NULL)
569100203Sgad					doentry(doentryinfo, *qent, jspec);
570100203Sgad				if (jspec->matcheduser != NULL) {
571100203Sgad					free(jspec->matcheduser);
572100203Sgad					jspec->matcheduser = NULL;
573100203Sgad				}
574100203Sgad				/*
575100203Sgad				 * Once there is a match, then there is no
576100203Sgad				 * point in checking this same job against
577100203Sgad				 * all the other jobspec's.
578100203Sgad				 */
579100203Sgad				break;
580100203Sgad			}
581100203Sgad		}
582100203Sgad	}
583100203Sgad
584100203Sgad	return (total);
585100203Sgad}
586