]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - scheduler/job.c
Merge changes from CUPS 1.6svn-r10390.
[thirdparty/cups.git] / scheduler / job.c
index 193ced46ca0dc117ac937feda4a3151fff9680a9..53968b897b034f55637eef7f4eb3c34d6be5aa60 100644 (file)
@@ -3,7 +3,7 @@
  *
  *   Job management routines for the CUPS scheduler.
  *
- *   Copyright 2007-2011 by Apple Inc.
+ *   Copyright 2007-2012 by Apple Inc.
  *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
  *
  *   These coded instructions, statements, and computer programs are the
  *
  * Contents:
  *
- *   cupsdAddJob()              - Add a new job to the job queue.
- *   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.
+*   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.
+ *   cupsdUpdateJobs()          - Update the history/file files for all jobs.
+ *   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.
+ *   remove_job_files()        - Remove the document files for a job.
+ *   remove_job_history()      - Remove the control file for a job.
+ *   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.
  */
 
 /*
 #include <grp.h>
 #include <cups/backend.h>
 #include <cups/dir.h>
+#ifdef __APPLE__
+#  include <IOKit/pwr_mgt/IOPMLib.h>
+#  ifdef HAVE_IOKIT_PWR_MGT_IOPMLIBPRIVATE_H
+#    include <IOKit/pwr_mgt/IOPMLibPrivate.h>
+#  endif /* HAVE_IOKIT_PWR_MGT_IOPMLIBPRIVATE_H */
+#endif /* __APPLE__ */
 
 
 /*
@@ -175,6 +185,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);
@@ -291,13 +303,24 @@ cupsdCheckJobs(void)
 
     if (job->kill_time && job->kill_time <= curtime)
     {
-      cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Stopping unresponsive job!",
+      cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Stopping unresponsive job.",
                      job->id);
 
       stop_job(job, CUPSD_JOB_FORCE);
       continue;
     }
 
+   /*
+    * Cancel stuck jobs...
+    */
+
+    if (job->cancel_time && job->cancel_time <= curtime)
+    {
+      cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_DEFAULT,
+                       "Canceling stuck job after %d seconds.", MaxJobTime);
+      continue;
+    }
+
    /*
     * Start held jobs if they are ready...
     */
@@ -347,14 +370,16 @@ cupsdCheckJobs(void)
     * Start pending jobs if the destination is available...
     */
 
-    if (job->state_value == IPP_JOB_PENDING && !NeedReload && !Sleeping &&
+    if (job->state_value == IPP_JOB_PENDING && !NeedReload &&
+#ifndef kIOPMAssertionTypeDenySystemSleep
+        !Sleeping &&
+#endif /* !kIOPMAssertionTypeDenySystemSleep */
         !DoingShutdown && !job->printer)
     {
       printer = cupsdFindDest(job->dest);
       pclass  = NULL;
 
-      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...
@@ -406,10 +431,7 @@ cupsdCheckJobs(void)
           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->state == IPP_PRINTER_IDLE)
         {
         /*
          * Start the job...
@@ -431,16 +453,56 @@ void
 cupsdCleanJobs(void)
 {
   cupsd_job_t  *job;                   /* Current job */
+  time_t       curtime;                /* Current time */
 
 
-  if (MaxJobs <= 0 && JobHistory)
+  cupsdLogMessage(CUPSD_LOG_DEBUG2,
+                  "cupsdCleanJobs: MaxJobs=%d, JobHistory=%d, JobFiles=%d",
+                  MaxJobs, JobHistory, JobFiles);
+
+  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.");
+        remove_job_files(job);
+
+        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);
 }
 
 
@@ -471,6 +533,7 @@ cupsdContinueJob(cupsd_job_t *job)  /* I - Job */
   int                  filterfds[2][2] = { { -1, -1 }, { -1, -1 } };
                                        /* Pipes used between filters */
   int                  envc;           /* Number of environment variables */
+  struct stat          fileinfo;       /* Job file information */
   char                 **argv = NULL,  /* Filter command-line arguments */
                        filename[1024], /* Job filename */
                        command[1024],  /* Full path to command */
@@ -543,18 +606,25 @@ cupsdContinueJob(cupsd_job_t *job)        /* I - Job */
     * Local jobs get filtered...
     */
 
-    filters = mimeFilter(MimeDatabase, job->filetypes[job->current_file],
-                         job->printer->filetype, &(job->cost));
+    snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
+             job->id, job->current_file + 1);
+    if (stat(filename, &fileinfo))
+      fileinfo.st_size = 0;
+
+    filters = mimeFilter2(MimeDatabase, job->filetypes[job->current_file],
+                          fileinfo.st_size, job->printer->filetype,
+                          &(job->cost));
 
     if (!filters)
     {
       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;
     }
 
