]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - scheduler/job.c
License change: Apache License, Version 2.0.
[thirdparty/cups.git] / scheduler / job.c
index 59e06dd55843bcf5feefcde1df6c37385fa9576d..b2d8afd2feaf3a82e9c5e02bac167d9420009261 100644 (file)
@@ -1,67 +1,10 @@
 /*
- * "$Id: job.c 7902 2008-09-03 14:20:17Z mike $"
+ * Job management routines for the CUPS scheduler.
  *
- *   Job management routines for the CUPS scheduler.
+ * Copyright 2007-2017 by Apple Inc.
+ * Copyright 1997-2007 by Easy Software Products, all rights reserved.
  *
- *   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
- *   property of Apple Inc. and are protected by Federal copyright
- *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
- *   which should have been included with this file.  If this file is
- *   file is missing or damaged, see the license at "http://www.cups.org/".
- *
- * Contents:
- *
- *   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.
- *   cupsdCleanJobs()           - Clean out old jobs.
- *   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, 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.
- *   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.
- *   cupsdSetJobState()         - Set the state of the specified print job.
- *   cupsdStopAllJobs()         - Stop all print jobs.
- *   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.
- *   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_request_root()        - Load jobs from the RequestRoot directory.
- *   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 job's filters.
- *   update_job_attrs()         - Update the job-printer-* attributes.
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
  */
 
 /*
@@ -170,6 +113,7 @@ static mime_filter_t        gziptoany_filter =
  */
 
 static int     compare_active_jobs(void *first, void *second, void *data);
+static int     compare_completed_jobs(void *first, void *second, void *data);
 static int     compare_jobs(void *first, void *second, void *data);
 static void    dump_job_history(cupsd_job_t *job);
 static void    finalize_job(cupsd_job_t *job, int set_job_state);
@@ -181,6 +125,8 @@ 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    remove_job_files(cupsd_job_t *job);
+static void    remove_job_history(cupsd_job_t *job);
 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);
@@ -261,8 +207,6 @@ cupsdCancelJobs(const char *dest,   /* I - Destination to cancel */
                         "Job canceled by user.");
     }
   }
-
-  cupsdCheckJobs();
 }
 
 
@@ -279,31 +223,56 @@ cupsdCheckJobs(void)
                        *pclass;        /* Printer class destination */
   ipp_attribute_t      *attr;          /* Job attribute */
   time_t               curtime;        /* Current time */
+  const char           *reasons;       /* job-state-reasons value */
 
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                  "cupsdCheckJobs: %d active jobs, sleeping=%d, reload=%d",
-                  cupsArrayCount(ActiveJobs), Sleeping, NeedReload);
-
   curtime = time(NULL);
 
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCheckJobs: %d active jobs, sleeping=%d, ac-power=%d, reload=%d, curtime=%ld", cupsArrayCount(ActiveJobs), Sleeping, ACPower, NeedReload, (long)curtime);
+
   for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
        job;
        job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
   {
+    cupsdLogMessage(CUPSD_LOG_DEBUG2,
+                    "cupsdCheckJobs: Job %d - dest=\"%s\", printer=%p, "
+                    "state=%d, cancel_time=%ld, hold_until=%ld, kill_time=%ld, "
+                    "pending_cost=%d, pending_timeout=%ld", job->id, job->dest,
+                    job->printer, job->state_value, (long)job->cancel_time,
+                    (long)job->hold_until, (long)job->kill_time,
+                    job->pending_cost, (long)job->pending_timeout);
+
    /*
     * 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);
+      if (!job->completed)
+        cupsdLogJob(job, CUPSD_LOG_ERROR, "Stopping unresponsive job.");
 
       stop_job(job, CUPSD_JOB_FORCE);
       continue;
     }
 
+   /*
+    * Cancel stuck jobs...
+    */
+
+    if (job->cancel_time && job->cancel_time <= curtime)
+    {
+      int cancel_after;                        /* job-cancel-after value */
+
+      attr         = ippFindAttribute(job->attrs, "job-cancel-after", IPP_TAG_INTEGER);
+      cancel_after = attr ? ippGetInteger(attr, 0) : MaxJobTime;
+
+      if (job->completed)
+       cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_FORCE, "Marking stuck job as completed after %d seconds.", cancel_after);
+      else
+       cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_DEFAULT, "Canceling stuck job after %d seconds.", cancel_after);
+      continue;
+    }
+
    /*
     * Start held jobs if they are ready...
     */
@@ -322,7 +291,6 @@ cupsdCheckJobs(void)
 
         cupsd_client_t *con;           /* Current client connection */
 
-
        for (con = (cupsd_client_t *)cupsArrayFirst(Clients);
             con;
             con = (cupsd_client_t *)cupsArrayNext(Clients))
@@ -349,21 +317,36 @@ cupsdCheckJobs(void)
        ((FilterLevel + job->pending_cost) < FilterLimit || FilterLevel == 0))
       cupsdContinueJob(job);
 
+   /*
+    * Skip jobs that where held-on-create
+    */
+
+    reasons = ippGetString(job->reasons, 0, NULL);
+    if (reasons && !strcmp(reasons, "job-held-on-create"))
+    {
+     /*
+      * Check whether the printer is still holding new jobs...
+      */
+
+      printer = cupsdFindDest(job->dest);
+
+      if (printer->holding_new_jobs)
+        continue;
+
+      ippSetString(job->attrs, &job->reasons, 0, "none");
+    }
+
    /*
     * Start pending jobs if the destination is available...
     */
 
     if (job->state_value == IPP_JOB_PENDING && !NeedReload &&
-#ifndef kIOPMAssertionTypeDenySystemSleep
-        !Sleeping &&
-#endif /* !kIOPMAssertionTypeDenySystemSleep */
-        !DoingShutdown && !job->printer)
+        (!Sleeping || ACPower) && !DoingShutdown && !job->printer)
     {
       printer = cupsdFindDest(job->dest);
       pclass  = NULL;
 
-      while (printer &&
-             (printer->type & (CUPS_PRINTER_IMPLICIT | CUPS_PRINTER_CLASS)))
+      while (printer && (printer->type & CUPS_PRINTER_CLASS))
       {
        /*
         * If the class is remote, just pass it to the remote server...
@@ -390,7 +373,7 @@ cupsdCheckJobs(void)
                         "Job aborted because the destination printer/class "
                         "has gone away.");
       }
-      else if (printer && !printer->holding_new_jobs)
+      else if (printer)
       {
        /*
         * See if the printer is available or remote and not printing a job;
@@ -400,31 +383,28 @@ cupsdCheckJobs(void)
         if (pclass)
        {
         /*
-         * Add/update a job-actual-printer-uri attribute for this job
+         * Add/update a job-printer-uri-actual attribute for this job
          * so that we know which printer actually printed the job...
          */
 
-          if ((attr = ippFindAttribute(job->attrs, "job-actual-printer-uri",
-                                      IPP_TAG_URI)) != NULL)
-            cupsdSetString(&attr->values[0].string.text, printer->uri);
+          if ((attr = ippFindAttribute(job->attrs, "job-printer-uri-actual", IPP_TAG_URI)) != NULL)
+            ippSetString(job->attrs, &attr, 0, printer->uri);
          else
-           ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI,
-                        "job-actual-printer-uri", NULL, printer->uri);
+           ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri-actual", 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, OR */
-           ((printer->type & CUPS_PRINTER_DISCOVERED) && /* Printer is remote */
-            !printer->job))                            /* and not printing */
+        if (!printer->job && printer->state == IPP_PRINTER_IDLE)
         {
         /*
          * Start the job...
          */
 
+         cupsArraySave(ActiveJobs);
          start_job(job, printer);
+         cupsArrayRestore(ActiveJobs);
        }
       }
     }
@@ -440,16 +420,59 @@ void
 cupsdCleanJobs(void)
 {
   cupsd_job_t  *job;                   /* Current job */
+  time_t       curtime;                /* Current time */
+
 
+  cupsdLogMessage(CUPSD_LOG_DEBUG2,
+                  "cupsdCleanJobs: MaxJobs=%d, JobHistory=%d, JobFiles=%d",
+                  MaxJobs, JobHistory, JobFiles);
 
-  if (MaxJobs <= 0 && JobHistory)
+  if (MaxJobs <= 0 && JobHistory == INT_MAX && JobFiles == INT_MAX)
     return;
 
+  curtime          = time(NULL);
+  JobHistoryUpdate = 0;
+
   for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
-       job && (cupsArrayCount(Jobs) >= MaxJobs || !JobHistory);
+       job;
        job = (cupsd_job_t *)cupsArrayNext(Jobs))
+  {
     if (job->state_value >= IPP_JOB_CANCELED && !job->printer)
-      cupsdDeleteJob(job, CUPSD_JOB_PURGE);
+    {
+     /*
+      * Expire old jobs (or job files)...
+      */
+
+      if ((MaxJobs > 0 && cupsArrayCount(Jobs) >= MaxJobs) ||
+          (job->history_time && job->history_time <= curtime))
+      {
+        cupsdLogJob(job, CUPSD_LOG_DEBUG, "Removing from history.");
+       cupsdDeleteJob(job, CUPSD_JOB_PURGE);
+      }
+      else if (job->file_time && job->file_time <= curtime)
+      {
+        cupsdLogJob(job, CUPSD_LOG_DEBUG, "Removing document files.");
+        cupsdLogJob(job, CUPSD_LOG_DEBUG2, "curtime=%ld, job->file_time=%ld", (long)curtime, (long)job->file_time);
+        remove_job_files(job);
+
+        cupsdMarkDirty(CUPSD_DIRTY_JOBS);
+
+        if (job->history_time < JobHistoryUpdate || !JobHistoryUpdate)
+         JobHistoryUpdate = job->history_time;
+      }
+      else
+      {
+        if (job->history_time < JobHistoryUpdate || !JobHistoryUpdate)
+         JobHistoryUpdate = job->history_time;
+
+       if (job->file_time < JobHistoryUpdate || !JobHistoryUpdate)
+         JobHistoryUpdate = job->file_time;
+      }
+    }
+  }
+
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCleanJobs: JobHistoryUpdate=%ld",
+                  (long)JobHistoryUpdate);
 }
 
 
@@ -481,6 +504,7 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
                                        /* Pipes used between filters */
   int                  envc;           /* Number of environment variables */
   struct stat          fileinfo;       /* Job file information */
+  int                  argc = 0;       /* Number of arguments */
   char                 **argv = NULL,  /* Filter command-line arguments */
                        filename[1024], /* Job filename */
                        command[1024],  /* Full path to command */
@@ -499,7 +523,7 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
                                        /* CONTENT_TYPE env variable */
                        device_uri[1024],
                                        /* DEVICE_URI env variable */
-                       final_content_type[1024],
+                       final_content_type[1024] = "",
                                        /* FINAL_CONTENT_TYPE env variable */
                        lang[255],      /* LANG env variable */
 #ifdef __APPLE__
@@ -537,7 +561,6 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
 
   memset(job->filters, 0, sizeof(job->filters));
 
