]> 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 cd185c4739ac641784542dab41d1c98eb3614e49..59e06dd55843bcf5feefcde1df6c37385fa9576d 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * "$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-2009 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
@@ -45,6 +45,8 @@
  *   compare_active_jobs()      - Compare the job IDs and priorities of two
  *                                jobs.
  *   compare_jobs()             - Compare the job IDs of two jobs.
+ *   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.
@@ -54,8 +56,6 @@
  *   load_next_job_id()         - Load the NextJobId value from the job.cache
  *                                file.
  *   load_request_root()        - Load jobs from the RequestRoot directory.
- *   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.
 #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__ */
 
 
 /*
@@ -165,15 +171,16 @@ 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    finalize_job(cupsd_job_t *job);
+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 int     ipp_length(ipp_t *ipp);
+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_hold_until(cupsd_job_t *job, time_t holdtime);
 static void    set_time(cupsd_job_t *job, const char *name);
 static void    start_job(cupsd_job_t *job, cupsd_printer_t *printer);
 static void    stop_job(cupsd_job_t *job, cupsd_jobaction_t action);
@@ -249,7 +256,7 @@ cupsdCancelJobs(const char *dest,   /* I - Destination to cancel */
       if (purge)
        cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_PURGE,
                         "Job purged by user.");
-      else
+      else if (job->state_value < IPP_JOB_CANCELED)
        cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_DEFAULT,
                         "Job canceled by user.");
     }
@@ -271,23 +278,39 @@ cupsdCheckJobs(void)
   cupsd_printer_t      *printer,       /* Printer destination */
                        *pclass;        /* Printer class destination */
   ipp_attribute_t      *attr;          /* Job attribute */
+  time_t               curtime;        /* Current time */
 
 
   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))
   {
+   /*
+    * Kill jobs if they are unresponsive...
+    */
+
+    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)
       {
@@ -318,12 +341,23 @@ cupsdCheckJobs(void)
                        "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 &&
-        !job->printer)
+    if (job->state_value == IPP_JOB_PENDING && !NeedReload &&
+#ifndef kIOPMAssertionTypeDenySystemSleep
+        !Sleeping &&
+#endif /* !kIOPMAssertionTypeDenySystemSleep */
+        !DoingShutdown && !job->printer)
     {
       printer = cupsdFindDest(job->dest);
       pclass  = NULL;
@@ -408,11 +442,11 @@ 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 && !job->printer)
       cupsdDeleteJob(job, CUPSD_JOB_PURGE);
@@ -428,7 +462,7 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
 {
   int                  i;              /* Looping var */
   int                  slot;           /* Pipe slot */
-  cups_array_t         *filters,       /* Filters for job */
+  cups_array_t         *filters = NULL,/* Filters for job */
                        *prefilters;    /* Filters with prefilters */
   mime_filter_t                *filter,        /* Current filter */
                        *prefilter,     /* Prefilter */
@@ -437,13 +471,17 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
   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];/* Pipes used between filters */
+  int                  filterfds[2][2] = { { -1, -1 }, { -1, -1 } };
+                                       /* Pipes used between filters */
   int                  envc;           /* Number of environment variables */
-  char                 **argv,         /* Filter command-line arguments */
+  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 */
@@ -451,7 +489,7 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
                                        /* Job title string */
                        copies[255],    /* # copies string */
                        *options,       /* Options string */
-                       *envp[MAX_ENV + 19],
+                       *envp[MAX_ENV + 21],
                                        /* Environment variables */
                        charset[255],   /* CHARSET env variable */
                        class_name[255],/* CLASS env variable */
@@ -468,6 +506,8 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
                        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 */
@@ -475,6 +515,8 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
                                        /* 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 */
 
@@ -490,7 +532,11 @@ cupsdContinueJob(cupsd_job_t *job) /* I - Job */
 
   FilterLevel -= job->cost;
 
-  filters = NULL;
+  job->cost         = 0;
+  job->pending_cost = 0;
+
+  memset(job->filters, 0, sizeof(job->filters));
+
 
   if (job->printer->raw)
   {
@@ -500,8 +546,6 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
     */
 
     cupsdLogJob(job, CUPSD_LOG_DEBUG, "Sending job to queue tagged as raw...");
-
-    filters = NULL;
   }
   else
   {
@@ -509,8 +553,14 @@ cupsdContinueJob(cupsd_job_t *job) /* I - Job */
     * Local jobs get filtered...
     */
 
-    filters = mimeFilter(MimeDatabase, job->filetypes[job->current_file],
-                         job->printer->filetype, &(job->cost));
+    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));
 
     if (!filters)
     {
@@ -518,13 +568,10 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
                  "Unable to convert file %d to printable format!",
                  job->current_file);
 
-      job->current_file ++;
-
-      if (job->current_file == job->num_files)
-        cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_DEFAULT,
-                        "Aborting job because it cannot be printed.");
+      abort_message = "Aborting job because it cannot be printed.";
+      abort_state   = IPP_JOB_ABORTED;
 
-      return;
+      goto abort_job;
     }
 
    /*
@@ -598,6 +645,9 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
                "cupsdContinueJob: file=%d, cost=%d, level=%d, limit=%d",
                job->current_file, job->cost, FilterLevel,
                FilterLimit);
+
+    job->pending_cost = job->cost;
+    job->cost         = 0;
     return;
   }
 
@@ -625,12 +675,9 @@ cupsdContinueJob(cupsd_job_t *job) /* I - Job */
 
       cupsArrayDelete(filters);
 
-      cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
-                      "Stopping job because the scheduler ran out of "
-                      "memory.");
+      abort_message = "Stopping job because the scheduler ran out of memory.";
 
-      FilterLevel -= job->cost;
-      return;
+      goto abort_job;
     }
   }
 
@@ -659,14 +706,9 @@ cupsdContinueJob(cupsd_job_t *job) /* I - Job */
       cupsdLogJob(job, CUPSD_LOG_DEBUG,
                  "Unable to add port monitor - %s", strerror(errno));
 
-      cupsArrayDelete(filters);
-
-      cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
-                      "Stopping job because the scheduler ran out of "
-                      "memory.");
+      abort_message = "Stopping job because the scheduler ran out of memory.";
 
-      FilterLevel -= job->cost;
-      return;
+      goto abort_job;
     }
   }
 
@@ -680,13 +722,10 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
                "Too many filters (%d > %d), unable to print!",
                cupsArrayCount(filters), MAX_FILTERS);
 
-    cupsArrayDelete(filters);
-    cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
-                    "Stopping job because it needs too many filters to "
-                    "print.");
+    abort_message = "Aborting job because it needs too many filters to print.";
+    abort_state   = IPP_JOB_ABORTED;
 
