]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - scheduler/job.c
Merge changes from CUPS 1.6svn-r9968.
[thirdparty/cups.git] / scheduler / job.c
index 19f48a50cd8a2137637c458e400cd6f70a2d3531..59e06dd55843bcf5feefcde1df6c37385fa9576d 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * "$Id: job.c 7005 2007-10-01 23:45:48Z mike $"
+ * "$Id: job.c 7902 2008-09-03 14:20:17Z mike $"
  *
- *   Job management routines for the Common UNIX Printing System (CUPS).
+ *   Job management routines for the CUPS scheduler.
  *
- *   Copyright 2007-2008 by Apple Inc.
+ *   Copyright 2007-2011 by Apple Inc.
  *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
  *
  *   These coded instructions, statements, and computer programs are the
  *
  * Contents:
  *
- *   cupsdAddJob()              - Add a new job to the job queue...
- *   cupsdCancelJob()           - Cancel the specified print job.
+ *   cupsdAddJob()              - Add a new job to the job queue.
  *   cupsdCancelJobs()          - Cancel all jobs for the given
- *                                destination/user...
- *   cupsdCheckJobs()           - Check the pending jobs and start any if
- *                                the destination is available.
+ *                                destination/user.
+ *   cupsdCheckJobs()           - Check the pending jobs and start any if the
+ *                                destination is available.
  *   cupsdCleanJobs()           - Clean out old jobs.
- *   cupsdFinishJob()           - Finish a job.
+ *   cupsdContinueJob()         - Continue printing with the next file in a job.
+ *   cupsdDeleteJob()           - Free all memory used by a job.
  *   cupsdFreeAllJobs()         - Free all jobs from memory.
  *   cupsdFindJob()             - Find the specified job.
- *   cupsdGetPrinterJobCount()  - Get the number of pending, processing,
- *   cupsdGetUserJobCount()     - Get the number of pending, processing,
- *   cupsdHoldJob()             - Hold the specified job.
+ *   cupsdGetPrinterJobCount()  - Get the number of pending, processing, or held
+ *                                jobs in a printer or class.
+ *   cupsdGetUserJobCount()     - Get the number of pending, processing, or held
+ *                                jobs for a user.
  *   cupsdLoadAllJobs()         - Load all jobs from disk.
- *   cupsdLoadJob()             - Load a single job...
+ *   cupsdLoadJob()             - Load a single job.
  *   cupsdMoveJob()             - Move the specified job to a different
  *                                destination.
  *   cupsdReleaseJob()          - Release the specified job.
  *   cupsdRestartJob()          - Restart the specified job.
  *   cupsdSaveAllJobs()         - Save a summary of all jobs to disk.
  *   cupsdSaveJob()             - Save a job to disk.
- *   cupsdSetJobHoldUntil()     - Set the hold time for a job...
- *   cupsdSetJobPriority()      - Set the priority of a job, moving it
- *                                up/down in the list as needed.
+ *   cupsdSetJobHoldUntil()     - Set the hold time for a job.
+ *   cupsdSetJobPriority()      - Set the priority of a job, moving it up/down
+ *                                in the list as needed.
+ *   cupsdSetJobState()         - Set the state of the specified print job.
  *   cupsdStopAllJobs()         - Stop all print jobs.
- *   cupsdStopJob()             - Stop a print job.
  *   cupsdUnloadCompletedJobs() - Flush completed job history from memory.
  *   compare_active_jobs()      - Compare the job IDs and priorities of two
  *                                jobs.
  *   compare_jobs()             - Compare the job IDs of two jobs.
- *   free_job()                 - Free all memory used by a job.
- *   ipp_length()               - Compute the size of the buffer needed to
- *                                hold the textual IPP attributes.
+ *   dump_job_history()         - Dump any debug messages for a job.
+ *   free_job_history()         - Free any log history.
+ *   finalize_job()             - Cleanup after job filter processes and support
+ *                                data.
+ *   get_options()              - Get a string containing the job options.
+ *   ipp_length()               - Compute the size of the buffer needed to hold
+ *                                the textual IPP attributes.
  *   load_job_cache()           - Load jobs from the job.cache file.
- *   load_next_job_id()         - Load the NextJobId value from the
- *                                job.cache file.
+ *   load_next_job_id()         - Load the NextJobId value from the job.cache
+ *                                file.
  *   load_request_root()        - Load jobs from the RequestRoot directory.
- *   set_time()                 - Set one of the "time-at-xyz" attributes...
- *   set_hold_until()           - Set the hold time and update job-hold-until
- *                                attribute...
+ *   set_time()                 - Set one of the "time-at-xyz" attributes.
  *   start_job()                - Start a print job.
+ *   stop_job()                 - Stop a print job.
  *   unload_job()               - Unload a job from memory.
- *   update_job()               - Read a status update from a jobs filters.
+ *   update_job()               - Read a status update from a job's filters.
  *   update_job_attrs()         - Update the job-printer-* attributes.
  */
 
 #include <grp.h>
 #include <cups/backend.h>
 #include <cups/dir.h>
+#ifdef __APPLE__
+#  include <IOKit/pwr_mgt/IOPMLib.h>
+#  ifdef HAVE_IOKIT_PWR_MGT_IOPMLIBPRIVATE_H
+#    include <IOKit/pwr_mgt/IOPMLibPrivate.h>
+#  endif /* HAVE_IOKIT_PWR_MGT_IOPMLIBPRIVATE_H */
+#endif /* __APPLE__ */
+
+
+/*
+ * Design Notes for Job Management
+ * -------------------------------
+ *
+ * STATE CHANGES
+ *
+ *     pending       Do nothing/check jobs
+ *     pending-held  Send SIGTERM to filters and backend
+ *     processing    Do nothing/start job
+ *     stopped       Send SIGKILL to filters and backend
+ *     canceled      Send SIGTERM to filters and backend
+ *     aborted       Finalize
+ *     completed     Finalize
+ *
+ *     Finalize clears the printer <-> job association, deletes the status
+ *     buffer, closes all of the pipes, etc. and doesn't get run until all of
+ *     the print processes are finished.
+ *
+ * UNLOADING OF JOBS (cupsdUnloadCompletedJobs)
+ *
+ *     We unload the job attributes when they are not needed to reduce overall
+ *     memory consumption.  We don't unload jobs where job->state_value <
+ *     IPP_JOB_STOPPED, job->printer != NULL, or job->access_time is recent.
+ *
+ * STARTING OF JOBS (start_job)
+ *
+ *     When a job is started, a status buffer, several pipes, a security
+ *     profile, and a backend process are created for the life of that job.
+ *     These are shared for every file in a job.  For remote print jobs, the
+ *     IPP backend is provided with every file in the job and no filters are
+ *     run.
+ *
+ *     The job->printer member tracks which printer is printing a job, which
+ *     can be different than the destination in job->dest for classes.  The
+ *     printer object also has a job pointer to track which job is being
+ *     printed.
+ *
+ * PRINTING OF JOB FILES (cupsdContinueJob)
+ *
+ *     Each file in a job is filtered by 0 or more programs.  After getting the
+ *     list of filters needed and the total cost, the job is either passed or
+ *     put back to the processing state until the current FilterLevel comes down
+ *     enough to allow printing.
+ *
+ *     If we can print, we build a string for the print options and run each of
+ *     the filters, piping the output from one into the next.
+ *
+ * JOB STATUS UPDATES (update_job)
+ *
+ *     The update_job function gets called whenever there are pending messages
+ *     on the status pipe.  These generally are updates to the marker-*,
+ *     printer-state-message, or printer-state-reasons attributes.  On EOF,
+ *     finalize_job is called to clean up.
+ *
+ * FINALIZING JOBS (finalize_job)
+ *
+ *     When all filters and the backend are done, we set the job state to
+ *     completed (no errors), aborted (filter errors or abort-job policy),
+ *     pending-held (auth required or retry-job policy), or pending
+ *     (retry-current-job or stop-printer policies) as appropriate.
+ *
+ *     Then we close the pipes and free the status buffers and profiles.
+ *
+ * JOB FILE COMPLETION (process_children in main.c)
+ *
+ *     For multiple-file jobs, process_children (in main.c) sees that all
+ *     filters have exited and calls in to print the next file if there are
+ *     more files in the job, otherwise it waits for the backend to exit and
+ *     update_job to do the cleanup.
+ */
 
 
 /*
@@ -89,21 +171,26 @@ static mime_filter_t       gziptoany_filter =
 
 static int     compare_active_jobs(void *first, void *second, void *data);
 static int     compare_jobs(void *first, void *second, void *data);
-static void    free_job(cupsd_job_t *job);
-static int     ipp_length(ipp_t *ipp);
+static void    dump_job_history(cupsd_job_t *job);
+static void    finalize_job(cupsd_job_t *job, int set_job_state);
+static void    free_job_history(cupsd_job_t *job);
+static char    *get_options(cupsd_job_t *job, int banner_page, char *copies,
+                            size_t copies_size, char *title,
+                            size_t title_size);
+static size_t  ipp_length(ipp_t *ipp);
 static void    load_job_cache(const char *filename);
 static void    load_next_job_id(const char *filename);
 static void    load_request_root(void);
 static void    set_time(cupsd_job_t *job, const char *name);
-static void    set_hold_until(cupsd_job_t *job, time_t holdtime);
 static void    start_job(cupsd_job_t *job, cupsd_printer_t *printer);
+static void    stop_job(cupsd_job_t *job, cupsd_jobaction_t action);
 static void    unload_job(cupsd_job_t *job);
 static void    update_job(cupsd_job_t *job);
-static void    update_job_attrs(cupsd_job_t *job);
+static void    update_job_attrs(cupsd_job_t *job, int do_message);
 
 
 /*
- * 'cupsdAddJob()' - Add a new job to the job queue...
+ * 'cupsdAddJob()' - Add a new job to the job queue.
  */
 
 cupsd_job_t *                          /* O - New job record */
@@ -141,171 +228,7 @@ cupsdAddJob(int        priority,  /* I - Job priority */
 
 
 /*
- * 'cupsdCancelJob()' - Cancel the specified print job.
- */
-
-void
-cupsdCancelJob(cupsd_job_t  *job,      /* I - Job to cancel */
-               int          purge,     /* I - Purge jobs? */
-              ipp_jstate_t newstate)   /* I - New job state */
-{
-  int          i;                      /* Looping var */
-  char         filename[1024];         /* Job filename */
-  cupsd_printer_t *printer;            /* Printer used by job */
-
-
-  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCancelJob: id = %d", job->id);
-
- /*
-  * Stop any processes that are working on the current job...
-  */
-
-  printer = job->printer;
-
-  if (job->state_value == IPP_JOB_PROCESSING)
-    cupsdStopJob(job, 0);
-
-  cupsdLoadJob(job);
-
-  if (job->attrs)
-    job->state->values[0].integer = newstate;
-
-  job->state_value = newstate;
-
-  set_time(job, "time-at-completed");
-
- /*
-  * Send any pending notifications and then expire them...
-  */
-
-  switch (newstate)
-  {
-    default :
-        break;
-
-    case IPP_JOB_CANCELED :
-       cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, printer, job,
-                      purge ? "Job purged." : "Job canceled.");
-        break;
-
-    case IPP_JOB_ABORTED :
-       cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, printer, job,
-                      "Job aborted; please consult the error_log file "
-                     "for details.");
-        break;
-
-    case IPP_JOB_COMPLETED :
-       /*
-       * Clear the printer's printer-state-message and move on...
-       */
-
-       printer->state_message[0] = '\0';
-
-       cupsdSetPrinterState(printer, IPP_PRINTER_IDLE, 0);
-
-       cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, printer, job,
-                      "Job completed.");
-        break;
-  }
-
-  cupsdExpireSubscriptions(NULL, job);
-
- /*
-  * Remove the job from the active list...
-  */
-
-  cupsArrayRemove(ActiveJobs, job);
-
- /*
-  * Remove any authentication data...
-  */
-
-  snprintf(filename, sizeof(filename), "%s/a%05d", RequestRoot, job->id);
-  if (cupsdRemoveFile(filename) && errno != ENOENT)
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                    "Unable to remove authentication cache: %s",
-                   strerror(errno));
-
-  cupsdClearString(&job->auth_username);
-  cupsdClearString(&job->auth_domain);
-  cupsdClearString(&job->auth_password);
-
-#ifdef HAVE_GSSAPI
- /*
-  * Destroy the credential cache and clear the KRB5CCNAME env var string.
-  */
-
-  if (job->ccache)
-  {
-    krb5_cc_destroy(KerberosContext, job->ccache);
-    job->ccache = NULL;
-  }
-
-  cupsdClearString(&job->ccname);
-#endif /* HAVE_GSSAPI */
-
- /*
-  * Remove the print file for good if we aren't preserving jobs or
-  * files...
-  */
-
-  job->current_file = 0;
-
-  if (!JobHistory || !JobFiles || purge)
-  {
-    for (i = 1; i <= job->num_files; i ++)
-    {
-      snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
-              job->id, i);
-      unlink(filename);
-    }
-
-    if (job->num_files > 0)
-    {
-      free(job->filetypes);
-      free(job->compressions);
-
-      job->num_files    = 0;
-      job->filetypes    = NULL;
-      job->compressions = NULL;
-    }
-  }
-
-  if (JobHistory && !purge)
-  {
-   /*
-    * Save job state info...
-    */
-
-    cupsdSaveJob(job);
-  }
-  else
-  {
-   /*
-    * Remove the job info file...
-    */
-
-    snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot,
-            job->id);
-    unlink(filename);
-
-   /*
-    * Remove the job from the "all jobs" list...
-    */
-
-    cupsArrayRemove(Jobs, job);
-
-   /*
-    * Free all memory used...
-    */
-
-    free_job(job);
-  }
-}
-
-
-/*
- * 'cupsdCancelJobs()' - Cancel all jobs for the given destination/user...
+ * 'cupsdCancelJobs()' - Cancel all jobs for the given destination/user.
  */
 
 void
@@ -320,20 +243,22 @@ cupsdCancelJobs(const char *dest, /* I - Destination to cancel */
        job;
        job = (cupsd_job_t *)cupsArrayNext(Jobs))
   {
-    if (!job->dest || !job->username)
-      cupsdLoadJob(job);
-
-    if (!job->dest || !job->username)
+    if ((!job->dest || !job->username) && !cupsdLoadJob(job))
       continue;
 
-    if ((dest == NULL || !strcmp(job->dest, dest)) &&
-        (username == NULL || !strcmp(job->username, username)))
+    if ((!dest || !strcmp(job->dest, dest)) &&
+        (!username || !strcmp(job->username, username)))
     {
      /*
       * Cancel all jobs matching this destination/user...
       */
 
-      cupsdCancelJob(job, purge, IPP_JOB_CANCELED);
+      if (purge)
+       cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_PURGE,
+                        "Job purged by user.");
+      else if (job->state_value < IPP_JOB_CANCELED)
+       cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_DEFAULT,
+                        "Job canceled by user.");
     }
   }
 
@@ -352,46 +277,87 @@ cupsdCheckJobs(void)
   cupsd_job_t          *job;           /* Current job in queue */
   cupsd_printer_t      *printer,       /* Printer destination */
                        *pclass;        /* Printer class destination */
+  ipp_attribute_t      *attr;          /* Job attribute */
+  time_t               curtime;        /* Current time */
 
 
-  DEBUG_puts("cupsdCheckJobs()");
-
   cupsdLogMessage(CUPSD_LOG_DEBUG2,
                   "cupsdCheckJobs: %d active jobs, sleeping=%d, reload=%d",
                   cupsArrayCount(ActiveJobs), Sleeping, NeedReload);
 
+  curtime = time(NULL);
+
   for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
        job;
        job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
   {
    /*
-    * Start held jobs if they are ready...
+    * Kill jobs if they are unresponsive...
     */
 
-    cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                    "cupsdCheckJobs: Job %d: state_value=%d, loaded=%s",
-                    job->id, job->state_value, job->attrs ? "yes" : "no");
+    if (job->kill_time && job->kill_time <= curtime)
+    {
+      cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Stopping unresponsive job!",
+                     job->id);
+
+      stop_job(job, CUPSD_JOB_FORCE);
+      continue;
+    }
+
+   /*
+    * Start held jobs if they are ready...
+    */
 
     if (job->state_value == IPP_JOB_HELD &&
         job->hold_until &&
-       job->hold_until < time(NULL))
+       job->hold_until < curtime)
     {
       if (job->pending_timeout)
       {
-       /* Add trailing banner as needed */
+       /*
+        * This job is pending; check that we don't have an active Send-Document
+       * operation in progress on any of the client connections, then timeout
+       * the job so we can start printing...
+       */
+
+        cupsd_client_t *con;           /* Current client connection */
+
+
+       for (con = (cupsd_client_t *)cupsArrayFirst(Clients);
+            con;
+            con = (cupsd_client_t *)cupsArrayNext(Clients))
+         if (con->request &&
+             con->request->request.op.operation_id == IPP_SEND_DOCUMENT)
+           break;
+
+        if (con)
+         continue;
+
         if (cupsdTimeoutJob(job))
          continue;
       }
 
-      job->state->values[0].integer = IPP_JOB_PENDING;
-      job->state_value              = IPP_JOB_PENDING;
+      cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT,
+                       "Job submission timed out.");
     }
 
+   /*
+    * Continue jobs that are waiting on the FilterLimit...
+    */
+
+    if (job->pending_cost > 0 &&
+       ((FilterLevel + job->pending_cost) < FilterLimit || FilterLevel == 0))
+      cupsdContinueJob(job);
+
    /*
     * Start pending jobs if the destination is available...
     */
 
-    if (job->state_value == IPP_JOB_PENDING && !NeedReload && !Sleeping)
+    if (job->state_value == IPP_JOB_PENDING && !NeedReload &&
+#ifndef kIOPMAssertionTypeDenySystemSleep
+        !Sleeping &&
+#endif /* !kIOPMAssertionTypeDenySystemSleep */
+        !DoingShutdown && !job->printer)
     {
       printer = cupsdFindDest(job->dest);
       pclass  = NULL;
@@ -420,17 +386,11 @@ cupsdCheckJobs(void)
        * cancel the job...
        */
 
-        cupsdLogMessage(CUPSD_LOG_WARN,
-                       "[Job %d] Printer/class %s has gone away; canceling job!",
-                       job->id, job->dest);
-
-       cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
-                      "Job canceled because the destination printer/class has "
-                     "gone away.");
-
-        cupsdCancelJob(job, 1, IPP_JOB_ABORTED);
+        cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_PURGE,
+                        "Job aborted because the destination printer/class "
+                        "has gone away.");
       }
-      else if (printer)
+      else if (printer && !printer->holding_new_jobs)
       {
        /*
         * See if the printer is available or remote and not printing a job;
@@ -444,30 +404,22 @@ cupsdCheckJobs(void)
          * so that we know which printer actually printed the job...
          */
 
-          ipp_attribute_t      *attr;  /* job-actual-printer-uri attribute */
-
-
           if ((attr = ippFindAttribute(job->attrs, "job-actual-printer-uri",
                                       IPP_TAG_URI)) != NULL)
             cupsdSetString(&attr->values[0].string.text, printer->uri);
          else
            ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI,
                         "job-actual-printer-uri", NULL, printer->uri);
+
+          job->dirty = 1;
+          cupsdMarkDirty(CUPSD_DIRTY_JOBS);
        }
 
         if ((!(printer->type & CUPS_PRINTER_DISCOVERED) && /* Printer is local */
-            printer->state == IPP_PRINTER_IDLE) ||     /* and idle */
+            printer->state == IPP_PRINTER_IDLE) ||     /* and idle, OR */
            ((printer->type & CUPS_PRINTER_DISCOVERED) && /* Printer is remote */
             !printer->job))                            /* and not printing */
         {
-        /* 
-         * Clear any message and reasons for the queue...
-         */
-
-          printer->state_message[0] = '\0';
-
-         cupsdSetPrinterReasons(printer, "none");
-
         /*
          * Start the job...
          */
@@ -490,2386 +442,3022 @@ cupsdCleanJobs(void)
   cupsd_job_t  *job;                   /* Current job */
 
 
-  if (MaxJobs <= 0)
+  if (MaxJobs <= 0 && JobHistory)
     return;
 
   for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
-       job && cupsArrayCount(Jobs) >= MaxJobs;
+       job && (cupsArrayCount(Jobs) >= MaxJobs || !JobHistory);
        job = (cupsd_job_t *)cupsArrayNext(Jobs))
-    if (job->state_value >= IPP_JOB_CANCELED)
-      cupsdCancelJob(job, 1, IPP_JOB_CANCELED);
+    if (job->state_value >= IPP_JOB_CANCELED && !job->printer)
+      cupsdDeleteJob(job, CUPSD_JOB_PURGE);
 }
 
 
 /*
- * 'cupsdFinishJob()' - Finish a job.
+ * 'cupsdContinueJob()' - Continue printing with the next file in a job.
  */
 
 void
-cupsdFinishJob(cupsd_job_t *job)       /* I - Job */
+cupsdContinueJob(cupsd_job_t *job)     /* I - Job */
 {
-  cupsd_printer_t      *printer;       /* Current printer */
-  ipp_attribute_t      *attr;          /* job-hold-until attribute */
-
+  int                  i;              /* Looping var */
+  int                  slot;           /* Pipe slot */
+  cups_array_t         *filters = NULL,/* Filters for job */
+                       *prefilters;    /* Filters with prefilters */
+  mime_filter_t                *filter,        /* Current filter */
+                       *prefilter,     /* Prefilter */
+                       port_monitor;   /* Port monitor filter */
+  char                 scheme[255];    /* Device URI scheme */
+  ipp_attribute_t      *attr;          /* Current attribute */
+  const char           *ptr,           /* Pointer into value */
+                       *abort_message; /* Abort message */
+  ipp_jstate_t         abort_state = IPP_JOB_STOPPED;
+                                       /* New job state on abort */
+  struct stat          backinfo;       /* Backend file information */
+  int                  backroot;       /* Run backend as root? */
+  int                  pid;            /* Process ID of new filter process */
+  int                  banner_page;    /* 1 if banner page, 0 otherwise */
+  int                  filterfds[2][2] = { { -1, -1 }, { -1, -1 } };
+                                       /* Pipes used between filters */
+  int                  envc;           /* Number of environment variables */
+  struct stat          fileinfo;       /* Job file information */
+  char                 **argv = NULL,  /* Filter command-line arguments */
+                       filename[1024], /* Job filename */
+                       command[1024],  /* Full path to command */
+                       jobid[255],     /* Job ID string */
+                       title[IPP_MAX_NAME],
+                                       /* Job title string */
+                       copies[255],    /* # copies string */
+                       *options,       /* Options string */
+                       *envp[MAX_ENV + 21],
+                                       /* Environment variables */
+                       charset[255],   /* CHARSET env variable */
+                       class_name[255],/* CLASS env variable */
+                       classification[1024],
+                                       /* CLASSIFICATION env variable */
+                       content_type[1024],
+                                       /* CONTENT_TYPE env variable */
+                       device_uri[1024],
+                                       /* DEVICE_URI env variable */
+                       final_content_type[1024],
+                                       /* FINAL_CONTENT_TYPE env variable */
+                       lang[255],      /* LANG env variable */
+#ifdef __APPLE__
+                       apple_language[255],
+                                       /* APPLE_LANGUAGE env variable */
+#endif /* __APPLE__ */
+                       auth_info_required[255],
+                                       /* AUTH_INFO_REQUIRED env variable */
+                       ppd[1024],      /* PPD env variable */
+                       printer_info[255],
+                                       /* PRINTER_INFO env variable */
+                       printer_location[255],
+                                       /* PRINTER_LOCATION env variable */
+                       printer_name[255],
+                                       /* PRINTER env variable */
+                       *printer_state_reasons = NULL,
+                                       /* PRINTER_STATE_REASONS env var */
+                       rip_max_cache[255];
+                                       /* RIP_MAX_CACHE env variable */
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] File %d is complete.",
-                  job->id, job->current_file - 1);
 
   cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                  "[Job %d] cupsdFinishJob: job->status is %d",
-                  job->id, job->status);
-
-  if (job->status_buffer &&
-      (job->status < 0 || job->current_file >= job->num_files))
-  {
-   /*
-    * Close the pipe and clear the input bit.
-    */
+                  "cupsdContinueJob(job=%p(%d)): current_file=%d, num_files=%d",
+                 job, job->id, job->current_file, job->num_files);
 
-    cupsdRemoveSelect(job->status_buffer->fd);
+ /*
+  * Figure out what filters are required to convert from
+  * the source to the destination type...
+  */
 
-    cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                   "[Job %d] cupsdFinishJob: Closing status pipes [ %d %d ]...",
-                   job->id, job->status_pipes[0], job->status_pipes[1]);
+  FilterLevel -= job->cost;
 
-    cupsdClosePipe(job->status_pipes);
-    cupsdStatBufDelete(job->status_buffer);
+  job->cost         = 0;
+  job->pending_cost = 0;
 
-    job->status_buffer = NULL;
-  }
+  memset(job->filters, 0, sizeof(job->filters));
 
-  printer = job->printer;
 
-  update_job_attrs(job);
+  if (job->printer->raw)
+  {
+   /*
+    * Remote jobs and raw queues go directly to the printer without
+    * filtering...
+    */
 
-  if (job->status < 0)
+    cupsdLogJob(job, CUPSD_LOG_DEBUG, "Sending job to queue tagged as raw...");
+  }
+  else
   {
    /*
-    * Backend had errors; stop it...
+    * Local jobs get filtered...
     */
 
-    int exit_code;                     /* Exit code from backend */
+    snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
+             job->id, job->current_file + 1);
+    if (stat(filename, &fileinfo))
+      fileinfo.st_size = 0;
 
+    filters = mimeFilter2(MimeDatabase, job->filetypes[job->current_file],
+                          fileinfo.st_size, job->printer->filetype,
+                          &(job->cost));
 
-   /*
-    * Convert the status to an exit code.  Due to the way the W* macros are
-    * implemented on MacOS X (bug?), we have to store the exit status in a
-    * variable first and then convert...
-    */
+    if (!filters)
+    {
+      cupsdLogJob(job, CUPSD_LOG_ERROR,
+                 "Unable to convert file %d to printable format!",
+                 job->current_file);
 
-    exit_code = -job->status;
-    if (WIFEXITED(exit_code))
-      exit_code = WEXITSTATUS(exit_code);
-    else
-      exit_code = job->status;
+      abort_message = "Aborting job because it cannot be printed.";
+      abort_state   = IPP_JOB_ABORTED;
 
-    cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Backend returned status %d (%s)",
-                    job->id, exit_code,
-                   exit_code == CUPS_BACKEND_FAILED ? "failed" :
-                       exit_code == CUPS_BACKEND_AUTH_REQUIRED ?
-                           "authentication required" :
-                       exit_code == CUPS_BACKEND_HOLD ? "hold job" :
-                       exit_code == CUPS_BACKEND_STOP ? "stop printer" :
-                       exit_code == CUPS_BACKEND_CANCEL ? "cancel job" :
-                       exit_code < 0 ? "crashed" : "unknown");
+      goto abort_job;
+    }
 
    /*
-    * Do what needs to be done...
+    * Remove NULL ("-") filters...
     */
 
-    switch (exit_code)
+    for (filter = (mime_filter_t *)cupsArrayFirst(filters);
+         filter;
+        filter = (mime_filter_t *)cupsArrayNext(filters))
+      if (!strcmp(filter->filter, "-"))
+        cupsArrayRemove(filters, filter);
+
+    if (cupsArrayCount(filters) == 0)
     {
-      default :
-      case CUPS_BACKEND_FAILED :
-         /*
-         * Backend failure, use the error-policy to determine how to
-         * act...
-         */
-
-         cupsdStopJob(job, 0);
+      cupsArrayDelete(filters);
+      filters = NULL;
+    }
 
-          if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))
-         {
-          /*
-           * Mark the job as pending again - we'll retry on another
-           * printer...
-           */
+   /*
+    * If this printer has any pre-filters, insert the required pre-filter
+    * in the filters array...
+    */
 
-           job->state->values[0].integer = IPP_JOB_PENDING;
-           job->state_value              = IPP_JOB_PENDING;
-          }
+    if (job->printer->prefiltertype && filters)
+    {
+      prefilters = cupsArrayNew(NULL, NULL);
 
-         cupsdSaveJob(job);
+      for (filter = (mime_filter_t *)cupsArrayFirst(filters);
+          filter;
+          filter = (mime_filter_t *)cupsArrayNext(filters))
+      {
+       if ((prefilter = mimeFilterLookup(MimeDatabase, filter->src,
+                                         job->printer->prefiltertype)))
+       {
+         cupsArrayAdd(prefilters, prefilter);
+         job->cost += prefilter->cost;
+       }
 
-        /*
-         * If the job was queued to a class, try requeuing it...  For
-         * faxes and retry-job queues, hold the current job for 5 minutes.
-         */
+       cupsArrayAdd(prefilters, filter);
+      }
 
-         if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))
-           cupsdCheckJobs();
-         else if ((printer->type & CUPS_PRINTER_FAX) ||
-                  !strcmp(printer->error_policy, "retry-job"))
-         {
-          /*
-           * See how many times we've tried to send the job; if more than
-           * the limit, cancel the job.
-           */
+      cupsArrayDelete(filters);
+      filters = prefilters;
+    }
+  }
 
