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