@@ -703,12 +773,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;
   }
 
@@ -722,7 +794,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",
@@ -732,15 +804,15 @@ 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;
-  else if (strcasecmp(job->job_sheets->values[0].string.text, "none") != 0 &&
+  else if (_cups_strcasecmp(job->job_sheets->values[0].string.text, "none") != 0 &&
           job->current_file == 0)
     banner_page = 1;
   else if (job->job_sheets->num_values > 1 &&
-          strcasecmp(job->job_sheets->values[1].string.text, "none") != 0 &&
+          _cups_strcasecmp(job->job_sheets->values[1].string.text, "none") != 0 &&
           job->current_file == (job->num_files - 1))
     banner_page = 1;
   else
@@ -990,19 +1062,22 @@ 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;
   }
 
   envp[envc ++] = auth_info_required;
-  if (job->auth_username)
-    envp[envc ++] = job->auth_username;
-  if (job->auth_domain)
-    envp[envc ++] = job->auth_domain;
-  if (job->auth_password)
-    envp[envc ++] = job->auth_password;
+
+  for (i = 0;
+       i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0]));
+       i ++)
+    if (job->auth_env[i])
+      envp[envc ++] = job->auth_env[i];
+    else
+      break;
+
   if (job->auth_uid)
     envp[envc ++] = job->auth_uid;
 
@@ -1297,43 +1372,31 @@ void
 cupsdDeleteJob(cupsd_job_t       *job, /* I - Job */
                cupsd_jobaction_t action)/* I - Action */
 {
-  char filename[1024];                 /* Job filename */
+  int  i;                              /* Looping var */
 
 
   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);
-    unlink(filename);
-  }
+    remove_job_history(job);
 
   cupsdClearString(&job->username);
   cupsdClearString(&job->dest);
-  cupsdClearString(&job->auth_username);
-  cupsdClearString(&job->auth_domain);
-  cupsdClearString(&job->auth_password);
+  for (i = 0;
+       i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0]));
+       i ++)
+    cupsdClearString(job->auth_env + i);
   cupsdClearString(&job->auth_uid);
 
-  if (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);
 
-    while (job->num_files > 0)
-    {
-      snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
-              job->id, job->num_files);
-      unlink(filename);
-
-      job->num_files --;
-    }
+    job->num_files = 0;
   }
 
   if (job->history)
@@ -1408,7 +1471,7 @@ cupsdGetPrinterJobCount(
   for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0;
        job;
        job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
-    if (job->dest && !strcasecmp(job->dest, dest))
+    if (job->dest && !_cups_strcasecmp(job->dest, dest))
       count ++;
 
   return (count);
@@ -1431,7 +1494,7 @@ cupsdGetUserJobCount(
   for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0;
        job;
        job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
-    if (!strcasecmp(job->username, username))
+    if (!_cups_strcasecmp(job->username, username))
       count ++;
 
   return (count);
@@ -1519,6 +1582,7 @@ cupsdLoadAllJobs(void)
 int                                    /* O - 1 on success, 0 on failure */
 cupsdLoadJob(cupsd_job_t *job)         /* I - Job */
 {
+  int                  i;              /* Looping var */
   char                 jobfile[1024];  /* Job filename */
   cups_file_t          *fp;            /* Job file */
   int                  fileid;         /* Current file ID */
@@ -1539,7 +1603,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);
   }
 
@@ -1550,18 +1614,13 @@ cupsdLoadJob(cupsd_job_t *job)          /* I - Job */
   cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Loading attributes...", job->id);
 
   snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, job->id);
-  if ((fp = cupsFileOpen(jobfile, "r")) == NULL)
-  {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                   "[Job %d] Unable to open job control file \"%s\" - %s!",
-                   job->id, jobfile, strerror(errno));
+  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,
+                   "[Job %d] Unable to read job control file \"%s\".", job->id,
                    jobfile);
     cupsFileClose(fp);
     goto error;
@@ -1577,7 +1636,7 @@ cupsdLoadJob(cupsd_job_t *job)            /* I - Job */
   {
     cupsdLogMessage(CUPSD_LOG_ERROR,
                    "[Job %d] Missing or bad time-at-creation attribute in "
-                   "control file!", job->id);
+                   "control file.", job->id);
     goto error;
   }
 