-           job->tries ++;
+ /*
+  * Set a minimum cost of 100 for all jobs so that FilterLimit
+  * works with raw queues and other low-cost paths.
+  */
 
-           if (job->tries >= JobRetryLimit)
-           {
-            /*
-             * Too many tries...
-             */
+  if (job->cost < 100)
+    job->cost = 100;
 
-             cupsdLogMessage(CUPSD_LOG_ERROR,
-                             "[Job %d] Canceling job since it could not be "
-                             "sent after %d tries.",
-                             job->id, JobRetryLimit);
+ /*
+  * See if the filter cost is too high...
+  */
 
-             cupsdCancelJob(job, 0, IPP_JOB_ABORTED);
-           }
-           else
-           {
-            /*
-             * Try again in N seconds...
-             */
+  if ((FilterLevel + job->cost) > FilterLimit && FilterLevel > 0 &&
+      FilterLimit > 0)
+  {
+   /*
+    * Don't print this job quite yet...
+    */
 
-             set_hold_until(job, time(NULL) + JobRetryInterval);
+    cupsArrayDelete(filters);
 
-             cupsdAddEvent(CUPSD_EVENT_JOB_STATE, job->printer, job,
-                           "Job held due to fax errors; please consult "
-                           "the error_log file for details.");
-             cupsdSetPrinterState(printer, IPP_PRINTER_IDLE, 0);
-           }
-         }
-         else if (!strcmp(printer->error_policy, "abort-job"))
-           cupsdCancelJob(job, 0, IPP_JOB_ABORTED);
-          else
-          {
-           cupsdSetPrinterState(printer, IPP_PRINTER_STOPPED, 1);
+    cupsdLogJob(job, CUPSD_LOG_INFO,
+               "Holding because filter limit has been reached.");
+    cupsdLogJob(job, CUPSD_LOG_DEBUG2,
+               "cupsdContinueJob: file=%d, cost=%d, level=%d, limit=%d",
+               job->current_file, job->cost, FilterLevel,
+               FilterLimit);
 
-           cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job,
-                         "Job stopped due to backend errors; please consult "
-                         "the error_log file for details.");
-         }
-          break;
+    job->pending_cost = job->cost;
+    job->cost         = 0;
+    return;
+  }
 
-      case CUPS_BACKEND_CANCEL :
-         /*
-         * Cancel the job...
-         */
+  FilterLevel += job->cost;
 
-         cupsdCancelJob(job, 0, IPP_JOB_CANCELED);
-          break;
+ /*
+  * Add decompression/raw filter as needed...
+  */
 
-      case CUPS_BACKEND_HOLD :
-         /*
-         * Hold the job...
-         */
+  if ((!job->printer->raw && job->compressions[job->current_file]) ||
+      (!filters && !job->printer->remote &&
+       (job->num_files > 1 || !strncmp(job->printer->device_uri, "file:", 5))))
+  {
+   /*
+    * Add gziptoany filter to the front of the list...
+    */
 
-         cupsdStopJob(job, 0);
+    if (!filters)
+      filters = cupsArrayNew(NULL, NULL);
 
-         cupsdSetJobHoldUntil(job, "indefinite");
+    if (!cupsArrayInsert(filters, &gziptoany_filter))
+    {
+      cupsdLogJob(job, CUPSD_LOG_DEBUG,
+                 "Unable to add decompression filter - %s", strerror(errno));
 
-         job->state->values[0].integer = IPP_JOB_HELD;
-         job->state_value              = IPP_JOB_HELD;
+      cupsArrayDelete(filters);
 
-         cupsdSaveJob(job);
+      abort_message = "Stopping job because the scheduler ran out of memory.";
 
-         cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job,
-                       "Job held due to backend errors; please consult "
-                       "the error_log file for details.");
-          break;
+      goto abort_job;
+    }
+  }
 
-      case CUPS_BACKEND_STOP :
-         /*
-         * Stop the printer...
-         */
+ /*
+  * Add port monitor, if any...
+  */
 
-         cupsdStopJob(job, 0);
+  if (job->printer->port_monitor)
+  {
+   /*
+    * Add port monitor to the end of the list...
+    */
 
-         job->state->values[0].integer = IPP_JOB_PENDING;
-         job->state_value              = IPP_JOB_PENDING;
+    if (!filters)
+      filters = cupsArrayNew(NULL, NULL);
 
-         cupsdSaveJob(job);
-         cupsdSetPrinterState(printer, IPP_PRINTER_STOPPED, 1);
+    port_monitor.src  = NULL;
+    port_monitor.dst  = NULL;
+    port_monitor.cost = 0;
 
-         cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job,
-                       "Job stopped due to backend errors; please consult "
-                       "the error_log file for details.");
-          break;
+    snprintf(port_monitor.filter, sizeof(port_monitor.filter),
+             "%s/monitor/%s", ServerBin, job->printer->port_monitor);
 
-      case CUPS_BACKEND_AUTH_REQUIRED :
-         cupsdStopJob(job, 0);
+    if (!cupsArrayAdd(filters, &port_monitor))
+    {
+      cupsdLogJob(job, CUPSD_LOG_DEBUG,
+                 "Unable to add port monitor - %s", strerror(errno));
 
-         cupsdSetJobHoldUntil(job, "auth-info-required");
+      abort_message = "Stopping job because the scheduler ran out of memory.";
 
-         if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
-                                      IPP_TAG_KEYWORD)) == NULL)
-           attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
+      goto abort_job;
+    }
+  }
 
-         if (attr)
-         {
-           attr->value_tag = IPP_TAG_KEYWORD;
-           cupsdSetString(&(attr->values[0].string.text),
-                          "auth-info-required");
-         }
+ /*
+  * Make sure we don't go over the "MAX_FILTERS" limit...
+  */
 
-         job->state->values[0].integer = IPP_JOB_HELD;
-         job->state_value              = IPP_JOB_HELD;
+  if (cupsArrayCount(filters) > MAX_FILTERS)
+  {
+    cupsdLogJob(job, CUPSD_LOG_DEBUG,
+               "Too many filters (%d > %d), unable to print!",
+               cupsArrayCount(filters), MAX_FILTERS);
 
-         cupsdSaveJob(job);
+    abort_message = "Aborting job because it needs too many filters to print.";
+    abort_state   = IPP_JOB_ABORTED;
 
-         cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job,
-                       "Authentication is required for job %d.", job->id);
-          break;
-    }
+    goto abort_job;
+  }
 
  /*
-    * Try printing another job...
-    */
+ /*
+  * Determine if we are printing a banner page or not...
+  */
 
-    cupsdCheckJobs();
-  }
-  else if (job->status > 0)
+  if (job->job_sheets == NULL)
   {
-   /*
-    * Filter had errors; stop job...
-    */
-
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                    "[Job %d] Job stopped due to filter errors.", job->id);
-    cupsdStopJob(job, 1);
-    cupsdSaveJob(job);
-    cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job,
-                  "Job stopped due to filter errors; please consult the "
-                 "error_log file for details.");
-    cupsdCheckJobs();
+    cupsdLogJob(job, CUPSD_LOG_DEBUG, "No job-sheets attribute.");
+    if ((job->job_sheets =
+         ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_ZERO)) != NULL)
+      cupsdLogJob(job, CUPSD_LOG_DEBUG,
+                 "... but someone added one without setting job_sheets!");
   }
+  else if (job->job_sheets->num_values == 1)
+    cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-sheets=%s",
+               job->job_sheets->values[0].string.text);
   else
-  {
-   /*
-    * Job printed successfully; cancel it...
-    */
+    cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-sheets=%s,%s",
+                job->job_sheets->values[0].string.text,
+                job->job_sheets->values[1].string.text);
 
-    if (job->current_file < job->num_files)
-    {
-     /*
-      * Start the next file in the job...
-      */
+  if (job->printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT))
+    banner_page = 0;
+  else if (job->job_sheets == NULL)
+    banner_page = 0;
+  else if (_cups_strcasecmp(job->job_sheets->values[0].string.text, "none") != 0 &&
+          job->current_file == 0)
+    banner_page = 1;
+  else if (job->job_sheets->num_values > 1 &&
+          _cups_strcasecmp(job->job_sheets->values[1].string.text, "none") != 0 &&
+          job->current_file == (job->num_files - 1))
+    banner_page = 1;
+  else
+    banner_page = 0;
 
-      FilterLevel -= job->cost;
-      start_job(job, printer);
-    }
-    else
-    {
-     /*
-      * Close out this job...
-      */
+  if ((options = get_options(job, banner_page, copies, sizeof(copies), title,
+                             sizeof(title))) == NULL)
+  {
+    abort_message = "Stopping job because the scheduler ran out of memory.";
 
-      cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Completed successfully.",
-                     job->id);
-      cupsdCancelJob(job, 0, IPP_JOB_COMPLETED);
-      cupsdCheckJobs();
-    }
+    goto abort_job;
   }
-}
 
+ /*
+  * Build the command-line arguments for the filters.  Each filter
+  * has 6 or 7 arguments:
+  *
+  *     argv[0] = printer
+  *     argv[1] = job ID
+  *     argv[2] = username
+  *     argv[3] = title
+  *     argv[4] = # copies
+  *     argv[5] = options
+  *     argv[6] = filename (optional; normally stdin)
+  *
+  * This allows legacy printer drivers that use the old System V
+  * printing interface to be used by CUPS.
+  *
+  * For remote jobs, we send all of the files in the argument list.
+  */
 
-/*
- * 'cupsdFreeAllJobs()' - Free all jobs from memory.
- */
+  if (job->printer->remote)
+    argv = calloc(7 + job->num_files, sizeof(char *));
+  else
+    argv = calloc(8, sizeof(char *));
 
-void
-cupsdFreeAllJobs(void)
-{
-  cupsd_job_t  *job;                   /* Current job */
+  if (!argv)
+  {
+    cupsdLogMessage(CUPSD_LOG_DEBUG, "Unable to allocate argument array - %s",
+                    strerror(errno));
 
+    abort_message = "Stopping job because the scheduler ran out of memory.";
 
-  if (!Jobs)
-    return;
+    goto abort_job;
+  }
 
-  cupsdHoldSignals();
+  sprintf(jobid, "%d", job->id);
 
-  cupsdStopAllJobs(1);
-  cupsdSaveAllJobs();
+  argv[0] = job->printer->name;
+  argv[1] = jobid;
+  argv[2] = job->username;
+  argv[3] = title;
+  argv[4] = copies;
+  argv[5] = options;
 
-  for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
-       job;
-       job = (cupsd_job_t *)cupsArrayNext(Jobs))
+  if (job->printer->remote && job->num_files > 1)
   {
-    cupsArrayRemove(Jobs, job);
-    cupsArrayRemove(ActiveJobs, job);
-
-    free_job(job);
+    for (i = 0; i < job->num_files; i ++)
+    {
+      snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
+               job->id, i + 1);
+      argv[6 + i] = strdup(filename);
+    }
+  }
+  else
+  {
+    snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
+             job->id, job->current_file + 1);
+    argv[6] = filename;
   }
 
-  cupsdReleaseSignals();
-}
-
+  for (i = 0; argv[i]; i ++)
+    cupsdLogJob(job, CUPSD_LOG_DEBUG, "argv[%d]=\"%s\"", i, argv[i]);
 
-/*
* 'cupsdFindJob()' - Find the specified job.
- */
+ /*
 * Create environment variable strings for the filters...
 */
 
-cupsd_job_t *                          /* O - Job data */
-cupsdFindJob(int id)                   /* I - Job ID */
-{
-  cupsd_job_t  key;                    /* Search key */
+  attr = ippFindAttribute(job->attrs, "attributes-natural-language",
+                          IPP_TAG_LANGUAGE);
 
+#ifdef __APPLE__
+  strcpy(apple_language, "APPLE_LANGUAGE=");
+  _cupsAppleLanguage(attr->values[0].string.text,
+                    apple_language + 15, sizeof(apple_language) - 15);
+#endif /* __APPLE__ */
 
-  key.id = id;
-
-  return ((cupsd_job_t *)cupsArrayFind(Jobs, &key));
-}
-
-
-/*
- * 'cupsdGetPrinterJobCount()' - Get the number of pending, processing,
- *                               or held jobs in a printer or class.
- */
-
-int                                    /* O - Job count */
-cupsdGetPrinterJobCount(
-    const char *dest)                  /* I - Printer or class name */
-{
-  int          count;                  /* Job count */
-  cupsd_job_t  *job;                   /* Current job */
+  switch (strlen(attr->values[0].string.text))
+  {
+    default :
+       /*
+        * This is an unknown or badly formatted language code; use
+       * the POSIX locale...
+       */
 
+       strcpy(lang, "LANG=C");
+       break;
 
-  for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0;
-       job;
-       job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
-    if (job->dest && !strcasecmp(job->dest, dest))
-      count ++;
+    case 2 :
+       /*
+        * Just the language code (ll)...
+       */
 
-  return (count);
-}
+        snprintf(lang, sizeof(lang), "LANG=%s.UTF-8",
+                attr->values[0].string.text);
+        break;
 
+    case 5 :
+       /*
+        * Language and country code (ll-cc)...
+       */
 
-/*
- * 'cupsdGetUserJobCount()' - Get the number of pending, processing,
- *                            or held jobs for a user.
- */
+        snprintf(lang, sizeof(lang), "LANG=%c%c_%c%c.UTF-8",
+                attr->values[0].string.text[0],
+                attr->values[0].string.text[1],
+                toupper(attr->values[0].string.text[3] & 255),
+                toupper(attr->values[0].string.text[4] & 255));
+        break;
+  }
 
-int                                    /* O - Job count */
-cupsdGetUserJobCount(
-    const char *username)              /* I - Username */
-{
-  int          count;                  /* Job count */
-  cupsd_job_t  *job;                   /* Current job */
+  if ((attr = ippFindAttribute(job->attrs, "document-format",
+                               IPP_TAG_MIMETYPE)) != NULL &&
+      (ptr = strstr(attr->values[0].string.text, "charset=")) != NULL)
+    snprintf(charset, sizeof(charset), "CHARSET=%s", ptr + 8);
+  else
+    strlcpy(charset, "CHARSET=utf-8", sizeof(charset));
 
+  snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s/%s",
+           job->filetypes[job->current_file]->super,
+           job->filetypes[job->current_file]->type);
+  snprintf(device_uri, sizeof(device_uri), "DEVICE_URI=%s",
+           job->printer->device_uri);
+  snprintf(ppd, sizeof(ppd), "PPD=%s/ppd/%s.ppd", ServerRoot,
+          job->printer->name);
+  snprintf(printer_info, sizeof(printer_name), "PRINTER_INFO=%s",
+           job->printer->info ? job->printer->info : "");
+  snprintf(printer_location, sizeof(printer_name), "PRINTER_LOCATION=%s",
+           job->printer->location ? job->printer->location : "");
+  snprintf(printer_name, sizeof(printer_name), "PRINTER=%s", job->printer->name);
+  if (job->printer->num_reasons > 0)
+  {
+    char       *psrptr;                /* Pointer into PRINTER_STATE_REASONS */
+    size_t     psrlen;                 /* Size of PRINTER_STATE_REASONS */
 
-  for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0;
-       job;
-       job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
-    if (!strcasecmp(job->username, username))
-      count ++;
+    for (psrlen = 22, i = 0; i < job->printer->num_reasons; i ++)
+      psrlen += strlen(job->printer->reasons[i]) + 1;
 
-  return (count);
-}
+    if ((printer_state_reasons = malloc(psrlen)) != NULL)
+    {
+     /*
+      * All of these strcpy's are safe because we allocated the psr string...
+      */
 
+      strcpy(printer_state_reasons, "PRINTER_STATE_REASONS=");
+      for (psrptr = printer_state_reasons + 22, i = 0;
+           i < job->printer->num_reasons;
+          i ++)
+      {
+        if (i)
+         *psrptr++ = ',';
+       strcpy(psrptr, job->printer->reasons[i]);
+       psrptr += strlen(psrptr);
+      }
+    }
+  }
+  snprintf(rip_max_cache, sizeof(rip_max_cache), "RIP_MAX_CACHE=%s", RIPCache);
 
-/*
- * 'cupsdHoldJob()' - Hold the specified job.
- */
+  if (job->printer->num_auth_info_required == 1)
+    snprintf(auth_info_required, sizeof(auth_info_required),
+             "AUTH_INFO_REQUIRED=%s",
+            job->printer->auth_info_required[0]);
+  else if (job->printer->num_auth_info_required == 2)
+    snprintf(auth_info_required, sizeof(auth_info_required),
+             "AUTH_INFO_REQUIRED=%s,%s",
+            job->printer->auth_info_required[0],
+            job->printer->auth_info_required[1]);
+  else if (job->printer->num_auth_info_required == 3)
+    snprintf(auth_info_required, sizeof(auth_info_required),
+             "AUTH_INFO_REQUIRED=%s,%s,%s",
+            job->printer->auth_info_required[0],
+            job->printer->auth_info_required[1],
+            job->printer->auth_info_required[2]);
+  else if (job->printer->num_auth_info_required == 4)
+    snprintf(auth_info_required, sizeof(auth_info_required),
+             "AUTH_INFO_REQUIRED=%s,%s,%s,%s",
+            job->printer->auth_info_required[0],
+            job->printer->auth_info_required[1],
+            job->printer->auth_info_required[2],
+            job->printer->auth_info_required[3]);
+  else
+    strlcpy(auth_info_required, "AUTH_INFO_REQUIRED=none",
+           sizeof(auth_info_required));
 
-void
-cupsdHoldJob(cupsd_job_t *job)         /* I - Job data */
-{
-  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdHoldJob: id = %d", job->id);
+  envc = cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0])));
 
-  if (job->state_value == IPP_JOB_PROCESSING)
-    cupsdStopJob(job, 0);
-  else
-    cupsdLoadJob(job);
+  envp[envc ++] = charset;
+  envp[envc ++] = lang;
+#ifdef __APPLE__
+  envp[envc ++] = apple_language;
+#endif /* __APPLE__ */
+  envp[envc ++] = ppd;
+  envp[envc ++] = rip_max_cache;
+  envp[envc ++] = content_type;
+  envp[envc ++] = device_uri;
+  envp[envc ++] = printer_info;
+  envp[envc ++] = printer_location;
+  envp[envc ++] = printer_name;
+  envp[envc ++] = printer_state_reasons ? printer_state_reasons :
+                                          "PRINTER_STATE_REASONS=none";
+  envp[envc ++] = banner_page ? "CUPS_FILETYPE=job-sheet" :
+                                "CUPS_FILETYPE=document";
 
-  DEBUG_puts("cupsdHoldJob: setting state to held...");
+  if (!job->printer->remote && !job->printer->raw)
+  {
+    filter = (mime_filter_t *)cupsArrayLast(filters);
 
-  job->state->values[0].integer = IPP_JOB_HELD;
-  job->state_value              = IPP_JOB_HELD;
-  job->current_file             = 0;
+    if (job->printer->port_monitor)
+      filter = (mime_filter_t *)cupsArrayPrev(filters);
 
-  cupsdSaveJob(job);
+    if (filter && filter->dst)
+    {
+      if ((ptr = strchr(filter->dst->type, '/')) != NULL)
+       snprintf(final_content_type, sizeof(final_content_type),
+                "FINAL_CONTENT_TYPE=%s", ptr + 1);
+      else
+       snprintf(final_content_type, sizeof(final_content_type),
+                "FINAL_CONTENT_TYPE=%s/%s", filter->dst->super,
+                filter->dst->type);
+      envp[envc ++] = final_content_type;
+    }
+  }
 
-  cupsdCheckJobs();
-}
+  if (Classification && !banner_page)
+  {
+    if ((attr = ippFindAttribute(job->attrs, "job-sheets",
+                                 IPP_TAG_NAME)) == NULL)
+      snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
+               Classification);
+    else if (attr->num_values > 1 &&
+             strcmp(attr->values[1].string.text, "none") != 0)
+      snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
+               attr->values[1].string.text);
+    else
+      snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
+               attr->values[0].string.text);
 
+    envp[envc ++] = classification;
+  }
 
-/*
- * 'cupsdLoadAllJobs()' - Load all jobs from disk.
- */
+  if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))
+  {
+    snprintf(class_name, sizeof(class_name), "CLASS=%s", job->dest);
+    envp[envc ++] = class_name;
+  }
 
-void
-cupsdLoadAllJobs(void)
-{
-  char         filename[1024];         /* Full filename of job.cache file */
-  struct stat  fileinfo,               /* Information on job.cache file */
-               dirinfo;                /* Information on RequestRoot dir */
+  envp[envc ++] = auth_info_required;
 
+  for (i = 0;
+       i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0]));
+       i ++)
+    if (job->auth_env[i])
+      envp[envc ++] = job->auth_env[i];
+    else
+      break;
 
+  if (job->auth_uid)
+    envp[envc ++] = job->auth_uid;
 
- /*
-  * Create the job arrays as needed...
-  */
+  envp[envc] = NULL;
 
-  if (!Jobs)
-    Jobs = cupsArrayNew(compare_jobs, NULL);
+  for (i = 0; i < envc; i ++)
+    if (!strncmp(envp[i], "AUTH_", 5))
+      cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"AUTH_%c****\"", i,
+                  envp[i][5]);
+    else if (strncmp(envp[i], "DEVICE_URI=", 11))
+      cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"%s\"", i, envp[i]);
+    else
+      cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"DEVICE_URI=%s\"", i,
+                  job->printer->sanitized_device_uri);
 
-  if (!ActiveJobs)
-    ActiveJobs = cupsArrayNew(compare_active_jobs, NULL);
+  if (job->printer->remote)
+    job->current_file = job->num_files;
+  else
+    job->current_file ++;
 
  /*
-  * See whether the job.cache file is older than the RequestRoot directory...
+  * Now create processes for all of the filters...
   */
 
-  snprintf(filename, sizeof(filename), "%s/job.cache", CacheDir);
+  cupsdSetPrinterReasons(job->printer, "-cups-missing-filter-warning,"
+                                      "cups-insecure-filter-warning");
 
-  if (stat(filename, &fileinfo))
+  for (i = 0, slot = 0, filter = (mime_filter_t *)cupsArrayFirst(filters);
+       filter;
+       i ++, filter = (mime_filter_t *)cupsArrayNext(filters))
   {
-    fileinfo.st_mtime = 0;
+    if (filter->filter[0] != '/')
+      snprintf(command, sizeof(command), "%s/filter/%s", ServerBin,
+               filter->filter);
+    else
+      strlcpy(command, filter->filter, sizeof(command));
 
-    if (errno != ENOENT)
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                      "Unable to get file information for \"%s\" - %s",
-                     filename, strerror(errno));
-  }
+    if (i < (cupsArrayCount(filters) - 1))
+    {
+      if (cupsdOpenPipe(filterfds[slot]))
+      {
+        abort_message = "Stopping job because the scheduler could not create "
+                       "the filter pipes.";
 
-  if (stat(RequestRoot, &dirinfo))
-  {
-    dirinfo.st_mtime = 0;
+        goto abort_job;
+      }
+    }
+    else
+    {
+      if (job->current_file == 1 ||
+          (job->printer->pc && job->printer->pc->single_file))
+      {
+       if (strncmp(job->printer->device_uri, "file:", 5) != 0)
+       {
+         if (cupsdOpenPipe(job->print_pipes))
+         {
+           abort_message = "Stopping job because the scheduler could not "
+                           "create the backend pipes.";
 
-    if (errno != ENOENT)
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                      "Unable to get directory information for \"%s\" - %s",
-                     RequestRoot, strerror(errno));
-  }
+            goto abort_job;
+         }
+       }
+       else
+       {
+         job->print_pipes[0] = -1;
+         if (!strcmp(job->printer->device_uri, "file:/dev/null") ||
+             !strcmp(job->printer->device_uri, "file:///dev/null"))
+           job->print_pipes[1] = -1;
+         else
+         {
+           if (!strncmp(job->printer->device_uri, "file:/dev/", 10))
+             job->print_pipes[1] = open(job->printer->device_uri + 5,
+                                        O_WRONLY | O_EXCL);
+           else if (!strncmp(job->printer->device_uri, "file:///dev/", 12))
+             job->print_pipes[1] = open(job->printer->device_uri + 7,
+                                        O_WRONLY | O_EXCL);
+           else if (!strncmp(job->printer->device_uri, "file:///", 8))
+             job->print_pipes[1] = open(job->printer->device_uri + 7,
+                                        O_WRONLY | O_CREAT | O_TRUNC, 0600);
+           else
+             job->print_pipes[1] = open(job->printer->device_uri + 5,
+                                        O_WRONLY | O_CREAT | O_TRUNC, 0600);
 
- /*
-  * Load the most recent source for job data...
-  */
+           if (job->print_pipes[1] < 0)
+           {
+             abort_message = "Stopping job because the scheduler could not "
+                             "open the output file.";
 
-  if (dirinfo.st_mtime > fileinfo.st_mtime)
-  {
-    load_request_root();
+              goto abort_job;
+           }
 
-    load_next_job_id(filename);
-  }
-  else
-    load_job_cache(filename);
+           fcntl(job->print_pipes[1], F_SETFD,
+                 fcntl(job->print_pipes[1], F_GETFD) | FD_CLOEXEC);
+          }
+       }
+      }
 
- /*
-  * Clean out old jobs as needed...
-  */
+      filterfds[slot][0] = job->print_pipes[0];
+      filterfds[slot][1] = job->print_pipes[1];
+    }
 
-  if (MaxJobs > 0 && cupsArrayCount(Jobs) >= MaxJobs)
-    cupsdCleanJobs();
-}
+    pid = cupsdStartProcess(command, argv, envp, filterfds[!slot][0],
+                            filterfds[slot][1], job->status_pipes[1],
+                           job->back_pipes[0], job->side_pipes[0], 0,
+                           job->profile, job, job->filters + i);
 
+    cupsdClosePipe(filterfds[!slot]);
 
-/*
- * 'cupsdLoadJob()' - Load a single job...
- */
+    if (pid == 0)
+    {
+      cupsdLogJob(job, CUPSD_LOG_ERROR, "Unable to start filter \"%s\" - %s.",
+                 filter->filter, strerror(errno));
 
-void
-cupsdLoadJob(cupsd_job_t *job)         /* I - Job */
-{
-  char                 jobfile[1024];  /* Job filename */
-  cups_file_t          *fp;            /* Job file */
-  int                  fileid;         /* Current file ID */
-  ipp_attribute_t      *attr;          /* Job attribute */
-  const char           *dest;          /* Destination name */
-  cupsd_printer_t      *destptr;       /* Pointer to destination */
-  mime_type_t          **filetypes;    /* New filetypes array */
-  int                  *compressions;  /* New compressions array */
+      abort_message = "Stopping job because the scheduler could not execute a "
+                     "filter.";
 
+      goto abort_job;
+    }
 
-  if (job->attrs)
-  {
-    if (job->state_value > IPP_JOB_STOPPED)
-      job->access_time = time(NULL);
+    cupsdLogJob(job, CUPSD_LOG_INFO, "Started filter %s (PID %d)", command,
+                pid);
 
-    return;
+    argv[6] = NULL;
+    slot    = !slot;
   }
 
-  if ((job->attrs = ippNew()) == NULL)
-  {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                    "[Job %d] Ran out of memory for job attributes!", job->id);
-    return;
-  }
+  cupsArrayDelete(filters);
+  filters = NULL;
 
  /*
-  * Load job attributes...
+  * Finally, pipe the final output into a backend process if needed...
   */
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Loading attributes...", job->id);
-
-  snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, job->id);
-  if ((fp = cupsFileOpen(jobfile, "r")) == NULL)
+  if (strncmp(job->printer->device_uri, "file:", 5) != 0)
   {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                   "[Job %d] Unable to open job control file \"%s\" - %s!",
-                   job->id, jobfile, strerror(errno));
-    ippDelete(job->attrs);
-    job->attrs = NULL;
-    return;
-  }
+    if (job->current_file == 1 || job->printer->remote ||
+        (job->printer->pc && job->printer->pc->single_file))
+    {
+      sscanf(job->printer->device_uri, "%254[^:]", scheme);
+      snprintf(command, sizeof(command), "%s/backend/%s", ServerBin, scheme);
 
-  if (ippReadIO(fp, (ipp_iocb_t)cupsFileRead, 1, NULL, job->attrs) != IPP_DATA)
-  {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                    "[Job %d] Unable to read job control file \"%s\"!",
-                   job->id, jobfile);
-    cupsFileClose(fp);
-    ippDelete(job->attrs);
-    job->attrs = NULL;
-    unlink(jobfile);
-    return;
-  }
+     /*
+      * See if the backend needs to run as root...
+      */
 