-    FilterLevel -= job->cost;
-    return;
+    goto abort_job;
   }
 
  /*
@@ -713,11 +752,11 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
     banner_page = 0;
   else if (job->job_sheets == NULL)
     banner_page = 0;
-  else if (strcasecmp(job->job_sheets->values[0].string.text, "none") != 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 &&
-          strcasecmp(job->job_sheets->values[1].string.text, "none") != 0 &&
+          _cups_strcasecmp(job->job_sheets->values[1].string.text, "none") != 0 &&
           job->current_file == (job->num_files - 1))
     banner_page = 1;
   else
@@ -726,12 +765,9 @@ cupsdContinueJob(cupsd_job_t *job) /* I - Job */
   if ((options = get_options(job, banner_page, copies, sizeof(copies), title,
                              sizeof(title))) == NULL)
   {
-    cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
-                    "Stopping job because the scheduler ran out of memory.");
-    cupsArrayDelete(filters);
+    abort_message = "Stopping job because the scheduler ran out of memory.";
 
-    FilterLevel -= job->cost;
-    return;
+    goto abort_job;
   }
 
  /*
@@ -761,12 +797,10 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
   {
     cupsdLogMessage(CUPSD_LOG_DEBUG, "Unable to allocate argument array - %s",
                     strerror(errno));
-    cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT,
-                    "Stopping job because the scheduler ran out of memory.");
-    cupsArrayDelete(filters);
 
-    FilterLevel -= job->cost;
-    return;
+    abort_message = "Stopping job because the scheduler ran out of memory.";
+
+    goto abort_job;
   }
 
   sprintf(jobid, "%d", job->id);
@@ -862,8 +896,60 @@ cupsdContinueJob(cupsd_job_t *job) /* I - Job */
   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 (psrlen = 22, i = 0; i < job->printer->num_reasons; i ++)
+      psrlen += strlen(job->printer->reasons[i]) + 1;
+
+    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);
 
+  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));
+
   envc = cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0])));
 
   envp[envc ++] = charset;
@@ -878,6 +964,8 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
   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";
 
@@ -890,9 +978,13 @@ cupsdContinueJob(cupsd_job_t *job) /* I - Job */
 
     if (filter && filter->dst)
     {
-      snprintf(final_content_type, sizeof(final_content_type),
-              "FINAL_CONTENT_TYPE=%s/%s",
-              filter->dst->super, filter->dst->type);
+      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;
     }
   }
@@ -920,17 +1012,18 @@ cupsdContinueJob(cupsd_job_t *job)       /* I - Job */
     envp[envc ++] = class_name;
   }
 
-  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;
+  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;
 
-#ifdef HAVE_GSSAPI
-  if (job->ccname)
-    envp[envc ++] = job->ccname;
-#endif /* HAVE_GSSAPI */
+  if (job->auth_uid)
+    envp[envc ++] = job->auth_uid;
 
   envp[envc] = NULL;
 
@@ -953,12 +1046,8 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
   * Now create processes for all of the filters...
   */
 
-  filterfds[0][0] = -1;
-  filterfds[0][1] = -1;
-  filterfds[1][0] = -1;
-  filterfds[1][1] = -1;
-
-  memset(job->filters, 0, sizeof(job->filters));
+  cupsdSetPrinterReasons(job->printer, "-cups-missing-filter-warning,"
+                                      "cups-insecure-filter-warning");
 
   for (i = 0, slot = 0, filter = (mime_filter_t *)cupsArrayFirst(filters);
        filter;
@@ -982,7 +1071,8 @@ cupsdContinueJob(cupsd_job_t *job) /* I - Job */
     }
     else
     {
-      if (job->current_file == 1)
+      if (job->current_file == 1 ||
+          (job->printer->pc && job->printer->pc->single_file))
       {
        if (strncmp(job->printer->device_uri, "file:", 5) != 0)
        {
@@ -1036,7 +1126,7 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
     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->id, job->filters + i);
+                           job->profile, job, job->filters + i);
 
     cupsdClosePipe(filterfds[!slot]);
 
@@ -1045,7 +1135,7 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
       cupsdLogJob(job, CUPSD_LOG_ERROR, "Unable to start filter \"%s\" - %s.",
                  filter->filter, strerror(errno));
 
-      abort_message = "Stopped job because the scheduler could not execute a "
+      abort_message = "Stopping job because the scheduler could not execute a "
                      "filter.";
 
       goto abort_job;
@@ -1067,7 +1157,8 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
 
   if (strncmp(job->printer->device_uri, "file:", 5) != 0)
   {
-    if (job->current_file == 1 || job->printer->remote)
+    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);
@@ -1091,7 +1182,7 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
       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->id, &(job->backend));
+                             backroot, job->profile, job, &(job->backend));
 
       if (pid == 0)
       {
@@ -1107,9 +1198,12 @@ cupsdContinueJob(cupsd_job_t *job)       /* I - Job */
       }
     }
 
+    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)
     {
-      cupsdClosePipe(job->print_pipes);
       cupsdClosePipe(job->back_pipes);
       cupsdClosePipe(job->side_pipes);
 
@@ -1122,10 +1216,12 @@ cupsdContinueJob(cupsd_job_t *job)      /* I - Job */
     filterfds[slot][0] = -1;
     filterfds[slot][1] = -1;
 
-    if (job->current_file == job->num_files)
-    {
+    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)
+    {
       close(job->status_pipes[1]);
       job->status_pipes[1] = -1;
     }
@@ -1140,6 +1236,8 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
   }
 
   free(argv);
+  if (printer_state_reasons)
+    free(printer_state_reasons);
 
   cupsdAddSelect(job->status_buffer->fd, (cupsd_selfunc_t)update_job, NULL,
                  job);
@@ -1157,25 +1255,56 @@ cupsdContinueJob(cupsd_job_t *job)      /* I - Job */
 
   abort_job:
 
+  FilterLevel -= job->cost;
+  job->cost = 0;
+
   for (slot = 0; slot < 2; slot ++)
     cupsdClosePipe(filterfds[slot]);
 
+  cupsArrayDelete(filters);
+
+  if (argv)
+  {
+    if (job->printer->remote && job->num_files > 1)
+    {
+      for (i = 0; i < job->num_files; i ++)
+       free(argv[i + 6]);
+    }
+
+    free(argv);
+  }
+
+  if (printer_state_reasons)
+    free(printer_state_reasons);
+
+  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;
 
-  cupsArrayDelete(filters);
+ /*
+  * Update the printer and job state.
+  */
 
-  if (job->printer->remote && job->num_files > 1)
-  {
-    for (i = 0; i < job->num_files; i ++)
-      free(argv[i + 6]);
-  }
+  cupsdSetJobState(job, abort_state, CUPSD_JOB_DEFAULT, "%s", abort_message);
+  cupsdSetPrinterState(job->printer, IPP_PRINTER_IDLE, 0);
+  update_job_attrs(job, 0);
 