-
   if (job->printer->raw)
   {
    /*
@@ -553,27 +576,92 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
     * Local jobs get filtered...
     */
 
+    mime_type_t        *dst = job->printer->filetype;
+                                       /* Destination file type */
+
     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 (job->retry_as_raster)
+    {
+     /*
+      * Need to figure out whether the printer supports image/pwg-raster or
+      * image/urf, and use the corresponding type...
+      */
+
+      char     type[MIME_MAX_TYPE];    /* MIME media type for printer */
+
+      snprintf(type, sizeof(type), "%s/image/urf", job->printer->name);
+      if ((dst = mimeType(MimeDatabase, "printer", type)) == NULL)
+      {
+       snprintf(type, sizeof(type), "%s/image/pwg-raster", job->printer->name);
+       dst = mimeType(MimeDatabase, "printer", type);
+      }
+
+      if (dst)
+        cupsdLogJob(job, CUPSD_LOG_DEBUG, "Retrying job as \"%s\".", strchr(dst->type, '/') + 1);
+      else
+        cupsdLogJob(job, CUPSD_LOG_ERROR, "Unable to retry job using a supported raster format.");
+    }
+
+    filters = mimeFilter2(MimeDatabase, job->filetypes[job->current_file], (size_t)fileinfo.st_size, dst, &(job->cost));
 
     if (!filters)
     {
       cupsdLogJob(job, CUPSD_LOG_ERROR,
-                 "Unable to convert file %d to printable format!",
+                 "Unable to convert file %d to printable format.",
                  job->current_file);
 
       abort_message = "Aborting job because it cannot be printed.";
       abort_state   = IPP_JOB_ABORTED;
 
+      ippSetString(job->attrs, &job->reasons, 0, "document-unprintable-error");
       goto abort_job;
     }
 
+   /*
+    * Figure out the final content type...
+    */
+
+    cupsdLogJob(job, CUPSD_LOG_DEBUG, "%d filters for job:",
+                cupsArrayCount(filters));
+    for (filter = (mime_filter_t *)cupsArrayFirst(filters);
+         filter;
+         filter = (mime_filter_t *)cupsArrayNext(filters))
+      cupsdLogJob(job, CUPSD_LOG_DEBUG, "%s (%s/%s to %s/%s, cost %d)",
+                 filter->filter,
+                 filter->src ? filter->src->super : "???",
+                 filter->src ? filter->src->type : "???",
+                 filter->dst ? filter->dst->super : "???",
+                 filter->dst ? filter->dst->type : "???",
+                 filter->cost);
+
+    if (!job->printer->remote)
+    {
+      for (filter = (mime_filter_t *)cupsArrayLast(filters);
+           filter && filter->dst;
+           filter = (mime_filter_t *)cupsArrayPrev(filters))
+        if (strcmp(filter->dst->super, "printer") ||
+            strcmp(filter->dst->type, job->printer->name))
+          break;
+
+      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);
+      }
+      else
+        snprintf(final_content_type, sizeof(final_content_type),
+                 "FINAL_CONTENT_TYPE=printer/%s", job->printer->name);
+    }
+
    /*
     * Remove NULL ("-") filters...
     */
@@ -657,9 +745,8 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
   * Add decompression/raw filter as needed...
   */
 
-  if ((!job->printer->raw && job->compressions[job->current_file]) ||
-      (!filters && !job->printer->remote &&
-       (job->num_files > 1 || !strncmp(job->printer->device_uri, "file:", 5))))
+  if ((job->compressions[job->current_file] && (!job->printer->remote || job->num_files == 1)) ||
+      (!job->printer->remote && job->printer->raw && job->num_files > 1))
   {
    /*
     * Add gziptoany filter to the front of the list...
@@ -719,12 +806,14 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
   if (cupsArrayCount(filters) > MAX_FILTERS)
   {
     cupsdLogJob(job, CUPSD_LOG_DEBUG,
-               "Too many filters (%d > %d), unable to print!",
+               "Too many filters (%d > %d), unable to print.",
                cupsArrayCount(filters), MAX_FILTERS);
 
     abort_message = "Aborting job because it needs too many filters to print.";
     abort_state   = IPP_JOB_ABORTED;
 
+    ippSetString(job->attrs, &job->reasons, 0, "document-unprintable-error");
+
     goto abort_job;
   }
 
@@ -738,7 +827,7 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
     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!");
+                 "... but someone added one without setting job_sheets.");
   }
   else if (job->job_sheets->num_values == 1)
     cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-sheets=%s",
@@ -748,7 +837,7 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
                 job->job_sheets->values[0].string.text,
                 job->job_sheets->values[1].string.text);
 
-  if (job->printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT))
+  if (job->printer->type & CUPS_PRINTER_REMOTE)
     banner_page = 0;
   else if (job->job_sheets == NULL)
     banner_page = 0;
@@ -789,11 +878,11 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
   */
 
   if (job->printer->remote)
-    argv = calloc(7 + job->num_files, sizeof(char *));
+    argc = 6 + job->num_files;
   else
-    argv = calloc(8, sizeof(char *));
+    argc = 7;
 
-  if (!argv)
+  if ((argv = calloc((size_t)argc + 1, sizeof(char *))) == NULL)
   {
     cupsdLogMessage(CUPSD_LOG_DEBUG, "Unable to allocate argument array - %s",
                     strerror(errno));
@@ -825,7 +914,7 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
   {
     snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
              job->id, job->current_file + 1);
-    argv[6] = filename;
+    argv[6] = strdup(filename);
   }
 
   for (i = 0; argv[i]; i ++)
@@ -839,7 +928,7 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
                           IPP_TAG_LANGUAGE);
 
 #ifdef __APPLE__
-  strcpy(apple_language, "APPLE_LANGUAGE=");
+  strlcpy(apple_language, "APPLE_LANGUAGE=", sizeof(apple_language));
   _cupsAppleLanguage(attr->values[0].string.text,
                     apple_language + 15, sizeof(apple_language) - 15);
 #endif /* __APPLE__ */
@@ -852,7 +941,7 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
        * the POSIX locale...
        */
 
-       strcpy(lang, "LANG=C");
+       strlcpy(lang, "LANG=C", sizeof(lang));
        break;
 
     case 2 :
@@ -910,14 +999,14 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
       * All of these strcpy's are safe because we allocated the psr string...
       */
 
-      strcpy(printer_state_reasons, "PRINTER_STATE_REASONS=");
+      strlcpy(printer_state_reasons, "PRINTER_STATE_REASONS=", psrlen);
       for (psrptr = printer_state_reasons + 22, i = 0;
            i < job->printer->num_reasons;
           i ++)
       {
         if (i)
          *psrptr++ = ',';
-       strcpy(psrptr, job->printer->reasons[i]);
+       strlcpy(psrptr, job->printer->reasons[i], psrlen - (size_t)(psrptr - printer_state_reasons));
        psrptr += strlen(psrptr);
       }
     }
@@ -969,25 +1058,8 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
   envp[envc ++] = banner_page ? "CUPS_FILETYPE=job-sheet" :
                                 "CUPS_FILETYPE=document";
 
-  if (!job->printer->remote && !job->printer->raw)
-  {
-    filter = (mime_filter_t *)cupsArrayLast(filters);
-
-    if (job->printer->port_monitor)
-      filter = (mime_filter_t *)cupsArrayPrev(filters);
-
-    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;
-    }
-  }
+  if (final_content_type[0])
+    envp[envc ++] = final_content_type;
 
   if (Classification && !banner_page)
   {
@@ -1006,7 +1078,7 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
     envp[envc ++] = classification;
   }
 
-  if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))
+  if (job->dtype & CUPS_PRINTER_CLASS)
   {
     snprintf(class_name, sizeof(class_name), "CLASS=%s", job->dest);
     envp[envc ++] = class_name;
@@ -1046,9 +1118,6 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
   * Now create processes for all of the filters...
   */
 
-  cupsdSetPrinterReasons(job->printer, "-cups-missing-filter-warning,"
-                                      "cups-insecure-filter-warning");
-
   for (i = 0, slot = 0, filter = (mime_filter_t *)cupsArrayFirst(filters);
        filter;
        i ++, filter = (mime_filter_t *)cupsArrayNext(filters))
@@ -1144,8 +1213,13 @@ cupsdContinueJob(cupsd_job_t *job)       /* I - Job */
     cupsdLogJob(job, CUPSD_LOG_INFO, "Started filter %s (PID %d)", command,
                 pid);
 
-    argv[6] = NULL;
-    slot    = !slot;
+    if (argv[6])
+    {
+      free(argv[6]);
+      argv[6] = NULL;
+    }
+
+    slot = !slot;
   }
 
   cupsArrayDelete(filters);
@@ -1172,7 +1246,7 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
       else if (stat(command, &backinfo))
        backroot = 0;
       else
-        backroot = !(backinfo.st_mode & (S_IRWXG | S_IRWXO));
+        backroot = !(backinfo.st_mode & (S_IWGRP | S_IRWXO));
 
       argv[0] = job->printer->sanitized_device_uri;
 
@@ -1182,7 +1256,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, &(job->backend));
+                             backroot, job->bprofile, job, &(job->backend));
 
       if (pid == 0)
       {
@@ -1229,13 +1303,12 @@ cupsdContinueJob(cupsd_job_t *job)      /* I - Job */
 
   cupsdClosePipe(filterfds[slot]);
 
-  if (job->printer->remote && job->num_files > 1)
-  {
-    for (i = 0; i < job->num_files; i ++)
-      free(argv[i + 6]);
-  }
+  for (i = 6; i < argc; i ++)
+    if (argv[i])
+      free(argv[i]);
 
   free(argv);
+
   if (printer_state_reasons)
     free(printer_state_reasons);
 
@@ -1265,13 +1338,9 @@ cupsdContinueJob(cupsd_job_t *job)       /* I - Job */
 
   if (argv)
   {
-    if (job->printer->remote && job->num_files > 1)
-    {
-      for (i = 0; i < job->num_files; i ++)
-       free(argv[i + 6]);
-    }
-
-    free(argv);
+    for (i = 6; i < argc; i ++)
+      if (argv[i])
+       free(argv[i]);
   }
 
   if (printer_state_reasons)
@@ -1317,25 +1386,13 @@ 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, 1);
 
   if (action == CUPSD_JOB_PURGE)
-  {
-   /*
-    * Remove the job info file...
-    */
-
-    snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot,
-            job->id);
-    if (Classification)
-      cupsdRemoveFile(filename);
-    else
-      unlink(filename);
-  }
+    remove_job_history(job);
 
   cupsdClearString(&job->username);
   cupsdClearString(&job->dest);
@@ -1345,27 +1402,14 @@ cupsdDeleteJob(cupsd_job_t       *job,  /* I - Job */
     cupsdClearString(job->auth_env + i);
   cupsdClearString(&job->auth_uid);
 
-  if (job->num_files > 0)
+  if (action == CUPSD_JOB_PURGE)
+    remove_job_files(job);
+  else if (job->num_files > 0)
   {
     free(job->compressions);
     free(job->filetypes);
 
-    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;
+    job->num_files = 0;
   }
 
   if (job->history)
@@ -1424,6 +1468,30 @@ cupsdFindJob(int id)                     /* I - Job ID */
 }
 
 
+/*
+ * 'cupsdGetCompletedJobs()'- Generate a completed jobs list.
+ */
+
+cups_array_t *                         /* O - Array of jobs */
+cupsdGetCompletedJobs(
+    cupsd_printer_t *p)                        /* I - Printer */
+{
+  cups_array_t *list;                  /* Array of jobs */
+  cupsd_job_t  *job;                   /* Current job */
+
+
+  list = cupsArrayNew(compare_completed_jobs, NULL);
+
+  for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
+       job;
+       job = (cupsd_job_t *)cupsArrayNext(Jobs))
+    if ((!p || !_cups_strcasecmp(p->name, job->dest)) && job->state_value >= IPP_JOB_STOPPED && job->completed_time)
+      cupsArrayAdd(list, job);
+
+  return (list);
+}
+
+
 /*
  * 'cupsdGetPrinterJobCount()' - Get the number of pending, processing,
  *                               or held jobs in a printer or class.
@@ -1478,9 +1546,10 @@ 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 */
-
+  struct stat  fileinfo;               /* Information on job.cache file */
+  cups_dir_t   *dir;                   /* RequestRoot dir */
+  cups_dentry_t        *dent;                  /* Entry in RequestRoot */
+  int          load_cache = 1;         /* Load the job.cache file? */
 
 
  /*
@@ -1504,36 +1573,65 @@ cupsdLoadAllJobs(void)
 
   if (stat(filename, &fileinfo))
   {
-    fileinfo.st_mtime = 0;
+   /*
+    * No job.cache file...
+    */
+
+    load_cache = 0;
 
     if (errno != ENOENT)
       cupsdLogMessage(CUPSD_LOG_ERROR,
                       "Unable to get file information for \"%s\" - %s",
                      filename, strerror(errno));
   }