@@ -1586,11 +1645,40 @@ cupsdLoadJob(cupsd_job_t *job)          /* I - Job */
   {
     cupsdLogMessage(CUPSD_LOG_ERROR,
                    "[Job %d] Missing or bad job-state attribute in control "
-                   "file!", job->id);
+                   "file.", job->id);
     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 (job->state_value >= IPP_JOB_CANCELED &&
+      (attr = ippFindAttribute(job->attrs, "time-at-completed",
+                              IPP_TAG_INTEGER)) != NULL)
+  {
+    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;
+
+    if (job->file_time < JobHistoryUpdate || !JobHistoryUpdate)
+      JobHistoryUpdate = job->file_time;
+
+    cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdLoadJob: JobHistoryUpdate=%ld",
+                   (long)JobHistoryUpdate);
+  }
 
   if (!job->dest)
   {
@@ -1598,7 +1686,7 @@ cupsdLoadJob(cupsd_job_t *job)            /* I - Job */
                                  IPP_TAG_URI)) == NULL)
     {
       cupsdLogMessage(CUPSD_LOG_ERROR,
-                     "[Job %d] No job-printer-uri attribute in control file!",
+                     "[Job %d] No job-printer-uri attribute in control file.",
                      job->id);
       goto error;
     }
@@ -1607,7 +1695,7 @@ cupsdLoadJob(cupsd_job_t *job)            /* I - Job */
                                   &destptr)) == NULL)
     {
       cupsdLogMessage(CUPSD_LOG_ERROR,
-                     "[Job %d] Unable to queue job for destination \"%s\"!",
+                     "[Job %d] Unable to queue job for destination \"%s\".",
                      job->id, attr->values[0].string.text);
       goto error;
     }
@@ -1617,11 +1705,74 @@ 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 %d] Unable to queue job for destination \"%s\".",
                    job->id, job->dest);
     goto error;
   }
 
+  if ((job->reasons = ippFindAttribute(job->attrs, "job-state-reasons",
+                                       IPP_TAG_KEYWORD)) == NULL)
+  {
+    const char *reason;                /* job-state-reason keyword */
+
+    cupsdLogMessage(CUPSD_LOG_DEBUG,
+                   "[Job %d] Adding missing job-state-reasons attribute to "
+                   " control file.", job->id);
+
+    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->sheets     = ippFindAttribute(job->attrs, "job-media-sheets-completed",
                                      IPP_TAG_INTEGER);
   job->job_sheets = ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_NAME);
@@ -1633,7 +1784,7 @@ cupsdLoadJob(cupsd_job_t *job)            /* I - Job */
     {
       cupsdLogMessage(CUPSD_LOG_ERROR,
                      "[Job %d] Missing or bad job-priority attribute in "
-                     "control file!", job->id);
+                     "control file.", job->id);
       goto error;
     }
 
@@ -1647,7 +1798,7 @@ cupsdLoadJob(cupsd_job_t *job)            /* I - Job */
     {
       cupsdLogMessage(CUPSD_LOG_ERROR,
                      "[Job %d] Missing or bad job-originating-user-name "
-                     "attribute in control file!", job->id);
+                     "attribute in control file.", job->id);
       goto error;
     }
 
