diff options
Diffstat (limited to 'scheduler/job.c')
-rw-r--r-- | scheduler/job.c | 762 |
1 files changed, 532 insertions, 230 deletions
diff --git a/scheduler/job.c b/scheduler/job.c index 92e19554..73a3a3ab 100644 --- a/scheduler/job.c +++ b/scheduler/job.c @@ -1,5 +1,5 @@ /* - * "$Id: job.c 10420 2012-04-20 03:01:06Z mike $" + * "$Id: job.c 10502 2012-05-23 01:36:50Z mike $" * * Job management routines for the CUPS scheduler. * @@ -14,54 +14,58 @@ * * 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. */ /* @@ -181,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); @@ -281,23 +287,32 @@ cupsdCheckJobs(void) time_t curtime; /* Current time */ - 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, reload=%d, " + "curtime=%ld", cupsArrayCount(ActiveJobs), Sleeping, + 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!", + cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Stopping unresponsive job.", job->id); stop_job(job, CUPSD_JOB_FORCE); @@ -305,6 +320,17 @@ cupsdCheckJobs(void) } /* + * 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... */ @@ -322,7 +348,6 @@ cupsdCheckJobs(void) cupsd_client_t *con; /* Current client connection */ - for (con = (cupsd_client_t *)cupsArrayFirst(Clients); con; con = (cupsd_client_t *)cupsArrayNext(Clients)) @@ -362,8 +387,7 @@ cupsdCheckJobs(void) 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... @@ -415,10 +439,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->job && printer->state == IPP_PRINTER_IDLE) { /* * Start the job... @@ -440,16 +461,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); } @@ -565,12 +626,13 @@ cupsdContinueJob(cupsd_job_t *job) /* I - Job */ 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; } @@ -719,12 +781,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 +802,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 +812,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; @@ -1006,7 +1070,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; @@ -1317,25 +1381,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 +1397,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) @@ -1572,7 +1611,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); } @@ -1583,22 +1622,8 @@ 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) - { - 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) { @@ -1619,7 +1644,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; } @@ -1628,11 +1653,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) { @@ -1640,7 +1694,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; } @@ -1649,7 +1703,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; } @@ -1659,11 +1713,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); @@ -1675,7 +1792,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; } @@ -1689,7 +1806,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; } @@ -1757,7 +1874,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); @@ -1846,7 +1963,7 @@ cupsdLoadJob(cupsd_job_t *job) /* I - Job */ } 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")) @@ -1878,24 +1995,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); } @@ -1945,8 +2046,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) @@ -2069,8 +2169,7 @@ 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 */ @@ -2078,17 +2177,10 @@ cupsdSaveJob(cupsd_job_t *job) /* I - Job */ job, job->id, job->attrs); snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, job->id); - snprintf(newfile, sizeof(newfile), "%s/c%05d.N", RequestRoot, job->id); - if ((fp = cupsFileOpen(newfile, "w")) == NULL) - { - cupsdLogMessage(CUPSD_LOG_ERROR, - "[Job %d] Unable to create job control file \"%s\": %s", - job->id, newfile, strerror(errno)); + if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm & 0600)) == NULL) return; - } - fchmod(cupsFileNumber(fp), 0600); fchown(cupsFileNumber(fp), RunUser, Group); job->attrs->state = IPP_IDLE; @@ -2099,24 +2191,11 @@ cupsdSaveJob(cupsd_job_t *job) /* I - Job */ cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unable to write job control file.", job->id); 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 - { - 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; - } + if (!cupsdCloseCreatedConfFile(fp, filename)) + job->dirty = 0; } @@ -2168,12 +2247,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")) { /* @@ -2181,6 +2264,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")) { @@ -2409,6 +2495,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; } @@ -2537,27 +2624,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) { @@ -2649,6 +2716,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. */ @@ -2907,14 +3030,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 : @@ -2940,7 +3070,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, @@ -2965,7 +3098,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 @@ -2976,6 +3109,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")) @@ -2989,6 +3125,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) || @@ -3015,6 +3153,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 { @@ -3029,6 +3169,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"); } } } @@ -3038,6 +3181,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) { @@ -3045,19 +3190,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; @@ -3069,6 +3217,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 " @@ -3086,7 +3236,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 : @@ -3100,6 +3255,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; @@ -3124,6 +3282,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 { @@ -3138,6 +3298,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; @@ -3151,6 +3314,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; } @@ -3166,6 +3331,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"); } } @@ -3193,6 +3363,21 @@ finalize_job(cupsd_job_t *job, /* I - Job */ cupsArrayRemove(PrintingJobs, job); /* + * 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; + } + + /* * Clear the printer <-> job association... */ @@ -3409,7 +3594,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); } @@ -3537,7 +3722,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 : @@ -3758,14 +3943,14 @@ load_job_cache(const char *filename) /* I - job.cache filename */ { 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; } @@ -3773,7 +3958,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; } @@ -3784,7 +3969,7 @@ 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; } @@ -3794,7 +3979,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; } @@ -3814,7 +3999,7 @@ 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 (!_cups_strcasecmp(line, "</Job>")) @@ -3828,7 +4013,7 @@ 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 (!_cups_strcasecmp(line, "State")) @@ -3866,7 +4051,7 @@ load_job_cache(const char *filename) /* I - job.cache filename */ 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; @@ -3878,7 +4063,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; @@ -3890,7 +4075,7 @@ 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; } @@ -3907,13 +4092,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; } @@ -3930,7 +4115,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, @@ -3948,7 +4133,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); } @@ -4048,7 +4233,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; } @@ -4094,6 +4279,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. */ @@ -4102,12 +4348,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); } } @@ -4129,6 +4402,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; @@ -4148,6 +4422,7 @@ start_job(cupsd_job_t *job, /* I - Job ID */ 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," @@ -4160,10 +4435,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... */ @@ -4327,6 +4609,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; @@ -4421,8 +4704,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) @@ -4446,10 +4745,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, @@ -4546,18 +4842,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 { @@ -4580,7 +4868,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)) @@ -4708,11 +4997,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... */ @@ -4768,9 +5067,12 @@ 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); } /* - * End of "$Id: job.c 10420 2012-04-20 03:01:06Z mike $". + * End of "$Id: job.c 10502 2012-05-23 01:36:50Z mike $". */ |