+  else if ((dir = cupsDirOpen(RequestRoot)) == NULL)
+  {
+   /*
+    * No spool directory...
+    */
 
-  if (stat(RequestRoot, &dirinfo))
+    load_cache = 0;
+  }
+  else
   {
-    dirinfo.st_mtime = 0;
+    while ((dent = cupsDirRead(dir)) != NULL)
+    {
+      if (strlen(dent->filename) >= 6 && dent->filename[0] == 'c' && dent->fileinfo.st_mtime > fileinfo.st_mtime)
+      {
+       /*
+        * Job history file is newer than job.cache file...
+       */
 
-    if (errno != ENOENT)
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                      "Unable to get directory information for \"%s\" - %s",
-                     RequestRoot, strerror(errno));
+        load_cache = 0;
+       break;
+      }
+    }
+
+    cupsDirClose(dir);
   }
 
  /*
   * Load the most recent source for job data...
   */
 
-  if (dirinfo.st_mtime > fileinfo.st_mtime)
+  if (load_cache)
   {
+   /*
+    * Load the job.cache file...
+    */
+
+    load_job_cache(filename);
+  }
+  else
+  {
+   /*
+    * Load the job history files...
+    */
+
     load_request_root();
 
     load_next_job_id(filename);
   }
-  else
-    load_job_cache(filename);
 
  /*
   * Clean out old jobs as needed...
@@ -1572,7 +1670,7 @@ cupsdLoadJob(cupsd_job_t *job)            /* I - Job */
 
   if ((job->attrs = ippNew()) == NULL)
   {
-    cupsdLogJob(job, CUPSD_LOG_ERROR, "Ran out of memory for job attributes!");
+    cupsdLogJob(job, CUPSD_LOG_ERROR, "Ran out of memory for job attributes.");
     return (0);
   }
 
@@ -1580,31 +1678,16 @@ cupsdLoadJob(cupsd_job_t *job)          /* I - Job */
   * Load job attributes...
   */
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Loading attributes...", job->id);
+  cupsdLogJob(job, CUPSD_LOG_DEBUG, "Loading attributes...");
 
   snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, job->id);
-  if ((fp = cupsFileOpen(jobfile, "r")) == NULL)
-  {
-    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 ((fp = cupsdOpenConfFile(jobfile)) == NULL)
+    goto error;
 
   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);
+    cupsdLogJob(job, CUPSD_LOG_ERROR,
+               "Unable to read job control file \"%s\".", jobfile);
     cupsFileClose(fp);
     goto error;
   }
@@ -1617,40 +1700,71 @@ cupsdLoadJob(cupsd_job_t *job)          /* I - Job */
 
   if (!ippFindAttribute(job->attrs, "time-at-creation", IPP_TAG_INTEGER))
   {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                   "[Job %d] Missing or bad time-at-creation attribute in "
-                   "control file!", job->id);
+    cupsdLogJob(job, CUPSD_LOG_ERROR,
+               "Missing or bad time-at-creation attribute in control file.");
     goto error;
   }
 
   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);
+    cupsdLogJob(job, CUPSD_LOG_ERROR,
+               "Missing or bad job-state attribute in control file.");
     goto error;
   }
 
-  job->state_value = (ipp_jstate_t)job->state->values[0].integer;
+  job->state_value  = (ipp_jstate_t)job->state->values[0].integer;
+  job->file_time    = 0;
+  job->history_time = 0;
+
+  if ((attr = ippFindAttribute(job->attrs, "time-at-creation", IPP_TAG_INTEGER)) != NULL)
+    job->creation_time = attr->values[0].integer;
+
+  if (job->state_value >= IPP_JOB_CANCELED && (attr = ippFindAttribute(job->attrs, "time-at-completed", IPP_TAG_INTEGER)) != NULL)
+  {
+    job->completed_time = attr->values[0].integer;
+
+    if (JobHistory < INT_MAX)
+      job->history_time = attr->values[0].integer + JobHistory;
+    else
+      job->history_time = INT_MAX;
+
+    if (job->history_time < time(NULL))
+      goto error;                      /* Expired, remove from history */
+
+    if (job->history_time < JobHistoryUpdate || !JobHistoryUpdate)
+      JobHistoryUpdate = job->history_time;
+
+    if (JobFiles < INT_MAX)
+      job->file_time = attr->values[0].integer + JobFiles;
+    else
+      job->file_time = INT_MAX;
+
+    cupsdLogJob(job, CUPSD_LOG_DEBUG2, "cupsdLoadJob: job->file_time=%ld, time-at-completed=%ld, JobFiles=%d", (long)job->file_time, (long)attr->values[0].integer, JobFiles);
+
+    if (job->file_time < JobHistoryUpdate || !JobHistoryUpdate)
+      JobHistoryUpdate = job->file_time;
+
+    cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdLoadJob: JobHistoryUpdate=%ld",
+                   (long)JobHistoryUpdate);
+  }
 
   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);
+      cupsdLogJob(job, CUPSD_LOG_ERROR,
+                 "No job-printer-uri attribute in control file.");
       goto error;
     }
 
     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);
+      cupsdLogJob(job, CUPSD_LOG_ERROR,
+                 "Unable to queue job for destination \"%s\".",
+                 attr->values[0].string.text);
       goto error;
     }
 
@@ -1658,24 +1772,88 @@ cupsdLoadJob(cupsd_job_t *job)          /* I - Job */
   }
   else if ((destptr = cupsdFindDest(job->dest)) == NULL)
   {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                   "[Job %d] Unable to queue job for destination \"%s\"!",
-                   job->id, job->dest);
+    cupsdLogJob(job, CUPSD_LOG_ERROR,
+               "Unable to queue job for destination \"%s\".",
+               job->dest);
     goto error;
   }
 
-  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->reasons = ippFindAttribute(job->attrs, "job-state-reasons",
+                                       IPP_TAG_KEYWORD)) == NULL)
+  {
+    const char *reason;                /* job-state-reason keyword */
+
+    cupsdLogJob(job, CUPSD_LOG_DEBUG,
+               "Adding missing job-state-reasons attribute to  control file.");
+
+    switch (job->state_value)
+    {
+      default :
+      case IPP_JOB_PENDING :
+          if (destptr->state == IPP_PRINTER_STOPPED)
+            reason = "printer-stopped";
+          else
+            reason = "none";
+          break;
+
+      case IPP_JOB_HELD :
+          if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
+                                       IPP_TAG_ZERO)) != NULL &&
+              (attr->value_tag == IPP_TAG_NAME ||
+              attr->value_tag == IPP_TAG_NAMELANG ||
+              attr->value_tag == IPP_TAG_KEYWORD) &&
+             strcmp(attr->values[0].string.text, "no-hold"))
+           reason = "job-hold-until-specified";
+         else
+           reason = "job-incoming";
+          break;
+
+      case IPP_JOB_PROCESSING :
+          reason = "job-printing";
+          break;
+
+      case IPP_JOB_STOPPED :
+          reason = "job-stopped";
+          break;
+
+      case IPP_JOB_CANCELED :
+          reason = "job-canceled-by-user";
+          break;
+
+      case IPP_JOB_ABORTED :
+          reason = "aborted-by-system";
+          break;
+
+      case IPP_JOB_COMPLETED :
+          reason = "job-completed-successfully";
+          break;
+    }
+
+    job->reasons = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD,
+                                "job-state-reasons", NULL, reason);
+  }
+  else if (job->state_value == IPP_JOB_PENDING)
+  {
+    if (destptr->state == IPP_PRINTER_STOPPED)
+      ippSetString(job->attrs, &job->reasons, 0, "printer-stopped");
+    else
+      ippSetString(job->attrs, &job->reasons, 0, "none");
+  }
+
+  job->impressions = ippFindAttribute(job->attrs, "job-impressions-completed", IPP_TAG_INTEGER);
+  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->impressions)
+    job->impressions = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-impressions-completed", 0);
 
   if (!job->priority)
   {
     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);
+      cupsdLogJob(job, CUPSD_LOG_ERROR,
+                 "Missing or bad job-priority attribute in control file.");
       goto error;
     }
 
@@ -1687,15 +1865,21 @@ cupsdLoadJob(cupsd_job_t *job)          /* I - Job */
     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);
+      cupsdLogJob(job, CUPSD_LOG_ERROR,
+                 "Missing or bad job-originating-user-name "
+                 "attribute in control file.");
       goto error;
     }
 
     cupsdSetString(&job->username, attr->values[0].string.text);
   }
 
+  if (!job->name)
+  {
+    if ((attr = ippFindAttribute(job->attrs, "job-name", IPP_TAG_NAME)) != NULL)
+      cupsdSetString(&job->name, attr->values[0].string.text);
+  }
+
  /*
   * Set the job hold-until time and state...
   */
@@ -1720,6 +1904,9 @@ cupsdLoadJob(cupsd_job_t *job)            /* I - Job */
     job->state_value              = IPP_JOB_PENDING;
   }
 
+  if ((attr = ippFindAttribute(job->attrs, "job-k-octets", IPP_TAG_INTEGER)) != NULL)
+    job->koctets = attr->values[0].integer;
+
   if (!job->num_files)
   {
    /*
@@ -1734,41 +1921,36 @@ cupsdLoadJob(cupsd_job_t *job)          /* I - Job */
       if (access(jobfile, 0))
         break;
 
-      cupsdLogMessage(CUPSD_LOG_DEBUG,
-                     "[Job %d] Auto-typing document file \"%s\"...", job->id,
-                     jobfile);
+      cupsdLogJob(job, CUPSD_LOG_DEBUG,
+                 "Auto-typing document file \"%s\"...", jobfile);
 
       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 *));
+         compressions = (int *)calloc((size_t)fileid, sizeof(int));
+         filetypes    = (mime_type_t **)calloc((size_t)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);
+         compressions = (int *)realloc(job->compressions, sizeof(int) * (size_t)fileid);
+         filetypes    = (mime_type_t **)realloc(job->filetypes, sizeof(mime_type_t *) * (size_t)fileid);
         }
 
+       if (compressions)
+         job->compressions = compressions;
+
+       if (filetypes)
+         job->filetypes = filetypes;
+
         if (!compressions || !filetypes)
        {
-          cupsdLogMessage(CUPSD_LOG_ERROR,
-                         "[Job %d] Ran out of memory for job file types!",
-                         job->id);
+          cupsdLogJob(job, CUPSD_LOG_ERROR,
+                     "Ran out of memory for job file types.");
 
          ippDelete(job->attrs);
          job->attrs = NULL;
 
-         if (compressions)
-           free(compressions);
-
-         if (filetypes)
-           free(filetypes);
-
          if (job->compressions)
          {
            free(job->compressions);
@@ -1785,9 +1967,7 @@ cupsdLoadJob(cupsd_job_t *job)            /* I - Job */
          return (0);
        }
 
-        job->compressions = compressions;
-        job->filetypes    = filetypes;
-       job->num_files    = fileid;
+       job->num_files = fileid;
       }
 
       job->filetypes[fileid - 1] = mimeFileType(MimeDatabase, jobfile, NULL,
@@ -1815,32 +1995,55 @@ cupsdLoadJob(cupsd_job_t *job)          /* I - Job */
 
     if ((fp = cupsFileOpen(jobfile, "r")) != NULL)
     {
-      int      bytes;                  /* Size of auth data */
+      int      bytes,                  /* Size of auth data */
+               linenum = 1;            /* Current line number */
       char     line[65536],            /* Line from file */
+               *value,                 /* Value from line */
                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 ++)
+      if (cupsFileGets(fp, line, sizeof(line)) &&
+          !strcmp(line, "CUPSD-AUTH-V3"))
       {
-        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);
-      }
+        i = 0;
+        while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
+        {
+         /*
+          * Decode value...
+          */
 
-      if (cupsFileGets(fp, line, sizeof(line)) && isdigit(line[0] & 255))
-        cupsdSetStringf(&job->auth_uid, "AUTH_UID=%s", line);
+          if (strcmp(line, "negotiate") && strcmp(line, "uid"))
+          {
+           bytes = sizeof(data);
+           httpDecode64_2(data, &bytes, value);
+         }
+
+         /*
+          * Assign environment variables...
+          */
+
+          if (!strcmp(line, "uid"))
+          {
+            cupsdSetStringf(&job->auth_uid, "AUTH_UID=%s", value);
+            continue;
+          }
+          else if (i >= (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0])))
+            break;
+
+         if (!strcmp(line, "username"))
+           cupsdSetStringf(job->auth_env + i, "AUTH_USERNAME=%s", data);
+         else if (!strcmp(line, "domain"))
+           cupsdSetStringf(job->auth_env + i, "AUTH_DOMAIN=%s", data);
+         else if (!strcmp(line, "password"))
+           cupsdSetStringf(job->auth_env + i, "AUTH_PASSWORD=%s", data);
+         else if (!strcmp(line, "negotiate"))
+           cupsdSetStringf(job->auth_env + i, "AUTH_NEGOTIATE=%s", value);
+         else
+           continue;
+
+         i ++;
+       }
+      }
 
       cupsFileClose(fp);
     }
