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