-  free(argv);
+  if (job->history)
+    free_job_history(job);
+
+  cupsArrayRemove(PrintingJobs, job);
+
+ /*
+  * Clear the printer <-> job association...
+  */
 
-  cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT, "%s",
-                   abort_message);
+  job->printer->job = NULL;
+  job->printer      = NULL;
 }
 
 
@@ -1187,8 +1316,12 @@ void
 cupsdDeleteJob(cupsd_job_t       *job, /* I - Job */
                cupsd_jobaction_t action)/* I - Action */
 {
+  int  i;                              /* Looping var */
+  char filename[1024];                 /* Job filename */
+
+
   if (job->printer)
-    finalize_job(job);
+    finalize_job(job, 1);
 
   if (action == CUPSD_JOB_PURGE)
   {
@@ -1196,41 +1329,48 @@ cupsdDeleteJob(cupsd_job_t       *job,  /* I - Job */
     * Remove the job info file...
     */
 
-    char       filename[1024];         /* Job filename */
-
     snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot,
             job->id);
-    unlink(filename);
+    if (Classification)
+      cupsdRemoveFile(filename);
+    else
+      unlink(filename);
   }
 
   cupsdClearString(&job->username);
   cupsdClearString(&job->dest);
-  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 */
+  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 (job->num_files > 0)
   {
     free(job->compressions);
     free(job->filetypes);
 
-    job->num_files = 0;
+    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);
+
+       job->num_files --;
+      }
+    }
+    else
+      job->num_files = 0;
   }
 
+  if (job->history)
+    free_job_history(job);
+
   unload_job(job);
 
   cupsArrayRemove(Jobs, job);
@@ -1256,7 +1396,7 @@ cupsdFreeAllJobs(void)
 
   cupsdHoldSignals();
 
-  cupsdStopAllJobs(1);
+  cupsdStopAllJobs(CUPSD_JOB_FORCE, 0);
   cupsdSaveAllJobs();
 
   for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
@@ -1300,7 +1440,7 @@ cupsdGetPrinterJobCount(
   for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0;
        job;
        job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
-    if (job->dest && !strcasecmp(job->dest, dest))
+    if (job->dest && !_cups_strcasecmp(job->dest, dest))
       count ++;
 
   return (count);
@@ -1323,7 +1463,7 @@ cupsdGetUserJobCount(
   for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0;
        job;
        job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
-    if (!strcasecmp(job->username, username))
+    if (!_cups_strcasecmp(job->username, username))
       count ++;
 
   return (count);
@@ -1411,6 +1551,7 @@ cupsdLoadAllJobs(void)
 int                                    /* O - 1 on success, 0 on failure */
 cupsdLoadJob(cupsd_job_t *job)         /* I - Job */
 {
+  int                  i;              /* Looping var */
   char                 jobfile[1024];  /* Job filename */
   cups_file_t          *fp;            /* Job file */
   int                  fileid;         /* Current file ID */
@@ -1444,16 +1585,25 @@ cupsdLoadJob(cupsd_job_t *job)          /* I - Job */
   snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, job->id);
   if ((fp = cupsFileOpen(jobfile, "r")) == NULL)
   {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                   "[Job %d] Unable to open job control file \"%s\" - %s!",
-                   job->id, jobfile, strerror(errno));
-    goto error;
+    char newfile[1024];                        /* New job filename */
+
+    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;
+    }
+
+    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,
+                   "[Job %d] Unable to read job control file \"%s\".", job->id,
                    jobfile);
     cupsFileClose(fp);
     goto error;
@@ -1609,7 +1759,30 @@ cupsdLoadJob(cupsd_job_t *job)           /* I - Job */
           cupsdLogMessage(CUPSD_LOG_ERROR,
                          "[Job %d] Ran out of memory for job file types!",
                          job->id);
-         return (1);
+
+         ippDelete(job->attrs);
+         job->attrs = NULL;
+
+         if (compressions)
+           free(compressions);
+
+         if (filetypes)
+           free(filetypes);
+
+         if (job->compressions)
+         {
+           free(job->compressions);
+           job->compressions = NULL;
+         }
+
+         if (job->filetypes)
+         {
+           free(job->filetypes);
+           job->filetypes = NULL;
+         }
+
+         job->num_files = 0;
+         return (0);
        }
 
         job->compressions = compressions;
@@ -1634,20 +1807,22 @@ cupsdLoadJob(cupsd_job_t *job)          /* I - Job */
   {
     snprintf(jobfile, sizeof(jobfile), "%s/a%05d", RequestRoot, job->id);
 
-    cupsdClearString(&job->auth_username);
-    cupsdClearString(&job->auth_domain);
-    cupsdClearString(&job->auth_password);
+    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      i,                      /* Looping var */
-               bytes;                  /* Size of auth data */
-      char     line[255],              /* Line from file */
-               data[255];              /* Decoded data */
+      int      bytes;                  /* Size of auth data */
+      char     line[65536],            /* Line from file */
+               data[65536];            /* Decoded data */
 
 
       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 ++)
       {
@@ -1655,13 +1830,18 @@ cupsdLoadJob(cupsd_job_t *job)          /* I - Job */
         httpDecode64_2(data, &bytes, line);
 
        if (!strcmp(destptr->auth_info_required[i], "username"))
-         cupsdSetStringf(&job->auth_username, "AUTH_USERNAME=%s", data);
+         cupsdSetStringf(job->auth_env + i, "AUTH_USERNAME=%s", data);
        else if (!strcmp(destptr->auth_info_required[i], "domain"))
-         cupsdSetStringf(&job->auth_domain, "AUTH_DOMAIN=%s", data);
+         cupsdSetStringf(job->auth_env + i, "AUTH_DOMAIN=%s", data);
        else if (!strcmp(destptr->auth_info_required[i], "password"))
-         cupsdSetStringf(&job->auth_password, "AUTH_PASSWORD=%s", data);
+         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);
       }
 
+      if (cupsFileGets(fp, line, sizeof(line)) && isdigit(line[0] & 255))
+        cupsdSetStringf(&job->auth_uid, "AUTH_UID=%s", line);
+
       cupsFileClose(fp);
     }
   }
@@ -1677,7 +1857,25 @@ cupsdLoadJob(cupsd_job_t *job)           /* I - Job */
 
   ippDelete(job->attrs);
   job->attrs = NULL;
-  unlink(jobfile);
+
+  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);
 }