@@ -1858,24 +2061,8 @@ cupsdLoadJob(cupsd_job_t *job)           /* I - Job */
   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);
+  remove_job_history(job);
+  remove_job_files(job);
 
   return (0);
 }
@@ -1925,12 +2112,11 @@ cupsdMoveJob(cupsd_job_t     *job,      /* I - Job */
                p->name);
 
   cupsdSetString(&job->dest, p->name);
-  job->dtype = p->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE |
-                          CUPS_PRINTER_IMPLICIT);
+  job->dtype = p->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE);
 
   if ((attr = ippFindAttribute(job->attrs, "job-printer-uri",
                                IPP_TAG_URI)) != NULL)
-    cupsdSetString(&(attr->values[0].string.text), p->uri);
+    ippSetString(job->attrs, &attr, 0, p->uri);
 
   cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, p, job,
                 "Job #%d moved from %s to %s.", job->id, olddest,
@@ -2024,13 +2210,29 @@ cupsdSaveAllJobs(void)
        job;
        job = (cupsd_job_t *)cupsArrayNext(Jobs))
   {
+    if (job->printer && job->printer->temporary)
+    {
+     /*
+      * Don't save jobs on temporary printers...
+      */
+
+      continue;
+    }
+
     cupsFilePrintf(fp, "<Job %d>\n", job->id);
     cupsFilePrintf(fp, "State %d\n", job->state_value);
+    cupsFilePrintf(fp, "Created %ld\n", (long)job->creation_time);
+    if (job->completed_time)
+      cupsFilePrintf(fp, "Completed %ld\n", (long)job->completed_time);
     cupsFilePrintf(fp, "Priority %d\n", job->priority);
-    cupsFilePrintf(fp, "HoldUntil %d\n", (int)job->hold_until);
+    if (job->hold_until)
+      cupsFilePrintf(fp, "HoldUntil %ld\n", (long)job->hold_until);
     cupsFilePrintf(fp, "Username %s\n", job->username);
+    if (job->name)
+      cupsFilePutConf(fp, "Name", job->name);
     cupsFilePrintf(fp, "Destination %s\n", job->dest);
     cupsFilePrintf(fp, "DestType %d\n", job->dtype);
+    cupsFilePrintf(fp, "KOctets %d\n", job->koctets);
     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,
@@ -2049,26 +2251,28 @@ cupsdSaveAllJobs(void)
 void
 cupsdSaveJob(cupsd_job_t *job)         /* I - Job */
 {
-  char         filename[1024],         /* Job control filename */
-               newfile[1024];          /* New job control filename */
+  char         filename[1024];         /* Job control filename */
   cups_file_t  *fp;                    /* Job file */
 
 
   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)
+  if (job->printer && job->printer->temporary)
   {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                   "[Job %d] Unable to create job control file \"%s\": %s",
-                   job->id, newfile, strerror(errno));
+   /*
+    * Don't save jobs on temporary printers...
+    */
+
+    job->dirty = 0;
     return;
   }
 
-  fchmod(cupsFileNumber(fp), 0600);
+  snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, job->id);
+
+  if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm & 0600)) == NULL)
+    return;
+
   fchown(cupsFileNumber(fp), RunUser, Group);
 
   job->attrs->state = IPP_IDLE;
@@ -2076,26 +2280,21 @@ 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);
+    cupsdLogJob(job, CUPSD_LOG_ERROR, "Unable to write job control file.");
     cupsFileClose(fp);
-    unlink(newfile);
     return;
   }
 
-  if (cupsFileClose(fp))
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                   "[Job %d] Unable to close job control file: %s",
-                   job->id, strerror(errno));
-  else
+  if (!cupsdCloseCreatedConfFile(fp, filename))
   {
+   /*
+    * Remove backup file and mark this job as clean...
+    */
+
+    strlcat(filename, ".O", sizeof(filename));
     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;
+
+    job->dirty = 0;
   }
 }
 
@@ -2133,7 +2332,7 @@ cupsdSetJobHoldUntil(cupsd_job_t *job,    /* I - Job */
       attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
 
     if (attr)
-      cupsdSetString(&(attr->values[0].string.text), when);
+      ippSetString(job->attrs, &attr, 0, when);
     else
       attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD,
                           "job-hold-until", NULL, when);
@@ -2148,12 +2347,20 @@ cupsdSetJobHoldUntil(cupsd_job_t *job,  /* I - Job */
       job->dirty = 1;
       cupsdMarkDirty(CUPSD_DIRTY_JOBS);
     }
+
   }
 
+  if (strcmp(when, "no-hold"))
+    ippSetString(job->attrs, &job->reasons, 0, "job-hold-until-specified");
+  else
+    ippSetString(job->attrs, &job->reasons, 0, "none");
+
  /*
   * Update the hold time...
   */
 
+  job->cancel_time = 0;
+
   if (!strcmp(when, "indefinite") || !strcmp(when, "auth-info-required"))
   {
    /*
@@ -2161,6 +2368,9 @@ cupsdSetJobHoldUntil(cupsd_job_t *job,    /* I - Job */
     */
 
     job->hold_until = 0;
+
+    if (MaxHoldTime > 0)
+      job->cancel_time = time(NULL) + MaxHoldTime;
   }
   else if (!strcmp(when, "day-time"))
   {
@@ -2362,8 +2572,10 @@ cupsdSetJobState(
   * Set the new job state...
   */
 
-  job->state->values[0].integer = newstate;
-  job->state_value              = newstate;
+  job->state_value = newstate;
+
+  if (job->state)
+    job->state->values[0].integer = newstate;
 
   switch (newstate)
   {
@@ -2378,8 +2590,8 @@ cupsdSetJobState(
 
        if (attr)
        {
-         attr->value_tag = IPP_TAG_KEYWORD;
-         cupsdSetString(&(attr->values[0].string.text), "no-hold");
+         ippSetValueTag(job->attrs, &attr, IPP_TAG_KEYWORD);
+         ippSetString(job->attrs, &attr, 0, "no-hold");
        }
 
     default :
@@ -2389,6 +2601,7 @@ cupsdSetJobState(
     case IPP_JOB_CANCELED :
     case IPP_JOB_COMPLETED :
        set_time(job, "time-at-completed");
+       ippSetString(job->attrs, &job->reasons, 0, "processing-to-stop-point");
         break;
   }
 
@@ -2517,27 +2730,7 @@ cupsdSetJobState(
        */
 
        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);
-         }
-
-         if (job->num_files > 0)
-         {
-           free(job->filetypes);
-           free(job->compressions);
-
-           job->num_files    = 0;
-           job->filetypes    = NULL;
-           job->compressions = NULL;
-         }
-       }
+         remove_job_files(job);
 
        if (JobHistory && action != CUPSD_JOB_PURGE)
        {
@@ -2571,7 +2764,7 @@ cupsdSetJobState(
   * Update the server "busy" state...
   */
 
-  cupsdSetBusyState();
+  cupsdSetBusyState(0);
 }
 
 
@@ -2593,10 +2786,17 @@ cupsdStopAllJobs(
        job;
        job = (cupsd_job_t *)cupsArrayNext(PrintingJobs))
   {
-    if (kill_delay)
-      job->kill_time = time(NULL) + kill_delay;
+    if (job->completed)
+    {
+      cupsdSetJobState(job, IPP_JOB_COMPLETED, CUPSD_JOB_FORCE, NULL);
+    }
+    else
+    {
+      if (kill_delay)
+        job->kill_time = time(NULL) + kill_delay;
 
-    cupsdSetJobState(job, IPP_JOB_PENDING, action, NULL);
+      cupsdSetJobState(job, IPP_JOB_PENDING, action, NULL);
+    }
   }
 }
 
@@ -2623,27 +2823,108 @@ cupsdUnloadCompletedJobs(void)
       if (job->dirty)
         cupsdSaveJob(job);
 
-      unload_job(job);
+      if (!job->dirty)
+        unload_job(job);
     }
 }
 
 
+/*
+ * 'cupsdUpdateJobs()' - Update the history/file files for all jobs.
+ */
+
+void
+cupsdUpdateJobs(void)
+{
+  cupsd_job_t          *job;           /* Current job */
+  time_t               curtime;        /* Current time */
+  ipp_attribute_t      *attr;          /* time-at-completed attribute */
+
+
+  curtime          = time(NULL);
+  JobHistoryUpdate = 0;
+
+  for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
+       job;
+       job = (cupsd_job_t *)cupsArrayNext(Jobs))
+  {
+    if (job->state_value >= IPP_JOB_CANCELED &&
+        (attr = ippFindAttribute(job->attrs, "time-at-completed",
+                                 IPP_TAG_INTEGER)) != NULL)
+    {
+     /*
+      * Update history/file expiration times...
+      */
+
+      if (JobHistory < INT_MAX)
+       job->history_time = attr->values[0].integer + JobHistory;
+      else
+       job->history_time = INT_MAX;
+
+      if (job->history_time < curtime)
+      {
+        cupsdDeleteJob(job, CUPSD_JOB_PURGE);
+        continue;
+      }
+
+      if (job->history_time < JobHistoryUpdate || !JobHistoryUpdate)
+       JobHistoryUpdate = job->history_time;
+
+      if (JobFiles < INT_MAX)
+       job->file_time = attr->values[0].integer + JobFiles;
+      else
+       job->file_time = INT_MAX;
+
+      cupsdLogJob(job, CUPSD_LOG_DEBUG2, "cupsdUpdateJobs: job->file_time=%ld, time-at-completed=%ld, JobFiles=%d", (long)job->file_time, (long)attr->values[0].integer, JobFiles);
+
+      if (job->file_time < JobHistoryUpdate || !JobHistoryUpdate)
+       JobHistoryUpdate = job->file_time;
+    }
+  }
+
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdUpdateJobs: JobHistoryUpdate=%ld",
+                  (long)JobHistoryUpdate);
+}
+
+
 /*
  * 'compare_active_jobs()' - Compare the job IDs and priorities of two jobs.
  */
 
 static int                             /* O - Difference */
-compare_active_jobs(void *first,       /* I - First job */
-                    void *second,      /* I - Second job */
-                   void *data)         /* I - App data (not used) */
+compare_active_jobs(void *first,       /* I - First job */
+                    void *second,      /* I - Second job */
+                   void *data)         /* I - App data (not used) */
+{
+  int  diff;                           /* Difference */
+
+
+  (void)data;
+
+  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);
+}
+
+
+/*
+ * 'compare_completed_jobs()' - Compare the job IDs and completion times of two jobs.
+ */
+
+static int                             /* O - Difference */
+compare_completed_jobs(void *first,    /* I - First job */
+                       void *second,   /* I - Second job */
+                      void *data)      /* I - App data (not used) */
 {
   int  diff;                           /* Difference */
 
 
   (void)data;
 
-  if ((diff = ((cupsd_job_t *)second)->priority -
-              ((cupsd_job_t *)first)->priority) != 0)
+  if ((diff = ((cupsd_job_t *)second)->completed_time -
+              ((cupsd_job_t *)first)->completed_time) != 0)
     return (diff);
   else
     return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
@@ -2745,7 +3026,7 @@ dump_job_history(cupsd_job_t *job)        /* I - Job */
     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));