-  cupsFileClose(fp);
+      if (RunUser)
+        backroot = 0;
+      else if (stat(command, &backinfo))
+       backroot = 0;
+      else
+        backroot = !(backinfo.st_mode & (S_IRWXG | S_IRWXO));
 
- /*
-  * Copy attribute data to the job object...
-  */
+      argv[0] = job->printer->sanitized_device_uri;
 
-  if ((job->state = ippFindAttribute(job->attrs, "job-state",
-                                     IPP_TAG_ENUM)) == NULL)
-  {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                   "[Job %d] Missing or bad job-state attribute in "
-                   "control file!",
-                   job->id);
-    ippDelete(job->attrs);
-    job->attrs = NULL;
-    unlink(jobfile);
-    return;
-  }
+      filterfds[slot][0] = -1;
+      filterfds[slot][1] = -1;
 
-  job->state_value = (ipp_jstate_t)job->state->values[0].integer;
+      pid = cupsdStartProcess(command, argv, envp, filterfds[!slot][0],
+                             filterfds[slot][1], job->status_pipes[1],
+                             job->back_pipes[1], job->side_pipes[1],
+                             backroot, job->profile, job, &(job->backend));
 
-  if (!job->dest)
-  {
-    if ((attr = ippFindAttribute(job->attrs, "job-printer-uri",
-                                 IPP_TAG_URI)) == NULL)
-    {
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                     "[Job %d] No job-printer-uri attribute in control file!",
-                     job->id);
-      ippDelete(job->attrs);
-      job->attrs = NULL;
-      unlink(jobfile);
-      return;
+      if (pid == 0)
+      {
+       abort_message = "Stopping job because the sheduler could not execute "
+                       "the backend.";
+
+        goto abort_job;
+      }
+      else
+      {
+       cupsdLogJob(job, CUPSD_LOG_INFO, "Started backend %s (PID %d)",
+                   command, pid);
+      }
     }
 
-    if ((dest = cupsdValidateDest(attr->values[0].string.text, &(job->dtype),
-                                  &destptr)) == NULL)
+    if (job->current_file == job->num_files ||
+        (job->printer->pc && job->printer->pc->single_file))
+      cupsdClosePipe(job->print_pipes);
+
+    if (job->current_file == job->num_files)
     {
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                     "[Job %d] Unable to queue job for destination \"%s\"!",
-                     job->id, attr->values[0].string.text);
-      ippDelete(job->attrs);
-      job->attrs = NULL;
-      unlink(jobfile);
-      return;
-    }
+      cupsdClosePipe(job->back_pipes);
+      cupsdClosePipe(job->side_pipes);
 
-    cupsdSetString(&job->dest, dest);
+      close(job->status_pipes[1]);
+      job->status_pipes[1] = -1;
+    }
   }
-  else if ((destptr = cupsdFindDest(job->dest)) == NULL)
+  else
   {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                   "[Job %d] Unable to queue job for destination \"%s\"!",
-                   job->id, job->dest);
-    ippDelete(job->attrs);
-    job->attrs = NULL;
-    unlink(jobfile);
-    return;
-  }
+    filterfds[slot][0] = -1;
+    filterfds[slot][1] = -1;
 
-  job->sheets     = ippFindAttribute(job->attrs, "job-media-sheets-completed",
-                                     IPP_TAG_INTEGER);
-  job->job_sheets = ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_NAME);
+    if (job->current_file == job->num_files ||
+        (job->printer->pc && job->printer->pc->single_file))
+      cupsdClosePipe(job->print_pipes);
 
-  if (!job->priority)
-  {
-    if ((attr = ippFindAttribute(job->attrs, "job-priority",
-                                IPP_TAG_INTEGER)) == NULL)
+    if (job->current_file == job->num_files)
     {
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                     "[Job %d] Missing or bad job-priority attribute in "
-                     "control file!", job->id);
-      ippDelete(job->attrs);
-      job->attrs = NULL;
-      unlink(jobfile);
-      return;
+      close(job->status_pipes[1]);
+      job->status_pipes[1] = -1;
     }
-
-    job->priority = attr->values[0].integer;
   }
 
-  if (!job->username)
-  {
-    if ((attr = ippFindAttribute(job->attrs, "job-originating-user-name",
-                                IPP_TAG_NAME)) == NULL)
-    {
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                     "[Job %d] Missing or bad job-originating-user-name "
-                     "attribute in control file!", job->id);
-      ippDelete(job->attrs);
-      job->attrs = NULL;
-      unlink(jobfile);
-      return;
-    }
+  cupsdClosePipe(filterfds[slot]);
 
-    cupsdSetString(&job->username, attr->values[0].string.text);
+  if (job->printer->remote && job->num_files > 1)
+  {
+    for (i = 0; i < job->num_files; i ++)
+      free(argv[i + 6]);
   }
 
- /*
-  * Set the job hold-until time and state...
-  */
-
-  if (job->state_value == IPP_JOB_HELD)
-  {
-    if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
-                                IPP_TAG_KEYWORD)) == NULL)
-      attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
+  free(argv);
+  if (printer_state_reasons)
+    free(printer_state_reasons);
 
-    if (attr)
-      cupsdSetJobHoldUntil(job, attr->values[0].string.text);
-    else
-    {
-      job->state->values[0].integer = IPP_JOB_PENDING;
-      job->state_value              = IPP_JOB_PENDING;
-    }
-  }
-  else if (job->state_value == IPP_JOB_PROCESSING)
-  {
-    job->state->values[0].integer = IPP_JOB_PENDING;
-    job->state_value              = IPP_JOB_PENDING;
-  }
+  cupsdAddSelect(job->status_buffer->fd, (cupsd_selfunc_t)update_job, NULL,
+                 job);
 
-  if (!job->num_files)
-  {
-   /*
-    * Find all the d##### files...
-    */
+  cupsdAddEvent(CUPSD_EVENT_JOB_STATE, job->printer, job, "Job #%d started.",
+                job->id);
 
-    for (fileid = 1; fileid < 10000; fileid ++)
-    {
-      snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot,
-               job->id, fileid);
+  return;
 
-      if (access(jobfile, 0))
-        break;
 
-      cupsdLogMessage(CUPSD_LOG_DEBUG,
-                      "[Job %d] Auto-typing document file \"%s\"...",
-                      job->id, jobfile);
+ /*
+  * If we get here, we need to abort the current job and close out all
+  * files and pipes...
+  */
 
-      if (fileid > job->num_files)
-      {
-        if (job->num_files == 0)
-       {
-         compressions = (int *)calloc(fileid, sizeof(int));
-         filetypes    = (mime_type_t **)calloc(fileid, sizeof(mime_type_t *));
-       }
-       else
-       {
-         compressions = (int *)realloc(job->compressions,
-                                       sizeof(int) * fileid);
-         filetypes    = (mime_type_t **)realloc(job->filetypes,
-                                                sizeof(mime_type_t *) *
-                                                fileid);
-        }
+  abort_job:
 
-        if (!compressions || !filetypes)
-       {
-          cupsdLogMessage(CUPSD_LOG_ERROR,
-                         "[Job %d] Ran out of memory for job file types!",
-                         job->id);
-         return;
-       }
+  FilterLevel -= job->cost;
+  job->cost = 0;
 
-        job->compressions = compressions;
-        job->filetypes    = filetypes;
-       job->num_files    = fileid;
-      }
+  for (slot = 0; slot < 2; slot ++)
+    cupsdClosePipe(filterfds[slot]);
 
-      job->filetypes[fileid - 1] = mimeFileType(MimeDatabase, jobfile, NULL,
-                                                job->compressions + fileid - 1);
+  cupsArrayDelete(filters);
 
-      if (!job->filetypes[fileid - 1])
-        job->filetypes[fileid - 1] = mimeType(MimeDatabase, "application",
-                                             "vnd.cups-raw");
+  if (argv)
+  {
+    if (job->printer->remote && job->num_files > 1)
+    {
+      for (i = 0; i < job->num_files; i ++)
+       free(argv[i + 6]);
     }
+
+    free(argv);
   }
 
- /*
-  * Load authentication information as needed...
-  */
+  if (printer_state_reasons)
+    free(printer_state_reasons);
 
-  if (job->state_value < IPP_JOB_STOPPED)
-  {
-    snprintf(jobfile, sizeof(jobfile), "%s/a%05d", RequestRoot, job->id);
+  cupsdClosePipe(job->print_pipes);
+  cupsdClosePipe(job->back_pipes);
+  cupsdClosePipe(job->side_pipes);
 
-    cupsdClearString(&job->auth_username);
-    cupsdClearString(&job->auth_domain);
-    cupsdClearString(&job->auth_password);
+  cupsdRemoveSelect(job->status_pipes[0]);
+  cupsdClosePipe(job->status_pipes);
+  cupsdStatBufDelete(job->status_buffer);
+  job->status_buffer = NULL;
 
-    if ((fp = cupsFileOpen(jobfile, "r")) != NULL)
-    {
-      int      i,                      /* Looping var */
-               bytes;                  /* Size of auth data */
-      char     line[255],              /* Line from file */
-               data[255];              /* Decoded data */
+ /*
+  * Update the printer and job state.
+  */
 
+  cupsdSetJobState(job, abort_state, CUPSD_JOB_DEFAULT, "%s", abort_message);
+  cupsdSetPrinterState(job->printer, IPP_PRINTER_IDLE, 0);
+  update_job_attrs(job, 0);
 
-      for (i = 0;
-           i < destptr->num_auth_info_required &&
-              cupsFileGets(fp, line, sizeof(line));
-          i ++)
-      {
-        bytes = sizeof(data);
-        httpDecode64_2(data, &bytes, line);
+  if (job->history)
+    free_job_history(job);
 
-       if (!strcmp(destptr->auth_info_required[i], "username"))
-         cupsdSetStringf(&job->auth_username, "AUTH_USERNAME=%s", data);
-       else if (!strcmp(destptr->auth_info_required[i], "domain"))
-         cupsdSetStringf(&job->auth_domain, "AUTH_DOMAIN=%s", data);
-       else if (!strcmp(destptr->auth_info_required[i], "password"))
-         cupsdSetStringf(&job->auth_password, "AUTH_PASSWORD=%s", data);
-      }
+  cupsArrayRemove(PrintingJobs, job);
 
-      cupsFileClose(fp);
-    }
-  }
-  job->access_time = time(NULL);
+ /*
+  * Clear the printer <-> job association...
+  */
+
+  job->printer->job = NULL;
+  job->printer      = NULL;
 }
 
 
 /*
- * 'cupsdMoveJob()' - Move the specified job to a different destination.
+ * 'cupsdDeleteJob()' - Free all memory used by a job.
  */
 
 void
-cupsdMoveJob(cupsd_job_t     *job,     /* I - Job */
-             cupsd_printer_t *p)       /* I - Destination printer or class */
+cupsdDeleteJob(cupsd_job_t       *job, /* I - Job */
+               cupsd_jobaction_t action)/* I - Action */
 {
-  ipp_attribute_t      *attr;          /* job-printer-uri attribute */
-  const char           *olddest;       /* Old destination */
-  cupsd_printer_t      *oldp;          /* Old pointer */
-
+  int  i;                              /* Looping var */
+  char filename[1024];                 /* Job filename */
 
- /*
-  * Don't move completed jobs...
-  */
 
-  if (job->state_value > IPP_JOB_STOPPED)
-    return;
+  if (job->printer)
+    finalize_job(job, 1);
 
- /*
-  * Get the old destination...
-  */
+  if (action == CUPSD_JOB_PURGE)
+  {
+   /*
+    * Remove the job info file...
+    */
 
-  olddest = job->dest;
+    snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot,
+            job->id);
+    if (Classification)
+      cupsdRemoveFile(filename);
+    else
+      unlink(filename);
+  }
 
-  if (job->printer)
-    oldp = job->printer;
-  else
-    oldp = cupsdFindDest(olddest);
+  cupsdClearString(&job->username);
+  cupsdClearString(&job->dest);
+  for (i = 0;
+       i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0]));
+       i ++)
+    cupsdClearString(job->auth_env + i);
+  cupsdClearString(&job->auth_uid);
 
- /*
-  * Change the destination information...
-  */
+  if (job->num_files > 0)
+  {
+    free(job->compressions);
+    free(job->filetypes);
 
-  if (job->state_value == IPP_JOB_PROCESSING)
-    cupsdStopJob(job, 0);
-  else
-    cupsdLoadJob(job);
+    if (action == CUPSD_JOB_PURGE)
+    {
+      while (job->num_files > 0)
+      {
+       snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
+                job->id, job->num_files);
+       if (Classification)
+         cupsdRemoveFile(filename);
+       else
+         unlink(filename);
 
-  cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, oldp, job,
-                "Job #%d moved from %s to %s.", job->id, olddest,
-               p->name);
+       job->num_files --;
+      }
+    }
+    else
+      job->num_files = 0;
+  }
 
-  cupsdSetString(&job->dest, p->name);
-  job->dtype = p->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE |
-                          CUPS_PRINTER_IMPLICIT);
+  if (job->history)
+    free_job_history(job);
 
-  if ((attr = ippFindAttribute(job->attrs, "job-printer-uri",
-                               IPP_TAG_URI)) != NULL)
-    cupsdSetString(&(attr->values[0].string.text), p->uri);
+  unload_job(job);
 
-  cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, p, job,
-                "Job #%d moved from %s to %s.", job->id, olddest,
-               p->name);
+  cupsArrayRemove(Jobs, job);
+  cupsArrayRemove(ActiveJobs, job);
+  cupsArrayRemove(PrintingJobs, job);
 
-  cupsdSaveJob(job);
+  free(job);
 }
 
 
 /*
- * 'cupsdReleaseJob()' - Release the specified job.
+ * 'cupsdFreeAllJobs()' - Free all jobs from memory.
  */
 
 void
-cupsdReleaseJob(cupsd_job_t *job)      /* I - Job */
+cupsdFreeAllJobs(void)
 {
-  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdReleaseJob: id = %d", job->id);
+  cupsd_job_t  *job;                   /* Current job */
 
-  if (job->state_value == IPP_JOB_HELD)
-  {
-    DEBUG_puts("cupsdReleaseJob: setting state to pending...");
 
-    job->state->values[0].integer = IPP_JOB_PENDING;
-    job->state_value              = IPP_JOB_PENDING;
-    cupsdSaveJob(job);
-    cupsdCheckJobs();
-  }
+  if (!Jobs)
+    return;
+
+  cupsdHoldSignals();
+
+  cupsdStopAllJobs(CUPSD_JOB_FORCE, 0);
+  cupsdSaveAllJobs();
+
+  for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
+       job;
+       job = (cupsd_job_t *)cupsArrayNext(Jobs))
+    cupsdDeleteJob(job, CUPSD_JOB_DEFAULT);
+
+  cupsdReleaseSignals();
 }
 
 
 /*
- * 'cupsdRestartJob()' - Restart the specified job.
+ * 'cupsdFindJob()' - Find the specified job.
  */
 
-void
-cupsdRestartJob(cupsd_job_t *job)      /* I - Job */
+cupsd_job_t *                          /* O - Job data */
+cupsdFindJob(int id)                   /* I - Job ID */
 {
-  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdRestartJob: id = %d", job->id);
+  cupsd_job_t  key;                    /* Search key */
 
-  if (job->state_value == IPP_JOB_STOPPED || job->num_files)
-  {
-    ipp_jstate_t       old_state;      /* Old job state */
 
+  key.id = id;
 
-    cupsdLoadJob(job);
+  return ((cupsd_job_t *)cupsArrayFind(Jobs, &key));
+}
 
-    old_state = job->state_value;
 
-    job->tries = 0;
-    job->state->values[0].integer = IPP_JOB_PENDING;
-    job->state_value              = IPP_JOB_PENDING;
+/*
+ * 'cupsdGetPrinterJobCount()' - Get the number of pending, processing,
+ *                               or held jobs in a printer or class.
+ */
+
+int                                    /* O - Job count */
+cupsdGetPrinterJobCount(
+    const char *dest)                  /* I - Printer or class name */
+{
+  int          count;                  /* Job count */
+  cupsd_job_t  *job;                   /* Current job */
 
-    cupsdSaveJob(job);
 
-    if (old_state > IPP_JOB_STOPPED)
-      cupsArrayAdd(ActiveJobs, job);
+  for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0;
+       job;
+       job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
+    if (job->dest && !_cups_strcasecmp(job->dest, dest))
+      count ++;
 
-    cupsdCheckJobs();
-  }
+  return (count);
 }
 
 
 /*
- * 'cupsdSaveAllJobs()' - Save a summary of all jobs to disk.
+ * 'cupsdGetUserJobCount()' - Get the number of pending, processing,
+ *                            or held jobs for a user.
  */
 
-void
-cupsdSaveAllJobs(void)
+int                                    /* O - Job count */
+cupsdGetUserJobCount(
+    const char *username)              /* I - Username */
 {
-  int          i;                      /* Looping var */
-  cups_file_t  *fp;                    /* Job cache file */
-  char         temp[1024];             /* Temporary string */
+  int          count;                  /* Job count */
   cupsd_job_t  *job;                   /* Current job */
-  time_t       curtime;                /* Current time */
-  struct tm    *curdate;               /* Current date */
 
 
-  snprintf(temp, sizeof(temp), "%s/job.cache", CacheDir);
-  if ((fp = cupsFileOpen(temp, "w")) == NULL)
-  {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                    "Unable to create job cache file \"%s\" - %s",
-                    temp, strerror(errno));
-    return;
-  }
+  for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0;
+       job;
+       job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
+    if (!_cups_strcasecmp(job->username, username))
+      count ++;
+
+  return (count);
+}
+
+
+/*
+ * 'cupsdLoadAllJobs()' - Load all jobs from disk.
+ */
+
+void
+cupsdLoadAllJobs(void)
+{
+  char         filename[1024];         /* Full filename of job.cache file */
+  struct stat  fileinfo,               /* Information on job.cache file */
+               dirinfo;                /* Information on RequestRoot dir */
+
 
-  cupsdLogMessage(CUPSD_LOG_INFO, "Saving job cache file \"%s\"...", temp);
 
  /*
-  * Restrict access to the file...
+  * Create the job arrays as needed...
   */
 
-  fchown(cupsFileNumber(fp), getuid(), Group);
-  fchmod(cupsFileNumber(fp), ConfigFilePerm);
+  if (!Jobs)
+    Jobs = cupsArrayNew(compare_jobs, NULL);
+
+  if (!ActiveJobs)
+    ActiveJobs = cupsArrayNew(compare_active_jobs, NULL);
+
+  if (!PrintingJobs)
+    PrintingJobs = cupsArrayNew(compare_jobs, NULL);
 
  /*
-  * Write a small header to the file...
+  * See whether the job.cache file is older than the RequestRoot directory...
   */
 
-  curtime = time(NULL);
-  curdate = localtime(&curtime);
-  strftime(temp, sizeof(temp) - 1, "%Y-%m-%d %H:%M", curdate);
+  snprintf(filename, sizeof(filename), "%s/job.cache", CacheDir);
 
-  cupsFilePuts(fp, "# Job cache file for " CUPS_SVERSION "\n");
-  cupsFilePrintf(fp, "# Written by cupsd on %s\n", temp);
-  cupsFilePrintf(fp, "NextJobId %d\n", NextJobId);
+  if (stat(filename, &fileinfo))
+  {
+    fileinfo.st_mtime = 0;
+
+    if (errno != ENOENT)
+      cupsdLogMessage(CUPSD_LOG_ERROR,
+                      "Unable to get file information for \"%s\" - %s",
+                     filename, strerror(errno));
+  }
+
+  if (stat(RequestRoot, &dirinfo))
+  {
+    dirinfo.st_mtime = 0;
+
+    if (errno != ENOENT)
+      cupsdLogMessage(CUPSD_LOG_ERROR,
+                      "Unable to get directory information for \"%s\" - %s",
+                     RequestRoot, strerror(errno));
+  }
 
  /*
-  * Write each job known to the system...
+  * Load the most recent source for job data...
   */
 
-  for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
-       job;
-       job = (cupsd_job_t *)cupsArrayNext(Jobs))
+  if (dirinfo.st_mtime > fileinfo.st_mtime)
   {
-    cupsFilePrintf(fp, "<Job %d>\n", job->id);
-    cupsFilePrintf(fp, "State %d\n", job->state_value);
-    cupsFilePrintf(fp, "Priority %d\n", job->priority);
-    cupsFilePrintf(fp, "Username %s\n", job->username);
-    cupsFilePrintf(fp, "Destination %s\n", job->dest);
-    cupsFilePrintf(fp, "DestType %d\n", job->dtype);
-    cupsFilePrintf(fp, "NumFiles %d\n", job->num_files);
-    for (i = 0; i < job->num_files; i ++)
-      cupsFilePrintf(fp, "File %d %s/%s %d\n", i + 1, job->filetypes[i]->super,
-                     job->filetypes[i]->type, job->compressions[i]);
-    cupsFilePuts(fp, "</Job>\n");
+    load_request_root();
+
+    load_next_job_id(filename);
   }
+  else
+    load_job_cache(filename);
 
-  cupsFileClose(fp);
+ /*
+  * Clean out old jobs as needed...
+  */
+
+  if (MaxJobs > 0 && cupsArrayCount(Jobs) >= MaxJobs)
+    cupsdCleanJobs();
 }
 
 
 /*
- * 'cupsdSaveJob()' - Save a job to disk.
+ * 'cupsdLoadJob()' - Load a single job.
  */
 
-void
-cupsdSaveJob(cupsd_job_t *job)         /* I - Job */
+int                                    /* O - 1 on success, 0 on failure */
+cupsdLoadJob(cupsd_job_t *job)         /* I - Job */
 {
-  char         filename[1024];         /* Job control filename */
-  cups_file_t  *fp;                    /* Job file */
+  int                  i;              /* Looping var */
+  char                 jobfile[1024];  /* Job filename */
+  cups_file_t          *fp;            /* Job file */
+  int                  fileid;         /* Current file ID */
+  ipp_attribute_t      *attr;          /* Job attribute */
+  const char           *dest;          /* Destination name */
+  cupsd_printer_t      *destptr;       /* Pointer to destination */
+  mime_type_t          **filetypes;    /* New filetypes array */
+  int                  *compressions;  /* New compressions array */
 
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSaveJob(job=%p(%d)): job->attrs=%p",
-                  job, job->id, job->attrs);
+  if (job->attrs)
+  {
+    if (job->state_value > IPP_JOB_STOPPED)
+      job->access_time = time(NULL);
 
-  snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, job->id);
+    return (1);
+  }
 
-  if ((fp = cupsFileOpen(filename, "w")) == NULL)
+  if ((job->attrs = ippNew()) == NULL)
   {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                    "[Job %d] Unable to create job control file \"%s\" - %s.",
-                    job->id, filename, strerror(errno));
-    return;
+    cupsdLogJob(job, CUPSD_LOG_ERROR, "Ran out of memory for job attributes!");
+    return (0);
   }
 
-  fchmod(cupsFileNumber(fp), 0600);
-  fchown(cupsFileNumber(fp), RunUser, Group);
-
-  job->attrs->state = IPP_IDLE;
-
-  if (ippWriteIO(fp, (ipp_iocb_t)cupsFileWrite, 1, NULL,
-                 job->attrs) != IPP_DATA)
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                    "[Job %d] Unable to write job control file!", job->id);
+ /*
+  * Load job attributes...
+  */
 
-  cupsFileClose(fp);
-}
+  cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Loading attributes...", job->id);
 
+  snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, job->id);
+  if ((fp = cupsFileOpen(jobfile, "r")) == NULL)
+  {
+    char newfile[1024];                        /* New job filename */
 
-/*
- * 'cupsdSetJobHoldUntil()' - Set the hold time for a job...
- */
+    snprintf(newfile, sizeof(newfile), "%s/c%05d.N", RequestRoot, job->id);
+    if ((fp = cupsFileOpen(newfile, "r")) == NULL)
+    {
+      cupsdLogMessage(CUPSD_LOG_ERROR,
+                     "[Job %d] Unable to open job control file \"%s\": %s",
+                     job->id, jobfile, strerror(errno));
+      goto error;
+    }
 
-void
-cupsdSetJobHoldUntil(cupsd_job_t *job, /* I - Job */
-                     const char  *when)        /* I - When to resume */
-{
-  time_t       curtime;                /* Current time */
-  struct tm    *curdate;               /* Current date */
-  int          hour;                   /* Hold hour */
-  int          minute;                 /* Hold minute */
-  int          second;                 /* Hold second */
+    unlink(jobfile);
+    rename(newfile, jobfile);
+  }
 
+  if (ippReadIO(fp, (ipp_iocb_t)cupsFileRead, 1, NULL, job->attrs) != IPP_DATA)
+  {
+    cupsdLogMessage(CUPSD_LOG_ERROR,
+                   "[Job %d] Unable to read job control file \"%s\".", job->id,
+                   jobfile);
+    cupsFileClose(fp);
+    goto error;
+  }
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSetJobHoldUntil(%d, \"%s\")",
-                  job->id, when);
+  cupsFileClose(fp);
 
-  second = 0;
+ /*
+  * Copy attribute data to the job object...
+  */
 
-  if (!strcmp(when, "indefinite") || !strcmp(when, "auth-info-required"))
+  if (!ippFindAttribute(job->attrs, "time-at-creation", IPP_TAG_INTEGER))
   {
-   /*
-    * Hold indefinitely...
-    */
-
-    job->hold_until = 0;
+    cupsdLogMessage(CUPSD_LOG_ERROR,
+                   "[Job %d] Missing or bad time-at-creation attribute in "
+                   "control file!", job->id);
+    goto error;
   }
-  else if (!strcmp(when, "day-time"))
+
+  if ((job->state = ippFindAttribute(job->attrs, "job-state",
+                                     IPP_TAG_ENUM)) == NULL)
   {
-   /*
-    * Hold to 6am the next morning unless local time is < 6pm.
-    */
+    cupsdLogMessage(CUPSD_LOG_ERROR,
+                   "[Job %d] Missing or bad job-state attribute in control "
+                   "file!", job->id);
+    goto error;
+  }
 
-    curtime = time(NULL);
-    curdate = localtime(&curtime);
+  job->state_value = (ipp_jstate_t)job->state->values[0].integer;
 
-    if (curdate->tm_hour < 18)
-      job->hold_until = curtime;
-    else
-      job->hold_until = curtime +
-                        ((29 - curdate->tm_hour) * 60 + 59 -
-                        curdate->tm_min) * 60 + 60 - curdate->tm_sec;
-  }
-  else if (!strcmp(when, "evening") || !strcmp(when, "night"))
+  if (!job->dest)
   {
-   /*
-    * Hold to 6pm unless local time is > 6pm or < 6am.
-    */
+    if ((attr = ippFindAttribute(job->attrs, "job-printer-uri",
+                                 IPP_TAG_URI)) == NULL)
+    {
+      cupsdLogMessage(CUPSD_LOG_ERROR,
+                     "[Job %d] No job-printer-uri attribute in control file!",
+                     job->id);
+      goto error;
+    }
 
-    curtime = time(NULL);
-    curdate = localtime(&curtime);
+    if ((dest = cupsdValidateDest(attr->values[0].string.text, &(job->dtype),
+                                  &destptr)) == NULL)
+    {
+      cupsdLogMessage(CUPSD_LOG_ERROR,
+                     "[Job %d] Unable to queue job for destination \"%s\"!",
+                     job->id, attr->values[0].string.text);
+      goto error;
+    }
 
-    if (curdate->tm_hour < 6 || curdate->tm_hour >= 18)
-      job->hold_until = curtime;
-    else
-      job->hold_until = curtime +
-                        ((17 - curdate->tm_hour) * 60 + 59 -
-                        curdate->tm_min) * 60 + 60 - curdate->tm_sec;
+    cupsdSetString(&job->dest, dest);
   }
-  else if (!strcmp(when, "second-shift"))
+  else if ((destptr = cupsdFindDest(job->dest)) == NULL)
   {
-   /*
-    * Hold to 4pm unless local time is > 4pm.
-    */
+    cupsdLogMessage(CUPSD_LOG_ERROR,
+                   "[Job %d] Unable to queue job for destination \"%s\"!",
+                   job->id, job->dest);
+    goto error;
+  }
 
-    curtime = time(NULL);
-    curdate = localtime(&curtime);
+  job->sheets     = ippFindAttribute(job->attrs, "job-media-sheets-completed",
+                                     IPP_TAG_INTEGER);
+  job->job_sheets = ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_NAME);
 
-    if (curdate->tm_hour >= 16)
-      job->hold_until = curtime;
-    else
-      job->hold_until = curtime +
-                        ((15 - curdate->tm_hour) * 60 + 59 -
-                        curdate->tm_min) * 60 + 60 - curdate->tm_sec;
-  }
-  else if (!strcmp(when, "third-shift"))
+  if (!job->priority)
   {
-   /*
-    * Hold to 12am unless local time is < 8am.
-    */
-
-    curtime = time(NULL);
-    curdate = localtime(&curtime);
+    if ((attr = ippFindAttribute(job->attrs, "job-priority",
+                                IPP_TAG_INTEGER)) == NULL)
+    {
+      cupsdLogMessage(CUPSD_LOG_ERROR,
+                     "[Job %d] Missing or bad job-priority attribute in "
+                     "control file!", job->id);
+      goto error;
+    }
 
-    if (curdate->tm_hour < 8)
-      job->hold_until = curtime;
-    else
-      job->hold_until = curtime +
-                        ((23 - curdate->tm_hour) * 60 + 59 -
-                        curdate->tm_min) * 60 + 60 - curdate->tm_sec;
+    job->priority = attr->values[0].integer;
   }