@@ -1718,8 +1916,9 @@ cupsdMoveJob(cupsd_job_t     *job,        /* I - Job */
   * Change the destination information...
   */
 
-  cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT,
-                   "Stopping job prior to move.");
+  if (job->state_value > IPP_JOB_HELD)
+    cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT,
+                    "Stopping job prior to move.");
 
   cupsdAddEvent(CUPSD_EVENT_JOB_CONFIG_CHANGED, oldp, job,
                 "Job #%d moved from %s to %s.", job->id, olddest,
@@ -1791,30 +1990,19 @@ void
 cupsdSaveAllJobs(void)
 {
   int          i;                      /* Looping var */
-  cups_file_t  *fp;                    /* Job cache file */
-  char         temp[1024];             /* Temporary string */
+  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 */
 
 
-  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));
+  snprintf(filename, sizeof(filename), "%s/job.cache", CacheDir);
+  if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm)) == NULL)
     return;
-  }
-
-  cupsdLogMessage(CUPSD_LOG_INFO, "Saving job cache file \"%s\"...", temp);
-
- /*
-  * Restrict access to the file...
-  */
 
-  fchown(cupsFileNumber(fp), getuid(), Group);
-  fchmod(cupsFileNumber(fp), ConfigFilePerm);
+  cupsdLogMessage(CUPSD_LOG_INFO, "Saving job.cache...");
 
  /*
   * Write a small header to the file...
@@ -1839,6 +2027,7 @@ cupsdSaveAllJobs(void)
     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);
@@ -1849,7 +2038,7 @@ cupsdSaveAllJobs(void)
     cupsFilePuts(fp, "</Job>\n");
   }
 
-  cupsFileClose(fp);
+  cupsdCloseCreatedConfFile(fp, filename);
 }
 
 
@@ -1860,7 +2049,8 @@ cupsdSaveAllJobs(void)
 void
 cupsdSaveJob(cupsd_job_t *job)         /* I - Job */
 {
-  char         filename[1024];         /* Job control filename */
+  char         filename[1024],         /* Job control filename */
+               newfile[1024];          /* New job control filename */
   cups_file_t  *fp;                    /* Job file */
 
 
@@ -1868,12 +2058,13 @@ cupsdSaveJob(cupsd_job_t *job)          /* I - Job */
                   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(filename, "w")) == NULL)
+  if ((fp = cupsFileOpen(newfile, "w")) == NULL)
   {
     cupsdLogMessage(CUPSD_LOG_ERROR,
-                   "[Job %d] Unable to create job control file \"%s\" - %s.",
-                   job->id, filename, strerror(errno));
+                   "[Job %d] Unable to create job control file \"%s\": %s",
+                   job->id, newfile, strerror(errno));
     return;
   }
 
@@ -1884,12 +2075,28 @@ cupsdSaveJob(cupsd_job_t *job)          /* I - Job */
 
   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);
-
-  cupsFileClose(fp);
+                    "[Job %d] Unable to write job control file.", job->id);
+    cupsFileClose(fp);
+    unlink(newfile);
+    return;
+  }
 
-  job->dirty = 0;
+  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;
+  }
 }
 
 