+      strlcpy(ptr, "none", sizeof(temp) - (size_t)(ptr - temp));
     else
     {
       for (i = 0;
@@ -2755,7 +3036,7 @@ dump_job_history(cupsd_job_t *job)        /* I - Job */
         if (i)
          *ptr++ = ',';
 
-       strlcpy(ptr, printer->reasons[i], sizeof(temp) - (ptr - temp));
+       strlcpy(ptr, printer->reasons[i], sizeof(temp) - (size_t)(ptr - temp));
        ptr += strlen(ptr);
       }
     }
@@ -2816,11 +3097,13 @@ finalize_job(cupsd_job_t *job,          /* I - 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, along with any remote printing job state...
+  * Clear the "connecting-to-device" and "cups-waiting-for-job-completed"
+  * reasons, which are only valid when a printer is processing, along with any
+  * remote printing job state...
   */
 
   cupsdSetPrinterReasons(job->printer, "-connecting-to-device,"
+                                       "cups-waiting-for-job-completed,"
                                       "cups-remote-pending,"
                                       "cups-remote-pending-held,"
                                       "cups-remote-processing,"
@@ -2834,7 +3117,8 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
   * rarely have current information for network devices...
   */
 
-  if (strncmp(job->printer->device_uri, "usb:", 4))
+  if (strncmp(job->printer->device_uri, "usb:", 4) &&
+      strncmp(job->printer->device_uri, "ippusb:", 7))
     cupsdSetPrinterReasons(job->printer, "-offline-report");
 
  /*
@@ -2843,12 +3127,15 @@ finalize_job(cupsd_job_t *job,          /* I - Job */
 
   cupsdDestroyProfile(job->profile);
   job->profile = NULL;
+  cupsdDestroyProfile(job->bprofile);
+  job->bprofile = NULL;
 
  /*
-  * Clear the unresponsive job watchdog timer...
+  * Clear the unresponsive job watchdog timers...
   */
 
-  job->kill_time = 0;
+  job->cancel_time = 0;
+  job->kill_time   = 0;
 
  /*
   * Close pipes and status buffer...
@@ -2887,14 +3174,22 @@ finalize_job(cupsd_job_t *job,          /* I - Job */
     case IPP_JOB_COMPLETED :
        job_state = IPP_JOB_COMPLETED;
        message   = "Job completed.";
+
+        if (!job->status)
+         ippSetString(job->attrs, &job->reasons, 0,
+                      "job-completed-successfully");
         break;
 
     case IPP_JOB_STOPPED :
         message = "Job stopped.";
+
+       ippSetString(job->attrs, &job->reasons, 0, "job-stopped");
        break;
 
     case IPP_JOB_CANCELED :
         message = "Job canceled.";
+
+       ippSetString(job->attrs, &job->reasons, 0, "job-canceled-by-user");
        break;
 
     case IPP_JOB_ABORTED :
@@ -2912,7 +3207,7 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
 
    /*
     * 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
+    * implemented on macOS (bug?), we have to store the exit status in a
     * variable first and then convert...
     */
 
@@ -2920,7 +3215,10 @@ finalize_job(cupsd_job_t *job,           /* I - Job */
     if (WIFEXITED(exit_code))
       exit_code = WEXITSTATUS(exit_code);
     else
+    {
+      ippSetString(job->attrs, &job->reasons, 0, "cups-backend-crashed");
       exit_code = job->status;
+    }
 
     cupsdLogJob(job, CUPSD_LOG_INFO, "Backend returned status %d (%s)",
                exit_code,
@@ -2930,6 +3228,8 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
                    exit_code == CUPS_BACKEND_HOLD ? "hold job" :
                    exit_code == CUPS_BACKEND_STOP ? "stop printer" :
                    exit_code == CUPS_BACKEND_CANCEL ? "cancel job" :
+                   exit_code == CUPS_BACKEND_RETRY ? "retry job later" :
+                   exit_code == CUPS_BACKEND_RETRY_CURRENT ? "retry job immediately" :
                    exit_code < 0 ? "crashed" : "unknown");
 
    /*
@@ -2945,7 +3245,7 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
          * act...
          */
 
-          if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))
+          if (job->dtype & CUPS_PRINTER_CLASS)
          {
           /*
            * Queued on a class - mark the job as pending and we'll retry on
@@ -2956,6 +3256,9 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
            {
              job_state = IPP_JOB_PENDING;
              message   = "Retrying job on another printer.";
+
+             ippSetString(job->attrs, &job->reasons, 0,
+                          "resources-are-not-ready");
            }
           }
          else if (!strcmp(job->printer->error_policy, "retry-current-job"))
@@ -2969,6 +3272,8 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
            {
              job_state = IPP_JOB_PENDING;
              message   = "Retrying job on same printer.";
+
+             ippSetString(job->attrs, &job->reasons, 0, "none");
            }
           }
          else if ((job->printer->type & CUPS_PRINTER_FAX) ||
@@ -2995,6 +3300,8 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
                         JobRetryLimit);
                job_state = IPP_JOB_ABORTED;
                message   = buffer;
+
+               ippSetString(job->attrs, &job->reasons, 0, "aborted-by-system");
              }
              else
              {
@@ -3009,6 +3316,9 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
                job->hold_until = time(NULL) + JobRetryInterval;
                job_state       = IPP_JOB_HELD;
                message         = buffer;
+
+               ippSetString(job->attrs, &job->reasons, 0,
+                            "resources-are-not-ready");
              }
             }
          }
@@ -3018,6 +3328,8 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
            job_state = IPP_JOB_ABORTED;
            message   = "Job aborted due to backend errors; please consult "
                        "the error_log file for details.";
+
+           ippSetString(job->attrs, &job->reasons, 0, "aborted-by-system");
          }
          else if (job->state_value == IPP_JOB_PROCESSING)
           {
@@ -3025,19 +3337,22 @@ finalize_job(cupsd_job_t *job,          /* I - Job */
            printer_state = IPP_PRINTER_STOPPED;
            message       = "Printer stopped due to backend errors; please "
                            "consult the error_log file for details.";
+
+           ippSetString(job->attrs, &job->reasons, 0, "none");
          }
           break;
 
       case CUPS_BACKEND_CANCEL :
          /*
-         * Abort the job...
+         * Cancel 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.";
+           job_state = IPP_JOB_CANCELED;
+           message   = "Job canceled at printer.";
+
+           ippSetString(job->attrs, &job->reasons, 0, "canceled-at-device");
          }
           break;
 
@@ -3048,11 +3363,48 @@ finalize_job(cupsd_job_t *job,          /* I - Job */
            * Hold the job...
            */
 
-           cupsdSetJobHoldUntil(job, "indefinite", 1);
+           const char *reason = ippGetString(job->reasons, 0, NULL);
 
-           job_state = IPP_JOB_HELD;
-           message   = "Job held indefinitely due to backend errors; please "
+           cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-state-reasons=\"%s\"",
+                       reason);
+
+           if (!reason || strncmp(reason, "account-", 8))
+           {
+             cupsdSetJobHoldUntil(job, "indefinite", 1);
+
+             ippSetString(job->attrs, &job->reasons, 0,
+                          "job-hold-until-specified");
+             message = "Job held indefinitely due to backend errors; please "
                        "consult the error_log file for details.";
+            }
+            else if (!strcmp(reason, "account-info-needed"))
+            {
+             cupsdSetJobHoldUntil(job, "indefinite", 0);
+
+             message = "Job held indefinitely - account information is "
+                       "required.";
+            }
+            else if (!strcmp(reason, "account-closed"))
+            {
+             cupsdSetJobHoldUntil(job, "indefinite", 0);
+
+             message = "Job held indefinitely - account has been closed.";
+           }
+            else if (!strcmp(reason, "account-limit-reached"))
+            {
+             cupsdSetJobHoldUntil(job, "indefinite", 0);
+
+             message = "Job held indefinitely - account limit has been "
+                       "reached.";
+           }
+            else
+            {
+             cupsdSetJobHoldUntil(job, "indefinite", 0);
+
+             message = "Job held indefinitely - account authorization failed.";
+           }
+
+           job_state = IPP_JOB_HELD;
           }
           break;
 
@@ -3066,7 +3418,12 @@ finalize_job(cupsd_job_t *job,           /* I - Job */
                          "consult the error_log file for details.";
 
          if (job_state == IPP_JOB_COMPLETED)
+         {
            job_state = IPP_JOB_PENDING;
+
+           ippSetString(job->attrs, &job->reasons, 0,
+                        "resources-are-not-ready");
+         }
           break;
 
       case CUPS_BACKEND_AUTH_REQUIRED :
@@ -3080,6 +3437,10 @@ finalize_job(cupsd_job_t *job,           /* I - Job */
 
            job_state = IPP_JOB_HELD;
            message   = "Job held for authentication.";
+
+            if (strncmp(job->reasons->values[0].string.text, "account-", 8))
+             ippSetString(job->attrs, &job->reasons, 0,
+                          "cups-held-for-authentication");
           }
           break;
 
@@ -3104,6 +3465,8 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
                       JobRetryLimit);
              job_state = IPP_JOB_ABORTED;
              message   = buffer;
+
+             ippSetString(job->attrs, &job->reasons, 0, "aborted-by-system");
            }
            else
            {
@@ -3118,6 +3481,9 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
              job->hold_until = time(NULL) + JobRetryInterval;
              job_state       = IPP_JOB_HELD;
              message         = buffer;
+
+             ippSetString(job->attrs, &job->reasons, 0,
+                          "resources-are-not-ready");
            }
          }
           break;
@@ -3131,6 +3497,8 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
          {
            job_state = IPP_JOB_PENDING;
            message   = "Retrying job on same printer.";
+
+           ippSetString(job->attrs, &job->reasons, 0, "none");
          }
           break;
     }
@@ -3146,6 +3514,11 @@ finalize_job(cupsd_job_t *job,           /* I - Job */
       job_state = IPP_JOB_STOPPED;
       message   = "Job stopped due to filter errors; please consult the "
                  "error_log file for details.";
+
+      if (WIFSIGNALED(job->status))
+       ippSetString(job->attrs, &job->reasons, 0, "cups-filter-crashed");
+      else
+       ippSetString(job->attrs, &job->reasons, 0, "job-completed-with-errors");
     }
   }
 
@@ -3173,18 +3546,33 @@ finalize_job(cupsd_job_t *job,          /* I - Job */
   cupsArrayRemove(PrintingJobs, job);
 
  /*
-  * Clear the printer <-> job association...
+  * Clear informational messages...
   */
 
-  job->printer->job = NULL;
-  job->printer      = NULL;
+  if (job->status_level > CUPSD_LOG_ERROR)
+    job->printer->state_message[0] = '\0';
+
+ /*
+  * Apply any PPD updates...
+  */
+
+  if (job->num_keywords)
+  {
+    if (cupsdUpdatePrinterPPD(job->printer, job->num_keywords, job->keywords))
+      cupsdSetPrinterAttrs(job->printer);
+
+    cupsFreeOptions(job->num_keywords, job->keywords);
+
+    job->num_keywords = 0;
+    job->keywords     = NULL;
+  }
 
  /*
-  * Try printing another job...
+  * Clear the printer <-> job association...
   */
 
-  if (printer_state != IPP_PRINTER_STOPPED)
-    cupsdCheckJobs();
+  job->printer->job = NULL;
+  job->printer      = NULL;
 }
 
 