-  else if (!strcmp(when, "weekend"))
+
+  if (!job->username)
   {
-   /*
-    * Hold to weekend unless we are in the weekend.
-    */
+    if ((attr = ippFindAttribute(job->attrs, "job-originating-user-name",
+                                IPP_TAG_NAME)) == NULL)
+    {
+      cupsdLogMessage(CUPSD_LOG_ERROR,
+                     "[Job %d] Missing or bad job-originating-user-name "
+                     "attribute in control file!", job->id);
+      goto error;
+    }
 
-    curtime = time(NULL);
-    curdate = localtime(&curtime);
+    cupsdSetString(&job->username, attr->values[0].string.text);
+  }
 
-    if (curdate->tm_wday || curdate->tm_wday == 6)
-      job->hold_until = curtime;
+ /*
+  * Set the job hold-until time and state...
+  */
+
+  if (job->state_value == IPP_JOB_HELD)
+  {
+    if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
+                                IPP_TAG_KEYWORD)) == NULL)
+      attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
+
+    if (attr)
+      cupsdSetJobHoldUntil(job, attr->values[0].string.text, CUPSD_JOB_DEFAULT);
     else
-      job->hold_until = curtime +
-                        (((5 - curdate->tm_wday) * 24 +
-                          (17 - curdate->tm_hour)) * 60 + 59 -
-                          curdate->tm_min) * 60 + 60 - curdate->tm_sec;
+    {
+      job->state->values[0].integer = IPP_JOB_PENDING;
+      job->state_value              = IPP_JOB_PENDING;
+    }
   }
-  else if (sscanf(when, "%d:%d:%d", &hour, &minute, &second) >= 2)
+  else if (job->state_value == IPP_JOB_PROCESSING)
+  {
+    job->state->values[0].integer = IPP_JOB_PENDING;
+    job->state_value              = IPP_JOB_PENDING;
+  }
+
+  if (!job->num_files)
   {
    /*
-    * Hold to specified GMT time (HH:MM or HH:MM:SS)...
+    * Find all the d##### files...
     */
 
-    curtime = time(NULL);
-    curdate = gmtime(&curtime);
+    for (fileid = 1; fileid < 10000; fileid ++)
+    {
+      snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot,
+               job->id, fileid);
 
-    job->hold_until = curtime +
-                      ((hour - curdate->tm_hour) * 60 + minute -
-                      curdate->tm_min) * 60 + second - curdate->tm_sec;
+      if (access(jobfile, 0))
+        break;
 
-   /*
-    * Hold until next day as needed...
-    */
+      cupsdLogMessage(CUPSD_LOG_DEBUG,
+                     "[Job %d] Auto-typing document file \"%s\"...", job->id,
+                     jobfile);
 
-    if (job->hold_until < curtime)
-      job->hold_until += 24 * 60 * 60;
-  }
+      if (fileid > job->num_files)
+      {
+        if (job->num_files == 0)
+       {
+         compressions = (int *)calloc(fileid, sizeof(int));
+         filetypes    = (mime_type_t **)calloc(fileid, sizeof(mime_type_t *));
+       }
+       else
+       {
+         compressions = (int *)realloc(job->compressions,
+                                       sizeof(int) * fileid);
+         filetypes    = (mime_type_t **)realloc(job->filetypes,
+                                                sizeof(mime_type_t *) *
+                                                fileid);
+        }
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSetJobHoldUntil: hold_until = %d",
-                  (int)job->hold_until);
-}
+        if (!compressions || !filetypes)
+       {
+          cupsdLogMessage(CUPSD_LOG_ERROR,
+                         "[Job %d] Ran out of memory for job file types!",
+                         job->id);
 
+         ippDelete(job->attrs);
+         job->attrs = NULL;
 
-/*
- * 'cupsdSetJobPriority()' - Set the priority of a job, moving it up/down in
- *                           the list as needed.
- */
+         if (compressions)
+           free(compressions);
 
-void
-cupsdSetJobPriority(
-    cupsd_job_t *job,                  /* I - Job ID */
-    int         priority)              /* I - New priority (0 to 100) */
-{
-  ipp_attribute_t      *attr;          /* Job attribute */
+         if (filetypes)
+           free(filetypes);
 
+         if (job->compressions)
+         {
+           free(job->compressions);
+           job->compressions = NULL;
+         }
 
- /*
-  * Don't change completed jobs...
-  */
+         if (job->filetypes)
+         {
+           free(job->filetypes);
+           job->filetypes = NULL;
+         }
 
-  if (job->state_value >= IPP_JOB_PROCESSING)
-    return;
+         job->num_files = 0;
+         return (0);
+       }
 
- /*
-  * Set the new priority and re-add the job into the active list...
-  */
+        job->compressions = compressions;
+        job->filetypes    = filetypes;
+       job->num_files    = fileid;
+      }
 
-  cupsArrayRemove(ActiveJobs, job);
+      job->filetypes[fileid - 1] = mimeFileType(MimeDatabase, jobfile, NULL,
+                                                job->compressions + fileid - 1);
 
-  job->priority = priority;
+      if (!job->filetypes[fileid - 1])
+        job->filetypes[fileid - 1] = mimeType(MimeDatabase, "application",
+                                             "vnd.cups-raw");
+    }
+  }
 
-  if ((attr = ippFindAttribute(job->attrs, "job-priority",
-                               IPP_TAG_INTEGER)) != NULL)
-    attr->values[0].integer = priority;
-  else
-    ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-priority",
-                  priority);
+ /*
+  * Load authentication information as needed...
+  */
 
-  cupsArrayAdd(ActiveJobs, job);
+  if (job->state_value < IPP_JOB_STOPPED)
+  {
+    snprintf(jobfile, sizeof(jobfile), "%s/a%05d", RequestRoot, job->id);
 
-  cupsdSaveJob(job);
-}
+    for (i = 0;
+        i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0]));
+        i ++)
+      cupsdClearString(job->auth_env + i);
+    cupsdClearString(&job->auth_uid);
 
+    if ((fp = cupsFileOpen(jobfile, "r")) != NULL)
+    {
+      int      bytes;                  /* Size of auth data */
+      char     line[65536],            /* Line from file */
+               data[65536];            /* Decoded data */
 
-/*
- * 'cupsdStopAllJobs()' - Stop all print jobs.
- */
 
-void
-cupsdStopAllJobs(int force)            /* I - 1 = Force all filters to stop */
-{
-  cupsd_job_t  *job;                   /* Current job */
+      for (i = 0;
+           i < destptr->num_auth_info_required &&
+              i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0])) &&
+              cupsFileGets(fp, line, sizeof(line));
+          i ++)
+      {
+        bytes = sizeof(data);
+        httpDecode64_2(data, &bytes, line);
 
+       if (!strcmp(destptr->auth_info_required[i], "username"))
+         cupsdSetStringf(job->auth_env + i, "AUTH_USERNAME=%s", data);
+       else if (!strcmp(destptr->auth_info_required[i], "domain"))
+         cupsdSetStringf(job->auth_env + i, "AUTH_DOMAIN=%s", data);
+       else if (!strcmp(destptr->auth_info_required[i], "password"))
+         cupsdSetStringf(job->auth_env + i, "AUTH_PASSWORD=%s", data);
+        else if (!strcmp(destptr->auth_info_required[i], "negotiate"))
+         cupsdSetStringf(job->auth_env + i, "AUTH_NEGOTIATE=%s", line);
+      }
 
-  DEBUG_puts("cupsdStopAllJobs()");
+      if (cupsFileGets(fp, line, sizeof(line)) && isdigit(line[0] & 255))
+        cupsdSetStringf(&job->auth_uid, "AUTH_UID=%s", line);
 
-  for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
-       job;
-       job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
-    if (job->state_value == IPP_JOB_PROCESSING)
-    {
-      cupsdStopJob(job, force);
-      job->state->values[0].integer = IPP_JOB_PENDING;
-      job->state_value              = IPP_JOB_PENDING;
+      cupsFileClose(fp);
     }
-}
+  }
+
+  job->access_time = time(NULL);
+  return (1);
+
+ /*
+  * If we get here then something bad happened...
+  */
+
+  error:
+
+  ippDelete(job->attrs);
+  job->attrs = NULL;
+
+  if (job->compressions)
+  {
+    free(job->compressions);
+    job->compressions = NULL;
+  }
+
+  if (job->filetypes)
+  {
+    free(job->filetypes);
+    job->filetypes = NULL;
+  }
+
+  job->num_files = 0;
+
+  if (Classification)
+    cupsdRemoveFile(jobfile);
+  else
+    unlink(jobfile);
+
+  return (0);
+}
 
 
 /*
- * 'cupsdStopJob()' - Stop a print job.
+ * 'cupsdMoveJob()' - Move the specified job to a different destination.
  */
 
 void
-cupsdStopJob(cupsd_job_t *job,         /* I - Job */
-             int         force)                /* I - 1 = Force all filters to stop */
+cupsdMoveJob(cupsd_job_t     *job,     /* I - Job */
+             cupsd_printer_t *p)       /* I - Destination printer or class */
 {
-  int  i;                              /* Looping var */
+  ipp_attribute_t      *attr;          /* job-printer-uri attribute */
+  const char           *olddest;       /* Old destination */
+  cupsd_printer_t      *oldp;          /* Old pointer */
 
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                  "[Job %d] cupsdStopJob: force = %d", job->id, force);
+ /*
+  * Don't move completed jobs...
+  */
 
-  if (job->state_value != IPP_JOB_PROCESSING)
+  if (job->state_value > IPP_JOB_STOPPED)
     return;
 
-  FilterLevel -= job->cost;
+ /*
+  * Get the old destination...
+  */
 
-  if (job->printer->state == IPP_PRINTER_PROCESSING)
-    cupsdSetPrinterState(job->printer, IPP_PRINTER_IDLE, 0);
+  olddest = job->dest;
 
-  job->state->values[0].integer = IPP_JOB_STOPPED;
-  job->state_value              = IPP_JOB_STOPPED;
-  job->printer->job = NULL;
-  job->printer      = NULL;
+  if (job->printer)
+    oldp = job->printer;
+  else
+    oldp = cupsdFindDest(olddest);
 
-  job->current_file --;
+ /*
+  * Change the destination information...
+  */
 
-  for (i = 0; job->filters[i]; i ++)
-    if (job->filters[i] > 0)
-    {
-      cupsdEndProcess(job->filters[i], force);
-      job->filters[i] = 0;
-    }
+  if (job->state_value > IPP_JOB_HELD)
+    cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT,
+                    "Stopping job prior to move.");
 
-  if (job->backend > 0)
-  {
-    cupsdEndProcess(job->backend, force);
-    job->backend = 0;
-  }
+  cupsdAddEvent(CUPSD_EVENT_JOB_CONFIG_CHANGED, oldp, job,
+                "Job #%d moved from %s to %s.", job->id, olddest,
+               p->name);
 
-  cupsdDestroyProfile(job->profile);
-  job->profile = NULL;
+  cupsdSetString(&job->dest, p->name);
+  job->dtype = p->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE |
+                          CUPS_PRINTER_IMPLICIT);
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] Closing print pipes [ %d %d ]...",
-                  job->id, job->print_pipes[0], job->print_pipes[1]);
+  if ((attr = ippFindAttribute(job->attrs, "job-printer-uri",
+                               IPP_TAG_URI)) != NULL)
+    cupsdSetString(&(attr->values[0].string.text), p->uri);
 
-  cupsdClosePipe(job->print_pipes);
+  cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, p, job,
+                "Job #%d moved from %s to %s.", job->id, olddest,
+               p->name);
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] Closing back pipes [ %d %d ]...",
-                  job->id, job->back_pipes[0], job->back_pipes[1]);
+  job->dirty = 1;
+  cupsdMarkDirty(CUPSD_DIRTY_JOBS);
+}
 
-  cupsdClosePipe(job->back_pipes);
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] Closing side pipes [ %d %d ]...",
-                  job->id, job->side_pipes[0], job->side_pipes[1]);
+/*
+ * 'cupsdReleaseJob()' - Release the specified job.
+ */
 
-  cupsdClosePipe(job->side_pipes);
+void
+cupsdReleaseJob(cupsd_job_t *job)      /* I - Job */
+{
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdReleaseJob(job=%p(%d))", job,
+                  job->id);
 
-  if (job->status_buffer)
+  if (job->state_value == IPP_JOB_HELD)
   {
    /*
-    * Close the pipe and clear the input bit.
+    * Add trailing banner as needed...
     */
 
-    cupsdRemoveSelect(job->status_buffer->fd);
+    if (job->pending_timeout)
+      cupsdTimeoutJob(job);
 
-    cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                    "[Job %d] Closing status pipes [ %d %d ]...", job->id,
-                    job->status_pipes[0], job->status_pipes[1]);
-
-    cupsdClosePipe(job->status_pipes);
-    cupsdStatBufDelete(job->status_buffer);
-
-    job->status_buffer = NULL;
+    cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT,
+                     "Job released by user.");
   }
 }
 
 
 /*
- * 'cupsdUnloadCompletedJobs()' - Flush completed job history from memory.
+ * 'cupsdRestartJob()' - Restart the specified job.
  */
 
 void
-cupsdUnloadCompletedJobs(void)
+cupsdRestartJob(cupsd_job_t *job)      /* I - Job */
 {
-  cupsd_job_t  *job;                   /* Current job */
-  time_t       expire;                 /* Expiration time */
-
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdRestartJob(job=%p(%d))", job,
+                  job->id);
 
-  expire = time(NULL) - 60;
-
-  for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
-       job;
-       job = (cupsd_job_t *)cupsArrayNext(Jobs))
-    if (job->attrs && job->state_value >= IPP_JOB_STOPPED &&
-        job->access_time < expire)
-      unload_job(job);
+  if (job->state_value == IPP_JOB_STOPPED || job->num_files)
+    cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT,
+                     "Job restarted by user.");
 }
 
 
 /*
- * 'compare_active_jobs()' - Compare the job IDs and priorities of two jobs.
+ * 'cupsdSaveAllJobs()' - Save a summary of all jobs to disk.
  */
 
-static int                             /* O - Difference */
-compare_active_jobs(void *first,       /* I - First job */
-                    void *second,      /* I - Second job */
-                   void *data)         /* I - App data (not used) */
+void
+cupsdSaveAllJobs(void)
 {
-  int  diff;                           /* Difference */
+  int          i;                      /* Looping var */
+  cups_file_t  *fp;                    /* job.cache file */
+  char         filename[1024],         /* job.cache filename */
+               temp[1024];             /* Temporary string */
+  cupsd_job_t  *job;                   /* Current job */
+  time_t       curtime;                /* Current time */
+  struct tm    *curdate;               /* Current date */
 
 
-  if ((diff = ((cupsd_job_t *)second)->priority -
-              ((cupsd_job_t *)first)->priority) != 0)
-    return (diff);
-  else
-    return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
-}
+  snprintf(filename, sizeof(filename), "%s/job.cache", CacheDir);
+  if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm)) == NULL)
+    return;
 
+  cupsdLogMessage(CUPSD_LOG_INFO, "Saving job.cache...");
 
-/*
* 'compare_jobs()' - Compare the job IDs of two jobs.
- */
+ /*
 * Write a small header to the file...
 */
 
-static int                             /* O - Difference */
-compare_jobs(void *first,              /* I - First job */
-             void *second,             /* I - Second job */
-            void *data)                /* I - App data (not used) */
-{
-  return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
+  curtime = time(NULL);
+  curdate = localtime(&curtime);
+  strftime(temp, sizeof(temp) - 1, "%Y-%m-%d %H:%M", curdate);
+
+  cupsFilePuts(fp, "# Job cache file for " CUPS_SVERSION "\n");
+  cupsFilePrintf(fp, "# Written by cupsd on %s\n", temp);
+  cupsFilePrintf(fp, "NextJobId %d\n", NextJobId);
+
+ /*
+  * Write each job known to the system...
+  */
+
+  for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
+       job;
+       job = (cupsd_job_t *)cupsArrayNext(Jobs))
+  {
+    cupsFilePrintf(fp, "<Job %d>\n", job->id);
+    cupsFilePrintf(fp, "State %d\n", job->state_value);
+    cupsFilePrintf(fp, "Priority %d\n", job->priority);
+    cupsFilePrintf(fp, "HoldUntil %d\n", (int)job->hold_until);
+    cupsFilePrintf(fp, "Username %s\n", job->username);
+    cupsFilePrintf(fp, "Destination %s\n", job->dest);
+    cupsFilePrintf(fp, "DestType %d\n", job->dtype);
+    cupsFilePrintf(fp, "NumFiles %d\n", job->num_files);
+    for (i = 0; i < job->num_files; i ++)
+      cupsFilePrintf(fp, "File %d %s/%s %d\n", i + 1, job->filetypes[i]->super,
+                     job->filetypes[i]->type, job->compressions[i]);
+    cupsFilePuts(fp, "</Job>\n");
+  }
+
+  cupsdCloseCreatedConfFile(fp, filename);
 }
 
 
 /*
- * 'free_job()' - Free all memory used by a job.
+ * 'cupsdSaveJob()' - Save a job to disk.
  */
 
-static void
-free_job(cupsd_job_t *job)             /* I - Job */
+void
+cupsdSaveJob(cupsd_job_t *job)         /* I - Job */
 {
-  cupsdClearString(&job->username);
-  cupsdClearString(&job->dest);
-  cupsdClearString(&job->auth_username);
-  cupsdClearString(&job->auth_domain);
-  cupsdClearString(&job->auth_password);
+  char         filename[1024],         /* Job control filename */
+               newfile[1024];          /* New job control filename */
+  cups_file_t  *fp;                    /* Job file */
 
-#ifdef HAVE_GSSAPI
- /*
-  * Destroy the credential cache and clear the KRB5CCNAME env var string.
-  */
 
-  if (job->ccache)
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSaveJob(job=%p(%d)): job->attrs=%p",
+                  job, job->id, job->attrs);
+
+  snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, job->id);
+  snprintf(newfile, sizeof(newfile), "%s/c%05d.N", RequestRoot, job->id);
+
+  if ((fp = cupsFileOpen(newfile, "w")) == NULL)
   {
-    krb5_cc_destroy(KerberosContext, job->ccache);
-    job->ccache = NULL;
+    cupsdLogMessage(CUPSD_LOG_ERROR,
+                   "[Job %d] Unable to create job control file \"%s\": %s",
+                   job->id, newfile, strerror(errno));
+    return;
   }
 
-  cupsdClearString(&job->ccname);
-#endif /* HAVE_GSSAPI */
+  fchmod(cupsFileNumber(fp), 0600);
+  fchown(cupsFileNumber(fp), RunUser, Group);
+
+  job->attrs->state = IPP_IDLE;
 
-  if (job->num_files > 0)
+  if (ippWriteIO(fp, (ipp_iocb_t)cupsFileWrite, 1, NULL,
+                 job->attrs) != IPP_DATA)
   {
-    free(job->compressions);
-    free(job->filetypes);
+    cupsdLogMessage(CUPSD_LOG_ERROR,
+                    "[Job %d] Unable to write job control file.", job->id);
+    cupsFileClose(fp);
+    unlink(newfile);
+    return;
   }
 
-  ippDelete(job->attrs);
-
-  free(job);
+  if (cupsFileClose(fp))
+    cupsdLogMessage(CUPSD_LOG_ERROR,
+                   "[Job %d] Unable to close job control file: %s",
+                   job->id, strerror(errno));
+  else
+  {
+    unlink(filename);
+    if (rename(newfile, filename))
+      cupsdLogMessage(CUPSD_LOG_ERROR,
+                      "[Job %d] Unable to finalize job control file: %s",
+                     job->id, strerror(errno));
+    else
+      job->dirty = 0;
+  }
 }
 
 
 /*
- * 'ipp_length()' - Compute the size of the buffer needed to hold
- *                 the textual IPP attributes.
+ * 'cupsdSetJobHoldUntil()' - Set the hold time for a job.
  */
 
-static int                             /* O - Size of attribute buffer */
-ipp_length(ipp_t *ipp)                 /* I - IPP request */
+void
+cupsdSetJobHoldUntil(cupsd_job_t *job, /* I - Job */
+                     const char  *when,        /* I - When to resume */
+                    int         update)/* I - Update job-hold-until attr? */
 {
-  int                  bytes;          /* Number of bytes */
-  int                  i;              /* Looping var */
-  ipp_attribute_t      *attr;          /* Current attribute */
-
+  time_t       curtime;                /* Current time */
+  struct tm    *curdate;               /* Current date */
+  int          hour;                   /* Hold hour */
+  int          minute;                 /* Hold minute */
+  int          second = 0;             /* Hold second */
 
- /*
-  * Loop through all attributes...
-  */
 
-  bytes = 0;
+  cupsdLogMessage(CUPSD_LOG_DEBUG2,
+                  "cupsdSetJobHoldUntil(job=%p(%d), when=\"%s\", update=%d)",
+                  job, job->id, when, update);
 
-  for (attr = ipp->attrs; attr != NULL; attr = attr->next)
+  if (update)
   {
    /*
-    * Skip attributes that won't be sent to filters...
+    * Update the job-hold-until attribute...
     */
 
-    if (attr->value_tag == IPP_TAG_MIMETYPE ||
-       attr->value_tag == IPP_TAG_NAMELANG ||
-       attr->value_tag == IPP_TAG_TEXTLANG ||
-       attr->value_tag == IPP_TAG_URI ||
-       attr->value_tag == IPP_TAG_URISCHEME)
-      continue;
+    ipp_attribute_t *attr;             /* job-hold-until attribute */
 
-    if (strncmp(attr->name, "time-", 5) == 0)
-      continue;
+    if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
+                                IPP_TAG_KEYWORD)) == NULL)
+      attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
 
-   /*
-    * Add space for a leading space and commas between each value.
-    * For the first attribute, the leading space isn't used, so the
-    * extra byte can be used as the nul terminator...
-    */
+    if (attr)
+      cupsdSetString(&(attr->values[0].string.text), when);
+    else
+      attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD,
+                          "job-hold-until", NULL, when);
 
-    bytes ++;                          /* " " separator */
-    bytes += attr->num_values;         /* "," separators */
+    if (attr)
+    {
+      if (isdigit(when[0] & 255))
+       attr->value_tag = IPP_TAG_NAME;
+      else
+       attr->value_tag = IPP_TAG_KEYWORD;
 
-   /*
-    * Boolean attributes appear as "foo,nofoo,foo,nofoo", while
-    * other attributes appear as "foo=value1,value2,...,valueN".
-    */
+      job->dirty = 1;
+      cupsdMarkDirty(CUPSD_DIRTY_JOBS);
+    }
+  }
 
-    if (attr->value_tag != IPP_TAG_BOOLEAN)
-      bytes += strlen(attr->name);
-    else
-      bytes += attr->num_values * strlen(attr->name);
+ /*
+  * Update the hold time...
+  */
 
+  if (!strcmp(when, "indefinite") || !strcmp(when, "auth-info-required"))
+  {
    /*
-    * Now add the size required for each value in the attribute...
+    * Hold indefinitely...
     */
 
-    switch (attr->value_tag)
-    {
-      case IPP_TAG_INTEGER :
-      case IPP_TAG_ENUM :
-         /*
-         * Minimum value of a signed integer is -2147483647, or 11 digits.
-         */
+    job->hold_until = 0;
+  }
+  else if (!strcmp(when, "day-time"))
+  {
+   /*
+    * Hold to 6am the next morning unless local time is < 6pm.
+    */
 
-         bytes += attr->num_values * 11;
-         break;
+    curtime = time(NULL);
+    curdate = localtime(&curtime);
 
-      case IPP_TAG_BOOLEAN :
-         /*
-         * Add two bytes for each false ("no") value...
-         */
+    if (curdate->tm_hour < 18)
+      job->hold_until = curtime;
+    else
+      job->hold_until = curtime +
+                        ((29 - curdate->tm_hour) * 60 + 59 -
+                        curdate->tm_min) * 60 + 60 - curdate->tm_sec;
+  }
+  else if (!strcmp(when, "evening") || !strcmp(when, "night"))
+  {
+   /*
+    * Hold to 6pm unless local time is > 6pm or < 6am.
+    */
 
-          for (i = 0; i < attr->num_values; i ++)
-           if (!attr->values[i].boolean)
-             bytes += 2;
-         break;
+    curtime = time(NULL);
+    curdate = localtime(&curtime);
 
-      case IPP_TAG_RANGE :
-         /*
-         * A range is two signed integers separated by a hyphen, or
-         * 23 characters max.
-         */
+    if (curdate->tm_hour < 6 || curdate->tm_hour >= 18)
+      job->hold_until = curtime;
+    else
+      job->hold_until = curtime +
+                        ((17 - curdate->tm_hour) * 60 + 59 -
+                        curdate->tm_min) * 60 + 60 - curdate->tm_sec;
+  }
+  else if (!strcmp(when, "second-shift"))
+  {
+   /*
+    * Hold to 4pm unless local time is > 4pm.
+    */
 
-         bytes += attr->num_values * 23;
-         break;
+    curtime = time(NULL);
+    curdate = localtime(&curtime);
 
-      case IPP_TAG_RESOLUTION :
-         /*
-         * A resolution is two signed integers separated by an "x" and
-         * suffixed by the units, or 26 characters max.
-         */
+    if (curdate->tm_hour >= 16)
+      job->hold_until = curtime;
+    else
+      job->hold_until = curtime +
+                        ((15 - curdate->tm_hour) * 60 + 59 -
+                        curdate->tm_min) * 60 + 60 - curdate->tm_sec;
+  }
+  else if (!strcmp(when, "third-shift"))
+  {
+   /*
+    * Hold to 12am unless local time is < 8am.
+    */
 
-         bytes += attr->num_values * 26;
-         break;
+    curtime = time(NULL);
+    curdate = localtime(&curtime);
 
-      case IPP_TAG_STRING :
-      case IPP_TAG_TEXT :
-      case IPP_TAG_NAME :
-      case IPP_TAG_KEYWORD :
-      case IPP_TAG_CHARSET :
-      case IPP_TAG_LANGUAGE :
-      case IPP_TAG_URI :
-         /*
-         * Strings can contain characters that need quoting.  We need
-         * at least 2 * len + 2 characters to cover the quotes and
-         * any backslashes in the string.
-         */
+    if (curdate->tm_hour < 8)
+      job->hold_until = curtime;
+    else
+      job->hold_until = curtime +
+                        ((23 - curdate->tm_hour) * 60 + 59 -
+                        curdate->tm_min) * 60 + 60 - curdate->tm_sec;
+  }
+  else if (!strcmp(when, "weekend"))
+  {
+   /*
+    * Hold to weekend unless we are in the weekend.
+    */
 
-          for (i = 0; i < attr->num_values; i ++)
-           bytes += 2 * strlen(attr->values[i].string.text) + 2;
-         break;
+    curtime = time(NULL);
+    curdate = localtime(&curtime);
 
-       default :
-         break; /* anti-compiler-warning-code */
-    }
+    if (curdate->tm_wday == 0 || curdate->tm_wday == 6)
+      job->hold_until = curtime;
+    else
+      job->hold_until = curtime +
+                        (((5 - curdate->tm_wday) * 24 +
+                          (17 - curdate->tm_hour)) * 60 + 59 -
+                          curdate->tm_min) * 60 + 60 - curdate->tm_sec;
   }
+  else if (sscanf(when, "%d:%d:%d", &hour, &minute, &second) >= 2)
+  {
+   /*
+    * Hold to specified GMT time (HH:MM or HH:MM:SS)...
+    */
 
-  return (bytes);
+    curtime = time(NULL);
+    curdate = gmtime(&curtime);
+
+    job->hold_until = curtime +
+                      ((hour - curdate->tm_hour) * 60 + minute -
+                      curdate->tm_min) * 60 + second - curdate->tm_sec;
+
+   /*
+    * Hold until next day as needed...
+    */
+
+    if (job->hold_until < curtime)
+      job->hold_until += 24 * 60 * 60;
+  }
+
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSetJobHoldUntil: hold_until=%d",
+                  (int)job->hold_until);
 }
 
 
 /*
- * 'load_job_cache()' - Load jobs from the job.cache file.
+ * 'cupsdSetJobPriority()' - Set the priority of a job, moving it up/down in
+ *                           the list as needed.
  */
 