@@ -2136,10 +2343,12 @@ cupsdSetJobState(
     return;
 
  /*
-  * Don't do anything if the state is unchanged...
+  * Don't do anything if the state is unchanged and we aren't purging the
+  * job...
   */
 
-  if (newstate == (oldstate = job->state_value))
+  oldstate = job->state_value;
+  if (newstate == oldstate && action != CUPSD_JOB_PURGE)
     return;
 
  /*
@@ -2147,7 +2356,7 @@ cupsdSetJobState(
   */
 
   if (oldstate == IPP_JOB_PROCESSING)
-    stop_job(job, action != CUPSD_JOB_DEFAULT);
+    stop_job(job, action);
 
  /*
   * Set the new job state...
@@ -2248,17 +2457,32 @@ cupsdSetJobState(
     case IPP_JOB_ABORTED :
     case IPP_JOB_CANCELED :
     case IPP_JOB_COMPLETED :
-       /*
-        * Expire job subscriptions since the job is now "completed"...
-       */
+        if (newstate == IPP_JOB_CANCELED)
+       {
+        /*
+         * Remove the job from the active list if there are no processes still
+         * running for it...
+         */
 
-        cupsdExpireSubscriptions(NULL, 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...
+         */
+
+         cupsArrayRemove(ActiveJobs, job);
+       }
 
        /*
-       * Remove the job from the active list...
+        * Expire job subscriptions since the job is now "completed"...
        */
 
-       cupsArrayRemove(ActiveJobs, job);
+        cupsdExpireSubscriptions(NULL, job);
 
 #ifdef __APPLE__
        /*
@@ -2280,23 +2504,12 @@ cupsdSetJobState(
                          "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;
-       }
+       for (i = 0;
+            i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0]));
+            i ++)
+         cupsdClearString(job->auth_env + i);
 
-       cupsdClearString(&job->ccname);
-#endif /* HAVE_GSSAPI */
+       cupsdClearString(&job->auth_uid);
 
        /*
        * Remove the print file for good if we aren't preserving jobs or
@@ -2309,7 +2522,10 @@ cupsdSetJobState(
          {
            snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
                     job->id, i);
-           unlink(filename);
+           if (Classification)
+             cupsdRemoveFile(filename);
+           else
+             unlink(filename);
          }
 
          if (job->num_files > 0)
@@ -2333,10 +2549,24 @@ cupsdSetJobState(
          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...
   */
@@ -2351,7 +2581,8 @@ cupsdSetJobState(
 
 void
 cupsdStopAllJobs(
-    cupsd_jobaction_t action)          /* I - Action */
+    cupsd_jobaction_t action,          /* I - Action */
+    int               kill_delay)      /* I - Number of seconds before we kill */
 {
   cupsd_job_t  *job;                   /* Current job */
 
@@ -2361,7 +2592,12 @@ cupsdStopAllJobs(
   for (job = (cupsd_job_t *)cupsArrayFirst(PrintingJobs);
        job;
        job = (cupsd_job_t *)cupsArrayNext(PrintingJobs))
+  {
+    if (kill_delay)
+      job->kill_time = time(NULL) + kill_delay;
+
     cupsdSetJobState(job, IPP_JOB_PENDING, action, NULL);
+  }
 }
 
 
@@ -2404,6 +2640,8 @@ compare_active_jobs(void *first,  /* I - First job */
   int  diff;                           /* Difference */
 
 
+  (void)data;
+
   if ((diff = ((cupsd_job_t *)second)->priority -
               ((cupsd_job_t *)first)->priority) != 0)
     return (diff);
@@ -2421,73 +2659,222 @@ compare_jobs(void *first,              /* I - First job */
              void *second,             /* I - Second job */
             void *data)                /* I - App data (not used) */
 {
+  (void)data;
+
   return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
 }
 
 
 /*
- * 'finalize_job()' - Cleanup after job filter processes and support data.
+ * 'dump_job_history()' - Dump any debug messages for a job.
  */
 
 static void
-finalize_job(cupsd_job_t *job)         /* I - Job */
+dump_job_history(cupsd_job_t *job)     /* I - Job */
 {
-  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 */
+  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 */
 
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG2, "finalize_job(job=%p(%d))", job, job->id);
-
  /*
-  * Clear the "connecting-to-device" reason, which is only valid when a
-  * printer is processing...
+  * See if we have anything to dump...
   */
 
-  cupsdSetPrinterReasons(job->printer, "-connecting-to-device");
+  if (!job->history)
+    return;
 
  /*
-  * Similarly, clear the "offline-report" reason for non-USB devices since we
-  * rarely have current information for network devices...
+  * Disable log rotation temporarily...
   */
 
-  if (strncmp(job->printer->device_uri, "usb:", 4))
-    cupsdSetPrinterReasons(job->printer, "-offline-report");
+  oldsize    = MaxLogSize;
+  MaxLogSize = 0;
 
  /*
-  * Free the security profile...
+  * Copy the debug messages to the log...
   */
 
-  cupsdDestroyProfile(job->profile);
-  job->profile = NULL;
+  message = (cupsd_joblog_t *)cupsArrayFirst(job->history);
+  date = localtime(&(message->time));
+  strftime(start, sizeof(start), "%X", date);
 
- /*
-  * Close pipes and status buffer...
-  */
+  message = (cupsd_joblog_t *)cupsArrayLast(job->history);
+  date = localtime(&(message->time));
+  strftime(end, sizeof(end), "%X", date);
 
-  cupsdRemoveSelect(job->status_buffer->fd);
+  snprintf(temp, sizeof(temp),
+           "[Job %d] The following messages were recorded from %s to %s",
+           job->id, start, end);
+  cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp);
 
-  cupsdClosePipe(job->print_pipes);
-  cupsdClosePipe(job->back_pipes);
-  cupsdClosePipe(job->side_pipes);
-  cupsdClosePipe(job->status_pipes);
+  for (message = (cupsd_joblog_t *)cupsArrayFirst(job->history);
+       message;
+       message = (cupsd_joblog_t *)cupsArrayNext(job->history))
+    cupsdWriteErrorLog(CUPSD_LOG_DEBUG, message->message);
 
-  cupsdStatBufDelete(job->status_buffer);
-  job->status_buffer = NULL;
+  snprintf(temp, sizeof(temp), "[Job %d] End of messages", job->id);
+  cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp);
 
  /*
-  * Process the exit status...
+  * Log the printer state values...
   */
 
-  if (job->printer->state == IPP_PRINTER_PROCESSING)
-    printer_state = IPP_PRINTER_IDLE;
-  else
-    printer_state = job->printer->state;
+  if ((printer = job->printer) == NULL)
+    printer = cupsdFindDest(job->dest);
 
-  switch (job_state = job->state_value)
+  if (printer)
   {
-    case IPP_JOB_PENDING :
+    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
+    {
+      for (i = 0;
+           i < printer->num_reasons && ptr < (temp + sizeof(temp) - 2);
+           i ++)
+      {
+        if (i)
+         *ptr++ = ',';
+
+       strlcpy(ptr, printer->reasons[i], sizeof(temp) - (ptr - temp));
+       ptr += strlen(ptr);
+      }
+    }
+    cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp);
+  }
+
+ /*
+  * Restore log file rotation...
+  */
+
+  MaxLogSize = oldsize;
+
+ /*
+  * Free all messages...
+  */
+
+  free_job_history(job);
+}
+
+
+/*
+ * 'free_job_history()' - Free any log history.
+ */
+
+static void
+free_job_history(cupsd_job_t *job)     /* I - Job */
+{
+  char *message;                       /* Current message */
+
+
+  if (!job->history)
+    return;
+
+  for (message = (char *)cupsArrayFirst(job->history);
+       message;
+       message = (char *)cupsArrayNext(job->history))
+    free(message);
+
+  cupsArrayDelete(job->history);
+  job->history = NULL;
+}
+
+
+/*
+ * 'finalize_job()' - Cleanup after job filter processes and support data.
+ */
+
+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 */
+
+
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "finalize_job(job=%p(%d))", job, job->id);
+
+ /*
+  * Clear the "connecting-to-device" reason, which is only valid when a printer
+  * is processing, along with any remote printing job state...
+  */
+
+  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");
+
+ /*
+  * Similarly, clear the "offline-report" reason for non-USB devices since we
+  * rarely have current information for network devices...
+  */
+
+  if (strncmp(job->printer->device_uri, "usb:", 4))
+    cupsdSetPrinterReasons(job->printer, "-offline-report");
+
+ /*
+  * Free the security profile...
+  */
+
+  cupsdDestroyProfile(job->profile);
+  job->profile = NULL;
+
+ /*
+  * Clear the unresponsive job watchdog timer...
+  */
+
+  job->kill_time = 0;
+
+ /*
+  * 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;
+
+ /*
+  * Process the exit status...
+  */
+
+  if (job->printer->state == IPP_PRINTER_PROCESSING)
+    printer_state = IPP_PRINTER_IDLE;
+  else
+    printer_state = job->printer->state;
+
+  switch (job_state = job->state_value)
+  {
+    case IPP_JOB_PENDING :
         message = "Job paused.";
        break;
 
@@ -2498,8 +2885,8 @@ finalize_job(cupsd_job_t *job)            /* I - Job */
     default :
     case IPP_JOB_PROCESSING :
     case IPP_JOB_COMPLETED :
-       job_state     = IPP_JOB_COMPLETED;
-       message       = "Job completed.";
+       job_state = IPP_JOB_COMPLETED;
+       message   = "Job completed.";
         break;
 
     case IPP_JOB_STOPPED :
@@ -2523,7 +2910,6 @@ finalize_job(cupsd_job_t *job)            /* I - Job */
 
     int exit_code;                     /* Exit code from backend */
 
-
    /*
     * 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
@@ -2566,8 +2952,11 @@ finalize_job(cupsd_job_t *job)           /* I - Job */
            * another printer...
            */
 
-            job_state = IPP_JOB_PENDING;
-           message   = "Retrying job 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"))
          {
@@ -2576,59 +2965,66 @@ finalize_job(cupsd_job_t *job)          /* I - Job */
            * and we'll retry on the same printer...
            */
 
-            job_state = IPP_JOB_PENDING;
-           message   = "Retrying job on 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"))
          {
-          /*
-           * 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)
+            if (job_state == IPP_JOB_COMPLETED)
            {
             /*
-             * Too many tries...
+             * 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.
              */
 
-              snprintf(buffer, sizeof(buffer),
-                      "Job aborted after %d unsuccessful attempts.",
-                      JobRetryLimit);
-              job_state = IPP_JOB_ABORTED;
-             message   = buffer;
-           }
-           else
-           {
-            /*
-             * Try again in N seconds...
-             */
+             job->tries ++;
 
-             set_hold_until(job, time(NULL) + JobRetryInterval);
+             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_state = IPP_JOB_HELD;
-             message   = buffer;
-           }
+               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"))
+         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
+         else if (job->state_value == IPP_JOB_PROCESSING)
           {
+            job_state     = IPP_JOB_PENDING;
            printer_state = IPP_PRINTER_STOPPED;
-           job_state     = IPP_JOB_PENDING;
            message       = "Printer stopped due to backend errors; please "
-                           "consult the error_log file for details.";
+                           "consult the error_log file for details.";
          }
           break;
 