@@ -3232,34 +3620,51 @@ get_options(cupsd_job_t *job,           /* I - Job */
   pwgppds     = NULL;
 
   if (pc &&
-      !ippFindAttribute(job->attrs,
-                        "com.apple.print.DocumentTicket.PMSpoolFormat",
-                       IPP_TAG_ZERO) &&
+      !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)))
+      (ippFindAttribute(job->attrs, "print-color-mode", IPP_TAG_ZERO) || ippFindAttribute(job->attrs, "print-quality", IPP_TAG_ZERO) || ippFindAttribute(job->attrs, "cupsPrintQuality", IPP_TAG_ZERO)))
   {
    /*
-    * Map output-mode and print-quality to a preset...
+    * Map print-color-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"))
+                                IPP_TAG_KEYWORD)) != NULL &&
+        !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;
+    if ((attr = ippFindAttribute(job->attrs, "print-quality", IPP_TAG_ENUM)) != NULL)
+    {
+      ipp_quality_t pq = (ipp_quality_t)ippGetInteger(attr, 0);
+
+      if (pq >= IPP_QUALITY_DRAFT && pq <= IPP_QUALITY_HIGH)
+        print_quality = attr->values[0].integer - IPP_QUALITY_DRAFT;
+      else
+        print_quality = _PWG_PRINT_QUALITY_NORMAL;
+    }
+    else if ((attr = ippFindAttribute(job->attrs, "cupsPrintQuality", IPP_TAG_NAME)) != NULL)
+    {
+      const char *pq = ippGetString(attr, 0, NULL);
+
+      if (!_cups_strcasecmp(pq, "draft"))
+        print_quality = _PWG_PRINT_QUALITY_DRAFT;
+      else if (!_cups_strcasecmp(pq, "high"))
+        print_quality = _PWG_PRINT_QUALITY_HIGH;
+      else
+        print_quality = _PWG_PRINT_QUALITY_NORMAL;
+
+      if (!ippFindAttribute(job->attrs, "print-quality", IPP_TAG_ENUM))
+      {
+        cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping cupsPrintQuality=%s to print-quality=%d", pq, print_quality + IPP_QUALITY_DRAFT);
+        num_pwgppds = cupsAddIntegerOption("print-quality", print_quality + IPP_QUALITY_DRAFT, num_pwgppds, &pwgppds);
+      }
+    }
     else
+    {
       print_quality = _PWG_PRINT_QUALITY_NORMAL;
+    }
 
     if (pc->num_presets[print_color_mode][print_quality] == 0)
     {
@@ -3292,36 +3697,59 @@ get_options(cupsd_job_t *job,           /* I - Job */
           i --, preset ++)
       {
         if (!ippFindAttribute(job->attrs, preset->name, IPP_TAG_ZERO))
-         num_pwgppds = cupsAddOption(preset->name, preset->value, num_pwgppds,
-                                     &pwgppds);
+        {
+          cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Adding preset option %s=%s", preset->name, preset->value);
+
+         num_pwgppds = cupsAddOption(preset->name, preset->value, num_pwgppds, &pwgppds);
+        }
       }
     }
   }
 
   if (pc)
   {
+    if ((attr = ippFindAttribute(job->attrs, "print-quality", IPP_TAG_ENUM)) != NULL)
+    {
+      int pq = ippGetInteger(attr, 0);
+      static const char * const pqs[] = { "Draft", "Normal", "High" };
+
+      if (pq >= IPP_QUALITY_DRAFT && pq <= IPP_QUALITY_HIGH)
+      {
+        cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping print-quality=%d to cupsPrintQuality=%s", pq, pqs[pq - IPP_QUALITY_DRAFT]);
+
+        num_pwgppds = cupsAddOption("cupsPrintQuality", pqs[pq - IPP_QUALITY_DRAFT], num_pwgppds, &pwgppds);
+      }
+    }
+
     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)
+    {
+      cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping media to MediaType=%s", ppd);
+
       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)
     {
+      cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping media to Pagesize=%s", ppd);
+
       num_pwgppds = cupsAddOption("PageSize", ppd, num_pwgppds, &pwgppds);
 
       if (!ippFindAttribute(job->attrs, "media", IPP_TAG_ZERO))
+      {
+        cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Adding media=%s", ppd);
+
         num_pwgppds = cupsAddOption("media", ppd, num_pwgppds, &pwgppds);
+      }
     }
 
     if (!ippFindAttribute(job->attrs, "OutputBin", IPP_TAG_ZERO) &&
@@ -3335,6 +3763,8 @@ get_options(cupsd_job_t *job,             /* I - Job */
       * Map output-bin to OutputBin option...
       */
 
+      cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping output-bin to OutputBin=%s", ppd);
+
       num_pwgppds = cupsAddOption("OutputBin", ppd, num_pwgppds, &pwgppds);
     }
 
@@ -3347,15 +3777,33 @@ get_options(cupsd_job_t *job,           /* I - Job */
       */
 
       if (!strcmp(attr->values[0].string.text, "one-sided"))
-        num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_1sided,
-                                   num_pwgppds, &pwgppds);
+      {
+        cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping sizes to Duplex=%s", pc->sides_1sided);
+
+        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);
+      {
+        cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping sizes to Duplex=%s", pc->sides_2sided_long);
+
+        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);
+      {
+        cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Mapping sizes to Duplex=%s", pc->sides_2sided_short);
+
+        num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_2sided_short, num_pwgppds, &pwgppds);
+      }
     }
+
+   /*
+    * Map finishings values...
+    */
+
+    num_pwgppds = _ppdCacheGetFinishingOptions(pc, job->attrs, IPP_FINISHINGS_NONE, num_pwgppds, &pwgppds);
+
+    for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++)
+      cupsdLogJob(job, CUPSD_LOG_DEBUG2, "After mapping finishings %s=%s", pwgppd->name, pwgppd->value);
   }
 
  /*
@@ -3384,7 +3832,7 @@ get_options(cupsd_job_t *job,             /* I - Job */
     if (!optptr)
     {
       cupsdLogJob(job, CUPSD_LOG_CRIT,
-                 "Unable to allocate " CUPS_LLFMT " bytes for option buffer!",
+                 "Unable to allocate " CUPS_LLFMT " bytes for option buffer.",
                  CUPS_LLCAST newlength);
       return (NULL);
     }
@@ -3430,18 +3878,30 @@ get_options(cupsd_job_t *job,           /* I - Job */
           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_URI && strcmp(attr->name, "job-uuid") &&
+          strcmp(attr->name, "job-authorization-uri")) ||
          attr->value_tag == IPP_TAG_URISCHEME ||
          attr->value_tag == IPP_TAG_BEGIN_COLLECTION) /* Not yet supported */
        continue;
 
-      if (!strcmp(attr->name, "job-hold-until"))
+      if (!strcmp(attr->name, "job-hold-until") ||
+          !strcmp(attr->name, "job-id") ||
+          !strcmp(attr->name, "job-k-octets") ||
+          !strcmp(attr->name, "job-media-sheets") ||
+          !strcmp(attr->name, "job-media-sheets-completed") ||
+          !strcmp(attr->name, "job-state") ||
+          !strcmp(attr->name, "job-state-reasons"))
        continue;
 
       if (!strncmp(attr->name, "job-", 4) &&
+          strcmp(attr->name, "job-account-id") &&
+          strcmp(attr->name, "job-accounting-user-id") &&
+          strcmp(attr->name, "job-authorization-uri") &&
           strcmp(attr->name, "job-billing") &&
           strcmp(attr->name, "job-impressions") &&
           strcmp(attr->name, "job-originating-host-name") &&
+          strcmp(attr->name, "job-password") &&
+          strcmp(attr->name, "job-password-encryption") &&
           strcmp(attr->name, "job-uuid") &&
           !(job->printer->type & CUPS_PRINTER_REMOTE))
        continue;
@@ -3466,18 +3926,18 @@ get_options(cupsd_job_t *job,           /* I - Job */
       */
 
       if (optptr > options)
-       strlcat(optptr, " ", optlength - (optptr - options));
+       strlcat(optptr, " ", optlength - (size_t)(optptr - options));
 
       if (attr->value_tag != IPP_TAG_BOOLEAN)
       {
-       strlcat(optptr, attr->name, optlength - (optptr - options));
-       strlcat(optptr, "=", optlength - (optptr - options));
+       strlcat(optptr, attr->name, optlength - (size_t)(optptr - options));
+       strlcat(optptr, "=", optlength - (size_t)(optptr - options));
       }
 
       for (i = 0; i < attr->num_values; i ++)
       {
        if (i)
-         strlcat(optptr, ",", optlength - (optptr - options));
+         strlcat(optptr, ",", optlength - (size_t)(optptr - options));
 
        optptr += strlen(optptr);
 
@@ -3485,34 +3945,33 @@ get_options(cupsd_job_t *job,           /* I - Job */
        {
          case IPP_TAG_INTEGER :
          case IPP_TAG_ENUM :
-             snprintf(optptr, optlength - (optptr - options),
+             snprintf(optptr, optlength - (size_t)(optptr - options),
                       "%d", attr->values[i].integer);
              break;
 
          case IPP_TAG_BOOLEAN :
              if (!attr->values[i].boolean)
-               strlcat(optptr, "no", optlength - (optptr - options));
+               strlcat(optptr, "no", optlength - (size_t)(optptr - options));
 
-             strlcat(optptr, attr->name,
-                     optlength - (optptr - options));
+             strlcat(optptr, attr->name, optlength - (size_t)(optptr - options));
              break;
 
          case IPP_TAG_RANGE :
              if (attr->values[i].range.lower == attr->values[i].range.upper)
-               snprintf(optptr, optlength - (optptr - options) - 1,
+               snprintf(optptr, optlength - (size_t)(optptr - options) - 1,
                         "%d", attr->values[i].range.lower);
               else
-               snprintf(optptr, optlength - (optptr - options) - 1,
+               snprintf(optptr, optlength - (size_t)(optptr - options) - 1,
                         "%d-%d", attr->values[i].range.lower,
                         attr->values[i].range.upper);
              break;
 
          case IPP_TAG_RESOLUTION :
-             snprintf(optptr, optlength - (optptr - options) - 1,
+             snprintf(optptr, optlength - (size_t)(optptr - options) - 1,
                       "%dx%d%s", attr->values[i].resolution.xres,
                       attr->values[i].resolution.yres,
                       attr->values[i].resolution.units == IPP_RES_PER_INCH ?
-                          "dpi" : "dpc");
+                          "dpi" : "dpcm");
              break;
 
           case IPP_TAG_STRING :
@@ -3548,10 +4007,10 @@ get_options(cupsd_job_t *job,           /* I - Job */
   for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++)
   {
     *optptr++ = ' ';
-    strcpy(optptr, pwgppd->name);
+    strlcpy(optptr, pwgppd->name, optlength - (size_t)(optptr - options));
     optptr += strlen(optptr);
     *optptr++ = '=';
-    strcpy(optptr, pwgppd->value);
+    strlcpy(optptr, pwgppd->value, optlength - (size_t)(optptr - options));
     optptr += strlen(optptr);
   }
 
@@ -3605,7 +4064,7 @@ ipp_length(ipp_t *ipp)                    /* I - IPP request */
     */
 
     bytes ++;                          /* " " separator */
-    bytes += attr->num_values;         /* "," separators */
+    bytes += (size_t)attr->num_values; /* "," separators */
 
    /*
     * Boolean attributes appear as "foo,nofoo,foo,nofoo", while
@@ -3615,7 +4074,7 @@ ipp_length(ipp_t *ipp)                    /* I - IPP request */
     if (attr->value_tag != IPP_TAG_BOOLEAN)
       bytes += strlen(attr->name);
     else
-      bytes += attr->num_values * strlen(attr->name);
+      bytes += (size_t)attr->num_values * strlen(attr->name);
 
    /*
     * Now add the size required for each value in the attribute...
@@ -3629,7 +4088,7 @@ ipp_length(ipp_t *ipp)                    /* I - IPP request */
          * Minimum value of a signed integer is -2147483647, or 11 digits.
          */
 
-         bytes += attr->num_values * 11;
+         bytes += (size_t)attr->num_values * 11;
          break;
 
       case IPP_TAG_BOOLEAN :
@@ -3648,7 +4107,7 @@ ipp_length(ipp_t *ipp)                    /* I - IPP request */
          * 23 characters max.
          */
 
-         bytes += attr->num_values * 23;
+         bytes += (size_t)attr->num_values * 23;
          break;
 
       case IPP_TAG_RESOLUTION :
@@ -3657,7 +4116,7 @@ ipp_length(ipp_t *ipp)                    /* I - IPP request */
          * suffixed by the units, or 26 characters max.
          */
 
-         bytes += attr->num_values * 26;
+         bytes += (size_t)attr->num_values * 26;
          break;
 
       case IPP_TAG_STRING :
@@ -3733,14 +4192,13 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
     {
       if (job)
       {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Missing </Job> directive on line %d!",
-                       linenum);
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Missing </Job> directive on line %d of %s.", linenum, filename);
         continue;
       }
 
       if (!value)
       {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Missing job ID on line %d!", linenum);
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Missing job ID on line %d of %s.", linenum, filename);
        continue;
       }
 
@@ -3748,8 +4206,7 @@ load_job_cache(const char *filename)      /* I - job.cache filename */
 
       if (jobid < 1)
       {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad job ID %d on line %d!", jobid,
-                       linenum);
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad job ID %d on line %d of %s.", jobid, linenum, filename);
         continue;
       }
 
@@ -3759,9 +4216,17 @@ load_job_cache(const char *filename)     /* I - job.cache filename */
        snprintf(jobfile, sizeof(jobfile), "%s/c%05d.N", RequestRoot, jobid);
        if (access(jobfile, 0))
        {
-         cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Files have gone away!",
+         cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Files have gone away.",
                          jobid);
-         continue;
+
+         /*
+          * job.cache file is out-of-date compared to spool directory; load
+          * that instead...
+          */
+
+         cupsFileClose(fp);
+          load_request_root();
+          return;
        }
       }
 
@@ -3769,7 +4234,7 @@ load_job_cache(const char *filename)      /* I - job.cache filename */
       if (!job)
       {
         cupsdLogMessage(CUPSD_LOG_EMERG,
-                       "[Job %d] Unable to allocate memory for job!", jobid);
+                       "[Job %d] Unable to allocate memory for job.", jobid);
         break;
       }
 
@@ -3783,13 +4248,12 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
       job->status_pipes[0] = -1;
       job->status_pipes[1] = -1;
 
-      cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Loading from cache...",
-                      job->id);
+      cupsdLogJob(job, CUPSD_LOG_DEBUG, "Loading from cache...");
     }
     else if (!job)
     {
       cupsdLogMessage(CUPSD_LOG_ERROR,
-                     "Missing <Job #> directive on line %d!", linenum);
+                     "Missing <Job #> directive on line %d of %s.", linenum, filename);
       continue;
     }
     else if (!_cups_strcasecmp(line, "</Job>"))
