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