@@ -2637,21 +3033,27 @@ finalize_job(cupsd_job_t *job)          /* I - Job */
          * Abort the job...
          */
 
-         job_state = IPP_JOB_ABORTED;
-         message   = "Job aborted due to backend errors; please consult "
-                     "the error_log file for details.";
+         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 :
-         /*
-         * Hold the job...
-         */
+         if (job_state == IPP_JOB_COMPLETED)
+         {
+          /*
+           * Hold the job...
+           */
 
-         cupsdSetJobHoldUntil(job, "indefinite", 1);
+           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.";
+           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 :
@@ -2660,9 +3062,11 @@ finalize_job(cupsd_job_t *job)           /* I - Job */
          */
 
          printer_state = IPP_PRINTER_STOPPED;
-         job_state     = IPP_JOB_PENDING;
          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 :
@@ -2670,10 +3074,64 @@ finalize_job(cupsd_job_t *job)          /* I - Job */
          * Hold the job for authentication...
          */
 
-         cupsdSetJobHoldUntil(job, "auth-info-required", 1);
+         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;
+
+      case CUPS_BACKEND_RETRY_CURRENT :
+        /*
+         * Mark the job as pending and retry on the same printer...
+         */
 
-         job_state = IPP_JOB_HELD;
-         message   = "Job held for authentication.";
+         if (job_state == IPP_JOB_COMPLETED)
+         {
+           job_state = IPP_JOB_PENDING;
+           message   = "Retrying job on same printer.";
+         }
           break;
     }
   }
@@ -2683,20 +3141,35 @@ finalize_job(cupsd_job_t *job)          /* I - Job */
     * Filter had errors; stop job...
     */
 
-    job_state = IPP_JOB_STOPPED;
-    message   = "Job stopped due to filter errors; please consult the "
-               "error_log file for details.";
+    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.";
+    }
   }
 
  /*
   * Update the printer and job state.
   */
 
-  cupsdSetJobState(job, job_state, CUPSD_JOB_DEFAULT, "%s", message);
+  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)
+  {
+    if (job->status &&
+        (job->state_value == IPP_JOB_ABORTED ||
+         job->state_value == IPP_JOB_STOPPED))
+      dump_job_history(job);
+    else
+      free_job_history(job);
+  }
+
   cupsArrayRemove(PrintingJobs, job);
 
  /*
@@ -2728,39 +3201,196 @@ get_options(cupsd_job_t *job,          /* I - Job */
            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 int           optlength = 0;  /* Length of option buffer */
+  static size_t                optlength = 0;  /* Length of option buffer */
 
 
  /*
-  * 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... :)
+  * 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...
+  * First build an options array for any PWG->PPD mapped option/choice pairs.
   */
 
-  i = ipp_length(job->attrs);
+  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...
+    */
+
+    if ((attr = ippFindAttribute(job->attrs, "print-color-mode",
+                                IPP_TAG_KEYWORD)) == NULL)
+      attr = ippFindAttribute(job->attrs, "output-mode", IPP_TAG_KEYWORD);
+
+    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 (i > optlength || !options)
+    if (pc->num_presets[print_color_mode][print_quality] == 0)
+    {
+     /*
+      * Try to find a preset that works so that we maximize the chances of us
+      * getting a good print using IPP attributes.
+      */
+
+      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;
+      }
+    }
+
+    if (pc->num_presets[print_color_mode][print_quality] > 0)
+    {
+     /*
+      * Copy the preset options as long as the corresponding names are not
+      * already defined in the IPP request...
+      */
+
+      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 (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 (!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 (!ippFindAttribute(job->attrs, "media", IPP_TAG_ZERO))
+        num_pwgppds = cupsAddOption("media", ppd, num_pwgppds, &pwgppds);
+    }
+
+    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(i);
+      optptr = malloc(newlength);
     else
-      optptr = realloc(options, i);
+      optptr = realloc(options, newlength);
 
     if (!optptr)
     {
       cupsdLogJob(job, CUPSD_LOG_CRIT,
-                 "Unable to allocate %d bytes for option buffer!", i);
+                 "Unable to allocate " CUPS_LLFMT " bytes for option buffer!",
+                 CUPS_LLCAST newlength);
       return (NULL);
     }
 
     options   = optptr;
-    optlength = i;
+    optlength = newlength;
   }
 
  /*
@@ -2796,7 +3426,8 @@ get_options(cupsd_job_t *job,             /* I - Job */
       * Filter out other unwanted attributes...
       */
 
-      if (attr->value_tag == IPP_TAG_MIMETYPE ||
+      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")) ||
@@ -2804,7 +3435,7 @@ get_options(cupsd_job_t *job,             /* I - Job */
          attr->value_tag == IPP_TAG_BEGIN_COLLECTION) /* Not yet supported */
        continue;
 
-      if (!strncmp(attr->name, "time-", 5))
+      if (!strcmp(attr->name, "job-hold-until"))
        continue;
 
       if (!strncmp(attr->name, "job-", 4) &&
@@ -2821,11 +3452,11 @@ get_options(cupsd_job_t *job,           /* I - Job */
            !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."
+          !_cups_strcasecmp(attr->name, "AP_FIRSTPAGE_InputSlot") ||
+          !_cups_strcasecmp(attr->name, "AP_FIRSTPAGE_ManualFeed") ||
+          !_cups_strcasecmp(attr->name, "com.apple.print.PrintSettings."
                                   "PMTotalSidesImaged..n.") ||
-          !strcasecmp(attr->name, "com.apple.print.PrintSettings."
+          !_cups_strcasecmp(attr->name, "com.apple.print.PrintSettings."
                                   "PMTotalBeginPages..n.")) &&
          banner_page)
         continue;
@@ -2862,7 +3493,6 @@ get_options(cupsd_job_t *job,             /* I - Job */
              if (!attr->values[i].boolean)
                strlcat(optptr, "no", optlength - (optptr - options));
 
-         case IPP_TAG_NOVALUE :
              strlcat(optptr, attr->name,
                      optlength - (optptr - options));
              break;
@@ -2911,6 +3541,25 @@ get_options(cupsd_job_t *job,            /* I - Job */
     }
   }
 
+ /*
+  * Finally loop through the PWG->PPD mapped options and add them...
+  */
+
+  for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++)
+  {
+    *optptr++ = ' ';
+    strcpy(optptr, pwgppd->name);
+    optptr += strlen(optptr);
+    *optptr++ = '=';
+    strcpy(optptr, pwgppd->value);
+    optptr += strlen(optptr);
+  }
+
+  cupsFreeOptions(num_pwgppds, pwgppds);
+
+ /*
+  * Return the options string...
+  */
 
   return (options);
 }
