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