@@ -1715,7 +1866,7 @@ cupsdLoadJob(cupsd_job_t *job)            /* I - Job */
         if (!compressions || !filetypes)
        {
           cupsdLogMessage(CUPSD_LOG_ERROR,
-                         "[Job %d] Ran out of memory for job file types!",
+                         "[Job %d] Ran out of memory for job file types.",
                          job->id);
 
          ippDelete(job->attrs);
@@ -1765,40 +1916,60 @@ cupsdLoadJob(cupsd_job_t *job)          /* I - Job */
   {
     snprintf(jobfile, sizeof(jobfile), "%s/a%05d", RequestRoot, job->id);
 
-    cupsdClearString(&job->auth_username);
-    cupsdClearString(&job->auth_domain);
-    cupsdClearString(&job->auth_password);
+    for (i = 0;
+        i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0]));
+        i ++)
+      cupsdClearString(job->auth_env + i);
     cupsdClearString(&job->auth_uid);
 
     if ((fp = cupsFileOpen(jobfile, "r")) != NULL)
     {
-      int      i,                      /* Looping var */
-               bytes;                  /* Size of auth data */
-      char     line[255],              /* Line from file */
-               data[255];              /* Decoded data */
+      int      bytes,                  /* Size of auth data */
+               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 &&
-              cupsFileGets(fp, line, sizeof(line));
-          i ++)
+      if (cupsFileGets(fp, line, sizeof(line)) &&
+          !strcmp(line, "CUPSD-AUTH-V2"))
       {
-        bytes = sizeof(data);
-        httpDecode64_2(data, &bytes, line);
-
-       if (!strcmp(destptr->auth_info_required[i], "username"))
-         cupsdSetStringf(&job->auth_username, "AUTH_USERNAME=%s", data);
-       else if (!strcmp(destptr->auth_info_required[i], "domain"))
-         cupsdSetStringf(&job->auth_domain, "AUTH_DOMAIN=%s", data);
-       else if (!strcmp(destptr->auth_info_required[i], "password"))
-         cupsdSetStringf(&job->auth_password, "AUTH_PASSWORD=%s", data);
-        else if (!strcmp(destptr->auth_info_required[i], "negotiate") &&
-                isdigit(line[0] & 255))
-         cupsdSetStringf(&job->auth_uid, "AUTH_UID=%s", line);
-      }
+        i = 0;
+        while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
+        {
+         /*
+          * Decode value...
+          */
+
+         bytes = sizeof(data);
+         httpDecode64_2(data, &bytes, value);
+
+         /*
+          * Assign environment variables...
+          */
 
-      if (cupsFileGets(fp, line, sizeof(line)) && isdigit(line[0] & 255))
-        cupsdSetStringf(&job->auth_uid, "AUTH_UID=%s", line);
+          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", line);
+         else
+           continue;
+
+         i ++;
+       }
+      }
 
       cupsFileClose(fp);
     }
@@ -1816,21 +1987,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;
-
-  unlink(jobfile);
+  remove_job_history(job);
+  remove_job_files(job);
 
   return (0);
 }
@@ -1880,8 +2038,7 @@ 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)
@@ -1945,30 +2102,19 @@ void
 cupsdSaveAllJobs(void)
 {
   int          i;                      /* Looping var */
-  cups_file_t  *fp;                    /* Job cache file */
-  char         temp[1024];             /* Temporary string */
+  cups_file_t  *fp;                    /* job.cache file */
+  char         filename[1024],         /* job.cache filename */
+               temp[1024];             /* Temporary string */
   cupsd_job_t  *job;                   /* Current job */
   time_t       curtime;                /* Current time */
   struct tm    *curdate;               /* Current date */
 
 
-  snprintf(temp, sizeof(temp), "%s/job.cache", CacheDir);
-  if ((fp = cupsFileOpen(temp, "w")) == NULL)
-  {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                    "Unable to create job cache file \"%s\" - %s",
-                    temp, strerror(errno));
+  snprintf(filename, sizeof(filename), "%s/job.cache", CacheDir);
+  if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm)) == NULL)
     return;
-  }
-
-  cupsdLogMessage(CUPSD_LOG_INFO, "Saving job cache file \"%s\"...", temp);
-
- /*
-  * Restrict access to the file...
-  */
 
-  fchown(cupsFileNumber(fp), getuid(), Group);
-  fchmod(cupsFileNumber(fp), ConfigFilePerm);
+  cupsdLogMessage(CUPSD_LOG_INFO, "Saving job.cache...");
 
  /*
   * Write a small header to the file...
@@ -2004,7 +2150,7 @@ cupsdSaveAllJobs(void)
     cupsFilePuts(fp, "</Job>\n");
   }
 
-  cupsFileClose(fp);
+  cupsdCloseCreatedConfFile(fp, filename);
 }
 
 
@@ -2024,27 +2170,24 @@ cupsdSaveJob(cupsd_job_t *job)          /* I - Job */
 
   snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, job->id);
 
-  if ((fp = cupsFileOpen(filename, "w")) == NULL)
-  {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                   "[Job %d] Unable to create job control file \"%s\" - %s.",
-                   job->id, filename, strerror(errno));
+  if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm & 0600)) == NULL)
     return;
-  }
 
-  fchmod(cupsFileNumber(fp), 0600);
   fchown(cupsFileNumber(fp), RunUser, Group);
 
   job->attrs->state = IPP_IDLE;
 
   if (ippWriteIO(fp, (ipp_iocb_t)cupsFileWrite, 1, NULL,
                  job->attrs) != IPP_DATA)
+  {
     cupsdLogMessage(CUPSD_LOG_ERROR,
-                    "[Job %d] Unable to write job control file!", job->id);
-
-  cupsFileClose(fp);
+                    "[Job %d] Unable to write job control file.", job->id);
+    cupsFileClose(fp);
+    return;
+  }
 
