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