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