-  job->dirty = 0;
+  if (!cupsdCloseCreatedConfFile(fp, filename))
+    job->dirty = 0;
 }
 
 
@@ -2096,12 +2239,16 @@ cupsdSetJobHoldUntil(cupsd_job_t *job,  /* I - Job */
       job->dirty = 1;
       cupsdMarkDirty(CUPSD_DIRTY_JOBS);
     }
+
+    ippSetString(job->attrs, &job->reasons, 0, "job-hold-until-specified");
   }
 
  /*
   * Update the hold time...
   */
 
+  job->cancel_time = 0;
+
   if (!strcmp(when, "indefinite") || !strcmp(when, "auth-info-required"))
   {
    /*
@@ -2109,6 +2256,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"))
   {
@@ -2290,14 +2440,14 @@ cupsdSetJobState(
   if (!cupsdLoadJob(job))
     return;
 
 /*
-   * Don't do anything if the state is unchanged and we aren't purging the
-   * job...
-   */
+ /*
+  * Don't do anything if the state is unchanged and we aren't purging the
+  * job...
+  */
 
-   oldstate = job->state_value;
-   if (newstate == oldstate && action != CUPSD_JOB_PURGE)
-     return;
+  oldstate = job->state_value;
+  if (newstate == oldstate && action != CUPSD_JOB_PURGE)
+    return;
 
  /*
   * Stop any processes that are working on the current job...
@@ -2337,6 +2487,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;
   }
 
@@ -2452,9 +2603,11 @@ cupsdSetJobState(
                          "Unable to remove authentication cache: %s",
                          strerror(errno));
 
-       cupsdClearString(&job->auth_username);
-       cupsdClearString(&job->auth_domain);
-       cupsdClearString(&job->auth_password);
+       for (i = 0;
+            i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0]));
+            i ++)
+         cupsdClearString(job->auth_env + i);
+
        cupsdClearString(&job->auth_uid);
 
        /*
@@ -2463,24 +2616,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);
-           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,6 +2707,62 @@ cupsdUnloadCompletedJobs(void)
 }
 
 
+/*
+ * '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;
+
+      if (job->file_time < JobHistoryUpdate || !JobHistoryUpdate)
+       JobHistoryUpdate = job->file_time;
+    }
+  }
+
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdUpdateAllJobs: JobHistoryUpdate=%ld",
+                  (long)JobHistoryUpdate);
+}
+
+
 /*
  * 'compare_active_jobs()' - Compare the job IDs and priorities of two jobs.
  */
@@ -2583,6 +2775,8 @@ compare_active_jobs(void *first,  /* I - First job */
   int  diff;                           /* Difference */
 
 
+  (void)data;
+
   if ((diff = ((cupsd_job_t *)second)->priority -
               ((cupsd_job_t *)first)->priority) != 0)
     return (diff);
@@ -2600,6 +2794,8 @@ compare_jobs(void *first,         /* I - First job */
              void *second,             /* I - Second job */
             void *data)                /* I - App data (not used) */
 {
+  (void)data;
+
   return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
 }
 
@@ -2826,14 +3022,21 @@ finalize_job(cupsd_job_t *job,          /* I - Job */
     case IPP_JOB_COMPLETED :
        job_state = IPP_JOB_COMPLETED;
        message   = "Job completed.";
+
+       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 :
@@ -2849,7 +3052,6 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
 
     int exit_code;                     /* Exit code from backend */
 
-
    /*
     * Convert the status to an exit code.  Due to the way the W* macros are
     * implemented on MacOS X (bug?), we have to store the exit status in a
@@ -2860,7 +3062,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,
@@ -2885,7 +3090,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
@@ -2896,6 +3101,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"))
@@ -2909,6 +3117,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) ||
@@ -2935,6 +3145,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
              {
@@ -2949,6 +3161,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");
              }
             }
          }
@@ -2958,6 +3173,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)
           {
@@ -2965,6 +3182,8 @@ 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;
 
@@ -2978,6 +3197,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");
          }
           break;
 
@@ -2989,6 +3210,8 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
            */
 
            cupsdSetJobHoldUntil(job, "indefinite", 1);
+           ippSetString(job->attrs, &job->reasons, 0,
+                        "job-hold-until-specified");
 
            job_state = IPP_JOB_HELD;
            message   = "Job held indefinitely due to backend errors; please "
@@ -3006,7 +3229,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 :
@@ -3020,6 +3248,9 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
 
            job_state = IPP_JOB_HELD;
            message   = "Job held for authentication.";
+
+           ippSetString(job->attrs, &job->reasons, 0,
+                        "cups-held-for-authentication");
           }
           break;
 
@@ -3044,6 +3275,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
            {
@@ -3058,6 +3291,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;
@@ -3071,6 +3307,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;
     }
@@ -3086,6 +3324,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");
     }
   }
 