-static void
-load_job_cache(const char *filename)   /* I - job.cache filename */
+void
+cupsdSetJobPriority(
+    cupsd_job_t *job,                  /* I - Job ID */
+    int         priority)              /* I - New priority (0 to 100) */
 {
-  cups_file_t  *fp;                    /* job.cache file */
-  char         line[1024],             /* Line buffer */
-               *value;                 /* Value on line */
-  int          linenum;                /* Line number in file */
-  cupsd_job_t  *job;                   /* Current job */
-  int          jobid;                  /* Job ID */
-  char         jobfile[1024];          /* Job filename */
+  ipp_attribute_t      *attr;          /* Job attribute */
 
 
  /*
-  * Open the job.cache file...
+  * Don't change completed jobs...
   */
 
-  if ((fp = cupsFileOpen(filename, "r")) == NULL)
-  {
-    if (errno != ENOENT)
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                      "Unable to open job cache file \"%s\": %s",
-                      filename, strerror(errno));
-
-    load_request_root();
-
+  if (job->state_value >= IPP_JOB_PROCESSING)
     return;
-  }
 
  /*
-  * Read entries from the job cache file and create jobs as needed.
+  * Set the new priority and re-add the job into the active list...
   */
 
-  cupsdLogMessage(CUPSD_LOG_INFO, "Loading job cache file \"%s\"...",
-                  filename);
+  cupsArrayRemove(ActiveJobs, job);
 
-  linenum = 0;
-  job     = NULL;
+  job->priority = priority;
 
-  while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
-  {
-    if (!strcasecmp(line, "NextJobId"))
-    {
-      if (value)
-        NextJobId = atoi(value);
-    }
-    else if (!strcasecmp(line, "<Job"))
-    {
-      if (job)
-      {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Missing </Job> directive on line %d!",
-                       linenum);
-        continue;
-      }
+  if ((attr = ippFindAttribute(job->attrs, "job-priority",
+                               IPP_TAG_INTEGER)) != NULL)
+    attr->values[0].integer = priority;
+  else
+    ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-priority",
+                  priority);
 
-      if (!value)
-      {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Missing job ID on line %d!", linenum);
-       continue;
-      }
+  cupsArrayAdd(ActiveJobs, job);
 
-      jobid = atoi(value);
+  job->dirty = 1;
+  cupsdMarkDirty(CUPSD_DIRTY_JOBS);
+}
 
-      if (jobid < 1)
-      {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad job ID %d on line %d!", jobid,
-                       linenum);
-        continue;
-      }
 
-      snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, jobid);
-      if (access(jobfile, 0))
-      {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Files have gone away!",
-                       jobid);
-        continue;
-      }
+/*
+ * 'cupsdSetJobState()' - Set the state of the specified print job.
+ */
 
-      job = calloc(1, sizeof(cupsd_job_t));
-      if (!job)
-      {
-        cupsdLogMessage(CUPSD_LOG_EMERG,
-                       "[Job %d] Unable to allocate memory for job!", jobid);
-        break;
-      }
+void
+cupsdSetJobState(
+    cupsd_job_t       *job,            /* I - Job to cancel */
+    ipp_jstate_t      newstate,                /* I - New job state */
+    cupsd_jobaction_t action,          /* I - Action to take */
+    const char        *message,                /* I - Message to log */
+    ...)                               /* I - Additional arguments as needed */
+{
+  int                  i;              /* Looping var */
+  ipp_jstate_t         oldstate;       /* Old state */
+  char                 filename[1024]; /* Job filename */
+  ipp_attribute_t      *attr;          /* Job attribute */
 
-      job->id              = jobid;
-      job->back_pipes[0]   = -1;
-      job->back_pipes[1]   = -1;
-      job->print_pipes[0]  = -1;
-      job->print_pipes[1]  = -1;
-      job->side_pipes[0]   = -1;
-      job->side_pipes[1]   = -1;
-      job->status_pipes[0] = -1;
-      job->status_pipes[1] = -1;
 
-      cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Loading from cache...", job->id);
-    }
-    else if (!job)
-    {
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                     "Missing <Job #> directive on line %d!", linenum);
-      continue;
-    }
-    else if (!strcasecmp(line, "</Job>"))
-    {
-      cupsArrayAdd(Jobs, job);
+  cupsdLogMessage(CUPSD_LOG_DEBUG2,
+                  "cupsdSetJobState(job=%p(%d), state=%d, newstate=%d, "
+                 "action=%d, message=\"%s\")", job, job->id, job->state_value,
+                 newstate, action, message ? message : "(null)");
 
-      if (job->state_value <= IPP_JOB_STOPPED)
-      {
-        cupsArrayAdd(ActiveJobs, job);
-       cupsdLoadJob(job);
-      }
 
-      job = NULL;
-    }
-    else if (!value)
-    {
-      cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d!", linenum);
-      continue;
-    }
-    else if (!strcasecmp(line, "State"))
-    {
-      job->state_value = (ipp_jstate_t)atoi(value);
-
-      if (job->state_value < IPP_JOB_PENDING)
-        job->state_value = IPP_JOB_PENDING;
-      else if (job->state_value > IPP_JOB_COMPLETED)
-        job->state_value = IPP_JOB_COMPLETED;
-    }
-    else if (!strcasecmp(line, "Priority"))
-    {
-      job->priority = atoi(value);
-    }
-    else if (!strcasecmp(line, "Username"))
-    {
-      cupsdSetString(&job->username, value);
-    }
-    else if (!strcasecmp(line, "Destination"))
-    {
-      cupsdSetString(&job->dest, value);
-    }
-    else if (!strcasecmp(line, "DestType"))
-    {
-      job->dtype = (cups_ptype_t)atoi(value);
-    }
-    else if (!strcasecmp(line, "NumFiles"))
-    {
-      job->num_files = atoi(value);
-
-      if (job->num_files < 0)
-      {
-       cupsdLogMessage(CUPSD_LOG_ERROR, "Bad NumFiles value %d on line %d!",
-                       job->num_files, linenum);
-        job->num_files = 0;
-       continue;
-      }
-
-      if (job->num_files > 0)
-      {
-        snprintf(jobfile, sizeof(jobfile), "%s/d%05d-001", RequestRoot,
-                job->id);
-        if (access(jobfile, 0))
-       {
-         cupsdLogMessage(CUPSD_LOG_INFO,
-                         "[Job %d] Data files have gone away!", job->id);
-          job->num_files = 0;
-         continue;
-       }
+ /*
+  * Make sure we have the job attributes...
+  */
 
-        job->filetypes    = calloc(job->num_files, sizeof(mime_type_t *));
-       job->compressions = calloc(job->num_files, sizeof(int));
+  if (!cupsdLoadJob(job))
+    return;
 
-        if (!job->filetypes || !job->compressions)
-       {
-         cupsdLogMessage(CUPSD_LOG_EMERG,
-                         "[Job %d] Unable to allocate memory for %d files!",
-                         job->id, job->num_files);
-          break;
-       }
-      }
-    }
-    else if (!strcasecmp(line, "File"))
-    {
-      int      number,                 /* File number */
-               compression;            /* Compression value */
-      char     super[MIME_MAX_SUPER],  /* MIME super type */
-               type[MIME_MAX_TYPE];    /* MIME type */
+ /*
+  * Don't do anything if the state is unchanged and we aren't purging the
+  * job...
+  */
 
+  oldstate = job->state_value;
+  if (newstate == oldstate && action != CUPSD_JOB_PURGE)
+    return;
 
-      if (sscanf(value, "%d%*[ \t]%15[^/]/%255s%d", &number, super, type,
-                 &compression) != 4)
-      {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File on line %d!", linenum);
-       continue;
-      }
+ /*
+  * Stop any processes that are working on the current job...
+  */
 
-      if (number < 1 || number > job->num_files)
-      {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File number %d on line %d!",
-                       number, linenum);
-        continue;
-      }
+  if (oldstate == IPP_JOB_PROCESSING)
+    stop_job(job, action);
 
-      number --;
+ /*
+  * Set the new job state...
+  */
 
-      job->compressions[number] = compression;
-      job->filetypes[number]    = mimeType(MimeDatabase, super, type);
+  job->state->values[0].integer = newstate;
+  job->state_value              = newstate;
 
-      if (!job->filetypes[number])
-      {
+  switch (newstate)
+  {
+    case IPP_JOB_PENDING :
        /*
-        * If the original MIME type is unknown, auto-type it!
+       * Update job-hold-until as needed...
        */
 
-        cupsdLogMessage(CUPSD_LOG_ERROR,
-                       "[Job %d] Unknown MIME type %s/%s for file %d!",
-                       job->id, super, type, number + 1);
+       if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
+                                    IPP_TAG_KEYWORD)) == NULL)
+         attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
 
-        snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot,
-                job->id, number + 1);
-        job->filetypes[number] = mimeFileType(MimeDatabase, jobfile, NULL,
-                                             job->compressions + number);
+       if (attr)
+       {
+         attr->value_tag = IPP_TAG_KEYWORD;
+         cupsdSetString(&(attr->values[0].string.text), "no-hold");
+       }
 
-       /*
-        * If that didn't work, assume it is raw...
-       */
+    default :
+       break;
 
-        if (!job->filetypes[number])
-         job->filetypes[number] = mimeType(MimeDatabase, "application",
-                                           "vnd.cups-raw");
-      }
-    }
-    else
-      cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown %s directive on line %d!",
-                      line, linenum);
+    case IPP_JOB_ABORTED :
+    case IPP_JOB_CANCELED :
+    case IPP_JOB_COMPLETED :
+       set_time(job, "time-at-completed");
+        break;
   }
 
-  cupsFileClose(fp);
-}
+ /*
+  * Log message as needed...
+  */
 
+  if (message)
+  {
+    char       buffer[2048];           /* Message buffer */
+    va_list    ap;                     /* Pointer to additional arguments */
 
-/*
- * 'load_next_job_id()' - Load the NextJobId value from the job.cache file.
- */
+    va_start(ap, message);
+    vsnprintf(buffer, sizeof(buffer), message, ap);
+    va_end(ap);
 
-static void
-load_next_job_id(const char *filename) /* I - job.cache filename */
-{
-  cups_file_t  *fp;                    /* job.cache file */
-  char         line[1024],             /* Line buffer */
-               *value;                 /* Value on line */
-  int          linenum;                /* Line number in file */
-  int          next_job_id;            /* NextJobId value from line */
+    if (newstate > IPP_JOB_STOPPED)
+      cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, "%s", buffer);
+    else
+      cupsdAddEvent(CUPSD_EVENT_JOB_STATE, job->printer, job, "%s", buffer);
 
+    if (newstate == IPP_JOB_STOPPED || newstate == IPP_JOB_ABORTED)
+      cupsdLogJob(job, CUPSD_LOG_ERROR, "%s", buffer);
+    else
+      cupsdLogJob(job, CUPSD_LOG_INFO, "%s", buffer);
+  }
 
  /*
-  * Read the NextJobId directive from the job.cache file and use
-  * the value (if any).
+  * Handle post-state-change actions...
   */
 
-  if ((fp = cupsFileOpen(filename, "r")) == NULL)
+  switch (newstate)
   {
-    if (errno != ENOENT)
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                      "Unable to open job cache file \"%s\": %s",
-                      filename, strerror(errno));
+    case IPP_JOB_PROCESSING :
+       /*
+        * Add the job to the "printing" list...
+       */
 
-    return;
-  }
+        if (!cupsArrayFind(PrintingJobs, job))
+         cupsArrayAdd(PrintingJobs, job);
 
-  cupsdLogMessage(CUPSD_LOG_INFO,
-                  "Loading NextJobId from job cache file \"%s\"...", filename);
+       /*
+       * Set the processing time...
+       */
 
-  linenum = 0;
+       set_time(job, "time-at-processing");
 
-  while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
-  {
-    if (!strcasecmp(line, "NextJobId"))
-    {
-      if (value)
-      {
-        next_job_id = atoi(value);
+    case IPP_JOB_PENDING :
+    case IPP_JOB_HELD :
+    case IPP_JOB_STOPPED :
+       /*
+        * Make sure the job is in the active list...
+       */
 
-        if (next_job_id > NextJobId)
-         NextJobId = next_job_id;
-      }
-      break;
-    }
-  }
+        if (!cupsArrayFind(ActiveJobs, job))
+         cupsArrayAdd(ActiveJobs, job);
 
-  cupsFileClose(fp);
-}
+       /*
+       * Save the job state to disk...
+       */
 
+       job->dirty = 1;
+       cupsdMarkDirty(CUPSD_DIRTY_JOBS);
+        break;
 
-/*
- * 'load_request_root()' - Load jobs from the RequestRoot directory.
- */
+    case IPP_JOB_ABORTED :
+    case IPP_JOB_CANCELED :
+    case IPP_JOB_COMPLETED :
+        if (newstate == IPP_JOB_CANCELED)
+       {
+        /*
+         * Remove the job from the active list if there are no processes still
+         * running for it...
+         */
 
-static void
-load_request_root(void)
-{
-  cups_dir_t           *dir;           /* Directory */
-  cups_dentry_t                *dent;          /* Directory entry */
-  cupsd_job_t          *job;           /* New job */
+         for (i = 0; job->filters[i] < 0; i++);
 
+         if (!job->filters[i] && job->backend <= 0)
+           cupsArrayRemove(ActiveJobs, job);
+       }
+       else
+       {
+        /*
+         * Otherwise just remove the job from the active list immediately...
+         */
 
- /*
-  * Open the requests directory...
-  */
+         cupsArrayRemove(ActiveJobs, job);
+       }
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG, "Scanning %s for jobs...", RequestRoot);
+       /*
+        * Expire job subscriptions since the job is now "completed"...
+       */
 
-  if ((dir = cupsDirOpen(RequestRoot)) == NULL)
-  {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                    "Unable to open spool directory \"%s\": %s",
-                    RequestRoot, strerror(errno));
-    return;
-  }
+        cupsdExpireSubscriptions(NULL, job);
 
- /*
-  * Read all the c##### files...
-  */
+#ifdef __APPLE__
+       /*
+       * If we are going to sleep and the PrintingJobs count is now 0, allow the
+       * sleep to happen immediately...
+       */
 
-  while ((dent = cupsDirRead(dir)) != NULL)
-    if (strlen(dent->filename) >= 6 && dent->filename[0] == 'c')
-    {
-     /*
-      * Allocate memory for the job...
-      */
+       if (Sleeping && cupsArrayCount(PrintingJobs) == 0)
+         cupsdAllowSleep();
+#endif /* __APPLE__ */
 
-      if ((job = calloc(sizeof(cupsd_job_t), 1)) == NULL)
-      {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Ran out of memory for jobs!");
-       cupsDirClose(dir);
-       return;
-      }
+       /*
+       * Remove any authentication data...
+       */
 
-     /*
-      * Assign the job ID...
-      */
+       snprintf(filename, sizeof(filename), "%s/a%05d", RequestRoot, job->id);
+       if (cupsdRemoveFile(filename) && errno != ENOENT)
+         cupsdLogMessage(CUPSD_LOG_ERROR,
+                         "Unable to remove authentication cache: %s",
+                         strerror(errno));
 
-      job->id              = atoi(dent->filename + 1);
-      job->back_pipes[0]   = -1;
-      job->back_pipes[1]   = -1;
-      job->print_pipes[0]  = -1;
-      job->print_pipes[1]  = -1;
-      job->side_pipes[0]   = -1;
-      job->side_pipes[1]   = -1;
-      job->status_pipes[0] = -1;
-      job->status_pipes[1] = -1;
+       for (i = 0;
+            i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0]));
+            i ++)
+         cupsdClearString(job->auth_env + i);
 
-      if (job->id >= NextJobId)
-        NextJobId = job->id + 1;
+       cupsdClearString(&job->auth_uid);
 
-     /*
-      * Load the job...
-      */
+       /*
+       * Remove the print file for good if we aren't preserving jobs or
+       * files...
+       */
 
-      cupsdLoadJob(job);
+       if (!JobHistory || !JobFiles || action == CUPSD_JOB_PURGE)
+       {
+         for (i = 1; i <= job->num_files; i ++)
+         {
+           snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
+                    job->id, i);
+           if (Classification)
+             cupsdRemoveFile(filename);
+           else
+             unlink(filename);
+         }
 
-     /*
-      * Insert the job into the array, sorting by job priority and ID...
-      */
+         if (job->num_files > 0)
+         {
+           free(job->filetypes);
+           free(job->compressions);
 
-      cupsArrayAdd(Jobs, job);
+           job->num_files    = 0;
+           job->filetypes    = NULL;
+           job->compressions = NULL;
+         }
+       }
 
-      if (job->state_value <= IPP_JOB_STOPPED)
-        cupsArrayAdd(ActiveJobs, job);
-      else
-        unload_job(job);
-    }
+       if (JobHistory && action != CUPSD_JOB_PURGE)
+       {
+        /*
+         * Save job state info...
+         */
 
-  cupsDirClose(dir);
+         job->dirty = 1;
+         cupsdMarkDirty(CUPSD_DIRTY_JOBS);
+       }
+       else if (!job->printer)
+       {
+        /*
+         * Delete the job immediately if not actively printing...
+         */
+
+         cupsdDeleteJob(job, CUPSD_JOB_PURGE);
+         job = NULL;
+       }
+       break;
+  }
+
+ /*
+  * Finalize the job immediately if we forced things...
+  */
+
+  if (action >= CUPSD_JOB_FORCE && job && job->printer)
+    finalize_job(job, 0);
+
+ /*
+  * Update the server "busy" state...
+  */
+
+  cupsdSetBusyState();
 }
 
 
 /*
- * 'set_time()' - Set one of the "time-at-xyz" attributes...
+ * 'cupsdStopAllJobs()' - Stop all print jobs.
  */
 
-static void
-set_time(cupsd_job_t *job,             /* I - Job to update */
-         const char  *name)            /* I - Name of attribute */
+void
+cupsdStopAllJobs(
+    cupsd_jobaction_t action,          /* I - Action */
+    int               kill_delay)      /* I - Number of seconds before we kill */
 {
-  ipp_attribute_t      *attr;          /* Time attribute */
+  cupsd_job_t  *job;                   /* Current job */
 
 
-  if ((attr = ippFindAttribute(job->attrs, name, IPP_TAG_ZERO)) != NULL)
+  DEBUG_puts("cupsdStopAllJobs()");
+
+  for (job = (cupsd_job_t *)cupsArrayFirst(PrintingJobs);
+       job;
+       job = (cupsd_job_t *)cupsArrayNext(PrintingJobs))
   {
-    attr->value_tag         = IPP_TAG_INTEGER;
-    attr->values[0].integer = time(NULL);
+    if (kill_delay)
+      job->kill_time = time(NULL) + kill_delay;
+
+    cupsdSetJobState(job, IPP_JOB_PENDING, action, NULL);
   }
 }
 
 
 /*
- * 'set_hold_until()' - Set the hold time and update job-hold-until attribute...
+ * 'cupsdUnloadCompletedJobs()' - Flush completed job history from memory.
  */
 
-static void
-set_hold_until(cupsd_job_t *job,       /* I - Job to update */
-              time_t      holdtime)    /* I - Hold until time */
+void
+cupsdUnloadCompletedJobs(void)
 {
-  ipp_attribute_t      *attr;          /* job-hold-until attribute */
-  struct tm            *holddate;      /* Hold date */
-  char                 holdstr[64];    /* Hold time */
+  cupsd_job_t  *job;                   /* Current job */
+  time_t       expire;                 /* Expiration time */
 
 
- /*
-  * Set the hold_until value and hold the job...
-  */
+  expire = time(NULL) - 60;
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG, "set_hold_until: hold_until = %d",
-                  (int)holdtime);
+  for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
+       job;
+       job = (cupsd_job_t *)cupsArrayNext(Jobs))
+    if (job->attrs && job->state_value >= IPP_JOB_STOPPED && !job->printer &&
+        job->access_time < expire)
+    {
+      if (job->dirty)
+        cupsdSaveJob(job);
 
-  job->state->values[0].integer = IPP_JOB_HELD;
-  job->state_value              = IPP_JOB_HELD;
-  job->hold_until               = holdtime;
+      unload_job(job);
+    }
+}
 
- /*
-  * Update the job-hold-until attribute with a string representing GMT
-  * time (HH:MM:SS)...
-  */
 
-  holddate = gmtime(&holdtime);
-  snprintf(holdstr, sizeof(holdstr), "%d:%d:%d", holddate->tm_hour,
-          holddate->tm_min, holddate->tm_sec);
+/*
+ * 'compare_active_jobs()' - Compare the job IDs and priorities of two jobs.
+ */
 
-  if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
-                               IPP_TAG_KEYWORD)) == NULL)
-    attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
+static int                             /* O - Difference */
+compare_active_jobs(void *first,       /* I - First job */
+                    void *second,      /* I - Second job */
+                   void *data)         /* I - App data (not used) */
+{
+  int  diff;                           /* Difference */
 
- /*
-  * Either add the attribute or update the value of the existing one
-  */
 
-  if (attr == NULL)
-    attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD,
-                        "job-hold-until", NULL, holdstr);
-  else
-    cupsdSetString(&attr->values[0].string.text, holdstr);
+  (void)data;
 
-  cupsdSaveJob(job);
+  if ((diff = ((cupsd_job_t *)second)->priority -
+              ((cupsd_job_t *)first)->priority) != 0)
+    return (diff);
+  else
+    return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
 }
 
 
 /*
- * 'start_job()' - Start a print job.
+ * 'compare_jobs()' - Compare the job IDs of two jobs.
  */
 
-static void
-start_job(cupsd_job_t     *job,                /* I - Job ID */
-          cupsd_printer_t *printer)    /* I - Printer to print job */
+static int                             /* O - Difference */
+compare_jobs(void *first,              /* I - First job */
+             void *second,             /* I - Second job */
+            void *data)                /* I - App data (not used) */
 {
-  int                  i;              /* Looping var */
-  int                  slot;           /* Pipe slot */
-  cups_array_t         *filters,       /* Filters for job */
-                       *prefilters;    /* Filters with prefilters */
-  mime_filter_t                *filter,        /* Current filter */
-                       *prefilter,     /* Prefilter */
-                       port_monitor;   /* Port monitor filter */
-  char                 method[255],    /* Method for output */
-                       *optptr,        /* Pointer to options */
-                       *valptr;        /* Pointer in value string */
-  ipp_attribute_t      *attr;          /* Current attribute */
-  struct stat          backinfo;       /* Backend file information */
-  int                  backroot;       /* Run backend as root? */
-  int                  pid;            /* Process ID of new filter process */
-  int                  banner_page;    /* 1 if banner page, 0 otherwise */
-  int                  filterfds[2][2];/* Pipes used between filters */
-  int                  envc;           /* Number of environment variables */
-  char                 **argv,         /* Filter command-line arguments */
-                       sani_uri[1024], /* Sanitized DEVICE_URI env var */
-                       filename[1024], /* Job filename */
-                       command[1024],  /* Full path to command */
-                       jobid[255],     /* Job ID string */
-                       title[IPP_MAX_NAME],
-                                       /* Job title string */
-                       copies[255],    /* # copies string */
-                       *envp[MAX_ENV + 16],
-                                       /* Environment variables */
-                       charset[255],   /* CHARSET env variable */
-                       class_name[255],/* CLASS env variable */
-                       classification[1024],
-                                       /* CLASSIFICATION env variable */
-                       content_type[1024],
-                                       /* CONTENT_TYPE env variable */
-                       device_uri[1024],
-                                       /* DEVICE_URI env variable */
-                       final_content_type[1024],
-                                       /* FINAL_CONTENT_TYPE env variable */
-                       lang[255],      /* LANG env variable */
-#ifdef __APPLE__
-                       apple_language[255],
-                                       /* APPLE_LANGUAGE env variable */
-#endif /* __APPLE__ */
-                       ppd[1024],      /* PPD env variable */
-                       printer_name[255],
-                                       /* PRINTER env variable */
-                       rip_max_cache[255];
-                                       /* RIP_MAX_CACHE env variable */
-  static char          *options = NULL;/* Full list of options */
-  static int           optlength = 0;  /* Length of option buffer */
+  (void)data;
 
+  return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
+}
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: file = %d/%d",
-                  job->id, job->current_file, job->num_files);
 
-  if (job->num_files == 0)
-  {
-    cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] No files, canceling job!",
-                    job->id);
+/*
+ * 'dump_job_history()' - Dump any debug messages for a job.
+ */
+
+static void
+dump_job_history(cupsd_job_t *job)     /* I - Job */
+{
+  int                  i,              /* Looping var */
+                       oldsize;        /* Current MaxLogSize */
+  struct tm            *date;          /* Date/time value */
+  cupsd_joblog_t       *message;       /* Current message */
+  char                 temp[2048],     /* Log message */
+                       *ptr,           /* Pointer into log message */
+                       start[256],     /* Start time */
+                       end[256];       /* End time */
+  cupsd_printer_t      *printer;       /* Printer for job */
 
-    cupsdCancelJob(job, 0, IPP_JOB_ABORTED);
-    return;
-  }
 
  /*
-  * Figure out what filters are required to convert from
-  * the source to the destination type...
+  * See if we have anything to dump...
   */
 
-  filters   = NULL;
-  job->cost = 0;
-
-  if (printer->raw)
-  {
-   /*
-    * Remote jobs and raw queues go directly to the printer without
-    * filtering...
-    */
-
-    cupsdLogMessage(CUPSD_LOG_DEBUG,
-                    "[Job %d] Sending job to queue tagged as raw...", job->id);
-
-    filters = NULL;
-  }
-  else
-  {
-   /*
-    * Local jobs get filtered...
-    */
+  if (!job->history)
+    return;
 
-    filters = mimeFilter(MimeDatabase, job->filetypes[job->current_file],
-                         printer->filetype, &(job->cost));
+ /*
+  * Disable log rotation temporarily...
+  */
 
-    if (!filters)
-    {
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                      "[Job %d] Unable to convert file %d to printable format!",
-                     job->current_file, job->id);
-      cupsdLogMessage(CUPSD_LOG_INFO,
-                      "Hint: Do you have Ghostscript installed?");
+  oldsize    = MaxLogSize;
+  MaxLogSize = 0;
 
-      if (LogLevel < CUPSD_LOG_DEBUG)
-        cupsdLogMessage(CUPSD_LOG_INFO,
-                       "Hint: Try setting the LogLevel to \"debug\".");
+ /*
+  * Copy the debug messages to the log...
+  */
 
-      job->current_file ++;
+  message = (cupsd_joblog_t *)cupsArrayFirst(job->history);
+  date = localtime(&(message->time));
+  strftime(start, sizeof(start), "%X", date);
 
-      if (job->current_file == job->num_files)
-        cupsdCancelJob(job, 0, IPP_JOB_ABORTED);
+  message = (cupsd_joblog_t *)cupsArrayLast(job->history);
+  date = localtime(&(message->time));
+  strftime(end, sizeof(end), "%X", date);
 
-      return;
-    }
+  snprintf(temp, sizeof(temp),
+           "[Job %d] The following messages were recorded from %s to %s",
+           job->id, start, end);
+  cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp);
 
-   /*
-    * Remove NULL ("-") filters...
-    */
+  for (message = (cupsd_joblog_t *)cupsArrayFirst(job->history);
+       message;
+       message = (cupsd_joblog_t *)cupsArrayNext(job->history))
+    cupsdWriteErrorLog(CUPSD_LOG_DEBUG, message->message);
 
-    for (filter = (mime_filter_t *)cupsArrayFirst(filters);
-         filter;
-        filter = (mime_filter_t *)cupsArrayNext(filters))
-      if (!strcmp(filter->filter, "-"))
-        cupsArrayRemove(filters, filter);
+  snprintf(temp, sizeof(temp), "[Job %d] End of messages", job->id);
+  cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp);
 
-    if (cupsArrayCount(filters) == 0)
-    {
-      cupsArrayDelete(filters);
-      filters = NULL;
-    }
+ /*
+  * Log the printer state values...
+  */
 
-   /*
-    * If this printer has any pre-filters, insert the required pre-filter
-    * in the filters array...
-    */
+  if ((printer = job->printer) == NULL)
+    printer = cupsdFindDest(job->dest);
 
-    if (printer->prefiltertype && filters)
+  if (printer)
+  {
+    snprintf(temp, sizeof(temp), "[Job %d] printer-state=%d(%s)", job->id,
+             printer->state,
+            printer->state == IPP_PRINTER_IDLE ? "idle" :
+                printer->state == IPP_PRINTER_PROCESSING ? "processing" :
+                "stopped");
+    cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp);
+
+    snprintf(temp, sizeof(temp), "[Job %d] printer-state-message=\"%s\"",
+             job->id, printer->state_message);
+    cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp);
+
+    snprintf(temp, sizeof(temp), "[Job %d] printer-state-reasons=", job->id);
+    ptr = temp + strlen(temp);
+    if (printer->num_reasons == 0)
+      strlcpy(ptr, "none", sizeof(temp) - (ptr - temp));
+    else
     {
-      prefilters = cupsArrayNew(NULL, NULL);
-
-      for (filter = (mime_filter_t *)cupsArrayFirst(filters);
-          filter;
-          filter = (mime_filter_t *)cupsArrayNext(filters))
+      for (i = 0;
+           i < printer->num_reasons && ptr < (temp + sizeof(temp) - 2);
+           i ++)
       {
-       if ((prefilter = mimeFilterLookup(MimeDatabase, filter->src,
-                                         printer->prefiltertype)))
-       {
-         cupsArrayAdd(prefilters, prefilter);
-         job->cost += prefilter->cost;
-       }
+        if (i)
+         *ptr++ = ',';
 
-       cupsArrayAdd(prefilters, filter);
+       strlcpy(ptr, printer->reasons[i], sizeof(temp) - (ptr - temp));
+       ptr += strlen(ptr);
       }
-
-      cupsArrayDelete(filters);
-      filters = prefilters;
     }
