PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/usr.sbin/lpr/common_source/matchjobs.c

https://bitbucket.org/freebsd/freebsd-head/
C | 568 lines | 334 code | 49 blank | 185 comment | 161 complexity | 3c6e4a4973d86507710c96201b0a89e1 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, LGPL-2.0, LGPL-2.1, BSD-2-Clause, 0BSD, JSON, AGPL-1.0, GPL-2.0
  1. /*
  2. * ------+---------+---------+---------+---------+---------+---------+---------*
  3. * Copyright (c) 2002,2011 - Garance Alistair Drosehn <gad@FreeBSD.org>.
  4. * All rights reserved.
  5. *
  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.
  14. *
  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.
  26. *
  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.
  31. *
  32. * ------+---------+---------+---------+---------+---------+---------+---------*
  33. */
  34. #include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
  35. __FBSDID("$FreeBSD$");
  36. /*
  37. * movejobs.c - The lpc commands which move jobs around.
  38. */
  39. #include <sys/file.h>
  40. #include <sys/param.h>
  41. #include <sys/queue.h>
  42. #include <sys/time.h>
  43. #include <dirent.h> /* for MAXNAMLEN, for job_cfname in lp.h! */
  44. #include <ctype.h>
  45. #include <errno.h>
  46. #include <fnmatch.h>
  47. #include <stdio.h>
  48. #include <stdlib.h>
  49. #include <string.h>
  50. #include <unistd.h>
  51. #include "ctlinfo.h"
  52. #include "lp.h"
  53. #include "matchjobs.h"
  54. #define DEBUG_PARSEJS 0 /* set to 1 when testing */
  55. #define DEBUG_SCANJS 0 /* set to 1 when testing */
  56. static int match_jobspec(struct jobqueue *_jq, struct jobspec *_jspec);
  57. /*
  58. * isdigit is defined to work on an 'int', in the range 0 to 255, plus EOF.
  59. * Define a wrapper which can take 'char', either signed or unsigned.
  60. */
  61. #define isdigitch(Anychar) isdigit(((int) Anychar) & 255)
  62. /*
  63. * Format a single jobspec into a string fit for printing.
  64. */
  65. void
  66. format_jobspec(struct jobspec *jspec, int fmt_wanted)
  67. {
  68. char rangestr[40], buildstr[200];
  69. const char fromuser[] = "from user ";
  70. const char fromhost[] = "from host ";
  71. size_t strsize;
  72. /*
  73. * If the struct already has a fmtstring, then release it
  74. * before building a new one.
  75. */
  76. if (jspec->fmtoutput != NULL) {
  77. free(jspec->fmtoutput);
  78. jspec->fmtoutput = NULL;
  79. }
  80. jspec->pluralfmt = 1; /* assume a "plural result" */
  81. rangestr[0] = '\0';
  82. if (jspec->startnum >= 0) {
  83. if (jspec->startnum != jspec->endrange)
  84. snprintf(rangestr, sizeof(rangestr), "%ld-%ld",
  85. jspec->startnum, jspec->endrange);
  86. else {
  87. jspec->pluralfmt = 0;
  88. snprintf(rangestr, sizeof(rangestr), "%ld",
  89. jspec->startnum);
  90. }
  91. }
  92. strsize = sizeof(buildstr);
  93. buildstr[0] = '\0';
  94. switch (fmt_wanted) {
  95. case FMTJS_TERSE:
  96. /* Build everything but the hostname in a temp string. */
  97. if (jspec->wanteduser != NULL)
  98. strlcat(buildstr, jspec->wanteduser, strsize);
  99. if (rangestr[0] != '\0') {
  100. if (buildstr[0] != '\0')
  101. strlcat(buildstr, ":", strsize);
  102. strlcat(buildstr, rangestr, strsize);
  103. }
  104. if (jspec->wantedhost != NULL)
  105. strlcat(buildstr, "@", strsize);
  106. /* Get space for the final result, including hostname */
  107. strsize = strlen(buildstr) + 1;
  108. if (jspec->wantedhost != NULL)
  109. strsize += strlen(jspec->wantedhost);
  110. jspec->fmtoutput = malloc(strsize);
  111. /* Put together the final result */
  112. strlcpy(jspec->fmtoutput, buildstr, strsize);
  113. if (jspec->wantedhost != NULL)
  114. strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
  115. break;
  116. case FMTJS_VERBOSE:
  117. default:
  118. /* Build everything but the hostname in a temp string. */
  119. strlcat(buildstr, rangestr, strsize);
  120. if (jspec->wanteduser != NULL) {
  121. if (rangestr[0] != '\0')
  122. strlcat(buildstr, " ", strsize);
  123. strlcat(buildstr, fromuser, strsize);
  124. strlcat(buildstr, jspec->wanteduser, strsize);
  125. }
  126. if (jspec->wantedhost != NULL) {
  127. if (jspec->wanteduser == NULL) {
  128. if (rangestr[0] != '\0')
  129. strlcat(buildstr, " ", strsize);
  130. strlcat(buildstr, fromhost, strsize);
  131. } else
  132. strlcat(buildstr, "@", strsize);
  133. }
  134. /* Get space for the final result, including hostname */
  135. strsize = strlen(buildstr) + 1;
  136. if (jspec->wantedhost != NULL)
  137. strsize += strlen(jspec->wantedhost);
  138. jspec->fmtoutput = malloc(strsize);
  139. /* Put together the final result */
  140. strlcpy(jspec->fmtoutput, buildstr, strsize);
  141. if (jspec->wantedhost != NULL)
  142. strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
  143. break;
  144. }
  145. }
  146. /*
  147. * Free all the jobspec-related information.
  148. */
  149. void
  150. free_jobspec(struct jobspec_hdr *js_hdr)
  151. {
  152. struct jobspec *jsinf;
  153. while (!STAILQ_EMPTY(js_hdr)) {
  154. jsinf = STAILQ_FIRST(js_hdr);
  155. STAILQ_REMOVE_HEAD(js_hdr, nextjs);
  156. if (jsinf->fmtoutput)
  157. free(jsinf->fmtoutput);
  158. if (jsinf->matcheduser)
  159. free(jsinf->matcheduser);
  160. free(jsinf);
  161. }
  162. }
  163. /*
  164. * This routine takes a string as typed in from the user, and parses it
  165. * into a job-specification. A job specification would match one or more
  166. * jobs in the queue of some single printer (the specification itself does
  167. * not indicate which queue should be searched).
  168. *
  169. * This recognizes a job-number range by itself (all digits, or a range
  170. * indicated by "digits-digits"), or a userid by itself. If a `:' is
  171. * found, it is treated as a separator between a job-number range and
  172. * a userid, where the job number range is the side which has a digit as
  173. * the first character. If an `@' is found, everything to the right of
  174. * it is treated as the hostname the job originated from.
  175. *
  176. * So, the user can specify:
  177. * jobrange userid userid:jobrange jobrange:userid
  178. * jobrange@hostname jobrange:userid@hostname
  179. * userid@hostname userid:jobrange@hostname
  180. *
  181. * XXX - it would be nice to add "not options" too, such as ^user,
  182. * ^jobrange, and @^hostname.
  183. *
  184. * This routine may modify the original input string if that input is
  185. * valid. If the input was *not* valid, then this routine should return
  186. * with the input string the same as when the routine was called.
  187. */
  188. int
  189. parse_jobspec(char *jobstr, struct jobspec_hdr *js_hdr)
  190. {
  191. struct jobspec *jsinfo;
  192. char *atsign, *colon, *lhside, *numstr, *period, *rhside;
  193. int jobnum;
  194. #if DEBUG_PARSEJS
  195. printf("\t [ pjs-input = %s ]\n", jobstr);
  196. #endif
  197. if ((jobstr == NULL) || (*jobstr == '\0'))
  198. return (0);
  199. jsinfo = malloc(sizeof(struct jobspec));
  200. memset(jsinfo, 0, sizeof(struct jobspec));
  201. jsinfo->startnum = jsinfo->endrange = -1;
  202. /* Find the separator characters, and nullify them. */
  203. numstr = NULL;
  204. atsign = strchr(jobstr, '@');
  205. colon = strchr(jobstr, ':');
  206. if (atsign != NULL)
  207. *atsign = '\0';
  208. if (colon != NULL)
  209. *colon = '\0';
  210. /* The at-sign always indicates a hostname. */
  211. if (atsign != NULL) {
  212. rhside = atsign + 1;
  213. if (*rhside != '\0')
  214. jsinfo->wantedhost = rhside;
  215. }
  216. /* Finish splitting the input into three parts. */
  217. rhside = NULL;
  218. if (colon != NULL) {
  219. rhside = colon + 1;
  220. if (*rhside == '\0')
  221. rhside = NULL;
  222. }
  223. lhside = NULL;
  224. if (*jobstr != '\0')
  225. lhside = jobstr;
  226. /*
  227. * If there is a `:' here, then it's either jobrange:userid,
  228. * userid:jobrange, or (if @hostname was not given) perhaps it
  229. * might be hostname:jobnum. The side which has a digit as the
  230. * first character is assumed to be the jobrange. It is an
  231. * input error if both sides start with a digit, or if neither
  232. * side starts with a digit.
  233. */
  234. if ((lhside != NULL) && (rhside != NULL)) {
  235. if (isdigitch(*lhside)) {
  236. if (isdigitch(*rhside))
  237. goto bad_input;
  238. numstr = lhside;
  239. jsinfo->wanteduser = rhside;
  240. } else if (isdigitch(*rhside)) {
  241. numstr = rhside;
  242. /*
  243. * The original implementation of 'lpc topq' accepted
  244. * hostname:jobnum. If the input did not include a
  245. * @hostname, then assume the userid is a hostname if
  246. * it includes a '.'.
  247. */
  248. period = strchr(lhside, '.');
  249. if ((atsign == NULL) && (period != NULL))
  250. jsinfo->wantedhost = lhside;
  251. else
  252. jsinfo->wanteduser = lhside;
  253. } else {
  254. /* Neither side is a job number = user error */
  255. goto bad_input;
  256. }
  257. } else if (lhside != NULL) {
  258. if (isdigitch(*lhside))
  259. numstr = lhside;
  260. else
  261. jsinfo->wanteduser = lhside;
  262. } else if (rhside != NULL) {
  263. if (isdigitch(*rhside))
  264. numstr = rhside;
  265. else
  266. jsinfo->wanteduser = rhside;
  267. }
  268. /*
  269. * Break down the numstr. It should be all digits, or a range
  270. * specified as "\d+-\d+".
  271. */
  272. if (numstr != NULL) {
  273. errno = 0;
  274. jobnum = strtol(numstr, &numstr, 10);
  275. if (errno != 0) /* error in conversion */
  276. goto bad_input;
  277. if (jobnum < 0) /* a bogus value for this purpose */
  278. goto bad_input;
  279. if (jobnum > 99999) /* too large for job number */
  280. goto bad_input;
  281. jsinfo->startnum = jsinfo->endrange = jobnum;
  282. /* Check for a range of numbers */
  283. if ((*numstr == '-') && (isdigitch(*(numstr + 1)))) {
  284. numstr++;
  285. errno = 0;
  286. jobnum = strtol(numstr, &numstr, 10);
  287. if (errno != 0) /* error in conversion */
  288. goto bad_input;
  289. if (jobnum < jsinfo->startnum)
  290. goto bad_input;
  291. if (jobnum > 99999) /* too large for job number */
  292. goto bad_input;
  293. jsinfo->endrange = jobnum;
  294. }
  295. /*
  296. * If there is anything left in the numstr, and if the
  297. * original string did not include a userid or a hostname,
  298. * then this might be the ancient form of '\d+hostname'
  299. * (with no separator between jobnum and hostname). Accept
  300. * that for backwards compatibility, but otherwise any
  301. * remaining characters mean a user-error. Note that the
  302. * ancient form accepted only a single number, but this
  303. * will also accept a range of numbers.
  304. */
  305. if (*numstr != '\0') {
  306. if (atsign != NULL)
  307. goto bad_input;
  308. if (jsinfo->wantedhost != NULL)
  309. goto bad_input;
  310. if (jsinfo->wanteduser != NULL)
  311. goto bad_input;
  312. /* Treat as the rest of the string as a hostname */
  313. jsinfo->wantedhost = numstr;
  314. }
  315. }
  316. if ((jsinfo->startnum < 0) && (jsinfo->wanteduser == NULL) &&
  317. (jsinfo->wantedhost == NULL))
  318. goto bad_input;
  319. /*
  320. * The input was valid, in the sense that it could be parsed
  321. * into the individual parts. Add this jobspec to the list
  322. * of jobspecs.
  323. */
  324. STAILQ_INSERT_TAIL(js_hdr, jsinfo, nextjs);
  325. #if DEBUG_PARSEJS
  326. printf("\t [ will check for");
  327. if (jsinfo->startnum >= 0) {
  328. if (jsinfo->startnum == jsinfo->endrange)
  329. printf(" jobnum = %ld", jsinfo->startnum);
  330. else
  331. printf(" jobrange = %ld to %ld", jsinfo->startnum,
  332. jsinfo->endrange);
  333. } else {
  334. printf(" jobs");
  335. }
  336. if ((jsinfo->wanteduser != NULL) || (jsinfo->wantedhost != NULL)) {
  337. printf(" from");
  338. if (jsinfo->wanteduser != NULL)
  339. printf(" user = %s", jsinfo->wanteduser);
  340. if (jsinfo->wantedhost != NULL)
  341. printf(" host = %s", jsinfo->wantedhost);
  342. }
  343. printf("]\n");
  344. #endif
  345. return (1);
  346. bad_input:
  347. /*
  348. * Restore any `@' and `:', in case the calling routine wants to
  349. * write an error message which includes the input string.
  350. */
  351. if (atsign != NULL)
  352. *atsign = '@';
  353. if (colon != NULL)
  354. *colon = ':';
  355. if (jsinfo != NULL)
  356. free(jsinfo);
  357. return (0);
  358. }
  359. /*
  360. * Check to see if a given job (specified by a jobqueue entry) matches
  361. * all of the specifications in a given jobspec.
  362. *
  363. * Returns 0 if no match, 1 if the job does match.
  364. */
  365. static int
  366. match_jobspec(struct jobqueue *jq, struct jobspec *jspec)
  367. {
  368. struct cjobinfo *cfinf;
  369. const char *cf_hoststr;
  370. int jnum, match;
  371. #if DEBUG_SCANJS
  372. printf("\t [ match-js checking %s ]\n", jq->job_cfname);
  373. #endif
  374. if (jspec == NULL || jq == NULL)
  375. return (0);
  376. /*
  377. * Keep track of which jobs have already been matched by this
  378. * routine, and thus (probably) already processed.
  379. */
  380. if (jq->job_matched)
  381. return (0);
  382. jnum = calc_jobnum(jq->job_cfname, &cf_hoststr);
  383. cfinf = NULL;
  384. match = 0; /* assume the job will not match */
  385. jspec->matcheduser = NULL;
  386. /*
  387. * Check the job-number range.
  388. */
  389. if (jspec->startnum >= 0) {
  390. if (jnum < jspec->startnum)
  391. goto nomatch;
  392. if (jnum > jspec->endrange)
  393. goto nomatch;
  394. }
  395. /*
  396. * Check the hostname. Strictly speaking this should be done by
  397. * reading the control file, but it is less expensive to check
  398. * the hostname-part of the control file name. Also, this value
  399. * can be easily seen in 'lpq -l', while there is no easy way for
  400. * a user/operator to see the hostname in the control file.
  401. */
  402. if (jspec->wantedhost != NULL) {
  403. if (fnmatch(jspec->wantedhost, cf_hoststr, 0) != 0)
  404. goto nomatch;
  405. }
  406. /*
  407. * Check for a match on the user name. This has to be done
  408. * by reading the control file.
  409. */
  410. if (jspec->wanteduser != NULL) {
  411. cfinf = ctl_readcf("fakeq", jq->job_cfname);
  412. if (cfinf == NULL)
  413. goto nomatch;
  414. if (fnmatch(jspec->wanteduser, cfinf->cji_acctuser, 0) != 0)
  415. goto nomatch;
  416. }
  417. /* This job matches all of the specified criteria. */
  418. match = 1;
  419. jq->job_matched = 1; /* avoid matching the job twice */
  420. jspec->matchcnt++;
  421. if (jspec->wanteduser != NULL) {
  422. /*
  423. * If the user specified a userid (which may have been a
  424. * pattern), then the caller's "doentry()" routine might
  425. * want to know the userid of this job that matched.
  426. */
  427. jspec->matcheduser = strdup(cfinf->cji_acctuser);
  428. }
  429. #if DEBUG_SCANJS
  430. printf("\t [ job matched! ]\n");
  431. #endif
  432. nomatch:
  433. if (cfinf != NULL)
  434. ctl_freeinf(cfinf);
  435. return (match);
  436. }
  437. /*
  438. * Scan a queue for all jobs which match a jobspec. The queue is scanned
  439. * from top to bottom.
  440. *
  441. * The caller can provide a routine which will be executed for each job
  442. * that does match. Note that the processing routine might do anything
  443. * to the matched job -- including the removal of it.
  444. *
  445. * This returns the number of jobs which were matched.
  446. */
  447. int
  448. scanq_jobspec(int qcount, struct jobqueue **squeue, int sopts, struct
  449. jobspec_hdr *js_hdr, process_jqe doentry, void *doentryinfo)
  450. {
  451. struct jobqueue **qent;
  452. struct jobspec *jspec;
  453. int cnt, matched, total;
  454. if (qcount < 1)
  455. return (0);
  456. if (js_hdr == NULL)
  457. return (-1);
  458. /* The caller must specify one of the scanning orders */
  459. if ((sopts & (SCQ_JSORDER|SCQ_QORDER)) == 0)
  460. return (-1);
  461. total = 0;
  462. if (sopts & SCQ_JSORDER) {
  463. /*
  464. * For each job specification, scan through the queue
  465. * looking for every job that matches.
  466. */
  467. STAILQ_FOREACH(jspec, js_hdr, nextjs) {
  468. for (qent = squeue, cnt = 0; cnt < qcount;
  469. qent++, cnt++) {
  470. matched = match_jobspec(*qent, jspec);
  471. if (!matched)
  472. continue;
  473. total++;
  474. if (doentry != NULL)
  475. doentry(doentryinfo, *qent, jspec);
  476. if (jspec->matcheduser != NULL) {
  477. free(jspec->matcheduser);
  478. jspec->matcheduser = NULL;
  479. }
  480. }
  481. /*
  482. * The entire queue has been scanned for this
  483. * jobspec. Call the user's routine again with
  484. * a NULL queue-entry, so it can print out any
  485. * kind of per-jobspec summary.
  486. */
  487. if (doentry != NULL)
  488. doentry(doentryinfo, NULL, jspec);
  489. }
  490. } else {
  491. /*
  492. * For each job in the queue, check all of the job
  493. * specifications to see if any one of them matches
  494. * that job.
  495. */
  496. for (qent = squeue, cnt = 0; cnt < qcount;
  497. qent++, cnt++) {
  498. STAILQ_FOREACH(jspec, js_hdr, nextjs) {
  499. matched = match_jobspec(*qent, jspec);
  500. if (!matched)
  501. continue;
  502. total++;
  503. if (doentry != NULL)
  504. doentry(doentryinfo, *qent, jspec);
  505. if (jspec->matcheduser != NULL) {
  506. free(jspec->matcheduser);
  507. jspec->matcheduser = NULL;
  508. }
  509. /*
  510. * Once there is a match, then there is no
  511. * point in checking this same job against
  512. * all the other jobspec's.
  513. */
  514. break;
  515. }
  516. }
  517. }
  518. return (total);
  519. }