@@ -3093,7 +3336,7 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
   * Update the printer and job state.
   */
 
-  if (job_state != job->state_value)
+  if (set_job_state && job_state != job->state_value)
     cupsdSetJobState(job, job_state, CUPSD_JOB_DEFAULT, "%s", message);
 
   cupsdSetPrinterState(job->printer, printer_state,
@@ -3102,7 +3345,9 @@ finalize_job(cupsd_job_t *job,            /* I - Job */
 
   if (job->history)
   {
-    if (job->status)
+    if (job->status &&
+        (job->state_value == IPP_JOB_ABORTED ||
+         job->state_value == IPP_JOB_STOPPED))
       dump_job_history(job);
     else
       free_job_history(job);
@@ -3244,9 +3489,6 @@ get_options(cupsd_job_t *job,             /* I - Job */
       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)
@@ -3294,6 +3536,14 @@ get_options(cupsd_job_t *job,            /* I - Job */
         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);
   }
 
  /*
@@ -3309,6 +3559,9 @@ get_options(cupsd_job_t *job,             /* I - Job */
   * Then allocate/reallocate the option buffer as needed...
   */
 
+  if (newlength == 0)                  /* This can never happen, but Clang */
+    newlength = 1;                     /* thinks it can... */
+
   if (newlength > optlength || !options)
   {
     if (!options)
@@ -3319,7 +3572,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);
     }
@@ -3387,11 +3640,11 @@ get_options(cupsd_job_t *job,           /* I - Job */
            !strncmp(attr->name, "number-up", 9) ||
           !strcmp(attr->name, "page-ranges") ||
           !strcmp(attr->name, "page-set") ||
-          !strcasecmp(attr->name, "AP_FIRSTPAGE_InputSlot") ||
-          !strcasecmp(attr->name, "AP_FIRSTPAGE_ManualFeed") ||
-          !strcasecmp(attr->name, "com.apple.print.PrintSettings."
+          !_cups_strcasecmp(attr->name, "AP_FIRSTPAGE_InputSlot") ||
+          !_cups_strcasecmp(attr->name, "AP_FIRSTPAGE_ManualFeed") ||
+          !_cups_strcasecmp(attr->name, "com.apple.print.PrintSettings."
                                   "PMTotalSidesImaged..n.") ||
-          !strcasecmp(attr->name, "com.apple.print.PrintSettings."
+          !_cups_strcasecmp(attr->name, "com.apple.print.PrintSettings."
                                   "PMTotalBeginPages..n.")) &&
          banner_page)
         continue;
@@ -3447,7 +3700,7 @@ get_options(cupsd_job_t *job,             /* I - Job */
                       "%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 :
@@ -3641,15 +3894,9 @@ load_job_cache(const char *filename)     /* I - job.cache filename */
   * Open the job.cache file...
   */
 
-  if ((fp = cupsFileOpen(filename, "r")) == NULL)
+  if ((fp = cupsdOpenConfFile(filename)) == NULL)
   {
-    if (errno != ENOENT)
-      cupsdLogMessage(CUPSD_LOG_ERROR,
-                      "Unable to open job cache file \"%s\": %s",
-                      filename, strerror(errno));
-
     load_request_root();
-
     return;
   }
 
@@ -3665,23 +3912,23 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
 
   while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
   {
-    if (!strcasecmp(line, "NextJobId"))
+    if (!_cups_strcasecmp(line, "NextJobId"))
     {
       if (value)
         NextJobId = atoi(value);
     }
-    else if (!strcasecmp(line, "<Job"))
+    else if (!_cups_strcasecmp(line, "<Job"))
     {
       if (job)
       {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Missing </Job> directive on line %d!",
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Missing </Job> directive on line %d.",
                        linenum);
         continue;
       }
 
       if (!value)
       {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Missing job ID on line %d!", linenum);
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Missing job ID on line %d.", linenum);
        continue;
       }
 
@@ -3689,7 +3936,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,
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad job ID %d on line %d.", jobid,
                        linenum);
         continue;
       }
@@ -3697,16 +3944,20 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
       snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, jobid);
       if (access(jobfile, 0))
       {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Files have gone away!",
-                       jobid);
-        continue;
+       snprintf(jobfile, sizeof(jobfile), "%s/c%05d.N", RequestRoot, jobid);
+       if (access(jobfile, 0))
+       {
+         cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Files have gone away.",
+                         jobid);
+         continue;
+       }
       }
 
       job = calloc(1, sizeof(cupsd_job_t));
       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;
       }
 