+    cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp);
   }
 
  /*
-  * Set a minimum cost of 100 for all jobs so that FilterLimit
-  * works with raw queues and other low-cost paths.
+  * Restore log file rotation...
   */
 
-  if (job->cost < 100)
-    job->cost = 100;
+  MaxLogSize = oldsize;
 
  /*
-  * See if the filter cost is too high...
+  * Free all messages...
   */
 
-  if ((FilterLevel + job->cost) > FilterLimit && FilterLevel > 0 &&
-      FilterLimit > 0)
-  {
-   /*
-    * Don't print this job quite yet...
-    */
+  free_job_history(job);
+}
 
-    cupsArrayDelete(filters);
 
-    cupsdLogMessage(CUPSD_LOG_INFO,
-                    "[Job %d] Holding because filter limit has been reached.",
-                    job->id);
-    cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                    "[Job %d] start_job: file=%d, cost=%d, level=%d, limit=%d",
-                    job->id, job->current_file, job->cost, FilterLevel,
-                   FilterLimit);
-    return;
-  }
+/*
+ * 'free_job_history()' - Free any log history.
+ */
 
-  FilterLevel += job->cost;
+static void
+free_job_history(cupsd_job_t *job)     /* I - Job */
+{
+  char *message;                       /* Current message */
 
- /*
-  * Add decompression/raw filter as needed...
-  */
 
-  if ((!printer->raw && job->compressions[job->current_file]) ||
-      (!filters && !printer->remote &&
-       (job->num_files > 1 || !strncmp(printer->device_uri, "file:", 5))))
-  {
-   /*
-    * Add gziptoany filter to the front of the list...
-    */
+  if (!job->history)
+    return;
 
-    if (!filters)
-      filters = cupsArrayNew(NULL, NULL);
+  for (message = (char *)cupsArrayFirst(job->history);
+       message;
+       message = (char *)cupsArrayNext(job->history))
+    free(message);
 
-    if (!cupsArrayInsert(filters, &gziptoany_filter))
-    {
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                      "[Job %d] Unable to add decompression filter - %s",
-                     job->id, strerror(errno));
+  cupsArrayDelete(job->history);
+  job->history = NULL;
+}
 
-      cupsArrayDelete(filters);
 
-      job->current_file ++;
+/*
+ * 'finalize_job()' - Cleanup after job filter processes and support data.
+ */
 
-      if (job->current_file == job->num_files)
-        cupsdCancelJob(job, 0, IPP_JOB_ABORTED);
+static void
+finalize_job(cupsd_job_t *job,         /* I - Job */
+             int         set_job_state)        /* I - 1 = set the job state */
+{
+  ipp_pstate_t         printer_state;  /* New printer state value */
+  ipp_jstate_t         job_state;      /* New job state value */
+  const char           *message;       /* Message for job state */
+  char                 buffer[1024];   /* Buffer for formatted messages */
 
-      return;
-    }
-  }
+
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "finalize_job(job=%p(%d))", job, job->id);
 
  /*
-  * Add port monitor, if any...
+  * Clear the "connecting-to-device" reason, which is only valid when a printer
+  * is processing, along with any remote printing job state...
   */
 
-  if (printer->port_monitor)
-  {
-   /*
-    * Add port monitor to the end of the list...
-    */
+  cupsdSetPrinterReasons(job->printer, "-connecting-to-device,"
+                                      "cups-remote-pending,"
+                                      "cups-remote-pending-held,"
+                                      "cups-remote-processing,"
+                                      "cups-remote-stopped,"
+                                      "cups-remote-canceled,"
+                                      "cups-remote-aborted,"
+                                      "cups-remote-completed");
 
-    if (!filters)
-      filters = cupsArrayNew(NULL, NULL);
+ /*
+  * Similarly, clear the "offline-report" reason for non-USB devices since we
+  * rarely have current information for network devices...
+  */
 
-    if (!cupsArrayAdd(filters, &port_monitor))
-    {
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                      "[Job %d] Unable to add port monitor - %s",
-                      job->id, strerror(errno));
+  if (strncmp(job->printer->device_uri, "usb:", 4))
+    cupsdSetPrinterReasons(job->printer, "-offline-report");
 
-      cupsArrayDelete(filters);
+ /*
+  * Free the security profile...
+  */
 
-      job->current_file ++;
+  cupsdDestroyProfile(job->profile);
+  job->profile = NULL;
 
-      if (job->current_file == job->num_files)
-        cupsdCancelJob(job, 0, IPP_JOB_ABORTED);
+ /*
+  * Clear the unresponsive job watchdog timer...
+  */
 
-      return;
-    }
+  job->kill_time = 0;
 
-    snprintf(port_monitor.filter, sizeof(port_monitor.filter),
-             "%s/monitor/%s", ServerBin, printer->port_monitor);
-  }
+ /*
+  * Close pipes and status buffer...
+  */
+
+  cupsdClosePipe(job->print_pipes);
+  cupsdClosePipe(job->back_pipes);
+  cupsdClosePipe(job->side_pipes);
+
+  cupsdRemoveSelect(job->status_pipes[0]);
+  cupsdClosePipe(job->status_pipes);
+  cupsdStatBufDelete(job->status_buffer);
+  job->status_buffer = NULL;
 
  /*
-  * Update the printer and job state to "processing"...
+  * Process the exit status...
   */
 
-  job->state->values[0].integer = IPP_JOB_PROCESSING;
-  job->state_value              = IPP_JOB_PROCESSING;
+  if (job->printer->state == IPP_PRINTER_PROCESSING)
+    printer_state = IPP_PRINTER_IDLE;
+  else
+    printer_state = job->printer->state;
 
-  job->status  = 0;
-  job->printer = printer;
-  printer->job = job;
+  switch (job_state = job->state_value)
+  {
+    case IPP_JOB_PENDING :
+        message = "Job paused.";
+       break;
 
-  cupsdSetPrinterState(printer, IPP_PRINTER_PROCESSING, 0);
+    case IPP_JOB_HELD :
+        message = "Job held.";
+       break;
+
+    default :
+    case IPP_JOB_PROCESSING :
+    case IPP_JOB_COMPLETED :
+       job_state = IPP_JOB_COMPLETED;
+       message   = "Job completed.";
+        break;
+
+    case IPP_JOB_STOPPED :
+        message = "Job stopped.";
+       break;
+
+    case IPP_JOB_CANCELED :
+        message = "Job canceled.";
+       break;
+
+    case IPP_JOB_ABORTED :
+        message = "Job aborted.";
+       break;
+  }
 
-  if (job->current_file == 0)
+  if (job->status < 0)
   {
    /*
-    * Set the processing time...
+    * Backend had errors...
     */
 
-    set_time(job, "time-at-processing");
+    int exit_code;                     /* Exit code from backend */
 
    /*
-    * Create the backchannel pipes and make them non-blocking...
+    * Convert the status to an exit code.  Due to the way the W* macros are
+    * implemented on MacOS X (bug?), we have to store the exit status in a
+    * variable first and then convert...
     */
 
-    cupsdOpenPipe(job->back_pipes);
-
-    fcntl(job->back_pipes[0], F_SETFL,
-          fcntl(job->back_pipes[0], F_GETFL) | O_NONBLOCK);
+    exit_code = -job->status;
+    if (WIFEXITED(exit_code))
+      exit_code = WEXITSTATUS(exit_code);
+    else
+      exit_code = job->status;
 
-    fcntl(job->back_pipes[1], F_SETFL,
-          fcntl(job->back_pipes[1], F_GETFL) | O_NONBLOCK);
+    cupsdLogJob(job, CUPSD_LOG_INFO, "Backend returned status %d (%s)",
+               exit_code,
+               exit_code == CUPS_BACKEND_FAILED ? "failed" :
+                   exit_code == CUPS_BACKEND_AUTH_REQUIRED ?
+                       "authentication required" :
+                   exit_code == CUPS_BACKEND_HOLD ? "hold job" :
+                   exit_code == CUPS_BACKEND_STOP ? "stop printer" :
+                   exit_code == CUPS_BACKEND_CANCEL ? "cancel job" :
+                   exit_code < 0 ? "crashed" : "unknown");
 
    /*
-    * Create the side-channel pipes and make them non-blocking...
+    * Do what needs to be done...
     */
 
-    socketpair(AF_LOCAL, SOCK_STREAM, 0, job->side_pipes);
+    switch (exit_code)
+    {
+      default :
+      case CUPS_BACKEND_FAILED :
+         /*
+         * Backend failure, use the error-policy to determine how to
+         * act...
+         */
+
+          if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))
+         {
+          /*
+           * Queued on a class - mark the job as pending and we'll retry on
+           * another printer...
+           */
+
+            if (job_state == IPP_JOB_COMPLETED)
+           {
+             job_state = IPP_JOB_PENDING;
+             message   = "Retrying job on another printer.";
+           }
+          }
+         else if (!strcmp(job->printer->error_policy, "retry-current-job"))
+         {
+          /*
+           * The error policy is "retry-current-job" - mark the job as pending
+           * and we'll retry on the same printer...
+           */
+
+            if (job_state == IPP_JOB_COMPLETED)
+           {
+             job_state = IPP_JOB_PENDING;
+             message   = "Retrying job on same printer.";
+           }
+          }
+         else if ((job->printer->type & CUPS_PRINTER_FAX) ||
+                  !strcmp(job->printer->error_policy, "retry-job"))
+         {
+            if (job_state == IPP_JOB_COMPLETED)
+           {
+            /*
+             * The job was queued on a fax or the error policy is "retry-job" -
+             * hold the job if the number of retries is less than the
+             * JobRetryLimit, otherwise abort the job.
+             */
+
+             job->tries ++;
+
+             if (job->tries > JobRetryLimit && JobRetryLimit > 0)
+             {
+              /*
+               * Too many tries...
+               */
+
+               snprintf(buffer, sizeof(buffer),
+                        "Job aborted after %d unsuccessful attempts.",
+                        JobRetryLimit);
+               job_state = IPP_JOB_ABORTED;
+               message   = buffer;
+             }
+             else
+             {
+              /*
+               * Try again in N seconds...
+               */
+
+               snprintf(buffer, sizeof(buffer),
+                        "Job held for %d seconds since it could not be sent.",
+                        JobRetryInterval);
+
+               job->hold_until = time(NULL) + JobRetryInterval;
+               job_state       = IPP_JOB_HELD;
+               message         = buffer;
+             }
+            }
+         }
+         else if (!strcmp(job->printer->error_policy, "abort-job") &&
+                  job_state == IPP_JOB_COMPLETED)
+         {
+           job_state = IPP_JOB_ABORTED;
+           message   = "Job aborted due to backend errors; please consult "
+                       "the error_log file for details.";
+         }
+         else if (job->state_value == IPP_JOB_PROCESSING)
+          {
+            job_state     = IPP_JOB_PENDING;
+           printer_state = IPP_PRINTER_STOPPED;
+           message       = "Printer stopped due to backend errors; please "
+                           "consult the error_log file for details.";
+         }
+          break;
+
+      case CUPS_BACKEND_CANCEL :
+         /*
+         * Abort the job...
+         */
+
+         if (job_state == IPP_JOB_COMPLETED)
+         {
+           job_state = IPP_JOB_ABORTED;
+           message   = "Job aborted due to backend errors; please consult "
+                       "the error_log file for details.";
+         }
+          break;
+
+      case CUPS_BACKEND_HOLD :
+         if (job_state == IPP_JOB_COMPLETED)
+         {
+          /*
+           * Hold the job...
+           */
+
+           cupsdSetJobHoldUntil(job, "indefinite", 1);
+
+           job_state = IPP_JOB_HELD;
+           message   = "Job held indefinitely due to backend errors; please "
+                       "consult the error_log file for details.";
+          }
+          break;
+
+      case CUPS_BACKEND_STOP :
+         /*
+         * Stop the printer...
+         */
+
+         printer_state = IPP_PRINTER_STOPPED;
+         message       = "Printer stopped due to backend errors; please "
+                         "consult the error_log file for details.";
+
+         if (job_state == IPP_JOB_COMPLETED)
+           job_state = IPP_JOB_PENDING;
+          break;
+
+      case CUPS_BACKEND_AUTH_REQUIRED :
+         /*
+         * Hold the job for authentication...
+         */
+
+         if (job_state == IPP_JOB_COMPLETED)
+         {
+           cupsdSetJobHoldUntil(job, "auth-info-required", 1);
+
+           job_state = IPP_JOB_HELD;
+           message   = "Job held for authentication.";
+          }
+          break;
+
+      case CUPS_BACKEND_RETRY :
+         if (job_state == IPP_JOB_COMPLETED)
+         {
+          /*
+           * Hold the job if the number of retries is less than the
+           * JobRetryLimit, otherwise abort the job.
+           */
+
+           job->tries ++;
+
+           if (job->tries > JobRetryLimit && JobRetryLimit > 0)
+           {
+            /*
+             * Too many tries...
+             */
+
+             snprintf(buffer, sizeof(buffer),
+                      "Job aborted after %d unsuccessful attempts.",
+                      JobRetryLimit);
+             job_state = IPP_JOB_ABORTED;
+             message   = buffer;
+           }
+           else
+           {
+            /*
+             * Try again in N seconds...
+             */
+
+             snprintf(buffer, sizeof(buffer),
+                      "Job held for %d seconds since it could not be sent.",
+                      JobRetryInterval);
+
+             job->hold_until = time(NULL) + JobRetryInterval;
+             job_state       = IPP_JOB_HELD;
+             message         = buffer;
+           }
+         }
+          break;
 
-    fcntl(job->side_pipes[0], F_SETFL,
-          fcntl(job->side_pipes[0], F_GETFL) | O_NONBLOCK);
+      case CUPS_BACKEND_RETRY_CURRENT :
+        /*
+         * Mark the job as pending and retry on the same printer...
+         */
+
+         if (job_state == IPP_JOB_COMPLETED)
+         {
+           job_state = IPP_JOB_PENDING;
+           message   = "Retrying job on same printer.";
+         }
+          break;
+    }
+  }
+  else if (job->status > 0)
+  {
+   /*
+    * Filter had errors; stop job...
+    */
 
-    fcntl(job->side_pipes[1], F_SETFL,
-          fcntl(job->side_pipes[1], F_GETFL) | O_NONBLOCK);
+    if (job_state == IPP_JOB_COMPLETED)
+    {
+      job_state = IPP_JOB_STOPPED;
+      message   = "Job stopped due to filter errors; please consult the "
+                 "error_log file for details.";
+    }
   }
 
  /*
-  * Determine if we are printing a banner page or not...
+  * Update the printer and job state.
   */
 
-  if (job->job_sheets == NULL)
+  if (set_job_state && job_state != job->state_value)
+    cupsdSetJobState(job, job_state, CUPSD_JOB_DEFAULT, "%s", message);
+
+  cupsdSetPrinterState(job->printer, printer_state,
+                       printer_state == IPP_PRINTER_STOPPED);
+  update_job_attrs(job, 0);
+
+  if (job->history)
   {
-    cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] No job-sheets attribute.",
-                    job->id);
-    if ((job->job_sheets =
-         ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_ZERO)) != NULL)
-      cupsdLogMessage(CUPSD_LOG_DEBUG,
-                      "[Job %d] ... but someone added one without setting "
-                     "job_sheets!", job->id);
+    if (job->status &&
+        (job->state_value == IPP_JOB_ABORTED ||
+         job->state_value == IPP_JOB_STOPPED))
+      dump_job_history(job);
+    else
+      free_job_history(job);
   }
-  else if (job->job_sheets->num_values == 1)
-    cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] job-sheets=%s", job->id,
-                    job->job_sheets->values[0].string.text);
-  else
-    cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] job-sheets=%s,%s", job->id,
-               job->job_sheets->values[0].string.text,
-               job->job_sheets->values[1].string.text);
-
-  if (printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT))
-    banner_page = 0;
-  else if (job->job_sheets == NULL)
-    banner_page = 0;
-  else if (strcasecmp(job->job_sheets->values[0].string.text, "none") != 0 &&
-          job->current_file == 0)
-    banner_page = 1;
-  else if (job->job_sheets->num_values > 1 &&
-          strcasecmp(job->job_sheets->values[1].string.text, "none") != 0 &&
-          job->current_file == (job->num_files - 1))
-    banner_page = 1;
-  else
-    banner_page = 0;
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] banner_page = %d", job->id,
-                  banner_page);
+  cupsArrayRemove(PrintingJobs, job);
 
  /*
-  * Building the options string is harder than it needs to be, but
-  * for the moment we need to pass strings for command-line args and
-  * not IPP attribute pointers... :)
-  *
-  * First allocate/reallocate the option buffer as needed...
+  * Clear the printer <-> job association...
   */
 
-  i = ipp_length(job->attrs);
+  job->printer->job = NULL;
+  job->printer      = NULL;
 
-  if (i > optlength)
-  {
-    if (optlength == 0)
-      optptr = malloc(i);
-    else
-      optptr = realloc(options, i);
+ /*
+  * Try printing another job...
+  */
 
-    if (optptr == NULL)
-    {
-      cupsdLogMessage(CUPSD_LOG_CRIT,
-                      "[Job %d] Unable to allocate %d bytes for option buffer!",
-                     job->id, i);
+  if (printer_state != IPP_PRINTER_STOPPED)
+    cupsdCheckJobs();
+}
 
-      cupsArrayDelete(filters);
 
-      FilterLevel -= job->cost;
+/*
+ * 'get_options()' - Get a string containing the job options.
+ */
 
-      cupsdCancelJob(job, 0, IPP_JOB_ABORTED);
-      return;
-    }
+static char *                          /* O - Options string */
+get_options(cupsd_job_t *job,          /* I - Job */
+            int         banner_page,   /* I - Printing a banner page? */
+           char        *copies,        /* I - Copies buffer */
+           size_t      copies_size,    /* I - Size of copies buffer */
+           char        *title,         /* I - Title buffer */
+           size_t      title_size)     /* I - Size of title buffer */
+{
+  int                  i;              /* Looping var */
+  size_t               newlength;      /* New option buffer length */
+  char                 *optptr,        /* Pointer to options */
+                       *valptr;        /* Pointer in value string */
+  ipp_attribute_t      *attr;          /* Current attribute */
+  _ppd_cache_t         *pc;            /* PPD cache and mapping data */
+  int                  num_pwgppds;    /* Number of PWG->PPD options */
+  cups_option_t                *pwgppds,       /* PWG->PPD options */
+                       *pwgppd,        /* Current PWG->PPD option */
+                       *preset;        /* Current preset option */
+  int                  print_color_mode,
+                                       /* Output mode (if any) */
+                       print_quality;  /* Print quality (if any) */
+  const char           *ppd;           /* PPD option choice */
+  int                  exact;          /* Did we get an exact match? */
+  static char          *options = NULL;/* Full list of options */
+  static size_t                optlength = 0;  /* Length of option buffer */
 
-    options   = optptr;
-    optlength = i;
-  }
 
  /*
-  * Now loop through the attributes and convert them to the textual
-  * representation used by the filters...
+  * Building the options string is harder than it needs to be, but for the
+  * moment we need to pass strings for command-line args and not IPP attribute
+  * pointers... :)
+  *
+  * First build an options array for any PWG->PPD mapped option/choice pairs.
   */
 
-  optptr  = options;
-  *optptr = '\0';
+  pc          = job->printer->pc;
+  num_pwgppds = 0;
+  pwgppds     = NULL;
+
+  if (pc &&
+      !ippFindAttribute(job->attrs,
+                        "com.apple.print.DocumentTicket.PMSpoolFormat",
+                       IPP_TAG_ZERO) &&
+      !ippFindAttribute(job->attrs, "APPrinterPreset", IPP_TAG_ZERO) &&
+      (ippFindAttribute(job->attrs, "output-mode", IPP_TAG_ZERO) ||
+       ippFindAttribute(job->attrs, "print-color-mode", IPP_TAG_ZERO) ||
+       ippFindAttribute(job->attrs, "print-quality", IPP_TAG_ZERO)))
+  {
+   /*
+    * Map output-mode and print-quality to a preset...
+    */
 
-  snprintf(title, sizeof(title), "%s-%d", printer->name, job->id);
-  strcpy(copies, "1");
+    if ((attr = ippFindAttribute(job->attrs, "print-color-mode",
+                                IPP_TAG_KEYWORD)) == NULL)
+      attr = ippFindAttribute(job->attrs, "output-mode", IPP_TAG_KEYWORD);
 
-  for (attr = job->attrs->attrs; attr != NULL; attr = attr->next)
-  {
-    if (!strcmp(attr->name, "copies") &&
-       attr->value_tag == IPP_TAG_INTEGER)
+    if (attr && !strcmp(attr->values[0].string.text, "monochrome"))
+      print_color_mode = _PWG_PRINT_COLOR_MODE_MONOCHROME;
+    else
+      print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR;
+
+    if ((attr = ippFindAttribute(job->attrs, "print-quality",
+                                IPP_TAG_ENUM)) != NULL &&
+       attr->values[0].integer >= IPP_QUALITY_DRAFT &&
+       attr->values[0].integer <= IPP_QUALITY_HIGH)
+      print_quality = attr->values[0].integer - IPP_QUALITY_DRAFT;
+    else
+      print_quality = _PWG_PRINT_QUALITY_NORMAL;
+
+    if (pc->num_presets[print_color_mode][print_quality] == 0)
     {
      /*
-      * Don't use the # copies attribute if we are printing the job sheets...
+      * Try to find a preset that works so that we maximize the chances of us
+      * getting a good print using IPP attributes.
       */
 
-      if (!banner_page)
-        sprintf(copies, "%d", attr->values[0].integer);
+      if (pc->num_presets[print_color_mode][_PWG_PRINT_QUALITY_NORMAL] > 0)
+        print_quality = _PWG_PRINT_QUALITY_NORMAL;
+      else if (pc->num_presets[_PWG_PRINT_COLOR_MODE_COLOR][print_quality] > 0)
+        print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR;
+      else
+      {
+        print_quality    = _PWG_PRINT_QUALITY_NORMAL;
+        print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR;
+      }
     }
-    else if (!strcmp(attr->name, "job-name") &&
-            (attr->value_tag == IPP_TAG_NAME ||
-             attr->value_tag == IPP_TAG_NAMELANG))
-      strlcpy(title, attr->values[0].string.text, sizeof(title));
-    else if (attr->group_tag == IPP_TAG_JOB)
+
+    if (pc->num_presets[print_color_mode][print_quality] > 0)
     {
      /*
-      * Filter out other unwanted attributes...
+      * Copy the preset options as long as the corresponding names are not
+      * already defined in the IPP request...
       */
 
-      if (attr->value_tag == IPP_TAG_MIMETYPE ||
-         attr->value_tag == IPP_TAG_NAMELANG ||
-         attr->value_tag == IPP_TAG_TEXTLANG ||
-         (attr->value_tag == IPP_TAG_URI && strcmp(attr->name, "job-uuid")) ||
-         attr->value_tag == IPP_TAG_URISCHEME ||
-         attr->value_tag == IPP_TAG_BEGIN_COLLECTION) /* Not yet supported */
-       continue;
+      for (i = pc->num_presets[print_color_mode][print_quality],
+              preset = pc->presets[print_color_mode][print_quality];
+          i > 0;
+          i --, preset ++)
+      {
+        if (!ippFindAttribute(job->attrs, preset->name, IPP_TAG_ZERO))
+         num_pwgppds = cupsAddOption(preset->name, preset->value, num_pwgppds,
+                                     &pwgppds);
+      }
+    }
+  }
 
-      if (!strncmp(attr->name, "time-", 5))
-       continue;
+  if (pc)
+  {
+    if (!ippFindAttribute(job->attrs, "InputSlot", IPP_TAG_ZERO) &&
+       !ippFindAttribute(job->attrs, "HPPaperSource", IPP_TAG_ZERO))
+    {
+      if ((ppd = _ppdCacheGetInputSlot(pc, job->attrs, NULL)) != NULL)
+       num_pwgppds = cupsAddOption(pc->source_option, ppd, num_pwgppds,
+                                   &pwgppds);
+      else if (!ippFindAttribute(job->attrs, "AP_D_InputSlot", IPP_TAG_ZERO))
+       num_pwgppds = cupsAddOption("AP_D_InputSlot", "", num_pwgppds,
+                                   &pwgppds);
+    }
+    if (!ippFindAttribute(job->attrs, "MediaType", IPP_TAG_ZERO) &&
+       (ppd = _ppdCacheGetMediaType(pc, job->attrs, NULL)) != NULL)
+      num_pwgppds = cupsAddOption("MediaType", ppd, num_pwgppds, &pwgppds);
 
-      if (!strncmp(attr->name, "job-", 4) && strcmp(attr->name, "job-uuid") &&
-          !(printer->type & CUPS_PRINTER_REMOTE))
-       continue;
+    if (!ippFindAttribute(job->attrs, "PageRegion", IPP_TAG_ZERO) &&
+       !ippFindAttribute(job->attrs, "PageSize", IPP_TAG_ZERO) &&
+       (ppd = _ppdCacheGetPageSize(pc, job->attrs, NULL, &exact)) != NULL)
+    {
+      num_pwgppds = cupsAddOption("PageSize", ppd, num_pwgppds, &pwgppds);
 
-      if (!strncmp(attr->name, "job-", 4) &&
-          strcmp(attr->name, "job-uuid") &&
-          strcmp(attr->name, "job-billing") &&
-          strcmp(attr->name, "job-sheets") &&
-          strcmp(attr->name, "job-hold-until") &&
-         strcmp(attr->name, "job-priority"))
-       continue;
+      if (!ippFindAttribute(job->attrs, "media", IPP_TAG_ZERO))
+        num_pwgppds = cupsAddOption("media", ppd, num_pwgppds, &pwgppds);
+    }
 
-      if ((!strcmp(attr->name, "page-label") ||
-           !strcmp(attr->name, "page-border") ||
-           !strncmp(attr->name, "number-up", 9) ||
-          !strcmp(attr->name, "page-ranges") ||
-          !strcmp(attr->name, "page-set") ||
-          !strcasecmp(attr->name, "AP_FIRSTPAGE_InputSlot") ||
-          !strcasecmp(attr->name, "AP_FIRSTPAGE_ManualFeed") ||
-          !strcasecmp(attr->name, "com.apple.print.PrintSettings."
-                                  "PMTotalSidesImaged..n.") ||
-          !strcasecmp(attr->name, "com.apple.print.PrintSettings."
-                                  "PMTotalBeginPages..n.")) &&
+    if (!ippFindAttribute(job->attrs, "OutputBin", IPP_TAG_ZERO) &&
+       (attr = ippFindAttribute(job->attrs, "output-bin",
+                                IPP_TAG_ZERO)) != NULL &&
+       (attr->value_tag == IPP_TAG_KEYWORD ||
+        attr->value_tag == IPP_TAG_NAME) &&
+       (ppd = _ppdCacheGetOutputBin(pc, attr->values[0].string.text)) != NULL)
+    {
+     /*
+      * Map output-bin to OutputBin option...
+      */
+
+      num_pwgppds = cupsAddOption("OutputBin", ppd, num_pwgppds, &pwgppds);
+    }
+
+    if (pc->sides_option &&
+        !ippFindAttribute(job->attrs, pc->sides_option, IPP_TAG_ZERO) &&
+       (attr = ippFindAttribute(job->attrs, "sides", IPP_TAG_KEYWORD)) != NULL)
+    {
+     /*
+      * Map sides to duplex option...
+      */
+
+      if (!strcmp(attr->values[0].string.text, "one-sided"))
+        num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_1sided,
+                                   num_pwgppds, &pwgppds);
+      else if (!strcmp(attr->values[0].string.text, "two-sided-long-edge"))
+        num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_2sided_long,
+                                   num_pwgppds, &pwgppds);
+      else if (!strcmp(attr->values[0].string.text, "two-sided-short-edge"))
+        num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_2sided_short,
+                                   num_pwgppds, &pwgppds);
+    }
+  }
+
+ /*
+  * Figure out how much room we need...
+  */
+
+  newlength = ipp_length(job->attrs);
+
+  for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++)
+    newlength += 1 + strlen(pwgppd->name) + 1 + strlen(pwgppd->value);
+
+ /*
+  * Then allocate/reallocate the option buffer as needed...
+  */
+
+  if (newlength == 0)                  /* This can never happen, but Clang */
+    newlength = 1;                     /* thinks it can... */
+
+  if (newlength > optlength || !options)
+  {
+    if (!options)
+      optptr = malloc(newlength);
+    else
+      optptr = realloc(options, newlength);
+
+    if (!optptr)
+    {
+      cupsdLogJob(job, CUPSD_LOG_CRIT,
+                 "Unable to allocate " CUPS_LLFMT " bytes for option buffer!",
+                 CUPS_LLCAST newlength);
+      return (NULL);
+    }
+
+    options   = optptr;
+    optlength = newlength;
+  }
+
+ /*
+  * Now loop through the attributes and convert them to the textual
+  * representation used by the filters...
+  */
+
+  optptr  = options;
+  *optptr = '\0';
+
+  snprintf(title, title_size, "%s-%d", job->printer->name, job->id);
+  strlcpy(copies, "1", copies_size);
+
+  for (attr = job->attrs->attrs; attr != NULL; attr = attr->next)
+  {
+    if (!strcmp(attr->name, "copies") &&
+       attr->value_tag == IPP_TAG_INTEGER)
+    {
+     /*
+      * Don't use the # copies attribute if we are printing the job sheets...
+      */
+
+      if (!banner_page)
+        snprintf(copies, copies_size, "%d", attr->values[0].integer);
+    }
+    else if (!strcmp(attr->name, "job-name") &&
+            (attr->value_tag == IPP_TAG_NAME ||
+             attr->value_tag == IPP_TAG_NAMELANG))
+      strlcpy(title, attr->values[0].string.text, title_size);
+    else if (attr->group_tag == IPP_TAG_JOB)
+    {
+     /*
+      * Filter out other unwanted attributes...
+      */
+
+      if (attr->value_tag == IPP_TAG_NOVALUE ||
+          attr->value_tag == IPP_TAG_MIMETYPE ||
+         attr->value_tag == IPP_TAG_NAMELANG ||
+         attr->value_tag == IPP_TAG_TEXTLANG ||
+         (attr->value_tag == IPP_TAG_URI && strcmp(attr->name, "job-uuid")) ||
+         attr->value_tag == IPP_TAG_URISCHEME ||
+         attr->value_tag == IPP_TAG_BEGIN_COLLECTION) /* Not yet supported */
+       continue;
+
+      if (!strcmp(attr->name, "job-hold-until"))
+       continue;
+
+      if (!strncmp(attr->name, "job-", 4) &&
+          strcmp(attr->name, "job-billing") &&
+          strcmp(attr->name, "job-impressions") &&
+          strcmp(attr->name, "job-originating-host-name") &&
+          strcmp(attr->name, "job-uuid") &&
+          !(job->printer->type & CUPS_PRINTER_REMOTE))
+       continue;
+
+      if ((!strcmp(attr->name, "job-impressions") ||
+           !strcmp(attr->name, "page-label") ||
+           !strcmp(attr->name, "page-border") ||
+           !strncmp(attr->name, "number-up", 9) ||
+          !strcmp(attr->name, "page-ranges") ||
+          !strcmp(attr->name, "page-set") ||
+          !_cups_strcasecmp(attr->name, "AP_FIRSTPAGE_InputSlot") ||
+          !_cups_strcasecmp(attr->name, "AP_FIRSTPAGE_ManualFeed") ||
+          !_cups_strcasecmp(attr->name, "com.apple.print.PrintSettings."
+                                  "PMTotalSidesImaged..n.") ||
+          !_cups_strcasecmp(attr->name, "com.apple.print.PrintSettings."
+                                  "PMTotalBeginPages..n.")) &&
          banner_page)
         continue;
 
@@ -2905,7 +3493,6 @@ start_job(cupsd_job_t     *job,           /* I - Job ID */
              if (!attr->values[i].boolean)
                strlcat(optptr, "no", optlength - (optptr - options));
 
-         case IPP_TAG_NOVALUE :
              strlcat(optptr, attr->name,
                      optlength - (optptr - options));
              break;
@@ -2955,570 +3542,743 @@ start_job(cupsd_job_t     *job,               /* I - Job ID */
   }
 
  /*
-  * Build the command-line arguments for the filters.  Each filter
-  * has 6 or 7 arguments:
-  *
-  *     argv[0] = printer
-  *     argv[1] = job ID
-  *     argv[2] = username
-  *     argv[3] = title
-  *     argv[4] = # copies
-  *     argv[5] = options
-  *     argv[6] = filename (optional; normally stdin)
-  *
-  * This allows legacy printer drivers that use the old System V
-  * printing interface to be used by CUPS.
-  *
-  * For remote jobs, we send all of the files in the argument list.
+  * Finally loop through the PWG->PPD mapped options and add them...
   */
 
-  if (printer->remote && job->num_files > 1)
-    argv = calloc(7 + job->num_files, sizeof(char *));
-  else
-    argv = calloc(8, sizeof(char *));
-
-  if (!argv)
+  for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++)
   {
-    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to allocate argument array!");
-    cupsArrayDelete(filters);
+    *optptr++ = ' ';
+    strcpy(optptr, pwgppd->name);
+    optptr += strlen(optptr);
+    *optptr++ = '=';
+    strcpy(optptr, pwgppd->value);
+    optptr += strlen(optptr);
+  }
 
-    FilterLevel -= job->cost;
+  cupsFreeOptions(num_pwgppds, pwgppds);
 
-    cupsdStopPrinter(printer, 0);
-    return;
-  }
+ /*
+  * Return the options string...
+  */
 
-  sprintf(jobid, "%d", job->id);
+  return (options);
+}
 
