]> git.ipfire.org Git - thirdparty/cups.git/blob - scheduler/job.c
b947d58ca9561432484183d42d86586fea4d6beb
[thirdparty/cups.git] / scheduler / job.c
1 /*
2 * "$Id: job.c 7902 2008-09-03 14:20:17Z mike $"
3 *
4 * Job management routines for the Common UNIX Printing System (CUPS).
5 *
6 * Copyright 2007-2009 by Apple Inc.
7 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
8 *
9 * These coded instructions, statements, and computer programs are the
10 * property of Apple Inc. and are protected by Federal copyright
11 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
12 * which should have been included with this file. If this file is
13 * file is missing or damaged, see the license at "http://www.cups.org/".
14 *
15 * Contents:
16 *
17 * cupsdAddJob() - Add a new job to the job queue.
18 * cupsdCancelJobs() - Cancel all jobs for the given
19 * destination/user.
20 * cupsdCheckJobs() - Check the pending jobs and start any if the
21 * destination is available.
22 * cupsdCleanJobs() - Clean out old jobs.
23 * cupsdContinueJob() - Continue printing with the next file in a job.
24 * cupsdDeleteJob() - Free all memory used by a job.
25 * cupsdFreeAllJobs() - Free all jobs from memory.
26 * cupsdFindJob() - Find the specified job.
27 * cupsdGetPrinterJobCount() - Get the number of pending, processing, or held
28 * jobs in a printer or class.
29 * cupsdGetUserJobCount() - Get the number of pending, processing, or held
30 * jobs for a user.
31 * cupsdLoadAllJobs() - Load all jobs from disk.
32 * cupsdLoadJob() - Load a single job.
33 * cupsdMoveJob() - Move the specified job to a different
34 * destination.
35 * cupsdReleaseJob() - Release the specified job.
36 * cupsdRestartJob() - Restart the specified job.
37 * cupsdSaveAllJobs() - Save a summary of all jobs to disk.
38 * cupsdSaveJob() - Save a job to disk.
39 * cupsdSetJobHoldUntil() - Set the hold time for a job.
40 * cupsdSetJobPriority() - Set the priority of a job, moving it up/down
41 * in the list as needed.
42 * cupsdSetJobState() - Set the state of the specified print job.
43 * cupsdStopAllJobs() - Stop all print jobs.
44 * cupsdUnloadCompletedJobs() - Flush completed job history from memory.
45 * compare_active_jobs() - Compare the job IDs and priorities of two
46 * jobs.
47 * compare_jobs() - Compare the job IDs of two jobs.
48 * finalize_job() - Cleanup after job filter processes and support
49 * data.
50 * get_options() - Get a string containing the job options.
51 * ipp_length() - Compute the size of the buffer needed to hold
52 * the textual IPP attributes.
53 * load_job_cache() - Load jobs from the job.cache file.
54 * load_next_job_id() - Load the NextJobId value from the job.cache
55 * file.
56 * load_request_root() - Load jobs from the RequestRoot directory.
57 * set_hold_until() - Set the hold time and update job-hold-until
58 * attribute.
59 * set_time() - Set one of the "time-at-xyz" attributes.
60 * start_job() - Start a print job.
61 * stop_job() - Stop a print job.
62 * unload_job() - Unload a job from memory.
63 * update_job() - Read a status update from a job's filters.
64 * update_job_attrs() - Update the job-printer-* attributes.
65 */
66
67 /*
68 * Include necessary headers...
69 */
70
71 #include "cupsd.h"
72 #include <grp.h>
73 #include <cups/backend.h>
74 #include <cups/dir.h>
75
76
77 /*
78 * Design Notes for Job Management
79 * -------------------------------
80 *
81 * STATE CHANGES
82 *
83 * pending Do nothing/check jobs
84 * pending-held Send SIGTERM to filters and backend
85 * processing Do nothing/start job
86 * stopped Send SIGKILL to filters and backend
87 * canceled Send SIGTERM to filters and backend
88 * aborted Finalize
89 * completed Finalize
90 *
91 * Finalize clears the printer <-> job association, deletes the status
92 * buffer, closes all of the pipes, etc. and doesn't get run until all of
93 * the print processes are finished.
94 *
95 * UNLOADING OF JOBS (cupsdUnloadCompletedJobs)
96 *
97 * We unload the job attributes when they are not needed to reduce overall
98 * memory consumption. We don't unload jobs where job->state_value <
99 * IPP_JOB_STOPPED, job->printer != NULL, or job->access_time is recent.
100 *
101 * STARTING OF JOBS (start_job)
102 *
103 * When a job is started, a status buffer, several pipes, a security
104 * profile, and a backend process are created for the life of that job.
105 * These are shared for every file in a job. For remote print jobs, the
106 * IPP backend is provided with every file in the job and no filters are
107 * run.
108 *
109 * The job->printer member tracks which printer is printing a job, which
110 * can be different than the destination in job->dest for classes. The
111 * printer object also has a job pointer to track which job is being
112 * printed.
113 *
114 * PRINTING OF JOB FILES (cupsdContinueJob)
115 *
116 * Each file in a job is filtered by 0 or more programs. After getting the
117 * list of filters needed and the total cost, the job is either passed or
118 * put back to the processing state until the current FilterLevel comes down
119 * enough to allow printing.
120 *
121 * If we can print, we build a string for the print options and run each of
122 * the filters, piping the output from one into the next.
123 *
124 * JOB STATUS UPDATES (update_job)
125 *
126 * The update_job function gets called whenever there are pending messages
127 * on the status pipe. These generally are updates to the marker-*,
128 * printer-state-message, or printer-state-reasons attributes. On EOF,
129 * finalize_job is called to clean up.
130 *
131 * FINALIZING JOBS (finalize_job)
132 *
133 * When all filters and the backend are done, we set the job state to
134 * completed (no errors), aborted (filter errors or abort-job policy),
135 * pending-held (auth required or retry-job policy), or pending
136 * (retry-current-job or stop-printer policies) as appropriate.
137 *
138 * Then we close the pipes and free the status buffers and profiles.
139 *
140 * JOB FILE COMPLETION (process_children in main.c)
141 *
142 * For multiple-file jobs, process_children (in main.c) sees that all
143 * filters have exited and calls in to print the next file if there are
144 * more files in the job, otherwise it waits for the backend to exit and
145 * update_job to do the cleanup.
146 */
147
148
149 /*
150 * Local globals...
151 */
152
153 static mime_filter_t gziptoany_filter =
154 {
155 NULL, /* Source type */
156 NULL, /* Destination type */
157 0, /* Cost */
158 "gziptoany" /* Filter program to run */
159 };
160
161
162 /*
163 * Local functions...
164 */
165
166 static int compare_active_jobs(void *first, void *second, void *data);
167 static int compare_jobs(void *first, void *second, void *data);
168 static void finalize_job(cupsd_job_t *job);
169 static char *get_options(cupsd_job_t *job, int banner_page, char *copies,
170 size_t copies_size, char *title,
171 size_t title_size);
172 static int ipp_length(ipp_t *ipp);
173 static void load_job_cache(const char *filename);
174 static void load_next_job_id(const char *filename);
175 static void load_request_root(void);
176 static void set_hold_until(cupsd_job_t *job, time_t holdtime);
177 static void set_time(cupsd_job_t *job, const char *name);
178 static void start_job(cupsd_job_t *job, cupsd_printer_t *printer);
179 static void stop_job(cupsd_job_t *job, cupsd_jobaction_t action);
180 static void unload_job(cupsd_job_t *job);
181 static void update_job(cupsd_job_t *job);
182 static void update_job_attrs(cupsd_job_t *job, int do_message);
183
184
185 /*
186 * 'cupsdAddJob()' - Add a new job to the job queue.
187 */
188
189 cupsd_job_t * /* O - New job record */
190 cupsdAddJob(int priority, /* I - Job priority */
191 const char *dest) /* I - Job destination */
192 {
193 cupsd_job_t *job; /* New job record */
194
195
196 if ((job = calloc(sizeof(cupsd_job_t), 1)) == NULL)
197 return (NULL);
198
199 job->id = NextJobId ++;
200 job->priority = priority;
201 job->back_pipes[0] = -1;
202 job->back_pipes[1] = -1;
203 job->print_pipes[0] = -1;
204 job->print_pipes[1] = -1;
205 job->side_pipes[0] = -1;
206 job->side_pipes[1] = -1;
207 job->status_pipes[0] = -1;
208 job->status_pipes[1] = -1;
209
210 cupsdSetString(&job->dest, dest);
211
212 /*
213 * Add the new job to the "all jobs" and "active jobs" lists...
214 */
215
216 cupsArrayAdd(Jobs, job);
217 cupsArrayAdd(ActiveJobs, job);
218
219 return (job);
220 }
221
222
223 /*
224 * 'cupsdCancelJobs()' - Cancel all jobs for the given destination/user.
225 */
226
227 void
228 cupsdCancelJobs(const char *dest, /* I - Destination to cancel */
229 const char *username, /* I - Username or NULL */
230 int purge) /* I - Purge jobs? */
231 {
232 cupsd_job_t *job; /* Current job */
233
234
235 for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
236 job;
237 job = (cupsd_job_t *)cupsArrayNext(Jobs))
238 {
239 if ((!job->dest || !job->username) && !cupsdLoadJob(job))
240 continue;
241
242 if ((!dest || !strcmp(job->dest, dest)) &&
243 (!username || !strcmp(job->username, username)))
244 {
245 /*
246 * Cancel all jobs matching this destination/user...
247 */
248
249 if (purge)
250 cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_PURGE,
251 "Job purged by user.");
252 else
253 cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_DEFAULT,
254 "Job canceled by user.");
255 }
256 }
257
258 cupsdCheckJobs();
259 }
260
261
262 /*
263 * 'cupsdCheckJobs()' - Check the pending jobs and start any if the destination
264 * is available.
265 */
266
267 void
268 cupsdCheckJobs(void)
269 {
270 cupsd_job_t *job; /* Current job in queue */
271 cupsd_printer_t *printer, /* Printer destination */
272 *pclass; /* Printer class destination */
273 ipp_attribute_t *attr; /* Job attribute */
274
275
276 cupsdLogMessage(CUPSD_LOG_DEBUG2,
277 "cupsdCheckJobs: %d active jobs, sleeping=%d, reload=%d",
278 cupsArrayCount(ActiveJobs), Sleeping, NeedReload);
279
280 for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
281 job;
282 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
283 {
284 /*
285 * Start held jobs if they are ready...
286 */
287
288 if (job->state_value == IPP_JOB_HELD &&
289 job->hold_until &&
290 job->hold_until < time(NULL))
291 {
292 if (job->pending_timeout)
293 {
294 /*
295 * This job is pending; check that we don't have an active Send-Document
296 * operation in progress on any of the client connections, then timeout
297 * the job so we can start printing...
298 */
299
300 cupsd_client_t *con; /* Current client connection */
301
302
303 for (con = (cupsd_client_t *)cupsArrayFirst(Clients);
304 con;
305 con = (cupsd_client_t *)cupsArrayNext(Clients))
306 if (con->request &&
307 con->request->request.op.operation_id == IPP_SEND_DOCUMENT)
308 break;
309
310 if (con)
311 continue;
312
313 if (cupsdTimeoutJob(job))
314 continue;
315 }
316
317 cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT,
318 "Job submission timed out.");
319 }
320
321 /*
322 * Start pending jobs if the destination is available...
323 */
324
325 if (job->state_value == IPP_JOB_PENDING && !NeedReload && !Sleeping &&
326 !job->printer)
327 {
328 printer = cupsdFindDest(job->dest);
329 pclass = NULL;
330
331 while (printer &&
332 (printer->type & (CUPS_PRINTER_IMPLICIT | CUPS_PRINTER_CLASS)))
333 {
334 /*
335 * If the class is remote, just pass it to the remote server...
336 */
337
338 pclass = printer;
339
340 if (pclass->state == IPP_PRINTER_STOPPED)
341 printer = NULL;
342 else if (pclass->type & CUPS_PRINTER_REMOTE)
343 break;
344 else
345 printer = cupsdFindAvailablePrinter(printer->name);
346 }
347
348 if (!printer && !pclass)
349 {
350 /*
351 * Whoa, the printer and/or class for this destination went away;
352 * cancel the job...
353 */
354
355 cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_PURGE,
356 "Job aborted because the destination printer/class "
357 "has gone away.");
358 }
359 else if (printer && !printer->holding_new_jobs)
360 {
361 /*
362 * See if the printer is available or remote and not printing a job;
363 * if so, start the job...
364 */
365
366 if (pclass)
367 {
368 /*
369 * Add/update a job-actual-printer-uri attribute for this job
370 * so that we know which printer actually printed the job...
371 */
372
373 if ((attr = ippFindAttribute(job->attrs, "job-actual-printer-uri",
374 IPP_TAG_URI)) != NULL)
375 cupsdSetString(&attr->values[0].string.text, printer->uri);
376 else
377 ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI,
378 "job-actual-printer-uri", NULL, printer->uri);
379
380 job->dirty = 1;
381 cupsdMarkDirty(CUPSD_DIRTY_JOBS);
382 }
383
384 if ((!(printer->type & CUPS_PRINTER_DISCOVERED) && /* Printer is local */
385 printer->state == IPP_PRINTER_IDLE) || /* and idle, OR */
386 ((printer->type & CUPS_PRINTER_DISCOVERED) && /* Printer is remote */
387 !printer->job)) /* and not printing */
388 {
389 /*
390 * Start the job...
391 */
392
393 start_job(job, printer);
394 }
395 }
396 }
397 }
398 }
399
400
401 /*
402 * 'cupsdCleanJobs()' - Clean out old jobs.
403 */
404
405 void
406 cupsdCleanJobs(void)
407 {
408 cupsd_job_t *job; /* Current job */
409
410
411 if (MaxJobs <= 0)
412 return;
413
414 for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
415 job && cupsArrayCount(Jobs) >= MaxJobs;
416 job = (cupsd_job_t *)cupsArrayNext(Jobs))
417 if (job->state_value >= IPP_JOB_CANCELED && !job->printer)
418 cupsdDeleteJob(job, CUPSD_JOB_PURGE);
419 }
420
421
422 /*
423 * 'cupsdContinueJob()' - Continue printing with the next file in a job.
424 */
425
426 void
427 cupsdContinueJob(cupsd_job_t *job) /* I - Job */
428 {
429 int i; /* Looping var */
430 int slot; /* Pipe slot */
431 cups_array_t *filters, /* Filters for job */
432 *prefilters; /* Filters with prefilters */
433 mime_filter_t *filter, /* Current filter */
434 *prefilter, /* Prefilter */
435 port_monitor; /* Port monitor filter */
436 char scheme[255]; /* Device URI scheme */
437 ipp_attribute_t *attr; /* Current attribute */
438 const char *ptr, /* Pointer into value */
439 *abort_message; /* Abort message */
440 struct stat backinfo; /* Backend file information */
441 int backroot; /* Run backend as root? */
442 int pid; /* Process ID of new filter process */
443 int banner_page; /* 1 if banner page, 0 otherwise */
444 int filterfds[2][2];/* Pipes used between filters */
445 int envc; /* Number of environment variables */
446 char **argv, /* Filter command-line arguments */
447 filename[1024], /* Job filename */
448 command[1024], /* Full path to command */
449 jobid[255], /* Job ID string */
450 title[IPP_MAX_NAME],
451 /* Job title string */
452 copies[255], /* # copies string */
453 *options, /* Options string */
454 *envp[MAX_ENV + 19],
455 /* Environment variables */
456 charset[255], /* CHARSET env variable */
457 class_name[255],/* CLASS env variable */
458 classification[1024],
459 /* CLASSIFICATION env variable */
460 content_type[1024],
461 /* CONTENT_TYPE env variable */
462 device_uri[1024],
463 /* DEVICE_URI env variable */
464 final_content_type[1024],
465 /* FINAL_CONTENT_TYPE env variable */
466 lang[255], /* LANG env variable */
467 #ifdef __APPLE__
468 apple_language[255],
469 /* APPLE_LANGUAGE env variable */
470 #endif /* __APPLE__ */
471 ppd[1024], /* PPD env variable */
472 printer_info[255],
473 /* PRINTER_INFO env variable */
474 printer_location[255],
475 /* PRINTER_LOCATION env variable */
476 printer_name[255],
477 /* PRINTER env variable */
478 rip_max_cache[255];
479 /* RIP_MAX_CACHE env variable */
480
481
482 cupsdLogMessage(CUPSD_LOG_DEBUG2,
483 "cupsdContinueJob(job=%p(%d)): current_file=%d, num_files=%d",
484 job, job->id, job->current_file, job->num_files);
485
486 /*
487 * Figure out what filters are required to convert from
488 * the source to the destination type...
489 */
490
491 FilterLevel -= job->cost;
492
493 filters = NULL;
494
495 if (job->printer->raw)
496 {
497 /*
498 * Remote jobs and raw queues go directly to the printer without
499 * filtering...
500 */
501
502 cupsdLogJob(job, CUPSD_LOG_DEBUG, "Sending job to queue tagged as raw...");
503
504 filters = NULL;
505 }
506 else
507 {
508 /*
509 * Local jobs get filtered...
510 */
511
512 filters = mimeFilter(MimeDatabase, job->filetypes[job->current_file],
513 job->printer->filetype, &(job->cost));
514
515 if (!filters)
516 {
517 cupsdLogJob(job, CUPSD_LOG_ERROR,
518 "Unable to convert file %d to printable format!",
519 job->current_file);
520
521 job->current_file ++;
522
523 if (job->current_file == job->num_files)
524 cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_DEFAULT,
525 "Aborting job because it cannot be printed.");
526
527 return;
528 }
529
530 /*
531 * Remove NULL ("-") filters...
532 */
533
534 for (filter = (mime_filter_t *)cupsArrayFirst(filters);
535 filter;
536 filter = (mime_filter_t *)cupsArrayNext(filters))
537 if (!strcmp(filter->filter, "-"))
538 cupsArrayRemove(filters, filter);
539
540 if (cupsArrayCount(filters) == 0)
541 {
542 cupsArrayDelete(filters);
543 filters = NULL;
544 }
545
546 /*
547 * If this printer has any pre-filters, insert the required pre-filter
548 * in the filters array...
549 */
550
551 if (job->printer->prefiltertype && filters)
552 {
553 prefilters = cupsArrayNew(NULL, NULL);
554
555 for (filter = (mime_filter_t *)cupsArrayFirst(filters);
556 filter;
557 filter = (mime_filter_t *)cupsArrayNext(filters))
558 {
559 if ((prefilter = mimeFilterLookup(MimeDatabase, filter->src,
560 job->printer->prefiltertype)))
561 {
562 cupsArrayAdd(prefilters, prefilter);
563 job->cost += prefilter->cost;
564 }
565
566 cupsArrayAdd(prefilters, filter);
567 }
568
569 cupsArrayDelete(filters);
570 filters = prefilters;
571 }
572 }
573
574 /*
575 * Set a minimum cost of 100 for all jobs so that FilterLimit
576 * works with raw queues and other low-cost paths.
577 */
578
579 if (job->cost < 100)
580 job->cost = 100;
581
582 /*
583 * See if the filter cost is too high...
584 */
585
586 if ((FilterLevel + job->cost) > FilterLimit && FilterLevel > 0 &&
587 FilterLimit > 0)
588 {
589 /*
590 * Don't print this job quite yet...
591 */
592
593 cupsArrayDelete(filters);
594
595 cupsdLogJob(job, CUPSD_LOG_INFO,
596 "Holding because filter limit has been reached.");
597 cupsdLogJob(job, CUPSD_LOG_DEBUG2,
598 "cupsdContinueJob: file=%d, cost=%d, level=%d, limit=%d",
599 job->current_file, job->cost, FilterLevel,
600 FilterLimit);
601 return;
602 }
603
604 FilterLevel += job->cost;
605
606 /*
607 * Add decompression/raw filter as needed...
608 */
609
610 if ((!job->printer->raw && job->compressions[job->current_file]) ||
611 (!filters && !job->printer->remote &&
612 (job->num_files > 1 || !strncmp(job->printer->device_uri, "file:", 5))))
613 {
614 /*
615 * Add gziptoany filter to the front of the list...
616 */
617
618 if (!filters)
619 filters = cupsArrayNew(NULL, NULL);
620
621 if (!cupsArrayInsert(filters, &gziptoany_filter))
622 {
623 cupsdLogJob(job, CUPSD_LOG_DEBUG,
624 "Unable to add decompression filter - %s", strerror(errno));
625
626 cupsArrayDelete(filters);
627
628 cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
629 "Stopping job because the scheduler ran out of "
630 "memory.");
631
632 FilterLevel -= job->cost;
633 return;
634 }
635 }
636
637 /*
638 * Add port monitor, if any...
639 */
640
641 if (job->printer->port_monitor)
642 {
643 /*
644 * Add port monitor to the end of the list...
645 */
646
647 if (!filters)
648 filters = cupsArrayNew(NULL, NULL);
649
650 port_monitor.src = NULL;
651 port_monitor.dst = NULL;
652 port_monitor.cost = 0;
653
654 snprintf(port_monitor.filter, sizeof(port_monitor.filter),
655 "%s/monitor/%s", ServerBin, job->printer->port_monitor);
656
657 if (!cupsArrayAdd(filters, &port_monitor))
658 {
659 cupsdLogJob(job, CUPSD_LOG_DEBUG,
660 "Unable to add port monitor - %s", strerror(errno));
661
662 cupsArrayDelete(filters);
663
664 cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
665 "Stopping job because the scheduler ran out of "
666 "memory.");
667
668 FilterLevel -= job->cost;
669 return;
670 }
671 }
672
673 /*
674 * Make sure we don't go over the "MAX_FILTERS" limit...
675 */
676
677 if (cupsArrayCount(filters) > MAX_FILTERS)
678 {
679 cupsdLogJob(job, CUPSD_LOG_DEBUG,
680 "Too many filters (%d > %d), unable to print!",
681 cupsArrayCount(filters), MAX_FILTERS);
682
683 cupsArrayDelete(filters);
684 cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
685 "Stopping job because it needs too many filters to "
686 "print.");
687
688 FilterLevel -= job->cost;
689 return;
690 }
691
692 /*
693 * Determine if we are printing a banner page or not...
694 */
695
696 if (job->job_sheets == NULL)
697 {
698 cupsdLogJob(job, CUPSD_LOG_DEBUG, "No job-sheets attribute.");
699 if ((job->job_sheets =
700 ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_ZERO)) != NULL)
701 cupsdLogJob(job, CUPSD_LOG_DEBUG,
702 "... but someone added one without setting job_sheets!");
703 }
704 else if (job->job_sheets->num_values == 1)
705 cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-sheets=%s",
706 job->job_sheets->values[0].string.text);
707 else
708 cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-sheets=%s,%s",
709 job->job_sheets->values[0].string.text,
710 job->job_sheets->values[1].string.text);
711
712 if (job->printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT))
713 banner_page = 0;
714 else if (job->job_sheets == NULL)
715 banner_page = 0;
716 else if (strcasecmp(job->job_sheets->values[0].string.text, "none") != 0 &&
717 job->current_file == 0)
718 banner_page = 1;
719 else if (job->job_sheets->num_values > 1 &&
720 strcasecmp(job->job_sheets->values[1].string.text, "none") != 0 &&
721 job->current_file == (job->num_files - 1))
722 banner_page = 1;
723 else
724 banner_page = 0;
725
726 if ((options = get_options(job, banner_page, copies, sizeof(copies), title,
727 sizeof(title))) == NULL)
728 {
729 cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
730 "Stopping job because the scheduler ran out of memory.");
731 cupsArrayDelete(filters);
732
733 FilterLevel -= job->cost;
734 return;
735 }
736
737 /*
738 * Build the command-line arguments for the filters. Each filter
739 * has 6 or 7 arguments:
740 *
741 * argv[0] = printer
742 * argv[1] = job ID
743 * argv[2] = username
744 * argv[3] = title
745 * argv[4] = # copies
746 * argv[5] = options
747 * argv[6] = filename (optional; normally stdin)
748 *
749 * This allows legacy printer drivers that use the old System V
750 * printing interface to be used by CUPS.
751 *
752 * For remote jobs, we send all of the files in the argument list.
753 */
754
755 if (job->printer->remote)
756 argv = calloc(7 + job->num_files, sizeof(char *));
757 else
758 argv = calloc(8, sizeof(char *));
759
760 if (!argv)
761 {
762 cupsdLogMessage(CUPSD_LOG_DEBUG, "Unable to allocate argument array - %s",
763 strerror(errno));
764 cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
765 "Stopping job because the scheduler ran out of memory.");
766 cupsArrayDelete(filters);
767
768 FilterLevel -= job->cost;
769 return;
770 }
771
772 sprintf(jobid, "%d", job->id);
773
774 argv[0] = job->printer->name;
775 argv[1] = jobid;
776 argv[2] = job->username;
777 argv[3] = title;
778 argv[4] = copies;
779 argv[5] = options;
780
781 if (job->printer->remote && job->num_files > 1)
782 {
783 for (i = 0; i < job->num_files; i ++)
784 {
785 snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
786 job->id, i + 1);
787 argv[6 + i] = strdup(filename);
788 }
789 }
790 else
791 {
792 snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
793 job->id, job->current_file + 1);
794 argv[6] = filename;
795 }
796
797 for (i = 0; argv[i]; i ++)
798 cupsdLogJob(job, CUPSD_LOG_DEBUG, "argv[%d]=\"%s\"", i, argv[i]);
799
800 /*
801 * Create environment variable strings for the filters...
802 */
803
804 attr = ippFindAttribute(job->attrs, "attributes-natural-language",
805 IPP_TAG_LANGUAGE);
806
807 #ifdef __APPLE__
808 strcpy(apple_language, "APPLE_LANGUAGE=");
809 _cupsAppleLanguage(attr->values[0].string.text,
810 apple_language + 15, sizeof(apple_language) - 15);
811 #endif /* __APPLE__ */
812
813 switch (strlen(attr->values[0].string.text))
814 {
815 default :
816 /*
817 * This is an unknown or badly formatted language code; use
818 * the POSIX locale...
819 */
820
821 strcpy(lang, "LANG=C");
822 break;
823
824 case 2 :
825 /*
826 * Just the language code (ll)...
827 */
828
829 snprintf(lang, sizeof(lang), "LANG=%s.UTF-8",
830 attr->values[0].string.text);
831 break;
832
833 case 5 :
834 /*
835 * Language and country code (ll-cc)...
836 */
837
838 snprintf(lang, sizeof(lang), "LANG=%c%c_%c%c.UTF-8",
839 attr->values[0].string.text[0],
840 attr->values[0].string.text[1],
841 toupper(attr->values[0].string.text[3] & 255),
842 toupper(attr->values[0].string.text[4] & 255));
843 break;
844 }
845
846 if ((attr = ippFindAttribute(job->attrs, "document-format",
847 IPP_TAG_MIMETYPE)) != NULL &&
848 (ptr = strstr(attr->values[0].string.text, "charset=")) != NULL)
849 snprintf(charset, sizeof(charset), "CHARSET=%s", ptr + 8);
850 else
851 strlcpy(charset, "CHARSET=utf-8", sizeof(charset));
852
853 snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s/%s",
854 job->filetypes[job->current_file]->super,
855 job->filetypes[job->current_file]->type);
856 snprintf(device_uri, sizeof(device_uri), "DEVICE_URI=%s",
857 job->printer->device_uri);
858 snprintf(ppd, sizeof(ppd), "PPD=%s/ppd/%s.ppd", ServerRoot,
859 job->printer->name);
860 snprintf(printer_info, sizeof(printer_name), "PRINTER_INFO=%s",
861 job->printer->info ? job->printer->info : "");
862 snprintf(printer_location, sizeof(printer_name), "PRINTER_LOCATION=%s",
863 job->printer->location ? job->printer->location : "");
864 snprintf(printer_name, sizeof(printer_name), "PRINTER=%s", job->printer->name);
865 snprintf(rip_max_cache, sizeof(rip_max_cache), "RIP_MAX_CACHE=%s", RIPCache);
866
867 envc = cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0])));
868
869 envp[envc ++] = charset;
870 envp[envc ++] = lang;
871 #ifdef __APPLE__
872 envp[envc ++] = apple_language;
873 #endif /* __APPLE__ */
874 envp[envc ++] = ppd;
875 envp[envc ++] = rip_max_cache;
876 envp[envc ++] = content_type;
877 envp[envc ++] = device_uri;
878 envp[envc ++] = printer_info;
879 envp[envc ++] = printer_location;
880 envp[envc ++] = printer_name;
881 envp[envc ++] = banner_page ? "CUPS_FILETYPE=job-sheet" :
882 "CUPS_FILETYPE=document";
883
884 if (!job->printer->remote && !job->printer->raw)
885 {
886 filter = (mime_filter_t *)cupsArrayLast(filters);
887
888 if (job->printer->port_monitor)
889 filter = (mime_filter_t *)cupsArrayPrev(filters);
890
891 if (filter && filter->dst)
892 {
893 snprintf(final_content_type, sizeof(final_content_type),
894 "FINAL_CONTENT_TYPE=%s/%s",
895 filter->dst->super, filter->dst->type);
896 envp[envc ++] = final_content_type;
897 }
898 }
899
900 if (Classification && !banner_page)
901 {
902 if ((attr = ippFindAttribute(job->attrs, "job-sheets",
903 IPP_TAG_NAME)) == NULL)
904 snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
905 Classification);
906 else if (attr->num_values > 1 &&
907 strcmp(attr->values[1].string.text, "none") != 0)
908 snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
909 attr->values[1].string.text);
910 else
911 snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
912 attr->values[0].string.text);
913
914 envp[envc ++] = classification;
915 }
916
917 if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))
918 {
919 snprintf(class_name, sizeof(class_name), "CLASS=%s", job->dest);
920 envp[envc ++] = class_name;
921 }
922
923 if (job->auth_username)
924 envp[envc ++] = job->auth_username;
925 if (job->auth_domain)
926 envp[envc ++] = job->auth_domain;
927 if (job->auth_password)
928 envp[envc ++] = job->auth_password;
929
930 #ifdef HAVE_GSSAPI
931 if (job->ccname)
932 envp[envc ++] = job->ccname;
933 #endif /* HAVE_GSSAPI */
934
935 envp[envc] = NULL;
936
937 for (i = 0; i < envc; i ++)
938 if (!strncmp(envp[i], "AUTH_", 5))
939 cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"AUTH_%c****\"", i,
940 envp[i][5]);
941 else if (strncmp(envp[i], "DEVICE_URI=", 11))
942 cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"%s\"", i, envp[i]);
943 else
944 cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"DEVICE_URI=%s\"", i,
945 job->printer->sanitized_device_uri);
946
947 if (job->printer->remote)
948 job->current_file = job->num_files;
949 else
950 job->current_file ++;
951
952 /*
953 * Now create processes for all of the filters...
954 */
955
956 filterfds[0][0] = -1;
957 filterfds[0][1] = -1;
958 filterfds[1][0] = -1;
959 filterfds[1][1] = -1;
960
961 memset(job->filters, 0, sizeof(job->filters));
962
963 for (i = 0, slot = 0, filter = (mime_filter_t *)cupsArrayFirst(filters);
964 filter;
965 i ++, filter = (mime_filter_t *)cupsArrayNext(filters))
966 {
967 if (filter->filter[0] != '/')
968 snprintf(command, sizeof(command), "%s/filter/%s", ServerBin,
969 filter->filter);
970 else
971 strlcpy(command, filter->filter, sizeof(command));
972
973 if (i < (cupsArrayCount(filters) - 1))
974 {
975 if (cupsdOpenPipe(filterfds[slot]))
976 {
977 abort_message = "Stopping job because the scheduler could not create "
978 "the filter pipes.";
979
980 goto abort_job;
981 }
982 }
983 else
984 {
985 if (job->current_file == 1)
986 {
987 if (strncmp(job->printer->device_uri, "file:", 5) != 0)
988 {
989 if (cupsdOpenPipe(job->print_pipes))
990 {
991 abort_message = "Stopping job because the scheduler could not "
992 "create the backend pipes.";
993
994 goto abort_job;
995 }
996 }
997 else
998 {
999 job->print_pipes[0] = -1;
1000 if (!strcmp(job->printer->device_uri, "file:/dev/null") ||
1001 !strcmp(job->printer->device_uri, "file:///dev/null"))
1002 job->print_pipes[1] = -1;
1003 else
1004 {
1005 if (!strncmp(job->printer->device_uri, "file:/dev/", 10))
1006 job->print_pipes[1] = open(job->printer->device_uri + 5,
1007 O_WRONLY | O_EXCL);
1008 else if (!strncmp(job->printer->device_uri, "file:///dev/", 12))
1009 job->print_pipes[1] = open(job->printer->device_uri + 7,
1010 O_WRONLY | O_EXCL);
1011 else if (!strncmp(job->printer->device_uri, "file:///", 8))
1012 job->print_pipes[1] = open(job->printer->device_uri + 7,
1013 O_WRONLY | O_CREAT | O_TRUNC, 0600);
1014 else
1015 job->print_pipes[1] = open(job->printer->device_uri + 5,
1016 O_WRONLY | O_CREAT | O_TRUNC, 0600);
1017
1018 if (job->print_pipes[1] < 0)
1019 {
1020 abort_message = "Stopping job because the scheduler could not "
1021 "open the output file.";
1022
1023 goto abort_job;
1024 }
1025
1026 fcntl(job->print_pipes[1], F_SETFD,
1027 fcntl(job->print_pipes[1], F_GETFD) | FD_CLOEXEC);
1028 }
1029 }
1030 }
1031
1032 filterfds[slot][0] = job->print_pipes[0];
1033 filterfds[slot][1] = job->print_pipes[1];
1034 }
1035
1036 pid = cupsdStartProcess(command, argv, envp, filterfds[!slot][0],
1037 filterfds[slot][1], job->status_pipes[1],
1038 job->back_pipes[0], job->side_pipes[0], 0,
1039 job->profile, job->id, job->filters + i);
1040
1041 cupsdClosePipe(filterfds[!slot]);
1042
1043 if (pid == 0)
1044 {
1045 cupsdLogJob(job, CUPSD_LOG_ERROR, "Unable to start filter \"%s\" - %s.",
1046 filter->filter, strerror(errno));
1047
1048 abort_message = "Stopped job because the scheduler could not execute a "
1049 "filter.";
1050
1051 goto abort_job;
1052 }
1053
1054 cupsdLogJob(job, CUPSD_LOG_INFO, "Started filter %s (PID %d)", command,
1055 pid);
1056
1057 argv[6] = NULL;
1058 slot = !slot;
1059 }
1060
1061 cupsArrayDelete(filters);
1062 filters = NULL;
1063
1064 /*
1065 * Finally, pipe the final output into a backend process if needed...
1066 */
1067
1068 if (strncmp(job->printer->device_uri, "file:", 5) != 0)
1069 {
1070 if (job->current_file == 1 || job->printer->remote)
1071 {
1072 sscanf(job->printer->device_uri, "%254[^:]", scheme);
1073 snprintf(command, sizeof(command), "%s/backend/%s", ServerBin, scheme);
1074
1075 /*
1076 * See if the backend needs to run as root...
1077 */
1078
1079 if (RunUser)
1080 backroot = 0;
1081 else if (stat(command, &backinfo))
1082 backroot = 0;
1083 else
1084 backroot = !(backinfo.st_mode & (S_IRWXG | S_IRWXO));
1085
1086 argv[0] = job->printer->sanitized_device_uri;
1087
1088 filterfds[slot][0] = -1;
1089 filterfds[slot][1] = -1;
1090
1091 pid = cupsdStartProcess(command, argv, envp, filterfds[!slot][0],
1092 filterfds[slot][1], job->status_pipes[1],
1093 job->back_pipes[1], job->side_pipes[1],
1094 backroot, job->profile, job->id, &(job->backend));
1095
1096 if (pid == 0)
1097 {
1098 abort_message = "Stopping job because the sheduler could not execute "
1099 "the backend.";
1100
1101 goto abort_job;
1102 }
1103 else
1104 {
1105 cupsdLogJob(job, CUPSD_LOG_INFO, "Started backend %s (PID %d)",
1106 command, pid);
1107 }
1108 }
1109
1110 if (job->current_file == job->num_files)
1111 {
1112 cupsdClosePipe(job->print_pipes);
1113 cupsdClosePipe(job->back_pipes);
1114 cupsdClosePipe(job->side_pipes);
1115
1116 close(job->status_pipes[1]);
1117 job->status_pipes[1] = -1;
1118 }
1119 }
1120 else
1121 {
1122 filterfds[slot][0] = -1;
1123 filterfds[slot][1] = -1;
1124
1125 if (job->current_file == job->num_files)
1126 {
1127 cupsdClosePipe(job->print_pipes);
1128
1129 close(job->status_pipes[1]);
1130 job->status_pipes[1] = -1;
1131 }
1132 }
1133
1134 cupsdClosePipe(filterfds[slot]);
1135
1136 if (job->printer->remote && job->num_files > 1)
1137 {
1138 for (i = 0; i < job->num_files; i ++)
1139 free(argv[i + 6]);
1140 }
1141
1142 free(argv);
1143
1144 cupsdAddSelect(job->status_buffer->fd, (cupsd_selfunc_t)update_job, NULL,
1145 job);
1146
1147 cupsdAddEvent(CUPSD_EVENT_JOB_STATE, job->printer, job, "Job #%d started.",
1148 job->id);
1149
1150 return;
1151
1152
1153 /*
1154 * If we get here, we need to abort the current job and close out all
1155 * files and pipes...
1156 */
1157
1158 abort_job:
1159
1160 for (slot = 0; slot < 2; slot ++)
1161 cupsdClosePipe(filterfds[slot]);
1162
1163 cupsdClosePipe(job->status_pipes);
1164 cupsdStatBufDelete(job->status_buffer);
1165 job->status_buffer = NULL;
1166
1167 cupsArrayDelete(filters);
1168
1169 if (job->printer->remote && job->num_files > 1)
1170 {
1171 for (i = 0; i < job->num_files; i ++)
1172 free(argv[i + 6]);
1173 }
1174
1175 free(argv);
1176
1177 cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT, "%s",
1178 abort_message);
1179 }
1180
1181
1182 /*
1183 * 'cupsdDeleteJob()' - Free all memory used by a job.
1184 */
1185
1186 void
1187 cupsdDeleteJob(cupsd_job_t *job, /* I - Job */
1188 cupsd_jobaction_t action)/* I - Action */
1189 {
1190 if (job->printer)
1191 finalize_job(job);
1192
1193 if (action == CUPSD_JOB_PURGE)
1194 {
1195 /*
1196 * Remove the job info file...
1197 */
1198
1199 char filename[1024]; /* Job filename */
1200
1201 snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot,
1202 job->id);
1203 unlink(filename);
1204 }
1205
1206 cupsdClearString(&job->username);
1207 cupsdClearString(&job->dest);
1208 cupsdClearString(&job->auth_username);
1209 cupsdClearString(&job->auth_domain);
1210 cupsdClearString(&job->auth_password);
1211
1212 #ifdef HAVE_GSSAPI
1213 /*
1214 * Destroy the credential cache and clear the KRB5CCNAME env var string.
1215 */
1216
1217 if (job->ccache)
1218 {
1219 krb5_cc_destroy(KerberosContext, job->ccache);
1220 job->ccache = NULL;
1221 }
1222
1223 cupsdClearString(&job->ccname);
1224 #endif /* HAVE_GSSAPI */
1225
1226 if (job->num_files > 0)
1227 {
1228 free(job->compressions);
1229 free(job->filetypes);
1230
1231 job->num_files = 0;
1232 }
1233
1234 unload_job(job);
1235
1236 cupsArrayRemove(Jobs, job);
1237 cupsArrayRemove(ActiveJobs, job);
1238 cupsArrayRemove(PrintingJobs, job);
1239
1240 free(job);
1241 }
1242
1243
1244 /*
1245 * 'cupsdFreeAllJobs()' - Free all jobs from memory.
1246 */
1247
1248 void
1249 cupsdFreeAllJobs(void)
1250 {
1251 cupsd_job_t *job; /* Current job */
1252
1253
1254 if (!Jobs)
1255 return;
1256
1257 cupsdHoldSignals();
1258
1259 cupsdStopAllJobs(1);
1260 cupsdSaveAllJobs();
1261
1262 for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
1263 job;
1264 job = (cupsd_job_t *)cupsArrayNext(Jobs))
1265 cupsdDeleteJob(job, CUPSD_JOB_DEFAULT);
1266
1267 cupsdReleaseSignals();
1268 }
1269
1270
1271 /*
1272 * 'cupsdFindJob()' - Find the specified job.
1273 */
1274
1275 cupsd_job_t * /* O - Job data */
1276 cupsdFindJob(int id) /* I - Job ID */
1277 {
1278 cupsd_job_t key; /* Search key */
1279
1280
1281 key.id = id;
1282
1283 return ((cupsd_job_t *)cupsArrayFind(Jobs, &key));
1284 }
1285
1286
1287 /*
1288 * 'cupsdGetPrinterJobCount()' - Get the number of pending, processing,
1289 * or held jobs in a printer or class.
1290 */
1291
1292 int /* O - Job count */
1293 cupsdGetPrinterJobCount(
1294 const char *dest) /* I - Printer or class name */
1295 {
1296 int count; /* Job count */
1297 cupsd_job_t *job; /* Current job */
1298
1299
1300 for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0;
1301 job;
1302 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
1303 if (job->dest && !strcasecmp(job->dest, dest))
1304 count ++;
1305
1306 return (count);
1307 }
1308
1309
1310 /*
1311 * 'cupsdGetUserJobCount()' - Get the number of pending, processing,
1312 * or held jobs for a user.
1313 */
1314
1315 int /* O - Job count */
1316 cupsdGetUserJobCount(
1317 const char *username) /* I - Username */
1318 {
1319 int count; /* Job count */
1320 cupsd_job_t *job; /* Current job */
1321
1322
1323 for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0;
1324 job;
1325 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
1326 if (!strcasecmp(job->username, username))
1327 count ++;
1328
1329 return (count);
1330 }
1331
1332
1333 /*
1334 * 'cupsdLoadAllJobs()' - Load all jobs from disk.
1335 */
1336
1337 void
1338 cupsdLoadAllJobs(void)
1339 {
1340 char filename[1024]; /* Full filename of job.cache file */
1341 struct stat fileinfo, /* Information on job.cache file */
1342 dirinfo; /* Information on RequestRoot dir */
1343
1344
1345
1346 /*
1347 * Create the job arrays as needed...
1348 */
1349
1350 if (!Jobs)
1351 Jobs = cupsArrayNew(compare_jobs, NULL);
1352
1353 if (!ActiveJobs)
1354 ActiveJobs = cupsArrayNew(compare_active_jobs, NULL);
1355
1356 if (!PrintingJobs)
1357 PrintingJobs = cupsArrayNew(compare_jobs, NULL);
1358
1359 /*
1360 * See whether the job.cache file is older than the RequestRoot directory...
1361 */
1362
1363 snprintf(filename, sizeof(filename), "%s/job.cache", CacheDir);
1364
1365 if (stat(filename, &fileinfo))
1366 {
1367 fileinfo.st_mtime = 0;
1368
1369 if (errno != ENOENT)
1370 cupsdLogMessage(CUPSD_LOG_ERROR,
1371 "Unable to get file information for \"%s\" - %s",
1372 filename, strerror(errno));
1373 }
1374
1375 if (stat(RequestRoot, &dirinfo))
1376 {
1377 dirinfo.st_mtime = 0;
1378
1379 if (errno != ENOENT)
1380 cupsdLogMessage(CUPSD_LOG_ERROR,
1381 "Unable to get directory information for \"%s\" - %s",
1382 RequestRoot, strerror(errno));
1383 }
1384
1385 /*
1386 * Load the most recent source for job data...
1387 */
1388
1389 if (dirinfo.st_mtime > fileinfo.st_mtime)
1390 {
1391 load_request_root();
1392
1393 load_next_job_id(filename);
1394 }
1395 else
1396 load_job_cache(filename);
1397
1398 /*
1399 * Clean out old jobs as needed...
1400 */
1401
1402 if (MaxJobs > 0 && cupsArrayCount(Jobs) >= MaxJobs)
1403 cupsdCleanJobs();
1404 }
1405
1406
1407 /*
1408 * 'cupsdLoadJob()' - Load a single job.
1409 */
1410
1411 int /* O - 1 on success, 0 on failure */
1412 cupsdLoadJob(cupsd_job_t *job) /* I - Job */
1413 {
1414 char jobfile[1024]; /* Job filename */
1415 cups_file_t *fp; /* Job file */
1416 int fileid; /* Current file ID */
1417 ipp_attribute_t *attr; /* Job attribute */
1418 const char *dest; /* Destination name */
1419 cupsd_printer_t *destptr; /* Pointer to destination */
1420 mime_type_t **filetypes; /* New filetypes array */
1421 int *compressions; /* New compressions array */
1422
1423
1424 if (job->attrs)
1425 {
1426 if (job->state_value > IPP_JOB_STOPPED)
1427 job->access_time = time(NULL);
1428
1429 return (1);
1430 }
1431
1432 if ((job->attrs = ippNew()) == NULL)
1433 {
1434 cupsdLogJob(job, CUPSD_LOG_ERROR, "Ran out of memory for job attributes!");
1435 return (0);
1436 }
1437
1438 /*
1439 * Load job attributes...
1440 */
1441
1442 cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Loading attributes...", job->id);
1443
1444 snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, job->id);
1445 if ((fp = cupsFileOpen(jobfile, "r")) == NULL)
1446 {
1447 cupsdLogMessage(CUPSD_LOG_ERROR,
1448 "[Job %d] Unable to open job control file \"%s\" - %s!",
1449 job->id, jobfile, strerror(errno));
1450 goto error;
1451 }
1452
1453 if (ippReadIO(fp, (ipp_iocb_t)cupsFileRead, 1, NULL, job->attrs) != IPP_DATA)
1454 {
1455 cupsdLogMessage(CUPSD_LOG_ERROR,
1456 "[Job %d] Unable to read job control file \"%s\"!", job->id,
1457 jobfile);
1458 cupsFileClose(fp);
1459 goto error;
1460 }
1461
1462 cupsFileClose(fp);
1463
1464 /*
1465 * Copy attribute data to the job object...
1466 */
1467
1468 if (!ippFindAttribute(job->attrs, "time-at-creation", IPP_TAG_INTEGER))
1469 {
1470 cupsdLogMessage(CUPSD_LOG_ERROR,
1471 "[Job %d] Missing or bad time-at-creation attribute in "
1472 "control file!", job->id);
1473 goto error;
1474 }
1475
1476 if ((job->state = ippFindAttribute(job->attrs, "job-state",
1477 IPP_TAG_ENUM)) == NULL)
1478 {
1479 cupsdLogMessage(CUPSD_LOG_ERROR,
1480 "[Job %d] Missing or bad job-state attribute in control "
1481 "file!", job->id);
1482 goto error;
1483 }
1484
1485 job->state_value = (ipp_jstate_t)job->state->values[0].integer;
1486
1487 if (!job->dest)
1488 {
1489 if ((attr = ippFindAttribute(job->attrs, "job-printer-uri",
1490 IPP_TAG_URI)) == NULL)
1491 {
1492 cupsdLogMessage(CUPSD_LOG_ERROR,
1493 "[Job %d] No job-printer-uri attribute in control file!",
1494 job->id);
1495 goto error;
1496 }
1497
1498 if ((dest = cupsdValidateDest(attr->values[0].string.text, &(job->dtype),
1499 &destptr)) == NULL)
1500 {
1501 cupsdLogMessage(CUPSD_LOG_ERROR,
1502 "[Job %d] Unable to queue job for destination \"%s\"!",
1503 job->id, attr->values[0].string.text);
1504 goto error;
1505 }
1506
1507 cupsdSetString(&job->dest, dest);
1508 }
1509 else if ((destptr = cupsdFindDest(job->dest)) == NULL)
1510 {
1511 cupsdLogMessage(CUPSD_LOG_ERROR,
1512 "[Job %d] Unable to queue job for destination \"%s\"!",
1513 job->id, job->dest);
1514 goto error;
1515 }
1516
1517 job->sheets = ippFindAttribute(job->attrs, "job-media-sheets-completed",
1518 IPP_TAG_INTEGER);
1519 job->job_sheets = ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_NAME);
1520
1521 if (!job->priority)
1522 {
1523 if ((attr = ippFindAttribute(job->attrs, "job-priority",
1524 IPP_TAG_INTEGER)) == NULL)
1525 {
1526 cupsdLogMessage(CUPSD_LOG_ERROR,
1527 "[Job %d] Missing or bad job-priority attribute in "
1528 "control file!", job->id);
1529 goto error;
1530 }
1531
1532 job->priority = attr->values[0].integer;
1533 }
1534
1535 if (!job->username)
1536 {
1537 if ((attr = ippFindAttribute(job->attrs, "job-originating-user-name",
1538 IPP_TAG_NAME)) == NULL)
1539 {
1540 cupsdLogMessage(CUPSD_LOG_ERROR,
1541 "[Job %d] Missing or bad job-originating-user-name "
1542 "attribute in control file!", job->id);
1543 goto error;
1544 }
1545
1546 cupsdSetString(&job->username, attr->values[0].string.text);
1547 }
1548
1549 /*
1550 * Set the job hold-until time and state...
1551 */
1552
1553 if (job->state_value == IPP_JOB_HELD)
1554 {
1555 if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
1556 IPP_TAG_KEYWORD)) == NULL)
1557 attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
1558
1559 if (attr)
1560 cupsdSetJobHoldUntil(job, attr->values[0].string.text, CUPSD_JOB_DEFAULT);
1561 else
1562 {
1563 job->state->values[0].integer = IPP_JOB_PENDING;
1564 job->state_value = IPP_JOB_PENDING;
1565 }
1566 }
1567 else if (job->state_value == IPP_JOB_PROCESSING)
1568 {
1569 job->state->values[0].integer = IPP_JOB_PENDING;
1570 job->state_value = IPP_JOB_PENDING;
1571 }
1572
1573 if (!job->num_files)
1574 {
1575 /*
1576 * Find all the d##### files...
1577 */
1578
1579 for (fileid = 1; fileid < 10000; fileid ++)
1580 {
1581 snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot,
1582 job->id, fileid);
1583
1584 if (access(jobfile, 0))
1585 break;
1586
1587 cupsdLogMessage(CUPSD_LOG_DEBUG,
1588 "[Job %d] Auto-typing document file \"%s\"...", job->id,
1589 jobfile);
1590
1591 if (fileid > job->num_files)
1592 {
1593 if (job->num_files == 0)
1594 {
1595 compressions = (int *)calloc(fileid, sizeof(int));
1596 filetypes = (mime_type_t **)calloc(fileid, sizeof(mime_type_t *));
1597 }
1598 else
1599 {
1600 compressions = (int *)realloc(job->compressions,
1601 sizeof(int) * fileid);
1602 filetypes = (mime_type_t **)realloc(job->filetypes,
1603 sizeof(mime_type_t *) *
1604 fileid);
1605 }
1606
1607 if (!compressions || !filetypes)
1608 {
1609 cupsdLogMessage(CUPSD_LOG_ERROR,
1610 "[Job %d] Ran out of memory for job file types!",
1611 job->id);
1612 return (1);
1613 }
1614
1615 job->compressions = compressions;
1616 job->filetypes = filetypes;
1617 job->num_files = fileid;
1618 }
1619
1620 job->filetypes[fileid - 1] = mimeFileType(MimeDatabase, jobfile, NULL,
1621 job->compressions + fileid - 1);
1622
1623 if (!job->filetypes[fileid - 1])
1624 job->filetypes[fileid - 1] = mimeType(MimeDatabase, "application",
1625 "vnd.cups-raw");
1626 }
1627 }
1628
1629 /*
1630 * Load authentication information as needed...
1631 */
1632
1633 if (job->state_value < IPP_JOB_STOPPED)
1634 {
1635 snprintf(jobfile, sizeof(jobfile), "%s/a%05d", RequestRoot, job->id);
1636
1637 cupsdClearString(&job->auth_username);
1638 cupsdClearString(&job->auth_domain);
1639 cupsdClearString(&job->auth_password);
1640
1641 if ((fp = cupsFileOpen(jobfile, "r")) != NULL)
1642 {
1643 int i, /* Looping var */
1644 bytes; /* Size of auth data */
1645 char line[255], /* Line from file */
1646 data[255]; /* Decoded data */
1647
1648
1649 for (i = 0;
1650 i < destptr->num_auth_info_required &&
1651 cupsFileGets(fp, line, sizeof(line));
1652 i ++)
1653 {
1654 bytes = sizeof(data);
1655 httpDecode64_2(data, &bytes, line);
1656
1657 if (!strcmp(destptr->auth_info_required[i], "username"))
1658 cupsdSetStringf(&job->auth_username, "AUTH_USERNAME=%s", data);
1659 else if (!strcmp(destptr->auth_info_required[i], "domain"))
1660 cupsdSetStringf(&job->auth_domain, "AUTH_DOMAIN=%s", data);
1661 else if (!strcmp(destptr->auth_info_required[i], "password"))
1662 cupsdSetStringf(&job->auth_password, "AUTH_PASSWORD=%s", data);
1663 }
1664
1665 cupsFileClose(fp);
1666 }
1667 }
1668
1669 job->access_time = time(NULL);
1670 return (1);
1671
1672 /*
1673 * If we get here then something bad happened...
1674 */
1675
1676 error:
1677
1678 ippDelete(job->attrs);
1679 job->attrs = NULL;
1680 unlink(jobfile);
1681
1682 return (0);
1683 }
1684
1685
1686 /*
1687 * 'cupsdMoveJob()' - Move the specified job to a different destination.
1688 */
1689
1690 void
1691 cupsdMoveJob(cupsd_job_t *job, /* I - Job */
1692 cupsd_printer_t *p) /* I - Destination printer or class */
1693 {
1694 ipp_attribute_t *attr; /* job-printer-uri attribute */
1695 const char *olddest; /* Old destination */
1696 cupsd_printer_t *oldp; /* Old pointer */
1697
1698
1699 /*
1700 * Don't move completed jobs...
1701 */
1702
1703 if (job->state_value > IPP_JOB_STOPPED)
1704 return;
1705
1706 /*
1707 * Get the old destination...
1708 */
1709
1710 olddest = job->dest;
1711
1712 if (job->printer)
1713 oldp = job->printer;
1714 else
1715 oldp = cupsdFindDest(olddest);
1716
1717 /*
1718 * Change the destination information...
1719 */
1720
1721 cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT,
1722 "Stopping job prior to move.");
1723
1724 cupsdAddEvent(CUPSD_EVENT_JOB_CONFIG_CHANGED, oldp, job,
1725 "Job #%d moved from %s to %s.", job->id, olddest,
1726 p->name);
1727
1728 cupsdSetString(&job->dest, p->name);
1729 job->dtype = p->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE |
1730 CUPS_PRINTER_IMPLICIT);
1731
1732 if ((attr = ippFindAttribute(job->attrs, "job-printer-uri",
1733 IPP_TAG_URI)) != NULL)
1734 cupsdSetString(&(attr->values[0].string.text), p->uri);
1735
1736 cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, p, job,
1737 "Job #%d moved from %s to %s.", job->id, olddest,
1738 p->name);
1739
1740 job->dirty = 1;
1741 cupsdMarkDirty(CUPSD_DIRTY_JOBS);
1742 }
1743
1744
1745 /*
1746 * 'cupsdReleaseJob()' - Release the specified job.
1747 */
1748
1749 void
1750 cupsdReleaseJob(cupsd_job_t *job) /* I - Job */
1751 {
1752 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdReleaseJob(job=%p(%d))", job,
1753 job->id);
1754
1755 if (job->state_value == IPP_JOB_HELD)
1756 {
1757 /*
1758 * Add trailing banner as needed...
1759 */
1760
1761 if (job->pending_timeout)
1762 cupsdTimeoutJob(job);
1763
1764 cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT,
1765 "Job released by user.");
1766 }
1767 }
1768
1769
1770 /*
1771 * 'cupsdRestartJob()' - Restart the specified job.
1772 */
1773
1774 void
1775 cupsdRestartJob(cupsd_job_t *job) /* I - Job */
1776 {
1777 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdRestartJob(job=%p(%d))", job,
1778 job->id);
1779
1780 if (job->state_value == IPP_JOB_STOPPED || job->num_files)
1781 cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT,
1782 "Job restarted by user.");
1783 }
1784
1785
1786 /*
1787 * 'cupsdSaveAllJobs()' - Save a summary of all jobs to disk.
1788 */
1789
1790 void
1791 cupsdSaveAllJobs(void)
1792 {
1793 int i; /* Looping var */
1794 cups_file_t *fp; /* Job cache file */
1795 char temp[1024]; /* Temporary string */
1796 cupsd_job_t *job; /* Current job */
1797 time_t curtime; /* Current time */
1798 struct tm *curdate; /* Current date */
1799
1800
1801 snprintf(temp, sizeof(temp), "%s/job.cache", CacheDir);
1802 if ((fp = cupsFileOpen(temp, "w")) == NULL)
1803 {
1804 cupsdLogMessage(CUPSD_LOG_ERROR,
1805 "Unable to create job cache file \"%s\" - %s",
1806 temp, strerror(errno));
1807 return;
1808 }
1809
1810 cupsdLogMessage(CUPSD_LOG_INFO, "Saving job cache file \"%s\"...", temp);
1811
1812 /*
1813 * Restrict access to the file...
1814 */
1815
1816 fchown(cupsFileNumber(fp), getuid(), Group);
1817 fchmod(cupsFileNumber(fp), ConfigFilePerm);
1818
1819 /*
1820 * Write a small header to the file...
1821 */
1822
1823 curtime = time(NULL);
1824 curdate = localtime(&curtime);
1825 strftime(temp, sizeof(temp) - 1, "%Y-%m-%d %H:%M", curdate);
1826
1827 cupsFilePuts(fp, "# Job cache file for " CUPS_SVERSION "\n");
1828 cupsFilePrintf(fp, "# Written by cupsd on %s\n", temp);
1829 cupsFilePrintf(fp, "NextJobId %d\n", NextJobId);
1830
1831 /*
1832 * Write each job known to the system...
1833 */
1834
1835 for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
1836 job;
1837 job = (cupsd_job_t *)cupsArrayNext(Jobs))
1838 {
1839 cupsFilePrintf(fp, "<Job %d>\n", job->id);
1840 cupsFilePrintf(fp, "State %d\n", job->state_value);
1841 cupsFilePrintf(fp, "Priority %d\n", job->priority);
1842 cupsFilePrintf(fp, "HoldUntil %d\n", (int)job->hold_until);
1843 cupsFilePrintf(fp, "Username %s\n", job->username);
1844 cupsFilePrintf(fp, "Destination %s\n", job->dest);
1845 cupsFilePrintf(fp, "DestType %d\n", job->dtype);
1846 cupsFilePrintf(fp, "NumFiles %d\n", job->num_files);
1847 for (i = 0; i < job->num_files; i ++)
1848 cupsFilePrintf(fp, "File %d %s/%s %d\n", i + 1, job->filetypes[i]->super,
1849 job->filetypes[i]->type, job->compressions[i]);
1850 cupsFilePuts(fp, "</Job>\n");
1851 }
1852
1853 cupsFileClose(fp);
1854 }
1855
1856
1857 /*
1858 * 'cupsdSaveJob()' - Save a job to disk.
1859 */
1860
1861 void
1862 cupsdSaveJob(cupsd_job_t *job) /* I - Job */
1863 {
1864 char filename[1024]; /* Job control filename */
1865 cups_file_t *fp; /* Job file */
1866
1867
1868 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSaveJob(job=%p(%d)): job->attrs=%p",
1869 job, job->id, job->attrs);
1870
1871 snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, job->id);
1872
1873 if ((fp = cupsFileOpen(filename, "w")) == NULL)
1874 {
1875 cupsdLogMessage(CUPSD_LOG_ERROR,
1876 "[Job %d] Unable to create job control file \"%s\" - %s.",
1877 job->id, filename, strerror(errno));
1878 return;
1879 }
1880
1881 fchmod(cupsFileNumber(fp), 0600);
1882 fchown(cupsFileNumber(fp), RunUser, Group);
1883
1884 job->attrs->state = IPP_IDLE;
1885
1886 if (ippWriteIO(fp, (ipp_iocb_t)cupsFileWrite, 1, NULL,
1887 job->attrs) != IPP_DATA)
1888 cupsdLogMessage(CUPSD_LOG_ERROR,
1889 "[Job %d] Unable to write job control file!", job->id);
1890
1891 cupsFileClose(fp);
1892
1893 job->dirty = 0;
1894 }
1895
1896
1897 /*
1898 * 'cupsdSetJobHoldUntil()' - Set the hold time for a job.
1899 */
1900
1901 void
1902 cupsdSetJobHoldUntil(cupsd_job_t *job, /* I - Job */
1903 const char *when, /* I - When to resume */
1904 int update)/* I - Update job-hold-until attr? */
1905 {
1906 time_t curtime; /* Current time */
1907 struct tm *curdate; /* Current date */
1908 int hour; /* Hold hour */
1909 int minute; /* Hold minute */
1910 int second = 0; /* Hold second */
1911
1912
1913 cupsdLogMessage(CUPSD_LOG_DEBUG2,
1914 "cupsdSetJobHoldUntil(job=%p(%d), when=\"%s\", update=%d)",
1915 job, job->id, when, update);
1916
1917 if (update)
1918 {
1919 /*
1920 * Update the job-hold-until attribute...
1921 */
1922
1923 ipp_attribute_t *attr; /* job-hold-until attribute */
1924
1925 if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
1926 IPP_TAG_KEYWORD)) == NULL)
1927 attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
1928
1929 if (attr)
1930 cupsdSetString(&(attr->values[0].string.text), when);
1931 else
1932 attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD,
1933 "job-hold-until", NULL, when);
1934
1935 if (attr)
1936 {
1937 if (isdigit(when[0] & 255))
1938 attr->value_tag = IPP_TAG_NAME;
1939 else
1940 attr->value_tag = IPP_TAG_KEYWORD;
1941
1942 job->dirty = 1;
1943 cupsdMarkDirty(CUPSD_DIRTY_JOBS);
1944 }
1945 }
1946
1947 /*
1948 * Update the hold time...
1949 */
1950
1951 if (!strcmp(when, "indefinite") || !strcmp(when, "auth-info-required"))
1952 {
1953 /*
1954 * Hold indefinitely...
1955 */
1956
1957 job->hold_until = 0;
1958 }
1959 else if (!strcmp(when, "day-time"))
1960 {
1961 /*
1962 * Hold to 6am the next morning unless local time is < 6pm.
1963 */
1964
1965 curtime = time(NULL);
1966 curdate = localtime(&curtime);
1967
1968 if (curdate->tm_hour < 18)
1969 job->hold_until = curtime;
1970 else
1971 job->hold_until = curtime +
1972 ((29 - curdate->tm_hour) * 60 + 59 -
1973 curdate->tm_min) * 60 + 60 - curdate->tm_sec;
1974 }
1975 else if (!strcmp(when, "evening") || !strcmp(when, "night"))
1976 {
1977 /*
1978 * Hold to 6pm unless local time is > 6pm or < 6am.
1979 */
1980
1981 curtime = time(NULL);
1982 curdate = localtime(&curtime);
1983
1984 if (curdate->tm_hour < 6 || curdate->tm_hour >= 18)
1985 job->hold_until = curtime;
1986 else
1987 job->hold_until = curtime +
1988 ((17 - curdate->tm_hour) * 60 + 59 -
1989 curdate->tm_min) * 60 + 60 - curdate->tm_sec;
1990 }
1991 else if (!strcmp(when, "second-shift"))
1992 {
1993 /*
1994 * Hold to 4pm unless local time is > 4pm.
1995 */
1996
1997 curtime = time(NULL);
1998 curdate = localtime(&curtime);
1999
2000 if (curdate->tm_hour >= 16)
2001 job->hold_until = curtime;
2002 else
2003 job->hold_until = curtime +
2004 ((15 - curdate->tm_hour) * 60 + 59 -
2005 curdate->tm_min) * 60 + 60 - curdate->tm_sec;
2006 }
2007 else if (!strcmp(when, "third-shift"))
2008 {
2009 /*
2010 * Hold to 12am unless local time is < 8am.
2011 */
2012
2013 curtime = time(NULL);
2014 curdate = localtime(&curtime);
2015
2016 if (curdate->tm_hour < 8)
2017 job->hold_until = curtime;
2018 else
2019 job->hold_until = curtime +
2020 ((23 - curdate->tm_hour) * 60 + 59 -
2021 curdate->tm_min) * 60 + 60 - curdate->tm_sec;
2022 }
2023 else if (!strcmp(when, "weekend"))
2024 {
2025 /*
2026 * Hold to weekend unless we are in the weekend.
2027 */
2028
2029 curtime = time(NULL);
2030 curdate = localtime(&curtime);
2031
2032 if (curdate->tm_wday == 0 || curdate->tm_wday == 6)
2033 job->hold_until = curtime;
2034 else
2035 job->hold_until = curtime +
2036 (((5 - curdate->tm_wday) * 24 +
2037 (17 - curdate->tm_hour)) * 60 + 59 -
2038 curdate->tm_min) * 60 + 60 - curdate->tm_sec;
2039 }
2040 else if (sscanf(when, "%d:%d:%d", &hour, &minute, &second) >= 2)
2041 {
2042 /*
2043 * Hold to specified GMT time (HH:MM or HH:MM:SS)...
2044 */
2045
2046 curtime = time(NULL);
2047 curdate = gmtime(&curtime);
2048
2049 job->hold_until = curtime +
2050 ((hour - curdate->tm_hour) * 60 + minute -
2051 curdate->tm_min) * 60 + second - curdate->tm_sec;
2052
2053 /*
2054 * Hold until next day as needed...
2055 */
2056
2057 if (job->hold_until < curtime)
2058 job->hold_until += 24 * 60 * 60;
2059 }
2060
2061 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSetJobHoldUntil: hold_until=%d",
2062 (int)job->hold_until);
2063 }
2064
2065
2066 /*
2067 * 'cupsdSetJobPriority()' - Set the priority of a job, moving it up/down in
2068 * the list as needed.
2069 */
2070
2071 void
2072 cupsdSetJobPriority(
2073 cupsd_job_t *job, /* I - Job ID */
2074 int priority) /* I - New priority (0 to 100) */
2075 {
2076 ipp_attribute_t *attr; /* Job attribute */
2077
2078
2079 /*
2080 * Don't change completed jobs...
2081 */
2082
2083 if (job->state_value >= IPP_JOB_PROCESSING)
2084 return;
2085
2086 /*
2087 * Set the new priority and re-add the job into the active list...
2088 */
2089
2090 cupsArrayRemove(ActiveJobs, job);
2091
2092 job->priority = priority;
2093
2094 if ((attr = ippFindAttribute(job->attrs, "job-priority",
2095 IPP_TAG_INTEGER)) != NULL)
2096 attr->values[0].integer = priority;
2097 else
2098 ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-priority",
2099 priority);
2100
2101 cupsArrayAdd(ActiveJobs, job);
2102
2103 job->dirty = 1;
2104 cupsdMarkDirty(CUPSD_DIRTY_JOBS);
2105 }
2106
2107
2108 /*
2109 * 'cupsdSetJobState()' - Set the state of the specified print job.
2110 */
2111
2112 void
2113 cupsdSetJobState(
2114 cupsd_job_t *job, /* I - Job to cancel */
2115 ipp_jstate_t newstate, /* I - New job state */
2116 cupsd_jobaction_t action, /* I - Action to take */
2117 const char *message, /* I - Message to log */
2118 ...) /* I - Additional arguments as needed */
2119 {
2120 int i; /* Looping var */
2121 ipp_jstate_t oldstate; /* Old state */
2122 char filename[1024]; /* Job filename */
2123 ipp_attribute_t *attr; /* Job attribute */
2124
2125
2126 cupsdLogMessage(CUPSD_LOG_DEBUG2,
2127 "cupsdSetJobState(job=%p(%d), state=%d, newstate=%d, "
2128 "action=%d, message=\"%s\")", job, job->id, job->state_value,
2129 newstate, action, message ? message : "(null)");
2130
2131
2132 /*
2133 * Make sure we have the job attributes...
2134 */
2135
2136 if (!cupsdLoadJob(job))
2137 return;
2138
2139 /*
2140 * Don't do anything if the state is unchanged...
2141 */
2142
2143 if (newstate == (oldstate = job->state_value))
2144 return;
2145
2146 /*
2147 * Stop any processes that are working on the current job...
2148 */
2149
2150 if (oldstate == IPP_JOB_PROCESSING)
2151 stop_job(job, action != CUPSD_JOB_DEFAULT);
2152
2153 /*
2154 * Set the new job state...
2155 */
2156
2157 job->state->values[0].integer = newstate;
2158 job->state_value = newstate;
2159
2160 switch (newstate)
2161 {
2162 case IPP_JOB_PENDING :
2163 /*
2164 * Update job-hold-until as needed...
2165 */
2166
2167 if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
2168 IPP_TAG_KEYWORD)) == NULL)
2169 attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
2170
2171 if (attr)
2172 {
2173 attr->value_tag = IPP_TAG_KEYWORD;
2174 cupsdSetString(&(attr->values[0].string.text), "no-hold");
2175 }
2176
2177 default :
2178 break;
2179
2180 case IPP_JOB_ABORTED :
2181 case IPP_JOB_CANCELED :
2182 case IPP_JOB_COMPLETED :
2183 set_time(job, "time-at-completed");
2184 break;
2185 }
2186
2187 /*
2188 * Log message as needed...
2189 */
2190
2191 if (message)
2192 {
2193 char buffer[2048]; /* Message buffer */
2194 va_list ap; /* Pointer to additional arguments */
2195
2196 va_start(ap, message);
2197 vsnprintf(buffer, sizeof(buffer), message, ap);
2198 va_end(ap);
2199
2200 if (newstate > IPP_JOB_STOPPED)
2201 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, "%s", buffer);
2202 else
2203 cupsdAddEvent(CUPSD_EVENT_JOB_STATE, job->printer, job, "%s", buffer);
2204
2205 if (newstate == IPP_JOB_STOPPED || newstate == IPP_JOB_ABORTED)
2206 cupsdLogJob(job, CUPSD_LOG_ERROR, "%s", buffer);
2207 else
2208 cupsdLogJob(job, CUPSD_LOG_INFO, "%s", buffer);
2209 }
2210
2211 /*
2212 * Handle post-state-change actions...
2213 */
2214
2215 switch (newstate)
2216 {
2217 case IPP_JOB_PROCESSING :
2218 /*
2219 * Add the job to the "printing" list...
2220 */
2221
2222 if (!cupsArrayFind(PrintingJobs, job))
2223 cupsArrayAdd(PrintingJobs, job);
2224
2225 /*
2226 * Set the processing time...
2227 */
2228
2229 set_time(job, "time-at-processing");
2230
2231 case IPP_JOB_PENDING :
2232 case IPP_JOB_HELD :
2233 case IPP_JOB_STOPPED :
2234 /*
2235 * Make sure the job is in the active list...
2236 */
2237
2238 if (!cupsArrayFind(ActiveJobs, job))
2239 cupsArrayAdd(ActiveJobs, job);
2240
2241 /*
2242 * Save the job state to disk...
2243 */
2244
2245 job->dirty = 1;
2246 cupsdMarkDirty(CUPSD_DIRTY_JOBS);
2247 break;
2248
2249 case IPP_JOB_ABORTED :
2250 case IPP_JOB_CANCELED :
2251 case IPP_JOB_COMPLETED :
2252 /*
2253 * Expire job subscriptions since the job is now "completed"...
2254 */
2255
2256 cupsdExpireSubscriptions(NULL, job);
2257
2258 /*
2259 * Remove the job from the active list...
2260 */
2261
2262 cupsArrayRemove(ActiveJobs, job);
2263
2264 #ifdef __APPLE__
2265 /*
2266 * If we are going to sleep and the PrintingJobs count is now 0, allow the
2267 * sleep to happen immediately...
2268 */
2269
2270 if (Sleeping && cupsArrayCount(PrintingJobs) == 0)
2271 cupsdAllowSleep();
2272 #endif /* __APPLE__ */
2273
2274 /*
2275 * Remove any authentication data...
2276 */
2277
2278 snprintf(filename, sizeof(filename), "%s/a%05d", RequestRoot, job->id);
2279 if (cupsdRemoveFile(filename) && errno != ENOENT)
2280 cupsdLogMessage(CUPSD_LOG_ERROR,
2281 "Unable to remove authentication cache: %s",
2282 strerror(errno));
2283
2284 cupsdClearString(&job->auth_username);
2285 cupsdClearString(&job->auth_domain);
2286 cupsdClearString(&job->auth_password);
2287
2288 #ifdef HAVE_GSSAPI
2289 /*
2290 * Destroy the credential cache and clear the KRB5CCNAME env var string.
2291 */
2292
2293 if (job->ccache)
2294 {
2295 krb5_cc_destroy(KerberosContext, job->ccache);
2296 job->ccache = NULL;
2297 }
2298
2299 cupsdClearString(&job->ccname);
2300 #endif /* HAVE_GSSAPI */
2301
2302 /*
2303 * Remove the print file for good if we aren't preserving jobs or
2304 * files...
2305 */
2306
2307 if (!JobHistory || !JobFiles || action == CUPSD_JOB_PURGE)
2308 {
2309 for (i = 1; i <= job->num_files; i ++)
2310 {
2311 snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
2312 job->id, i);
2313 unlink(filename);
2314 }
2315
2316 if (job->num_files > 0)
2317 {
2318 free(job->filetypes);
2319 free(job->compressions);
2320
2321 job->num_files = 0;
2322 job->filetypes = NULL;
2323 job->compressions = NULL;
2324 }
2325 }
2326
2327 if (JobHistory && action != CUPSD_JOB_PURGE)
2328 {
2329 /*
2330 * Save job state info...
2331 */
2332
2333 job->dirty = 1;
2334 cupsdMarkDirty(CUPSD_DIRTY_JOBS);
2335 }
2336 else if (!job->printer)
2337 cupsdDeleteJob(job, CUPSD_JOB_PURGE);
2338 break;
2339 }
2340
2341 /*
2342 * Update the server "busy" state...
2343 */
2344
2345 cupsdSetBusyState();
2346 }
2347
2348
2349 /*
2350 * 'cupsdStopAllJobs()' - Stop all print jobs.
2351 */
2352
2353 void
2354 cupsdStopAllJobs(
2355 cupsd_jobaction_t action) /* I - Action */
2356 {
2357 cupsd_job_t *job; /* Current job */
2358
2359
2360 DEBUG_puts("cupsdStopAllJobs()");
2361
2362 for (job = (cupsd_job_t *)cupsArrayFirst(PrintingJobs);
2363 job;
2364 job = (cupsd_job_t *)cupsArrayNext(PrintingJobs))
2365 cupsdSetJobState(job, IPP_JOB_PENDING, action, NULL);
2366 }
2367
2368
2369 /*
2370 * 'cupsdUnloadCompletedJobs()' - Flush completed job history from memory.
2371 */
2372
2373 void
2374 cupsdUnloadCompletedJobs(void)
2375 {
2376 cupsd_job_t *job; /* Current job */
2377 time_t expire; /* Expiration time */
2378
2379
2380 expire = time(NULL) - 60;
2381
2382 for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
2383 job;
2384 job = (cupsd_job_t *)cupsArrayNext(Jobs))
2385 if (job->attrs && job->state_value >= IPP_JOB_STOPPED && !job->printer &&
2386 job->access_time < expire)
2387 {
2388 if (job->dirty)
2389 cupsdSaveJob(job);
2390
2391 unload_job(job);
2392 }
2393 }
2394
2395
2396 /*
2397 * 'compare_active_jobs()' - Compare the job IDs and priorities of two jobs.
2398 */
2399
2400 static int /* O - Difference */
2401 compare_active_jobs(void *first, /* I - First job */
2402 void *second, /* I - Second job */
2403 void *data) /* I - App data (not used) */
2404 {
2405 int diff; /* Difference */
2406
2407
2408 if ((diff = ((cupsd_job_t *)second)->priority -
2409 ((cupsd_job_t *)first)->priority) != 0)
2410 return (diff);
2411 else
2412 return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
2413 }
2414
2415
2416 /*
2417 * 'compare_jobs()' - Compare the job IDs of two jobs.
2418 */
2419
2420 static int /* O - Difference */
2421 compare_jobs(void *first, /* I - First job */
2422 void *second, /* I - Second job */
2423 void *data) /* I - App data (not used) */
2424 {
2425 return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
2426 }
2427
2428
2429 /*
2430 * 'finalize_job()' - Cleanup after job filter processes and support data.
2431 */
2432
2433 static void
2434 finalize_job(cupsd_job_t *job) /* I - Job */
2435 {
2436 ipp_pstate_t printer_state; /* New printer state value */
2437 ipp_jstate_t job_state; /* New job state value */
2438 const char *message; /* Message for job state */
2439 char buffer[1024]; /* Buffer for formatted messages */
2440
2441
2442 cupsdLogMessage(CUPSD_LOG_DEBUG2, "finalize_job(job=%p(%d))", job, job->id);
2443
2444 /*
2445 * Clear the "connecting-to-device" reason, which is only valid when a
2446 * printer is processing...
2447 */
2448
2449 cupsdSetPrinterReasons(job->printer, "-connecting-to-device");
2450
2451 /*
2452 * Similarly, clear the "offline-report" reason for non-USB devices since we
2453 * rarely have current information for network devices...
2454 */
2455
2456 if (strncmp(job->printer->device_uri, "usb:", 4))
2457 cupsdSetPrinterReasons(job->printer, "-offline-report");
2458
2459 /*
2460 * Free the security profile...
2461 */
2462
2463 cupsdDestroyProfile(job->profile);
2464 job->profile = NULL;
2465
2466 /*
2467 * Close pipes and status buffer...
2468 */
2469
2470 cupsdRemoveSelect(job->status_buffer->fd);
2471
2472 cupsdClosePipe(job->print_pipes);
2473 cupsdClosePipe(job->back_pipes);
2474 cupsdClosePipe(job->side_pipes);
2475 cupsdClosePipe(job->status_pipes);
2476
2477 cupsdStatBufDelete(job->status_buffer);
2478 job->status_buffer = NULL;
2479
2480 /*
2481 * Process the exit status...
2482 */
2483
2484 if (job->printer->state == IPP_PRINTER_PROCESSING)
2485 printer_state = IPP_PRINTER_IDLE;
2486 else
2487 printer_state = job->printer->state;
2488
2489 switch (job_state = job->state_value)
2490 {
2491 case IPP_JOB_PENDING :
2492 message = "Job paused.";
2493 break;
2494
2495 case IPP_JOB_HELD :
2496 message = "Job held.";
2497 break;
2498
2499 default :
2500 case IPP_JOB_PROCESSING :
2501 case IPP_JOB_COMPLETED :
2502 job_state = IPP_JOB_COMPLETED;
2503 message = "Job completed.";
2504 break;
2505
2506 case IPP_JOB_STOPPED :
2507 message = "Job stopped.";
2508 break;
2509
2510 case IPP_JOB_CANCELED :
2511 message = "Job canceled.";
2512 break;
2513
2514 case IPP_JOB_ABORTED :
2515 message = "Job aborted.";
2516 break;
2517 }
2518
2519 if (job->status < 0)
2520 {
2521 /*
2522 * Backend had errors...
2523 */
2524
2525 int exit_code; /* Exit code from backend */
2526
2527
2528 /*
2529 * Convert the status to an exit code. Due to the way the W* macros are
2530 * implemented on MacOS X (bug?), we have to store the exit status in a
2531 * variable first and then convert...
2532 */
2533
2534 exit_code = -job->status;
2535 if (WIFEXITED(exit_code))
2536 exit_code = WEXITSTATUS(exit_code);
2537 else
2538 exit_code = job->status;
2539
2540 cupsdLogJob(job, CUPSD_LOG_INFO, "Backend returned status %d (%s)",
2541 exit_code,
2542 exit_code == CUPS_BACKEND_FAILED ? "failed" :
2543 exit_code == CUPS_BACKEND_AUTH_REQUIRED ?
2544 "authentication required" :
2545 exit_code == CUPS_BACKEND_HOLD ? "hold job" :
2546 exit_code == CUPS_BACKEND_STOP ? "stop printer" :
2547 exit_code == CUPS_BACKEND_CANCEL ? "cancel job" :
2548 exit_code < 0 ? "crashed" : "unknown");
2549
2550 /*
2551 * Do what needs to be done...
2552 */
2553
2554 switch (exit_code)
2555 {
2556 default :
2557 case CUPS_BACKEND_FAILED :
2558 /*
2559 * Backend failure, use the error-policy to determine how to
2560 * act...
2561 */
2562
2563 if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))
2564 {
2565 /*
2566 * Queued on a class - mark the job as pending and we'll retry on
2567 * another printer...
2568 */
2569
2570 if (job_state == IPP_JOB_COMPLETED)
2571 {
2572 job_state = IPP_JOB_PENDING;
2573 message = "Retrying job on another printer.";
2574 }
2575 }
2576 else if (!strcmp(job->printer->error_policy, "retry-current-job"))
2577 {
2578 /*
2579 * The error policy is "retry-current-job" - mark the job as pending
2580 * and we'll retry on the same printer...
2581 */
2582
2583 if (job_state == IPP_JOB_COMPLETED)
2584 {
2585 job_state = IPP_JOB_PENDING;
2586 message = "Retrying job on same printer.";
2587 }
2588 }
2589 else if ((job->printer->type & CUPS_PRINTER_FAX) ||
2590 !strcmp(job->printer->error_policy, "retry-job"))
2591 {
2592 if (job_state == IPP_JOB_COMPLETED)
2593 {
2594 /*
2595 * The job was queued on a fax or the error policy is "retry-job" -
2596 * hold the job if the number of retries is less than the
2597 * JobRetryLimit, otherwise abort the job.
2598 */
2599
2600 job->tries ++;
2601
2602 if (job->tries >= JobRetryLimit)
2603 {
2604 /*
2605 * Too many tries...
2606 */
2607
2608 snprintf(buffer, sizeof(buffer),
2609 "Job aborted after %d unsuccessful attempts.",
2610 JobRetryLimit);
2611 job_state = IPP_JOB_ABORTED;
2612 message = buffer;
2613 }
2614 else
2615 {
2616 /*
2617 * Try again in N seconds...
2618 */
2619
2620 set_hold_until(job, time(NULL) + JobRetryInterval);
2621
2622 snprintf(buffer, sizeof(buffer),
2623 "Job held for %d seconds since it could not be sent.",
2624 JobRetryInterval);
2625 job_state = IPP_JOB_HELD;
2626 message = buffer;
2627 }
2628 }
2629 }
2630 else if (!strcmp(job->printer->error_policy, "abort-job") &&
2631 job_state == IPP_JOB_COMPLETED)
2632 {
2633 job_state = IPP_JOB_ABORTED;
2634 message = "Job aborted due to backend errors; please consult "
2635 "the error_log file for details.";
2636 }
2637 else
2638 {
2639 printer_state = IPP_PRINTER_STOPPED;
2640 message = "Printer stopped due to backend errors; please "
2641 "consult the error_log file for details.";
2642
2643 if (job_state == IPP_JOB_COMPLETED)
2644 job_state = IPP_JOB_PENDING;
2645 }
2646 break;
2647
2648 case CUPS_BACKEND_CANCEL :
2649 /*
2650 * Abort the job...
2651 */
2652
2653 if (job_state == IPP_JOB_COMPLETED)
2654 {
2655 job_state = IPP_JOB_ABORTED;
2656 message = "Job aborted due to backend errors; please consult "
2657 "the error_log file for details.";
2658 }
2659 break;
2660
2661 case CUPS_BACKEND_HOLD :
2662 if (job_state == IPP_JOB_COMPLETED)
2663 {
2664 /*
2665 * Hold the job...
2666 */
2667
2668 cupsdSetJobHoldUntil(job, "indefinite", 1);
2669
2670 job_state = IPP_JOB_HELD;
2671 message = "Job held indefinitely due to backend errors; please "
2672 "consult the error_log file for details.";
2673 }
2674 break;
2675
2676 case CUPS_BACKEND_STOP :
2677 /*
2678 * Stop the printer...
2679 */
2680
2681 printer_state = IPP_PRINTER_STOPPED;
2682 message = "Printer stopped due to backend errors; please "
2683 "consult the error_log file for details.";
2684
2685 if (job_state == IPP_JOB_COMPLETED)
2686 job_state = IPP_JOB_PENDING;
2687 break;
2688
2689 case CUPS_BACKEND_AUTH_REQUIRED :
2690 /*
2691 * Hold the job for authentication...
2692 */
2693
2694 if (job_state == IPP_JOB_COMPLETED)
2695 {
2696 cupsdSetJobHoldUntil(job, "auth-info-required", 1);
2697
2698 job_state = IPP_JOB_HELD;
2699 message = "Job held for authentication.";
2700 }
2701 break;
2702 }
2703 }
2704 else if (job->status > 0)
2705 {
2706 /*
2707 * Filter had errors; stop job...
2708 */
2709
2710 if (job_state == IPP_JOB_COMPLETED)
2711 {
2712 job_state = IPP_JOB_STOPPED;
2713 message = "Job stopped due to filter errors; please consult the "
2714 "error_log file for details.";
2715 }
2716 }
2717
2718 /*
2719 * Update the printer and job state.
2720 */
2721
2722 cupsdSetJobState(job, job_state, CUPSD_JOB_DEFAULT, "%s", message);
2723 cupsdSetPrinterState(job->printer, printer_state,
2724 printer_state == IPP_PRINTER_STOPPED);
2725 update_job_attrs(job, 0);
2726
2727 cupsArrayRemove(PrintingJobs, job);
2728
2729 /*
2730 * Clear the printer <-> job association...
2731 */
2732
2733 job->printer->job = NULL;
2734 job->printer = NULL;
2735
2736 /*
2737 * Try printing another job...
2738 */
2739
2740 if (printer_state != IPP_PRINTER_STOPPED)
2741 cupsdCheckJobs();
2742 }
2743
2744
2745 /*
2746 * 'get_options()' - Get a string containing the job options.
2747 */
2748
2749 static char * /* O - Options string */
2750 get_options(cupsd_job_t *job, /* I - Job */
2751 int banner_page, /* I - Printing a banner page? */
2752 char *copies, /* I - Copies buffer */
2753 size_t copies_size, /* I - Size of copies buffer */
2754 char *title, /* I - Title buffer */
2755 size_t title_size) /* I - Size of title buffer */
2756 {
2757 int i; /* Looping var */
2758 char *optptr, /* Pointer to options */
2759 *valptr; /* Pointer in value string */
2760 ipp_attribute_t *attr; /* Current attribute */
2761 static char *options = NULL;/* Full list of options */
2762 static int optlength = 0; /* Length of option buffer */
2763
2764
2765 /*
2766 * Building the options string is harder than it needs to be, but
2767 * for the moment we need to pass strings for command-line args and
2768 * not IPP attribute pointers... :)
2769 *
2770 * First allocate/reallocate the option buffer as needed...
2771 */
2772
2773 i = ipp_length(job->attrs);
2774
2775 if (i > optlength || !options)
2776 {
2777 if (!options)
2778 optptr = malloc(i);
2779 else
2780 optptr = realloc(options, i);
2781
2782 if (!optptr)
2783 {
2784 cupsdLogJob(job, CUPSD_LOG_CRIT,
2785 "Unable to allocate %d bytes for option buffer!", i);
2786 return (NULL);
2787 }
2788
2789 options = optptr;
2790 optlength = i;
2791 }
2792
2793 /*
2794 * Now loop through the attributes and convert them to the textual
2795 * representation used by the filters...
2796 */
2797
2798 optptr = options;
2799 *optptr = '\0';
2800
2801 snprintf(title, title_size, "%s-%d", job->printer->name, job->id);
2802 strlcpy(copies, "1", copies_size);
2803
2804 for (attr = job->attrs->attrs; attr != NULL; attr = attr->next)
2805 {
2806 if (!strcmp(attr->name, "copies") &&
2807 attr->value_tag == IPP_TAG_INTEGER)
2808 {
2809 /*
2810 * Don't use the # copies attribute if we are printing the job sheets...
2811 */
2812
2813 if (!banner_page)
2814 snprintf(copies, copies_size, "%d", attr->values[0].integer);
2815 }
2816 else if (!strcmp(attr->name, "job-name") &&
2817 (attr->value_tag == IPP_TAG_NAME ||
2818 attr->value_tag == IPP_TAG_NAMELANG))
2819 strlcpy(title, attr->values[0].string.text, title_size);
2820 else if (attr->group_tag == IPP_TAG_JOB)
2821 {
2822 /*
2823 * Filter out other unwanted attributes...
2824 */
2825
2826 if (attr->value_tag == IPP_TAG_MIMETYPE ||
2827 attr->value_tag == IPP_TAG_NAMELANG ||
2828 attr->value_tag == IPP_TAG_TEXTLANG ||
2829 (attr->value_tag == IPP_TAG_URI && strcmp(attr->name, "job-uuid")) ||
2830 attr->value_tag == IPP_TAG_URISCHEME ||
2831 attr->value_tag == IPP_TAG_BEGIN_COLLECTION) /* Not yet supported */
2832 continue;
2833
2834 if (!strncmp(attr->name, "time-", 5))
2835 continue;
2836
2837 if (!strncmp(attr->name, "job-", 4) &&
2838 strcmp(attr->name, "job-billing") &&
2839 strcmp(attr->name, "job-impressions") &&
2840 strcmp(attr->name, "job-originating-host-name") &&
2841 strcmp(attr->name, "job-uuid") &&
2842 !(job->printer->type & CUPS_PRINTER_REMOTE))
2843 continue;
2844
2845 if ((!strcmp(attr->name, "job-impressions") ||
2846 !strcmp(attr->name, "page-label") ||
2847 !strcmp(attr->name, "page-border") ||
2848 !strncmp(attr->name, "number-up", 9) ||
2849 !strcmp(attr->name, "page-ranges") ||
2850 !strcmp(attr->name, "page-set") ||
2851 !strcasecmp(attr->name, "AP_FIRSTPAGE_InputSlot") ||
2852 !strcasecmp(attr->name, "AP_FIRSTPAGE_ManualFeed") ||
2853 !strcasecmp(attr->name, "com.apple.print.PrintSettings."
2854 "PMTotalSidesImaged..n.") ||
2855 !strcasecmp(attr->name, "com.apple.print.PrintSettings."
2856 "PMTotalBeginPages..n.")) &&
2857 banner_page)
2858 continue;
2859
2860 /*
2861 * Otherwise add them to the list...
2862 */
2863
2864 if (optptr > options)
2865 strlcat(optptr, " ", optlength - (optptr - options));
2866
2867 if (attr->value_tag != IPP_TAG_BOOLEAN)
2868 {
2869 strlcat(optptr, attr->name, optlength - (optptr - options));
2870 strlcat(optptr, "=", optlength - (optptr - options));
2871 }
2872
2873 for (i = 0; i < attr->num_values; i ++)
2874 {
2875 if (i)
2876 strlcat(optptr, ",", optlength - (optptr - options));
2877
2878 optptr += strlen(optptr);
2879
2880 switch (attr->value_tag)
2881 {
2882 case IPP_TAG_INTEGER :
2883 case IPP_TAG_ENUM :
2884 snprintf(optptr, optlength - (optptr - options),
2885 "%d", attr->values[i].integer);
2886 break;
2887
2888 case IPP_TAG_BOOLEAN :
2889 if (!attr->values[i].boolean)
2890 strlcat(optptr, "no", optlength - (optptr - options));
2891
2892 case IPP_TAG_NOVALUE :
2893 strlcat(optptr, attr->name,
2894 optlength - (optptr - options));
2895 break;
2896
2897 case IPP_TAG_RANGE :
2898 if (attr->values[i].range.lower == attr->values[i].range.upper)
2899 snprintf(optptr, optlength - (optptr - options) - 1,
2900 "%d", attr->values[i].range.lower);
2901 else
2902 snprintf(optptr, optlength - (optptr - options) - 1,
2903 "%d-%d", attr->values[i].range.lower,
2904 attr->values[i].range.upper);
2905 break;
2906
2907 case IPP_TAG_RESOLUTION :
2908 snprintf(optptr, optlength - (optptr - options) - 1,
2909 "%dx%d%s", attr->values[i].resolution.xres,
2910 attr->values[i].resolution.yres,
2911 attr->values[i].resolution.units == IPP_RES_PER_INCH ?
2912 "dpi" : "dpc");
2913 break;
2914
2915 case IPP_TAG_STRING :
2916 case IPP_TAG_TEXT :
2917 case IPP_TAG_NAME :
2918 case IPP_TAG_KEYWORD :
2919 case IPP_TAG_CHARSET :
2920 case IPP_TAG_LANGUAGE :
2921 case IPP_TAG_URI :
2922 for (valptr = attr->values[i].string.text; *valptr;)
2923 {
2924 if (strchr(" \t\n\\\'\"", *valptr))
2925 *optptr++ = '\\';
2926 *optptr++ = *valptr++;
2927 }
2928
2929 *optptr = '\0';
2930 break;
2931
2932 default :
2933 break; /* anti-compiler-warning-code */
2934 }
2935 }
2936
2937 optptr += strlen(optptr);
2938 }
2939 }
2940
2941
2942 return (options);
2943 }
2944
2945
2946 /*
2947 * 'ipp_length()' - Compute the size of the buffer needed to hold
2948 * the textual IPP attributes.
2949 */
2950
2951 static int /* O - Size of attribute buffer */
2952 ipp_length(ipp_t *ipp) /* I - IPP request */
2953 {
2954 int bytes; /* Number of bytes */
2955 int i; /* Looping var */
2956 ipp_attribute_t *attr; /* Current attribute */
2957
2958
2959 /*
2960 * Loop through all attributes...
2961 */
2962
2963 bytes = 0;
2964
2965 for (attr = ipp->attrs; attr != NULL; attr = attr->next)
2966 {
2967 /*
2968 * Skip attributes that won't be sent to filters...
2969 */
2970
2971 if (attr->value_tag == IPP_TAG_MIMETYPE ||
2972 attr->value_tag == IPP_TAG_NAMELANG ||
2973 attr->value_tag == IPP_TAG_TEXTLANG ||
2974 attr->value_tag == IPP_TAG_URI ||
2975 attr->value_tag == IPP_TAG_URISCHEME)
2976 continue;
2977
2978 if (strncmp(attr->name, "time-", 5) == 0)
2979 continue;
2980
2981 /*
2982 * Add space for a leading space and commas between each value.
2983 * For the first attribute, the leading space isn't used, so the
2984 * extra byte can be used as the nul terminator...
2985 */
2986
2987 bytes ++; /* " " separator */
2988 bytes += attr->num_values; /* "," separators */
2989
2990 /*
2991 * Boolean attributes appear as "foo,nofoo,foo,nofoo", while
2992 * other attributes appear as "foo=value1,value2,...,valueN".
2993 */
2994
2995 if (attr->value_tag != IPP_TAG_BOOLEAN)
2996 bytes += strlen(attr->name);
2997 else
2998 bytes += attr->num_values * strlen(attr->name);
2999
3000 /*
3001 * Now add the size required for each value in the attribute...
3002 */
3003
3004 switch (attr->value_tag)
3005 {
3006 case IPP_TAG_INTEGER :
3007 case IPP_TAG_ENUM :
3008 /*
3009 * Minimum value of a signed integer is -2147483647, or 11 digits.
3010 */
3011
3012 bytes += attr->num_values * 11;
3013 break;
3014
3015 case IPP_TAG_BOOLEAN :
3016 /*
3017 * Add two bytes for each false ("no") value...
3018 */
3019
3020 for (i = 0; i < attr->num_values; i ++)
3021 if (!attr->values[i].boolean)
3022 bytes += 2;
3023 break;
3024
3025 case IPP_TAG_RANGE :
3026 /*
3027 * A range is two signed integers separated by a hyphen, or
3028 * 23 characters max.
3029 */
3030
3031 bytes += attr->num_values * 23;
3032 break;
3033
3034 case IPP_TAG_RESOLUTION :
3035 /*
3036 * A resolution is two signed integers separated by an "x" and
3037 * suffixed by the units, or 26 characters max.
3038 */
3039
3040 bytes += attr->num_values * 26;
3041 break;
3042
3043 case IPP_TAG_STRING :
3044 case IPP_TAG_TEXT :
3045 case IPP_TAG_NAME :
3046 case IPP_TAG_KEYWORD :
3047 case IPP_TAG_CHARSET :
3048 case IPP_TAG_LANGUAGE :
3049 case IPP_TAG_URI :
3050 /*
3051 * Strings can contain characters that need quoting. We need
3052 * at least 2 * len + 2 characters to cover the quotes and
3053 * any backslashes in the string.
3054 */
3055
3056 for (i = 0; i < attr->num_values; i ++)
3057 bytes += 2 * strlen(attr->values[i].string.text) + 2;
3058 break;
3059
3060 default :
3061 break; /* anti-compiler-warning-code */
3062 }
3063 }
3064
3065 return (bytes);
3066 }
3067
3068
3069 /*
3070 * 'load_job_cache()' - Load jobs from the job.cache file.
3071 */
3072
3073 static void
3074 load_job_cache(const char *filename) /* I - job.cache filename */
3075 {
3076 cups_file_t *fp; /* job.cache file */
3077 char line[1024], /* Line buffer */
3078 *value; /* Value on line */
3079 int linenum; /* Line number in file */
3080 cupsd_job_t *job; /* Current job */
3081 int jobid; /* Job ID */
3082 char jobfile[1024]; /* Job filename */
3083
3084
3085 /*
3086 * Open the job.cache file...
3087 */
3088
3089 if ((fp = cupsFileOpen(filename, "r")) == NULL)
3090 {
3091 if (errno != ENOENT)
3092 cupsdLogMessage(CUPSD_LOG_ERROR,
3093 "Unable to open job cache file \"%s\": %s",
3094 filename, strerror(errno));
3095
3096 load_request_root();
3097
3098 return;
3099 }
3100
3101 /*
3102 * Read entries from the job cache file and create jobs as needed.
3103 */
3104
3105 cupsdLogMessage(CUPSD_LOG_INFO, "Loading job cache file \"%s\"...",
3106 filename);
3107
3108 linenum = 0;
3109 job = NULL;
3110
3111 while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
3112 {
3113 if (!strcasecmp(line, "NextJobId"))
3114 {
3115 if (value)
3116 NextJobId = atoi(value);
3117 }
3118 else if (!strcasecmp(line, "<Job"))
3119 {
3120 if (job)
3121 {
3122 cupsdLogMessage(CUPSD_LOG_ERROR, "Missing </Job> directive on line %d!",
3123 linenum);
3124 continue;
3125 }
3126
3127 if (!value)
3128 {
3129 cupsdLogMessage(CUPSD_LOG_ERROR, "Missing job ID on line %d!", linenum);
3130 continue;
3131 }
3132
3133 jobid = atoi(value);
3134
3135 if (jobid < 1)
3136 {
3137 cupsdLogMessage(CUPSD_LOG_ERROR, "Bad job ID %d on line %d!", jobid,
3138 linenum);
3139 continue;
3140 }
3141
3142 snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, jobid);
3143 if (access(jobfile, 0))
3144 {
3145 cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Files have gone away!",
3146 jobid);
3147 continue;
3148 }
3149
3150 job = calloc(1, sizeof(cupsd_job_t));
3151 if (!job)
3152 {
3153 cupsdLogMessage(CUPSD_LOG_EMERG,
3154 "[Job %d] Unable to allocate memory for job!", jobid);
3155 break;
3156 }
3157
3158 job->id = jobid;
3159 job->back_pipes[0] = -1;
3160 job->back_pipes[1] = -1;
3161 job->print_pipes[0] = -1;
3162 job->print_pipes[1] = -1;
3163 job->side_pipes[0] = -1;
3164 job->side_pipes[1] = -1;
3165 job->status_pipes[0] = -1;
3166 job->status_pipes[1] = -1;
3167
3168 cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Loading from cache...",
3169 job->id);
3170 }
3171 else if (!job)
3172 {
3173 cupsdLogMessage(CUPSD_LOG_ERROR,
3174 "Missing <Job #> directive on line %d!", linenum);
3175 continue;
3176 }
3177 else if (!strcasecmp(line, "</Job>"))
3178 {
3179 cupsArrayAdd(Jobs, job);
3180
3181 if (job->state_value <= IPP_JOB_STOPPED)
3182 {
3183 cupsArrayAdd(ActiveJobs, job);
3184 cupsdLoadJob(job);
3185 }
3186
3187 job = NULL;
3188 }
3189 else if (!value)
3190 {
3191 cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d!", linenum);
3192 continue;
3193 }
3194 else if (!strcasecmp(line, "State"))
3195 {
3196 job->state_value = (ipp_jstate_t)atoi(value);
3197
3198 if (job->state_value < IPP_JOB_PENDING)
3199 job->state_value = IPP_JOB_PENDING;
3200 else if (job->state_value > IPP_JOB_COMPLETED)
3201 job->state_value = IPP_JOB_COMPLETED;
3202 }
3203 else if (!strcasecmp(line, "HoldUntil"))
3204 {
3205 job->hold_until = atoi(value);
3206 }
3207 else if (!strcasecmp(line, "Priority"))
3208 {
3209 job->priority = atoi(value);
3210 }
3211 else if (!strcasecmp(line, "Username"))
3212 {
3213 cupsdSetString(&job->username, value);
3214 }
3215 else if (!strcasecmp(line, "Destination"))
3216 {
3217 cupsdSetString(&job->dest, value);
3218 }
3219 else if (!strcasecmp(line, "DestType"))
3220 {
3221 job->dtype = (cups_ptype_t)atoi(value);
3222 }
3223 else if (!strcasecmp(line, "NumFiles"))
3224 {
3225 job->num_files = atoi(value);
3226
3227 if (job->num_files < 0)
3228 {
3229 cupsdLogMessage(CUPSD_LOG_ERROR, "Bad NumFiles value %d on line %d!",
3230 job->num_files, linenum);
3231 job->num_files = 0;
3232 continue;
3233 }
3234
3235 if (job->num_files > 0)
3236 {
3237 snprintf(jobfile, sizeof(jobfile), "%s/d%05d-001", RequestRoot,
3238 job->id);
3239 if (access(jobfile, 0))
3240 {
3241 cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Data files have gone away!",
3242 job->id);
3243 job->num_files = 0;
3244 continue;
3245 }
3246
3247 job->filetypes = calloc(job->num_files, sizeof(mime_type_t *));
3248 job->compressions = calloc(job->num_files, sizeof(int));
3249
3250 if (!job->filetypes || !job->compressions)
3251 {
3252 cupsdLogMessage(CUPSD_LOG_EMERG,
3253 "[Job %d] Unable to allocate memory for %d files!",
3254 job->id, job->num_files);
3255 break;
3256 }
3257 }
3258 }
3259 else if (!strcasecmp(line, "File"))
3260 {
3261 int number, /* File number */
3262 compression; /* Compression value */
3263 char super[MIME_MAX_SUPER], /* MIME super type */
3264 type[MIME_MAX_TYPE]; /* MIME type */
3265
3266
3267 if (sscanf(value, "%d%*[ \t]%15[^/]/%255s%d", &number, super, type,
3268 &compression) != 4)
3269 {
3270 cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File on line %d!", linenum);
3271 continue;
3272 }
3273
3274 if (number < 1 || number > job->num_files)
3275 {
3276 cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File number %d on line %d!",
3277 number, linenum);
3278 continue;
3279 }
3280
3281 number --;
3282
3283 job->compressions[number] = compression;
3284 job->filetypes[number] = mimeType(MimeDatabase, super, type);
3285
3286 if (!job->filetypes[number])
3287 {
3288 /*
3289 * If the original MIME type is unknown, auto-type it!
3290 */
3291
3292 cupsdLogMessage(CUPSD_LOG_ERROR,
3293 "[Job %d] Unknown MIME type %s/%s for file %d!",
3294 job->id, super, type, number + 1);
3295
3296 snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot,
3297 job->id, number + 1);
3298 job->filetypes[number] = mimeFileType(MimeDatabase, jobfile, NULL,
3299 job->compressions + number);
3300
3301 /*
3302 * If that didn't work, assume it is raw...
3303 */
3304
3305 if (!job->filetypes[number])
3306 job->filetypes[number] = mimeType(MimeDatabase, "application",
3307 "vnd.cups-raw");
3308 }
3309 }
3310 else
3311 cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown %s directive on line %d!",
3312 line, linenum);
3313 }
3314
3315 cupsFileClose(fp);
3316 }
3317
3318
3319 /*
3320 * 'load_next_job_id()' - Load the NextJobId value from the job.cache file.
3321 */
3322
3323 static void
3324 load_next_job_id(const char *filename) /* I - job.cache filename */
3325 {
3326 cups_file_t *fp; /* job.cache file */
3327 char line[1024], /* Line buffer */
3328 *value; /* Value on line */
3329 int linenum; /* Line number in file */
3330 int next_job_id; /* NextJobId value from line */
3331
3332
3333 /*
3334 * Read the NextJobId directive from the job.cache file and use
3335 * the value (if any).
3336 */
3337
3338 if ((fp = cupsFileOpen(filename, "r")) == NULL)
3339 {
3340 if (errno != ENOENT)
3341 cupsdLogMessage(CUPSD_LOG_ERROR,
3342 "Unable to open job cache file \"%s\": %s",
3343 filename, strerror(errno));
3344
3345 return;
3346 }
3347
3348 cupsdLogMessage(CUPSD_LOG_INFO,
3349 "Loading NextJobId from job cache file \"%s\"...", filename);
3350
3351 linenum = 0;
3352
3353 while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
3354 {
3355 if (!strcasecmp(line, "NextJobId"))
3356 {
3357 if (value)
3358 {
3359 next_job_id = atoi(value);
3360
3361 if (next_job_id > NextJobId)
3362 NextJobId = next_job_id;
3363 }
3364 break;
3365 }
3366 }
3367
3368 cupsFileClose(fp);
3369 }
3370
3371
3372 /*
3373 * 'load_request_root()' - Load jobs from the RequestRoot directory.
3374 */
3375
3376 static void
3377 load_request_root(void)
3378 {
3379 cups_dir_t *dir; /* Directory */
3380 cups_dentry_t *dent; /* Directory entry */
3381 cupsd_job_t *job; /* New job */
3382
3383
3384 /*
3385 * Open the requests directory...
3386 */
3387
3388 cupsdLogMessage(CUPSD_LOG_DEBUG, "Scanning %s for jobs...", RequestRoot);
3389
3390 if ((dir = cupsDirOpen(RequestRoot)) == NULL)
3391 {
3392 cupsdLogMessage(CUPSD_LOG_ERROR,
3393 "Unable to open spool directory \"%s\": %s",
3394 RequestRoot, strerror(errno));
3395 return;
3396 }
3397
3398 /*
3399 * Read all the c##### files...
3400 */
3401
3402 while ((dent = cupsDirRead(dir)) != NULL)
3403 if (strlen(dent->filename) >= 6 && dent->filename[0] == 'c')
3404 {
3405 /*
3406 * Allocate memory for the job...
3407 */
3408
3409 if ((job = calloc(sizeof(cupsd_job_t), 1)) == NULL)
3410 {
3411 cupsdLogMessage(CUPSD_LOG_ERROR, "Ran out of memory for jobs!");
3412 cupsDirClose(dir);
3413 return;
3414 }
3415
3416 /*
3417 * Assign the job ID...
3418 */
3419
3420 job->id = atoi(dent->filename + 1);
3421 job->back_pipes[0] = -1;
3422 job->back_pipes[1] = -1;
3423 job->print_pipes[0] = -1;
3424 job->print_pipes[1] = -1;
3425 job->side_pipes[0] = -1;
3426 job->side_pipes[1] = -1;
3427 job->status_pipes[0] = -1;
3428 job->status_pipes[1] = -1;
3429
3430 if (job->id >= NextJobId)
3431 NextJobId = job->id + 1;
3432
3433 /*
3434 * Load the job...
3435 */
3436
3437 cupsdLoadJob(job);
3438
3439 /*
3440 * Insert the job into the array, sorting by job priority and ID...
3441 */
3442
3443 cupsArrayAdd(Jobs, job);
3444
3445 if (job->state_value <= IPP_JOB_STOPPED)
3446 cupsArrayAdd(ActiveJobs, job);
3447 else
3448 unload_job(job);
3449 }
3450
3451 cupsDirClose(dir);
3452 }
3453
3454
3455 /*
3456 * 'set_hold_until()' - Set the hold time and update job-hold-until attribute.
3457 */
3458
3459 static void
3460 set_hold_until(cupsd_job_t *job, /* I - Job to update */
3461 time_t holdtime) /* I - Hold until time */
3462 {
3463 ipp_attribute_t *attr; /* job-hold-until attribute */
3464 struct tm *holddate; /* Hold date */
3465 char holdstr[64]; /* Hold time */
3466
3467
3468 /*
3469 * Set the hold_until value and hold the job...
3470 */
3471
3472 cupsdLogMessage(CUPSD_LOG_DEBUG, "set_hold_until: hold_until = %d",
3473 (int)holdtime);
3474
3475 job->state->values[0].integer = IPP_JOB_HELD;
3476 job->state_value = IPP_JOB_HELD;
3477 job->hold_until = holdtime;
3478
3479 /*
3480 * Update the job-hold-until attribute with a string representing GMT
3481 * time (HH:MM:SS)...
3482 */
3483
3484 holddate = gmtime(&holdtime);
3485 snprintf(holdstr, sizeof(holdstr), "%d:%d:%d", holddate->tm_hour,
3486 holddate->tm_min, holddate->tm_sec);
3487
3488 if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
3489 IPP_TAG_KEYWORD)) == NULL)
3490 attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
3491
3492 /*
3493 * Either add the attribute or update the value of the existing one
3494 */
3495
3496 if (attr == NULL)
3497 ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD, "job-hold-until",
3498 NULL, holdstr);
3499 else
3500 cupsdSetString(&attr->values[0].string.text, holdstr);
3501
3502 job->dirty = 1;
3503 cupsdMarkDirty(CUPSD_DIRTY_JOBS);
3504 }
3505
3506
3507 /*
3508 * 'set_time()' - Set one of the "time-at-xyz" attributes.
3509 */
3510
3511 static void
3512 set_time(cupsd_job_t *job, /* I - Job to update */
3513 const char *name) /* I - Name of attribute */
3514 {
3515 ipp_attribute_t *attr; /* Time attribute */
3516
3517
3518 if ((attr = ippFindAttribute(job->attrs, name, IPP_TAG_ZERO)) != NULL)
3519 {
3520 attr->value_tag = IPP_TAG_INTEGER;
3521 attr->values[0].integer = time(NULL);
3522 }
3523 }
3524
3525
3526 /*
3527 * 'start_job()' - Start a print job.
3528 */
3529
3530 static void
3531 start_job(cupsd_job_t *job, /* I - Job ID */
3532 cupsd_printer_t *printer) /* I - Printer to print job */
3533 {
3534 cupsdLogMessage(CUPSD_LOG_DEBUG2, "start_job(job=%p(%d), printer=%p(%s))",
3535 job, job->id, printer, printer->name);
3536
3537 /*
3538 * Make sure we have some files around before we try to print...
3539 */
3540
3541 if (job->num_files == 0)
3542 {
3543 cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_DEFAULT,
3544 "Aborting job because it has no files.");
3545 return;
3546 }
3547
3548 /*
3549 * Update the printer and job state to "processing"...
3550 */
3551
3552 if (!cupsdLoadJob(job))
3553 return;
3554
3555 cupsdSetJobState(job, IPP_JOB_PROCESSING, CUPSD_JOB_DEFAULT, NULL);
3556 cupsdSetPrinterState(printer, IPP_PRINTER_PROCESSING, 0);
3557
3558 job->cost = 0;
3559 job->current_file = 0;
3560 job->progress = 0;
3561 job->printer = printer;
3562 printer->job = job;
3563
3564 /*
3565 * Setup the last exit status and security profiles...
3566 */
3567
3568 job->status = 0;
3569 job->profile = cupsdCreateProfile(job->id);
3570
3571 /*
3572 * Create the status pipes and buffer...
3573 */
3574
3575 if (cupsdOpenPipe(job->status_pipes))
3576 {
3577 cupsdLogJob(job, CUPSD_LOG_DEBUG,
3578 "Unable to create job status pipes - %s.", strerror(errno));
3579
3580 cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
3581 "Job stopped because the scheduler could not create the "
3582 "job status pipes.");
3583
3584 cupsdDestroyProfile(job->profile);
3585 job->profile = NULL;
3586 return;
3587 }
3588
3589 job->status_buffer = cupsdStatBufNew(job->status_pipes[0], NULL);
3590 job->status_level = CUPSD_LOG_INFO;
3591
3592 /*
3593 * Create the backchannel pipes and make them non-blocking...
3594 */
3595
3596 if (cupsdOpenPipe(job->back_pipes))
3597 {
3598 cupsdLogJob(job, CUPSD_LOG_DEBUG,
3599 "Unable to create back-channel pipes - %s.", strerror(errno));
3600
3601 cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
3602 "Job stopped because the scheduler could not create the "
3603 "back-channel pipes.");
3604
3605 cupsdClosePipe(job->status_pipes);
3606 cupsdStatBufDelete(job->status_buffer);
3607 job->status_buffer = NULL;
3608
3609 cupsdDestroyProfile(job->profile);
3610 job->profile = NULL;
3611 return;
3612 }
3613
3614 fcntl(job->back_pipes[0], F_SETFL,
3615 fcntl(job->back_pipes[0], F_GETFL) | O_NONBLOCK);
3616 fcntl(job->back_pipes[1], F_SETFL,
3617 fcntl(job->back_pipes[1], F_GETFL) | O_NONBLOCK);
3618
3619 /*
3620 * Create the side-channel pipes and make them non-blocking...
3621 */
3622
3623 if (socketpair(AF_LOCAL, SOCK_STREAM, 0, job->side_pipes))
3624 {
3625 cupsdLogJob(job, CUPSD_LOG_DEBUG,
3626 "Unable to create side-channel pipes - %s.", strerror(errno));
3627
3628 cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
3629 "Job stopped because the scheduler could not create the "
3630 "side-channel pipes.");
3631
3632 cupsdClosePipe(job->back_pipes);
3633
3634 cupsdClosePipe(job->status_pipes);
3635 cupsdStatBufDelete(job->status_buffer);
3636 job->status_buffer = NULL;
3637
3638 cupsdDestroyProfile(job->profile);
3639 job->profile = NULL;
3640 return;
3641 }
3642
3643 fcntl(job->side_pipes[0], F_SETFL,
3644 fcntl(job->side_pipes[0], F_GETFL) | O_NONBLOCK);
3645 fcntl(job->side_pipes[1], F_SETFL,
3646 fcntl(job->side_pipes[1], F_GETFL) | O_NONBLOCK);
3647
3648 /*
3649 * Now start the first file in the job...
3650 */
3651
3652 cupsdContinueJob(job);
3653 }
3654
3655
3656 /*
3657 * 'stop_job()' - Stop a print job.
3658 */
3659
3660 static void
3661 stop_job(cupsd_job_t *job, /* I - Job */
3662 cupsd_jobaction_t action) /* I - Action */
3663 {
3664 int i; /* Looping var */
3665
3666
3667 cupsdLogMessage(CUPSD_LOG_DEBUG2, "stop_job(job=%p(%d), action=%d)", job,
3668 job->id, action);
3669
3670 FilterLevel -= job->cost;
3671 job->cost = 0;
3672
3673 for (i = 0; job->filters[i]; i ++)
3674 if (job->filters[i] > 0)
3675 cupsdEndProcess(job->filters[i], action == CUPSD_JOB_FORCE);
3676
3677 if (job->backend > 0)
3678 cupsdEndProcess(job->backend, action == CUPSD_JOB_FORCE);
3679 }
3680
3681
3682 /*
3683 * 'unload_job()' - Unload a job from memory.
3684 */
3685
3686 static void
3687 unload_job(cupsd_job_t *job) /* I - Job */
3688 {
3689 if (!job->attrs)
3690 return;
3691
3692 cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Unloading...", job->id);
3693
3694 ippDelete(job->attrs);
3695
3696 job->attrs = NULL;
3697 job->state = NULL;
3698 job->sheets = NULL;
3699 job->job_sheets = NULL;
3700 job->printer_message = NULL;
3701 job->printer_reasons = NULL;
3702 }
3703
3704
3705 /*
3706 * 'update_job()' - Read a status update from a job's filters.
3707 */
3708
3709 void
3710 update_job(cupsd_job_t *job) /* I - Job to check */
3711 {
3712 int i; /* Looping var */
3713 int copies; /* Number of copies printed */
3714 char message[1024], /* Message text */
3715 *ptr; /* Pointer update... */
3716 int loglevel, /* Log level for message */
3717 event = 0; /* Events? */
3718 static const char * const levels[] = /* Log levels */
3719 {
3720 "NONE",
3721 "EMERG",
3722 "ALERT",
3723 "CRIT",
3724 "ERROR",
3725 "WARN",
3726 "NOTICE",
3727 "INFO",
3728 "DEBUG",
3729 "DEBUG2"
3730 };
3731
3732
3733 /*
3734 * Get the printer associated with this job; if the printer is stopped for
3735 * any reason then job->printer will be reset to NULL, so make sure we have
3736 * a valid pointer...
3737 */
3738
3739 while ((ptr = cupsdStatBufUpdate(job->status_buffer, &loglevel,
3740 message, sizeof(message))) != NULL)
3741 {
3742 /*
3743 * Process page and printer state messages as needed...
3744 */
3745
3746 if (loglevel == CUPSD_LOG_PAGE)
3747 {
3748 /*
3749 * Page message; send the message to the page_log file and update the
3750 * job sheet count...
3751 */
3752
3753 cupsdLogJob(job, CUPSD_LOG_DEBUG, "PAGE: %s", message);
3754
3755 if (job->sheets)
3756 {
3757 if (!strncasecmp(message, "total ", 6))
3758 {
3759 /*
3760 * Got a total count of pages from a backend or filter...
3761 */
3762
3763 copies = atoi(message + 6);
3764 copies -= job->sheets->values[0].integer; /* Just track the delta */
3765 }
3766 else if (!sscanf(message, "%*d%d", &copies))
3767 copies = 1;
3768
3769 job->sheets->values[0].integer += copies;
3770
3771 if (job->printer->page_limit)
3772 {
3773 cupsd_quota_t *q = cupsdUpdateQuota(job->printer, job->username,
3774 copies, 0);
3775
3776 #ifdef __APPLE__
3777 if (AppleQuotas && q->page_count == -3)
3778 {
3779 /*
3780 * Quota limit exceeded, cancel job in progress immediately...
3781 */
3782
3783 cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_DEFAULT,
3784 "Canceled job because pages exceed user %s "
3785 "quota limit on printer %s (%s).",
3786 job->username, job->printer->name,
3787 job->printer->info);
3788 return;
3789 }
3790 #else
3791 (void)q;
3792 #endif /* __APPLE__ */
3793 }
3794 }
3795
3796 cupsdLogPage(job, message);
3797
3798 if (job->sheets)
3799 cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job,
3800 "Printed %d page(s).", job->sheets->values[0].integer);
3801 }
3802 else if (loglevel == CUPSD_LOG_STATE)
3803 {
3804 cupsdLogJob(job, CUPSD_LOG_DEBUG, "STATE: %s", message);
3805
3806 if (!strcmp(message, "paused"))
3807 {
3808 cupsdStopPrinter(job->printer, 1);
3809 return;
3810 }
3811 else
3812 {
3813 cupsdSetPrinterReasons(job->printer, message);
3814 cupsdAddPrinterHistory(job->printer);
3815 event |= CUPSD_EVENT_PRINTER_STATE;
3816 }
3817
3818 update_job_attrs(job, 0);
3819 }
3820 else if (loglevel == CUPSD_LOG_ATTR)
3821 {
3822 /*
3823 * Set attribute(s)...
3824 */
3825
3826 int num_attrs; /* Number of attributes */
3827 cups_option_t *attrs; /* Attributes */
3828 const char *attr; /* Attribute */
3829
3830
3831 cupsdLogJob(job, CUPSD_LOG_DEBUG, "ATTR: %s", message);
3832
3833 num_attrs = cupsParseOptions(message, 0, &attrs);
3834
3835 if ((attr = cupsGetOption("auth-info-required", num_attrs,
3836 attrs)) != NULL)
3837 {
3838 cupsdSetAuthInfoRequired(job->printer, attr, NULL);
3839 cupsdSetPrinterAttrs(job->printer);
3840
3841 if (job->printer->type & CUPS_PRINTER_DISCOVERED)
3842 cupsdMarkDirty(CUPSD_DIRTY_REMOTE);
3843 else
3844 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
3845 }
3846
3847 if ((attr = cupsGetOption("job-media-progress", num_attrs,
3848 attrs)) != NULL)
3849 {
3850 int progress = atoi(attr);
3851
3852
3853 if (progress >= 0 && progress <= 100)
3854 {
3855 job->progress = progress;
3856
3857 if (job->sheets)
3858 cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job,
3859 "Printing page %d, %d%%",
3860 job->sheets->values[0].integer, job->progress);
3861 }
3862 }
3863
3864 if ((attr = cupsGetOption("printer-alert", num_attrs, attrs)) != NULL)
3865 {
3866 cupsdSetString(&job->printer->alert, attr);
3867 event |= CUPSD_EVENT_PRINTER_STATE;
3868 }
3869
3870 if ((attr = cupsGetOption("printer-alert-description", num_attrs,
3871 attrs)) != NULL)
3872 {
3873 cupsdSetString(&job->printer->alert_description, attr);
3874 event |= CUPSD_EVENT_PRINTER_STATE;
3875 }
3876
3877 if ((attr = cupsGetOption("marker-colors", num_attrs, attrs)) != NULL)
3878 {
3879 cupsdSetPrinterAttr(job->printer, "marker-colors", (char *)attr);
3880 job->printer->marker_time = time(NULL);
3881 event |= CUPSD_EVENT_PRINTER_STATE;
3882 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
3883 }
3884
3885 if ((attr = cupsGetOption("marker-levels", num_attrs, attrs)) != NULL)
3886 {
3887 cupsdSetPrinterAttr(job->printer, "marker-levels", (char *)attr);
3888 job->printer->marker_time = time(NULL);
3889 event |= CUPSD_EVENT_PRINTER_STATE;
3890 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
3891 }
3892
3893 if ((attr = cupsGetOption("marker-low-levels", num_attrs, attrs)) != NULL)
3894 {
3895 cupsdSetPrinterAttr(job->printer, "marker-low-levels", (char *)attr);
3896 job->printer->marker_time = time(NULL);
3897 event |= CUPSD_EVENT_PRINTER_STATE;
3898 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
3899 }
3900
3901 if ((attr = cupsGetOption("marker-high-levels", num_attrs, attrs)) != NULL)
3902 {
3903 cupsdSetPrinterAttr(job->printer, "marker-high-levels", (char *)attr);
3904 job->printer->marker_time = time(NULL);
3905 event |= CUPSD_EVENT_PRINTER_STATE;
3906 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
3907 }
3908
3909 if ((attr = cupsGetOption("marker-message", num_attrs, attrs)) != NULL)
3910 {
3911 cupsdSetPrinterAttr(job->printer, "marker-message", (char *)attr);
3912 job->printer->marker_time = time(NULL);
3913 event |= CUPSD_EVENT_PRINTER_STATE;
3914 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
3915 }
3916
3917 if ((attr = cupsGetOption("marker-names", num_attrs, attrs)) != NULL)
3918 {
3919 cupsdSetPrinterAttr(job->printer, "marker-names", (char *)attr);
3920 job->printer->marker_time = time(NULL);
3921 event |= CUPSD_EVENT_PRINTER_STATE;
3922 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
3923 }
3924
3925 if ((attr = cupsGetOption("marker-types", num_attrs, attrs)) != NULL)
3926 {
3927 cupsdSetPrinterAttr(job->printer, "marker-types", (char *)attr);
3928 job->printer->marker_time = time(NULL);
3929 event |= CUPSD_EVENT_PRINTER_STATE;
3930 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
3931 }
3932
3933 cupsFreeOptions(num_attrs, attrs);
3934 }
3935 else if (loglevel == CUPSD_LOG_PPD)
3936 {
3937 /*
3938 * Set attribute(s)...
3939 */
3940
3941 int num_keywords; /* Number of keywords */
3942 cups_option_t *keywords; /* Keywords */
3943
3944
3945 cupsdLogJob(job, CUPSD_LOG_DEBUG, "PPD: %s", message);
3946
3947 num_keywords = cupsParseOptions(message, 0, &keywords);
3948
3949 if (cupsdUpdatePrinterPPD(job->printer, num_keywords, keywords))
3950 cupsdSetPrinterAttrs(job->printer);
3951
3952 cupsFreeOptions(num_keywords, keywords);
3953 }
3954 #ifdef __APPLE__
3955 else if (!strncmp(message, "recoverable:", 12))
3956 {
3957 ptr = message + 12;
3958 while (isspace(*ptr & 255))
3959 ptr ++;
3960
3961 if (*ptr)
3962 {
3963 cupsdSetPrinterReasons(job->printer,
3964 "+com.apple.print.recoverable-warning");
3965 cupsdSetString(&(job->printer->recoverable), ptr);
3966 cupsdAddPrinterHistory(job->printer);
3967 event |= CUPSD_EVENT_PRINTER_STATE;
3968 }
3969 }
3970 else if (!strncmp(message, "recovered:", 10))
3971 {
3972 cupsdSetPrinterReasons(job->printer,
3973 "-com.apple.print.recoverable-warning");
3974
3975 ptr = message + 10;
3976 while (isspace(*ptr & 255))
3977 ptr ++;
3978
3979 cupsdSetString(&(job->printer->recoverable), ptr);
3980 cupsdAddPrinterHistory(job->printer);
3981 event |= CUPSD_EVENT_PRINTER_STATE;
3982 }
3983 #endif /* __APPLE__ */
3984 else
3985 {
3986 if (loglevel != CUPSD_LOG_INFO && loglevel > LogLevel)
3987 cupsdLogJob(job, loglevel, "%s", message);
3988
3989 if (loglevel < CUPSD_LOG_DEBUG)
3990 {
3991 strlcpy(job->printer->state_message, message,
3992 sizeof(job->printer->state_message));
3993 cupsdAddPrinterHistory(job->printer);
3994
3995 event |= CUPSD_EVENT_PRINTER_STATE | CUPSD_EVENT_JOB_PROGRESS;
3996
3997 if (loglevel <= job->status_level)
3998 {
3999 /*
4000 * Some messages show in the job-printer-state-message attribute...
4001 */
4002
4003 if (loglevel != CUPSD_LOG_NOTICE)
4004 job->status_level = loglevel;
4005
4006 update_job_attrs(job, 1);
4007
4008 cupsdLogJob(job, CUPSD_LOG_DEBUG,
4009 "Set job-printer-state-message to \"%s\", "
4010 "current level=%s",
4011 job->printer_message->values[0].string.text,
4012 levels[job->status_level]);
4013 }
4014 }
4015 }
4016
4017 if (!strchr(job->status_buffer->buffer, '\n'))
4018 break;
4019 }
4020
4021 if (event & CUPSD_EVENT_PRINTER_STATE)
4022 cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, job->printer, NULL,
4023 (job->printer->type & CUPS_PRINTER_CLASS) ?
4024 "Class \"%s\" state changed." :
4025 "Printer \"%s\" state changed.",
4026 job->printer->name);
4027
4028 if (event & CUPSD_EVENT_JOB_PROGRESS)
4029 cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job,
4030 "%s", job->printer->state_message);
4031
4032 if (ptr == NULL && !job->status_buffer->bufused)
4033 {
4034 /*
4035 * See if all of the filters and the backend have returned their
4036 * exit statuses.
4037 */
4038
4039 for (i = 0; job->filters[i] < 0; i ++);
4040
4041 if (job->filters[i])
4042 {
4043 /*
4044 * EOF but we haven't collected the exit status of all filters...
4045 */
4046
4047 cupsdCheckProcess();
4048 return;
4049 }
4050
4051 if (job->current_file >= job->num_files && job->backend > 0)
4052 {
4053 /*
4054 * EOF but we haven't collected the exit status of the backend...
4055 */
4056
4057 cupsdCheckProcess();
4058 return;
4059 }
4060
4061 /*
4062 * Handle the end of job stuff...
4063 */
4064
4065 finalize_job(job);
4066
4067 /*
4068 * Check for new jobs...
4069 */
4070
4071 cupsdCheckJobs();
4072 }
4073 }
4074
4075
4076 /*
4077 * 'update_job_attrs()' - Update the job-printer-* attributes.
4078 */
4079
4080 void
4081 update_job_attrs(cupsd_job_t *job, /* I - Job to update */
4082 int do_message)/* I - 1 = copy job-printer-state message */
4083 {
4084 int i; /* Looping var */
4085 int num_reasons; /* Actual number of reasons */
4086 const char * const *reasons; /* Reasons */
4087 static const char *none = "none"; /* "none" reason */
4088
4089
4090 /*
4091 * Get/create the job-printer-state-* attributes...
4092 */
4093
4094 if (!job->printer_message)
4095 {
4096 if ((job->printer_message = ippFindAttribute(job->attrs,
4097 "job-printer-state-message",
4098 IPP_TAG_TEXT)) == NULL)
4099 job->printer_message = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_TEXT,
4100 "job-printer-state-message",
4101 NULL, "");
4102 }
4103
4104 if (!job->printer_reasons)
4105 job->printer_reasons = ippFindAttribute(job->attrs,
4106 "job-printer-state-reasons",
4107 IPP_TAG_KEYWORD);
4108
4109 /*
4110 * Copy or clear the printer-state-message value as needed...
4111 */
4112
4113 if (job->state_value != IPP_JOB_PROCESSING &&
4114 job->status_level == CUPSD_LOG_INFO)
4115 cupsdSetString(&(job->printer_message->values[0].string.text), "");
4116 else if (job->printer->state_message[0] && do_message)
4117 cupsdSetString(&(job->printer_message->values[0].string.text),
4118 job->printer->state_message);
4119
4120 /*
4121 * ... and the printer-state-reasons value...
4122 */
4123
4124 if (job->printer->num_reasons == 0)
4125 {
4126 num_reasons = 1;
4127 reasons = &none;
4128 }
4129 else
4130 {
4131 num_reasons = job->printer->num_reasons;
4132 reasons = (const char * const *)job->printer->reasons;
4133 }
4134
4135 if (!job->printer_reasons || job->printer_reasons->num_values != num_reasons)
4136 {
4137 /*
4138 * Replace/create a job-printer-state-reasons attribute...
4139 */
4140
4141 ippDeleteAttribute(job->attrs, job->printer_reasons);
4142
4143 job->printer_reasons = ippAddStrings(job->attrs,
4144 IPP_TAG_JOB, IPP_TAG_KEYWORD,
4145 "job-printer-state-reasons",
4146 num_reasons, NULL, NULL);
4147 }
4148 else
4149 {
4150 /*
4151 * Don't bother clearing the reason strings if they are the same...
4152 */
4153
4154 for (i = 0; i < num_reasons; i ++)
4155 if (strcmp(job->printer_reasons->values[i].string.text, reasons[i]))
4156 break;
4157
4158 if (i >= num_reasons)
4159 return;
4160
4161 /*
4162 * Not the same, so free the current strings...
4163 */
4164
4165 for (i = 0; i < num_reasons; i ++)
4166 _cupsStrFree(job->printer_reasons->values[i].string.text);
4167 }
4168
4169 /*
4170 * Copy the reasons...
4171 */
4172
4173 for (i = 0; i < num_reasons; i ++)
4174 job->printer_reasons->values[i].string.text = _cupsStrAlloc(reasons[i]);
4175 }
4176
4177
4178 /*
4179 * End of "$Id: job.c 7902 2008-09-03 14:20:17Z mike $".
4180 */