@@ -3726,10 +3977,10 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
     else if (!job)
     {
       cupsdLogMessage(CUPSD_LOG_ERROR,
-                     "Missing <Job #> directive on line %d!", linenum);
+                     "Missing <Job #> directive on line %d.", linenum);
       continue;
     }
-    else if (!strcasecmp(line, "</Job>"))
+    else if (!_cups_strcasecmp(line, "</Job>"))
     {
       cupsArrayAdd(Jobs, job);
 
@@ -3740,10 +3991,10 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
     }
     else if (!value)
     {
-      cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d!", linenum);
+      cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d.", linenum);
       continue;
     }
-    else if (!strcasecmp(line, "State"))
+    else if (!_cups_strcasecmp(line, "State"))
     {
       job->state_value = (ipp_jstate_t)atoi(value);
 
@@ -3752,33 +4003,33 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
       else if (job->state_value > IPP_JOB_COMPLETED)
         job->state_value = IPP_JOB_COMPLETED;
     }
-    else if (!strcasecmp(line, "HoldUntil"))
+    else if (!_cups_strcasecmp(line, "HoldUntil"))
     {
       job->hold_until = atoi(value);
     }
-    else if (!strcasecmp(line, "Priority"))
+    else if (!_cups_strcasecmp(line, "Priority"))
     {
       job->priority = atoi(value);
     }
-    else if (!strcasecmp(line, "Username"))
+    else if (!_cups_strcasecmp(line, "Username"))
     {
       cupsdSetString(&job->username, value);
     }
-    else if (!strcasecmp(line, "Destination"))
+    else if (!_cups_strcasecmp(line, "Destination"))
     {
       cupsdSetString(&job->dest, value);
     }
-    else if (!strcasecmp(line, "DestType"))
+    else if (!_cups_strcasecmp(line, "DestType"))
     {
       job->dtype = (cups_ptype_t)atoi(value);
     }
-    else if (!strcasecmp(line, "NumFiles"))
+    else if (!_cups_strcasecmp(line, "NumFiles"))
     {
       job->num_files = atoi(value);
 
       if (job->num_files < 0)
       {
-       cupsdLogMessage(CUPSD_LOG_ERROR, "Bad NumFiles value %d on line %d!",
+       cupsdLogMessage(CUPSD_LOG_ERROR, "Bad NumFiles value %d on line %d.",
                        job->num_files, linenum);
         job->num_files = 0;
        continue;
@@ -3790,7 +4041,7 @@ 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!",
+         cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Data files have gone away.",
                          job->id);
           job->num_files = 0;
          continue;
@@ -3802,13 +4053,13 @@ load_job_cache(const char *filename)    /* I - job.cache filename */
         if (!job->filetypes || !job->compressions)
        {
          cupsdLogMessage(CUPSD_LOG_EMERG,
-                         "[Job %d] Unable to allocate memory for %d files!",
+                         "[Job %d] Unable to allocate memory for %d files.",
                          job->id, job->num_files);
           break;
        }
       }
     }
-    else if (!strcasecmp(line, "File"))
+    else if (!_cups_strcasecmp(line, "File"))
     {
       int      number,                 /* File number */
                compression;            /* Compression value */
@@ -3819,13 +4070,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.", linenum);
        continue;
       }
 
       if (number < 1 || number > job->num_files)
       {
-        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File number %d on line %d!",
+        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File number %d on line %d.",
                        number, linenum);
         continue;
       }
@@ -3842,7 +4093,7 @@ load_job_cache(const char *filename)      /* I - job.cache filename */
        */
 
         cupsdLogMessage(CUPSD_LOG_ERROR,
-                       "[Job %d] Unknown MIME type %s/%s for file %d!",
+                       "[Job %d] Unknown MIME type %s/%s for file %d.",
                        job->id, super, type, number + 1);
 
         snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot,
@@ -3860,7 +4111,7 @@ load_job_cache(const char *filename)      /* I - job.cache filename */
       }
     }
     else
-      cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown %s directive on line %d!",
+      cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown %s directive on line %d.",
                       line, linenum);
   }
 