@@ -2921,10 +3570,10 @@ get_options(cupsd_job_t *job,           /* I - Job */
  *                 the textual IPP attributes.
  */
 
-static int                             /* O - Size of attribute buffer */
+static size_t                          /* O - Size of attribute buffer */
 ipp_length(ipp_t *ipp)                 /* I - IPP request */
 {
-  int                  bytes;          /* Number of bytes */
+  size_t               bytes;          /* Number of bytes */
   int                  i;              /* Looping var */
   ipp_attribute_t      *attr;          /* Current attribute */
 
@@ -2941,16 +3590,14 @@ ipp_length(ipp_t *ipp)                  /* I - IPP request */
     * Skip attributes that won't be sent to filters...
     */
 
-    if (attr->value_tag == IPP_TAG_MIMETYPE ||
+    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;
 
-    if (strncmp(attr->name, "time-", 5) == 0)
-      continue;
-
    /*
     * Add space for a leading space and commas between each value.
     * For the first attribute, the leading space isn't used, so the
@@ -3059,15 +3706,9 @@ load_job_cache(const char *filename)     /* I - job.cache filename */
   * Open the job.cache file...
   */
 
-  if ((fp = cupsFileOpen(filename, "r")) == NULL)
+  if ((fp = cupsdOpenConfFile(filename)) == NULL)
   {
-    if (errno != ENOENT)
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                      "Unable to open job cache file \"%s\": %s",
-                      filename, strerror(errno));
-
     load_request_root();
-
     return;
   }
 
@@ -3083,12 +3724,12 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
 
   while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
   {
-    if (!strcasecmp(line, "NextJobId"))
+    if (!_cups_strcasecmp(line, "NextJobId"))
     {
       if (value)
         NextJobId = atoi(value);
     }
-    else if (!strcasecmp(line, "<Job"))
+    else if (!_cups_strcasecmp(line, "<Job"))
     {
       if (job)
       {
@@ -3115,9 +3756,13 @@ load_job_cache(const char *filename)     /* I - job.cache filename */
       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;
+       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));
@@ -3147,15 +3792,12 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
                      "Missing <Job #> directive on line %d!", linenum);
       continue;
     }
-    else if (!strcasecmp(line, "</Job>"))
+    else if (!_cups_strcasecmp(line, "</Job>"))
     {
       cupsArrayAdd(Jobs, job);
 
-      if (job->state_value <= IPP_JOB_STOPPED)
-      {
-        cupsArrayAdd(ActiveJobs, job);
-       cupsdLoadJob(job);
-      }
+      if (job->state_value <= IPP_JOB_STOPPED && cupsdLoadJob(job))
+       cupsArrayAdd(ActiveJobs, job);
 
       job = NULL;
     }
@@ -3164,7 +3806,7 @@ load_job_cache(const char *filename)      /* I - job.cache filename */
       cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d!", linenum);
       continue;
     }
-    else if (!strcasecmp(line, "State"))
+    else if (!_cups_strcasecmp(line, "State"))
     {
       job->state_value = (ipp_jstate_t)atoi(value);
 
@@ -3173,23 +3815,27 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
       else if (job->state_value > IPP_JOB_COMPLETED)
         job->state_value = IPP_JOB_COMPLETED;
     }
-    else if (!strcasecmp(line, "Priority"))
+    else if (!_cups_strcasecmp(line, "HoldUntil"))
+    {
+      job->hold_until = atoi(value);
+    }
+    else if (!_cups_strcasecmp(line, "Priority"))
     {
       job->priority = atoi(value);
     }
-    else if (!strcasecmp(line, "Username"))
+    else if (!_cups_strcasecmp(line, "Username"))
     {
       cupsdSetString(&job->username, value);
     }
-    else if (!strcasecmp(line, "Destination"))
+    else if (!_cups_strcasecmp(line, "Destination"))
     {
       cupsdSetString(&job->dest, value);
     }
-    else if (!strcasecmp(line, "DestType"))
+    else if (!_cups_strcasecmp(line, "DestType"))
     {
       job->dtype = (cups_ptype_t)atoi(value);
     }
-    else if (!strcasecmp(line, "NumFiles"))
+    else if (!_cups_strcasecmp(line, "NumFiles"))
     {
       job->num_files = atoi(value);
 
@@ -3225,7 +3871,7 @@ load_job_cache(const char *filename)      /* I - job.cache filename */
        }
       }
     }