@@ -3798,12 +4262,20 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
 
       if (job->state_value <= IPP_JOB_STOPPED && cupsdLoadJob(job))
        cupsArrayAdd(ActiveJobs, job);
+      else if (job->state_value > IPP_JOB_STOPPED)
+      {
+        if (!job->completed_time || !job->creation_time || !job->name || !job->koctets)
+       {
+         cupsdLoadJob(job);
+         unload_job(job);
+       }
+      }
 
       job = NULL;
     }
     else if (!value)
     {
-      cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d!", linenum);
+      cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d of %s.", linenum, filename);
       continue;
     }
     else if (!_cups_strcasecmp(line, "State"))
@@ -3815,9 +4287,21 @@ 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 (!_cups_strcasecmp(line, "Name"))
+    {
+      cupsdSetString(&(job->name), value);
+    }
+    else if (!_cups_strcasecmp(line, "Created"))
+    {
+      job->creation_time = strtol(value, NULL, 10);
+    }
+    else if (!_cups_strcasecmp(line, "Completed"))
+    {
+      job->completed_time = strtol(value, NULL, 10);
+    }
     else if (!_cups_strcasecmp(line, "HoldUntil"))
     {
-      job->hold_until = atoi(value);
+      job->hold_until = strtol(value, NULL, 10);
     }
     else if (!_cups_strcasecmp(line, "Priority"))
     {
@@ -3835,14 +4319,17 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
     {
       job->dtype = (cups_ptype_t)atoi(value);
     }
+    else if (!_cups_strcasecmp(line, "KOctets"))
+    {
+      job->koctets = 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);
+       cupsdLogMessage(CUPSD_LOG_ERROR, "Bad NumFiles value %d on line %d of %s.", job->num_files, linenum, filename);
         job->num_files = 0;
        continue;
       }
@@ -3853,20 +4340,19 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
                 job->id);
         if (access(jobfile, 0))
        {
-         cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Data files have gone away!",
-                         job->id);
+         cupsdLogJob(job, CUPSD_LOG_INFO, "Data files have gone away.");
           job->num_files = 0;
          continue;
        }
 
-        job->filetypes    = calloc(job->num_files, sizeof(mime_type_t *));
-       job->compressions = calloc(job->num_files, sizeof(int));
+        job->filetypes    = calloc((size_t)job->num_files, sizeof(mime_type_t *));
+       job->compressions = calloc((size_t)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);
+         cupsdLogJob(job, CUPSD_LOG_EMERG,
+                     "Unable to allocate memory for %d files.",
+                     job->num_files);
           break;
        }
       }
@@ -3882,14 +4368,13 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
       if (sscanf(value, "%d%*[ \t]%15[^/]/%255s%d", &number, super, type,
                  &compression) != 4)
       {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File on line %d!", linenum);
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File on line %d of %s.", linenum, filename);
        continue;
       }
 
       if (number < 1 || number > job->num_files)
       {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File number %d on line %d!",
-                       number, linenum);
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File number %d on line %d of %s.", number, linenum, filename);
         continue;
       }
 
@@ -3904,9 +4389,9 @@ load_job_cache(const char *filename)      /* I - job.cache filename */
         * If the original MIME type is unknown, auto-type it!
        */
 
-        cupsdLogMessage(CUPSD_LOG_ERROR,
-                       "[Job %d] Unknown MIME type %s/%s for file %d!",
-                       job->id, super, type, number + 1);
+        cupsdLogJob(job, CUPSD_LOG_ERROR,
+                   "Unknown MIME type %s/%s for file %d.",
+                   super, type, number + 1);
 
         snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot,
                 job->id, number + 1);
@@ -3923,8 +4408,14 @@ load_job_cache(const char *filename)     /* I - job.cache filename */
       }
     }
     else
-      cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown %s directive on line %d!",
-                      line, linenum);
+      cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown %s directive on line %d of %s.", line, linenum, filename);
+  }
+
+  if (job)
+  {
+    cupsdLogMessage(CUPSD_LOG_ERROR,
+                   "Missing </Job> directive on line %d of %s.", linenum, filename);
+    cupsdDeleteJob(job, CUPSD_JOB_PURGE);
   }
 
   cupsFileClose(fp);
@@ -4023,7 +4514,7 @@ load_request_root(void)
 
       if ((job = calloc(sizeof(cupsd_job_t), 1)) == NULL)
       {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Ran out of memory for jobs!");
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Ran out of memory for jobs.");
        cupsDirClose(dir);
        return;
       }
@@ -4062,12 +4553,69 @@ load_request_root(void)
        else
          unload_job(job);
       }
+      else
+        free(job);
     }
 
   cupsDirClose(dir);
 }
 
 
+/*
+ * 'remove_job_files()' - Remove the document files for a job.
+ */
+
+static void
+remove_job_files(cupsd_job_t *job)     /* I - Job */
+{
+  int  i;                              /* Looping var */
+  char filename[1024];                 /* Document filename */
+
+
+  if (job->num_files <= 0)
+    return;
+
+  for (i = 1; i <= job->num_files; i ++)
+  {
+    snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
+            job->id, i);
+    cupsdUnlinkOrRemoveFile(filename);
+  }
+
+  free(job->filetypes);
+  free(job->compressions);
+
+  job->file_time    = 0;
+  job->num_files    = 0;
+  job->filetypes    = NULL;
+  job->compressions = NULL;
+
+  LastEvent |= CUPSD_EVENT_PRINTER_STATE_CHANGED;
+}
+
+
+/*
+ * 'remove_job_history()' - Remove the control file for a job.
+ */
+
+static void
+remove_job_history(cupsd_job_t *job)   /* I - Job */
+{
+  char filename[1024];                 /* Control filename */
+
+
+ /*
+  * Remove the job info file...
+  */
+
+  snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot,
+          job->id);
+  cupsdUnlinkOrRemoveFile(filename);
+
+  LastEvent |= CUPSD_EVENT_PRINTER_STATE_CHANGED;
+}
+
+
 /*
  * 'set_time()' - Set one of the "time-at-xyz" attributes.
  */
@@ -4076,13 +4624,51 @@ static void
 set_time(cupsd_job_t *job,             /* I - Job to update */
          const char  *name)            /* I - Name of attribute */
 {
+  char                 date_name[128]; /* date-time-at-xxx */
   ipp_attribute_t      *attr;          /* Time attribute */
+  time_t               curtime;        /* Current time */
+
+
+  curtime = time(NULL);
 
+  cupsdLogJob(job, CUPSD_LOG_DEBUG, "%s=%ld", name, (long)curtime);
 
   if ((attr = ippFindAttribute(job->attrs, name, IPP_TAG_ZERO)) != NULL)
   {
     attr->value_tag         = IPP_TAG_INTEGER;
-    attr->values[0].integer = time(NULL);
+    attr->values[0].integer = curtime;
+  }
+
+  snprintf(date_name, sizeof(date_name), "date-%s", name);
+
+  if ((attr = ippFindAttribute(job->attrs, date_name, IPP_TAG_ZERO)) != NULL)
+  {
+    attr->value_tag = IPP_TAG_DATE;
+    ippSetDate(job->attrs, &attr, 0, ippTimeToDate(curtime));
+  }
+
+  if (!strcmp(name, "time-at-completed"))
+  {
+    job->completed_time = curtime;
+
+    if (JobHistory < INT_MAX && attr)
+      job->history_time = attr->values[0].integer + JobHistory;
+    else
+      job->history_time = INT_MAX;
+
+    if (job->history_time < JobHistoryUpdate || !JobHistoryUpdate)
+      JobHistoryUpdate = job->history_time;
+
+    if (JobFiles < INT_MAX && attr)
+      job->file_time = curtime + JobFiles;
+    else
+      job->file_time = INT_MAX;
+
+    if (job->file_time < JobHistoryUpdate || !JobHistoryUpdate)
+      JobHistoryUpdate = job->file_time;
+
+    cupsdLogMessage(CUPSD_LOG_DEBUG2, "set_time: JobHistoryUpdate=%ld",
+                   (long)JobHistoryUpdate);
   }
 }
 
