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