-  argv[0] = printer->name;
-  argv[1] = jobid;
-  argv[2] = job->username;
-  argv[3] = title;
-  argv[4] = copies;
-  argv[5] = options;
 
-  if (printer->remote && job->num_files > 1)
+/*
+ * 'ipp_length()' - Compute the size of the buffer needed to hold
+ *                 the textual IPP attributes.
+ */
+
+static size_t                          /* O - Size of attribute buffer */
+ipp_length(ipp_t *ipp)                 /* I - IPP request */
+{
+  size_t               bytes;          /* Number of bytes */
+  int                  i;              /* Looping var */
+  ipp_attribute_t      *attr;          /* Current attribute */
+
+
+ /*
+  * Loop through all attributes...
+  */
+
+  bytes = 0;
+
+  for (attr = ipp->attrs; attr != NULL; attr = attr->next)
   {
-    for (i = 0; i < job->num_files; i ++)
+   /*
+    * Skip attributes that won't be sent to filters...
+    */
+
+    if (attr->value_tag == IPP_TAG_NOVALUE ||
+       attr->value_tag == IPP_TAG_MIMETYPE ||
+       attr->value_tag == IPP_TAG_NAMELANG ||
+       attr->value_tag == IPP_TAG_TEXTLANG ||
+       attr->value_tag == IPP_TAG_URI ||
+       attr->value_tag == IPP_TAG_URISCHEME)
+      continue;
+
+   /*
+    * Add space for a leading space and commas between each value.
+    * For the first attribute, the leading space isn't used, so the
+    * extra byte can be used as the nul terminator...
+    */
+
+    bytes ++;                          /* " " separator */
+    bytes += attr->num_values;         /* "," separators */
+
+   /*
+    * Boolean attributes appear as "foo,nofoo,foo,nofoo", while
+    * other attributes appear as "foo=value1,value2,...,valueN".
+    */
+
+    if (attr->value_tag != IPP_TAG_BOOLEAN)
+      bytes += strlen(attr->name);
+    else
+      bytes += attr->num_values * strlen(attr->name);
+
+   /*
+    * Now add the size required for each value in the attribute...
+    */
+
+    switch (attr->value_tag)
     {
-      snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
-               job->id, i + 1);
-      argv[6 + i] = strdup(filename);
+      case IPP_TAG_INTEGER :
+      case IPP_TAG_ENUM :
+         /*
+         * Minimum value of a signed integer is -2147483647, or 11 digits.
+         */
+
+         bytes += attr->num_values * 11;
+         break;
+
+      case IPP_TAG_BOOLEAN :
+         /*
+         * Add two bytes for each false ("no") value...
+         */
+
+          for (i = 0; i < attr->num_values; i ++)
+           if (!attr->values[i].boolean)
+             bytes += 2;
+         break;
+
+      case IPP_TAG_RANGE :
+         /*
+         * A range is two signed integers separated by a hyphen, or
+         * 23 characters max.
+         */
+
+         bytes += attr->num_values * 23;
+         break;
+
+      case IPP_TAG_RESOLUTION :
+         /*
+         * A resolution is two signed integers separated by an "x" and
+         * suffixed by the units, or 26 characters max.
+         */
+
+         bytes += attr->num_values * 26;
+         break;
+
+      case IPP_TAG_STRING :
+      case IPP_TAG_TEXT :
+      case IPP_TAG_NAME :
+      case IPP_TAG_KEYWORD :
+      case IPP_TAG_CHARSET :
+      case IPP_TAG_LANGUAGE :
+      case IPP_TAG_URI :
+         /*
+         * Strings can contain characters that need quoting.  We need
+         * at least 2 * len + 2 characters to cover the quotes and
+         * any backslashes in the string.
+         */
+
+          for (i = 0; i < attr->num_values; i ++)
+           bytes += 2 * strlen(attr->values[i].string.text) + 2;
+         break;
+
+       default :
+         break; /* anti-compiler-warning-code */
     }
   }
-  else
+
+  return (bytes);
+}
+
+
+/*
+ * 'load_job_cache()' - Load jobs from the job.cache file.
+ */
+
+static void
+load_job_cache(const char *filename)   /* I - job.cache filename */
+{
+  cups_file_t  *fp;                    /* job.cache file */
+  char         line[1024],             /* Line buffer */
+               *value;                 /* Value on line */
+  int          linenum;                /* Line number in file */
+  cupsd_job_t  *job;                   /* Current job */
+  int          jobid;                  /* Job ID */
+  char         jobfile[1024];          /* Job filename */
+
+
+ /*
+  * Open the job.cache file...
+  */
+
+  if ((fp = cupsdOpenConfFile(filename)) == NULL)
   {
-    snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
-             job->id, job->current_file + 1);
-    argv[6] = filename;
+    load_request_root();
+    return;
   }
 
-  for (i = 0; argv[i]; i ++)
-    cupsdLogMessage(CUPSD_LOG_DEBUG,
-                    "[Job %d] argv[%d]=\"%s\"", job->id, i, argv[i]);
-
  /*
-  * Create environment variable strings for the filters...
+  * Read entries from the job cache file and create jobs as needed.
   */
 
-  attr = ippFindAttribute(job->attrs, "attributes-natural-language",
-                          IPP_TAG_LANGUAGE);
+  cupsdLogMessage(CUPSD_LOG_INFO, "Loading job cache file \"%s\"...",
+                  filename);
 
-#ifdef __APPLE__
-  strcpy(apple_language, "APPLE_LANGUAGE=");
-  _cupsAppleLanguage(attr->values[0].string.text,
-                    apple_language + 15, sizeof(apple_language) - 15);
-#endif /* __APPLE__ */
+  linenum = 0;
+  job     = NULL;
 
-  switch (strlen(attr->values[0].string.text))
+  while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
   {
-    default :
-       /*
-        * This is an unknown or badly formatted language code; use
-       * the POSIX locale...
-       */
+    if (!_cups_strcasecmp(line, "NextJobId"))
+    {
+      if (value)
+        NextJobId = atoi(value);
+    }
+    else if (!_cups_strcasecmp(line, "<Job"))
+    {
+      if (job)
+      {
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Missing </Job> directive on line %d!",
+                       linenum);
+        continue;
+      }
+
+      if (!value)
+      {
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Missing job ID on line %d!", linenum);
+       continue;
+      }
+
+      jobid = atoi(value);
+
+      if (jobid < 1)
+      {
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad job ID %d on line %d!", jobid,
+                       linenum);
+        continue;
+      }
+
+      snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, jobid);
+      if (access(jobfile, 0))
+      {
+       snprintf(jobfile, sizeof(jobfile), "%s/c%05d.N", RequestRoot, jobid);
+       if (access(jobfile, 0))
+       {
+         cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Files have gone away!",
+                         jobid);
+         continue;
+       }
+      }
+
+      job = calloc(1, sizeof(cupsd_job_t));
+      if (!job)
+      {
+        cupsdLogMessage(CUPSD_LOG_EMERG,
+                       "[Job %d] Unable to allocate memory for job!", jobid);
+        break;
+      }
+
+      job->id              = jobid;
+      job->back_pipes[0]   = -1;
+      job->back_pipes[1]   = -1;
+      job->print_pipes[0]  = -1;
+      job->print_pipes[1]  = -1;
+      job->side_pipes[0]   = -1;
+      job->side_pipes[1]   = -1;
+      job->status_pipes[0] = -1;
+      job->status_pipes[1] = -1;
+
+      cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Loading from cache...",
+                      job->id);
+    }
+    else if (!job)
+    {
+      cupsdLogMessage(CUPSD_LOG_ERROR,
+                     "Missing <Job #> directive on line %d!", linenum);
+      continue;
+    }
+    else if (!_cups_strcasecmp(line, "</Job>"))
+    {
+      cupsArrayAdd(Jobs, job);
+
+      if (job->state_value <= IPP_JOB_STOPPED && cupsdLoadJob(job))
+       cupsArrayAdd(ActiveJobs, job);
+
+      job = NULL;
+    }
+    else if (!value)
+    {
+      cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d!", linenum);
+      continue;
+    }
+    else if (!_cups_strcasecmp(line, "State"))
+    {
+      job->state_value = (ipp_jstate_t)atoi(value);
+
+      if (job->state_value < IPP_JOB_PENDING)
+        job->state_value = IPP_JOB_PENDING;
+      else if (job->state_value > IPP_JOB_COMPLETED)
+        job->state_value = IPP_JOB_COMPLETED;
+    }
+    else if (!_cups_strcasecmp(line, "HoldUntil"))
+    {
+      job->hold_until = atoi(value);
+    }
+    else if (!_cups_strcasecmp(line, "Priority"))
+    {
+      job->priority = atoi(value);
+    }
+    else if (!_cups_strcasecmp(line, "Username"))
+    {
+      cupsdSetString(&job->username, value);
+    }
+    else if (!_cups_strcasecmp(line, "Destination"))
+    {
+      cupsdSetString(&job->dest, value);
+    }
+    else if (!_cups_strcasecmp(line, "DestType"))
+    {
+      job->dtype = (cups_ptype_t)atoi(value);
+    }
+    else if (!_cups_strcasecmp(line, "NumFiles"))
+    {
+      job->num_files = atoi(value);
+
+      if (job->num_files < 0)
+      {
+       cupsdLogMessage(CUPSD_LOG_ERROR, "Bad NumFiles value %d on line %d!",
+                       job->num_files, linenum);
+        job->num_files = 0;
+       continue;
+      }
+
+      if (job->num_files > 0)
+      {
+        snprintf(jobfile, sizeof(jobfile), "%s/d%05d-001", RequestRoot,
+                job->id);
+        if (access(jobfile, 0))
+       {
+         cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Data files have gone away!",
+                         job->id);
+          job->num_files = 0;
+         continue;
+       }
+
+        job->filetypes    = calloc(job->num_files, sizeof(mime_type_t *));
+       job->compressions = calloc(job->num_files, sizeof(int));
+
+        if (!job->filetypes || !job->compressions)
+       {
+         cupsdLogMessage(CUPSD_LOG_EMERG,
+                         "[Job %d] Unable to allocate memory for %d files!",
+                         job->id, job->num_files);
+          break;
+       }
+      }
+    }
+    else if (!_cups_strcasecmp(line, "File"))
+    {
+      int      number,                 /* File number */
+               compression;            /* Compression value */
+      char     super[MIME_MAX_SUPER],  /* MIME super type */
+               type[MIME_MAX_TYPE];    /* MIME type */
+
+
+      if (sscanf(value, "%d%*[ \t]%15[^/]/%255s%d", &number, super, type,
+                 &compression) != 4)
+      {
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File on line %d!", linenum);
+       continue;
+      }
+
+      if (number < 1 || number > job->num_files)
+      {
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File number %d on line %d!",
+                       number, linenum);
+        continue;
+      }
+
+      number --;
 
-       strcpy(lang, "LANG=C");
-       break;
+      job->compressions[number] = compression;
+      job->filetypes[number]    = mimeType(MimeDatabase, super, type);
 
-    case 2 :
+      if (!job->filetypes[number])
+      {
        /*
-        * Just the language code (ll)...
+        * If the original MIME type is unknown, auto-type it!
        */
 
-        snprintf(lang, sizeof(lang), "LANG=%s.UTF8",
-                attr->values[0].string.text);
-        break;
+        cupsdLogMessage(CUPSD_LOG_ERROR,
+                       "[Job %d] Unknown MIME type %s/%s for file %d!",
+                       job->id, super, type, number + 1);
+
+        snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot,
+                job->id, number + 1);
+        job->filetypes[number] = mimeFileType(MimeDatabase, jobfile, NULL,
+                                             job->compressions + number);
 
-    case 5 :
        /*
-        * Language and country code (ll-cc)...
+        * If that didn't work, assume it is raw...
        */
 
-        snprintf(lang, sizeof(lang), "LANG=%c%c_%c%c.UTF8",
-                attr->values[0].string.text[0],
-                attr->values[0].string.text[1],
-                toupper(attr->values[0].string.text[3] & 255),
-                toupper(attr->values[0].string.text[4] & 255));
-        break;
+        if (!job->filetypes[number])
+         job->filetypes[number] = mimeType(MimeDatabase, "application",
+                                           "vnd.cups-raw");
+      }
+    }
+    else
+      cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown %s directive on line %d!",
+                      line, linenum);
   }
 
-  attr = ippFindAttribute(job->attrs, "document-format",
-                          IPP_TAG_MIMETYPE);
-  if (attr != NULL &&
-      (optptr = strstr(attr->values[0].string.text, "charset=")) != NULL)
-    snprintf(charset, sizeof(charset), "CHARSET=%s", optptr + 8);
-  else
-  {
-    attr = ippFindAttribute(job->attrs, "attributes-charset",
-                           IPP_TAG_CHARSET);
-    snprintf(charset, sizeof(charset), "CHARSET=%s",
-             attr->values[0].string.text);
-  }
+  cupsFileClose(fp);
+}
 
-  snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s/%s",
-           job->filetypes[job->current_file]->super,
-           job->filetypes[job->current_file]->type);
-  snprintf(device_uri, sizeof(device_uri), "DEVICE_URI=%s",
-           printer->device_uri);
-  cupsdSanitizeURI(printer->device_uri, sani_uri, sizeof(sani_uri));
-  snprintf(ppd, sizeof(ppd), "PPD=%s/ppd/%s.ppd", ServerRoot, printer->name);
-  snprintf(printer_name, sizeof(printer_name), "PRINTER=%s", printer->name);
-  snprintf(rip_max_cache, sizeof(rip_max_cache), "RIP_MAX_CACHE=%s", RIPCache);
 
-  envc = cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0])));
+/*
+ * 'load_next_job_id()' - Load the NextJobId value from the job.cache file.
+ */
 
-  envp[envc ++] = charset;
-  envp[envc ++] = lang;
-#ifdef __APPLE__
-  envp[envc ++] = apple_language;
-#endif /* __APPLE__ */
-  envp[envc ++] = ppd;
-  envp[envc ++] = rip_max_cache;
-  envp[envc ++] = content_type;
-  envp[envc ++] = device_uri;
-  envp[envc ++] = printer_name;
+static void
+load_next_job_id(const char *filename) /* I - job.cache filename */
+{
+  cups_file_t  *fp;                    /* job.cache file */
+  char         line[1024],             /* Line buffer */
+               *value;                 /* Value on line */
+  int          linenum;                /* Line number in file */
+  int          next_job_id;            /* NextJobId value from line */
 
-  if (!printer->remote && !printer->raw &&
-      (filter = (mime_filter_t *)cupsArrayLast(filters)) != NULL &&
-      filter->dst)
-  {
-    snprintf(final_content_type, sizeof(final_content_type),
-             "FINAL_CONTENT_TYPE=%s/%s",
-            filter->dst->super, filter->dst->type);
-    envp[envc ++] = final_content_type;
-  }
 
-  if (Classification && !banner_page)
+ /*
+  * Read the NextJobId directive from the job.cache file and use
+  * the value (if any).
+  */
+
+  if ((fp = cupsFileOpen(filename, "r")) == NULL)
   {
-    if ((attr = ippFindAttribute(job->attrs, "job-sheets",
-                                 IPP_TAG_NAME)) == NULL)
-      snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
-               Classification);
-    else if (attr->num_values > 1 &&
-             strcmp(attr->values[1].string.text, "none") != 0)
-      snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
-               attr->values[1].string.text);
-    else
-      snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
-               attr->values[0].string.text);
+    if (errno != ENOENT)
+      cupsdLogMessage(CUPSD_LOG_ERROR,
+                      "Unable to open job cache file \"%s\": %s",
+                      filename, strerror(errno));
 
-    envp[envc ++] = classification;
+    return;
   }
 
-  if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))
+  cupsdLogMessage(CUPSD_LOG_INFO,
+                  "Loading NextJobId from job cache file \"%s\"...", filename);
+
+  linenum = 0;
+
+  while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
   {
-    snprintf(class_name, sizeof(class_name), "CLASS=%s", job->dest);
-    envp[envc ++] = class_name;
+    if (!_cups_strcasecmp(line, "NextJobId"))
+    {
+      if (value)
+      {
+        next_job_id = atoi(value);
+
+        if (next_job_id > NextJobId)
+         NextJobId = next_job_id;
+      }
+      break;
+    }
   }
 
-  if (job->auth_username)
-    envp[envc ++] = job->auth_username;
-  if (job->auth_domain)
-    envp[envc ++] = job->auth_domain;
-  if (job->auth_password)
-    envp[envc ++] = job->auth_password;
+  cupsFileClose(fp);
+}
 
-#ifdef HAVE_GSSAPI
-  if (job->ccname)
-    envp[envc ++] = job->ccname;
-#endif /* HAVE_GSSAPI */
 
-  envp[envc] = NULL;
+/*
+ * 'load_request_root()' - Load jobs from the RequestRoot directory.
+ */
 
-  for (i = 0; i < envc; i ++)
-    if (!strncmp(envp[i], "CUPSD_AUTH_", 5))
-      cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] envp[%d]=\"CUPSD_AUTH_%c****\"",
-                      job->id, i, envp[i][5]);
-    else if (strncmp(envp[i], "DEVICE_URI=", 11))
-      cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] envp[%d]=\"%s\"",
-                      job->id, i, envp[i]);
-    else
-      cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] envp[%d]=\"DEVICE_URI=%s\"",
-                      job->id, i, sani_uri);
+static void
+load_request_root(void)
+{
+  cups_dir_t           *dir;           /* Directory */
+  cups_dentry_t                *dent;          /* Directory entry */
+  cupsd_job_t          *job;           /* New job */
 
-  if (printer->remote)
-    job->current_file = job->num_files;
-  else
-    job->current_file ++;
 
  /*
-  * Now create processes for all of the filters...
+  * Open the requests directory...
   */
 
-  filterfds[0][0] = -1;
-  filterfds[0][1] = -1;
-  filterfds[1][0] = -1;
-  filterfds[1][1] = -1;
+  cupsdLogMessage(CUPSD_LOG_DEBUG, "Scanning %s for jobs...", RequestRoot);
 
-  if (!job->status_buffer)
+  if ((dir = cupsDirOpen(RequestRoot)) == NULL)
   {
-    if (cupsdOpenPipe(job->status_pipes))
-    {
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                      "[Job %d] Unable to create job status pipes - %s.",
-                     job->id, strerror(errno));
-      snprintf(printer->state_message, sizeof(printer->state_message),
-              "Unable to create status pipes - %s.", strerror(errno));
-
-      cupsdAddPrinterHistory(printer);
-
-      cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
-                   "Job canceled because the server could not create the job "
-                   "status pipes.");
-
-      goto abort_job;
-    }
-
-    cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                    "[Job %d] start_job: status_pipes = [ %d %d ]",
-                   job->id, job->status_pipes[0], job->status_pipes[1]);
-
-    job->status_buffer = cupsdStatBufNew(job->status_pipes[0], "[Job %d]",
-                                        job->id);
-    job->status_level  = CUPSD_LOG_INFO;
+    cupsdLogMessage(CUPSD_LOG_ERROR,
+                    "Unable to open spool directory \"%s\": %s",
+                    RequestRoot, strerror(errno));
+    return;
   }
 
-  job->status = 0;
-  memset(job->filters, 0, sizeof(job->filters));
-
-  if (!job->profile)
-    job->profile = cupsdCreateProfile(job->id);
-
-  for (i = 0, slot = 0, filter = (mime_filter_t *)cupsArrayFirst(filters);
-       filter;
-       i ++, filter = (mime_filter_t *)cupsArrayNext(filters))
-  {
-    if (filter->filter[0] != '/')
-      snprintf(command, sizeof(command), "%s/filter/%s", ServerBin,
-               filter->filter);
-    else
-      strlcpy(command, filter->filter, sizeof(command));
+ /*
+  * Read all the c##### files...
+  */
 
-    if (i < (cupsArrayCount(filters) - 1))
+  while ((dent = cupsDirRead(dir)) != NULL)
+    if (strlen(dent->filename) >= 6 && dent->filename[0] == 'c')
     {
-      if (cupsdOpenPipe(filterfds[slot]))
-      {
-       cupsdLogMessage(CUPSD_LOG_ERROR,
-                       "[Job %d] Unable to create job filter pipes - %s.",
-                       job->id, strerror(errno));
-       snprintf(printer->state_message, sizeof(printer->state_message),
-               "Unable to create filter pipes - %s.", strerror(errno));
-       cupsdAddPrinterHistory(printer);
-
-       cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
-                      "Job canceled because the server could not create the "
-                     "filter pipes.");
+     /*
+      * Allocate memory for the job...
+      */
 
-        goto abort_job;
-      }
-    }
-    else
-    {
-      if (job->current_file == 1)
+      if ((job = calloc(sizeof(cupsd_job_t), 1)) == NULL)
       {
-       if (strncmp(printer->device_uri, "file:", 5) != 0)
-       {
-         if (cupsdOpenPipe(job->print_pipes))
-         {
-           cupsdLogMessage(CUPSD_LOG_ERROR,
-                           "[Job %d] Unable to create job backend pipes - %s.",
-                           job->id, strerror(errno));
-           snprintf(printer->state_message, sizeof(printer->state_message),
-                   "Unable to create backend pipes - %s.", strerror(errno));
-           cupsdAddPrinterHistory(printer);
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Ran out of memory for jobs!");
+       cupsDirClose(dir);
+       return;
+      }
 
-           cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
-                         "Job canceled because the server could not create "
-                         "the backend pipes.");
+     /*
+      * Assign the job ID...
+      */
 
-            goto abort_job;
-         }
-       }
-       else
-       {
-         job->print_pipes[0] = -1;
-         if (!strcmp(printer->device_uri, "file:/dev/null") ||
-             !strcmp(printer->device_uri, "file:///dev/null"))
-           job->print_pipes[1] = -1;
-         else
-         {
-           if (!strncmp(printer->device_uri, "file:/dev/", 10))
-             job->print_pipes[1] = open(printer->device_uri + 5,
-                                        O_WRONLY | O_EXCL);
-           else if (!strncmp(printer->device_uri, "file:///dev/", 12))
-             job->print_pipes[1] = open(printer->device_uri + 7,
-                                        O_WRONLY | O_EXCL);
-           else if (!strncmp(printer->device_uri, "file:///", 8))
-             job->print_pipes[1] = open(printer->device_uri + 7,
-                                        O_WRONLY | O_CREAT | O_TRUNC, 0600);
-           else
-             job->print_pipes[1] = open(printer->device_uri + 5,
-                                        O_WRONLY | O_CREAT | O_TRUNC, 0600);
+      job->id              = atoi(dent->filename + 1);
+      job->back_pipes[0]   = -1;
+      job->back_pipes[1]   = -1;
+      job->print_pipes[0]  = -1;
+      job->print_pipes[1]  = -1;
+      job->side_pipes[0]   = -1;
+      job->side_pipes[1]   = -1;
+      job->status_pipes[0] = -1;
+      job->status_pipes[1] = -1;
 
-           if (job->print_pipes[1] < 0)
-           {
-              cupsdLogMessage(CUPSD_LOG_ERROR,
-                             "[Job %d] Unable to open output file \"%s\" - %s.",
-                             job->id, printer->device_uri, strerror(errno));
-              snprintf(printer->state_message, sizeof(printer->state_message),
-                      "Unable to open output file \"%s\" - %s.",
-                      printer->device_uri, strerror(errno));
+      if (job->id >= NextJobId)
+        NextJobId = job->id + 1;
 
-             cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
-                           "Job canceled because the server could not open the "
-                           "output file.");
+     /*
+      * Load the job...
+      */
 
-              goto abort_job;
-           }
+      if (cupsdLoadJob(job))
+      {
+       /*
+        * Insert the job into the array, sorting by job priority and ID...
+        */
 
-           fcntl(job->print_pipes[1], F_SETFD,
-                 fcntl(job->print_pipes[1], F_GETFD) | FD_CLOEXEC);
-          }
-       }
+       cupsArrayAdd(Jobs, job);
 
