Load cups into easysw/current.
[thirdparty/cups.git] / scheduler / job.c
1 /*
2  * "$Id: job.c 4906 2006-01-10 20:53:28Z mike $"
3  *
4  *   Job management routines for the Common UNIX Printing System (CUPS).
5  *
6  *   Copyright 1997-2005 by Easy Software Products, all rights reserved.
7  *
8  *   These coded instructions, statements, and computer programs are the
9  *   property of Easy Software Products and are protected by Federal
10  *   copyright law.  Distribution and use rights are outlined in the file
11  *   "LICENSE.txt" which should have been included with this file.  If this
12  *   file is missing or damaged please contact Easy Software Products
13  *   at:
14  *
15  *       Attn: CUPS Licensing Information
16  *       Easy Software Products
17  *       44141 Airport View Drive, Suite 204
18  *       Hollywood, Maryland 20636 USA
19  *
20  *       Voice: (301) 373-9600
21  *       EMail: cups-info@cups.org
22  *         WWW: http://www.cups.org
23  *
24  * Contents:
25  *
26  *   cupsdAddJob()              - Add a new job to the job queue...
27  *   cupsdCancelJob()           - Cancel the specified print job.
28  *   cupsdCancelJobs()          - Cancel all jobs for the given destination/user...
29  *   cupsdCheckJobs()           - Check the pending jobs and start any if the
30  *                                destination is available.
31  *   cupsdCleanJobs()           - Clean out old jobs.
32  *   cupsdFreeAllJobs()         - Free all jobs from memory.
33  *   cupsdFindJob()             - Find the specified job.
34  *   cupsdGetPrinterJobCount()  - Get the number of pending, processing,
35  *                                or held jobs in a printer or class.
36  *   cupsdGetUserJobCount()     - Get the number of pending, processing,
37  *                                or held jobs for a user.
38  *   cupsdHoldJob()             - Hold the specified job.
39  *   cupsdLoadAllJobs()         - Load all jobs from disk.
40  *   cupsdMoveJob()             - Move the specified job to a different
41  *                                destination.
42  *   cupsdReleaseJob()          - Release the specified job.
43  *   cupsdRestartJob()          - Restart the specified job.
44  *   cupsdSaveJob()             - Save a job to disk.
45  *   cupsdSetJobHoldUntil()     - Set the hold time for a job...
46  *   cupsdSetJobPriority()      - Set the priority of a job, moving it up/down
47  *                                in the list as needed.
48  *   cupsdStartJob()            - Start a print job.
49  *   cupsdStopAllJobs()         - Stop all print jobs.
50  *   cupsdStopJob()             - Stop a print job.
51  *   cupsdUpdateJob()           - Read a status update from a job's filters.
52  *   compare_active_jobs()      - Compare the job IDs and priorities of two jobs.
53  *   compare_jobs()             - Compare the job IDs of two jobs.
54  *   ipp_length()               - Compute the size of the buffer needed to hold 
55  *                                the textual IPP attributes.
56  *   set_hold_until()           - Set the hold time and update job-hold-until attribute.
57  */
58
59 /*
60  * Include necessary headers...
61  */
62
63 #include "cupsd.h"
64 #include <grp.h>
65 #include <cups/backend.h>
66 #include <cups/dir.h>
67
68
69 /*
70  * Local globals...
71  */
72
73 static mime_filter_t    gziptoany_filter =
74                         {
75                           NULL,         /* Source type */
76                           NULL,         /* Destination type */
77                           0,            /* Cost */
78                           "gziptoany"   /* Filter program to run */
79                         };
80
81
82 /*
83  * Local functions...
84  */
85
86 static int      compare_active_jobs(void *first, void *second, void *data);
87 static int      compare_jobs(void *first, void *second, void *data);
88 static int      ipp_length(ipp_t *ipp);
89 static void     set_time(cupsd_job_t *job, const char *name);
90 static void     set_hold_until(cupsd_job_t *job, time_t holdtime);
91
92
93 /*
94  * 'cupsdAddJob()' - Add a new job to the job queue...
95  */
96
97 cupsd_job_t *                           /* O - New job record */
98 cupsdAddJob(int        priority,        /* I - Job priority */
99             const char *dest)           /* I - Job destination */
100 {
101   cupsd_job_t   *job;                   /* New job record */
102
103
104   job = calloc(sizeof(cupsd_job_t), 1);
105
106   job->id             = NextJobId ++;
107   job->priority       = priority;
108   job->back_pipes[0]  = -1;
109   job->back_pipes[1]  = -1;
110   job->print_pipes[0] = -1;
111   job->print_pipes[1] = -1;
112
113   cupsdSetString(&job->dest, dest);
114
115  /*
116   * Add the new job to the "all jobs" and "active jobs" lists...
117   */
118
119   cupsArrayAdd(Jobs, job);
120   cupsArrayAdd(ActiveJobs, job);
121
122   return (job);
123 }
124
125
126 /*
127  * 'cupsdCancelJob()' - Cancel the specified print job.
128  */
129
130 void
131 cupsdCancelJob(cupsd_job_t *job,        /* I - Job to cancel */
132                int         purge)       /* I - Purge jobs? */
133 {
134   int           i;                      /* Looping var */
135   char          filename[1024];         /* Job filename */
136
137
138   cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdCancelJob: id = %d", job->id);
139
140  /*
141   * Remove the job from the active list...
142   */
143
144   cupsArrayRemove(ActiveJobs, job);
145
146  /*
147   * Stop any processes that are working on the current job...
148   */
149
150   if (job->state->values[0].integer == IPP_JOB_PROCESSING)
151     cupsdStopJob(job, 0);
152
153   cupsArrayRemove(ActiveJobs, job);
154
155   job->state->values[0].integer = IPP_JOB_CANCELLED;
156
157   set_time(job, "time-at-completed");
158
159   cupsdExpireSubscriptions(NULL, job);
160
161  /*
162   * Remove any authentication data...
163   */
164
165   snprintf(filename, sizeof(filename), "%s/a%05d", RequestRoot,
166            job->id);
167   unlink(filename);
168
169  /*
170   * Remove the print file for good if we aren't preserving jobs or
171   * files...
172   */
173
174   job->current_file = 0;
175
176   if (!JobHistory || !JobFiles || purge ||
177       (job->dtype & CUPS_PRINTER_REMOTE))
178     for (i = 1; i <= job->num_files; i ++)
179     {
180       snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
181                job->id, i);
182       unlink(filename);
183     }
184
185   if (JobHistory && !purge && !(job->dtype & CUPS_PRINTER_REMOTE))
186   {
187    /*
188     * Save job state info...
189     */
190
191     cupsdSaveJob(job);
192   }
193   else
194   {
195    /*
196     * Remove the job info file...
197     */
198
199     snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot,
200              job->id);
201     unlink(filename);
202
203    /*
204     * Remove the job from the "all jobs" list...
205     */
206
207     cupsArrayRemove(Jobs, job);
208
209    /*
210     * Free all memory used...
211     */
212
213     if (job->attrs != NULL)
214       ippDelete(job->attrs);
215
216     if (job->num_files > 0)
217     {
218       free(job->compressions);
219       free(job->filetypes);
220     }
221
222     cupsdClearString(&job->username);
223     cupsdClearString(&job->dest);
224
225     free(job);
226   }
227 }
228
229
230 /*
231  * 'cupsdCancelJobs()' - Cancel all jobs for the given destination/user...
232  */
233
234 void
235 cupsdCancelJobs(const char *dest,       /* I - Destination to cancel */
236                 const char *username,   /* I - Username or NULL */
237                 int        purge)       /* I - Purge jobs? */
238 {
239   cupsd_job_t   *job;                   /* Current job */
240
241
242   for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
243        job;
244        job = (cupsd_job_t *)cupsArrayNext(Jobs))
245     if ((dest == NULL || !strcmp(job->dest, dest)) &&
246         (username == NULL || !strcmp(job->username, username)))
247     {
248      /*
249       * Cancel all jobs matching this destination/user...
250       */
251
252       cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
253                     purge ? "Job purged." : "Job canceled.");
254
255       cupsdCancelJob(job, purge);
256     }
257
258   cupsdCheckJobs();
259 }
260
261
262 /*
263  * 'cupsdCheckJobs()' - Check the pending jobs and start any if the destination
264  *                      is available.
265  */
266
267 void
268 cupsdCheckJobs(void)
269 {
270   cupsd_job_t           *job;           /* Current job in queue */
271   cupsd_printer_t       *printer,       /* Printer destination */
272                         *pclass;        /* Printer class destination */
273
274
275   DEBUG_puts("cupsdCheckJobs()");
276
277   for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
278        job;
279        job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
280   {
281    /*
282     * Start held jobs if they are ready...
283     */
284
285     if (job->state->values[0].integer == IPP_JOB_HELD &&
286         job->hold_until &&
287         job->hold_until < time(NULL))
288       job->state->values[0].integer = IPP_JOB_PENDING;
289
290    /*
291     * Start pending jobs if the destination is available...
292     */
293
294     if (job->state->values[0].integer == IPP_JOB_PENDING && !NeedReload)
295     {
296       printer = cupsdFindDest(job->dest);
297       pclass  = NULL;
298
299       while (printer &&
300              (printer->type & (CUPS_PRINTER_IMPLICIT | CUPS_PRINTER_CLASS)))
301       {
302        /*
303         * If the class is remote, just pass it to the remote server...
304         */
305
306         pclass = printer;
307
308         if (!(pclass->type & CUPS_PRINTER_REMOTE))
309         {
310           if (pclass->state != IPP_PRINTER_STOPPED)
311             printer = cupsdFindAvailablePrinter(job->dest);
312           else
313             printer = NULL;
314         }
315       }
316
317       if (!printer && !pclass)
318       {
319        /*
320         * Whoa, the printer and/or class for this destination went away;
321         * cancel the job...
322         */
323
324         cupsdLogMessage(CUPSD_LOG_WARN,
325                         "Printer/class %s has gone away; cancelling job %d!",
326                         job->dest, job->id);
327
328         cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
329                       "Job canceled because the destination printer/class has gone away.");
330
331         cupsdCancelJob(job, 1);
332       }
333       else if (printer)
334       {
335        /*
336         * See if the printer is available or remote and not printing a job;
337         * if so, start the job...
338         */
339
340         if (pclass)
341         {
342          /*
343           * Add/update a job-actual-printer-uri attribute for this job
344           * so that we know which printer actually printed the job...
345           */
346
347           ipp_attribute_t       *attr;  /* job-actual-printer-uri attribute */
348
349
350           if ((attr = ippFindAttribute(job->attrs, "job-actual-printer-uri",
351                                        IPP_TAG_URI)) != NULL)
352             cupsdSetString(&attr->values[0].string.text, printer->uri);
353           else
354             ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI,
355                          "job-actual-printer-uri", NULL, printer->uri);
356         }
357
358         if (printer->state == IPP_PRINTER_IDLE ||       /* Printer is idle */
359             ((printer->type & CUPS_PRINTER_REMOTE) &&   /* Printer is remote */
360              !printer->job))                            /* and not printing a job */
361           cupsdStartJob(job, printer);
362       }
363     }
364   }
365 }
366
367
368 /*
369  * 'cupsdCleanJobs()' - Clean out old jobs.
370  */
371
372 void
373 cupsdCleanJobs(void)
374 {
375   cupsd_job_t   *job;                   /* Current job */
376
377
378   if (!MaxJobs)
379     return;
380
381   for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
382        job && cupsArrayCount(Jobs) >= MaxJobs;
383        job = (cupsd_job_t *)cupsArrayNext(Jobs))
384     if (job->state->values[0].integer >= IPP_JOB_CANCELLED)
385       cupsdCancelJob(job, 1);
386 }
387
388
389 /*
390  * 'cupsdFinishJob()' - Finish a job.
391  */
392
393 void
394 cupsdFinishJob(cupsd_job_t *job)        /* I - Job */
395 {
396   int                   job_history;    /* Did cupsdCancelJob() keep the job? */
397   cupsd_printer_t       *printer;       /* Current printer */
398
399
400   cupsdLogMessage(CUPSD_LOG_DEBUG,
401                   "cupsdFinishJob: job %d, file %d is complete.",
402                   job->id, job->current_file - 1);
403
404   cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdFinishJob: job->status is %d",
405                   job->status);
406
407   if (job->status_buffer && job->current_file >= job->num_files)
408   {
409    /*
410     * Close the pipe and clear the input bit.
411     */
412
413     cupsdLogMessage(CUPSD_LOG_DEBUG2,
414                     "cupsdFinishJob: Removing fd %d from InputSet...",
415                     job->status_buffer->fd);
416
417     FD_CLR(job->status_buffer->fd, InputSet);
418
419     cupsdLogMessage(CUPSD_LOG_DEBUG2,
420                     "cupsdFinishJob: Closing status input pipe %d...",
421                     job->status_buffer->fd);
422
423     cupsdStatBufDelete(job->status_buffer);
424
425     job->status_buffer = NULL;
426   }
427
428   if (job->status < 0)
429   {
430    /*
431     * Backend had errors; stop it...
432     */
433
434     printer = job->printer;
435
436     switch (-job->status)
437     {
438       default :
439       case CUPS_BACKEND_FAILED :
440          /*
441           * Backend failure, use the error-policy to determine how to
442           * act...
443           */
444
445           cupsdStopJob(job, 0);
446           job->state->values[0].integer = IPP_JOB_PENDING;
447           cupsdSaveJob(job);
448
449          /*
450           * If the job was queued to a class, try requeuing it...  For
451           * faxes and retry-job queues, hold the current job for 5 minutes.
452           */
453
454           if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))
455             cupsdCheckJobs();
456           else if ((printer->type & CUPS_PRINTER_FAX) ||
457                    !strcmp(printer->error_policy, "retry-job"))
458           {
459            /*
460             * See how many times we've tried to send the job; if more than
461             * the limit, cancel the job.
462             */
463
464             job->tries ++;
465
466             if (job->tries >= JobRetryLimit)
467             {
468              /*
469               * Too many tries...
470               */
471
472               cupsdLogMessage(CUPSD_LOG_ERROR,
473                               "Canceling job %d since it could not be sent after %d tries.",
474                               job->id, JobRetryLimit);
475
476               cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
477                             "Job canceled since it could not be sent after %d tries.",
478                             JobRetryLimit);
479
480               cupsdCancelJob(job, 0);
481             }
482             else
483             {
484              /*
485               * Try again in N seconds...
486               */
487
488               set_hold_until(job, time(NULL) + JobRetryInterval);
489             }
490           }
491           else if (!strcmp(printer->error_policy, "abort-job"))
492             cupsdCancelJob(job, 0);
493           break;
494
495       case CUPS_BACKEND_CANCEL :
496          /*
497           * Cancel the job...
498           */
499
500           cupsdCancelJob(job, 0);
501           break;
502
503       case CUPS_BACKEND_HOLD :
504          /*
505           * Hold the job...
506           */
507
508           cupsdStopJob(job, 0);
509           cupsdSetJobHoldUntil(job, "indefinite");
510           cupsdSaveJob(job);
511           break;
512
513       case CUPS_BACKEND_STOP :
514          /*
515           * Stop the printer...
516           */
517
518           cupsdStopJob(job, 0);
519           cupsdSaveJob(job);
520           cupsdSetPrinterState(printer, IPP_PRINTER_STOPPED, 1);
521           break;
522
523       case CUPS_BACKEND_AUTH_REQUIRED :
524           cupsdStopJob(job, 0);
525           cupsdSetJobHoldUntil(job, "authenticated");
526           cupsdSaveJob(job);
527
528           cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job,
529                         "Authentication is required for job %d.", job->id);
530           break;
531     }
532
533    /*
534     * Try printing another job...
535     */
536
537     cupsdCheckJobs();
538   }
539   else if (job->status > 0)
540   {
541    /*
542     * Filter had errors; cancel it...
543     */
544
545     if (job->current_file < job->num_files)
546       cupsdStartJob(job, job->printer);
547     else
548     {
549       cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
550                     "Job aborted due to filter errors; please consult the "
551                     "error_log file for details.");
552
553       job_history = JobHistory && !(job->dtype & CUPS_PRINTER_REMOTE);
554
555       cupsdCancelJob(job, 0);
556
557       if (job_history)
558       {
559         job->state->values[0].integer = IPP_JOB_ABORTED;
560         cupsdSaveJob(job);
561       }
562
563       cupsdCheckJobs();
564     }
565   }
566   else
567   {
568    /*
569     * Job printed successfully; cancel it...
570     */
571
572     if (job->current_file < job->num_files)
573     {
574       FilterLevel -= job->cost;
575       cupsdStartJob(job, job->printer);
576     }
577     else
578     {
579       cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
580                     "Job completed successfully.");
581
582       job_history = JobHistory && !(job->dtype & CUPS_PRINTER_REMOTE);
583
584       cupsdCancelJob(job, 0);
585
586       if (job_history)
587       {
588         job->state->values[0].integer = IPP_JOB_COMPLETED;
589         cupsdSaveJob(job);
590       }
591
592       cupsdCheckJobs();
593     }
594   }
595 }
596
597
598 /*
599  * 'cupsdFreeAllJobs()' - Free all jobs from memory.
600  */
601
602 void
603 cupsdFreeAllJobs(void)
604 {
605   cupsd_job_t   *job;                   /* Current job */
606
607
608   cupsdHoldSignals();
609
610   cupsdStopAllJobs();
611
612   for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
613        job;
614        job = (cupsd_job_t *)cupsArrayNext(Jobs))
615   {
616     cupsArrayRemove(Jobs, job);
617     cupsArrayRemove(ActiveJobs, job);
618
619     ippDelete(job->attrs);
620
621     if (job->num_files > 0)
622     {
623       free(job->compressions);
624       free(job->filetypes);
625     }
626
627     free(job);
628   }
629
630   cupsdReleaseSignals();
631 }
632
633
634 /*
635  * 'cupsdFindJob()' - Find the specified job.
636  */
637
638 cupsd_job_t *                           /* O - Job data */
639 cupsdFindJob(int id)                    /* I - Job ID */
640 {
641   cupsd_job_t   key;                    /* Search key */
642
643
644   key.id = id;
645
646   return ((cupsd_job_t *)cupsArrayFind(Jobs, &key));
647 }
648
649
650 /*
651  * 'cupsdGetPrinterJobCount()' - Get the number of pending, processing,
652  *                               or held jobs in a printer or class.
653  */
654
655 int                                     /* O - Job count */
656 cupsdGetPrinterJobCount(
657     const char *dest)                   /* I - Printer or class name */
658 {
659   int           count;                  /* Job count */
660   cupsd_job_t   *job;                   /* Current job */
661
662
663   for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0;
664        job;
665        job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
666     if (!strcasecmp(job->dest, dest))
667       count ++;
668
669   return (count);
670 }
671
672
673 /*
674  * 'cupsdGetUserJobCount()' - Get the number of pending, processing,
675  *                            or held jobs for a user.
676  */
677
678 int                                     /* O - Job count */
679 cupsdGetUserJobCount(
680     const char *username)               /* I - Username */
681 {
682   int           count;                  /* Job count */
683   cupsd_job_t   *job;                   /* Current job */
684
685
686   for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0;
687        job;
688        job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
689     if (!strcasecmp(job->username, username))
690       count ++;
691
692   return (count);
693 }
694
695
696 /*
697  * 'cupsdHoldJob()' - Hold the specified job.
698  */
699
700 void
701 cupsdHoldJob(cupsd_job_t *job)          /* I - Job data */
702 {
703   cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdHoldJob: id = %d", job->id);
704
705   if (job->state->values[0].integer == IPP_JOB_PROCESSING)
706     cupsdStopJob(job, 0);
707
708   DEBUG_puts("cupsdHoldJob: setting state to held...");
709
710   job->state->values[0].integer = IPP_JOB_HELD;
711
712   cupsdSaveJob(job);
713
714   cupsdCheckJobs();
715 }
716
717
718 /*
719  * 'cupsdLoadAllJobs()' - Load all jobs from disk.
720  */
721
722 void
723 cupsdLoadAllJobs(void)
724 {
725   cups_dir_t            *dir;           /* Directory */
726   cups_dentry_t         *dent;          /* Directory entry */
727   char                  filename[1024]; /* Full filename of job file */
728   int                   fd;             /* File descriptor */
729   cupsd_job_t           *job;           /* New job */
730   int                   jobid,          /* Current job ID */
731                         fileid;         /* Current file ID */
732   ipp_attribute_t       *attr;          /* Job attribute */
733   char                  method[HTTP_MAX_URI],
734                                         /* Method portion of URI */
735                         username[HTTP_MAX_URI],
736                                         /* Username portion of URI */
737                         host[HTTP_MAX_URI],
738                                         /* Host portion of URI */
739                         resource[HTTP_MAX_URI];
740                                         /* Resource portion of URI */
741   int                   port;           /* Port portion of URI */
742   const char            *dest;          /* Destination */
743   mime_type_t           **filetypes;    /* New filetypes array */
744   int                   *compressions;  /* New compressions array */
745
746
747  /*
748   * First create the job lists...
749   */
750
751   if (!Jobs)
752     Jobs = cupsArrayNew(compare_jobs, NULL);
753
754   if (!ActiveJobs)
755     ActiveJobs = cupsArrayNew(compare_active_jobs, NULL);
756
757  /*
758   * Then open the requests directory...
759   */
760
761   cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdLoadAllJobs: Scanning %s...",
762                   RequestRoot);
763
764   if ((dir = cupsDirOpen(RequestRoot)) == NULL)
765   {
766     cupsdLogMessage(CUPSD_LOG_ERROR,
767                     "cupsdLoadAllJobs: Unable to open spool directory %s: %s",
768                     RequestRoot, strerror(errno));
769     return;
770   }
771
772  /*
773   * Read all the c##### files...
774   */
775
776   while ((dent = cupsDirRead(dir)) != NULL)
777     if (strlen(dent->filename) >= 6 && dent->filename[0] == 'c')
778     {
779      /*
780       * Allocate memory for the job...
781       */
782
783       if ((job = calloc(sizeof(cupsd_job_t), 1)) == NULL)
784       {
785         cupsdLogMessage(CUPSD_LOG_ERROR,
786                         "cupsdLoadAllJobs: Ran out of memory for jobs!");
787         cupsDirClose(dir);
788         return;
789       }
790
791       if ((job->attrs = ippNew()) == NULL)
792       {
793         free(job);
794         cupsdLogMessage(CUPSD_LOG_ERROR,
795                         "cupsdLoadAllJobs: Ran out of memory for job attributes!");
796         cupsDirClose(dir);
797         return;
798       }
799
800      /*
801       * Assign the job ID...
802       */
803
804       job->id             = atoi(dent->filename + 1);
805       job->back_pipes[0]  = -1;
806       job->back_pipes[1]  = -1;
807       job->print_pipes[0] = -1;
808       job->print_pipes[1] = -1;
809
810       cupsdLogMessage(CUPSD_LOG_DEBUG,
811                       "cupsdLoadAllJobs: Loading attributes for job %d...",
812                       job->id);
813
814       if (job->id >= NextJobId)
815         NextJobId = job->id + 1;
816
817      /*
818       * Load the job control file...
819       */
820
821       snprintf(filename, sizeof(filename), "%s/%s", RequestRoot, dent->filename);
822       if ((fd = open(filename, O_RDONLY)) < 0)
823       {
824         cupsdLogMessage(CUPSD_LOG_ERROR,
825                         "cupsdLoadAllJobs: Unable to open job control file \"%s\" - %s!",
826                         filename, strerror(errno));
827         ippDelete(job->attrs);
828         free(job);
829         unlink(filename);
830         continue;
831       }
832       else
833       {
834         if (ippReadFile(fd, job->attrs) != IPP_DATA)
835         {
836           cupsdLogMessage(CUPSD_LOG_ERROR,
837                           "cupsdLoadAllJobs: Unable to read job control file \"%s\"!",
838                           filename);
839           close(fd);
840           ippDelete(job->attrs);
841           free(job);
842           unlink(filename);
843           continue;
844         }
845
846         close(fd);
847       }
848
849       if ((job->state = ippFindAttribute(job->attrs, "job-state", IPP_TAG_ENUM)) == NULL)
850       {
851         cupsdLogMessage(CUPSD_LOG_ERROR,
852                         "cupsdLoadAllJobs: Missing or bad job-state attribute in control file \"%s\"!",
853                         filename);
854         ippDelete(job->attrs);
855         free(job);
856         unlink(filename);
857         continue;
858       }
859
860       if ((attr = ippFindAttribute(job->attrs, "job-printer-uri", IPP_TAG_URI)) == NULL)
861       {
862         cupsdLogMessage(CUPSD_LOG_ERROR,
863                         "cupsdLoadAllJobs: No job-printer-uri attribute in control file \"%s\"!",
864                         filename);
865         ippDelete(job->attrs);
866         free(job);
867         unlink(filename);
868         continue;
869       }
870
871       httpSeparateURI(attr->values[0].string.text, method, sizeof(method),
872                       username, sizeof(username), host, sizeof(host), &port,
873                       resource, sizeof(resource));
874
875       if ((dest = cupsdValidateDest(host, resource, &(job->dtype),
876                                     NULL)) == NULL)
877       {
878         cupsdLogMessage(CUPSD_LOG_ERROR,
879                         "cupsdLoadAllJobs: Unable to queue job for destination \"%s\"!",
880                         attr->values[0].string.text);
881         ippDelete(job->attrs);
882         free(job);
883         unlink(filename);
884         continue;
885       }
886
887       cupsdSetString(&job->dest, dest);
888
889       job->sheets     = ippFindAttribute(job->attrs, "job-media-sheets-completed",
890                                          IPP_TAG_INTEGER);
891       job->job_sheets = ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_NAME);
892
893       if ((attr = ippFindAttribute(job->attrs, "job-priority", IPP_TAG_INTEGER)) == NULL)
894       {
895         cupsdLogMessage(CUPSD_LOG_ERROR,
896                         "cupsdLoadAllJobs: Missing or bad job-priority attribute in control file \"%s\"!",
897                         filename);
898         ippDelete(job->attrs);
899         free(job);
900         unlink(filename);
901         continue;
902       }
903       job->priority = attr->values[0].integer;
904
905       if ((attr = ippFindAttribute(job->attrs, "job-originating-user-name", IPP_TAG_NAME)) == NULL)
906       {
907         cupsdLogMessage(CUPSD_LOG_ERROR,
908                         "cupsdLoadAllJobs: Missing or bad job-originating-user-name attribute in control file \"%s\"!",
909                         filename);
910         ippDelete(job->attrs);
911         free(job);
912         unlink(filename);
913         continue;
914       }
915       cupsdSetString(&job->username, attr->values[0].string.text);
916
917      /*
918       * Insert the job into the array, sorting by job priority and ID...
919       */
920
921       cupsArrayAdd(Jobs, job);
922       if (job->state->values[0].integer < IPP_JOB_STOPPED)
923         cupsArrayAdd(ActiveJobs,job);
924
925      /*
926       * Set the job hold-until time and state...
927       */
928
929       if (job->state->values[0].integer == IPP_JOB_HELD)
930       {
931         if ((attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_KEYWORD)) == NULL)
932           attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
933
934         if (attr == NULL)
935           job->state->values[0].integer = IPP_JOB_PENDING;
936         else
937           cupsdSetJobHoldUntil(job, attr->values[0].string.text);
938       }
939       else if (job->state->values[0].integer == IPP_JOB_PROCESSING)
940         job->state->values[0].integer = IPP_JOB_PENDING;
941     }
942
943  /*
944   * Read all the d##### files...
945   */
946
947   cupsDirRewind(dir);
948
949   while ((dent = cupsDirRead(dir)) != NULL)
950     if (strlen(dent->filename) > 7 && dent->filename[0] == 'd' &&
951         strchr(dent->filename, '-'))
952     {
953      /*
954       * Find the job...
955       */
956
957       jobid  = atoi(dent->filename + 1);
958       fileid = atoi(strchr(dent->filename, '-') + 1);
959
960       cupsdLogMessage(CUPSD_LOG_DEBUG,
961                       "cupsdLoadAllJobs: Auto-typing document file %s...",
962                       dent->filename);
963
964       snprintf(filename, sizeof(filename), "%s/%s", RequestRoot, dent->filename);
965
966       if ((job = cupsdFindJob(jobid)) == NULL)
967       {
968         cupsdLogMessage(CUPSD_LOG_ERROR,
969                         "cupsdLoadAllJobs: Orphaned print file \"%s\"!",
970                         filename);
971         unlink(filename);
972         continue;
973       }
974
975       if (fileid > job->num_files)
976       {
977         if (job->num_files == 0)
978         {
979           compressions = (int *)calloc(fileid, sizeof(int));
980           filetypes    = (mime_type_t **)calloc(fileid, sizeof(mime_type_t *));
981         }
982         else
983         {
984           compressions = (int *)realloc(job->compressions,
985                                         sizeof(int) * fileid);
986           filetypes    = (mime_type_t **)realloc(job->filetypes,
987                                                  sizeof(mime_type_t *) * fileid);
988         }
989
990         if (compressions == NULL || filetypes == NULL)
991         {
992           cupsdLogMessage(CUPSD_LOG_ERROR, "cupsdLoadAllJobs: Ran out of memory for job file types!");
993           continue;
994         }
995
996         job->compressions = compressions;
997         job->filetypes    = filetypes;
998         job->num_files    = fileid;
999       }
1000
1001       job->filetypes[fileid - 1] = mimeFileType(MimeDatabase, filename,
1002                                                 job->compressions + fileid - 1);
1003
1004       if (job->filetypes[fileid - 1] == NULL)
1005         job->filetypes[fileid - 1] = mimeType(MimeDatabase, "application",
1006                                               "vnd.cups-raw");
1007     }
1008
1009   cupsDirClose(dir);
1010
1011  /*
1012   * Clean out old jobs as needed...
1013   */
1014
1015   cupsdCleanJobs();
1016 }
1017
1018
1019 /*
1020  * 'cupsdMoveJob()' - Move the specified job to a different destination.
1021  */
1022
1023 void
1024 cupsdMoveJob(cupsd_job_t *job,          /* I - Job */
1025              const char  *dest)         /* I - Destination */
1026 {
1027   ipp_attribute_t       *attr;          /* job-printer-uri attribute */
1028   cupsd_printer_t       *p;             /* Destination printer or class */
1029
1030
1031  /*
1032   * Find the printer...
1033   */
1034
1035   if ((p = cupsdFindDest(dest)) == NULL)
1036     return;
1037
1038  /*
1039   * Don't move completed jobs...
1040   */
1041
1042   if (job->state->values[0].integer >= IPP_JOB_PROCESSING)
1043     return;
1044
1045  /*
1046   * Change the destination information...
1047   */
1048
1049   cupsdSetString(&job->dest, dest);
1050   job->dtype = p->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE |
1051                           CUPS_PRINTER_IMPLICIT);
1052
1053   if ((attr = ippFindAttribute(job->attrs, "job-printer-uri", IPP_TAG_URI)) != NULL)
1054     cupsdSetString(&(attr->values[0].string.text), p->uri);
1055
1056   cupsdSaveJob(job);
1057 }
1058
1059
1060 /*
1061  * 'cupsdReleaseJob()' - Release the specified job.
1062  */
1063
1064 void
1065 cupsdReleaseJob(cupsd_job_t *job)       /* I - Job */
1066 {
1067   cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdReleaseJob: id = %d", job->id);
1068
1069   if (job->state->values[0].integer == IPP_JOB_HELD)
1070   {
1071     DEBUG_puts("cupsdReleaseJob: setting state to pending...");
1072
1073     job->state->values[0].integer = IPP_JOB_PENDING;
1074     cupsdSaveJob(job);
1075     cupsdCheckJobs();
1076   }
1077 }
1078
1079
1080 /*
1081  * 'cupsdRestartJob()' - Restart the specified job.
1082  */
1083
1084 void
1085 cupsdRestartJob(cupsd_job_t *job)       /* I - Job */
1086 {
1087   cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdRestartJob: id = %d", job->id);
1088
1089   if (job->state->values[0].integer == IPP_JOB_STOPPED || JobFiles)
1090   {
1091     job->tries = 0;
1092     job->state->values[0].integer = IPP_JOB_PENDING;
1093     cupsdSaveJob(job);
1094     cupsdCheckJobs();
1095   }
1096 }
1097
1098
1099 /*
1100  * 'cupsdSaveJob()' - Save a job to disk.
1101  */
1102
1103 void
1104 cupsdSaveJob(cupsd_job_t *job)          /* I - Job */
1105 {
1106   char          filename[1024];         /* Job control filename */
1107   int           fd;                     /* File descriptor */
1108
1109
1110   snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, job->id);
1111
1112   if ((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0)
1113   {
1114     cupsdLogMessage(CUPSD_LOG_ERROR,
1115                     "cupsdSaveJob: Unable to create job control file \"%s\" - %s.",
1116                     filename, strerror(errno));
1117     return;
1118   }
1119
1120   fchmod(fd, 0600);
1121   fchown(fd, RunUser, Group);
1122
1123   ippWriteFile(fd, job->attrs);
1124
1125   cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSaveJob: Closing file %d...", fd);
1126
1127   close(fd);
1128 }
1129
1130
1131 /*
1132  * 'cupsdSetJobHoldUntil()' - Set the hold time for a job...
1133  */
1134
1135 void
1136 cupsdSetJobHoldUntil(cupsd_job_t *job,  /* I - Job */
1137                      const char  *when) /* I - When to resume */
1138 {
1139   time_t        curtime;                /* Current time */
1140   struct tm     *curdate;               /* Current date */
1141   int           hour;                   /* Hold hour */
1142   int           minute;                 /* Hold minute */
1143   int           second;                 /* Hold second */
1144
1145
1146   cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdSetJobHoldUntil(%d, \"%s\")",
1147                   job->id, when);
1148
1149   second = 0;
1150
1151   if (!strcmp(when, "indefinite") || !strcmp(when, "authenticated"))
1152   {
1153    /*
1154     * Hold indefinitely...
1155     */
1156
1157     job->hold_until = 0;
1158   }
1159   else if (!strcmp(when, "day-time"))
1160   {
1161    /*
1162     * Hold to 6am the next morning unless local time is < 6pm.
1163     */
1164
1165     curtime = time(NULL);
1166     curdate = localtime(&curtime);
1167
1168     if (curdate->tm_hour < 18)
1169       job->hold_until = curtime;
1170     else
1171       job->hold_until = curtime +
1172                         ((29 - curdate->tm_hour) * 60 + 59 -
1173                          curdate->tm_min) * 60 + 60 - curdate->tm_sec;
1174   }
1175   else if (!strcmp(when, "evening") || strcmp(when, "night"))
1176   {
1177    /*
1178     * Hold to 6pm unless local time is > 6pm or < 6am.
1179     */
1180
1181     curtime = time(NULL);
1182     curdate = localtime(&curtime);
1183
1184     if (curdate->tm_hour < 6 || curdate->tm_hour >= 18)
1185       job->hold_until = curtime;
1186     else
1187       job->hold_until = curtime +
1188                         ((17 - curdate->tm_hour) * 60 + 59 -
1189                          curdate->tm_min) * 60 + 60 - curdate->tm_sec;
1190   }  
1191   else if (!strcmp(when, "second-shift"))
1192   {
1193    /*
1194     * Hold to 4pm unless local time is > 4pm.
1195     */
1196
1197     curtime = time(NULL);
1198     curdate = localtime(&curtime);
1199
1200     if (curdate->tm_hour >= 16)
1201       job->hold_until = curtime;
1202     else
1203       job->hold_until = curtime +
1204                         ((15 - curdate->tm_hour) * 60 + 59 -
1205                          curdate->tm_min) * 60 + 60 - curdate->tm_sec;
1206   }  
1207   else if (!strcmp(when, "third-shift"))
1208   {
1209    /*
1210     * Hold to 12am unless local time is < 8am.
1211     */
1212
1213     curtime = time(NULL);
1214     curdate = localtime(&curtime);
1215
1216     if (curdate->tm_hour < 8)
1217       job->hold_until = curtime;
1218     else
1219       job->hold_until = curtime +
1220                         ((23 - curdate->tm_hour) * 60 + 59 -
1221                          curdate->tm_min) * 60 + 60 - curdate->tm_sec;
1222   }  
1223   else if (!strcmp(when, "weekend"))
1224   {
1225    /*
1226     * Hold to weekend unless we are in the weekend.
1227     */
1228
1229     curtime = time(NULL);
1230     curdate = localtime(&curtime);
1231
1232     if (curdate->tm_wday || curdate->tm_wday == 6)
1233       job->hold_until = curtime;
1234     else
1235       job->hold_until = curtime +
1236                         (((5 - curdate->tm_wday) * 24 +
1237                           (17 - curdate->tm_hour)) * 60 + 59 -
1238                            curdate->tm_min) * 60 + 60 - curdate->tm_sec;
1239   }
1240   else if (sscanf(when, "%d:%d:%d", &hour, &minute, &second) >= 2)
1241   {
1242    /*
1243     * Hold to specified GMT time (HH:MM or HH:MM:SS)...
1244     */
1245
1246     curtime = time(NULL);
1247     curdate = gmtime(&curtime);
1248
1249     job->hold_until = curtime +
1250                       ((hour - curdate->tm_hour) * 60 + minute -
1251                        curdate->tm_min) * 60 + second - curdate->tm_sec;
1252
1253    /*
1254     * Hold until next day as needed...
1255     */
1256
1257     if (job->hold_until < curtime)
1258       job->hold_until += 24 * 60 * 60 * 60;
1259   }
1260
1261   cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdSetJobHoldUntil: hold_until = %d",
1262                   (int)job->hold_until);
1263 }
1264
1265
1266 /*
1267  * 'cupsdSetJobPriority()' - Set the priority of a job, moving it up/down in
1268  *                           the list as needed.
1269  */
1270
1271 void
1272 cupsdSetJobPriority(
1273     cupsd_job_t *job,                   /* I - Job ID */
1274     int         priority)               /* I - New priority (0 to 100) */
1275 {
1276   ipp_attribute_t       *attr;          /* Job attribute */
1277
1278
1279  /*
1280   * Don't change completed jobs...
1281   */
1282
1283   if (job->state->values[0].integer >= IPP_JOB_PROCESSING)
1284     return;
1285
1286  /*
1287   * Set the new priority and re-add the job into the active list...
1288   */
1289
1290   cupsArrayRemove(ActiveJobs, job);
1291
1292   job->priority = priority;
1293
1294   if ((attr = ippFindAttribute(job->attrs, "job-priority", IPP_TAG_INTEGER)) != NULL)
1295     attr->values[0].integer = priority;
1296   else
1297     ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-priority",
1298                   priority);
1299
1300   cupsArrayAdd(ActiveJobs, job);
1301
1302   cupsdSaveJob(job);
1303 }
1304
1305
1306 /*
1307  * 'cupsdStartJob()' - Start a print job.
1308  */
1309
1310 void
1311 cupsdStartJob(cupsd_job_t     *job,     /* I - Job ID */
1312               cupsd_printer_t *printer) /* I - Printer to print job */
1313 {
1314   int                   i;              /* Looping var */
1315   int                   slot;           /* Pipe slot */
1316   int                   num_filters;    /* Number of filters for job */
1317   mime_filter_t         *filters;       /* Filters for job */
1318   char                  method[255],    /* Method for output */
1319                         *optptr,        /* Pointer to options */
1320                         *valptr;        /* Pointer in value string */
1321   ipp_attribute_t       *attr;          /* Current attribute */
1322   int                   pid;            /* Process ID of new filter process */
1323   int                   banner_page;    /* 1 if banner page, 0 otherwise */
1324   int                   statusfds[2],   /* Pipes used between the filters and scheduler */
1325                         filterfds[2][2];/* Pipes used between the filters */
1326   int                   envc;           /* Number of environment variables */
1327   char                  *argv[8],       /* Filter command-line arguments */
1328                         sani_uri[1024], /* Sanitized DEVICE_URI env var */
1329                         filename[1024], /* Job filename */
1330                         command[1024],  /* Full path to filter/backend command */
1331                         jobid[255],     /* Job ID string */
1332                         title[IPP_MAX_NAME],
1333                                         /* Job title string */
1334                         copies[255],    /* # copies string */
1335                         *envp[100],     /* Environment variables */
1336                         charset[255],   /* CHARSET environment variable */
1337                         class_name[255],/* CLASS environment variable */
1338                         classification[1024],
1339                                         /* CLASSIFICATION environment variable */
1340                         content_type[1024],
1341                                         /* CONTENT_TYPE environment variable */
1342                         device_uri[1024],
1343                                         /* DEVICE_URI environment variable */
1344                         lang[255],      /* LANG environment variable */
1345                         ppd[1024],      /* PPD environment variable */
1346                         printer_name[255],
1347                                         /* PRINTER environment variable */
1348                         rip_max_cache[255];
1349                                         /* RIP_MAX_CACHE environment variable */
1350   static char           *options = NULL;/* Full list of options */
1351   static int            optlength = 0;  /* Length of option buffer */
1352
1353
1354   cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdStartJob() id = %d, file = %d/%d",
1355                   job->id, job->current_file, job->num_files);
1356
1357   if (job->num_files == 0)
1358   {
1359     cupsdLogMessage(CUPSD_LOG_ERROR, "Job ID %d has no files!  Cancelling it!",
1360                     job->id);
1361
1362     cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
1363                   "Job canceled because it has no files.");
1364
1365     cupsdCancelJob(job, 0);
1366     return;
1367   }
1368
1369  /*
1370   * Figure out what filters are required to convert from
1371   * the source to the destination type...
1372   */
1373
1374   num_filters   = 0;
1375   job->cost = 0;
1376
1377   if (printer->raw)
1378   {
1379    /*
1380     * Remote jobs and raw queues go directly to the printer without
1381     * filtering...
1382     */
1383
1384     cupsdLogMessage(CUPSD_LOG_DEBUG,
1385                     "cupsdStartJob: Sending job to queue tagged as raw...");
1386
1387     filters = NULL;
1388   }
1389   else
1390   {
1391    /*
1392     * Local jobs get filtered...
1393     */
1394
1395     filters = mimeFilter(MimeDatabase, job->filetypes[job->current_file],
1396                          printer->filetype, &num_filters, MAX_FILTERS - 1);
1397
1398     if (num_filters == 0)
1399     {
1400       cupsdLogMessage(CUPSD_LOG_ERROR,
1401                       "Unable to convert file %d to printable format for job %d!",
1402                       job->current_file, job->id);
1403       cupsdLogMessage(CUPSD_LOG_INFO,
1404                       "Hint: Do you have ESP Ghostscript installed?");
1405
1406       if (LogLevel < CUPSD_LOG_DEBUG)
1407         cupsdLogMessage(CUPSD_LOG_INFO,
1408                         "Hint: Try setting the LogLevel to \"debug\".");
1409
1410       job->current_file ++;
1411
1412       if (job->current_file == job->num_files)
1413       {
1414         cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
1415                       "Job canceled because it has no files that can be printed.");
1416
1417         cupsdCancelJob(job, 0);
1418       }
1419
1420       return;
1421     }
1422
1423    /*
1424     * Remove NULL ("-") filters...
1425     */
1426
1427     for (i = 0; i < num_filters;)
1428       if (strcmp(filters[i].filter, "-") == 0)
1429       {
1430         num_filters --;
1431         if (i < num_filters)
1432           memcpy(filters + i, filters + i + 1,
1433                  (num_filters - i) * sizeof(mime_filter_t));
1434       }
1435       else
1436         i ++;
1437
1438     if (num_filters == 0)
1439     {
1440       free(filters);
1441       filters = NULL;
1442     }
1443     else
1444     {
1445      /*
1446       * Compute filter cost...
1447       */
1448
1449       for (i = 0; i < num_filters; i ++)
1450         job->cost += filters[i].cost;
1451     }
1452   }
1453
1454  /*
1455   * See if the filter cost is too high...
1456   */
1457
1458   if ((FilterLevel + job->cost) > FilterLimit && FilterLevel > 0 &&
1459       FilterLimit > 0)
1460   {
1461    /*
1462     * Don't print this job quite yet...
1463     */
1464
1465     if (filters != NULL)
1466       free(filters);
1467
1468     cupsdLogMessage(CUPSD_LOG_INFO,
1469                     "Holding job %d because filter limit has been reached.",
1470                     job->id);
1471     cupsdLogMessage(CUPSD_LOG_DEBUG,
1472                     "cupsdStartJob: id=%d, file=%d, cost=%d, level=%d, limit=%d",
1473                     job->id, job->current_file, job->cost, FilterLevel,
1474                     FilterLimit);
1475     return;
1476   }
1477
1478   FilterLevel += job->cost;
1479
1480  /*
1481   * Add decompression filters, if any...
1482   */
1483
1484   if (job->compressions[job->current_file])
1485   {
1486    /*
1487     * Add gziptoany filter to the front of the list...
1488     */
1489
1490     mime_filter_t       *temp_filters;
1491
1492     if (num_filters == 0)
1493       temp_filters = malloc(sizeof(mime_filter_t));
1494     else
1495       temp_filters = realloc(filters,
1496                              sizeof(mime_filter_t) * (num_filters + 1));
1497
1498     if (temp_filters == NULL)
1499     {
1500       cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to add decompression filter - %s",
1501                       strerror(errno));
1502
1503       if (filters != NULL)
1504         free(filters);
1505
1506       job->current_file ++;
1507
1508       if (job->current_file == job->num_files)
1509       {
1510         cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
1511                       "Job canceled because the print file could not be decompressed.");
1512
1513         cupsdCancelJob(job, 0);
1514       }
1515
1516       return;
1517     }
1518
1519     filters = temp_filters;
1520     memmove(filters + 1, filters, num_filters * sizeof(mime_filter_t));
1521     *filters = gziptoany_filter;
1522     num_filters ++;
1523   }
1524
1525  /*
1526   * Add port monitor, if any...
1527   */
1528
1529   if (printer->port_monitor)
1530   {
1531    /*
1532     * Add port monitor to the end of the list...
1533     */
1534
1535     mime_filter_t       *temp_filters;
1536
1537     if (num_filters == 0)
1538       temp_filters = malloc(sizeof(mime_filter_t));
1539     else
1540       temp_filters = realloc(filters,
1541                              sizeof(mime_filter_t) * (num_filters + 1));
1542
1543     if (temp_filters == NULL)
1544     {
1545       cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to add port monitor - %s",
1546                       strerror(errno));
1547
1548       if (filters != NULL)
1549         free(filters);
1550
1551       job->current_file ++;
1552
1553       if (job->current_file == job->num_files)
1554       {
1555         cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
1556                       "Job canceled because the port monitor could not be added.");
1557
1558         cupsdCancelJob(job, 0);
1559       }
1560
1561       return;
1562     }
1563
1564     filters = temp_filters;
1565     memset(filters + num_filters, 0, sizeof(mime_filter_t));
1566     snprintf(filters[num_filters].filter, sizeof(filters[num_filters].filter),
1567              "%s/monitor/%s", ServerBin, printer->port_monitor);
1568     num_filters ++;
1569   }
1570
1571  /*
1572   * Update the printer and job state to "processing"...
1573   */
1574
1575   job->state->values[0].integer = IPP_JOB_PROCESSING;
1576   job->status  = 0;
1577   job->printer = printer;
1578   printer->job = job;
1579   cupsdSetPrinterState(printer, IPP_PRINTER_PROCESSING, 0);
1580
1581   if (job->current_file == 0)
1582   {
1583     set_time(job, "time-at-processing");
1584     cupsdOpenPipe(job->back_pipes);
1585   }
1586
1587  /*
1588   * Determine if we are printing a banner page or not...
1589   */
1590
1591   if (job->job_sheets == NULL)
1592   {
1593     cupsdLogMessage(CUPSD_LOG_DEBUG, "No job-sheets attribute.");
1594     if ((job->job_sheets =
1595          ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_ZERO)) != NULL)
1596       cupsdLogMessage(CUPSD_LOG_DEBUG,
1597                       "... but someone added one without setting job_sheets!");
1598   }
1599   else if (job->job_sheets->num_values == 1)
1600     cupsdLogMessage(CUPSD_LOG_DEBUG, "job-sheets=%s",
1601                job->job_sheets->values[0].string.text);
1602   else
1603     cupsdLogMessage(CUPSD_LOG_DEBUG, "job-sheets=%s,%s",
1604                job->job_sheets->values[0].string.text,
1605                job->job_sheets->values[1].string.text);
1606
1607   if (printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT))
1608     banner_page = 0;
1609   else if (job->job_sheets == NULL)
1610     banner_page = 0;
1611   else if (strcasecmp(job->job_sheets->values[0].string.text, "none") != 0 &&
1612            job->current_file == 0)
1613     banner_page = 1;
1614   else if (job->job_sheets->num_values > 1 &&
1615            strcasecmp(job->job_sheets->values[1].string.text, "none") != 0 &&
1616            job->current_file == (job->num_files - 1))
1617     banner_page = 1;
1618   else
1619     banner_page = 0;
1620
1621   cupsdLogMessage(CUPSD_LOG_DEBUG, "banner_page = %d", banner_page);
1622
1623  /*
1624   * Building the options string is harder than it needs to be, but
1625   * for the moment we need to pass strings for command-line args and
1626   * not IPP attribute pointers... :)
1627   *
1628   * First allocate/reallocate the option buffer as needed...
1629   */
1630
1631   i = ipp_length(job->attrs);
1632
1633   if (i > optlength)
1634   {
1635     if (optlength == 0)
1636       optptr = malloc(i);
1637     else
1638       optptr = realloc(options, i);
1639
1640     if (optptr == NULL)
1641     {
1642       cupsdLogMessage(CUPSD_LOG_CRIT,
1643                       "cupsdStartJob: Unable to allocate %d bytes for option buffer for job %d!",
1644                       i, job->id);
1645
1646       if (filters != NULL)
1647         free(filters);
1648
1649       FilterLevel -= job->cost;
1650
1651       cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
1652                     "Job canceled because the server ran out of memory.");
1653
1654       cupsdCancelJob(job, 0);
1655       return;
1656     }
1657
1658     options   = optptr;
1659     optlength = i;
1660   }
1661
1662  /*
1663   * Now loop through the attributes and convert them to the textual
1664   * representation used by the filters...
1665   */
1666
1667   optptr  = options;
1668   *optptr = '\0';
1669
1670   snprintf(title, sizeof(title), "%s-%d", printer->name, job->id);
1671   strcpy(copies, "1");
1672
1673   for (attr = job->attrs->attrs; attr != NULL; attr = attr->next)
1674   {
1675     if (strcmp(attr->name, "copies") == 0 &&
1676         attr->value_tag == IPP_TAG_INTEGER)
1677     {
1678      /*
1679       * Don't use the # copies attribute if we are printing the job sheets...
1680       */
1681
1682       if (!banner_page)
1683         sprintf(copies, "%d", attr->values[0].integer);
1684     }
1685     else if (strcmp(attr->name, "job-name") == 0 &&
1686              (attr->value_tag == IPP_TAG_NAME ||
1687               attr->value_tag == IPP_TAG_NAMELANG))
1688       strlcpy(title, attr->values[0].string.text, sizeof(title));
1689     else if (attr->group_tag == IPP_TAG_JOB)
1690     {
1691      /*
1692       * Filter out other unwanted attributes...
1693       */
1694
1695       if (attr->value_tag == IPP_TAG_MIMETYPE ||
1696           attr->value_tag == IPP_TAG_NAMELANG ||
1697           attr->value_tag == IPP_TAG_TEXTLANG ||
1698           attr->value_tag == IPP_TAG_URI ||
1699           attr->value_tag == IPP_TAG_URISCHEME ||
1700           attr->value_tag == IPP_TAG_BEGIN_COLLECTION) /* Not yet supported */
1701         continue;
1702
1703       if (strncmp(attr->name, "time-", 5) == 0)
1704         continue;
1705
1706       if (strncmp(attr->name, "job-", 4) == 0 &&
1707           !(printer->type & CUPS_PRINTER_REMOTE))
1708         continue;
1709
1710       if (strncmp(attr->name, "job-", 4) == 0 &&
1711           strcmp(attr->name, "job-billing") != 0 &&
1712           strcmp(attr->name, "job-sheets") != 0 &&
1713           strcmp(attr->name, "job-hold-until") != 0 &&
1714           strcmp(attr->name, "job-priority") != 0)
1715         continue;
1716
1717       if ((strcmp(attr->name, "page-label") == 0 ||
1718            strcmp(attr->name, "page-border") == 0 ||
1719            strncmp(attr->name, "number-up", 9) == 0 ||
1720            strcmp(attr->name, "page-set") == 0) &&
1721           banner_page)
1722         continue;
1723
1724      /*
1725       * Otherwise add them to the list...
1726       */
1727
1728       if (optptr > options)
1729         strlcat(optptr, " ", optlength - (optptr - options));
1730
1731       if (attr->value_tag != IPP_TAG_BOOLEAN)
1732       {
1733         strlcat(optptr, attr->name, optlength - (optptr - options));
1734         strlcat(optptr, "=", optlength - (optptr - options));
1735       }
1736
1737       for (i = 0; i < attr->num_values; i ++)
1738       {
1739         if (i)
1740           strlcat(optptr, ",", optlength - (optptr - options));
1741
1742         optptr += strlen(optptr);
1743
1744         switch (attr->value_tag)
1745         {
1746           case IPP_TAG_INTEGER :
1747           case IPP_TAG_ENUM :
1748               snprintf(optptr, optlength - (optptr - options),
1749                        "%d", attr->values[i].integer);
1750               break;
1751
1752           case IPP_TAG_BOOLEAN :
1753               if (!attr->values[i].boolean)
1754                 strlcat(optptr, "no", optlength - (optptr - options));
1755
1756           case IPP_TAG_NOVALUE :
1757               strlcat(optptr, attr->name,
1758                       optlength - (optptr - options));
1759               break;
1760
1761           case IPP_TAG_RANGE :
1762               if (attr->values[i].range.lower == attr->values[i].range.upper)
1763                 snprintf(optptr, optlength - (optptr - options) - 1,
1764                          "%d", attr->values[i].range.lower);
1765               else
1766                 snprintf(optptr, optlength - (optptr - options) - 1,
1767                          "%d-%d", attr->values[i].range.lower,
1768                          attr->values[i].range.upper);
1769               break;
1770
1771           case IPP_TAG_RESOLUTION :
1772               snprintf(optptr, optlength - (optptr - options) - 1,
1773                        "%dx%d%s", attr->values[i].resolution.xres,
1774                        attr->values[i].resolution.yres,
1775                        attr->values[i].resolution.units == IPP_RES_PER_INCH ?
1776                            "dpi" : "dpc");
1777               break;
1778
1779           case IPP_TAG_STRING :
1780           case IPP_TAG_TEXT :
1781           case IPP_TAG_NAME :
1782           case IPP_TAG_KEYWORD :
1783           case IPP_TAG_CHARSET :
1784           case IPP_TAG_LANGUAGE :
1785               for (valptr = attr->values[i].string.text; *valptr;)
1786               {
1787                 if (strchr(" \t\n\\\'\"", *valptr))
1788                   *optptr++ = '\\';
1789                 *optptr++ = *valptr++;
1790               }
1791
1792               *optptr = '\0';
1793               break;
1794
1795           default :
1796               break; /* anti-compiler-warning-code */
1797         }
1798       }
1799
1800       optptr += strlen(optptr);
1801     }
1802   }
1803
1804  /*
1805   * Build the command-line arguments for the filters.  Each filter
1806   * has 6 or 7 arguments:
1807   *
1808   *     argv[0] = printer
1809   *     argv[1] = job ID
1810   *     argv[2] = username
1811   *     argv[3] = title
1812   *     argv[4] = # copies
1813   *     argv[5] = options
1814   *     argv[6] = filename (optional; normally stdin)
1815   *
1816   * This allows legacy printer drivers that use the old System V
1817   * printing interface to be used by CUPS.
1818   */
1819
1820   sprintf(jobid, "%d", job->id);
1821   snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
1822            job->id, job->current_file + 1);
1823
1824   argv[0] = printer->name;
1825   argv[1] = jobid;
1826   argv[2] = job->username;
1827   argv[3] = title;
1828   argv[4] = copies;
1829   argv[5] = options;
1830   argv[6] = filename;
1831   argv[7] = NULL;
1832
1833   cupsdLogMessage(CUPSD_LOG_DEBUG,
1834                   "cupsdStartJob: argv = \"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"",
1835                   argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]);
1836
1837  /*
1838   * Create environment variable strings for the filters...
1839   */
1840
1841   attr = ippFindAttribute(job->attrs, "attributes-natural-language",
1842                           IPP_TAG_LANGUAGE);
1843
1844   switch (strlen(attr->values[0].string.text))
1845   {
1846     default :
1847        /*
1848         * This is an unknown or badly formatted language code; use
1849         * the POSIX locale...
1850         */
1851
1852         strcpy(lang, "LANG=C");
1853         break;
1854
1855     case 2 :
1856        /*
1857         * Just the language code (ll)...
1858         */
1859
1860         snprintf(lang, sizeof(lang), "LANG=%s",
1861                  attr->values[0].string.text);
1862         break;
1863
1864     case 5 :
1865        /*
1866         * Language and country code (ll-cc)...
1867         */
1868
1869         snprintf(lang, sizeof(lang), "LANG=%c%c_%c%c",
1870                  attr->values[0].string.text[0],
1871                  attr->values[0].string.text[1],
1872                  toupper(attr->values[0].string.text[3] & 255),
1873                  toupper(attr->values[0].string.text[4] & 255));
1874         break;
1875   }
1876
1877   attr = ippFindAttribute(job->attrs, "document-format",
1878                           IPP_TAG_MIMETYPE);
1879   if (attr != NULL &&
1880       (optptr = strstr(attr->values[0].string.text, "charset=")) != NULL)
1881     snprintf(charset, sizeof(charset), "CHARSET=%s", optptr + 8);
1882   else
1883   {
1884     attr = ippFindAttribute(job->attrs, "attributes-charset",
1885                             IPP_TAG_CHARSET);
1886     snprintf(charset, sizeof(charset), "CHARSET=%s",
1887              attr->values[0].string.text);
1888   }
1889
1890   snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s/%s",
1891            job->filetypes[job->current_file]->super,
1892            job->filetypes[job->current_file]->type);
1893   snprintf(device_uri, sizeof(device_uri), "DEVICE_URI=%s", printer->device_uri);
1894   cupsdSanitizeURI(printer->device_uri, sani_uri, sizeof(sani_uri));
1895   snprintf(ppd, sizeof(ppd), "PPD=%s/ppd/%s.ppd", ServerRoot, printer->name);
1896   snprintf(printer_name, sizeof(printer_name), "PRINTER=%s", printer->name);
1897   snprintf(rip_max_cache, sizeof(rip_max_cache), "RIP_MAX_CACHE=%s", RIPCache);
1898
1899   envc = cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0])));
1900
1901   envp[envc ++] = charset;
1902   envp[envc ++] = lang;
1903   envp[envc ++] = ppd;
1904   envp[envc ++] = rip_max_cache;
1905   envp[envc ++] = content_type;
1906   envp[envc ++] = device_uri;
1907   envp[envc ++] = printer_name;
1908
1909   if (Classification && !banner_page)
1910   {
1911     if ((attr = ippFindAttribute(job->attrs, "job-sheets",
1912                                  IPP_TAG_NAME)) == NULL)
1913       snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
1914                Classification);
1915     else if (attr->num_values > 1 &&
1916              strcmp(attr->values[1].string.text, "none") != 0)
1917       snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
1918                attr->values[1].string.text);
1919     else
1920       snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
1921                attr->values[0].string.text);
1922
1923     envp[envc ++] = classification;
1924   }
1925
1926   if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))
1927   {
1928     snprintf(class_name, sizeof(class_name), "CLASS=%s", job->dest);
1929     envp[envc ++] = class_name;
1930   }
1931
1932   envp[envc] = NULL;
1933
1934   for (i = 0; i < envc; i ++)
1935     if (strncmp(envp[i], "DEVICE_URI=", 11))
1936       cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdStartJob: envp[%d]=\"%s\"",
1937                       i, envp[i]);
1938     else
1939       cupsdLogMessage(CUPSD_LOG_DEBUG,
1940                       "cupsdStartJob: envp[%d]=\"DEVICE_URI=%s\"", i, sani_uri);
1941
1942   job->current_file ++;
1943
1944  /*
1945   * Now create processes for all of the filters...
1946   */
1947
1948   if (cupsdOpenPipe(statusfds))
1949   {
1950     cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create job status pipes - %s.",
1951                     strerror(errno));
1952     snprintf(printer->state_message, sizeof(printer->state_message),
1953              "Unable to create status pipes - %s.", strerror(errno));
1954
1955     cupsdAddPrinterHistory(printer);
1956
1957     if (filters != NULL)
1958       free(filters);
1959
1960     cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
1961                   "Job canceled because the server could not create the job status pipes.");
1962
1963     cupsdCancelJob(job, 0);
1964     return;
1965   }
1966
1967   cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdStartJob: statusfds = [ %d %d ]",
1968                   statusfds[0], statusfds[1]);
1969
1970 #ifdef FD_CLOEXEC
1971   fcntl(statusfds[0], F_SETFD, FD_CLOEXEC);
1972   fcntl(statusfds[1], F_SETFD, FD_CLOEXEC);
1973 #endif /* FD_CLOEXEC */
1974
1975   job->status_buffer = cupsdStatBufNew(statusfds[0], "[Job %d]",
1976                                            job->id);
1977   job->status        = 0;
1978   memset(job->filters, 0, sizeof(job->filters));
1979
1980   filterfds[1][0] = open("/dev/null", O_RDONLY);
1981   filterfds[1][1] = -1;
1982
1983   if (filterfds[1][0] < 0)
1984   {
1985     cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open \"/dev/null\" - %s.",
1986                     strerror(errno));
1987     snprintf(printer->state_message, sizeof(printer->state_message),
1988              "Unable to open \"/dev/null\" - %s.", strerror(errno));
1989
1990     cupsdAddPrinterHistory(printer);
1991
1992     if (filters != NULL)
1993       free(filters);
1994
1995     cupsdClosePipe(statusfds);
1996     cupsdCancelJob(job, 0);
1997     return;
1998   }
1999
2000   fcntl(filterfds[1][0], F_SETFD, fcntl(filterfds[1][0], F_GETFD) | FD_CLOEXEC);
2001
2002   cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdStartJob: filterfds[%d] = [ %d %d ]",
2003                   1, filterfds[1][0], filterfds[1][1]);
2004
2005   for (i = 0, slot = 0; i < num_filters; i ++)
2006   {
2007     if (filters[i].filter[0] != '/')
2008       snprintf(command, sizeof(command), "%s/filter/%s", ServerBin,
2009                filters[i].filter);
2010     else
2011       strlcpy(command, filters[i].filter, sizeof(command));
2012
2013     if (i < (num_filters - 1))
2014     {
2015       if (cupsdOpenPipe(filterfds[slot]))
2016       {
2017         cupsdLogMessage(CUPSD_LOG_ERROR,
2018                         "Unable to create job filter pipes - %s.",
2019                         strerror(errno));
2020         snprintf(printer->state_message, sizeof(printer->state_message),
2021                 "Unable to create filter pipes - %s.", strerror(errno));
2022         cupsdAddPrinterHistory(printer);
2023
2024         if (filters != NULL)
2025           free(filters);
2026
2027         cupsdClosePipe(statusfds);
2028         cupsdClosePipe(filterfds[!slot]);
2029
2030         cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
2031                       "Job canceled because the server could not create the filter pipes.");
2032
2033         cupsdCancelJob(job, 0);
2034         return;
2035       }
2036     }
2037     else
2038     {
2039       if (job->current_file == 1)
2040       {
2041         if (strncmp(printer->device_uri, "file:", 5) != 0)
2042         {
2043           if (cupsdOpenPipe(job->print_pipes))
2044           {
2045             cupsdLogMessage(CUPSD_LOG_ERROR,
2046                             "Unable to create job backend pipes - %s.",
2047                             strerror(errno));
2048             snprintf(printer->state_message, sizeof(printer->state_message),
2049                     "Unable to create backend pipes - %s.", strerror(errno));
2050             cupsdAddPrinterHistory(printer);
2051
2052             if (filters != NULL)
2053               free(filters);
2054
2055             cupsdClosePipe(statusfds);
2056             cupsdClosePipe(filterfds[!slot]);
2057
2058             cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
2059                           "Job canceled because the server could not create the backend pipes.");
2060
2061             cupsdCancelJob(job, 0);
2062             return;
2063           }
2064         }
2065         else
2066         {
2067           job->print_pipes[0] = -1;
2068           if (!strncmp(printer->device_uri, "file:/dev/", 10) &&
2069               strcmp(printer->device_uri, "file:/dev/null"))
2070             job->print_pipes[1] = open(printer->device_uri + 5,
2071                                        O_WRONLY | O_EXCL);
2072           else if (!strncmp(printer->device_uri, "file:///dev/", 12) &&
2073                    strcmp(printer->device_uri, "file:///dev/null"))
2074             job->print_pipes[1] = open(printer->device_uri + 7,
2075                                        O_WRONLY | O_EXCL);
2076           else
2077             job->print_pipes[1] = open(printer->device_uri + 5,
2078                                        O_WRONLY | O_CREAT | O_TRUNC, 0600);
2079
2080           if (job->print_pipes[1] < 0)
2081           {
2082             cupsdLogMessage(CUPSD_LOG_ERROR,
2083                             "Unable to open output file \"%s\" - %s.",
2084                             printer->device_uri, strerror(errno));
2085             snprintf(printer->state_message, sizeof(printer->state_message),
2086                      "Unable to open output file \"%s\" - %s.",
2087                      printer->device_uri, strerror(errno));
2088
2089             cupsdAddPrinterHistory(printer);
2090
2091             if (filters != NULL)
2092               free(filters);
2093
2094             cupsdClosePipe(statusfds);
2095             cupsdClosePipe(filterfds[!slot]);
2096
2097             cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
2098                           "Job canceled because the server could not open the output file.");
2099
2100             cupsdCancelJob(job, 0);
2101             return;
2102           }
2103
2104           fcntl(job->print_pipes[1], F_SETFD,
2105                 fcntl(job->print_pipes[1], F_GETFD) | FD_CLOEXEC);
2106         }
2107
2108         cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdStartJob: print_pipes = [ %d %d ]",
2109                    job->print_pipes[0], job->print_pipes[1]);
2110       }
2111
2112       filterfds[slot][0] = job->print_pipes[0];
2113       filterfds[slot][1] = job->print_pipes[1];
2114     }
2115
2116     cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdStartJob: filter = \"%s\"", command);
2117     cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdStartJob: filterfds[%d] = [ %d %d ]",
2118                     slot, filterfds[slot][0], filterfds[slot][1]);
2119
2120     pid = cupsdStartProcess(command, argv, envp, filterfds[!slot][0],
2121                             filterfds[slot][1], statusfds[1],
2122                             job->back_pipes[0], 0, job->filters + i);
2123
2124     cupsdLogMessage(CUPSD_LOG_DEBUG2,
2125                     "cupsdStartJob: Closing filter pipes for slot %d [ %d %d ]...",
2126                     !slot, filterfds[!slot][0], filterfds[!slot][1]);
2127
2128     cupsdClosePipe(filterfds[!slot]);
2129
2130     if (pid == 0)
2131     {
2132       cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to start filter \"%s\" - %s.",
2133                       filters[i].filter, strerror(errno));
2134       snprintf(printer->state_message, sizeof(printer->state_message),
2135                "Unable to start filter \"%s\" - %s.",
2136                filters[i].filter, strerror(errno));
2137
2138       cupsdAddPrinterHistory(printer);
2139
2140       if (filters != NULL)
2141         free(filters);
2142
2143       cupsdAddPrinterHistory(printer);
2144
2145       cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
2146                     "Job canceled because the server could not execute a filter.");
2147
2148       cupsdCancelJob(job, 0);
2149       return;
2150     }
2151
2152     cupsdLogMessage(CUPSD_LOG_INFO, "Started filter %s (PID %d) for job %d.",
2153                     command, pid, job->id);
2154
2155     argv[6] = NULL;
2156     slot    = !slot;
2157   }
2158
2159   if (filters != NULL)
2160     free(filters);
2161
2162  /*
2163   * Finally, pipe the final output into a backend process if needed...
2164   */
2165
2166   if (strncmp(printer->device_uri, "file:", 5) != 0)
2167   {
2168     if (job->current_file == 1)
2169     {
2170       sscanf(printer->device_uri, "%254[^:]", method);
2171       snprintf(command, sizeof(command), "%s/backend/%s", ServerBin, method);
2172
2173       argv[0] = sani_uri;
2174
2175       filterfds[slot][0] = -1;
2176       filterfds[slot][1] = open("/dev/null", O_WRONLY);
2177
2178       if (filterfds[slot][1] < 0)
2179       {
2180         cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open \"/dev/null\" - %s.",
2181                         strerror(errno));
2182         snprintf(printer->state_message, sizeof(printer->state_message),
2183                  "Unable to open \"/dev/null\" - %s.", strerror(errno));
2184
2185         cupsdAddPrinterHistory(printer);
2186
2187         if (filters != NULL)
2188           free(filters);
2189
2190         cupsdClosePipe(statusfds);
2191
2192         cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
2193                       "Job canceled because the server could not open a file.");
2194
2195         cupsdCancelJob(job, 0);
2196         return;
2197       }
2198
2199       fcntl(filterfds[slot][1], F_SETFD,
2200             fcntl(filterfds[slot][1], F_GETFD) | FD_CLOEXEC);
2201
2202       cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdStartJob: backend = \"%s\"",
2203                       command);
2204       cupsdLogMessage(CUPSD_LOG_DEBUG,
2205                       "cupsdStartJob: filterfds[%d] = [ %d %d ]",
2206                       slot, filterfds[slot][0], filterfds[slot][1]);
2207
2208       pid = cupsdStartProcess(command, argv, envp, filterfds[!slot][0],
2209                               filterfds[slot][1], statusfds[1],
2210                               job->back_pipes[1], 1,
2211                               &(job->backend));
2212
2213       if (pid == 0)
2214       {
2215         cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to start backend \"%s\" - %s.",
2216                         method, strerror(errno));
2217         snprintf(printer->state_message, sizeof(printer->state_message),
2218                  "Unable to start backend \"%s\" - %s.", method, strerror(errno));
2219
2220         cupsdLogMessage(CUPSD_LOG_DEBUG2,
2221                         "cupsdStartJob: Closing print pipes [ %d %d ]...",
2222                         job->print_pipes[0], job->print_pipes[1]);
2223
2224         cupsdClosePipe(job->print_pipes);
2225
2226         cupsdLogMessage(CUPSD_LOG_DEBUG2,
2227                         "cupsdStartJob: Closing back pipes [ %d %d ]...",
2228                         job->back_pipes[0], job->back_pipes[1]);
2229
2230         cupsdClosePipe(job->back_pipes);
2231
2232         cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
2233                       "Job canceled because the server could not execute the backend.");
2234
2235         cupsdCancelJob(job, 0);
2236         return;
2237       }
2238       else
2239       {
2240         cupsdLogMessage(CUPSD_LOG_INFO,
2241                         "Started backend %s (PID %d) for job %d.",
2242                         command, pid, job->id);
2243       }
2244     }
2245
2246     if (job->current_file == job->num_files)
2247     {
2248       cupsdLogMessage(CUPSD_LOG_DEBUG2,
2249                       "cupsdStartJob: Closing print pipes [ %d %d ]...",
2250                       job->print_pipes[0], job->print_pipes[1]);
2251
2252       cupsdClosePipe(job->print_pipes);
2253
2254       cupsdLogMessage(CUPSD_LOG_DEBUG2,
2255                       "cupsdStartJob: Closing back pipes [ %d %d ]...",
2256                       job->back_pipes[0], job->back_pipes[1]);
2257
2258       cupsdClosePipe(job->back_pipes);
2259     }
2260   }
2261   else
2262   {
2263     filterfds[slot][0] = -1;
2264     filterfds[slot][1] = -1;
2265
2266     if (job->current_file == job->num_files)
2267     {
2268       cupsdLogMessage(CUPSD_LOG_DEBUG2,
2269                       "cupsdStartJob: Closing print pipes [ %d %d ]...",
2270                       job->print_pipes[0], job->print_pipes[1]);
2271
2272       cupsdClosePipe(job->print_pipes);
2273     }
2274   }
2275
2276   cupsdLogMessage(CUPSD_LOG_DEBUG2,
2277                   "cupsdStartJob: Closing filter pipes for slot %d [ %d %d ]...",
2278                   slot, filterfds[slot][0], filterfds[slot][1]);
2279
2280   cupsdClosePipe(filterfds[slot]);
2281
2282   cupsdLogMessage(CUPSD_LOG_DEBUG2,
2283                   "cupsdStartJob: Closing status output pipe %d...",
2284                   statusfds[1]);
2285
2286   close(statusfds[1]);
2287
2288   cupsdLogMessage(CUPSD_LOG_DEBUG2,
2289                   "cupsdStartJob: Adding fd %d to InputSet...",
2290                   job->status_buffer->fd);
2291
2292   FD_SET(job->status_buffer->fd, InputSet);
2293 }
2294
2295
2296 /*
2297  * 'cupsdStopAllJobs()' - Stop all print jobs.
2298  */
2299
2300 void
2301 cupsdStopAllJobs(void)
2302 {
2303   cupsd_job_t   *job;                   /* Current job */
2304
2305
2306   DEBUG_puts("cupsdStopAllJobs()");
2307
2308   for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
2309        job;
2310        job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
2311     if (job->state->values[0].integer == IPP_JOB_PROCESSING)
2312     {
2313       cupsdStopJob(job, 1);
2314       job->state->values[0].integer = IPP_JOB_PENDING;
2315     }
2316 }
2317
2318
2319 /*
2320  * 'cupsdStopJob()' - Stop a print job.
2321  */
2322
2323 void
2324 cupsdStopJob(cupsd_job_t *job,          /* I - Job */
2325              int         force)         /* I - 1 = Force all filters to stop */
2326 {
2327   int   i;                              /* Looping var */
2328
2329
2330   cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdStopJob: id = %d, force = %d",
2331                   job->id, force);
2332
2333   if (job->state->values[0].integer != IPP_JOB_PROCESSING)
2334     return;
2335
2336   FilterLevel -= job->cost;
2337
2338   if (job->status < 0 &&
2339       !(job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT)) &&
2340       !(job->printer->type & CUPS_PRINTER_FAX) &&
2341       !strcmp(job->printer->error_policy, "stop-printer"))
2342     cupsdSetPrinterState(job->printer, IPP_PRINTER_STOPPED, 1);
2343   else if (job->printer->state != IPP_PRINTER_STOPPED)
2344     cupsdSetPrinterState(job->printer, IPP_PRINTER_IDLE, 0);
2345
2346   cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdStopJob: printer state is %d",
2347                   job->printer->state);
2348
2349   job->state->values[0].integer = IPP_JOB_STOPPED;
2350   job->printer->job = NULL;
2351   job->printer      = NULL;
2352
2353   job->current_file --;
2354
2355   for (i = 0; job->filters[i]; i ++)
2356     if (job->filters[i] > 0)
2357     {
2358       cupsdEndProcess(job->filters[i], force);
2359       job->filters[i] = 0;
2360     }
2361
2362   if (job->backend > 0)
2363   {
2364     cupsdEndProcess(job->backend, force);
2365     job->backend = 0;
2366   }
2367
2368   cupsdLogMessage(CUPSD_LOG_DEBUG2,
2369                   "cupsdStopJob: Closing print pipes [ %d %d ]...",
2370                   job->print_pipes[0], job->print_pipes[1]);
2371
2372   cupsdClosePipe(job->print_pipes);
2373
2374   cupsdLogMessage(CUPSD_LOG_DEBUG2,
2375                   "cupsdStopJob: Closing back pipes [ %d %d ]...",
2376                   job->back_pipes[0], job->back_pipes[1]);
2377
2378   cupsdClosePipe(job->back_pipes);
2379
2380   if (job->status_buffer)
2381   {
2382    /*
2383     * Close the pipe and clear the input bit.
2384     */
2385
2386     cupsdLogMessage(CUPSD_LOG_DEBUG2,
2387                     "cupsdStopJob: Removing fd %d from InputSet...",
2388                     job->status_buffer->fd);
2389
2390     FD_CLR(job->status_buffer->fd, InputSet);
2391
2392     cupsdLogMessage(CUPSD_LOG_DEBUG2,
2393                     "cupsdStopJob: Closing status input pipe %d...",
2394                     job->status_buffer->fd);
2395
2396     cupsdStatBufDelete(job->status_buffer);
2397
2398     job->status_buffer = NULL;
2399   }
2400 }
2401
2402
2403 /*
2404  * 'cupsdUpdateJob()' - Read a status update from a job's filters.
2405  */
2406
2407 void
2408 cupsdUpdateJob(cupsd_job_t *job)        /* I - Job to check */
2409 {
2410   int           i;                      /* Looping var */
2411   int           copies;                 /* Number of copies printed */
2412   char          message[1024],          /* Message text */
2413                 *ptr;                   /* Pointer update... */
2414   int           loglevel;               /* Log level for message */
2415
2416
2417   while ((ptr = cupsdStatBufUpdate(job->status_buffer, &loglevel,
2418                                    message, sizeof(message))) != NULL)
2419   {
2420    /*
2421     * Process page and printer state messages as needed...
2422     */
2423
2424     if (loglevel == CUPSD_LOG_PAGE)
2425     {
2426      /*
2427       * Page message; send the message to the page_log file and update the
2428       * job sheet count...
2429       */
2430
2431       if (job->sheets != NULL)
2432       {
2433         if (!strncasecmp(message, "total ", 6))
2434         {
2435          /*
2436           * Got a total count of pages from a backend or filter...
2437           */
2438
2439           copies = atoi(message + 6);
2440           copies -= job->sheets->values[0].integer; /* Just track the delta */
2441         }
2442         else if (!sscanf(message, "%*d%d", &copies))
2443           copies = 1;
2444           
2445         job->sheets->values[0].integer += copies;
2446
2447         if (job->printer->page_limit)
2448           cupsdUpdateQuota(job->printer, job->username, copies, 0);
2449       }
2450
2451       cupsdLogPage(job, message);
2452
2453       cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job,
2454                     "Printed %d page(s).", job->sheets->values[0].integer);
2455     }
2456     else if (loglevel == CUPSD_LOG_STATE)
2457       cupsdSetPrinterReasons(job->printer, message);
2458     else if (loglevel == CUPSD_LOG_ATTR)
2459     {
2460      /*
2461       * Set attribute(s)...
2462       */
2463
2464       /**** TODO ****/
2465     }
2466
2467     if (!strchr(job->status_buffer->buffer, '\n'))
2468       break;
2469   }
2470
2471   if (ptr == NULL)
2472   {
2473    /*
2474     * See if all of the filters and the backend have returned their
2475     * exit statuses.
2476     */
2477
2478     for (i = 0; job->filters[i] < 0; i ++);
2479
2480     if (job->filters[i])
2481       return;
2482
2483     if (job->current_file >= job->num_files && job->backend > 0)
2484       return;
2485
2486    /*
2487     * Handle the end of job stuff...
2488     */
2489
2490     cupsdFinishJob(job);
2491   }
2492 }
2493
2494
2495 /*
2496  * 'compare_active_jobs()' - Compare the job IDs and priorities of two jobs.
2497  */
2498
2499 static int                              /* O - Difference */
2500 compare_active_jobs(void *first,        /* I - First job */
2501                     void *second,       /* I - Second job */
2502                     void *data)         /* I - App data (not used) */
2503 {
2504   int   diff;                           /* Difference */
2505
2506
2507   if ((diff = ((cupsd_job_t *)first)->priority - ((cupsd_job_t *)second)->priority) != 0)
2508     return (diff);
2509   else
2510     return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
2511 }
2512
2513
2514 /*
2515  * 'compare_jobs()' - Compare the job IDs of two jobs.
2516  */
2517
2518 static int                              /* O - Difference */
2519 compare_jobs(void *first,               /* I - First job */
2520              void *second,              /* I - Second job */
2521              void *data)                /* I - App data (not used) */
2522 {
2523   return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
2524 }
2525
2526
2527 /*
2528  * 'ipp_length()' - Compute the size of the buffer needed to hold 
2529  *                  the textual IPP attributes.
2530  */
2531
2532 int                                     /* O - Size of buffer to hold IPP attributes */
2533 ipp_length(ipp_t *ipp)                  /* I - IPP request */
2534 {
2535   int                   bytes;          /* Number of bytes */
2536   int                   i;              /* Looping var */
2537   ipp_attribute_t       *attr;          /* Current attribute */
2538
2539
2540  /*
2541   * Loop through all attributes...
2542   */
2543
2544   bytes = 0;
2545
2546   for (attr = ipp->attrs; attr != NULL; attr = attr->next)
2547   {
2548    /*
2549     * Skip attributes that won't be sent to filters...
2550     */
2551
2552     if (attr->value_tag == IPP_TAG_MIMETYPE ||
2553         attr->value_tag == IPP_TAG_NAMELANG ||
2554         attr->value_tag == IPP_TAG_TEXTLANG ||
2555         attr->value_tag == IPP_TAG_URI ||
2556         attr->value_tag == IPP_TAG_URISCHEME)
2557       continue;
2558
2559     if (strncmp(attr->name, "time-", 5) == 0)
2560       continue;
2561
2562    /*
2563     * Add space for a leading space and commas between each value.
2564     * For the first attribute, the leading space isn't used, so the
2565     * extra byte can be used as the nul terminator...
2566     */
2567
2568     bytes ++;                           /* " " separator */
2569     bytes += attr->num_values;          /* "," separators */
2570
2571    /*
2572     * Boolean attributes appear as "foo,nofoo,foo,nofoo", while
2573     * other attributes appear as "foo=value1,value2,...,valueN".
2574     */
2575
2576     if (attr->value_tag != IPP_TAG_BOOLEAN)
2577       bytes += strlen(attr->name);
2578     else
2579       bytes += attr->num_values * strlen(attr->name);
2580
2581    /*
2582     * Now add the size required for each value in the attribute...
2583     */
2584
2585     switch (attr->value_tag)
2586     {
2587       case IPP_TAG_INTEGER :
2588       case IPP_TAG_ENUM :
2589          /*
2590           * Minimum value of a signed integer is -2147483647, or 11 digits.
2591           */
2592
2593           bytes += attr->num_values * 11;
2594           break;
2595
2596       case IPP_TAG_BOOLEAN :
2597          /*
2598           * Add two bytes for each false ("no") value...
2599           */
2600
2601           for (i = 0; i < attr->num_values; i ++)
2602             if (!attr->values[i].boolean)
2603               bytes += 2;
2604           break;
2605
2606       case IPP_TAG_RANGE :
2607          /*
2608           * A range is two signed integers separated by a hyphen, or
2609           * 23 characters max.
2610           */
2611
2612           bytes += attr->num_values * 23;
2613           break;
2614
2615       case IPP_TAG_RESOLUTION :
2616          /*
2617           * A resolution is two signed integers separated by an "x" and
2618           * suffixed by the units, or 26 characters max.
2619           */
2620
2621           bytes += attr->num_values * 26;
2622           break;
2623
2624       case IPP_TAG_STRING :
2625       case IPP_TAG_TEXT :
2626       case IPP_TAG_NAME :
2627       case IPP_TAG_KEYWORD :
2628       case IPP_TAG_CHARSET :
2629       case IPP_TAG_LANGUAGE :
2630          /*
2631           * Strings can contain characters that need quoting.  We need
2632           * at least 2 * len + 2 characters to cover the quotes and
2633           * any backslashes in the string.
2634           */
2635
2636           for (i = 0; i < attr->num_values; i ++)
2637             bytes += 2 * strlen(attr->values[i].string.text) + 2;
2638           break;
2639
2640        default :
2641           break; /* anti-compiler-warning-code */
2642     }
2643   }
2644
2645   return (bytes);
2646 }
2647
2648
2649 /*
2650  * 'set_time()' - Set one of the "time-at-xyz" attributes...
2651  */
2652
2653 static void
2654 set_time(cupsd_job_t *job,              /* I - Job to update */
2655          const char  *name)             /* I - Name of attribute */
2656 {
2657   ipp_attribute_t       *attr;          /* Time attribute */
2658
2659
2660   if ((attr = ippFindAttribute(job->attrs, name, IPP_TAG_ZERO)) != NULL)
2661   {
2662     attr->value_tag         = IPP_TAG_INTEGER;
2663     attr->values[0].integer = time(NULL);
2664   }
2665 }
2666
2667
2668 /*
2669  * 'set_hold_until()' - Set the hold time and update job-hold-until attribute...
2670  */
2671
2672 static void 
2673 set_hold_until(cupsd_job_t *job,        /* I - Job to update */
2674                time_t      holdtime)    /* I - Hold until time */
2675 {
2676   ipp_attribute_t       *attr;          /* job-hold-until attribute */
2677   struct tm             *holddate;      /* Hold date */
2678   char                  holdstr[64];    /* Hold time */
2679
2680
2681  /*
2682   * Set the hold_until value and hold the job...
2683   */
2684
2685   cupsdLogMessage(CUPSD_LOG_DEBUG, "set_hold_until: hold_until = %d", (int)holdtime);
2686
2687   job->state->values[0].integer = IPP_JOB_HELD;
2688   job->hold_until               = holdtime;
2689
2690  /*
2691   * Update the job-hold-until attribute with a string representing GMT
2692   * time (HH:MM:SS)...
2693   */
2694
2695   holddate = gmtime(&holdtime);
2696   snprintf(holdstr, sizeof(holdstr), "%d:%d:%d", holddate->tm_hour, 
2697            holddate->tm_min, holddate->tm_sec);
2698
2699   if ((attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_KEYWORD)) == NULL)
2700     attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
2701
2702  /*
2703   * Either add the attribute or update the value of the existing one
2704   */
2705
2706   if (attr == NULL)
2707     attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD,
2708                         "job-hold-until", NULL, holdstr);
2709   else
2710     cupsdSetString(&attr->values[0].string.text, holdstr);
2711
2712   cupsdSaveJob(job);
2713 }
2714
2715
2716 /*
2717  * End of "$Id: job.c 4906 2006-01-10 20:53:28Z mike $".
2718  */