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   sprintf(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_IRWXO));
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 		temp[1024];		/* Temporary string */
2184   cupsd_job_t	*job;			/* Current job */
2185   time_t	curtime;		/* Current time */
2186   struct tm	curdate;		/* Current date */
2187 
2188 
2189   snprintf(filename, sizeof(filename), "%s/job.cache", CacheDir);
2190   if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm)) == NULL)
2191     return;
2192 
2193   cupsdLogMessage(CUPSD_LOG_INFO, "Saving job.cache...");
2194 
2195  /*
2196   * Write a small header to the file...
2197   */
2198 
2199   time(&curtime);
2200   localtime_r(&curtime, &curdate);
2201   strftime(temp, sizeof(temp) - 1, "%Y-%m-%d %H:%M", &curdate);
2202 
2203   cupsFilePuts(fp, "# Job cache file for " CUPS_SVERSION "\n");
2204   cupsFilePrintf(fp, "# Written by cupsd on %s\n", temp);
2205   cupsFilePrintf(fp, "NextJobId %d\n", NextJobId);
2206 
2207  /*
2208   * Write each job known to the system...
2209   */
2210 
2211   for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
2212        job;
2213        job = (cupsd_job_t *)cupsArrayNext(Jobs))
2214   {
2215     if (job->printer && job->printer->temporary)
2216     {
2217      /*
2218       * Don't save jobs on temporary printers...
2219       */
2220 
2221       continue;
2222     }
2223 
2224     cupsFilePrintf(fp, "<Job %d>\n", job->id);
2225     cupsFilePrintf(fp, "State %d\n", job->state_value);
2226     cupsFilePrintf(fp, "Created %ld\n", (long)job->creation_time);
2227     if (job->completed_time)
2228       cupsFilePrintf(fp, "Completed %ld\n", (long)job->completed_time);
2229     cupsFilePrintf(fp, "Priority %d\n", job->priority);
2230     if (job->hold_until)
2231       cupsFilePrintf(fp, "HoldUntil %ld\n", (long)job->hold_until);
2232     cupsFilePrintf(fp, "Username %s\n", job->username);
2233     if (job->name)
2234       cupsFilePutConf(fp, "Name", job->name);
2235     cupsFilePrintf(fp, "Destination %s\n", job->dest);
2236     cupsFilePrintf(fp, "DestType %d\n", job->dtype);
2237     cupsFilePrintf(fp, "KOctets %d\n", job->koctets);
2238     cupsFilePrintf(fp, "NumFiles %d\n", job->num_files);
2239     for (i = 0; i < job->num_files; i ++)
2240       cupsFilePrintf(fp, "File %d %s/%s %d\n", i + 1, job->filetypes[i]->super,
2241                      job->filetypes[i]->type, job->compressions[i]);
2242     cupsFilePuts(fp, "</Job>\n");
2243   }
2244 
2245   cupsdCloseCreatedConfFile(fp, filename);
2246 }
2247 
2248 
2249 /*
2250  * 'cupsdSaveJob()' - Save a job to disk.
2251  */
2252 
2253 void
cupsdSaveJob(cupsd_job_t * job)2254 cupsdSaveJob(cupsd_job_t *job)		/* I - Job */
2255 {
2256   char		filename[1024];		/* Job control filename */
2257   cups_file_t	*fp;			/* Job file */
2258 
2259 
2260   cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSaveJob(job=%p(%d)): job->attrs=%p",
2261                   job, job->id, job->attrs);
2262 
2263   if (job->printer && job->printer->temporary)
2264   {
2265    /*
2266     * Don't save jobs on temporary printers...
2267     */
2268 
2269     job->dirty = 0;
2270     return;
2271   }
2272 
2273   snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, job->id);
2274 
2275   if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm & 0600)) == NULL)
2276     return;
2277 
2278   fchown(cupsFileNumber(fp), RunUser, Group);
2279 
2280   job->attrs->state = IPP_IDLE;
2281 
2282   if (ippWriteIO(fp, (ipp_iocb_t)cupsFileWrite, 1, NULL,
2283                  job->attrs) != IPP_DATA)
2284   {
2285     cupsdLogJob(job, CUPSD_LOG_ERROR, "Unable to write job control file.");
2286     cupsFileClose(fp);
2287     return;
2288   }
2289 
2290   if (!cupsdCloseCreatedConfFile(fp, filename))
2291   {
2292    /*
2293     * Remove backup file and mark this job as clean...
2294     */
2295 
2296     strlcat(filename, ".O", sizeof(filename));
2297     unlink(filename);
2298 
2299     job->dirty = 0;
2300   }
2301 }
2302 
2303 
2304 /*
2305  * 'cupsdSetJobHoldUntil()' - Set the hold time for a job.
2306  */
2307 
2308 void
cupsdSetJobHoldUntil(cupsd_job_t * job,const char * when,int update)2309 cupsdSetJobHoldUntil(cupsd_job_t *job,	/* I - Job */
2310                      const char  *when,	/* I - When to resume */
2311 		     int         update)/* I - Update job-hold-until attr? */
2312 {
2313   time_t	curtime;		/* Current time */
2314   struct tm	curdate;		/* Current date */
2315   int		hour;			/* Hold hour */
2316   int		minute;			/* Hold minute */
2317   int		second = 0;		/* Hold second */
2318 
2319 
2320   cupsdLogMessage(CUPSD_LOG_DEBUG2,
2321                   "cupsdSetJobHoldUntil(job=%p(%d), when=\"%s\", update=%d)",
2322                   job, job->id, when, update);
2323 
2324   if (update)
2325   {
2326    /*
2327     * Update the job-hold-until attribute...
2328     */
2329 
2330     ipp_attribute_t *attr;		/* job-hold-until attribute */
2331 
2332     if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
2333 				 IPP_TAG_KEYWORD)) == NULL)
2334       attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
2335 
2336     if (attr)
2337       ippSetString(job->attrs, &attr, 0, when);
2338     else
2339       attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD,
2340                           "job-hold-until", NULL, when);
2341 
2342     if (attr)
2343     {
2344       if (isdigit(when[0] & 255))
2345 	attr->value_tag = IPP_TAG_NAME;
2346       else
2347 	attr->value_tag = IPP_TAG_KEYWORD;
2348 
2349       job->dirty = 1;
2350       cupsdMarkDirty(CUPSD_DIRTY_JOBS);
2351     }
2352 
2353   }
2354 
2355   if (strcmp(when, "no-hold"))
2356     ippSetString(job->attrs, &job->reasons, 0, "job-hold-until-specified");
2357   else
2358     ippSetString(job->attrs, &job->reasons, 0, "none");
2359 
2360  /*
2361   * Update the hold time...
2362   */
2363 
2364   job->cancel_time = 0;
2365 
2366   if (!strcmp(when, "indefinite") || !strcmp(when, "auth-info-required"))
2367   {
2368    /*
2369     * Hold indefinitely...
2370     */
2371 
2372     job->hold_until = 0;
2373 
2374     if (MaxHoldTime > 0)
2375       job->cancel_time = time(NULL) + MaxHoldTime;
2376   }
2377   else if (!strcmp(when, "day-time"))
2378   {
2379    /*
2380     * Hold to 6am the next morning unless local time is < 6pm.
2381     */
2382 
2383     time(&curtime);
2384     localtime_r(&curtime, &curdate);
2385 
2386     if (curdate.tm_hour < 18)
2387       job->hold_until = curtime;
2388     else
2389       job->hold_until = curtime +
2390                         ((29 - curdate.tm_hour) * 60 + 59 -
2391 			 curdate.tm_min) * 60 + 60 - curdate.tm_sec;
2392   }
2393   else if (!strcmp(when, "evening") || !strcmp(when, "night"))
2394   {
2395    /*
2396     * Hold to 6pm unless local time is > 6pm or < 6am.
2397     */
2398 
2399     time(&curtime);
2400     localtime_r(&curtime, &curdate);
2401 
2402     if (curdate.tm_hour < 6 || curdate.tm_hour >= 18)
2403       job->hold_until = curtime;
2404     else
2405       job->hold_until = curtime +
2406                         ((17 - curdate.tm_hour) * 60 + 59 -
2407 			 curdate.tm_min) * 60 + 60 - curdate.tm_sec;
2408   }
2409   else if (!strcmp(when, "second-shift"))
2410   {
2411    /*
2412     * Hold to 4pm unless local time is > 4pm.
2413     */
2414 
2415     time(&curtime);
2416     localtime_r(&curtime, &curdate);
2417 
2418     if (curdate.tm_hour >= 16)
2419       job->hold_until = curtime;
2420     else
2421       job->hold_until = curtime +
2422                         ((15 - curdate.tm_hour) * 60 + 59 -
2423 			 curdate.tm_min) * 60 + 60 - curdate.tm_sec;
2424   }
2425   else if (!strcmp(when, "third-shift"))
2426   {
2427    /*
2428     * Hold to 12am unless local time is < 8am.
2429     */
2430 
2431     time(&curtime);
2432     localtime_r(&curtime, &curdate);
2433 
2434     if (curdate.tm_hour < 8)
2435       job->hold_until = curtime;
2436     else
2437       job->hold_until = curtime +
2438                         ((23 - curdate.tm_hour) * 60 + 59 -
2439 			 curdate.tm_min) * 60 + 60 - curdate.tm_sec;
2440   }
2441   else if (!strcmp(when, "weekend"))
2442   {
2443    /*
2444     * Hold to weekend unless we are in the weekend.
2445     */
2446 
2447     time(&curtime);
2448     localtime_r(&curtime, &curdate);
2449 
2450     if (curdate.tm_wday == 0 || curdate.tm_wday == 6)
2451       job->hold_until = curtime;
2452     else
2453       job->hold_until = curtime +
2454                         (((5 - curdate.tm_wday) * 24 +
2455                           (17 - curdate.tm_hour)) * 60 + 59 -
2456 			   curdate.tm_min) * 60 + 60 - curdate.tm_sec;
2457   }
2458   else if (sscanf(when, "%d:%d:%d", &hour, &minute, &second) >= 2)
2459   {
2460    /*
2461     * Hold to specified GMT time (HH:MM or HH:MM:SS)...
2462     */
2463 
2464     time(&curtime);
2465     gmtime_r(&curtime, &curdate);
2466 
2467     job->hold_until = curtime +
2468                       ((hour - curdate.tm_hour) * 60 + minute -
2469 		       curdate.tm_min) * 60 + second - curdate.tm_sec;
2470 
2471    /*
2472     * Hold until next day as needed...
2473     */
2474 
2475     if (job->hold_until < curtime)
2476       job->hold_until += 24 * 60 * 60;
2477   }
2478 
2479   cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSetJobHoldUntil: hold_until=%d",
2480                   (int)job->hold_until);
2481 }
2482 
2483 
2484 /*
2485  * 'cupsdSetJobPriority()' - Set the priority of a job, moving it up/down in
2486  *                           the list as needed.
2487  */
2488 
2489 void
cupsdSetJobPriority(cupsd_job_t * job,int priority)2490 cupsdSetJobPriority(
2491     cupsd_job_t *job,			/* I - Job ID */
2492     int         priority)		/* I - New priority (0 to 100) */
2493 {
2494   ipp_attribute_t	*attr;		/* Job attribute */
2495 
2496 
2497  /*
2498   * Don't change completed jobs...
2499   */
2500 
2501   if (job->state_value >= IPP_JOB_PROCESSING)
2502     return;
2503 
2504  /*
2505   * Set the new priority and re-add the job into the active list...
2506   */
2507 
2508   cupsArrayRemove(ActiveJobs, job);
2509 
2510   job->priority = priority;
2511 
2512   if ((attr = ippFindAttribute(job->attrs, "job-priority",
2513                                IPP_TAG_INTEGER)) != NULL)
2514     attr->values[0].integer = priority;
2515   else
2516     ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-priority",
2517                   priority);
2518 
2519   cupsArrayAdd(ActiveJobs, job);
2520 
2521   job->dirty = 1;
2522   cupsdMarkDirty(CUPSD_DIRTY_JOBS);
2523 }
2524 
2525 
2526 /*
2527  * 'cupsdSetJobState()' - Set the state of the specified print job.
2528  */
2529 
2530 void
cupsdSetJobState(cupsd_job_t * job,ipp_jstate_t newstate,cupsd_jobaction_t action,const char * message,...)2531 cupsdSetJobState(
2532     cupsd_job_t       *job,		/* I - Job to cancel */
2533     ipp_jstate_t      newstate,		/* I - New job state */
2534     cupsd_jobaction_t action,		/* I - Action to take */
2535     const char        *message,		/* I - Message to log */
2536     ...)				/* I - Additional arguments as needed */
2537 {
2538   int			i;		/* Looping var */
2539   ipp_jstate_t		oldstate;	/* Old state */
2540   char			filename[1024];	/* Job filename */
2541   ipp_attribute_t	*attr;		/* Job attribute */
2542 
2543 
2544   cupsdLogMessage(CUPSD_LOG_DEBUG2,
2545                   "cupsdSetJobState(job=%p(%d), state=%d, newstate=%d, "
2546 		  "action=%d, message=\"%s\")", job, job->id, job->state_value,
2547 		  newstate, action, message ? message : "(null)");
2548 
2549 
2550  /*
2551   * Make sure we have the job attributes...
2552   */
2553 
2554   if (!cupsdLoadJob(job))
2555     return;
2556 
2557  /*
2558   * Don't do anything if the state is unchanged and we aren't purging the
2559   * job...
2560   */
2561 
2562   oldstate = job->state_value;
2563   if (newstate == oldstate && action != CUPSD_JOB_PURGE)
2564     return;
2565 
2566  /*
2567   * Stop any processes that are working on the current job...
2568   */
2569 
2570   if (oldstate == IPP_JOB_PROCESSING)
2571     stop_job(job, action);
2572 
2573  /*
2574   * Set the new job state...
2575   */
2576 
2577   job->state_value = newstate;
2578 
2579   if (job->state)
2580     job->state->values[0].integer = (int)newstate;
2581 
2582   switch (newstate)
2583   {
2584     case IPP_JOB_PENDING :
2585        /*
2586 	* Update job-hold-until as needed...
2587 	*/
2588 
2589 	if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
2590 				     IPP_TAG_KEYWORD)) == NULL)
2591 	  attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
2592 
2593 	if (attr)
2594 	{
2595 	  ippSetValueTag(job->attrs, &attr, IPP_TAG_KEYWORD);
2596 	  ippSetString(job->attrs, &attr, 0, "no-hold");
2597 	}
2598 
2599     default :
2600 	break;
2601 
2602     case IPP_JOB_ABORTED :
2603     case IPP_JOB_CANCELED :
2604     case IPP_JOB_COMPLETED :
2605 	set_time(job, "time-at-completed");
2606 	ippSetString(job->attrs, &job->reasons, 0, "processing-to-stop-point");
2607         break;
2608   }
2609 
2610  /*
2611   * Log message as needed...
2612   */
2613 
2614   if (message)
2615   {
2616     char	buffer[2048];		/* Message buffer */
2617     va_list	ap;			/* Pointer to additional arguments */
2618 
2619     va_start(ap, message);
2620     vsnprintf(buffer, sizeof(buffer), message, ap);
2621     va_end(ap);
2622 
2623     if (newstate > IPP_JOB_STOPPED)
2624       cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, "%s", buffer);
2625     else
2626       cupsdAddEvent(CUPSD_EVENT_JOB_STATE, job->printer, job, "%s", buffer);
2627 
2628     if (newstate == IPP_JOB_STOPPED || newstate == IPP_JOB_ABORTED || newstate == IPP_JOB_HELD)
2629       cupsdLogJob(job, CUPSD_LOG_ERROR, "%s", buffer);
2630     else
2631       cupsdLogJob(job, CUPSD_LOG_INFO, "%s", buffer);
2632   }
2633 
2634  /*
2635   * Handle post-state-change actions...
2636   */
2637 
2638   switch (newstate)
2639   {
2640     case IPP_JOB_PROCESSING :
2641        /*
2642         * Add the job to the "printing" list...
2643 	*/
2644 
2645         if (!cupsArrayFind(PrintingJobs, job))
2646 	  cupsArrayAdd(PrintingJobs, job);
2647 
2648        /*
2649 	* Set the processing time...
2650 	*/
2651 
2652 	set_time(job, "time-at-processing");
2653 
2654     case IPP_JOB_PENDING :
2655     case IPP_JOB_HELD :
2656     case IPP_JOB_STOPPED :
2657        /*
2658         * Make sure the job is in the active list...
2659 	*/
2660 
2661         if (!cupsArrayFind(ActiveJobs, job))
2662 	  cupsArrayAdd(ActiveJobs, job);
2663 
2664        /*
2665 	* Save the job state to disk...
2666 	*/
2667 
2668 	job->dirty = 1;
2669 	cupsdMarkDirty(CUPSD_DIRTY_JOBS);
2670         break;
2671 
2672     case IPP_JOB_ABORTED :
2673     case IPP_JOB_CANCELED :
2674     case IPP_JOB_COMPLETED :
2675         if (newstate == IPP_JOB_CANCELED)
2676 	{
2677 	 /*
2678 	  * Remove the job from the active list if there are no processes still
2679 	  * running for it...
2680 	  */
2681 
2682 	  for (i = 0; job->filters[i] < 0; i++);
2683 
2684 	  if (!job->filters[i] && job->backend <= 0)
2685 	    cupsArrayRemove(ActiveJobs, job);
2686 	}
2687 	else
2688 	{
2689 	 /*
2690 	  * Otherwise just remove the job from the active list immediately...
2691 	  */
2692 
2693 	  cupsArrayRemove(ActiveJobs, job);
2694 	}
2695 
2696        /*
2697         * Expire job subscriptions since the job is now "completed"...
2698 	*/
2699 
2700         cupsdExpireSubscriptions(NULL, job);
2701 
2702 #ifdef __APPLE__
2703        /*
2704 	* If we are going to sleep and the PrintingJobs count is now 0, allow the
2705 	* sleep to happen immediately...
2706 	*/
2707 
2708 	if (Sleeping && cupsArrayCount(PrintingJobs) == 0)
2709 	  cupsdAllowSleep();
2710 #endif /* __APPLE__ */
2711 
2712        /*
2713 	* Remove any authentication data...
2714 	*/
2715 
2716 	snprintf(filename, sizeof(filename), "%s/a%05d", RequestRoot, job->id);
2717 	if (cupsdRemoveFile(filename) && errno != ENOENT)
2718 	  cupsdLogMessage(CUPSD_LOG_ERROR,
2719 			  "Unable to remove authentication cache: %s",
2720 			  strerror(errno));
2721 
2722 	for (i = 0;
2723 	     i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0]));
2724 	     i ++)
2725 	  cupsdClearString(job->auth_env + i);
2726 
2727 	cupsdClearString(&job->auth_uid);
2728 
2729        /*
2730 	* Remove the print file for good if we aren't preserving jobs or
2731 	* files...
2732 	*/
2733 
2734 	if (!JobHistory || !JobFiles || action == CUPSD_JOB_PURGE)
2735 	  remove_job_files(job);
2736 
2737 	if (JobHistory && action != CUPSD_JOB_PURGE)
2738 	{
2739 	 /*
2740 	  * Save job state info...
2741 	  */
2742 
2743 	  job->dirty = 1;
2744 	  cupsdMarkDirty(CUPSD_DIRTY_JOBS);
2745 	}
2746 	else if (!job->printer)
2747 	{
2748 	 /*
2749 	  * Delete the job immediately if not actively printing...
2750 	  */
2751 
2752 	  cupsdDeleteJob(job, CUPSD_JOB_PURGE);
2753 	  job = NULL;
2754 	}
2755 	break;
2756   }
2757 
2758  /*
2759   * Finalize the job immediately if we forced things...
2760   */
2761 
2762   if (action >= CUPSD_JOB_FORCE && job && job->printer)
2763     finalize_job(job, 0);
2764 
2765  /*
2766   * Update the server "busy" state...
2767   */
2768 
2769   cupsdSetBusyState(0);
2770 }
2771 
2772 
2773 /*
2774  * 'cupsdStopAllJobs()' - Stop all print jobs.
2775  */
2776 
2777 void
cupsdStopAllJobs(cupsd_jobaction_t action,int kill_delay)2778 cupsdStopAllJobs(
2779     cupsd_jobaction_t action,		/* I - Action */
2780     int               kill_delay)	/* I - Number of seconds before we kill */
2781 {
2782   cupsd_job_t	*job;			/* Current job */
2783 
2784 
2785   for (job = (cupsd_job_t *)cupsArrayFirst(PrintingJobs);
2786        job;
2787        job = (cupsd_job_t *)cupsArrayNext(PrintingJobs))
2788   {
2789     if (job->completed)
2790     {
2791       cupsdSetJobState(job, IPP_JOB_COMPLETED, CUPSD_JOB_FORCE, NULL);
2792     }
2793     else
2794     {
2795       if (kill_delay)
2796         job->kill_time = time(NULL) + kill_delay;
2797 
2798       cupsdSetJobState(job, IPP_JOB_PENDING, action, NULL);
2799     }
2800   }
2801 }
2802 
2803 
2804 /*
2805  * 'cupsdUnloadCompletedJobs()' - Flush completed job history from memory.
2806  */
2807 
2808 void
cupsdUnloadCompletedJobs(void)2809 cupsdUnloadCompletedJobs(void)
2810 {
2811   cupsd_job_t	*job;			/* Current job */
2812   time_t	expire;			/* Expiration time */
2813 
2814 
2815   expire = time(NULL) - 60;
2816 
2817   for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
2818        job;
2819        job = (cupsd_job_t *)cupsArrayNext(Jobs))
2820     if (job->attrs && job->state_value >= IPP_JOB_STOPPED && !job->printer &&
2821         job->access_time < expire)
2822     {
2823       if (job->dirty)
2824         cupsdSaveJob(job);
2825 
2826       if (!job->dirty)
2827         unload_job(job);
2828     }
2829 }
2830 
2831 
2832 /*
2833  * 'cupsdUpdateJobs()' - Update the history/file files for all jobs.
2834  */
2835 
2836 void
cupsdUpdateJobs(void)2837 cupsdUpdateJobs(void)
2838 {
2839   cupsd_job_t		*job;		/* Current job */
2840   time_t		curtime;	/* Current time */
2841   ipp_attribute_t	*attr;		/* time-at-completed attribute */
2842 
2843 
2844   curtime          = time(NULL);
2845   JobHistoryUpdate = 0;
2846 
2847   for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
2848        job;
2849        job = (cupsd_job_t *)cupsArrayNext(Jobs))
2850   {
2851     if (job->state_value >= IPP_JOB_CANCELED &&
2852         (attr = ippFindAttribute(job->attrs, "time-at-completed",
2853                                  IPP_TAG_INTEGER)) != NULL)
2854     {
2855      /*
2856       * Update history/file expiration times...
2857       */
2858 
2859       job->completed_time = attr->values[0].integer;
2860 
2861       if (JobHistory < INT_MAX)
2862 	job->history_time = job->completed_time + JobHistory;
2863       else
2864 	job->history_time = INT_MAX;
2865 
2866       if (job->history_time < curtime)
2867       {
2868         cupsdDeleteJob(job, CUPSD_JOB_PURGE);
2869         continue;
2870       }
2871 
2872       if (job->history_time < JobHistoryUpdate || !JobHistoryUpdate)
2873 	JobHistoryUpdate = job->history_time;
2874 
2875       if (JobFiles < INT_MAX)
2876 	job->file_time = job->completed_time + JobFiles;
2877       else
2878 	job->file_time = INT_MAX;
2879 
2880       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);
2881 
2882       if (job->file_time < JobHistoryUpdate || !JobHistoryUpdate)
2883 	JobHistoryUpdate = job->file_time;
2884     }
2885   }
2886 
2887   cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdUpdateJobs: JobHistoryUpdate=%ld",
2888                   (long)JobHistoryUpdate);
2889 }
2890 
2891 
2892 /*
2893  * 'compare_active_jobs()' - Compare the job IDs and priorities of two jobs.
2894  */
2895 
2896 static int				/* O - Difference */
compare_active_jobs(void * first,void * second,void * data)2897 compare_active_jobs(void *first,	/* I - First job */
2898                     void *second,	/* I - Second job */
2899 		    void *data)		/* I - App data (not used) */
2900 {
2901   int	diff;				/* Difference */
2902 
2903 
2904   (void)data;
2905 
2906   if ((diff = ((cupsd_job_t *)second)->priority -
2907               ((cupsd_job_t *)first)->priority) != 0)
2908     return (diff);
2909   else
2910     return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
2911 }
2912 
2913 
2914 /*
2915  * 'compare_completed_jobs()' - Compare the job IDs and completion times of two jobs.
2916  */
2917 
2918 static int				/* O - Difference */
compare_completed_jobs(void * first,void * second,void * data)2919 compare_completed_jobs(void *first,	/* I - First job */
2920                        void *second,	/* I - Second job */
2921 		       void *data)	/* I - App data (not used) */
2922 {
2923   int	diff;				/* Difference */
2924 
2925 
2926   (void)data;
2927 
2928   if ((diff = ((cupsd_job_t *)second)->completed_time -
2929               ((cupsd_job_t *)first)->completed_time) != 0)
2930     return (diff);
2931   else
2932     return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
2933 }
2934 
2935 
2936 /*
2937  * 'compare_jobs()' - Compare the job IDs of two jobs.
2938  */
2939 
2940 static int				/* O - Difference */
compare_jobs(void * first,void * second,void * data)2941 compare_jobs(void *first,		/* I - First job */
2942              void *second,		/* I - Second job */
2943 	     void *data)		/* I - App data (not used) */
2944 {
2945   (void)data;
2946 
2947   return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
2948 }
2949 
2950 
2951 /*
2952  * 'dump_job_history()' - Dump any debug messages for a job.
2953  */
2954 
2955 static void
dump_job_history(cupsd_job_t * job)2956 dump_job_history(cupsd_job_t *job)	/* I - Job */
2957 {
2958   int			i,		/* Looping var */
2959 			oldsize;	/* Current MaxLogSize */
2960   struct tm		date;		/* Date/time value */
2961   cupsd_joblog_t	*message;	/* Current message */
2962   char			temp[2048],	/* Log message */
2963 			*ptr,		/* Pointer into log message */
2964 			start[256],	/* Start time */
2965 			end[256];	/* End time */
2966   cupsd_printer_t	*printer;	/* Printer for job */
2967 
2968 
2969  /*
2970   * See if we have anything to dump...
2971   */
2972 
2973   if (!job->history)
2974     return;
2975 
2976  /*
2977   * Disable log rotation temporarily...
2978   */
2979 
2980   oldsize    = MaxLogSize;
2981   MaxLogSize = 0;
2982 
2983  /*
2984   * Copy the debug messages to the log...
2985   */
2986 
2987   message = (cupsd_joblog_t *)cupsArrayFirst(job->history);
2988   localtime_r(&(message->time), &date);
2989   strftime(start, sizeof(start), "%X", &date);
2990 
2991   message = (cupsd_joblog_t *)cupsArrayLast(job->history);
2992   localtime_r(&(message->time), &date);
2993   strftime(end, sizeof(end), "%X", &date);
2994 
2995   snprintf(temp, sizeof(temp),
2996            "[Job %d] The following messages were recorded from %s to %s",
2997            job->id, start, end);
2998   cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp);
2999 
3000   for (message = (cupsd_joblog_t *)cupsArrayFirst(job->history);
3001        message;
3002        message = (cupsd_joblog_t *)cupsArrayNext(job->history))
3003     cupsdWriteErrorLog(CUPSD_LOG_DEBUG, message->message);
3004 
3005   snprintf(temp, sizeof(temp), "[Job %d] End of messages", job->id);
3006   cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp);
3007 
3008  /*
3009   * Log the printer state values...
3010   */
3011 
3012   if ((printer = job->printer) == NULL)
3013     printer = cupsdFindDest(job->dest);
3014 
3015   if (printer)
3016   {
3017     snprintf(temp, sizeof(temp), "[Job %d] printer-state=%d(%s)", job->id,
3018              printer->state,
3019 	     printer->state == IPP_PRINTER_IDLE ? "idle" :
3020 	         printer->state == IPP_PRINTER_PROCESSING ? "processing" :
3021 		 "stopped");
3022     cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp);
3023 
3024     snprintf(temp, sizeof(temp), "[Job %d] printer-state-message=\"%s\"",
3025              job->id, printer->state_message);
3026     cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp);
3027 
3028     snprintf(temp, sizeof(temp), "[Job %d] printer-state-reasons=", job->id);
3029     ptr = temp + strlen(temp);
3030     if (printer->num_reasons == 0)
3031       strlcpy(ptr, "none", sizeof(temp) - (size_t)(ptr - temp));
3032     else
3033     {
3034       for (i = 0;
3035            i < printer->num_reasons && ptr < (temp + sizeof(temp) - 2);
3036            i ++)
3037       {
3038         if (i)
3039 	  *ptr++ = ',';
3040 
3041 	strlcpy(ptr, printer->reasons[i], sizeof(temp) - (size_t)(ptr - temp));
3042 	ptr += strlen(ptr);
3043       }
3044     }
3045     cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp);
3046   }
3047 
3048  /*
3049   * Restore log file rotation...
3050   */
3051 
3052   MaxLogSize = oldsize;
3053 
3054  /*
3055   * Free all messages...
3056   */
3057 
3058   free_job_history(job);
3059 }
3060 
3061 
3062 /*
3063  * 'free_job_history()' - Free any log history.
3064  */
3065 
3066 static void
free_job_history(cupsd_job_t * job)3067 free_job_history(cupsd_job_t *job)	/* I - Job */
3068 {
3069   char	*message;			/* Current message */
3070 
3071 
3072   if (!job->history)
3073     return;
3074 
3075   for (message = (char *)cupsArrayFirst(job->history);
3076        message;
3077        message = (char *)cupsArrayNext(job->history))
3078     free(message);
3079 
3080   cupsArrayDelete(job->history);
3081   job->history = NULL;
3082 }
3083 
3084 
3085 /*
3086  * 'finalize_job()' - Cleanup after job filter processes and support data.
3087  */
3088 
3089 static void
finalize_job(cupsd_job_t * job,int set_job_state)3090 finalize_job(cupsd_job_t *job,		/* I - Job */
3091              int         set_job_state)	/* I - 1 = set the job state */
3092 {
3093   ipp_pstate_t		printer_state;	/* New printer state value */
3094   ipp_jstate_t		job_state;	/* New job state value */
3095   const char		*message;	/* Message for job state */
3096   char			buffer[1024];	/* Buffer for formatted messages */
3097 
3098 
3099   cupsdLogMessage(CUPSD_LOG_DEBUG2, "finalize_job(job=%p(%d))", job, job->id);
3100 
3101  /*
3102   * Clear the "connecting-to-device" and "cups-waiting-for-job-completed"
3103   * reasons, which are only valid when a printer is processing, along with any
3104   * remote printing job state...
3105   */
3106 
3107   cupsdSetPrinterReasons(job->printer, "-connecting-to-device,"
3108                                        "cups-waiting-for-job-completed,"
3109 				       "cups-remote-pending,"
3110 				       "cups-remote-pending-held,"
3111 				       "cups-remote-processing,"
3112 				       "cups-remote-stopped,"
3113 				       "cups-remote-canceled,"
3114 				       "cups-remote-aborted,"
3115 				       "cups-remote-completed");
3116 
3117  /*
3118   * Similarly, clear the "offline-report" reason for non-USB devices since we
3119   * rarely have current information for network devices...
3120   */
3121 
3122   if (strncmp(job->printer->device_uri, "usb:", 4) &&
3123       strncmp(job->printer->device_uri, "ippusb:", 7))
3124     cupsdSetPrinterReasons(job->printer, "-offline-report");
3125 
3126  /*
3127   * Free the security profile...
3128   */
3129 
3130   cupsdDestroyProfile(job->profile);
3131   job->profile = NULL;
3132   cupsdDestroyProfile(job->bprofile);
3133   job->bprofile = NULL;
3134 
3135  /*
3136   * Clear the unresponsive job watchdog timers...
3137   */
3138 
3139   job->cancel_time = 0;
3140   job->kill_time   = 0;
3141 
3142  /*
3143   * Close pipes and status buffer...
3144   */
3145 
3146   cupsdClosePipe(job->print_pipes);
3147   cupsdClosePipe(job->back_pipes);
3148   cupsdClosePipe(job->side_pipes);
3149 
3150   cupsdRemoveSelect(job->status_pipes[0]);
3151   cupsdClosePipe(job->status_pipes);
3152   cupsdStatBufDelete(job->status_buffer);
3153   job->status_buffer = NULL;
3154 
3155  /*
3156   * Log the final impression (page) count...
3157   */
3158 
3159   snprintf(buffer, sizeof(buffer), "total %d", ippGetInteger(job->impressions, 0));
3160   cupsdLogPage(job, buffer);
3161 
3162  /*
3163   * Process the exit status...
3164   */
3165 
3166   if (job->printer->state == IPP_PRINTER_PROCESSING)
3167     printer_state = IPP_PRINTER_IDLE;
3168   else
3169     printer_state = job->printer->state;
3170 
3171   switch (job_state = job->state_value)
3172   {
3173     case IPP_JOB_PENDING :
3174         message = "Job paused.";
3175 	break;
3176 
3177     case IPP_JOB_HELD :
3178         message = "Job held.";
3179 	break;
3180 
3181     default :
3182     case IPP_JOB_PROCESSING :
3183     case IPP_JOB_COMPLETED :
3184 	job_state = IPP_JOB_COMPLETED;
3185 	message   = "Job completed.";
3186 
3187         if (!job->status)
3188 	  ippSetString(job->attrs, &job->reasons, 0,
3189 		       "job-completed-successfully");
3190         break;
3191 
3192     case IPP_JOB_STOPPED :
3193         message = "Job stopped.";
3194 
3195 	ippSetString(job->attrs, &job->reasons, 0, "job-stopped");
3196 	break;
3197 
3198     case IPP_JOB_CANCELED :
3199         message = "Job canceled.";
3200 
3201 	ippSetString(job->attrs, &job->reasons, 0, "job-canceled-by-user");
3202 	break;
3203 
3204     case IPP_JOB_ABORTED :
3205         message = "Job aborted.";
3206 	break;
3207   }
3208 
3209   if (job->status < 0)
3210   {
3211    /*
3212     * Backend had errors...
3213     */
3214 
3215     int exit_code;			/* Exit code from backend */
3216 
3217    /*
3218     * Convert the status to an exit code.  Due to the way the W* macros are
3219     * implemented on macOS (bug?), we have to store the exit status in a
3220     * variable first and then convert...
3221     */
3222 
3223     exit_code = -job->status;
3224     if (WIFEXITED(exit_code))
3225       exit_code = WEXITSTATUS(exit_code);
3226     else
3227     {
3228       ippSetString(job->attrs, &job->reasons, 0, "cups-backend-crashed");
3229       exit_code = job->status;
3230     }
3231 
3232     cupsdLogJob(job, CUPSD_LOG_INFO, "Backend returned status %d (%s)",
3233 		exit_code,
3234 		exit_code == CUPS_BACKEND_FAILED ? "failed" :
3235 		    exit_code == CUPS_BACKEND_AUTH_REQUIRED ?
3236 			"authentication required" :
3237 		    exit_code == CUPS_BACKEND_HOLD ? "hold job" :
3238 		    exit_code == CUPS_BACKEND_STOP ? "stop printer" :
3239 		    exit_code == CUPS_BACKEND_CANCEL ? "cancel job" :
3240 		    exit_code == CUPS_BACKEND_RETRY ? "retry job later" :
3241 		    exit_code == CUPS_BACKEND_RETRY_CURRENT ? "retry job immediately" :
3242 		    exit_code < 0 ? "crashed" : "unknown");
3243 
3244    /*
3245     * Do what needs to be done...
3246     */
3247 
3248     switch (exit_code)
3249     {
3250       default :
3251       case CUPS_BACKEND_FAILED :
3252          /*
3253 	  * Backend failure, use the error-policy to determine how to
3254 	  * act...
3255 	  */
3256 
3257           if (job->dtype & CUPS_PRINTER_CLASS)
3258 	  {
3259 	   /*
3260 	    * Queued on a class - mark the job as pending and we'll retry on
3261 	    * another printer...
3262 	    */
3263 
3264             if (job_state == IPP_JOB_COMPLETED)
3265 	    {
3266 	      job_state = IPP_JOB_PENDING;
3267 	      message   = "Retrying job on another printer.";
3268 
3269 	      ippSetString(job->attrs, &job->reasons, 0,
3270 	                   "resources-are-not-ready");
3271 	    }
3272           }
3273 	  else if (!strcmp(job->printer->error_policy, "retry-current-job"))
3274 	  {
3275 	   /*
3276 	    * The error policy is "retry-current-job" - mark the job as pending
3277 	    * and we'll retry on the same printer...
3278 	    */
3279 
3280             if (job_state == IPP_JOB_COMPLETED)
3281 	    {
3282 	      job_state = IPP_JOB_PENDING;
3283 	      message   = "Retrying job on same printer.";
3284 
3285 	      ippSetString(job->attrs, &job->reasons, 0, "none");
3286 	    }
3287           }
3288 	  else if ((job->printer->type & CUPS_PRINTER_FAX) ||
3289         	   !strcmp(job->printer->error_policy, "retry-job"))
3290 	  {
3291             if (job_state == IPP_JOB_COMPLETED)
3292 	    {
3293 	     /*
3294 	      * The job was queued on a fax or the error policy is "retry-job" -
3295 	      * hold the job if the number of retries is less than the
3296 	      * JobRetryLimit, otherwise abort the job.
3297 	      */
3298 
3299 	      job->tries ++;
3300 
3301 	      if (job->tries > JobRetryLimit && JobRetryLimit > 0)
3302 	      {
3303 	       /*
3304 		* Too many tries...
3305 		*/
3306 
3307 		snprintf(buffer, sizeof(buffer),
3308 			 "Job aborted after %d unsuccessful attempts.",
3309 			 JobRetryLimit);
3310 		job_state = IPP_JOB_ABORTED;
3311 		message   = buffer;
3312 
3313 		ippSetString(job->attrs, &job->reasons, 0, "aborted-by-system");
3314 	      }
3315 	      else
3316 	      {
3317 	       /*
3318 		* Try again in N seconds...
3319 		*/
3320 
3321 		snprintf(buffer, sizeof(buffer),
3322 			 "Job held for %d seconds since it could not be sent.",
3323 			 JobRetryInterval);
3324 
3325 		job->hold_until = time(NULL) + JobRetryInterval;
3326 		job_state       = IPP_JOB_HELD;
3327 		message         = buffer;
3328 
3329 		ippSetString(job->attrs, &job->reasons, 0,
3330 		             "resources-are-not-ready");
3331 	      }
3332             }
3333 	  }
3334 	  else if (!strcmp(job->printer->error_policy, "abort-job") &&
3335 	           job_state == IPP_JOB_COMPLETED)
3336 	  {
3337 	    job_state = IPP_JOB_ABORTED;
3338 
3339 	    if (ErrorLog)
3340 	    {
3341 	      snprintf(buffer, sizeof(buffer), "Job aborted due to backend errors; please consult the %s file for details.", ErrorLog);
3342 	      message = buffer;
3343             }
3344             else
3345 	      message = "Job aborted due to backend errors.";
3346 
3347 	    ippSetString(job->attrs, &job->reasons, 0, "aborted-by-system");
3348 	  }
3349 	  else if (job->state_value == IPP_JOB_PROCESSING)
3350           {
3351             job_state     = IPP_JOB_PENDING;
3352 	    printer_state = IPP_PRINTER_STOPPED;
3353 
3354 	    if (ErrorLog)
3355 	    {
3356 	      snprintf(buffer, sizeof(buffer), "Printer stopped due to backend errors; please consult the %s file for details.", ErrorLog);
3357 	      message = buffer;
3358             }
3359             else
3360 	      message = "Printer stopped due to backend errors.";
3361 
3362 	    ippSetString(job->attrs, &job->reasons, 0, "none");
3363 	  }
3364           break;
3365 
3366       case CUPS_BACKEND_CANCEL :
3367          /*
3368 	  * Cancel the job...
3369 	  */
3370 
3371 	  if (job_state == IPP_JOB_COMPLETED)
3372 	  {
3373 	    job_state = IPP_JOB_CANCELED;
3374 	    message   = "Job canceled at printer.";
3375 
3376 	    ippSetString(job->attrs, &job->reasons, 0, "canceled-at-device");
3377 	  }
3378           break;
3379 
3380       case CUPS_BACKEND_HOLD :
3381 	  if (job_state == IPP_JOB_COMPLETED)
3382 	  {
3383 	   /*
3384 	    * Hold the job...
3385 	    */
3386 
3387 	    const char *reason = ippGetString(job->reasons, 0, NULL);
3388 
3389 	    cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-state-reasons=\"%s\"",
3390 	                reason);
3391 
3392 	    if (!reason || strncmp(reason, "account-", 8))
3393 	    {
3394 	      cupsdSetJobHoldUntil(job, "indefinite", 1);
3395 
3396 	      ippSetString(job->attrs, &job->reasons, 0,
3397 			   "job-hold-until-specified");
3398 
3399 	      if (ErrorLog)
3400 	      {
3401 		snprintf(buffer, sizeof(buffer), "Job held indefinitely due to backend errors; please consult the %s file for details.", ErrorLog);
3402 		message = buffer;
3403 	      }
3404 	      else
3405 		message = "Job held indefinitely due to backend errors.";
3406             }
3407             else if (!strcmp(reason, "account-info-needed"))
3408             {
3409 	      cupsdSetJobHoldUntil(job, "indefinite", 0);
3410 
3411 	      message = "Job held indefinitely - account information is required.";
3412             }
3413             else if (!strcmp(reason, "account-closed"))
3414             {
3415 	      cupsdSetJobHoldUntil(job, "indefinite", 0);
3416 
3417 	      message = "Job held indefinitely - account has been closed.";
3418 	    }
3419             else if (!strcmp(reason, "account-limit-reached"))
3420             {
3421 	      cupsdSetJobHoldUntil(job, "indefinite", 0);
3422 
3423 	      message = "Job held indefinitely - account limit has been reached.";
3424 	    }
3425             else
3426             {
3427 	      cupsdSetJobHoldUntil(job, "indefinite", 0);
3428 
3429 	      message = "Job held indefinitely - account authorization failed.";
3430 	    }
3431 
3432 	    job_state = IPP_JOB_HELD;
3433           }
3434           break;
3435 
3436       case CUPS_BACKEND_STOP :
3437          /*
3438 	  * Stop the printer...
3439 	  */
3440 
3441           if (job_state == IPP_JSTATE_CANCELED || job_state == IPP_JSTATE_ABORTED)
3442           {
3443             cupsdLogJob(job, CUPSD_LOG_INFO, "Ignored STOP from backend since the job is %s.", job_state == IPP_JSTATE_CANCELED ? "canceled" : "aborted");
3444             break;
3445 	  }
3446 
3447 	  printer_state = IPP_PRINTER_STOPPED;
3448 
3449 	  if (ErrorLog)
3450 	  {
3451 	    snprintf(buffer, sizeof(buffer), "Printer stopped due to backend errors; please consult the %s file for details.", ErrorLog);
3452 	    message = buffer;
3453 	  }
3454 	  else
3455 	    message = "Printer stopped due to backend errors.";
3456 
3457 	  if (job_state == IPP_JOB_COMPLETED)
3458 	  {
3459 	    job_state = IPP_JOB_PENDING;
3460 
3461 	    ippSetString(job->attrs, &job->reasons, 0, "resources-are-not-ready");
3462 	  }
3463           break;
3464 
3465       case CUPS_BACKEND_AUTH_REQUIRED :
3466          /*
3467 	  * Hold the job for authentication...
3468 	  */
3469 
3470 	  if (job_state == IPP_JOB_COMPLETED)
3471 	  {
3472 	    cupsdSetJobHoldUntil(job, "auth-info-required", 1);
3473 
3474 	    job_state = IPP_JOB_HELD;
3475 	    message   = "Job held for authentication.";
3476 
3477             if (strncmp(job->reasons->values[0].string.text, "account-", 8))
3478 	      ippSetString(job->attrs, &job->reasons, 0,
3479 			   "cups-held-for-authentication");
3480           }
3481           break;
3482 
3483       case CUPS_BACKEND_RETRY :
3484 	  if (job_state == IPP_JOB_COMPLETED)
3485 	  {
3486 	   /*
3487 	    * Hold the job if the number of retries is less than the
3488 	    * JobRetryLimit, otherwise abort the job.
3489 	    */
3490 
3491 	    job->tries ++;
3492 
3493 	    if (job->tries > JobRetryLimit && JobRetryLimit > 0)
3494 	    {
3495 	     /*
3496 	      * Too many tries...
3497 	      */
3498 
3499 	      snprintf(buffer, sizeof(buffer),
3500 		       "Job aborted after %d unsuccessful attempts.",
3501 		       JobRetryLimit);
3502 	      job_state = IPP_JOB_ABORTED;
3503 	      message   = buffer;
3504 
3505 	      ippSetString(job->attrs, &job->reasons, 0, "aborted-by-system");
3506 	    }
3507 	    else
3508 	    {
3509 	     /*
3510 	      * Try again in N seconds...
3511 	      */
3512 
3513 	      snprintf(buffer, sizeof(buffer),
3514 		       "Job held for %d seconds since it could not be sent.",
3515 		       JobRetryInterval);
3516 
3517 	      job->hold_until = time(NULL) + JobRetryInterval;
3518 	      job_state       = IPP_JOB_HELD;
3519 	      message         = buffer;
3520 
3521 	      ippSetString(job->attrs, &job->reasons, 0,
3522 	                   "resources-are-not-ready");
3523 	    }
3524 	  }
3525           break;
3526 
3527       case CUPS_BACKEND_RETRY_CURRENT :
3528 	 /*
3529 	  * Mark the job as pending and retry on the same printer...
3530 	  */
3531 
3532 	  if (job_state == IPP_JOB_COMPLETED)
3533 	  {
3534 	    job_state = IPP_JOB_PENDING;
3535 	    message   = "Retrying job on same printer.";
3536 
3537 	    ippSetString(job->attrs, &job->reasons, 0, "none");
3538 	  }
3539           break;
3540     }
3541   }
3542   else if (job->status > 0)
3543   {
3544    /*
3545     * Filter had errors; stop job...
3546     */
3547 
3548     if (job_state == IPP_JOB_COMPLETED)
3549     {
3550       job_state = IPP_JOB_STOPPED;
3551 
3552       if (ErrorLog)
3553       {
3554 	snprintf(buffer, sizeof(buffer), "Job stopped due to filter errors; please consult the %s file for details.", ErrorLog);
3555 	message = buffer;
3556       }
3557       else
3558 	message = "Job stopped due to filter errors.";
3559 
3560       if (WIFSIGNALED(job->status))
3561 	ippSetString(job->attrs, &job->reasons, 0, "cups-filter-crashed");
3562       else
3563 	ippSetString(job->attrs, &job->reasons, 0, "job-completed-with-errors");
3564     }
3565   }
3566 
3567  /*
3568   * Update the printer and job state.
3569   */
3570 
3571   if (set_job_state && job_state != job->state_value)
3572     cupsdSetJobState(job, job_state, CUPSD_JOB_DEFAULT, "%s", message);
3573 
3574   cupsdSetPrinterState(job->printer, printer_state,
3575                        printer_state == IPP_PRINTER_STOPPED);
3576   update_job_attrs(job, 0);
3577 
3578   if (job->history)
3579   {
3580     if (job->status &&
3581         (job->state_value == IPP_JOB_ABORTED ||
3582          job->state_value == IPP_JOB_STOPPED))
3583       dump_job_history(job);
3584     else
3585       free_job_history(job);
3586   }
3587 
3588   cupsArrayRemove(PrintingJobs, job);
3589 
3590  /*
3591   * Clear informational messages...
3592   */
3593 
3594   if (job->status_level > CUPSD_LOG_ERROR)
3595     job->printer->state_message[0] = '\0';
3596 
3597  /*
3598   * Apply any PPD updates...
3599   */
3600 
3601   if (job->num_keywords)
3602   {
3603     if (cupsdUpdatePrinterPPD(job->printer, job->num_keywords, job->keywords))
3604       cupsdSetPrinterAttrs(job->printer);
3605 
3606     cupsFreeOptions(job->num_keywords, job->keywords);
3607 
3608     job->num_keywords = 0;
3609     job->keywords     = NULL;
3610   }
3611 
3612  /*
3613   * Clear the printer <-> job association...
3614   */
3615 
3616   job->printer->job = NULL;
3617   job->printer      = NULL;
3618 }
3619 
3620 
3621 /*
3622  * 'get_options()' - Get a string containing the job options.
3623  */
3624 
3625 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)3626 get_options(cupsd_job_t *job,		/* I - Job */
3627             int         banner_page,	/* I - Printing a banner page? */
3628 	    char        *copies,	/* I - Copies buffer */
3629 	    size_t      copies_size,	/* I - Size of copies buffer */
3630 	    char        *title,		/* I - Title buffer */
3631 	    size_t      title_size)	/* I - Size of title buffer */
3632 {
3633   int			i;		/* Looping var */
3634   size_t		newlength;	/* New option buffer length */
3635   char			*optptr,	/* Pointer to options */
3636 			*valptr;	/* Pointer in value string */
3637   ipp_attribute_t	*attr;		/* Current attribute */
3638   _ppd_cache_t		*pc;		/* PPD cache and mapping data */
3639   int			num_pwgppds;	/* Number of PWG->PPD options */
3640   cups_option_t		*pwgppds,	/* PWG->PPD options */
3641 			*pwgppd,	/* Current PWG->PPD option */
3642 			*preset;	/* Current preset option */
3643   int			print_color_mode,
3644 					/* Output mode (if any) */
3645 			print_quality;	/* Print quality (if any) */
3646   const char		*ppd;		/* PPD option choice */
3647   int			exact;		/* Did we get an exact match? */
3648   static char		*options = NULL;/* Full list of options */
3649   static size_t		optlength = 0;	/* Length of option buffer */
3650 
3651 
3652  /*
3653   * Building the options string is harder than it needs to be, but for the
3654   * moment we need to pass strings for command-line args and not IPP attribute
3655   * pointers... :)
3656   *
3657   * First build an options array for any PWG->PPD mapped option/choice pairs.
3658   */
3659 
3660   pc          = job->printer->pc;
3661   num_pwgppds = 0;
3662   pwgppds     = NULL;
3663 
3664   if (pc &&
3665       !ippFindAttribute(job->attrs, "com.apple.print.DocumentTicket.PMSpoolFormat", IPP_TAG_ZERO) &&
3666       !ippFindAttribute(job->attrs, "APPrinterPreset", IPP_TAG_ZERO) &&
3667       (ippFindAttribute(job->attrs, "print-color-mode", IPP_TAG_ZERO) || ippFindAttribute(job->attrs, "print-quality", IPP_TAG_ZERO) || ippFindAttribute(job->attrs, "cupsPrintQuality", IPP_TAG_ZERO)))
3668   {
3669    /*
3670     * Map print-color-mode and print-quality to a preset...
3671     */
3672 
3673     if ((attr = ippFindAttribute(job->attrs, "print-color-mode",
3674 				 IPP_TAG_KEYWORD)) != NULL &&
3675         !strcmp(attr->values[0].string.text, "monochrome"))
3676       print_color_mode = _PWG_PRINT_COLOR_MODE_MONOCHROME;
3677     else
3678       print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR;
3679 
3680     if ((attr = ippFindAttribute(job->attrs, "print-quality", IPP_TAG_ENUM)) != NULL)
3681     {
3682       ipp_quality_t pq = (ipp_quality_t)ippGetInteger(attr, 0);
3683 
3684       if (pq >= IPP_QUALITY_DRAFT && pq <= IPP_QUALITY_HIGH)
3685         print_quality = attr->values[0].integer - IPP_QUALITY_DRAFT;
3686       else
3687         print_quality = _PWG_PRINT_QUALITY_NORMAL;
3688     }
3689     else if ((attr = ippFindAttribute(job->attrs, "cupsPrintQuality", IPP_TAG_NAME)) != NULL)
3690     {
3691       const char *pq = ippGetString(attr, 0, NULL);
3692 
3693       if (!_cups_strcasecmp(pq, "draft"))
3694         print_quality = _PWG_PRINT_QUALITY_DRAFT;
3695       else if (!_cups_strcasecmp(pq, "high"))
3696         print_quality = _PWG_PRINT_QUALITY_HIGH;
3697       else
3698         print_quality = _PWG_PRINT_QUALITY_NORMAL;
3699 
3700       if (!ippFindAttribute(job->attrs, "print-quality", IPP_TAG_ENUM))
3701       {
3702         cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping cupsPrintQuality=%s to print-quality=%d", pq, print_quality + IPP_QUALITY_DRAFT);
3703         num_pwgppds = cupsAddIntegerOption("print-quality", print_quality + IPP_QUALITY_DRAFT, num_pwgppds, &pwgppds);
3704       }
3705     }
3706     else
3707     {
3708       print_quality = _PWG_PRINT_QUALITY_NORMAL;
3709     }
3710 
3711     if (pc->num_presets[print_color_mode][print_quality] == 0)
3712     {
3713      /*
3714       * Try to find a preset that works so that we maximize the chances of us
3715       * getting a good print using IPP attributes.
3716       */
3717 
3718       if (pc->num_presets[print_color_mode][_PWG_PRINT_QUALITY_NORMAL] > 0)
3719         print_quality = _PWG_PRINT_QUALITY_NORMAL;
3720       else if (pc->num_presets[_PWG_PRINT_COLOR_MODE_COLOR][print_quality] > 0)
3721         print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR;
3722       else
3723       {
3724         print_quality    = _PWG_PRINT_QUALITY_NORMAL;
3725         print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR;
3726       }
3727     }
3728 
3729     if (pc->num_presets[print_color_mode][print_quality] > 0)
3730     {
3731      /*
3732       * Copy the preset options as long as the corresponding names are not
3733       * already defined in the IPP request...
3734       */
3735 
3736       for (i = pc->num_presets[print_color_mode][print_quality],
3737 	       preset = pc->presets[print_color_mode][print_quality];
3738 	   i > 0;
3739 	   i --, preset ++)
3740       {
3741         if (!ippFindAttribute(job->attrs, preset->name, IPP_TAG_ZERO))
3742         {
3743           cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Adding preset option %s=%s", preset->name, preset->value);
3744 
3745 	  num_pwgppds = cupsAddOption(preset->name, preset->value, num_pwgppds, &pwgppds);
3746         }
3747       }
3748     }
3749   }
3750 
3751   if (pc)
3752   {
3753     if ((attr = ippFindAttribute(job->attrs, "print-quality", IPP_TAG_ENUM)) != NULL)
3754     {
3755       int pq = ippGetInteger(attr, 0);
3756       static const char * const pqs[] = { "Draft", "Normal", "High" };
3757 
3758       if (pq >= IPP_QUALITY_DRAFT && pq <= IPP_QUALITY_HIGH)
3759       {
3760         cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping print-quality=%d to cupsPrintQuality=%s", pq, pqs[pq - IPP_QUALITY_DRAFT]);
3761 
3762         num_pwgppds = cupsAddOption("cupsPrintQuality", pqs[pq - IPP_QUALITY_DRAFT], num_pwgppds, &pwgppds);
3763       }
3764     }
3765 
3766     if (!ippFindAttribute(job->attrs, "InputSlot", IPP_TAG_ZERO) &&
3767 	!ippFindAttribute(job->attrs, "HPPaperSource", IPP_TAG_ZERO))
3768     {
3769       if ((ppd = _ppdCacheGetInputSlot(pc, job->attrs, NULL)) != NULL)
3770 	num_pwgppds = cupsAddOption(pc->source_option, ppd, num_pwgppds,
3771 				    &pwgppds);
3772     }
3773     if (!ippFindAttribute(job->attrs, "MediaType", IPP_TAG_ZERO) &&
3774 	(ppd = _ppdCacheGetMediaType(pc, job->attrs, NULL)) != NULL)
3775     {
3776       cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping media to MediaType=%s", ppd);
3777 
3778       num_pwgppds = cupsAddOption("MediaType", ppd, num_pwgppds, &pwgppds);
3779     }
3780 
3781     if (!ippFindAttribute(job->attrs, "PageRegion", IPP_TAG_ZERO) &&
3782 	!ippFindAttribute(job->attrs, "PageSize", IPP_TAG_ZERO) &&
3783 	(ppd = _ppdCacheGetPageSize(pc, job->attrs, NULL, &exact)) != NULL)
3784     {
3785       cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping media to Pagesize=%s", ppd);
3786 
3787       num_pwgppds = cupsAddOption("PageSize", ppd, num_pwgppds, &pwgppds);
3788 
3789       if (!ippFindAttribute(job->attrs, "media", IPP_TAG_ZERO))
3790       {
3791         cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Adding media=%s", ppd);
3792 
3793         num_pwgppds = cupsAddOption("media", ppd, num_pwgppds, &pwgppds);
3794       }
3795     }
3796 
3797     if (!ippFindAttribute(job->attrs, "OutputBin", IPP_TAG_ZERO) &&
3798 	(attr = ippFindAttribute(job->attrs, "output-bin",
3799 				 IPP_TAG_ZERO)) != NULL &&
3800 	(attr->value_tag == IPP_TAG_KEYWORD ||
3801 	 attr->value_tag == IPP_TAG_NAME) &&
3802 	(ppd = _ppdCacheGetOutputBin(pc, attr->values[0].string.text)) != NULL)
3803     {
3804      /*
3805       * Map output-bin to OutputBin option...
3806       */
3807 
3808       cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping output-bin to OutputBin=%s", ppd);
3809 
3810       num_pwgppds = cupsAddOption("OutputBin", ppd, num_pwgppds, &pwgppds);
3811     }
3812 
3813     if (pc->sides_option &&
3814         !ippFindAttribute(job->attrs, pc->sides_option, IPP_TAG_ZERO) &&
3815 	(attr = ippFindAttribute(job->attrs, "sides", IPP_TAG_KEYWORD)) != NULL)
3816     {
3817      /*
3818       * Map sides to duplex option...
3819       */
3820 
3821       if (!strcmp(attr->values[0].string.text, "one-sided"))
3822       {
3823         cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping sizes to Duplex=%s", pc->sides_1sided);
3824 
3825         num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_1sided, num_pwgppds, &pwgppds);
3826       }
3827       else if (!strcmp(attr->values[0].string.text, "two-sided-long-edge"))
3828       {
3829         cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping sizes to Duplex=%s", pc->sides_2sided_long);
3830 
3831         num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_2sided_long, num_pwgppds, &pwgppds);
3832       }
3833       else if (!strcmp(attr->values[0].string.text, "two-sided-short-edge"))
3834       {
3835         cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping sizes to Duplex=%s", pc->sides_2sided_short);
3836 
3837         num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_2sided_short, num_pwgppds, &pwgppds);
3838       }
3839     }
3840 
3841    /*
3842     * Map finishings values...
3843     */
3844 
3845     num_pwgppds = _ppdCacheGetFinishingOptions(pc, job->attrs, IPP_FINISHINGS_NONE, num_pwgppds, &pwgppds);
3846 
3847     for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++)
3848       cupsdLogJob(job, CUPSD_LOG_DEBUG2, "After mapping finishings %s=%s", pwgppd->name, pwgppd->value);
3849   }
3850 
3851  /*
3852   * Map page-delivery values...
3853   */
3854 
3855   if ((attr = ippFindAttribute(job->attrs, "page-delivery", IPP_TAG_KEYWORD)) != NULL && !ippFindAttribute(job->attrs, "outputorder", IPP_TAG_ZERO))
3856   {
3857     const char *page_delivery = ippGetString(attr, 0, NULL);
3858 
3859     if (!strncmp(page_delivery, "same-order", 10))
3860       num_pwgppds = cupsAddOption("OutputOrder", "Normal", num_pwgppds, &pwgppds);
3861     else if (!strncmp(page_delivery, "reverse-order", 13))
3862       num_pwgppds = cupsAddOption("OutputOrder", "Reverse", num_pwgppds, &pwgppds);
3863   }
3864 
3865  /*
3866   * Figure out how much room we need...
3867   */
3868 
3869   newlength = ipp_length(job->attrs);
3870 
3871   for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++)
3872     newlength += 1 + strlen(pwgppd->name) + 1 + strlen(pwgppd->value);
3873 
3874  /*
3875   * Then allocate/reallocate the option buffer as needed...
3876   */
3877 
3878   if (newlength == 0)			/* This can never happen, but Clang */
3879     newlength = 1;			/* thinks it can... */
3880 
3881   if (newlength > optlength || !options)
3882   {
3883     if (!options)
3884       optptr = malloc(newlength);
3885     else
3886       optptr = realloc(options, newlength);
3887 
3888     if (!optptr)
3889     {
3890       cupsdLogJob(job, CUPSD_LOG_CRIT,
3891 		  "Unable to allocate " CUPS_LLFMT " bytes for option buffer.",
3892 		  CUPS_LLCAST newlength);
3893       return (NULL);
3894     }
3895 
3896     options   = optptr;
3897     optlength = newlength;
3898   }
3899 
3900  /*
3901   * Now loop through the attributes and convert them to the textual
3902   * representation used by the filters...
3903   */
3904 
3905   optptr  = options;
3906   *optptr = '\0';
3907 
3908   snprintf(title, title_size, "%s-%d", job->printer->name, job->id);
3909   strlcpy(copies, "1", copies_size);
3910 
3911   for (attr = job->attrs->attrs; attr != NULL; attr = attr->next)
3912   {
3913     if (!strcmp(attr->name, "copies") &&
3914 	attr->value_tag == IPP_TAG_INTEGER)
3915     {
3916      /*
3917       * Don't use the # copies attribute if we are printing the job sheets...
3918       */
3919 
3920       if (!banner_page)
3921         snprintf(copies, copies_size, "%d", attr->values[0].integer);
3922     }
3923     else if (!strcmp(attr->name, "job-name") &&
3924 	     (attr->value_tag == IPP_TAG_NAME ||
3925 	      attr->value_tag == IPP_TAG_NAMELANG))
3926       strlcpy(title, attr->values[0].string.text, title_size);
3927     else if (attr->group_tag == IPP_TAG_JOB)
3928     {
3929      /*
3930       * Filter out other unwanted attributes...
3931       */
3932 
3933       if (attr->value_tag == IPP_TAG_NOVALUE ||
3934           attr->value_tag == IPP_TAG_MIMETYPE ||
3935 	  attr->value_tag == IPP_TAG_NAMELANG ||
3936 	  attr->value_tag == IPP_TAG_TEXTLANG ||
3937 	  (attr->value_tag == IPP_TAG_URI && strcmp(attr->name, "job-uuid") &&
3938 	   strcmp(attr->name, "job-authorization-uri")) ||
3939 	  attr->value_tag == IPP_TAG_URISCHEME ||
3940 	  attr->value_tag == IPP_TAG_BEGIN_COLLECTION) /* Not yet supported */
3941 	continue;
3942 
3943       if (!strcmp(attr->name, "job-hold-until") ||
3944           !strcmp(attr->name, "job-id") ||
3945           !strcmp(attr->name, "job-k-octets") ||
3946           !strcmp(attr->name, "job-media-sheets") ||
3947           !strcmp(attr->name, "job-media-sheets-completed") ||
3948           !strcmp(attr->name, "job-state") ||
3949           !strcmp(attr->name, "job-state-reasons"))
3950 	continue;
3951 
3952       if (!strncmp(attr->name, "job-", 4) &&
3953           strcmp(attr->name, "job-account-id") &&
3954           strcmp(attr->name, "job-accounting-user-id") &&
3955           strcmp(attr->name, "job-authorization-uri") &&
3956           strcmp(attr->name, "job-billing") &&
3957           strcmp(attr->name, "job-impressions") &&
3958           strcmp(attr->name, "job-originating-host-name") &&
3959           strcmp(attr->name, "job-password") &&
3960           strcmp(attr->name, "job-password-encryption") &&
3961           strcmp(attr->name, "job-uuid") &&
3962           !(job->printer->type & CUPS_PRINTER_REMOTE))
3963 	continue;
3964 
3965       if ((!strcmp(attr->name, "job-impressions") ||
3966            !strcmp(attr->name, "page-label") ||
3967            !strcmp(attr->name, "page-border") ||
3968            !strncmp(attr->name, "number-up", 9) ||
3969 	   !strcmp(attr->name, "page-ranges") ||
3970 	   !strcmp(attr->name, "page-set") ||
3971 	   !_cups_strcasecmp(attr->name, "AP_FIRSTPAGE_InputSlot") ||
3972 	   !_cups_strcasecmp(attr->name, "AP_FIRSTPAGE_ManualFeed") ||
3973 	   !_cups_strcasecmp(attr->name, "com.apple.print.PrintSettings."
3974 	                           "PMTotalSidesImaged..n.") ||
3975 	   !_cups_strcasecmp(attr->name, "com.apple.print.PrintSettings."
3976 	                           "PMTotalBeginPages..n.")) &&
3977 	  banner_page)
3978         continue;
3979 
3980      /*
3981       * Otherwise add them to the list...
3982       */
3983 
3984       if (optptr > options)
3985 	strlcat(optptr, " ", optlength - (size_t)(optptr - options));
3986 
3987       if (attr->value_tag != IPP_TAG_BOOLEAN)
3988       {
3989 	strlcat(optptr, attr->name, optlength - (size_t)(optptr - options));
3990 	strlcat(optptr, "=", optlength - (size_t)(optptr - options));
3991       }
3992 
3993       for (i = 0; i < attr->num_values; i ++)
3994       {
3995 	if (i)
3996 	  strlcat(optptr, ",", optlength - (size_t)(optptr - options));
3997 
3998 	optptr += strlen(optptr);
3999 
4000 	switch (attr->value_tag)
4001 	{
4002 	  case IPP_TAG_INTEGER :
4003 	  case IPP_TAG_ENUM :
4004 	      snprintf(optptr, optlength - (size_t)(optptr - options),
4005 	               "%d", attr->values[i].integer);
4006 	      break;
4007 
4008 	  case IPP_TAG_BOOLEAN :
4009 	      if (!attr->values[i].boolean)
4010 		strlcat(optptr, "no", optlength - (size_t)(optptr - options));
4011 
4012 	      strlcat(optptr, attr->name, optlength - (size_t)(optptr - options));
4013 	      break;
4014 
4015 	  case IPP_TAG_RANGE :
4016 	      if (attr->values[i].range.lower == attr->values[i].range.upper)
4017 		snprintf(optptr, optlength - (size_t)(optptr - options) - 1,
4018 	        	 "%d", attr->values[i].range.lower);
4019               else
4020 		snprintf(optptr, optlength - (size_t)(optptr - options) - 1,
4021 	        	 "%d-%d", attr->values[i].range.lower,
4022 			 attr->values[i].range.upper);
4023 	      break;
4024 
4025 	  case IPP_TAG_RESOLUTION :
4026 	      snprintf(optptr, optlength - (size_t)(optptr - options) - 1,
4027 	               "%dx%d%s", attr->values[i].resolution.xres,
4028 		       attr->values[i].resolution.yres,
4029 		       attr->values[i].resolution.units == IPP_RES_PER_INCH ?
4030 			   "dpi" : "dpcm");
4031 	      break;
4032 
4033           case IPP_TAG_STRING :
4034               {
4035                 int length = attr->values[i].unknown.length;
4036 
4037 		for (valptr = attr->values[i].unknown.data; length > 0; length --)
4038 		{
4039 		  if ((*valptr & 255) < 0x20 || *valptr == 0x7f)
4040 		    break;
4041 		}
4042 
4043 		if (length > 0)
4044 		{
4045 		 /*
4046 		  * Encode this string as hex characters...
4047 		  */
4048 
4049                   *optptr++ = '<';
4050 
4051 		  for (valptr = attr->values[i].unknown.data, length = attr->values[i].unknown.length; length > 0; length --)
4052 		  {
4053 		    snprintf(optptr, optlength - (size_t)(optptr - options) - 1, "%02X", *valptr & 255);
4054 		    optptr += 2;
4055 		  }
4056 
4057                   *optptr++ = '>';
4058 		}
4059 		else
4060 		{
4061 		  for (valptr = attr->values[i].unknown.data, length = attr->values[i].unknown.length; length > 0; length --)
4062 		  {
4063 		    if (strchr(" \t\n\\\'\"", *valptr))
4064 		      *optptr++ = '\\';
4065 		    *optptr++ = *valptr++;
4066 		  }
4067 		}
4068 	      }
4069 
4070 	      *optptr = '\0';
4071 	      break;
4072 
4073 	  case IPP_TAG_TEXT :
4074 	  case IPP_TAG_NAME :
4075 	  case IPP_TAG_KEYWORD :
4076 	  case IPP_TAG_CHARSET :
4077 	  case IPP_TAG_LANGUAGE :
4078 	  case IPP_TAG_URI :
4079 	      for (valptr = attr->values[i].string.text; *valptr;)
4080 	      {
4081 	        if (strchr(" \t\n\\\'\"", *valptr))
4082 		  *optptr++ = '\\';
4083 		*optptr++ = *valptr++;
4084 	      }
4085 
4086 	      *optptr = '\0';
4087 	      break;
4088 
4089           default :
4090 	      break; /* anti-compiler-warning-code */
4091 	}
4092       }
4093 
4094       optptr += strlen(optptr);
4095     }
4096   }
4097 
4098  /*
4099   * Finally loop through the PWG->PPD mapped options and add them...
4100   */
4101 
4102   for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++)
4103   {
4104     *optptr++ = ' ';
4105     strlcpy(optptr, pwgppd->name, optlength - (size_t)(optptr - options));
4106     optptr += strlen(optptr);
4107     *optptr++ = '=';
4108     strlcpy(optptr, pwgppd->value, optlength - (size_t)(optptr - options));
4109     optptr += strlen(optptr);
4110   }
4111 
4112   cupsFreeOptions(num_pwgppds, pwgppds);
4113 
4114  /*
4115   * Return the options string...
4116   */
4117 
4118   return (options);
4119 }
4120 
4121 
4122 /*
4123  * 'ipp_length()' - Compute the size of the buffer needed to hold
4124  *		    the textual IPP attributes.
4125  */
4126 
4127 static size_t				/* O - Size of attribute buffer */
ipp_length(ipp_t * ipp)4128 ipp_length(ipp_t *ipp)			/* I - IPP request */
4129 {
4130   size_t		bytes; 		/* Number of bytes */
4131   int			i;		/* Looping var */
4132   ipp_attribute_t	*attr;		/* Current attribute */
4133 
4134 
4135  /*
4136   * Loop through all attributes...
4137   */
4138 
4139   bytes = 0;
4140 
4141   for (attr = ipp->attrs; attr != NULL; attr = attr->next)
4142   {
4143    /*
4144     * Skip attributes that won't be sent to filters...
4145     */
4146 
4147     if (attr->value_tag == IPP_TAG_NOVALUE ||
4148 	attr->value_tag == IPP_TAG_MIMETYPE ||
4149 	attr->value_tag == IPP_TAG_NAMELANG ||
4150 	attr->value_tag == IPP_TAG_TEXTLANG ||
4151 	attr->value_tag == IPP_TAG_URI ||
4152 	attr->value_tag == IPP_TAG_URISCHEME)
4153       continue;
4154 
4155    /*
4156     * Add space for a leading space and commas between each value.
4157     * For the first attribute, the leading space isn't used, so the
4158     * extra byte can be used as the nul terminator...
4159     */
4160 
4161     bytes ++;				/* " " separator */
4162     bytes += (size_t)attr->num_values;	/* "," separators */
4163 
4164    /*
4165     * Boolean attributes appear as "foo,nofoo,foo,nofoo", while
4166     * other attributes appear as "foo=value1,value2,...,valueN".
4167     */
4168 
4169     if (attr->value_tag != IPP_TAG_BOOLEAN)
4170       bytes += strlen(attr->name);
4171     else
4172       bytes += (size_t)attr->num_values * strlen(attr->name);
4173 
4174    /*
4175     * Now add the size required for each value in the attribute...
4176     */
4177 
4178     switch (attr->value_tag)
4179     {
4180       case IPP_TAG_INTEGER :
4181       case IPP_TAG_ENUM :
4182          /*
4183 	  * Minimum value of a signed integer is -2147483647, or 11 digits.
4184 	  */
4185 
4186 	  bytes += (size_t)attr->num_values * 11;
4187 	  break;
4188 
4189       case IPP_TAG_BOOLEAN :
4190          /*
4191 	  * Add two bytes for each false ("no") value...
4192 	  */
4193 
4194           for (i = 0; i < attr->num_values; i ++)
4195 	    if (!attr->values[i].boolean)
4196 	      bytes += 2;
4197 	  break;
4198 
4199       case IPP_TAG_RANGE :
4200          /*
4201 	  * A range is two signed integers separated by a hyphen, or
4202 	  * 23 characters max.
4203 	  */
4204 
4205 	  bytes += (size_t)attr->num_values * 23;
4206 	  break;
4207 
4208       case IPP_TAG_RESOLUTION :
4209          /*
4210 	  * A resolution is two signed integers separated by an "x" and
4211 	  * suffixed by the units, or 26 characters max.
4212 	  */
4213 
4214 	  bytes += (size_t)attr->num_values * 26;
4215 	  break;
4216 
4217       case IPP_TAG_STRING :
4218          /*
4219 	  * Octet strings can contain characters that need quoting.  We need
4220 	  * at least 2 * len + 2 characters to cover the quotes and any
4221 	  * backslashes in the string.
4222 	  */
4223 
4224           for (i = 0; i < attr->num_values; i ++)
4225 	    bytes += 2 * (size_t)attr->values[i].unknown.length + 2;
4226 	  break;
4227 
4228       case IPP_TAG_TEXT :
4229       case IPP_TAG_NAME :
4230       case IPP_TAG_KEYWORD :
4231       case IPP_TAG_CHARSET :
4232       case IPP_TAG_LANGUAGE :
4233       case IPP_TAG_URI :
4234          /*
4235 	  * Strings can contain characters that need quoting.  We need
4236 	  * at least 2 * len + 2 characters to cover the quotes and
4237 	  * any backslashes in the string.
4238 	  */
4239 
4240           for (i = 0; i < attr->num_values; i ++)
4241 	    bytes += 2 * strlen(attr->values[i].string.text) + 2;
4242 	  break;
4243 
4244        default :
4245 	  break; /* anti-compiler-warning-code */
4246     }
4247   }
4248 
4249   return (bytes);
4250 }
4251 
4252 
4253 /*
4254  * 'load_job_cache()' - Load jobs from the job.cache file.
4255  */
4256 
4257 static void
load_job_cache(const char * filename)4258 load_job_cache(const char *filename)	/* I - job.cache filename */
4259 {
4260   cups_file_t	*fp;			/* job.cache file */
4261   char		line[1024],		/* Line buffer */
4262 		*value;			/* Value on line */
4263   int		linenum;		/* Line number in file */
4264   cupsd_job_t	*job;			/* Current job */
4265   int		jobid;			/* Job ID */
4266   char		jobfile[1024];		/* Job filename */
4267 
4268 
4269  /*
4270   * Open the job.cache file...
4271   */
4272 
4273   if ((fp = cupsdOpenConfFile(filename)) == NULL)
4274   {
4275     load_request_root();
4276     return;
4277   }
4278 
4279  /*
4280   * Read entries from the job cache file and create jobs as needed.
4281   */
4282 
4283   cupsdLogMessage(CUPSD_LOG_INFO, "Loading job cache file \"%s\"...",
4284                   filename);
4285 
4286   linenum = 0;
4287   job     = NULL;
4288 
4289   while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
4290   {
4291     if (!_cups_strcasecmp(line, "NextJobId"))
4292     {
4293       if (value)
4294         NextJobId = atoi(value);
4295     }
4296     else if (!_cups_strcasecmp(line, "<Job"))
4297     {
4298       if (job)
4299       {
4300         cupsdLogMessage(CUPSD_LOG_ERROR, "Missing </Job> directive on line %d of %s.", linenum, filename);
4301         continue;
4302       }
4303 
4304       if (!value)
4305       {
4306         cupsdLogMessage(CUPSD_LOG_ERROR, "Missing job ID on line %d of %s.", linenum, filename);
4307 	continue;
4308       }
4309 
4310       jobid = atoi(value);
4311 
4312       if (jobid < 1)
4313       {
4314         cupsdLogMessage(CUPSD_LOG_ERROR, "Bad job ID %d on line %d of %s.", jobid, linenum, filename);
4315         continue;
4316       }
4317 
4318       snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, jobid);
4319       if (access(jobfile, 0))
4320       {
4321 	snprintf(jobfile, sizeof(jobfile), "%s/c%05d.N", RequestRoot, jobid);
4322 	if (access(jobfile, 0))
4323 	{
4324 	  cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Files have gone away.",
4325 			  jobid);
4326 
4327          /*
4328           * job.cache file is out-of-date compared to spool directory; load
4329           * that instead...
4330           */
4331 
4332 	  cupsFileClose(fp);
4333           load_request_root();
4334           return;
4335 	}
4336       }
4337 
4338       job = calloc(1, sizeof(cupsd_job_t));
4339       if (!job)
4340       {
4341         cupsdLogMessage(CUPSD_LOG_EMERG,
4342 		        "[Job %d] Unable to allocate memory for job.", jobid);
4343         break;
4344       }
4345 
4346       job->id              = jobid;
4347       job->back_pipes[0]   = -1;
4348       job->back_pipes[1]   = -1;
4349       job->print_pipes[0]  = -1;
4350       job->print_pipes[1]  = -1;
4351       job->side_pipes[0]   = -1;
4352       job->side_pipes[1]   = -1;
4353       job->status_pipes[0] = -1;
4354       job->status_pipes[1] = -1;
4355 
4356       cupsdLogJob(job, CUPSD_LOG_DEBUG, "Loading from cache...");
4357     }
4358     else if (!job)
4359     {
4360       cupsdLogMessage(CUPSD_LOG_ERROR,
4361 	              "Missing <Job #> directive on line %d of %s.", linenum, filename);
4362       continue;
4363     }
4364     else if (!_cups_strcasecmp(line, "</Job>"))
4365     {
4366       cupsArrayAdd(Jobs, job);
4367 
4368       if (job->state_value <= IPP_JOB_STOPPED && cupsdLoadJob(job))
4369 	cupsArrayAdd(ActiveJobs, job);
4370       else if (job->state_value > IPP_JOB_STOPPED)
4371       {
4372         if (!job->completed_time || !job->creation_time || !job->name || !job->koctets)
4373 	{
4374 	  cupsdLoadJob(job);
4375 	  unload_job(job);
4376 	}
4377       }
4378 
4379       job = NULL;
4380     }
4381     else if (!value)
4382     {
4383       cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d of %s.", linenum, filename);
4384       continue;
4385     }
4386     else if (!_cups_strcasecmp(line, "State"))
4387     {
4388       job->state_value = (ipp_jstate_t)atoi(value);
4389 
4390       if (job->state_value < IPP_JOB_PENDING)
4391         job->state_value = IPP_JOB_PENDING;
4392       else if (job->state_value > IPP_JOB_COMPLETED)
4393         job->state_value = IPP_JOB_COMPLETED;
4394     }
4395     else if (!_cups_strcasecmp(line, "Name"))
4396     {
4397       cupsdSetString(&(job->name), value);
4398     }
4399     else if (!_cups_strcasecmp(line, "Created"))
4400     {
4401       job->creation_time = strtol(value, NULL, 10);
4402     }
4403     else if (!_cups_strcasecmp(line, "Completed"))
4404     {
4405       job->completed_time = strtol(value, NULL, 10);
4406     }
4407     else if (!_cups_strcasecmp(line, "HoldUntil"))
4408     {
4409       job->hold_until = strtol(value, NULL, 10);
4410     }
4411     else if (!_cups_strcasecmp(line, "Priority"))
4412     {
4413       job->priority = atoi(value);
4414     }
4415     else if (!_cups_strcasecmp(line, "Username"))
4416     {
4417       cupsdSetString(&job->username, value);
4418     }
4419     else if (!_cups_strcasecmp(line, "Destination"))
4420     {
4421       cupsdSetString(&job->dest, value);
4422     }
4423     else if (!_cups_strcasecmp(line, "DestType"))
4424     {
4425       job->dtype = (cups_ptype_t)atoi(value);
4426     }
4427     else if (!_cups_strcasecmp(line, "KOctets"))
4428     {
4429       job->koctets = atoi(value);
4430     }
4431     else if (!_cups_strcasecmp(line, "NumFiles"))
4432     {
4433       job->num_files = atoi(value);
4434 
4435       if (job->num_files < 0)
4436       {
4437 	cupsdLogMessage(CUPSD_LOG_ERROR, "Bad NumFiles value %d on line %d of %s.", job->num_files, linenum, filename);
4438         job->num_files = 0;
4439 	continue;
4440       }
4441 
4442       if (job->num_files > 0)
4443       {
4444         snprintf(jobfile, sizeof(jobfile), "%s/d%05d-001", RequestRoot,
4445 	         job->id);
4446         if (access(jobfile, 0))
4447 	{
4448 	  cupsdLogJob(job, CUPSD_LOG_INFO, "Data files have gone away.");
4449           job->num_files = 0;
4450 	  continue;
4451 	}
4452 
4453         job->filetypes    = calloc((size_t)job->num_files, sizeof(mime_type_t *));
4454 	job->compressions = calloc((size_t)job->num_files, sizeof(int));
4455 
4456         if (!job->filetypes || !job->compressions)
4457 	{
4458 	  cupsdLogJob(job, CUPSD_LOG_EMERG,
4459 		      "Unable to allocate memory for %d files.",
4460 		      job->num_files);
4461           break;
4462 	}
4463       }
4464     }
4465     else if (!_cups_strcasecmp(line, "File"))
4466     {
4467       int	number,			/* File number */
4468 		compression;		/* Compression value */
4469       char	super[MIME_MAX_SUPER],	/* MIME super type */
4470 		type[MIME_MAX_TYPE];	/* MIME type */
4471 
4472 
4473       if (sscanf(value, "%d%*[ \t]%15[^/]/%255s%d", &number, super, type,
4474                  &compression) != 4)
4475       {
4476         cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File on line %d of %s.", linenum, filename);
4477 	continue;
4478       }
4479 
4480       if (number < 1 || number > job->num_files)
4481       {
4482         cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File number %d on line %d of %s.", number, linenum, filename);
4483         continue;
4484       }
4485 
4486       number --;
4487 
4488       job->compressions[number] = compression;
4489       job->filetypes[number]    = mimeType(MimeDatabase, super, type);
4490 
4491       if (!job->filetypes[number])
4492       {
4493        /*
4494         * If the original MIME type is unknown, auto-type it!
4495 	*/
4496 
4497         cupsdLogJob(job, CUPSD_LOG_ERROR,
4498 		    "Unknown MIME type %s/%s for file %d.",
4499 		    super, type, number + 1);
4500 
4501         snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot,
4502 	         job->id, number + 1);
4503         job->filetypes[number] = mimeFileType(MimeDatabase, jobfile, NULL,
4504 	                                      job->compressions + number);
4505 
4506        /*
4507         * If that didn't work, assume it is raw...
4508 	*/
4509 
4510         if (!job->filetypes[number])
4511 	  job->filetypes[number] = mimeType(MimeDatabase, "application",
4512 	                                    "vnd.cups-raw");
4513       }
4514     }
4515     else
4516       cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown %s directive on line %d of %s.", line, linenum, filename);
4517   }
4518 
4519   if (job)
4520   {
4521     cupsdLogMessage(CUPSD_LOG_ERROR,
4522 		    "Missing </Job> directive on line %d of %s.", linenum, filename);
4523     cupsdDeleteJob(job, CUPSD_JOB_PURGE);
4524   }
4525 
4526   cupsFileClose(fp);
4527 }
4528 
4529 
4530 /*
4531  * 'load_next_job_id()' - Load the NextJobId value from the job.cache file.
4532  */
4533 
4534 static void
load_next_job_id(const char * filename)4535 load_next_job_id(const char *filename)	/* I - job.cache filename */
4536 {
4537   cups_file_t	*fp;			/* job.cache file */
4538   char		line[1024],		/* Line buffer */
4539 		*value;			/* Value on line */
4540   int		linenum;		/* Line number in file */
4541   int		next_job_id;		/* NextJobId value from line */
4542 
4543 
4544  /*
4545   * Read the NextJobId directive from the job.cache file and use
4546   * the value (if any).
4547   */
4548 
4549   if ((fp = cupsFileOpen(filename, "r")) == NULL)
4550   {
4551     if (errno != ENOENT)
4552       cupsdLogMessage(CUPSD_LOG_ERROR,
4553                       "Unable to open job cache file \"%s\": %s",
4554                       filename, strerror(errno));
4555 
4556     return;
4557   }
4558 
4559   cupsdLogMessage(CUPSD_LOG_INFO,
4560                   "Loading NextJobId from job cache file \"%s\"...", filename);
4561 
4562   linenum = 0;
4563 
4564   while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
4565   {
4566     if (!_cups_strcasecmp(line, "NextJobId"))
4567     {
4568       if (value)
4569       {
4570         next_job_id = atoi(value);
4571 
4572         if (next_job_id > NextJobId)
4573 	  NextJobId = next_job_id;
4574       }
4575       break;
4576     }
4577   }
4578 
4579   cupsFileClose(fp);
4580 }
4581 
4582 
4583 /*
4584  * 'load_request_root()' - Load jobs from the RequestRoot directory.
4585  */
4586 
4587 static void
load_request_root(void)4588 load_request_root(void)
4589 {
4590   cups_dir_t		*dir;		/* Directory */
4591   cups_dentry_t		*dent;		/* Directory entry */
4592   cupsd_job_t		*job;		/* New job */
4593 
4594 
4595  /*
4596   * Open the requests directory...
4597   */
4598 
4599   cupsdLogMessage(CUPSD_LOG_DEBUG, "Scanning %s for jobs...", RequestRoot);
4600 
4601   if ((dir = cupsDirOpen(RequestRoot)) == NULL)
4602   {
4603     cupsdLogMessage(CUPSD_LOG_ERROR,
4604                     "Unable to open spool directory \"%s\": %s",
4605                     RequestRoot, strerror(errno));
4606     return;
4607   }
4608 
4609  /*
4610   * Read all the c##### files...
4611   */
4612 
4613   while ((dent = cupsDirRead(dir)) != NULL)
4614     if (strlen(dent->filename) >= 6 && dent->filename[0] == 'c')
4615     {
4616      /*
4617       * Allocate memory for the job...
4618       */
4619 
4620       if ((job = calloc(sizeof(cupsd_job_t), 1)) == NULL)
4621       {
4622         cupsdLogMessage(CUPSD_LOG_ERROR, "Ran out of memory for jobs.");
4623 	cupsDirClose(dir);
4624 	return;
4625       }
4626 
4627      /*
4628       * Assign the job ID...
4629       */
4630 
4631       job->id              = atoi(dent->filename + 1);
4632       job->back_pipes[0]   = -1;
4633       job->back_pipes[1]   = -1;
4634       job->print_pipes[0]  = -1;
4635       job->print_pipes[1]  = -1;
4636       job->side_pipes[0]   = -1;
4637       job->side_pipes[1]   = -1;
4638       job->status_pipes[0] = -1;
4639       job->status_pipes[1] = -1;
4640 
4641       if (job->id >= NextJobId)
4642         NextJobId = job->id + 1;
4643 
4644      /*
4645       * Load the job...
4646       */
4647 
4648       if (cupsdLoadJob(job))
4649       {
4650        /*
4651         * Insert the job into the array, sorting by job priority and ID...
4652         */
4653 
4654 	cupsArrayAdd(Jobs, job);
4655 
4656 	if (job->state_value <= IPP_JOB_STOPPED)
4657 	  cupsArrayAdd(ActiveJobs, job);
4658 	else
4659 	  unload_job(job);
4660       }
4661       else
4662         free(job);
4663     }
4664 
4665   cupsDirClose(dir);
4666 }
4667 
4668 
4669 /*
4670  * 'remove_job_files()' - Remove the document files for a job.
4671  */
4672 
4673 static void
remove_job_files(cupsd_job_t * job)4674 remove_job_files(cupsd_job_t *job)	/* I - Job */
4675 {
4676   int	i;				/* Looping var */
4677   char	filename[1024];			/* Document filename */
4678 
4679 
4680   if (job->num_files <= 0)
4681     return;
4682 
4683   for (i = 1; i <= job->num_files; i ++)
4684   {
4685     snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
4686 	     job->id, i);
4687     cupsdUnlinkOrRemoveFile(filename);
4688   }
4689 
4690   free(job->filetypes);
4691   free(job->compressions);
4692 
4693   job->file_time    = 0;
4694   job->num_files    = 0;
4695   job->filetypes    = NULL;
4696   job->compressions = NULL;
4697 
4698   LastEvent |= CUPSD_EVENT_PRINTER_STATE_CHANGED;
4699 }
4700 
4701 
4702 /*
4703  * 'remove_job_history()' - Remove the control file for a job.
4704  */
4705 
4706 static void
remove_job_history(cupsd_job_t * job)4707 remove_job_history(cupsd_job_t *job)	/* I - Job */
4708 {
4709   char	filename[1024];			/* Control filename */
4710 
4711 
4712  /*
4713   * Remove the job info file...
4714   */
4715 
4716   snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot,
4717 	   job->id);
4718   cupsdUnlinkOrRemoveFile(filename);
4719 
4720   LastEvent |= CUPSD_EVENT_PRINTER_STATE_CHANGED;
4721 }
4722 
4723 
4724 /*
4725  * 'set_time()' - Set one of the "time-at-xyz" attributes.
4726  */
4727 
4728 static void
set_time(cupsd_job_t * job,const char * name)4729 set_time(cupsd_job_t *job,		/* I - Job to update */
4730          const char  *name)		/* I - Name of attribute */
4731 {
4732   char			date_name[128];	/* date-time-at-xxx */
4733   ipp_attribute_t	*attr;		/* Time attribute */
4734   time_t		curtime;	/* Current time */
4735 
4736 
4737   curtime = time(NULL);
4738 
4739   cupsdLogJob(job, CUPSD_LOG_DEBUG, "%s=%ld", name, (long)curtime);
4740 
4741   if ((attr = ippFindAttribute(job->attrs, name, IPP_TAG_ZERO)) != NULL)
4742   {
4743     attr->value_tag         = IPP_TAG_INTEGER;
4744     attr->values[0].integer = curtime;
4745   }
4746 
4747   snprintf(date_name, sizeof(date_name), "date-%s", name);
4748 
4749   if ((attr = ippFindAttribute(job->attrs, date_name, IPP_TAG_ZERO)) != NULL)
4750   {
4751     attr->value_tag = IPP_TAG_DATE;
4752     ippSetDate(job->attrs, &attr, 0, ippTimeToDate(curtime));
4753   }
4754 
4755   if (!strcmp(name, "time-at-completed"))
4756   {
4757     job->completed_time = curtime;
4758 
4759     if (JobHistory < INT_MAX && attr)
4760       job->history_time = job->completed_time + JobHistory;
4761     else
4762       job->history_time = INT_MAX;
4763 
4764     if (job->history_time < JobHistoryUpdate || !JobHistoryUpdate)
4765       JobHistoryUpdate = job->history_time;
4766 
4767     if (JobFiles < INT_MAX && attr)
4768       job->file_time = job->completed_time + JobFiles;
4769     else
4770       job->file_time = INT_MAX;
4771 
4772     if (job->file_time < JobHistoryUpdate || !JobHistoryUpdate)
4773       JobHistoryUpdate = job->file_time;
4774 
4775     cupsdLogMessage(CUPSD_LOG_DEBUG2, "set_time: JobHistoryUpdate=%ld",
4776 		    (long)JobHistoryUpdate);
4777   }
4778 }
4779 
4780 
4781 /*
4782  * 'start_job()' - Start a print job.
4783  */
4784 
4785 static void
start_job(cupsd_job_t * job,cupsd_printer_t * printer)4786 start_job(cupsd_job_t     *job,		/* I - Job ID */
4787           cupsd_printer_t *printer)	/* I - Printer to print job */
4788 {
4789   const char	*filename;		/* Support filename */
4790   ipp_attribute_t *cancel_after = ippFindAttribute(job->attrs,
4791 						   "job-cancel-after",
4792 						   IPP_TAG_INTEGER);
4793 					/* job-cancel-after attribute */
4794 
4795 
4796   cupsdLogMessage(CUPSD_LOG_DEBUG2, "start_job(job=%p(%d), printer=%p(%s))",
4797                   job, job->id, printer, printer->name);
4798 
4799  /*
4800   * Make sure we have some files around before we try to print...
4801   */
4802 
4803   if (job->num_files == 0)
4804   {
4805     ippSetString(job->attrs, &job->reasons, 0, "aborted-by-system");
4806     cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_DEFAULT,
4807                      "Aborting job because it has no files.");
4808     return;
4809   }
4810 
4811  /*
4812   * Update the printer and job state to "processing"...
4813   */
4814 
4815   if (!cupsdLoadJob(job))
4816     return;
4817 
4818   if (!job->printer_message)
4819     job->printer_message = ippFindAttribute(job->attrs,
4820                                             "job-printer-state-message",
4821                                             IPP_TAG_TEXT);
4822   if (job->printer_message)
4823     ippSetString(job->attrs, &job->printer_message, 0, "");
4824 
4825   ippSetString(job->attrs, &job->reasons, 0, "job-printing");
4826   cupsdSetJobState(job, IPP_JOB_PROCESSING, CUPSD_JOB_DEFAULT, NULL);
4827   cupsdSetPrinterState(printer, IPP_PRINTER_PROCESSING, 0);
4828   cupsdSetPrinterReasons(printer, "-cups-remote-pending,"
4829 				  "cups-remote-pending-held,"
4830 				  "cups-remote-processing,"
4831 				  "cups-remote-stopped,"
4832 				  "cups-remote-canceled,"
4833 				  "cups-remote-aborted,"
4834 				  "cups-remote-completed");
4835 
4836   job->cost         = 0;
4837   job->current_file = 0;
4838   job->file_time    = 0;
4839   job->history_time = 0;
4840   job->progress     = 0;
4841   job->printer      = printer;
4842   printer->job      = job;
4843 
4844   if (cancel_after)
4845     job->cancel_time = time(NULL) + ippGetInteger(cancel_after, 0);
4846   else if (MaxJobTime > 0)
4847     job->cancel_time = time(NULL) + MaxJobTime;
4848   else
4849     job->cancel_time = 0;
4850 
4851  /*
4852   * Check for support files...
4853   */
4854 
4855   cupsdSetPrinterReasons(job->printer, "-cups-missing-filter-warning,"
4856 			               "cups-insecure-filter-warning");
4857 
4858   if (printer->pc)
4859   {
4860     for (filename = (const char *)cupsArrayFirst(printer->pc->support_files);
4861          filename;
4862          filename = (const char *)cupsArrayNext(printer->pc->support_files))
4863     {
4864       if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_FILE, !RunUser,
4865 			 cupsdLogFCMessage, printer))
4866         break;
4867     }
4868   }
4869 
4870  /*
4871   * Setup the last exit status and security profiles...
4872   */
4873 
4874   job->status   = 0;
4875   job->profile  = cupsdCreateProfile(job->id, 0);
4876   job->bprofile = cupsdCreateProfile(job->id, 1);
4877 
4878 #ifdef HAVE_SANDBOX_H
4879   if ((!job->profile || !job->bprofile) && UseSandboxing && Sandboxing != CUPSD_SANDBOXING_OFF)
4880   {
4881    /*
4882     * Failure to create the sandbox profile means something really bad has
4883     * happened and we need to shutdown immediately.
4884     */
4885 
4886     return;
4887   }
4888 #endif /* HAVE_SANDBOX_H */
4889 
4890  /*
4891   * Create the status pipes and buffer...
4892   */
4893 
4894   if (cupsdOpenPipe(job->status_pipes))
4895   {
4896     cupsdLogJob(job, CUPSD_LOG_DEBUG,
4897 		"Unable to create job status pipes - %s.", strerror(errno));
4898 
4899     cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
4900 		     "Job stopped because the scheduler could not create the "
4901 		     "job status pipes.");
4902 
4903     cupsdDestroyProfile(job->profile);
4904     job->profile = NULL;
4905     cupsdDestroyProfile(job->bprofile);
4906     job->bprofile = NULL;
4907     return;
4908   }
4909 
4910   job->status_buffer = cupsdStatBufNew(job->status_pipes[0], NULL);
4911   job->status_level  = CUPSD_LOG_INFO;
4912 
4913  /*
4914   * Create the backchannel pipes and make them non-blocking...
4915   */
4916 
4917   if (cupsdOpenPipe(job->back_pipes))
4918   {
4919     cupsdLogJob(job, CUPSD_LOG_DEBUG,
4920 		"Unable to create back-channel pipes - %s.", strerror(errno));
4921 
4922     cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
4923 		     "Job stopped because the scheduler could not create the "
4924 		     "back-channel pipes.");
4925 
4926     cupsdClosePipe(job->status_pipes);
4927     cupsdStatBufDelete(job->status_buffer);
4928     job->status_buffer = NULL;
4929 
4930     cupsdDestroyProfile(job->profile);
4931     job->profile = NULL;
4932     cupsdDestroyProfile(job->bprofile);
4933     job->bprofile = NULL;
4934     return;
4935   }
4936 
4937   fcntl(job->back_pipes[0], F_SETFL,
4938 	fcntl(job->back_pipes[0], F_GETFL) | O_NONBLOCK);
4939   fcntl(job->back_pipes[1], F_SETFL,
4940 	fcntl(job->back_pipes[1], F_GETFL) | O_NONBLOCK);
4941 
4942  /*
4943   * Create the side-channel pipes and make them non-blocking...
4944   */
4945 
4946   if (socketpair(AF_LOCAL, SOCK_STREAM, 0, job->side_pipes))
4947   {
4948     cupsdLogJob(job, CUPSD_LOG_DEBUG,
4949 		"Unable to create side-channel pipes - %s.", strerror(errno));
4950 
4951     cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
4952 		     "Job stopped because the scheduler could not create the "
4953 		     "side-channel pipes.");
4954 
4955     cupsdClosePipe(job->back_pipes);
4956 
4957     cupsdClosePipe(job->status_pipes);
4958     cupsdStatBufDelete(job->status_buffer);
4959     job->status_buffer = NULL;
4960 
4961     cupsdDestroyProfile(job->profile);
4962     job->profile = NULL;
4963     cupsdDestroyProfile(job->bprofile);
4964     job->bprofile = NULL;
4965     return;
4966   }
4967 
4968   fcntl(job->side_pipes[0], F_SETFL,
4969 	fcntl(job->side_pipes[0], F_GETFL) | O_NONBLOCK);
4970   fcntl(job->side_pipes[1], F_SETFL,
4971 	fcntl(job->side_pipes[1], F_GETFL) | O_NONBLOCK);
4972 
4973   fcntl(job->side_pipes[0], F_SETFD,
4974 	fcntl(job->side_pipes[0], F_GETFD) | FD_CLOEXEC);
4975   fcntl(job->side_pipes[1], F_SETFD,
4976 	fcntl(job->side_pipes[1], F_GETFD) | FD_CLOEXEC);
4977 
4978  /*
4979   * Now start the first file in the job...
4980   */
4981 
4982   cupsdContinueJob(job);
4983 }
4984 
4985 
4986 /*
4987  * 'stop_job()' - Stop a print job.
4988  */
4989 
4990 static void
stop_job(cupsd_job_t * job,cupsd_jobaction_t action)4991 stop_job(cupsd_job_t       *job,	/* I - Job */
4992          cupsd_jobaction_t action)	/* I - Action */
4993 {
4994   int	i;				/* Looping var */
4995 
4996 
4997   cupsdLogMessage(CUPSD_LOG_DEBUG2, "stop_job(job=%p(%d), action=%d)", job,
4998                   job->id, action);
4999 
5000   FilterLevel -= job->cost;
5001   job->cost   = 0;
5002 
5003   if (action == CUPSD_JOB_DEFAULT && !job->kill_time && job->backend > 0)
5004     job->kill_time = time(NULL) + JobKillDelay;
5005   else if (action >= CUPSD_JOB_FORCE)
5006     job->kill_time = 0;
5007 
5008   for (i = 0; job->filters[i]; i ++)
5009     if (job->filters[i] > 0)
5010     {
5011       cupsdEndProcess(job->filters[i], action >= CUPSD_JOB_FORCE);
5012 
5013       if (action >= CUPSD_JOB_FORCE)
5014         job->filters[i] = -job->filters[i];
5015     }
5016 
5017   if (job->backend > 0)
5018   {
5019     cupsdEndProcess(job->backend, action >= CUPSD_JOB_FORCE);
5020 
5021     if (action >= CUPSD_JOB_FORCE)
5022       job->backend = -job->backend;
5023   }
5024 
5025   if (action >= CUPSD_JOB_FORCE)
5026   {
5027    /*
5028     * Clear job status...
5029     */
5030 
5031     job->status = 0;
5032   }
5033 }
5034 
5035 
5036 /*
5037  * 'unload_job()' - Unload a job from memory.
5038  */
5039 
5040 static void
unload_job(cupsd_job_t * job)5041 unload_job(cupsd_job_t *job)		/* I - Job */
5042 {
5043   if (!job->attrs)
5044     return;
5045 
5046   cupsdLogJob(job, CUPSD_LOG_DEBUG, "Unloading...");
5047 
5048   ippDelete(job->attrs);
5049 
5050   job->attrs           = NULL;
5051   job->state           = NULL;
5052   job->reasons         = NULL;
5053   job->impressions     = NULL;
5054   job->sheets          = NULL;
5055   job->job_sheets      = NULL;
5056   job->printer_message = NULL;
5057   job->printer_reasons = NULL;
5058 }
5059 
5060 
5061 /*
5062  * 'update_job()' - Read a status update from a job's filters.
5063  */
5064 
5065 void
update_job(cupsd_job_t * job)5066 update_job(cupsd_job_t *job)		/* I - Job to check */
5067 {
5068   int		i;			/* Looping var */
5069   char		message[CUPSD_SB_BUFFER_SIZE],
5070 					/* Message text */
5071 		*ptr;			/* Pointer update... */
5072   int		loglevel,		/* Log level for message */
5073 		event = 0;		/* Events? */
5074   cupsd_printer_t *printer = job->printer;
5075 					/* Printer */
5076   static const char * const levels[] =	/* Log levels */
5077 		{
5078 		  "NONE",
5079 		  "EMERG",
5080 		  "ALERT",
5081 		  "CRIT",
5082 		  "ERROR",
5083 		  "WARN",
5084 		  "NOTICE",
5085 		  "INFO",
5086 		  "DEBUG",
5087 		  "DEBUG2"
5088 		};
5089 
5090 
5091  /*
5092   * Get the printer associated with this job; if the printer is stopped for
5093   * any reason then job->printer will be reset to NULL, so make sure we have
5094   * a valid pointer...
5095   */
5096 
5097   while ((ptr = cupsdStatBufUpdate(job->status_buffer, &loglevel,
5098                                    message, sizeof(message))) != NULL)
5099   {
5100    /*
5101     * Process page and printer state messages as needed...
5102     */
5103 
5104     if (loglevel == CUPSD_LOG_PAGE)
5105     {
5106       int	impressions = ippGetInteger(job->impressions, 0);
5107 				/* Number of impressions printed */
5108       int	delta;		/* Number of impressions added */
5109 
5110      /*
5111       * Page message; send the message to the page_log file and update the
5112       * job sheet count...
5113       */
5114 
5115       cupsdLogJob(job, CUPSD_LOG_DEBUG, "PAGE: %s", message);
5116 
5117       if (!_cups_strncasecmp(message, "total ", 6))
5118       {
5119        /*
5120 	* Got a total count of pages from a backend or filter...
5121 	*/
5122 
5123 	int total = atoi(message + 6);	/* Total impressions */
5124 
5125 	if (total > impressions)
5126 	{
5127 	  delta       = total - impressions;
5128 	  impressions = total;
5129 	}
5130 	else
5131 	  delta = 0;
5132       }
5133       else
5134       {
5135        /*
5136         * Add the number of copies to the impression count...
5137         */
5138 
5139 	int copies;			/* Number of copies */
5140 
5141 	if (!sscanf(message, "%*d%d", &copies) || copies <= 0)
5142 	  copies = 1;
5143 
5144         delta = copies;
5145 	impressions += copies;
5146       }
5147 
5148       if (job->impressions)
5149         ippSetInteger(job->attrs, &job->impressions, 0, impressions);
5150 
5151       if (job->sheets)
5152       {
5153 	const char *sides = ippGetString(ippFindAttribute(job->attrs, "sides", IPP_TAG_KEYWORD), 0, NULL);
5154 
5155         if (sides && strcmp(sides, "one-sided"))
5156           ippSetInteger(job->attrs, &job->sheets, 0, impressions / 2);
5157 	else
5158           ippSetInteger(job->attrs, &job->sheets, 0, impressions);
5159 
5160 	cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job, "Printed %d page(s).", ippGetInteger(job->sheets, 0));
5161       }
5162 
5163       job->dirty = 1;
5164       cupsdMarkDirty(CUPSD_DIRTY_JOBS);
5165 
5166       if (job->printer->page_limit)
5167 	cupsdUpdateQuota(job->printer, job->username, delta, 0);
5168     }
5169     else if (loglevel == CUPSD_LOG_JOBSTATE)
5170     {
5171      /*
5172       * Support "keyword" to set job-state-reasons to the specified keyword.
5173       * This is sufficient for the current paid printing stuff.
5174       */
5175 
5176       cupsdLogJob(job, CUPSD_LOG_DEBUG, "JOBSTATE: %s", message);
5177 
5178       if (!strcmp(message, "cups-retry-as-raster"))
5179         job->retry_as_raster = 1;
5180       else
5181         ippSetString(job->attrs, &job->reasons, 0, message);
5182     }
5183     else if (loglevel == CUPSD_LOG_STATE)
5184     {
5185       cupsdLogJob(job, CUPSD_LOG_DEBUG, "STATE: %s", message);
5186 
5187       if (!strcmp(message, "paused"))
5188       {
5189         cupsdStopPrinter(job->printer, 1);
5190 	return;
5191       }
5192       else if (message[0] && cupsdSetPrinterReasons(job->printer, message))
5193       {
5194 	event |= CUPSD_EVENT_PRINTER_STATE;
5195 
5196         if (MaxJobTime > 0)
5197         {
5198          /*
5199           * Reset cancel time after connecting to the device...
5200           */
5201 
5202           for (i = 0; i < job->printer->num_reasons; i ++)
5203             if (!strcmp(job->printer->reasons[i], "connecting-to-device"))
5204               break;
5205 
5206           if (i >= job->printer->num_reasons)
5207           {
5208 	    ipp_attribute_t *cancel_after = ippFindAttribute(job->attrs,
5209 							     "job-cancel-after",
5210 							     IPP_TAG_INTEGER);
5211 					/* job-cancel-after attribute */
5212 
5213             if (cancel_after)
5214 	      job->cancel_time = time(NULL) + ippGetInteger(cancel_after, 0);
5215 	    else if (MaxJobTime > 0)
5216 	      job->cancel_time = time(NULL) + MaxJobTime;
5217 	    else
5218 	      job->cancel_time = 0;
5219 	  }
5220         }
5221       }
5222 
5223       update_job_attrs(job, 0);
5224     }
5225     else if (loglevel == CUPSD_LOG_ATTR)
5226     {
5227      /*
5228       * Set attribute(s)...
5229       */
5230 
5231       int		num_attrs;	/* Number of attributes */
5232       cups_option_t	*attrs;		/* Attributes */
5233       const char	*attr;		/* Attribute */
5234 
5235       cupsdLogJob(job, CUPSD_LOG_DEBUG, "ATTR: %s", message);
5236 
5237       num_attrs = cupsParseOptions(message, 0, &attrs);
5238 
5239       if ((attr = cupsGetOption("auth-info-default", num_attrs,
5240                                 attrs)) != NULL)
5241       {
5242         job->printer->num_options = cupsAddOption("auth-info", attr,
5243 						  job->printer->num_options,
5244 						  &(job->printer->options));
5245 	cupsdSetPrinterAttrs(job->printer);
5246 
5247 	cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
5248       }
5249 
5250       if ((attr = cupsGetOption("auth-info-required", num_attrs,
5251                                 attrs)) != NULL)
5252       {
5253         cupsdSetAuthInfoRequired(job->printer, attr, NULL);
5254 	cupsdSetPrinterAttrs(job->printer);
5255 
5256 	cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
5257       }
5258 
5259       if ((attr = cupsGetOption("job-media-progress", num_attrs,
5260                                 attrs)) != NULL)
5261       {
5262         int progress = atoi(attr);
5263 
5264 
5265         if (progress >= 0 && progress <= 100)
5266 	{
5267 	  job->progress = progress;
5268 
5269 	  if (job->sheets)
5270 	    cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job,
5271 			  "Printing page %d, %d%%",
5272 			  job->sheets->values[0].integer, job->progress);
5273         }
5274       }
5275 
5276       if ((attr = cupsGetOption("printer-alert", num_attrs, attrs)) != NULL)
5277       {
5278         cupsdSetString(&job->printer->alert, attr);
5279 	event |= CUPSD_EVENT_PRINTER_STATE;
5280       }
5281 
5282       if ((attr = cupsGetOption("printer-alert-description", num_attrs,
5283                                 attrs)) != NULL)
5284       {
5285         cupsdSetString(&job->printer->alert_description, attr);
5286 	event |= CUPSD_EVENT_PRINTER_STATE;
5287       }
5288 
5289       if ((attr = cupsGetOption("marker-colors", num_attrs, attrs)) != NULL)
5290       {
5291         cupsdSetPrinterAttr(job->printer, "marker-colors", (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-levels", num_attrs, attrs)) != NULL)
5298       {
5299         cupsdSetPrinterAttr(job->printer, "marker-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-low-levels", num_attrs, attrs)) != NULL)
5306       {
5307         cupsdSetPrinterAttr(job->printer, "marker-low-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-high-levels", num_attrs, attrs)) != NULL)
5314       {
5315         cupsdSetPrinterAttr(job->printer, "marker-high-levels", (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-message", num_attrs, attrs)) != NULL)
5322       {
5323         cupsdSetPrinterAttr(job->printer, "marker-message", (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-names", num_attrs, attrs)) != NULL)
5330       {
5331         cupsdSetPrinterAttr(job->printer, "marker-names", (char *)attr);
5332 	job->printer->marker_time = time(NULL);
5333 	event |= CUPSD_EVENT_PRINTER_STATE;
5334         cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
5335       }
5336 
5337       if ((attr = cupsGetOption("marker-types", num_attrs, attrs)) != NULL)
5338       {
5339         cupsdSetPrinterAttr(job->printer, "marker-types", (char *)attr);
5340 	job->printer->marker_time = time(NULL);
5341 	event |= CUPSD_EVENT_PRINTER_STATE;
5342         cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
5343       }
5344 
5345       cupsFreeOptions(num_attrs, attrs);
5346     }
5347     else if (loglevel == CUPSD_LOG_PPD)
5348     {
5349      /*
5350       * Set attribute(s)...
5351       */
5352 
5353       cupsdLogJob(job, CUPSD_LOG_DEBUG, "PPD: %s", message);
5354 
5355       job->num_keywords = cupsParseOptions(message, job->num_keywords,
5356                                            &job->keywords);
5357     }
5358     else
5359     {
5360      /*
5361       * Strip legacy message prefix...
5362       */
5363 
5364       if (!strncmp(message, "recoverable:", 12))
5365       {
5366         ptr = message + 12;
5367 	while (isspace(*ptr & 255))
5368           ptr ++;
5369       }
5370       else if (!strncmp(message, "recovered:", 10))
5371       {
5372         ptr = message + 10;
5373 	while (isspace(*ptr & 255))
5374           ptr ++;
5375       }
5376       else
5377         ptr = message;
5378 
5379       if (*ptr)
5380         cupsdLogJob(job, loglevel == CUPSD_LOG_INFO ? CUPSD_LOG_DEBUG : loglevel, "%s", ptr);
5381 
5382       if (loglevel < CUPSD_LOG_DEBUG &&
5383           strcmp(job->printer->state_message, ptr))
5384       {
5385 	strlcpy(job->printer->state_message, ptr,
5386 		sizeof(job->printer->state_message));
5387 
5388 	event |= CUPSD_EVENT_PRINTER_STATE | CUPSD_EVENT_JOB_PROGRESS;
5389 
5390 	if (loglevel <= job->status_level && job->status_level > CUPSD_LOG_ERROR)
5391 	{
5392 	 /*
5393 	  * Some messages show in the job-printer-state-message attribute...
5394 	  */
5395 
5396 	  if (loglevel != CUPSD_LOG_NOTICE)
5397 	    job->status_level = loglevel;
5398 
5399 	  update_job_attrs(job, 1);
5400 
5401 	  cupsdLogJob(job, CUPSD_LOG_DEBUG,
5402 	              "Set job-printer-state-message to \"%s\", "
5403 	              "current level=%s",
5404 	              job->printer_message->values[0].string.text,
5405 	              levels[job->status_level]);
5406 	}
5407       }
5408     }
5409 
5410     if (!strchr(job->status_buffer->buffer, '\n'))
5411       break;
5412   }
5413 
5414   if (event & CUPSD_EVENT_JOB_PROGRESS)
5415     cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job,
5416                   "%s", job->printer->state_message);
5417   if (event & CUPSD_EVENT_PRINTER_STATE)
5418     cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, job->printer, NULL,
5419 		  (job->printer->type & CUPS_PRINTER_CLASS) ?
5420 		      "Class \"%s\" state changed." :
5421 		      "Printer \"%s\" state changed.",
5422 		  job->printer->name);
5423 
5424 
5425   if (ptr == NULL && !job->status_buffer->bufused)
5426   {
5427    /*
5428     * See if all of the filters and the backend have returned their
5429     * exit statuses.
5430     */
5431 
5432     for (i = 0; job->filters[i] < 0; i ++);
5433 
5434     if (job->filters[i])
5435     {
5436      /*
5437       * EOF but we haven't collected the exit status of all filters...
5438       */
5439 
5440       cupsdCheckProcess();
5441       return;
5442     }
5443 
5444     if (job->current_file >= job->num_files && job->backend > 0)
5445     {
5446      /*
5447       * EOF but we haven't collected the exit status of the backend...
5448       */
5449 
5450       cupsdCheckProcess();
5451       return;
5452     }
5453 
5454    /*
5455     * Handle the end of job stuff...
5456     */
5457 
5458     finalize_job(job, 1);
5459 
5460    /*
5461     * Try printing another job...
5462     */
5463 
5464     if (printer->state != IPP_PRINTER_STOPPED)
5465       cupsdCheckJobs();
5466   }
5467 }
5468 
5469 
5470 /*
5471  * 'update_job_attrs()' - Update the job-printer-* attributes.
5472  */
5473 
5474 void
update_job_attrs(cupsd_job_t * job,int do_message)5475 update_job_attrs(cupsd_job_t *job,	/* I - Job to update */
5476                  int         do_message)/* I - 1 = copy job-printer-state message */
5477 {
5478   int			i;		/* Looping var */
5479   int			num_reasons;	/* Actual number of reasons */
5480   const char * const	*reasons;	/* Reasons */
5481   static const char	*none = "none";	/* "none" reason */
5482 
5483 
5484  /*
5485   * Get/create the job-printer-state-* attributes...
5486   */
5487 
5488   if (!job->printer_message)
5489   {
5490     if ((job->printer_message = ippFindAttribute(job->attrs,
5491                                                  "job-printer-state-message",
5492 						 IPP_TAG_TEXT)) == NULL)
5493       job->printer_message = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_TEXT,
5494                                           "job-printer-state-message",
5495 					  NULL, "");
5496   }
5497 
5498   if (!job->printer_reasons)
5499     job->printer_reasons = ippFindAttribute(job->attrs,
5500 					    "job-printer-state-reasons",
5501 					    IPP_TAG_KEYWORD);
5502 
5503  /*
5504   * Copy or clear the printer-state-message value as needed...
5505   */
5506 
5507   if (job->state_value != IPP_JOB_PROCESSING &&
5508       job->status_level == CUPSD_LOG_INFO)
5509   {
5510     ippSetString(job->attrs, &job->printer_message, 0, "");
5511 
5512     job->dirty = 1;
5513     cupsdMarkDirty(CUPSD_DIRTY_JOBS);
5514   }
5515   else if (job->printer->state_message[0] && do_message)
5516   {
5517     ippSetString(job->attrs, &job->printer_message, 0, job->printer->state_message);
5518 
5519     job->dirty = 1;
5520     cupsdMarkDirty(CUPSD_DIRTY_JOBS);
5521   }
5522 
5523  /*
5524   * ... and the printer-state-reasons value...
5525   */
5526 
5527   if (job->printer->num_reasons == 0)
5528   {
5529     num_reasons = 1;
5530     reasons     = &none;
5531   }
5532   else
5533   {
5534     num_reasons = job->printer->num_reasons;
5535     reasons     = (const char * const *)job->printer->reasons;
5536   }
5537 
5538   if (!job->printer_reasons || job->printer_reasons->num_values != num_reasons)
5539   {
5540    /*
5541     * Replace/create a job-printer-state-reasons attribute...
5542     */
5543 
5544     ippDeleteAttribute(job->attrs, job->printer_reasons);
5545 
5546     job->printer_reasons = ippAddStrings(job->attrs,
5547                                          IPP_TAG_JOB, IPP_TAG_KEYWORD,
5548 					 "job-printer-state-reasons",
5549 					 num_reasons, NULL, NULL);
5550   }
5551   else
5552   {
5553    /*
5554     * Don't bother clearing the reason strings if they are the same...
5555     */
5556 
5557     for (i = 0; i < num_reasons; i ++)
5558       if (strcmp(job->printer_reasons->values[i].string.text, reasons[i]))
5559         break;
5560 
5561     if (i >= num_reasons)
5562       return;
5563 
5564    /*
5565     * Not the same, so free the current strings...
5566     */
5567 
5568     for (i = 0; i < num_reasons; i ++)
5569       _cupsStrFree(job->printer_reasons->values[i].string.text);
5570   }
5571 
5572  /*
5573   * Copy the reasons...
5574   */
5575 
5576   for (i = 0; i < num_reasons; i ++)
5577     job->printer_reasons->values[i].string.text = _cupsStrAlloc(reasons[i]);
5578 
5579   job->dirty = 1;
5580   cupsdMarkDirty(CUPSD_DIRTY_JOBS);
5581 }
5582