@@ -4095,6 +4681,13 @@ static void
 start_job(cupsd_job_t     *job,                /* I - Job ID */
           cupsd_printer_t *printer)    /* I - Printer to print job */
 {
+  const char   *filename;              /* Support filename */
+  ipp_attribute_t *cancel_after = ippFindAttribute(job->attrs,
+                                                  "job-cancel-after",
+                                                  IPP_TAG_INTEGER);
+                                       /* job-cancel-after attribute */
+
+
   cupsdLogMessage(CUPSD_LOG_DEBUG2, "start_job(job=%p(%d), printer=%p(%s))",
                   job, job->id, printer, printer->name);
 
@@ -4104,6 +4697,7 @@ start_job(cupsd_job_t     *job,           /* I - Job ID */
 
   if (job->num_files == 0)
   {
+    ippSetString(job->attrs, &job->reasons, 0, "aborted-by-system");
     cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_DEFAULT,
                      "Aborting job because it has no files.");
     return;
@@ -4116,9 +4710,14 @@ start_job(cupsd_job_t     *job,          /* I - Job ID */
   if (!cupsdLoadJob(job))
     return;
 
+  if (!job->printer_message)
+    job->printer_message = ippFindAttribute(job->attrs,
+                                            "job-printer-state-message",
+                                            IPP_TAG_TEXT);
   if (job->printer_message)
-    cupsdSetString(&(job->printer_message->values[0].string.text), "");
+    ippSetString(job->attrs, &job->printer_message, 0, "");
 
+  ippSetString(job->attrs, &job->reasons, 0, "job-printing");
   cupsdSetJobState(job, IPP_JOB_PROCESSING, CUPSD_JOB_DEFAULT, NULL);
   cupsdSetPrinterState(printer, IPP_PRINTER_PROCESSING, 0);
   cupsdSetPrinterReasons(printer, "-cups-remote-pending,"
@@ -4131,16 +4730,45 @@ start_job(cupsd_job_t     *job,         /* I - Job ID */
 
   job->cost         = 0;
   job->current_file = 0;
+  job->file_time    = 0;
+  job->history_time = 0;
   job->progress     = 0;
   job->printer      = printer;
   printer->job      = job;
 
+  if (cancel_after)
+    job->cancel_time = time(NULL) + ippGetInteger(cancel_after, 0);
+  else if (MaxJobTime > 0)
+    job->cancel_time = time(NULL) + MaxJobTime;
+  else
+    job->cancel_time = 0;
+
+ /*
+  * Check for support files...
+  */
+
+  cupsdSetPrinterReasons(job->printer, "-cups-missing-filter-warning,"
+                                      "cups-insecure-filter-warning");
+
+  if (printer->pc)
+  {
+    for (filename = (const char *)cupsArrayFirst(printer->pc->support_files);
+         filename;
+         filename = (const char *)cupsArrayNext(printer->pc->support_files))
+    {
+      if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_FILE, !RunUser,
+                        cupsdLogFCMessage, printer))
+        break;
+    }
+  }
+
  /*
   * Setup the last exit status and security profiles...
   */
 
-  job->status  = 0;
-  job->profile = cupsdCreateProfile(job->id);
+  job->status   = 0;
+  job->profile  = cupsdCreateProfile(job->id, 0);
+  job->bprofile = cupsdCreateProfile(job->id, 1);
 
  /*
   * Create the status pipes and buffer...
@@ -4157,6 +4785,8 @@ start_job(cupsd_job_t     *job,           /* I - Job ID */
 
     cupsdDestroyProfile(job->profile);
     job->profile = NULL;
+    cupsdDestroyProfile(job->bprofile);
+    job->bprofile = NULL;
     return;
   }
 
@@ -4182,6 +4812,8 @@ start_job(cupsd_job_t     *job,           /* I - Job ID */
 
     cupsdDestroyProfile(job->profile);
     job->profile = NULL;
+    cupsdDestroyProfile(job->bprofile);
+    job->bprofile = NULL;
     return;
   }
 
@@ -4211,6 +4843,8 @@ start_job(cupsd_job_t     *job,           /* I - Job ID */
 
     cupsdDestroyProfile(job->profile);
     job->profile = NULL;
+    cupsdDestroyProfile(job->bprofile);
+    job->bprofile = NULL;
     return;
   }
 
@@ -4249,7 +4883,7 @@ stop_job(cupsd_job_t       *job,  /* I - Job */
   FilterLevel -= job->cost;
   job->cost   = 0;
 
-  if (action == CUPSD_JOB_DEFAULT && !job->kill_time)
+  if (action == CUPSD_JOB_DEFAULT && !job->kill_time && job->backend > 0)
     job->kill_time = time(NULL) + JobKillDelay;
   else if (action >= CUPSD_JOB_FORCE)
     job->kill_time = 0;
@@ -4292,12 +4926,14 @@ unload_job(cupsd_job_t *job)            /* I - Job */
   if (!job->attrs)
     return;
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Unloading...", job->id);
+  cupsdLogJob(job, CUPSD_LOG_DEBUG, "Unloading...");
 
   ippDelete(job->attrs);
 
   job->attrs           = NULL;
   job->state           = NULL;
+  job->reasons         = NULL;
+  job->impressions     = NULL;
   job->sheets          = NULL;
   job->job_sheets      = NULL;
   job->printer_message = NULL;
@@ -4319,6 +4955,8 @@ update_job(cupsd_job_t *job)              /* I - Job to check */
                *ptr;                   /* Pointer update... */
   int          loglevel,               /* Log level for message */
                event = 0;              /* Events? */
+  cupsd_printer_t *printer = job->printer;
+                                       /* Printer */
   static const char * const levels[] = /* Log levels */
                {
                  "NONE",
@@ -4356,6 +4994,25 @@ update_job(cupsd_job_t *job)             /* I - Job to check */
 
       cupsdLogJob(job, CUPSD_LOG_DEBUG, "PAGE: %s", message);
 
+      if (job->impressions)
+      {
+        if (!_cups_strncasecmp(message, "total ", 6))
+       {
+        /*
+         * Got a total count of pages from a backend or filter...
+         */
+
+         copies = atoi(message + 6);
+         copies -= ippGetInteger(job->impressions, 0); /* Just track the delta */
+       }
+       else if (!sscanf(message, "%*d%d", &copies))
+         copies = 1;
+
+        ippSetInteger(job->attrs, &job->impressions, 0, ippGetInteger(job->impressions, 0) + copies);
+        job->dirty = 1;
+       cupsdMarkDirty(CUPSD_DIRTY_JOBS);
+      }
+
       if (job->sheets)
       {
         if (!_cups_strncasecmp(message, "total ", 6))
@@ -4365,12 +5022,14 @@ update_job(cupsd_job_t *job)            /* I - Job to check */
          */
 
          copies = atoi(message + 6);
-         copies -= job->sheets->values[0].integer; /* Just track the delta */
+         copies -= ippGetInteger(job->sheets, 0); /* Just track the delta */
        }
        else if (!sscanf(message, "%*d%d", &copies))
          copies = 1;
 
-        job->sheets->values[0].integer += copies;
+        ippSetInteger(job->attrs, &job->sheets, 0, ippGetInteger(job->sheets, 0) + copies);
+        job->dirty = 1;
+       cupsdMarkDirty(CUPSD_DIRTY_JOBS);
 
        if (job->printer->page_limit)
          cupsdUpdateQuota(job->printer, job->username, copies, 0);
@@ -4379,8 +5038,21 @@ update_job(cupsd_job_t *job)             /* I - Job to check */
       cupsdLogPage(job, message);
 
       if (job->sheets)
-       cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job,
-                     "Printed %d page(s).", job->sheets->values[0].integer);
+       cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job, "Printed %d page(s).", ippGetInteger(job->sheets, 0));
+    }
+    else if (loglevel == CUPSD_LOG_JOBSTATE)
+    {
+     /*
+      * Support "keyword" to set job-state-reasons to the specified keyword.
+      * This is sufficient for the current paid printing stuff.
+      */
+
+      cupsdLogJob(job, CUPSD_LOG_DEBUG, "JOBSTATE: %s", message);
+
+      if (!strcmp(message, "cups-retry-as-raster"))
+        job->retry_as_raster = 1;
+      else
+        ippSetString(job->attrs, &job->reasons, 0, message);
     }
     else if (loglevel == CUPSD_LOG_STATE)
     {
@@ -4391,9 +5063,35 @@ update_job(cupsd_job_t *job)             /* I - Job to check */
         cupsdStopPrinter(job->printer, 1);
        return;
       }
-      else if (cupsdSetPrinterReasons(job->printer, message))
+      else if (message[0] && cupsdSetPrinterReasons(job->printer, message))
+      {
        event |= CUPSD_EVENT_PRINTER_STATE;
 
+        if (MaxJobTime > 0)
+        {
+         /*
+          * Reset cancel time after connecting to the device...
+          */
+
+          for (i = 0; i < job->printer->num_reasons; i ++)
+            if (!strcmp(job->printer->reasons[i], "connecting-to-device"))
+              break;
+
+          if (i >= job->printer->num_reasons)
+          {
+           ipp_attribute_t *cancel_after = ippFindAttribute(job->attrs,
+                                                            "job-cancel-after",
+                                                            IPP_TAG_INTEGER);
+                                       /* job-cancel-after attribute */
+
+            if (cancel_after)
+             job->cancel_time = time(NULL) + ippGetInteger(cancel_after, 0);
+           else
+             job->cancel_time = time(NULL) + MaxJobTime;
+         }
+        }
+      }
+
       update_job_attrs(job, 0);
     }
     else if (loglevel == CUPSD_LOG_ATTR)
@@ -4406,21 +5104,28 @@ update_job(cupsd_job_t *job)            /* I - Job to check */
       cups_option_t    *attrs;         /* Attributes */
       const char       *attr;          /* Attribute */
 
-
       cupsdLogJob(job, CUPSD_LOG_DEBUG, "ATTR: %s", message);
 
       num_attrs = cupsParseOptions(message, 0, &attrs);
 
+      if ((attr = cupsGetOption("auth-info-default", num_attrs,
+                                attrs)) != NULL)
+      {
+        job->printer->num_options = cupsAddOption("auth-info", attr,
+                                                 job->printer->num_options,
+                                                 &(job->printer->options));
+       cupsdSetPrinterAttrs(job->printer);
+
+       cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
+      }
+
       if ((attr = cupsGetOption("auth-info-required", num_attrs,
                                 attrs)) != NULL)
       {
         cupsdSetAuthInfoRequired(job->printer, attr, NULL);
        cupsdSetPrinterAttrs(job->printer);
 
-       if (job->printer->type & CUPS_PRINTER_DISCOVERED)
-         cupsdMarkDirty(CUPSD_DIRTY_REMOTE);
-       else
-         cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
+       cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
       }
 
       if ((attr = cupsGetOption("job-media-progress", num_attrs,
@@ -4517,18 +5222,10 @@ update_job(cupsd_job_t *job)            /* I - Job to check */
       * Set attribute(s)...
       */
 
-      int              num_keywords;   /* Number of keywords */
-      cups_option_t    *keywords;      /* Keywords */
-
-
       cupsdLogJob(job, CUPSD_LOG_DEBUG, "PPD: %s", message);
 
-      num_keywords = cupsParseOptions(message, 0, &keywords);
-
-      if (cupsdUpdatePrinterPPD(job->printer, num_keywords, keywords))
-        cupsdSetPrinterAttrs(job->printer);
-
-      cupsFreeOptions(num_keywords, keywords);
+      job->num_keywords = cupsParseOptions(message, job->num_keywords,
+                                           &job->keywords);
     }
     else
     {
@@ -4551,7 +5248,8 @@ update_job(cupsd_job_t *job)              /* I - Job to check */
       else
         ptr = message;
 
-      cupsdLogJob(job, loglevel, "%s", ptr);
+      if (*ptr)
+        cupsdLogJob(job, loglevel == CUPSD_LOG_INFO ? CUPSD_LOG_DEBUG : loglevel, "%s", ptr);
 
       if (loglevel < CUPSD_LOG_DEBUG &&
           strcmp(job->printer->state_message, ptr))
@@ -4632,10 +5330,11 @@ update_job(cupsd_job_t *job)            /* I - Job to check */
     finalize_job(job, 1);
 
    /*
-    * Check for new jobs...
+    * Try printing another job...
     */
 
-    cupsdCheckJobs();
+    if (printer->state != IPP_PRINTER_STOPPED)
+      cupsdCheckJobs();
   }
 }
 
@@ -4679,10 +5378,19 @@ update_job_attrs(cupsd_job_t *job,      /* I - Job to update */
 
   if (job->state_value != IPP_JOB_PROCESSING &&
       job->status_level == CUPSD_LOG_INFO)
-    cupsdSetString(&(job->printer_message->values[0].string.text), "");
+  {
+    ippSetString(job->attrs, &job->printer_message, 0, "");
+
+    job->dirty = 1;
+    cupsdMarkDirty(CUPSD_DIRTY_JOBS);
+  }
   else if (job->printer->state_message[0] && do_message)
-    cupsdSetString(&(job->printer_message->values[0].string.text),
-                  job->printer->state_message);
+  {
+    ippSetString(job->attrs, &job->printer_message, 0, job->printer->state_message);
+
+    job->dirty = 1;
+    cupsdMarkDirty(CUPSD_DIRTY_JOBS);
+  }
 
  /*
   * ... and the printer-state-reasons value...
@@ -4739,9 +5447,7 @@ update_job_attrs(cupsd_job_t *job,        /* I - Job to update */
 
   for (i = 0; i < num_reasons; i ++)
     job->printer_reasons->values[i].string.text = _cupsStrAlloc(reasons[i]);
-}
 
-
-/*
- * End of "$Id: job.c 7902 2008-09-03 14:20:17Z mike $".
- */
+  job->dirty = 1;
+  cupsdMarkDirty(CUPSD_DIRTY_JOBS);
+}