-    else if (!strcasecmp(line, "File"))
+    else if (!_cups_strcasecmp(line, "File"))
     {
       int      number,                 /* File number */
                compression;            /* Compression value */
@@ -3321,7 +3967,7 @@ load_next_job_id(const char *filename)    /* I - job.cache filename */
 
   while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
   {
-    if (!strcasecmp(line, "NextJobId"))
+    if (!_cups_strcasecmp(line, "NextJobId"))
     {
       if (value)
       {
@@ -3403,76 +4049,25 @@ load_request_root(void)
       * Load the job...
       */
 
-      cupsdLoadJob(job);
-
-     /*
-      * Insert the job into the array, sorting by job priority and ID...
-      */
+      if (cupsdLoadJob(job))
+      {
+       /*
+        * Insert the job into the array, sorting by job priority and ID...
+        */
 
-      cupsArrayAdd(Jobs, job);
+       cupsArrayAdd(Jobs, job);
 
-      if (job->state_value <= IPP_JOB_STOPPED)
-        cupsArrayAdd(ActiveJobs, job);
-      else
-        unload_job(job);
+       if (job->state_value <= IPP_JOB_STOPPED)
+         cupsArrayAdd(ActiveJobs, job);
+       else
+         unload_job(job);
+      }
     }
 
   cupsDirClose(dir);
 }
 
 
-/*
- * 'set_hold_until()' - Set the hold time and update job-hold-until attribute.
- */
-
-static void
-set_hold_until(cupsd_job_t *job,       /* I - Job to update */
-              time_t      holdtime)    /* I - Hold until time */
-{
-  ipp_attribute_t      *attr;          /* job-hold-until attribute */
-  struct tm            *holddate;      /* Hold date */
-  char                 holdstr[64];    /* Hold time */
-
-
- /*
-  * Set the hold_until value and hold the job...
-  */
-
-  cupsdLogMessage(CUPSD_LOG_DEBUG, "set_hold_until: hold_until = %d",
-                  (int)holdtime);
-
-  job->state->values[0].integer = IPP_JOB_HELD;
-  job->state_value              = IPP_JOB_HELD;
-  job->hold_until               = holdtime;
-
- /*
-  * 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);
-
-  if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
-                               IPP_TAG_KEYWORD)) == NULL)
-    attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
-
- /*
-  * Either add the attribute or update the value of the existing one
-  */
-
-  if (attr == NULL)
-    ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD, "job-hold-until",
-                 NULL, holdstr);
-  else
-    cupsdSetString(&attr->values[0].string.text, holdstr);
-
-  job->dirty = 1;
-  cupsdMarkDirty(CUPSD_DIRTY_JOBS);
-}
-
-
 /*
  * 'set_time()' - Set one of the "time-at-xyz" attributes.
  */
@@ -3521,8 +4116,18 @@ start_job(cupsd_job_t     *job,          /* I - Job ID */
   if (!cupsdLoadJob(job))
     return;
 
+  if (job->printer_message)
+    cupsdSetString(&(job->printer_message->values[0].string.text), "");
+
   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;
@@ -3614,6 +4219,11 @@ start_job(cupsd_job_t     *job,          /* I - Job ID */
   fcntl(job->side_pipes[1], F_SETFL,
        fcntl(job->side_pipes[1], F_GETFL) | O_NONBLOCK);
 
+  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);
+
  /*
   * Now start the first file in the job...
   */
@@ -3639,12 +4249,36 @@ stop_job(cupsd_job_t       *job,        /* I - Job */
   FilterLevel -= job->cost;
   job->cost   = 0;
 
+  if (action == CUPSD_JOB_DEFAULT && !job->kill_time)
+    job->kill_time = time(NULL) + JobKillDelay;
+  else if (action >= CUPSD_JOB_FORCE)
+    job->kill_time = 0;
+
   for (i = 0; job->filters[i]; i ++)
     if (job->filters[i] > 0)
-      cupsdEndProcess(job->filters[i], action == CUPSD_JOB_FORCE);
+    {
+      cupsdEndProcess(job->filters[i], action >= CUPSD_JOB_FORCE);
+
+      if (action >= CUPSD_JOB_FORCE)
+        job->filters[i] = -job->filters[i];
+    }
 
   if (job->backend > 0)
-    cupsdEndProcess(job->backend, action == CUPSD_JOB_FORCE);
+  {
+    cupsdEndProcess(job->backend, action >= CUPSD_JOB_FORCE);
+
+    if (action >= CUPSD_JOB_FORCE)
+      job->backend = -job->backend;
+  }
+
+  if (action >= CUPSD_JOB_FORCE)
+  {
+   /*
+    * Clear job status...
+    */
+
+    job->status = 0;
+  }
 }
 
 
@@ -3680,7 +4314,8 @@ 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? */
@@ -3723,7 +4358,7 @@ update_job(cupsd_job_t *job)              /* I - Job to check */
 
       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...
@@ -3738,28 +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...
-           */
-
-           cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_DEFAULT,
-                            "Canceled job because pages exceed user %s "
-                            "quota limit on printer %s (%s).",
-                            job->username, job->printer->name,
-                            job->printer->info);
-           return;
-         }
-#else
-          (void)q;
-#endif /* __APPLE__ */
-       }
+         cupsdUpdateQuota(job->printer, job->username, copies, 0);
       }
 
       cupsdLogPage(job, message);
@@ -3777,12 +4391,8 @@ update_job(cupsd_job_t *job)             /* I - Job to check */
         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, 0);
     }
@@ -3920,50 +4530,38 @@ update_job(cupsd_job_t *job)            /* I - Job to check */
 
       cupsFreeOptions(num_keywords, keywords);
     }
-#ifdef __APPLE__
-    else if (!strncmp(message, "recoverable:", 12))
+    else
     {
-      ptr = message + 12;
-      while (isspace(*ptr & 255))
-        ptr ++;
+     /*
+      * Strip legacy message prefix...
+      */
 
-      if (*ptr)
+      if (!strncmp(message, "recoverable:", 12))
       {
-       cupsdSetPrinterReasons(job->printer,
-                              "+com.apple.print.recoverable-warning");
-       cupsdSetString(&(job->printer->recoverable), ptr);
-       cupsdAddPrinterHistory(job->printer);
-       event |= CUPSD_EVENT_PRINTER_STATE;
+        ptr = message + 12;
+       while (isspace(*ptr & 255))
+          ptr ++;
       }
-    }
-    else if (!strncmp(message, "recovered:", 10))
-    {
-      cupsdSetPrinterReasons(job->printer,
-                             "-com.apple.print.recoverable-warning");
+      else if (!strncmp(message, "recovered:", 10))
+      {
+        ptr = message + 10;
+       while (isspace(*ptr & 255))
+          ptr ++;
+      }
+      else
+        ptr = message;
 
-      ptr = message + 10;
-      while (isspace(*ptr & 255))
-        ptr ++;
+      cupsdLogJob(job, loglevel, "%s", ptr);
 
-      cupsdSetString(&(job->printer->recoverable), ptr);
-      cupsdAddPrinterHistory(job->printer);
-      event |= CUPSD_EVENT_PRINTER_STATE;
-    }
-#endif /* __APPLE__ */
-    else
-    {
-      if (loglevel != CUPSD_LOG_INFO && loglevel > LogLevel)
-       cupsdLogJob(job, loglevel, "%d %s", loglevel, message);
-
-      if (loglevel < CUPSD_LOG_DEBUG)
+      if (loglevel < CUPSD_LOG_DEBUG &&
+          strcmp(job->printer->state_message, ptr))
       {
-       strlcpy(job->printer->state_message, message,
+       strlcpy(job->printer->state_message, ptr,
                sizeof(job->printer->state_message));
-       cupsdAddPrinterHistory(job->printer);
 
-       event |= CUPSD_EVENT_PRINTER_STATE;
+       event |= CUPSD_EVENT_PRINTER_STATE | CUPSD_EVENT_JOB_PROGRESS;
 
-       if (loglevel <= job->status_level)
+       if (loglevel <= job->status_level && job->status_level > CUPSD_LOG_ERROR)
        {
         /*
          * Some messages show in the job-printer-state-message attribute...
@@ -3987,6 +4585,9 @@ update_job(cupsd_job_t *job)              /* I - Job to check */
       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) ?
@@ -3994,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)
   {
    /*
@@ -4004,16 +4606,30 @@ 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...
     */
 
-    finalize_job(job);
+    finalize_job(job, 1);
 
    /*
     * Check for new jobs...
@@ -4067,7 +4683,7 @@ update_job_attrs(cupsd_job_t *job,        /* I - Job to update */
   else if (job->printer->state_message[0] && do_message)
     cupsdSetString(&(job->printer_message->values[0].string.text),
                   job->printer->state_message);
-  
+
  /*
   * ... and the printer-state-reasons value...
   */