-       cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                       "[Job %d] start_job: print_pipes = [ %d %d ]",
-                        job->id, job->print_pipes[0], job->print_pipes[1]);
+       if (job->state_value <= IPP_JOB_STOPPED)
+         cupsArrayAdd(ActiveJobs, job);
+       else
+         unload_job(job);
       }
-
-      filterfds[slot][0] = job->print_pipes[0];
-      filterfds[slot][1] = job->print_pipes[1];
     }
 
-    cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: filter=\"%s\"",
-                    job->id, command);
-    cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                    "[Job %d] start_job: filterfds[%d]=[ %d %d ]",
-                    job->id, slot, filterfds[slot][0], filterfds[slot][1]);
+  cupsDirClose(dir);
+}
 
-    pid = cupsdStartProcess(command, argv, envp, filterfds[!slot][0],
-                            filterfds[slot][1], job->status_pipes[1],
-                           job->back_pipes[0], job->side_pipes[0], 0,
-                           job->profile, job->filters + i);
 
-    cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                    "[Job %d] start_job: Closing filter pipes for slot %d "
-                   "[ %d %d ]...",
-                    job->id, !slot, filterfds[!slot][0], filterfds[!slot][1]);
+/*
+ * 'set_time()' - Set one of the "time-at-xyz" attributes.
+ */
 
-    cupsdClosePipe(filterfds[!slot]);
+static void
+set_time(cupsd_job_t *job,             /* I - Job to update */
+         const char  *name)            /* I - Name of attribute */
+{
+  ipp_attribute_t      *attr;          /* Time attribute */
 
-    if (pid == 0)
-    {
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                      "[Job %d] Unable to start filter \"%s\" - %s.",
-                      job->id, filter->filter, strerror(errno));
-      snprintf(printer->state_message, sizeof(printer->state_message),
-               "Unable to start filter \"%s\" - %s.",
-               filter->filter, strerror(errno));
 
-      cupsdAddPrinterHistory(printer);
+  if ((attr = ippFindAttribute(job->attrs, name, IPP_TAG_ZERO)) != NULL)
+  {
+    attr->value_tag         = IPP_TAG_INTEGER;
+    attr->values[0].integer = time(NULL);
+  }
+}
 
-      cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
-                    "Job canceled because the server could not execute a "
-                   "filter.");
 
-      goto abort_job;
-    }
+/*
+ * 'start_job()' - Start a print job.
+ */
 
-    cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Started filter %s (PID %d)",
-                    job->id, command, pid);
+static void
+start_job(cupsd_job_t     *job,                /* I - Job ID */
+          cupsd_printer_t *printer)    /* I - Printer to print job */
+{
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "start_job(job=%p(%d), printer=%p(%s))",
+                  job, job->id, printer, printer->name);
 
-    argv[6] = NULL;
-    slot    = !slot;
-  }
+ /*
+  * Make sure we have some files around before we try to print...
+  */
 
-  cupsArrayDelete(filters);
+  if (job->num_files == 0)
+  {
+    cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_DEFAULT,
+                     "Aborting job because it has no files.");
+    return;
+  }
 
  /*
-  * Finally, pipe the final output into a backend process if needed...
+  * Update the printer and job state to "processing"...
   */
 
-  if (strncmp(printer->device_uri, "file:", 5) != 0)
-  {
-    if (job->current_file == 1 || printer->remote)
-    {
-      sscanf(printer->device_uri, "%254[^:]", method);
-      snprintf(command, sizeof(command), "%s/backend/%s", ServerBin, method);
+  if (!cupsdLoadJob(job))
+    return;
 
-     /*
-      * See if the backend needs to run as root...
-      */
+  if (job->printer_message)
+    cupsdSetString(&(job->printer_message->values[0].string.text), "");
 
-      if (RunUser)
-        backroot = 0;
-      else if (stat(command, &backinfo))
-       backroot = 0;
-      else
-        backroot = !(backinfo.st_mode & (S_IRWXG | S_IRWXO));
+  cupsdSetJobState(job, IPP_JOB_PROCESSING, CUPSD_JOB_DEFAULT, NULL);
+  cupsdSetPrinterState(printer, IPP_PRINTER_PROCESSING, 0);
+  cupsdSetPrinterReasons(printer, "-cups-remote-pending,"
+                                 "cups-remote-pending-held,"
+                                 "cups-remote-processing,"
+                                 "cups-remote-stopped,"
+                                 "cups-remote-canceled,"
+                                 "cups-remote-aborted,"
+                                 "cups-remote-completed");
+
+  job->cost         = 0;
+  job->current_file = 0;
+  job->progress     = 0;
+  job->printer      = printer;
+  printer->job      = job;
 
-      argv[0] = sani_uri;
+ /*
+  * Setup the last exit status and security profiles...
+  */
 
-      filterfds[slot][0] = -1;
-      filterfds[slot][1] = -1;
+  job->status  = 0;
+  job->profile = cupsdCreateProfile(job->id);
 
-      cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: backend=\"%s\"",
-                      job->id, command);
-      cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                      "[Job %d] start_job: filterfds[%d] = [ %d %d ]", job->id,
-                     slot, filterfds[slot][0], filterfds[slot][1]);
+ /*
+  * Create the status pipes and buffer...
+  */
 
-      pid = cupsdStartProcess(command, argv, envp, filterfds[!slot][0],
-                             filterfds[slot][1], job->status_pipes[1],
-                             job->back_pipes[1], job->side_pipes[1],
-                             backroot, job->profile, &(job->backend));
+  if (cupsdOpenPipe(job->status_pipes))
+  {
+    cupsdLogJob(job, CUPSD_LOG_DEBUG,
+               "Unable to create job status pipes - %s.", strerror(errno));
 
-      if (pid == 0)
-      {
-       cupsdLogMessage(CUPSD_LOG_ERROR,
-                       "[Job %d] Unable to start backend \"%s\" - %s.",
-                       job->id, method, strerror(errno));
-       snprintf(printer->state_message, sizeof(printer->state_message),
-                "Unable to start backend \"%s\" - %s.", method,
-                strerror(errno));
+    cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
+                    "Job stopped because the scheduler could not create the "
+                    "job status pipes.");
 
-       cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job,
-                      "Job canceled because the server could not execute "
-                     "the backend.");
+    cupsdDestroyProfile(job->profile);
+    job->profile = NULL;
+    return;
+  }
 
-        goto abort_job;
-      }
-      else
-      {
-       cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Started backend %s (PID %d)",
-                       job->id, command, pid);
-      }
-    }
+  job->status_buffer = cupsdStatBufNew(job->status_pipes[0], NULL);
+  job->status_level  = CUPSD_LOG_INFO;
 
-    if (job->current_file == job->num_files)
-    {
-      cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                      "[Job %d] start_job: Closing print pipes [ %d %d ]...",
-                     job->id, job->print_pipes[0], job->print_pipes[1]);
+ /*
+  * Create the backchannel pipes and make them non-blocking...
+  */
 
-      cupsdClosePipe(job->print_pipes);
+  if (cupsdOpenPipe(job->back_pipes))
+  {
+    cupsdLogJob(job, CUPSD_LOG_DEBUG,
+               "Unable to create back-channel pipes - %s.", strerror(errno));
 
-      cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                      "[Job %d] start_job: Closing back pipes [ %d %d ]...",
-                     job->id, job->back_pipes[0], job->back_pipes[1]);
+    cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
+                    "Job stopped because the scheduler could not create the "
+                    "back-channel pipes.");
 
-      cupsdClosePipe(job->back_pipes);
+    cupsdClosePipe(job->status_pipes);
+    cupsdStatBufDelete(job->status_buffer);
+    job->status_buffer = NULL;
 
-      cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                      "[Job %d] start_job: Closing side pipes [ %d %d ]...",
-                     job->id, job->side_pipes[0], job->side_pipes[1]);
+    cupsdDestroyProfile(job->profile);
+    job->profile = NULL;
+    return;
+  }
 
-      cupsdClosePipe(job->side_pipes);
+  fcntl(job->back_pipes[0], F_SETFL,
+       fcntl(job->back_pipes[0], F_GETFL) | O_NONBLOCK);
+  fcntl(job->back_pipes[1], F_SETFL,
+       fcntl(job->back_pipes[1], F_GETFL) | O_NONBLOCK);
 
-      cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                     "[Job %d] start_job: Closing status output pipe %d...",
-                     job->id, job->status_pipes[1]);
+ /*
+  * Create the side-channel pipes and make them non-blocking...
+  */
 
-      close(job->status_pipes[1]);
-      job->status_pipes[1] = -1;
-    }
-  }
-  else
+  if (socketpair(AF_LOCAL, SOCK_STREAM, 0, job->side_pipes))
   {
-    filterfds[slot][0] = -1;
-    filterfds[slot][1] = -1;
+    cupsdLogJob(job, CUPSD_LOG_DEBUG,
+               "Unable to create side-channel pipes - %s.", strerror(errno));
 
-    if (job->current_file == job->num_files)
-    {
-      cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                      "[Job %d] start_job: Closing print pipes [ %d %d ]...",
-                     job->id, job->print_pipes[0], job->print_pipes[1]);
+    cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
+                    "Job stopped because the scheduler could not create the "
+                    "side-channel pipes.");
 
-      cupsdClosePipe(job->print_pipes);
+    cupsdClosePipe(job->back_pipes);
 
-      cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                     "[Job %d] start_job: Closing status output pipe %d...",
-                     job->id, job->status_pipes[1]);
+    cupsdClosePipe(job->status_pipes);
+    cupsdStatBufDelete(job->status_buffer);
+    job->status_buffer = NULL;
 
-      close(job->status_pipes[1]);
-      job->status_pipes[1] = -1;
-    }
+    cupsdDestroyProfile(job->profile);
+    job->profile = NULL;
+    return;
   }
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                 "[Job %d] start_job: Closing filter pipes for slot %d "
-                 "[ %d %d ]...",
-                 job->id, slot, filterfds[slot][0], filterfds[slot][1]);
-  cupsdClosePipe(filterfds[slot]);
+  fcntl(job->side_pipes[0], F_SETFL,
+       fcntl(job->side_pipes[0], F_GETFL) | O_NONBLOCK);
+  fcntl(job->side_pipes[1], F_SETFL,
+       fcntl(job->side_pipes[1], F_GETFL) | O_NONBLOCK);
 
-  if (printer->remote && job->num_files > 1)
-  {
-    for (i = 0; i < job->num_files; i ++)
-      free(argv[i + 6]);
-  }
+  fcntl(job->side_pipes[0], F_SETFD,
+       fcntl(job->side_pipes[0], F_GETFD) | FD_CLOEXEC);
+  fcntl(job->side_pipes[1], F_SETFD,
+       fcntl(job->side_pipes[1], F_GETFD) | FD_CLOEXEC);
 
-  free(argv);
+ /*
+  * Now start the first file in the job...
+  */
 
-  cupsdAddSelect(job->status_buffer->fd, (cupsd_selfunc_t)update_job, NULL,
-                 job);
+  cupsdContinueJob(job);
+}
 
-  cupsdAddEvent(CUPSD_EVENT_JOB_STATE, job->printer, job, "Job #%d started.",
-                job->id);
 
-  return;
+/*
+ * 'stop_job()' - Stop a print job.
+ */
 
+static void
+stop_job(cupsd_job_t       *job,       /* I - Job */
+         cupsd_jobaction_t action)     /* I - Action */
+{
+  int  i;                              /* Looping var */
 
- /*
-  * If we get here, we need to abort the current job and close out all
-  * files and pipes...
-  */
 
-  abort_job:
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "stop_job(job=%p(%d), action=%d)", job,
+                  job->id, action);
 
-  for (slot = 0; slot < 2; slot ++)
-  {
-    cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                    "[Job %d] start_job: Closing filter pipes for slot %d "
-                   "[ %d %d ]...",
-                    job->id, slot, filterfds[slot][0], filterfds[slot][1]);
-    cupsdClosePipe(filterfds[slot]);
-  }
+  FilterLevel -= job->cost;
+  job->cost   = 0;
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                  "[Job %d] start_job: Closing status pipes [ %d %d ]...",
-                  job->id, job->status_pipes[0], job->status_pipes[1]);
-  cupsdClosePipe(job->status_pipes);
-  cupsdStatBufDelete(job->status_buffer);
+  if (action == CUPSD_JOB_DEFAULT && !job->kill_time)
+    job->kill_time = time(NULL) + JobKillDelay;
+  else if (action >= CUPSD_JOB_FORCE)
+    job->kill_time = 0;
 
-  job->status_buffer = NULL;
+  for (i = 0; job->filters[i]; i ++)
+    if (job->filters[i] > 0)
+    {
+      cupsdEndProcess(job->filters[i], action >= CUPSD_JOB_FORCE);
 
-  cupsArrayDelete(filters);
+      if (action >= CUPSD_JOB_FORCE)
+        job->filters[i] = -job->filters[i];
+    }
 
-  if (printer->remote && job->num_files > 1)
+  if (job->backend > 0)
   {
-    for (i = 0; i < job->num_files; i ++)
-      free(argv[i + 6]);
+    cupsdEndProcess(job->backend, action >= CUPSD_JOB_FORCE);
+
+    if (action >= CUPSD_JOB_FORCE)
+      job->backend = -job->backend;
   }
 
-  free(argv);
+  if (action >= CUPSD_JOB_FORCE)
+  {
+   /*
+    * Clear job status...
+    */
 
-  cupsdStopJob(job, 0);
+    job->status = 0;
+  }
 }
 
 
@@ -3554,11 +4314,31 @@ update_job(cupsd_job_t *job)            /* I - Job to check */
 {
   int          i;                      /* Looping var */
   int          copies;                 /* Number of copies printed */
-  char         message[1024],          /* Message text */
+  char         message[CUPSD_SB_BUFFER_SIZE],
+                                       /* Message text */
                *ptr;                   /* Pointer update... */
   int          loglevel,               /* Log level for message */
                event = 0;              /* Events? */
+  static const char * const levels[] = /* Log levels */
+               {
+                 "NONE",
+                 "EMERG",
+                 "ALERT",
+                 "CRIT",
+                 "ERROR",
+                 "WARN",
+                 "NOTICE",
+                 "INFO",
+                 "DEBUG",
+                 "DEBUG2"
+               };
+
 
+ /*
+  * Get the printer associated with this job; if the printer is stopped for
+  * any reason then job->printer will be reset to NULL, so make sure we have
+  * a valid pointer...
+  */
 
   while ((ptr = cupsdStatBufUpdate(job->status_buffer, &loglevel,
                                    message, sizeof(message))) != NULL)
@@ -3574,9 +4354,11 @@ update_job(cupsd_job_t *job)             /* I - Job to check */
       * job sheet count...
       */
 
+      cupsdLogJob(job, CUPSD_LOG_DEBUG, "PAGE: %s", message);
+
       if (job->sheets)
       {
-        if (!strncasecmp(message, "total ", 6))
+        if (!_cups_strncasecmp(message, "total ", 6))
        {
         /*
          * Got a total count of pages from a backend or filter...
@@ -3591,30 +4373,7 @@ update_job(cupsd_job_t *job)             /* I - Job to check */
         job->sheets->values[0].integer += copies;
 
        if (job->printer->page_limit)
-       {
-         cupsd_quota_t *q = cupsdUpdateQuota(job->printer, job->username,
-                                             copies, 0);
-
-#ifdef __APPLE__
-         if (AppleQuotas && q->page_count == -3)
-         {
-          /*
-           * Quota limit exceeded, cancel job in progress immediately...
-           */
-
-           cupsdLogMessage(CUPSD_LOG_INFO,
-                           "[Job %d] Canceled because pages exceed user %s "
-                           "quota limit on printer %s (%s).",
-                           job->id, job->username, job->printer->name,
-                           job->printer->info);
-
-           cupsdCancelJob(job, 1, IPP_JOB_CANCELED);
-           return;
-         }
-#else
-          (void)q;
-#endif /* __APPLE__ */
-       }
+         cupsdUpdateQuota(job->printer, job->username, copies, 0);
       }
 
       cupsdLogPage(job, message);
@@ -3625,19 +4384,17 @@ update_job(cupsd_job_t *job)            /* I - Job to check */
     }
     else if (loglevel == CUPSD_LOG_STATE)
     {
+      cupsdLogJob(job, CUPSD_LOG_DEBUG, "STATE: %s", message);
+
       if (!strcmp(message, "paused"))
       {
         cupsdStopPrinter(job->printer, 1);
        return;
       }
-      else
-      {
-       cupsdSetPrinterReasons(job->printer, message);
-       cupsdAddPrinterHistory(job->printer);
+      else if (cupsdSetPrinterReasons(job->printer, message))
        event |= CUPSD_EVENT_PRINTER_STATE;
-      }
 
-      update_job_attrs(job);
+      update_job_attrs(job, 0);
     }
     else if (loglevel == CUPSD_LOG_ATTR)
     {
@@ -3650,6 +4407,8 @@ update_job(cupsd_job_t *job)              /* I - Job to check */
       const char       *attr;          /* Attribute */
 
 
+      cupsdLogJob(job, CUPSD_LOG_DEBUG, "ATTR: %s", message);
+
       num_attrs = cupsParseOptions(message, 0, &attrs);
 
       if ((attr = cupsGetOption("auth-info-required", num_attrs,
@@ -3657,7 +4416,28 @@ update_job(cupsd_job_t *job)             /* I - Job to check */
       {
         cupsdSetAuthInfoRequired(job->printer, attr, NULL);
        cupsdSetPrinterAttrs(job->printer);
-       cupsdSaveAllPrinters();
+
+       if (job->printer->type & CUPS_PRINTER_DISCOVERED)
+         cupsdMarkDirty(CUPSD_DIRTY_REMOTE);
+       else
+         cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
+      }
+
+      if ((attr = cupsGetOption("job-media-progress", num_attrs,
+                                attrs)) != NULL)
+      {
+        int progress = atoi(attr);
+
+
+        if (progress >= 0 && progress <= 100)
+       {
+         job->progress = progress;
+
+         if (job->sheets)
+           cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job,
+                         "Printing page %d, %d%%",
+                         job->sheets->values[0].integer, job->progress);
+        }
       }
 
       if ((attr = cupsGetOption("printer-alert", num_attrs, attrs)) != NULL)
@@ -3673,57 +4453,141 @@ update_job(cupsd_job_t *job)           /* I - Job to check */
        event |= CUPSD_EVENT_PRINTER_STATE;
       }
 
+      if ((attr = cupsGetOption("marker-colors", num_attrs, attrs)) != NULL)
+      {
+        cupsdSetPrinterAttr(job->printer, "marker-colors", (char *)attr);
+       job->printer->marker_time = time(NULL);
+       event |= CUPSD_EVENT_PRINTER_STATE;
+        cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
+      }
+
+      if ((attr = cupsGetOption("marker-levels", num_attrs, attrs)) != NULL)
+      {
+        cupsdSetPrinterAttr(job->printer, "marker-levels", (char *)attr);
+       job->printer->marker_time = time(NULL);
+       event |= CUPSD_EVENT_PRINTER_STATE;
+        cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
+      }
+
+      if ((attr = cupsGetOption("marker-low-levels", num_attrs, attrs)) != NULL)
+      {
+        cupsdSetPrinterAttr(job->printer, "marker-low-levels", (char *)attr);
+       job->printer->marker_time = time(NULL);
+       event |= CUPSD_EVENT_PRINTER_STATE;
+        cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
+      }
+
+      if ((attr = cupsGetOption("marker-high-levels", num_attrs, attrs)) != NULL)
+      {
+        cupsdSetPrinterAttr(job->printer, "marker-high-levels", (char *)attr);
+       job->printer->marker_time = time(NULL);
+       event |= CUPSD_EVENT_PRINTER_STATE;
+        cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
+      }
+
+      if ((attr = cupsGetOption("marker-message", num_attrs, attrs)) != NULL)
+      {
+        cupsdSetPrinterAttr(job->printer, "marker-message", (char *)attr);
+       job->printer->marker_time = time(NULL);
+       event |= CUPSD_EVENT_PRINTER_STATE;
+        cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
+      }
+
+      if ((attr = cupsGetOption("marker-names", num_attrs, attrs)) != NULL)
+      {
+        cupsdSetPrinterAttr(job->printer, "marker-names", (char *)attr);
+       job->printer->marker_time = time(NULL);
+       event |= CUPSD_EVENT_PRINTER_STATE;
+        cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
+      }
+
+      if ((attr = cupsGetOption("marker-types", num_attrs, attrs)) != NULL)
+      {
+        cupsdSetPrinterAttr(job->printer, "marker-types", (char *)attr);
+       job->printer->marker_time = time(NULL);
+       event |= CUPSD_EVENT_PRINTER_STATE;
+        cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
+      }
+
       cupsFreeOptions(num_attrs, attrs);
     }
-#ifdef __APPLE__
-    else if (!strncmp(message, "recoverable:", 12))
+    else if (loglevel == CUPSD_LOG_PPD)
     {
-      cupsdSetPrinterReasons(job->printer,
-                             "+com.apple.print.recoverable-warning");
+     /*
+      * Set attribute(s)...
+      */
 
-      ptr = message + 12;
-      while (isspace(*ptr & 255))
-        ptr ++;
+      int              num_keywords;   /* Number of keywords */
+      cups_option_t    *keywords;      /* Keywords */
 
-      cupsdSetString(&job->printer->recoverable, ptr);
-      cupsdAddPrinterHistory(job->printer);
-      event |= CUPSD_EVENT_PRINTER_STATE;
-    }
-    else if (!strncmp(message, "recovered:", 10))
-    {
-      cupsdSetPrinterReasons(job->printer,
-                             "-com.apple.print.recoverable-warning");
 
-      ptr = message + 10;
-      while (isspace(*ptr & 255))
-        ptr ++;
+      cupsdLogJob(job, CUPSD_LOG_DEBUG, "PPD: %s", message);
 
-      cupsdSetString(&job->printer->recoverable, ptr);
-      cupsdAddPrinterHistory(job->printer);
-      event |= CUPSD_EVENT_PRINTER_STATE;
+      num_keywords = cupsParseOptions(message, 0, &keywords);
+
+      if (cupsdUpdatePrinterPPD(job->printer, num_keywords, keywords))
+        cupsdSetPrinterAttrs(job->printer);
+
+      cupsFreeOptions(num_keywords, keywords);
     }
-#endif /* __APPLE__ */
-    else if (loglevel <= job->status_level)
+    else
     {
      /*
-      * Some message to show in the printer-state-message attribute...
+      * Strip legacy message prefix...
       */
 
-      if (loglevel != CUPSD_LOG_NOTICE)
-        job->status_level = loglevel;
+      if (!strncmp(message, "recoverable:", 12))
+      {
+        ptr = message + 12;
+       while (isspace(*ptr & 255))
+          ptr ++;
+      }
+      else if (!strncmp(message, "recovered:", 10))
+      {
+        ptr = message + 10;
+       while (isspace(*ptr & 255))
+          ptr ++;
+      }
+      else
+        ptr = message;
+
+      cupsdLogJob(job, loglevel, "%s", ptr);
+
+      if (loglevel < CUPSD_LOG_DEBUG &&
+          strcmp(job->printer->state_message, ptr))
+      {
+       strlcpy(job->printer->state_message, ptr,
+               sizeof(job->printer->state_message));
+
+       event |= CUPSD_EVENT_PRINTER_STATE | CUPSD_EVENT_JOB_PROGRESS;
+
+       if (loglevel <= job->status_level && job->status_level > CUPSD_LOG_ERROR)
+       {
+        /*
+         * Some messages show in the job-printer-state-message attribute...
+         */
+
+         if (loglevel != CUPSD_LOG_NOTICE)
+           job->status_level = loglevel;
 
-      strlcpy(job->printer->state_message, message,
-              sizeof(job->printer->state_message));
-      cupsdAddPrinterHistory(job->printer);
-      event |= CUPSD_EVENT_PRINTER_STATE;
+         update_job_attrs(job, 1);
 
-      update_job_attrs(job);
+         cupsdLogJob(job, CUPSD_LOG_DEBUG,
+                     "Set job-printer-state-message to \"%s\", "
+                     "current level=%s",
+                     job->printer_message->values[0].string.text,
+                     levels[job->status_level]);
+       }
+      }
     }
 
     if (!strchr(job->status_buffer->buffer, '\n'))
       break;
   }
 
+  if (event & CUPSD_EVENT_JOB_PROGRESS)
+    cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job,
+                  "%s", job->printer->state_message);
   if (event & CUPSD_EVENT_PRINTER_STATE)
     cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, job->printer, NULL,
                  (job->printer->type & CUPS_PRINTER_CLASS) ?
@@ -3731,6 +4595,7 @@ update_job(cupsd_job_t *job)              /* I - Job to check */
                      "Printer \"%s\" state changed.",
                  job->printer->name);
 
+
   if (ptr == NULL && !job->status_buffer->bufused)
   {
    /*
@@ -3741,16 +4606,36 @@ update_job(cupsd_job_t *job)            /* I - Job to check */
     for (i = 0; job->filters[i] < 0; i ++);
 
     if (job->filters[i])
+    {
+     /*
+      * EOF but we haven't collected the exit status of all filters...
+      */
+
+      cupsdCheckProcess();
       return;
+    }
 
     if (job->current_file >= job->num_files && job->backend > 0)
+    {
+     /*
+      * EOF but we haven't collected the exit status of the backend...
+      */
+
+      cupsdCheckProcess();
       return;
+    }
 
    /*
     * Handle the end of job stuff...
     */
 
-    cupsdFinishJob(job);
+    finalize_job(job, 1);
+
+   /*
+    * Check for new jobs...
+    */
+
+    cupsdCheckJobs();
   }
 }
 
@@ -3760,14 +4645,13 @@ update_job(cupsd_job_t *job)            /* I - Job to check */
  */
 
 void
-update_job_attrs(cupsd_job_t *job)     /* I - Job to update */
+update_job_attrs(cupsd_job_t *job,     /* I - Job to update */
+                 int         do_message)/* I - 1 = copy job-printer-state message */
 {
   int                  i;              /* Looping var */
   int                  num_reasons;    /* Actual number of reasons */
   const char * const   *reasons;       /* Reasons */
-  static const char    *none = "none", /* "none" */
-                       *paused = "paused";
-                                       /* "paused" */
+  static const char    *none = "none"; /* "none" reason */
 
 
  /*
@@ -3790,17 +4674,13 @@ update_job_attrs(cupsd_job_t *job)      /* I - Job to update */
                                            IPP_TAG_KEYWORD);
 
  /*
-  * If the job isn't printing, return now...
+  * Copy or clear the printer-state-message value as needed...
   */
 
-  if (!job->printer)
-    return;
-
- /*
-  * Otherwise copy the printer-state-message value...
-  */
-
-  if (job->printer->state_message[0])
+  if (job->state_value != IPP_JOB_PROCESSING &&
+      job->status_level == CUPSD_LOG_INFO)
+    cupsdSetString(&(job->printer_message->values[0].string.text), "");
+  else if (job->printer->state_message[0] && do_message)
     cupsdSetString(&(job->printer_message->values[0].string.text),
                   job->printer->state_message);
 
@@ -3811,7 +4691,7 @@ update_job_attrs(cupsd_job_t *job)        /* I - Job to update */
   if (job->printer->num_reasons == 0)
   {
     num_reasons = 1;
-    reasons     = job->printer->state == IPP_PRINTER_STOPPED ? &paused : &none;
+    reasons     = &none;
   }
   else
   {
@@ -3821,6 +4701,10 @@ update_job_attrs(cupsd_job_t *job)       /* I - Job to update */
 
   if (!job->printer_reasons || job->printer_reasons->num_values != num_reasons)
   {
+   /*
+    * Replace/create a job-printer-state-reasons attribute...
+    */
+
     ippDeleteAttribute(job->attrs, job->printer_reasons);
 
     job->printer_reasons = ippAddStrings(job->attrs,
@@ -3828,12 +4712,36 @@ update_job_attrs(cupsd_job_t *job)      /* I - Job to update */
                                         "job-printer-state-reasons",
                                         num_reasons, NULL, NULL);
   }
+  else
+  {
+   /*
+    * Don't bother clearing the reason strings if they are the same...
+    */
+
+    for (i = 0; i < num_reasons; i ++)
+      if (strcmp(job->printer_reasons->values[i].string.text, reasons[i]))
+        break;
+
+    if (i >= num_reasons)
+      return;
+
+   /*
+    * Not the same, so free the current strings...
+    */
+
+    for (i = 0; i < num_reasons; i ++)
+      _cupsStrFree(job->printer_reasons->values[i].string.text);
+  }
+
+ /*
+  * Copy the reasons...
+  */
 
   for (i = 0; i < num_reasons; i ++)
-    cupsdSetString(&(job->printer_reasons->values[i].string.text), reasons[i]);
+    job->printer_reasons->values[i].string.text = _cupsStrAlloc(reasons[i]);
 }
 
 
 /*
- * End of "$Id: job.c 7005 2007-10-01 23:45:48Z mike $".
+ * End of "$Id: job.c 7902 2008-09-03 14:20:17Z mike $".
  */