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