@@ -3904,7 +4155,7 @@ load_next_job_id(const char *filename)    /* I - job.cache filename */
 
   while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
   {
-    if (!strcasecmp(line, "NextJobId"))
+    if (!_cups_strcasecmp(line, "NextJobId"))
     {
       if (value)
       {
@@ -3960,7 +4211,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;
       }
@@ -4005,6 +4256,67 @@ load_request_root(void)
 }
 
 
+/*
+ * '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);
+    if (Classification)
+      cupsdRemoveFile(filename);
+    else
+      unlink(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);
+  if (Classification)
+    cupsdRemoveFile(filename);
+  else
+    unlink(filename);
+
+  LastEvent |= CUPSD_EVENT_PRINTER_STATE_CHANGED;
+}
+
+
 /*
  * 'set_time()' - Set one of the "time-at-xyz" attributes.
  */
@@ -4014,12 +4326,39 @@ set_time(cupsd_job_t *job,              /* I - Job to update */
          const char  *name)            /* I - Name of attribute */
 {
   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;
+  }
+
+  if (!strcmp(name, "time-at-completed"))
+  {
+    if (JobHistory < INT_MAX)
+      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)
+      job->file_time = attr->values[0].integer + 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);
   }
 }
 
@@ -4041,6 +4380,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;
@@ -4053,6 +4393,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->reasons, 0, "job-printing");
   cupsdSetJobState(job, IPP_JOB_PROCESSING, CUPSD_JOB_DEFAULT, NULL);
   cupsdSetPrinterState(printer, IPP_PRINTER_PROCESSING, 0);
   cupsdSetPrinterReasons(printer, "-cups-remote-pending,"
@@ -4065,10 +4413,17 @@ 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 (MaxJobTime > 0)
+    job->cancel_time = time(NULL) + MaxJobTime;
+  else
+    job->cancel_time = 0;
+
  /*
   * Setup the last exit status and security profiles...
   */
@@ -4097,9 +4452,6 @@ start_job(cupsd_job_t     *job,           /* I - Job ID */
   job->status_buffer = cupsdStatBufNew(job->status_pipes[0], NULL);
   job->status_level  = CUPSD_LOG_INFO;
 
-  if (job->printer_message)
-    cupsdSetString(&(job->printer_message->values[0].string.text), "");
-
  /*
   * Create the backchannel pipes and make them non-blocking...
   */
@@ -4235,6 +4587,7 @@ unload_job(cupsd_job_t *job)              /* I - Job */
 
   job->attrs           = NULL;
   job->state           = NULL;
+  job->reasons         = NULL;
   job->sheets          = NULL;
   job->job_sheets      = NULL;
   job->printer_message = NULL;
@@ -4295,7 +4648,7 @@ update_job(cupsd_job_t *job)              /* I - Job to check */
 
       if (job->sheets)
       {
-        if (!strncasecmp(message, "total ", 6))
+        if (!_cups_strncasecmp(message, "total ", 6))
        {
         /*
          * Got a total count of pages from a backend or filter...
@@ -4329,8 +4682,24 @@ update_job(cupsd_job_t *job)             /* I - Job to check */
        return;
       }
       else if (cupsdSetPrinterReasons(job->printer, message))
+      {
        event |= CUPSD_EVENT_PRINTER_STATE;
 
+        if (MaxJobTime > 0 && strstr(message, "connecting-to-device") != NULL)
+        {
+         /*
+          * 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)
+           job->cancel_time = time(NULL) + MaxJobTime;
+        }
+      }
+
       update_job_attrs(job, 0);
     }
     else if (loglevel == CUPSD_LOG_ATTR)
@@ -4354,10 +4723,7 @@ update_job(cupsd_job_t *job)             /* I - Job to check */
         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,
@@ -4488,7 +4854,8 @@ update_job(cupsd_job_t *job)              /* I - Job to check */
       else
         ptr = message;
 
-      cupsdLogJob(job, loglevel, "%s", ptr);
+      if (*ptr)
+        cupsdLogJob(job, loglevel, "%s", ptr);
 
       if (loglevel < CUPSD_LOG_DEBUG &&
           strcmp(job->printer->state_message, ptr))
@@ -4616,11 +4983,21 @@ 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), "");
+
+    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);
 
+    job->dirty = 1;
+    cupsdMarkDirty(CUPSD_DIRTY_JOBS);
+  }
+
  /*
   * ... and the printer-state-reasons value...
   */
@@ -4676,6 +5053,9 @@ 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]);
+
+  job->dirty = 1;
+  cupsdMarkDirty(CUPSD_DIRTY_JOBS);
 }