/* * "$Id: job.c 7005 2007-10-01 23:45:48Z mike $" * * Job management routines for the Common UNIX Printing System (CUPS). * * Copyright 2007-2008 by Apple Inc. * Copyright 1997-2007 by Easy Software Products, all rights reserved. * * These coded instructions, statements, and computer programs are the * property of Apple Inc. and are protected by Federal copyright * law. Distribution and use rights are outlined in the file "LICENSE.txt" * which should have been included with this file. If this file is * file is missing or damaged, see the license at "http://www.cups.org/". * * Contents: * * cupsdAddJob() - Add a new job to the job queue... * cupsdCancelJob() - Cancel the specified print job. * 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. * cupsdDeleteJob() - Free all memory used by a job. * cupsdFinishJob() - Finish a job. * cupsdFreeAllJobs() - Free all jobs from memory. * cupsdFindJob() - Find the specified job. * cupsdGetPrinterJobCount() - Get the number of pending, processing, * cupsdGetUserJobCount() - Get the number of pending, processing, * cupsdHoldJob() - Hold the specified job. * 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. * cupsdStopAllJobs() - Stop all print jobs. * cupsdStopJob() - Stop a print job. * cupsdUnloadCompletedJobs() - Flush completed job history from memory. * compare_active_jobs() - Compare the job IDs and priorities of two * jobs. * compare_jobs() - Compare the job IDs of two jobs. * 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... * set_hold_until() - Set the hold time and update job-hold-until * attribute... * start_job() - Start a print job. * unload_job() - Unload a job from memory. * update_job() - Read a status update from a jobs filters. * update_job_attrs() - Update the job-printer-* attributes. */ /* * Include necessary headers... */ #include "cupsd.h" #include #include #include /* * Local globals... */ static mime_filter_t gziptoany_filter = { NULL, /* Source type */ NULL, /* Destination type */ 0, /* Cost */ "gziptoany" /* Filter program to run */ }; /* * Local functions... */ static int compare_active_jobs(void *first, void *second, void *data); static int compare_jobs(void *first, void *second, void *data); static int ipp_length(ipp_t *ipp); static void load_job_cache(const char *filename); static void load_next_job_id(const char *filename); static void load_request_root(void); static void set_time(cupsd_job_t *job, const char *name); static void set_hold_until(cupsd_job_t *job, time_t holdtime); static void start_job(cupsd_job_t *job, cupsd_printer_t *printer); static void unload_job(cupsd_job_t *job); static void update_job(cupsd_job_t *job); static void update_job_attrs(cupsd_job_t *job); /* * 'cupsdAddJob()' - Add a new job to the job queue... */ cupsd_job_t * /* O - New job record */ cupsdAddJob(int priority, /* I - Job priority */ const char *dest) /* I - Job destination */ { cupsd_job_t *job; /* New job record */ if ((job = calloc(sizeof(cupsd_job_t), 1)) == NULL) return (NULL); job->id = NextJobId ++; job->priority = priority; job->back_pipes[0] = -1; job->back_pipes[1] = -1; job->print_pipes[0] = -1; job->print_pipes[1] = -1; job->side_pipes[0] = -1; job->side_pipes[1] = -1; job->status_pipes[0] = -1; job->status_pipes[1] = -1; cupsdSetString(&job->dest, dest); /* * Add the new job to the "all jobs" and "active jobs" lists... */ cupsArrayAdd(Jobs, job); cupsArrayAdd(ActiveJobs, job); return (job); } /* * 'cupsdCancelJob()' - Cancel the specified print job. */ void cupsdCancelJob(cupsd_job_t *job, /* I - Job to cancel */ int purge, /* I - Purge jobs? */ ipp_jstate_t newstate) /* I - New job state */ { int i; /* Looping var */ char filename[1024]; /* Job filename */ cupsd_printer_t *printer; /* Printer used by job */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCancelJob: id = %d", job->id); /* * Stop any processes that are working on the current job... */ printer = job->printer; if (job->state_value == IPP_JOB_PROCESSING) cupsdStopJob(job, 0); cupsdLoadJob(job); if (job->attrs) job->state->values[0].integer = newstate; job->state_value = newstate; set_time(job, "time-at-completed"); /* * Send any pending notifications and then expire them... */ switch (newstate) { default : break; case IPP_JOB_CANCELED : cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, printer, job, purge ? "Job purged." : "Job canceled."); break; case IPP_JOB_ABORTED : cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, printer, job, "Job aborted; please consult the error_log file " "for details."); break; case IPP_JOB_COMPLETED : /* * Clear the printer's printer-state-message and move on... */ printer->state_message[0] = '\0'; cupsdSetPrinterState(printer, IPP_PRINTER_IDLE, 0); cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, printer, job, "Job completed."); break; } cupsdExpireSubscriptions(NULL, job); /* * Remove the job from the active and printing lists... */ cupsArrayRemove(ActiveJobs, job); cupsArrayRemove(PrintingJobs, job); /* * Remove any authentication data... */ snprintf(filename, sizeof(filename), "%s/a%05d", RequestRoot, job->id); if (cupsdRemoveFile(filename) && errno != ENOENT) cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to remove authentication cache: %s", strerror(errno)); cupsdClearString(&job->auth_username); cupsdClearString(&job->auth_domain); cupsdClearString(&job->auth_password); #ifdef HAVE_GSSAPI /* * Destroy the credential cache and clear the KRB5CCNAME env var string. */ if (job->ccache) { krb5_cc_destroy(KerberosContext, job->ccache); job->ccache = NULL; } cupsdClearString(&job->ccname); #endif /* HAVE_GSSAPI */ /* * Remove the print file for good if we aren't preserving jobs or * files... */ job->current_file = 0; if (!JobHistory || !JobFiles || purge) { for (i = 1; i <= job->num_files; i ++) { snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, job->id, i); unlink(filename); } if (job->num_files > 0) { free(job->filetypes); free(job->compressions); job->num_files = 0; job->filetypes = NULL; job->compressions = NULL; } } if (JobHistory && !purge) { /* * Save job state info... */ job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); } else { /* * Remove the job info file... */ snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, job->id); unlink(filename); /* * Remove the job from the "all jobs" list... */ cupsArrayRemove(Jobs, job); /* * Free all memory used... */ cupsdDeleteJob(job); } cupsdSetBusyState(); } /* * 'cupsdCancelJobs()' - Cancel all jobs for the given destination/user... */ void cupsdCancelJobs(const char *dest, /* I - Destination to cancel */ const char *username, /* I - Username or NULL */ int purge) /* I - Purge jobs? */ { cupsd_job_t *job; /* Current job */ for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); job; job = (cupsd_job_t *)cupsArrayNext(Jobs)) { if (!job->dest || !job->username) cupsdLoadJob(job); if (!job->dest || !job->username) continue; if ((dest == NULL || !strcmp(job->dest, dest)) && (username == NULL || !strcmp(job->username, username))) { /* * Cancel all jobs matching this destination/user... */ cupsdCancelJob(job, purge, IPP_JOB_CANCELED); } } cupsdCheckJobs(); } /* * 'cupsdCheckJobs()' - Check the pending jobs and start any if the destination * is available. */ void cupsdCheckJobs(void) { cupsd_job_t *job; /* Current job in queue */ cupsd_printer_t *printer, /* Printer destination */ *pclass; /* Printer class destination */ ipp_attribute_t *attr; /* Job attribute */ DEBUG_puts("cupsdCheckJobs()"); cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCheckJobs: %d active jobs, sleeping=%d, reload=%d", cupsArrayCount(ActiveJobs), Sleeping, NeedReload); for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs); job; job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) { /* * Start held jobs if they are ready... */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCheckJobs: Job %d: state_value=%d, loaded=%s", job->id, job->state_value, job->attrs ? "yes" : "no"); if (job->state_value == IPP_JOB_HELD && job->hold_until && job->hold_until < time(NULL)) { if (job->pending_timeout) { /* Add trailing banner as needed */ if (cupsdTimeoutJob(job)) continue; } job->state->values[0].integer = IPP_JOB_PENDING; job->state_value = IPP_JOB_PENDING; if ((attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_KEYWORD)) == NULL) attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); if (attr) { attr->value_tag = IPP_TAG_KEYWORD; cupsdSetString(&(attr->values[0].string.text), "no-hold"); job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); } } /* * Start pending jobs if the destination is available... */ if (job->state_value == IPP_JOB_PENDING && !NeedReload && !Sleeping) { printer = cupsdFindDest(job->dest); pclass = NULL; while (printer && (printer->type & (CUPS_PRINTER_IMPLICIT | CUPS_PRINTER_CLASS))) { /* * If the class is remote, just pass it to the remote server... */ pclass = printer; if (pclass->state == IPP_PRINTER_STOPPED) printer = NULL; else if (pclass->type & CUPS_PRINTER_REMOTE) break; else printer = cupsdFindAvailablePrinter(printer->name); } if (!printer && !pclass) { /* * Whoa, the printer and/or class for this destination went away; * cancel the job... */ cupsdLogMessage(CUPSD_LOG_WARN, "[Job %d] Printer/class %s has gone away; canceling job!", job->id, job->dest); cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, "Job canceled because the destination printer/class has " "gone away."); cupsdCancelJob(job, 1, IPP_JOB_ABORTED); } else if (printer) { /* * See if the printer is available or remote and not printing a job; * if so, start the job... */ if (pclass) { /* * Add/update a job-actual-printer-uri attribute for this job * so that we know which printer actually printed the job... */ if ((attr = ippFindAttribute(job->attrs, "job-actual-printer-uri", IPP_TAG_URI)) != NULL) cupsdSetString(&attr->values[0].string.text, printer->uri); else ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-actual-printer-uri", NULL, printer->uri); job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); } if ((!(printer->type & CUPS_PRINTER_DISCOVERED) && /* Printer is local */ printer->state == IPP_PRINTER_IDLE) || /* and idle */ ((printer->type & CUPS_PRINTER_DISCOVERED) && /* Printer is remote */ !printer->job)) /* and not printing */ { /* * Clear any message and reasons for the queue... */ printer->state_message[0] = '\0'; cupsdSetPrinterReasons(printer, "none"); /* * Start the job... */ start_job(job, printer); } } } } } /* * 'cupsdCleanJobs()' - Clean out old jobs. */ void cupsdCleanJobs(void) { cupsd_job_t *job; /* Current job */ if (MaxJobs <= 0) return; for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); job && cupsArrayCount(Jobs) >= MaxJobs; job = (cupsd_job_t *)cupsArrayNext(Jobs)) if (job->state_value >= IPP_JOB_CANCELED) cupsdCancelJob(job, 1, IPP_JOB_CANCELED); } /* * 'cupsdDeleteJob()' - Free all memory used by a job. */ void cupsdDeleteJob(cupsd_job_t *job) /* I - Job */ { cupsdClearString(&job->username); cupsdClearString(&job->dest); cupsdClearString(&job->auth_username); cupsdClearString(&job->auth_domain); cupsdClearString(&job->auth_password); #ifdef HAVE_GSSAPI /* * Destroy the credential cache and clear the KRB5CCNAME env var string. */ if (job->ccache) { krb5_cc_destroy(KerberosContext, job->ccache); job->ccache = NULL; } cupsdClearString(&job->ccname); #endif /* HAVE_GSSAPI */ if (job->num_files > 0) { free(job->compressions); free(job->filetypes); } ippDelete(job->attrs); free(job); } /* * 'cupsdFinishJob()' - Finish a job. */ void cupsdFinishJob(cupsd_job_t *job) /* I - Job */ { cupsd_printer_t *printer; /* Current printer */ ipp_attribute_t *attr; /* job-hold-until attribute */ cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] File %d is complete.", job->id, job->current_file - 1); cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] cupsdFinishJob: job->status is %d", job->id, job->status); if (job->status_buffer && (job->status < 0 || job->current_file >= job->num_files)) { /* * Close the pipe and clear the input bit. */ cupsdRemoveSelect(job->status_buffer->fd); cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] cupsdFinishJob: Closing status pipes [ %d %d ]...", job->id, job->status_pipes[0], job->status_pipes[1]); cupsdClosePipe(job->status_pipes); cupsdStatBufDelete(job->status_buffer); job->status_buffer = NULL; } printer = job->printer; update_job_attrs(job); if (job->status < 0) { /* * Backend had errors; stop it... */ 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 * variable first and then convert... */ exit_code = -job->status; if (WIFEXITED(exit_code)) exit_code = WEXITSTATUS(exit_code); else exit_code = job->status; cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Backend returned status %d (%s)", job->id, exit_code, exit_code == CUPS_BACKEND_FAILED ? "failed" : exit_code == CUPS_BACKEND_AUTH_REQUIRED ? "authentication required" : exit_code == CUPS_BACKEND_HOLD ? "hold job" : exit_code == CUPS_BACKEND_STOP ? "stop printer" : exit_code == CUPS_BACKEND_CANCEL ? "cancel job" : exit_code < 0 ? "crashed" : "unknown"); /* * Do what needs to be done... */ switch (exit_code) { default : case CUPS_BACKEND_FAILED : /* * Backend failure, use the error-policy to determine how to * act... */ cupsdStopJob(job, 0); if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT)) { /* * Mark the job as pending again - we'll retry on another * printer... */ job->state->values[0].integer = IPP_JOB_PENDING; job->state_value = IPP_JOB_PENDING; } job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); /* * If the job was queued to a class, try requeuing it... For * faxes and retry-job queues, hold the current job for 5 minutes. */ if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT)) cupsdCheckJobs(); else if ((printer->type & CUPS_PRINTER_FAX) || !strcmp(printer->error_policy, "retry-job")) { /* * See how many times we've tried to send the job; if more than * the limit, cancel the job. */ job->tries ++; if (job->tries >= JobRetryLimit) { /* * Too many tries... */ cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Canceling job since it could not be " "sent after %d tries.", job->id, JobRetryLimit); cupsdCancelJob(job, 0, IPP_JOB_ABORTED); } else { /* * Try again in N seconds... */ set_hold_until(job, time(NULL) + JobRetryInterval); cupsdAddEvent(CUPSD_EVENT_JOB_STATE, job->printer, job, "Job held due to fax errors; please consult " "the error_log file for details."); cupsdSetPrinterState(printer, IPP_PRINTER_IDLE, 0); } } else if (!strcmp(printer->error_policy, "abort-job")) cupsdCancelJob(job, 0, IPP_JOB_ABORTED); else { cupsdSetPrinterState(printer, IPP_PRINTER_STOPPED, 1); cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job, "Job stopped due to backend errors; please consult " "the error_log file for details."); } break; case CUPS_BACKEND_CANCEL : /* * Cancel the job... */ cupsdCancelJob(job, 0, IPP_JOB_CANCELED); break; case CUPS_BACKEND_HOLD : /* * Hold the job... */ cupsdStopJob(job, 0); cupsdSetJobHoldUntil(job, "indefinite"); job->state->values[0].integer = IPP_JOB_HELD; job->state_value = IPP_JOB_HELD; job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job, "Job held due to backend errors; please consult " "the error_log file for details."); break; case CUPS_BACKEND_STOP : /* * Stop the printer... */ cupsdStopJob(job, 0); job->state->values[0].integer = IPP_JOB_PENDING; job->state_value = IPP_JOB_PENDING; job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); cupsdSetPrinterState(printer, IPP_PRINTER_STOPPED, 1); cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job, "Job stopped due to backend errors; please consult " "the error_log file for details."); break; case CUPS_BACKEND_AUTH_REQUIRED : cupsdStopJob(job, 0); cupsdSetJobHoldUntil(job, "auth-info-required"); if ((attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_KEYWORD)) == NULL) attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); if (attr) { attr->value_tag = IPP_TAG_KEYWORD; cupsdSetString(&(attr->values[0].string.text), "auth-info-required"); } job->state->values[0].integer = IPP_JOB_HELD; job->state_value = IPP_JOB_HELD; job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job, "Authentication is required for job %d.", job->id); break; } /* * Try printing another job... */ cupsdCheckJobs(); } else if (job->status > 0) { /* * Filter had errors; stop job... */ cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Job stopped due to filter errors.", job->id); cupsdStopJob(job, 1); job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job, "Job stopped due to filter errors; please consult the " "error_log file for details."); cupsdCheckJobs(); } else { /* * Job printed successfully; cancel it... */ if (job->current_file < job->num_files) { /* * Start the next file in the job... */ FilterLevel -= job->cost; start_job(job, printer); } else { /* * Close out this job... */ cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Completed successfully.", job->id); cupsdCancelJob(job, 0, IPP_JOB_COMPLETED); cupsdCheckJobs(); } } } /* * 'cupsdFreeAllJobs()' - Free all jobs from memory. */ void cupsdFreeAllJobs(void) { cupsd_job_t *job; /* Current job */ if (!Jobs) return; cupsdHoldSignals(); cupsdStopAllJobs(1); cupsdSaveAllJobs(); for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); job; job = (cupsd_job_t *)cupsArrayNext(Jobs)) { cupsArrayRemove(Jobs, job); cupsArrayRemove(ActiveJobs, job); cupsArrayRemove(PrintingJobs, job); cupsdDeleteJob(job); } cupsdReleaseSignals(); } /* * 'cupsdFindJob()' - Find the specified job. */ cupsd_job_t * /* O - Job data */ cupsdFindJob(int id) /* I - Job ID */ { cupsd_job_t key; /* Search key */ key.id = id; return ((cupsd_job_t *)cupsArrayFind(Jobs, &key)); } /* * 'cupsdGetPrinterJobCount()' - Get the number of pending, processing, * or held jobs in a printer or class. */ int /* O - Job count */ cupsdGetPrinterJobCount( const char *dest) /* I - Printer or class name */ { int count; /* Job count */ cupsd_job_t *job; /* Current job */ for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0; job; job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) if (job->dest && !strcasecmp(job->dest, dest)) count ++; return (count); } /* * 'cupsdGetUserJobCount()' - Get the number of pending, processing, * or held jobs for a user. */ int /* O - Job count */ cupsdGetUserJobCount( const char *username) /* I - Username */ { int count; /* Job count */ cupsd_job_t *job; /* Current job */ for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0; job; job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) if (!strcasecmp(job->username, username)) count ++; return (count); } /* * 'cupsdHoldJob()' - Hold the specified job. */ void cupsdHoldJob(cupsd_job_t *job) /* I - Job data */ { cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdHoldJob: id = %d", job->id); if (job->state_value == IPP_JOB_PROCESSING) cupsdStopJob(job, 0); else cupsdLoadJob(job); DEBUG_puts("cupsdHoldJob: setting state to held..."); job->state->values[0].integer = IPP_JOB_HELD; job->state_value = IPP_JOB_HELD; job->current_file = 0; job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); cupsdCheckJobs(); } /* * 'cupsdLoadAllJobs()' - Load all jobs from disk. */ void cupsdLoadAllJobs(void) { char filename[1024]; /* Full filename of job.cache file */ struct stat fileinfo, /* Information on job.cache file */ dirinfo; /* Information on RequestRoot dir */ /* * Create the job arrays as needed... */ if (!Jobs) Jobs = cupsArrayNew(compare_jobs, NULL); if (!ActiveJobs) ActiveJobs = cupsArrayNew(compare_active_jobs, NULL); if (!PrintingJobs) PrintingJobs = cupsArrayNew(compare_jobs, NULL); /* * See whether the job.cache file is older than the RequestRoot directory... */ snprintf(filename, sizeof(filename), "%s/job.cache", CacheDir); if (stat(filename, &fileinfo)) { fileinfo.st_mtime = 0; if (errno != ENOENT) cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to get file information for \"%s\" - %s", filename, strerror(errno)); } if (stat(RequestRoot, &dirinfo)) { dirinfo.st_mtime = 0; if (errno != ENOENT) cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to get directory information for \"%s\" - %s", RequestRoot, strerror(errno)); } /* * Load the most recent source for job data... */ if (dirinfo.st_mtime > fileinfo.st_mtime) { load_request_root(); load_next_job_id(filename); } else load_job_cache(filename); /* * Clean out old jobs as needed... */ if (MaxJobs > 0 && cupsArrayCount(Jobs) >= MaxJobs) cupsdCleanJobs(); } /* * 'cupsdLoadJob()' - Load a single job... */ void cupsdLoadJob(cupsd_job_t *job) /* I - Job */ { char jobfile[1024]; /* Job filename */ cups_file_t *fp; /* Job file */ int fileid; /* Current file ID */ ipp_attribute_t *attr; /* Job attribute */ const char *dest; /* Destination name */ cupsd_printer_t *destptr; /* Pointer to destination */ mime_type_t **filetypes; /* New filetypes array */ int *compressions; /* New compressions array */ if (job->attrs) { if (job->state_value > IPP_JOB_STOPPED) job->access_time = time(NULL); return; } if ((job->attrs = ippNew()) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Ran out of memory for job attributes!", job->id); return; } /* * Load job attributes... */ 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)); ippDelete(job->attrs); job->attrs = NULL; return; } if (ippReadIO(fp, (ipp_iocb_t)cupsFileRead, 1, NULL, job->attrs) != IPP_DATA) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unable to read job control file \"%s\"!", job->id, jobfile); cupsFileClose(fp); ippDelete(job->attrs); job->attrs = NULL; unlink(jobfile); return; } cupsFileClose(fp); /* * Copy attribute data to the job object... */ if ((job->state = ippFindAttribute(job->attrs, "job-state", IPP_TAG_ENUM)) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Missing or bad job-state attribute in " "control file!", job->id); ippDelete(job->attrs); job->attrs = NULL; unlink(jobfile); return; } job->state_value = (ipp_jstate_t)job->state->values[0].integer; if (!job->dest) { if ((attr = ippFindAttribute(job->attrs, "job-printer-uri", IPP_TAG_URI)) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] No job-printer-uri attribute in control file!", job->id); ippDelete(job->attrs); job->attrs = NULL; unlink(jobfile); return; } if ((dest = cupsdValidateDest(attr->values[0].string.text, &(job->dtype), &destptr)) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unable to queue job for destination \"%s\"!", job->id, attr->values[0].string.text); ippDelete(job->attrs); job->attrs = NULL; unlink(jobfile); return; } cupsdSetString(&job->dest, dest); } else if ((destptr = cupsdFindDest(job->dest)) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unable to queue job for destination \"%s\"!", job->id, job->dest); ippDelete(job->attrs); job->attrs = NULL; unlink(jobfile); return; } job->sheets = ippFindAttribute(job->attrs, "job-media-sheets-completed", IPP_TAG_INTEGER); job->job_sheets = ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_NAME); if (!job->priority) { if ((attr = ippFindAttribute(job->attrs, "job-priority", IPP_TAG_INTEGER)) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Missing or bad job-priority attribute in " "control file!", job->id); ippDelete(job->attrs); job->attrs = NULL; unlink(jobfile); return; } job->priority = attr->values[0].integer; } if (!job->username) { if ((attr = ippFindAttribute(job->attrs, "job-originating-user-name", IPP_TAG_NAME)) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Missing or bad job-originating-user-name " "attribute in control file!", job->id); ippDelete(job->attrs); job->attrs = NULL; unlink(jobfile); return; } cupsdSetString(&job->username, attr->values[0].string.text); } /* * Set the job hold-until time and state... */ if (job->state_value == IPP_JOB_HELD) { if ((attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_KEYWORD)) == NULL) attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); if (attr) cupsdSetJobHoldUntil(job, attr->values[0].string.text); else { job->state->values[0].integer = IPP_JOB_PENDING; job->state_value = IPP_JOB_PENDING; } } else if (job->state_value == IPP_JOB_PROCESSING) { job->state->values[0].integer = IPP_JOB_PENDING; job->state_value = IPP_JOB_PENDING; } if (!job->num_files) { /* * Find all the d##### files... */ for (fileid = 1; fileid < 10000; fileid ++) { snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot, job->id, fileid); if (access(jobfile, 0)) break; cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Auto-typing document file \"%s\"...", job->id, jobfile); if (fileid > job->num_files) { if (job->num_files == 0) { compressions = (int *)calloc(fileid, sizeof(int)); filetypes = (mime_type_t **)calloc(fileid, sizeof(mime_type_t *)); } else { compressions = (int *)realloc(job->compressions, sizeof(int) * fileid); filetypes = (mime_type_t **)realloc(job->filetypes, sizeof(mime_type_t *) * fileid); } if (!compressions || !filetypes) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Ran out of memory for job file types!", job->id); return; } job->compressions = compressions; job->filetypes = filetypes; job->num_files = fileid; } job->filetypes[fileid - 1] = mimeFileType(MimeDatabase, jobfile, NULL, job->compressions + fileid - 1); if (!job->filetypes[fileid - 1]) job->filetypes[fileid - 1] = mimeType(MimeDatabase, "application", "vnd.cups-raw"); } } /* * Load authentication information as needed... */ if (job->state_value < IPP_JOB_STOPPED) { snprintf(jobfile, sizeof(jobfile), "%s/a%05d", RequestRoot, job->id); cupsdClearString(&job->auth_username); cupsdClearString(&job->auth_domain); cupsdClearString(&job->auth_password); 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 */ for (i = 0; i < destptr->num_auth_info_required && cupsFileGets(fp, line, sizeof(line)); i ++) { bytes = sizeof(data); httpDecode64_2(data, &bytes, line); if (!strcmp(destptr->auth_info_required[i], "username")) cupsdSetStringf(&job->auth_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); } cupsFileClose(fp); } } job->access_time = time(NULL); } /* * 'cupsdMoveJob()' - Move the specified job to a different destination. */ void cupsdMoveJob(cupsd_job_t *job, /* I - Job */ cupsd_printer_t *p) /* I - Destination printer or class */ { ipp_attribute_t *attr; /* job-printer-uri attribute */ const char *olddest; /* Old destination */ cupsd_printer_t *oldp; /* Old pointer */ /* * Don't move completed jobs... */ if (job->state_value > IPP_JOB_STOPPED) return; /* * Get the old destination... */ olddest = job->dest; if (job->printer) oldp = job->printer; else oldp = cupsdFindDest(olddest); /* * Change the destination information... */ if (job->state_value == IPP_JOB_PROCESSING) cupsdStopJob(job, 0); else cupsdLoadJob(job); cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, oldp, job, "Job #%d moved from %s to %s.", job->id, olddest, p->name); cupsdSetString(&job->dest, p->name); job->dtype = p->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT); if ((attr = ippFindAttribute(job->attrs, "job-printer-uri", IPP_TAG_URI)) != NULL) cupsdSetString(&(attr->values[0].string.text), p->uri); cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, p, job, "Job #%d moved from %s to %s.", job->id, olddest, p->name); job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); } /* * 'cupsdReleaseJob()' - Release the specified job. */ void cupsdReleaseJob(cupsd_job_t *job) /* I - Job */ { cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdReleaseJob: id = %d", job->id); if (job->state_value == IPP_JOB_HELD) { DEBUG_puts("cupsdReleaseJob: setting state to pending..."); job->state->values[0].integer = IPP_JOB_PENDING; job->state_value = IPP_JOB_PENDING; job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); cupsdCheckJobs(); } } /* * 'cupsdRestartJob()' - Restart the specified job. */ void cupsdRestartJob(cupsd_job_t *job) /* I - Job */ { cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdRestartJob: id = %d", job->id); if (job->state_value == IPP_JOB_STOPPED || job->num_files) { ipp_jstate_t old_state; /* Old job state */ cupsdLoadJob(job); old_state = job->state_value; job->tries = 0; job->state->values[0].integer = IPP_JOB_PENDING; job->state_value = IPP_JOB_PENDING; job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); if (old_state > IPP_JOB_STOPPED) cupsArrayAdd(ActiveJobs, job); cupsdCheckJobs(); } } /* * 'cupsdSaveAllJobs()' - Save a summary of all jobs to disk. */ void cupsdSaveAllJobs(void) { int i; /* Looping var */ cups_file_t *fp; /* Job cache file */ char 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)); 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); /* * Write a small header to the file... */ curtime = time(NULL); curdate = localtime(&curtime); strftime(temp, sizeof(temp) - 1, "%Y-%m-%d %H:%M", curdate); cupsFilePuts(fp, "# Job cache file for " CUPS_SVERSION "\n"); cupsFilePrintf(fp, "# Written by cupsd on %s\n", temp); cupsFilePrintf(fp, "NextJobId %d\n", NextJobId); /* * Write each job known to the system... */ for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); job; job = (cupsd_job_t *)cupsArrayNext(Jobs)) { cupsFilePrintf(fp, "\n", job->id); cupsFilePrintf(fp, "State %d\n", job->state_value); cupsFilePrintf(fp, "Priority %d\n", job->priority); cupsFilePrintf(fp, "Username %s\n", job->username); cupsFilePrintf(fp, "Destination %s\n", job->dest); cupsFilePrintf(fp, "DestType %d\n", job->dtype); cupsFilePrintf(fp, "NumFiles %d\n", job->num_files); for (i = 0; i < job->num_files; i ++) cupsFilePrintf(fp, "File %d %s/%s %d\n", i + 1, job->filetypes[i]->super, job->filetypes[i]->type, job->compressions[i]); cupsFilePuts(fp, "\n"); } cupsFileClose(fp); } /* * 'cupsdSaveJob()' - Save a job to disk. */ void cupsdSaveJob(cupsd_job_t *job) /* I - Job */ { char filename[1024]; /* Job control filename */ cups_file_t *fp; /* Job file */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSaveJob(job=%p(%d)): job->attrs=%p", job, job->id, job->attrs); snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, job->id); 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)); 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->dirty = 0; } /* * 'cupsdSetJobHoldUntil()' - Set the hold time for a job... */ void cupsdSetJobHoldUntil(cupsd_job_t *job, /* I - Job */ const char *when) /* I - When to resume */ { time_t curtime; /* Current time */ struct tm *curdate; /* Current date */ int hour; /* Hold hour */ int minute; /* Hold minute */ int second; /* Hold second */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSetJobHoldUntil(%d, \"%s\")", job->id, when); second = 0; if (!strcmp(when, "indefinite") || !strcmp(when, "auth-info-required")) { /* * Hold indefinitely... */ job->hold_until = 0; } else if (!strcmp(when, "day-time")) { /* * Hold to 6am the next morning unless local time is < 6pm. */ curtime = time(NULL); curdate = localtime(&curtime); if (curdate->tm_hour < 18) job->hold_until = curtime; else job->hold_until = curtime + ((29 - curdate->tm_hour) * 60 + 59 - curdate->tm_min) * 60 + 60 - curdate->tm_sec; } else if (!strcmp(when, "evening") || !strcmp(when, "night")) { /* * Hold to 6pm unless local time is > 6pm or < 6am. */ curtime = time(NULL); curdate = localtime(&curtime); if (curdate->tm_hour < 6 || curdate->tm_hour >= 18) job->hold_until = curtime; else job->hold_until = curtime + ((17 - curdate->tm_hour) * 60 + 59 - curdate->tm_min) * 60 + 60 - curdate->tm_sec; } else if (!strcmp(when, "second-shift")) { /* * Hold to 4pm unless local time is > 4pm. */ curtime = time(NULL); curdate = localtime(&curtime); if (curdate->tm_hour >= 16) job->hold_until = curtime; else job->hold_until = curtime + ((15 - curdate->tm_hour) * 60 + 59 - curdate->tm_min) * 60 + 60 - curdate->tm_sec; } else if (!strcmp(when, "third-shift")) { /* * Hold to 12am unless local time is < 8am. */ curtime = time(NULL); curdate = localtime(&curtime); if (curdate->tm_hour < 8) job->hold_until = curtime; else job->hold_until = curtime + ((23 - curdate->tm_hour) * 60 + 59 - curdate->tm_min) * 60 + 60 - curdate->tm_sec; } else if (!strcmp(when, "weekend")) { /* * Hold to weekend unless we are in the weekend. */ curtime = time(NULL); curdate = localtime(&curtime); if (curdate->tm_wday || curdate->tm_wday == 6) job->hold_until = curtime; else job->hold_until = curtime + (((5 - curdate->tm_wday) * 24 + (17 - curdate->tm_hour)) * 60 + 59 - curdate->tm_min) * 60 + 60 - curdate->tm_sec; } else if (sscanf(when, "%d:%d:%d", &hour, &minute, &second) >= 2) { /* * Hold to specified GMT time (HH:MM or HH:MM:SS)... */ curtime = time(NULL); curdate = gmtime(&curtime); job->hold_until = curtime + ((hour - curdate->tm_hour) * 60 + minute - curdate->tm_min) * 60 + second - curdate->tm_sec; /* * Hold until next day as needed... */ if (job->hold_until < curtime) job->hold_until += 24 * 60 * 60; } cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSetJobHoldUntil: hold_until = %d", (int)job->hold_until); } /* * 'cupsdSetJobPriority()' - Set the priority of a job, moving it up/down in * the list as needed. */ void cupsdSetJobPriority( cupsd_job_t *job, /* I - Job ID */ int priority) /* I - New priority (0 to 100) */ { ipp_attribute_t *attr; /* Job attribute */ /* * Don't change completed jobs... */ if (job->state_value >= IPP_JOB_PROCESSING) return; /* * Set the new priority and re-add the job into the active list... */ cupsArrayRemove(ActiveJobs, job); job->priority = priority; if ((attr = ippFindAttribute(job->attrs, "job-priority", IPP_TAG_INTEGER)) != NULL) attr->values[0].integer = priority; else ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-priority", priority); cupsArrayAdd(ActiveJobs, job); job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); } /* * 'cupsdStopAllJobs()' - Stop all print jobs. */ void cupsdStopAllJobs(int force) /* I - 1 = Force all filters to stop */ { cupsd_job_t *job; /* Current job */ DEBUG_puts("cupsdStopAllJobs()"); for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs); job; job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) if (job->state_value == IPP_JOB_PROCESSING) { cupsdStopJob(job, force); job->state->values[0].integer = IPP_JOB_PENDING; job->state_value = IPP_JOB_PENDING; } } /* * 'cupsdStopJob()' - Stop a print job. */ void cupsdStopJob(cupsd_job_t *job, /* I - Job */ int force) /* I - 1 = Force all filters to stop */ { int i; /* Looping var */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] cupsdStopJob: force = %d", job->id, force); if (job->state_value != IPP_JOB_PROCESSING) return; FilterLevel -= job->cost; if (job->printer->state == IPP_PRINTER_PROCESSING) cupsdSetPrinterState(job->printer, IPP_PRINTER_IDLE, 0); job->state->values[0].integer = IPP_JOB_STOPPED; job->state_value = IPP_JOB_STOPPED; job->printer->job = NULL; job->printer = NULL; job->current_file --; for (i = 0; job->filters[i]; i ++) if (job->filters[i] > 0) { cupsdEndProcess(job->filters[i], force); job->filters[i] = 0; } if (job->backend > 0) { cupsdEndProcess(job->backend, force); job->backend = 0; } cupsdDestroyProfile(job->profile); job->profile = NULL; cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] Closing print pipes [ %d %d ]...", job->id, job->print_pipes[0], job->print_pipes[1]); cupsdClosePipe(job->print_pipes); cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] Closing back pipes [ %d %d ]...", job->id, job->back_pipes[0], job->back_pipes[1]); cupsdClosePipe(job->back_pipes); cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] Closing side pipes [ %d %d ]...", job->id, job->side_pipes[0], job->side_pipes[1]); cupsdClosePipe(job->side_pipes); if (job->status_buffer) { /* * Close the pipe and clear the input bit. */ cupsdRemoveSelect(job->status_buffer->fd); cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] Closing status pipes [ %d %d ]...", job->id, job->status_pipes[0], job->status_pipes[1]); cupsdClosePipe(job->status_pipes); cupsdStatBufDelete(job->status_buffer); job->status_buffer = NULL; } } /* * 'cupsdUnloadCompletedJobs()' - Flush completed job history from memory. */ void cupsdUnloadCompletedJobs(void) { cupsd_job_t *job; /* Current job */ time_t expire; /* Expiration time */ expire = time(NULL) - 60; for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); job; job = (cupsd_job_t *)cupsArrayNext(Jobs)) if (job->attrs && job->state_value >= IPP_JOB_STOPPED && job->access_time < expire) { if (job->dirty) cupsdSaveJob(job); unload_job(job); } } /* * 'compare_active_jobs()' - Compare the job IDs and priorities of two jobs. */ static int /* O - Difference */ compare_active_jobs(void *first, /* I - First job */ void *second, /* I - Second job */ void *data) /* I - App data (not used) */ { int diff; /* Difference */ if ((diff = ((cupsd_job_t *)second)->priority - ((cupsd_job_t *)first)->priority) != 0) return (diff); else return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id); } /* * 'compare_jobs()' - Compare the job IDs of two jobs. */ static int /* O - Difference */ compare_jobs(void *first, /* I - First job */ void *second, /* I - Second job */ void *data) /* I - App data (not used) */ { return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id); } /* * 'ipp_length()' - Compute the size of the buffer needed to hold * the textual IPP attributes. */ static int /* O - Size of attribute buffer */ ipp_length(ipp_t *ipp) /* I - IPP request */ { int bytes; /* Number of bytes */ int i; /* Looping var */ ipp_attribute_t *attr; /* Current attribute */ /* * Loop through all attributes... */ bytes = 0; for (attr = ipp->attrs; attr != NULL; attr = attr->next) { /* * Skip attributes that won't be sent to filters... */ if (attr->value_tag == IPP_TAG_MIMETYPE || attr->value_tag == IPP_TAG_NAMELANG || attr->value_tag == IPP_TAG_TEXTLANG || attr->value_tag == IPP_TAG_URI || attr->value_tag == IPP_TAG_URISCHEME) continue; if (strncmp(attr->name, "time-", 5) == 0) continue; /* * Add space for a leading space and commas between each value. * For the first attribute, the leading space isn't used, so the * extra byte can be used as the nul terminator... */ bytes ++; /* " " separator */ bytes += attr->num_values; /* "," separators */ /* * Boolean attributes appear as "foo,nofoo,foo,nofoo", while * other attributes appear as "foo=value1,value2,...,valueN". */ if (attr->value_tag != IPP_TAG_BOOLEAN) bytes += strlen(attr->name); else bytes += attr->num_values * strlen(attr->name); /* * Now add the size required for each value in the attribute... */ switch (attr->value_tag) { case IPP_TAG_INTEGER : case IPP_TAG_ENUM : /* * Minimum value of a signed integer is -2147483647, or 11 digits. */ bytes += attr->num_values * 11; break; case IPP_TAG_BOOLEAN : /* * Add two bytes for each false ("no") value... */ for (i = 0; i < attr->num_values; i ++) if (!attr->values[i].boolean) bytes += 2; break; case IPP_TAG_RANGE : /* * A range is two signed integers separated by a hyphen, or * 23 characters max. */ bytes += attr->num_values * 23; break; case IPP_TAG_RESOLUTION : /* * A resolution is two signed integers separated by an "x" and * suffixed by the units, or 26 characters max. */ bytes += attr->num_values * 26; break; case IPP_TAG_STRING : case IPP_TAG_TEXT : case IPP_TAG_NAME : case IPP_TAG_KEYWORD : case IPP_TAG_CHARSET : case IPP_TAG_LANGUAGE : case IPP_TAG_URI : /* * Strings can contain characters that need quoting. We need * at least 2 * len + 2 characters to cover the quotes and * any backslashes in the string. */ for (i = 0; i < attr->num_values; i ++) bytes += 2 * strlen(attr->values[i].string.text) + 2; break; default : break; /* anti-compiler-warning-code */ } } return (bytes); } /* * 'load_job_cache()' - Load jobs from the job.cache file. */ static void load_job_cache(const char *filename) /* I - job.cache filename */ { cups_file_t *fp; /* job.cache file */ char line[1024], /* Line buffer */ *value; /* Value on line */ int linenum; /* Line number in file */ cupsd_job_t *job; /* Current job */ int jobid; /* Job ID */ char jobfile[1024]; /* Job filename */ /* * Open the job.cache file... */ if ((fp = cupsFileOpen(filename, "r")) == NULL) { if (errno != ENOENT) cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open job cache file \"%s\": %s", filename, strerror(errno)); load_request_root(); return; } /* * Read entries from the job cache file and create jobs as needed. */ cupsdLogMessage(CUPSD_LOG_INFO, "Loading job cache file \"%s\"...", filename); linenum = 0; job = NULL; while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) { if (!strcasecmp(line, "NextJobId")) { if (value) NextJobId = atoi(value); } else if (!strcasecmp(line, " directive on line %d!", linenum); continue; } if (!value) { cupsdLogMessage(CUPSD_LOG_ERROR, "Missing job ID on line %d!", linenum); continue; } jobid = atoi(value); if (jobid < 1) { cupsdLogMessage(CUPSD_LOG_ERROR, "Bad job ID %d on line %d!", jobid, linenum); continue; } snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, jobid); if (access(jobfile, 0)) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Files have gone away!", jobid); continue; } job = calloc(1, sizeof(cupsd_job_t)); if (!job) { cupsdLogMessage(CUPSD_LOG_EMERG, "[Job %d] Unable to allocate memory for job!", jobid); break; } job->id = jobid; job->back_pipes[0] = -1; job->back_pipes[1] = -1; job->print_pipes[0] = -1; job->print_pipes[1] = -1; job->side_pipes[0] = -1; job->side_pipes[1] = -1; job->status_pipes[0] = -1; job->status_pipes[1] = -1; cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Loading from cache...", job->id); } else if (!job) { cupsdLogMessage(CUPSD_LOG_ERROR, "Missing directive on line %d!", linenum); continue; } else if (!strcasecmp(line, "")) { cupsArrayAdd(Jobs, job); if (job->state_value <= IPP_JOB_STOPPED) { cupsArrayAdd(ActiveJobs, job); cupsdLoadJob(job); } job = NULL; } else if (!value) { cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d!", linenum); continue; } else if (!strcasecmp(line, "State")) { job->state_value = (ipp_jstate_t)atoi(value); if (job->state_value < IPP_JOB_PENDING) job->state_value = IPP_JOB_PENDING; else if (job->state_value > IPP_JOB_COMPLETED) job->state_value = IPP_JOB_COMPLETED; } else if (!strcasecmp(line, "Priority")) { job->priority = atoi(value); } else if (!strcasecmp(line, "Username")) { cupsdSetString(&job->username, value); } else if (!strcasecmp(line, "Destination")) { cupsdSetString(&job->dest, value); } else if (!strcasecmp(line, "DestType")) { job->dtype = (cups_ptype_t)atoi(value); } else if (!strcasecmp(line, "NumFiles")) { job->num_files = atoi(value); if (job->num_files < 0) { cupsdLogMessage(CUPSD_LOG_ERROR, "Bad NumFiles value %d on line %d!", job->num_files, linenum); job->num_files = 0; continue; } if (job->num_files > 0) { snprintf(jobfile, sizeof(jobfile), "%s/d%05d-001", RequestRoot, job->id); if (access(jobfile, 0)) { cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Data files have gone away!", job->id); job->num_files = 0; continue; } job->filetypes = calloc(job->num_files, sizeof(mime_type_t *)); job->compressions = calloc(job->num_files, sizeof(int)); if (!job->filetypes || !job->compressions) { cupsdLogMessage(CUPSD_LOG_EMERG, "[Job %d] Unable to allocate memory for %d files!", job->id, job->num_files); break; } } } else if (!strcasecmp(line, "File")) { int number, /* File number */ compression; /* Compression value */ char super[MIME_MAX_SUPER], /* MIME super type */ type[MIME_MAX_TYPE]; /* MIME type */ if (sscanf(value, "%d%*[ \t]%15[^/]/%255s%d", &number, super, type, &compression) != 4) { cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File on line %d!", linenum); continue; } if (number < 1 || number > job->num_files) { cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File number %d on line %d!", number, linenum); continue; } number --; job->compressions[number] = compression; job->filetypes[number] = mimeType(MimeDatabase, super, type); if (!job->filetypes[number]) { /* * If the original MIME type is unknown, auto-type it! */ cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unknown MIME type %s/%s for file %d!", job->id, super, type, number + 1); snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot, job->id, number + 1); job->filetypes[number] = mimeFileType(MimeDatabase, jobfile, NULL, job->compressions + number); /* * If that didn't work, assume it is raw... */ if (!job->filetypes[number]) job->filetypes[number] = mimeType(MimeDatabase, "application", "vnd.cups-raw"); } } else cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown %s directive on line %d!", line, linenum); } cupsFileClose(fp); } /* * 'load_next_job_id()' - Load the NextJobId value from the job.cache file. */ static void load_next_job_id(const char *filename) /* I - job.cache filename */ { cups_file_t *fp; /* job.cache file */ char line[1024], /* Line buffer */ *value; /* Value on line */ int linenum; /* Line number in file */ int next_job_id; /* NextJobId value from line */ /* * Read the NextJobId directive from the job.cache file and use * the value (if any). */ if ((fp = cupsFileOpen(filename, "r")) == NULL) { if (errno != ENOENT) cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open job cache file \"%s\": %s", filename, strerror(errno)); return; } cupsdLogMessage(CUPSD_LOG_INFO, "Loading NextJobId from job cache file \"%s\"...", filename); linenum = 0; while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) { if (!strcasecmp(line, "NextJobId")) { if (value) { next_job_id = atoi(value); if (next_job_id > NextJobId) NextJobId = next_job_id; } break; } } cupsFileClose(fp); } /* * 'load_request_root()' - Load jobs from the RequestRoot directory. */ static void load_request_root(void) { cups_dir_t *dir; /* Directory */ cups_dentry_t *dent; /* Directory entry */ cupsd_job_t *job; /* New job */ /* * Open the requests directory... */ cupsdLogMessage(CUPSD_LOG_DEBUG, "Scanning %s for jobs...", RequestRoot); if ((dir = cupsDirOpen(RequestRoot)) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open spool directory \"%s\": %s", RequestRoot, strerror(errno)); return; } /* * Read all the c##### files... */ while ((dent = cupsDirRead(dir)) != NULL) if (strlen(dent->filename) >= 6 && dent->filename[0] == 'c') { /* * Allocate memory for the job... */ if ((job = calloc(sizeof(cupsd_job_t), 1)) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "Ran out of memory for jobs!"); cupsDirClose(dir); return; } /* * Assign the job ID... */ job->id = atoi(dent->filename + 1); job->back_pipes[0] = -1; job->back_pipes[1] = -1; job->print_pipes[0] = -1; job->print_pipes[1] = -1; job->side_pipes[0] = -1; job->side_pipes[1] = -1; job->status_pipes[0] = -1; job->status_pipes[1] = -1; if (job->id >= NextJobId) NextJobId = job->id + 1; /* * Load the job... */ cupsdLoadJob(job); /* * Insert the job into the array, sorting by job priority and ID... */ cupsArrayAdd(Jobs, job); if (job->state_value <= IPP_JOB_STOPPED) cupsArrayAdd(ActiveJobs, job); else unload_job(job); } cupsDirClose(dir); } /* * 'set_time()' - Set one of the "time-at-xyz" attributes... */ static void set_time(cupsd_job_t *job, /* I - Job to update */ const char *name) /* I - Name of attribute */ { ipp_attribute_t *attr; /* Time attribute */ if ((attr = ippFindAttribute(job->attrs, name, IPP_TAG_ZERO)) != NULL) { attr->value_tag = IPP_TAG_INTEGER; attr->values[0].integer = time(NULL); } } /* * 'set_hold_until()' - Set the hold time and update job-hold-until attribute... */ static void set_hold_until(cupsd_job_t *job, /* I - Job to update */ time_t holdtime) /* I - Hold until time */ { ipp_attribute_t *attr; /* job-hold-until attribute */ struct tm *holddate; /* Hold date */ char holdstr[64]; /* Hold time */ /* * Set the hold_until value and hold the job... */ cupsdLogMessage(CUPSD_LOG_DEBUG, "set_hold_until: hold_until = %d", (int)holdtime); job->state->values[0].integer = IPP_JOB_HELD; job->state_value = IPP_JOB_HELD; job->hold_until = holdtime; /* * Update the job-hold-until attribute with a string representing GMT * time (HH:MM:SS)... */ holddate = gmtime(&holdtime); snprintf(holdstr, sizeof(holdstr), "%d:%d:%d", holddate->tm_hour, holddate->tm_min, holddate->tm_sec); if ((attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_KEYWORD)) == NULL) attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); /* * Either add the attribute or update the value of the existing one */ if (attr == NULL) attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD, "job-hold-until", NULL, holdstr); else cupsdSetString(&attr->values[0].string.text, holdstr); job->dirty = 1; cupsdMarkDirty(CUPSD_DIRTY_JOBS); } /* * 'start_job()' - Start a print job. */ static void start_job(cupsd_job_t *job, /* I - Job ID */ cupsd_printer_t *printer) /* I - Printer to print job */ { int i; /* Looping var */ int slot; /* Pipe slot */ cups_array_t *filters, /* Filters for job */ *prefilters; /* Filters with prefilters */ mime_filter_t *filter, /* Current filter */ *prefilter, /* Prefilter */ port_monitor; /* Port monitor filter */ char method[255], /* Method for output */ *optptr, /* Pointer to options */ *valptr; /* Pointer in value string */ ipp_attribute_t *attr; /* Current attribute */ struct stat backinfo; /* Backend file information */ int backroot; /* Run backend as root? */ int pid; /* Process ID of new filter process */ int banner_page; /* 1 if banner page, 0 otherwise */ int filterfds[2][2];/* Pipes used between filters */ int envc; /* Number of environment variables */ char **argv, /* Filter command-line arguments */ sani_uri[1024], /* Sanitized DEVICE_URI env var */ filename[1024], /* Job filename */ command[1024], /* Full path to command */ jobid[255], /* Job ID string */ title[IPP_MAX_NAME], /* Job title string */ copies[255], /* # copies string */ *envp[MAX_ENV + 16], /* Environment variables */ charset[255], /* CHARSET env variable */ class_name[255],/* CLASS env variable */ classification[1024], /* CLASSIFICATION env variable */ content_type[1024], /* CONTENT_TYPE env variable */ device_uri[1024], /* DEVICE_URI env variable */ final_content_type[1024], /* FINAL_CONTENT_TYPE env variable */ lang[255], /* LANG env variable */ #ifdef __APPLE__ apple_language[255], /* APPLE_LANGUAGE env variable */ #endif /* __APPLE__ */ ppd[1024], /* PPD env variable */ printer_name[255], /* PRINTER env variable */ rip_max_cache[255]; /* RIP_MAX_CACHE env variable */ static char *options = NULL;/* Full list of options */ static int optlength = 0; /* Length of option buffer */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: file = %d/%d", job->id, job->current_file, job->num_files); if (job->num_files == 0) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] No files, canceling job!", job->id); cupsdCancelJob(job, 0, IPP_JOB_ABORTED); return; } /* * Figure out what filters are required to convert from * the source to the destination type... */ filters = NULL; job->cost = 0; if (printer->raw) { /* * Remote jobs and raw queues go directly to the printer without * filtering... */ cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Sending job to queue tagged as raw...", job->id); filters = NULL; } else { /* * Local jobs get filtered... */ filters = mimeFilter(MimeDatabase, job->filetypes[job->current_file], printer->filetype, &(job->cost)); if (!filters) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unable to convert file %d to printable format!", job->current_file, job->id); cupsdLogMessage(CUPSD_LOG_INFO, "Hint: Do you have Ghostscript installed?"); if (LogLevel < CUPSD_LOG_DEBUG) cupsdLogMessage(CUPSD_LOG_INFO, "Hint: Try setting the LogLevel to \"debug\"."); job->current_file ++; if (job->current_file == job->num_files) cupsdCancelJob(job, 0, IPP_JOB_ABORTED); return; } /* * Remove NULL ("-") filters... */ for (filter = (mime_filter_t *)cupsArrayFirst(filters); filter; filter = (mime_filter_t *)cupsArrayNext(filters)) if (!strcmp(filter->filter, "-")) cupsArrayRemove(filters, filter); if (cupsArrayCount(filters) == 0) { cupsArrayDelete(filters); filters = NULL; } /* * If this printer has any pre-filters, insert the required pre-filter * in the filters array... */ if (printer->prefiltertype && filters) { prefilters = cupsArrayNew(NULL, NULL); for (filter = (mime_filter_t *)cupsArrayFirst(filters); filter; filter = (mime_filter_t *)cupsArrayNext(filters)) { if ((prefilter = mimeFilterLookup(MimeDatabase, filter->src, printer->prefiltertype))) { cupsArrayAdd(prefilters, prefilter); job->cost += prefilter->cost; } cupsArrayAdd(prefilters, filter); } cupsArrayDelete(filters); filters = prefilters; } } /* * Set a minimum cost of 100 for all jobs so that FilterLimit * works with raw queues and other low-cost paths. */ if (job->cost < 100) job->cost = 100; /* * See if the filter cost is too high... */ if ((FilterLevel + job->cost) > FilterLimit && FilterLevel > 0 && FilterLimit > 0) { /* * Don't print this job quite yet... */ cupsArrayDelete(filters); cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Holding because filter limit has been reached.", job->id); cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: file=%d, cost=%d, level=%d, limit=%d", job->id, job->current_file, job->cost, FilterLevel, FilterLimit); return; } FilterLevel += job->cost; /* * Add decompression/raw filter as needed... */ if ((!printer->raw && job->compressions[job->current_file]) || (!filters && !printer->remote && (job->num_files > 1 || !strncmp(printer->device_uri, "file:", 5)))) { /* * Add gziptoany filter to the front of the list... */ if (!filters) filters = cupsArrayNew(NULL, NULL); if (!cupsArrayInsert(filters, &gziptoany_filter)) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unable to add decompression filter - %s", job->id, strerror(errno)); cupsArrayDelete(filters); job->current_file ++; if (job->current_file == job->num_files) cupsdCancelJob(job, 0, IPP_JOB_ABORTED); return; } } /* * Add port monitor, if any... */ if (printer->port_monitor) { /* * Add port monitor to the end of the list... */ if (!filters) filters = cupsArrayNew(NULL, NULL); port_monitor.src = NULL; port_monitor.dst = NULL; port_monitor.cost = 0; snprintf(port_monitor.filter, sizeof(port_monitor.filter), "%s/monitor/%s", ServerBin, printer->port_monitor); if (!cupsArrayAdd(filters, &port_monitor)) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unable to add port monitor - %s", job->id, strerror(errno)); cupsArrayDelete(filters); job->current_file ++; if (job->current_file == job->num_files) cupsdCancelJob(job, 0, IPP_JOB_ABORTED); return; } } /* * Make sure we don't go over the "MAX_FILTERS" limit... */ if (cupsArrayCount(filters) > MAX_FILTERS) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Too many filters (%d > %d), unable to print!", job->id, cupsArrayCount(filters), MAX_FILTERS); cupsArrayDelete(filters); cupsdCancelJob(job, 0, IPP_JOB_STOPPED); return; } /* * Update the printer and job state to "processing"... */ job->state->values[0].integer = IPP_JOB_PROCESSING; job->state_value = IPP_JOB_PROCESSING; job->status = 0; job->printer = printer; printer->job = job; cupsdSetPrinterState(printer, IPP_PRINTER_PROCESSING, 0); if (job->current_file == 0) { /* * Add to the printing list... */ cupsArrayAdd(PrintingJobs, job); cupsdSetBusyState(); /* * Set the processing time... */ set_time(job, "time-at-processing"); /* * Create the backchannel pipes and make them non-blocking... */ cupsdOpenPipe(job->back_pipes); fcntl(job->back_pipes[0], F_SETFL, fcntl(job->back_pipes[0], F_GETFL) | O_NONBLOCK); fcntl(job->back_pipes[1], F_SETFL, fcntl(job->back_pipes[1], F_GETFL) | O_NONBLOCK); /* * Create the side-channel pipes and make them non-blocking... */ socketpair(AF_LOCAL, SOCK_STREAM, 0, job->side_pipes); fcntl(job->side_pipes[0], F_SETFL, fcntl(job->side_pipes[0], F_GETFL) | O_NONBLOCK); fcntl(job->side_pipes[1], F_SETFL, fcntl(job->side_pipes[1], F_GETFL) | O_NONBLOCK); } /* * Determine if we are printing a banner page or not... */ if (job->job_sheets == NULL) { cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] No job-sheets attribute.", job->id); if ((job->job_sheets = ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_ZERO)) != NULL) cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] ... but someone added one without setting " "job_sheets!", job->id); } else if (job->job_sheets->num_values == 1) cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] job-sheets=%s", job->id, job->job_sheets->values[0].string.text); else cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] job-sheets=%s,%s", job->id, job->job_sheets->values[0].string.text, job->job_sheets->values[1].string.text); if (printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT)) banner_page = 0; else if (job->job_sheets == NULL) banner_page = 0; else if (strcasecmp(job->job_sheets->values[0].string.text, "none") != 0 && job->current_file == 0) banner_page = 1; else if (job->job_sheets->num_values > 1 && strcasecmp(job->job_sheets->values[1].string.text, "none") != 0 && job->current_file == (job->num_files - 1)) banner_page = 1; else banner_page = 0; cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] banner_page = %d", job->id, banner_page); /* * Building the options string is harder than it needs to be, but * for the moment we need to pass strings for command-line args and * not IPP attribute pointers... :) * * First allocate/reallocate the option buffer as needed... */ i = ipp_length(job->attrs); if (i > optlength) { if (optlength == 0) optptr = malloc(i); else optptr = realloc(options, i); if (optptr == NULL) { cupsdLogMessage(CUPSD_LOG_CRIT, "[Job %d] Unable to allocate %d bytes for option buffer!", job->id, i); cupsArrayDelete(filters); FilterLevel -= job->cost; cupsdCancelJob(job, 0, IPP_JOB_ABORTED); return; } options = optptr; optlength = i; } /* * Now loop through the attributes and convert them to the textual * representation used by the filters... */ optptr = options; *optptr = '\0'; snprintf(title, sizeof(title), "%s-%d", printer->name, job->id); strcpy(copies, "1"); for (attr = job->attrs->attrs; attr != NULL; attr = attr->next) { if (!strcmp(attr->name, "copies") && attr->value_tag == IPP_TAG_INTEGER) { /* * Don't use the # copies attribute if we are printing the job sheets... */ if (!banner_page) sprintf(copies, "%d", attr->values[0].integer); } else if (!strcmp(attr->name, "job-name") && (attr->value_tag == IPP_TAG_NAME || attr->value_tag == IPP_TAG_NAMELANG)) strlcpy(title, attr->values[0].string.text, sizeof(title)); else if (attr->group_tag == IPP_TAG_JOB) { /* * Filter out other unwanted attributes... */ if (attr->value_tag == IPP_TAG_MIMETYPE || attr->value_tag == IPP_TAG_NAMELANG || attr->value_tag == IPP_TAG_TEXTLANG || (attr->value_tag == IPP_TAG_URI && strcmp(attr->name, "job-uuid")) || attr->value_tag == IPP_TAG_URISCHEME || attr->value_tag == IPP_TAG_BEGIN_COLLECTION) /* Not yet supported */ continue; if (!strncmp(attr->name, "time-", 5)) continue; if (!strncmp(attr->name, "job-", 4) && strcmp(attr->name, "job-uuid") && !(printer->type & CUPS_PRINTER_REMOTE)) continue; if (!strncmp(attr->name, "job-", 4) && strcmp(attr->name, "job-uuid") && strcmp(attr->name, "job-billing") && strcmp(attr->name, "job-sheets") && strcmp(attr->name, "job-hold-until") && strcmp(attr->name, "job-priority")) continue; if ((!strcmp(attr->name, "page-label") || !strcmp(attr->name, "page-border") || !strncmp(attr->name, "number-up", 9) || !strcmp(attr->name, "page-ranges") || !strcmp(attr->name, "page-set") || !strcasecmp(attr->name, "AP_FIRSTPAGE_InputSlot") || !strcasecmp(attr->name, "AP_FIRSTPAGE_ManualFeed") || !strcasecmp(attr->name, "com.apple.print.PrintSettings." "PMTotalSidesImaged..n.") || !strcasecmp(attr->name, "com.apple.print.PrintSettings." "PMTotalBeginPages..n.")) && banner_page) continue; /* * Otherwise add them to the list... */ if (optptr > options) strlcat(optptr, " ", optlength - (optptr - options)); if (attr->value_tag != IPP_TAG_BOOLEAN) { strlcat(optptr, attr->name, optlength - (optptr - options)); strlcat(optptr, "=", optlength - (optptr - options)); } for (i = 0; i < attr->num_values; i ++) { if (i) strlcat(optptr, ",", optlength - (optptr - options)); optptr += strlen(optptr); switch (attr->value_tag) { case IPP_TAG_INTEGER : case IPP_TAG_ENUM : snprintf(optptr, optlength - (optptr - options), "%d", attr->values[i].integer); break; case IPP_TAG_BOOLEAN : if (!attr->values[i].boolean) strlcat(optptr, "no", optlength - (optptr - options)); case IPP_TAG_NOVALUE : strlcat(optptr, attr->name, optlength - (optptr - options)); break; case IPP_TAG_RANGE : if (attr->values[i].range.lower == attr->values[i].range.upper) snprintf(optptr, optlength - (optptr - options) - 1, "%d", attr->values[i].range.lower); else snprintf(optptr, optlength - (optptr - options) - 1, "%d-%d", attr->values[i].range.lower, attr->values[i].range.upper); break; case IPP_TAG_RESOLUTION : snprintf(optptr, optlength - (optptr - options) - 1, "%dx%d%s", attr->values[i].resolution.xres, attr->values[i].resolution.yres, attr->values[i].resolution.units == IPP_RES_PER_INCH ? "dpi" : "dpc"); break; case IPP_TAG_STRING : case IPP_TAG_TEXT : case IPP_TAG_NAME : case IPP_TAG_KEYWORD : case IPP_TAG_CHARSET : case IPP_TAG_LANGUAGE : case IPP_TAG_URI : for (valptr = attr->values[i].string.text; *valptr;) { if (strchr(" \t\n\\\'\"", *valptr)) *optptr++ = '\\'; *optptr++ = *valptr++; } *optptr = '\0'; break; default : break; /* anti-compiler-warning-code */ } } optptr += strlen(optptr); } } /* * Build the command-line arguments for the filters. Each filter * has 6 or 7 arguments: * * argv[0] = printer * argv[1] = job ID * argv[2] = username * argv[3] = title * argv[4] = # copies * argv[5] = options * argv[6] = filename (optional; normally stdin) * * This allows legacy printer drivers that use the old System V * printing interface to be used by CUPS. * * For remote jobs, we send all of the files in the argument list. */ if (printer->remote && job->num_files > 1) argv = calloc(7 + job->num_files, sizeof(char *)); else argv = calloc(8, sizeof(char *)); if (!argv) { cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to allocate argument array!"); cupsArrayDelete(filters); FilterLevel -= job->cost; cupsdStopPrinter(printer, 0); return; } sprintf(jobid, "%d", job->id); argv[0] = printer->name; argv[1] = jobid; argv[2] = job->username; argv[3] = title; argv[4] = copies; argv[5] = options; if (printer->remote && job->num_files > 1) { for (i = 0; i < job->num_files; i ++) { snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, job->id, i + 1); argv[6 + i] = strdup(filename); } } else { snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, job->id, job->current_file + 1); argv[6] = filename; } for (i = 0; argv[i]; i ++) cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] argv[%d]=\"%s\"", job->id, i, argv[i]); /* * Create environment variable strings for the filters... */ attr = ippFindAttribute(job->attrs, "attributes-natural-language", IPP_TAG_LANGUAGE); #ifdef __APPLE__ strcpy(apple_language, "APPLE_LANGUAGE="); _cupsAppleLanguage(attr->values[0].string.text, apple_language + 15, sizeof(apple_language) - 15); #endif /* __APPLE__ */ switch (strlen(attr->values[0].string.text)) { default : /* * This is an unknown or badly formatted language code; use * the POSIX locale... */ strcpy(lang, "LANG=C"); break; case 2 : /* * Just the language code (ll)... */ snprintf(lang, sizeof(lang), "LANG=%s.UTF8", attr->values[0].string.text); break; case 5 : /* * Language and country code (ll-cc)... */ snprintf(lang, sizeof(lang), "LANG=%c%c_%c%c.UTF8", attr->values[0].string.text[0], attr->values[0].string.text[1], toupper(attr->values[0].string.text[3] & 255), toupper(attr->values[0].string.text[4] & 255)); break; } attr = ippFindAttribute(job->attrs, "document-format", IPP_TAG_MIMETYPE); if (attr != NULL && (optptr = strstr(attr->values[0].string.text, "charset=")) != NULL) snprintf(charset, sizeof(charset), "CHARSET=%s", optptr + 8); else { attr = ippFindAttribute(job->attrs, "attributes-charset", IPP_TAG_CHARSET); snprintf(charset, sizeof(charset), "CHARSET=%s", attr->values[0].string.text); } snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s/%s", job->filetypes[job->current_file]->super, job->filetypes[job->current_file]->type); snprintf(device_uri, sizeof(device_uri), "DEVICE_URI=%s", printer->device_uri); cupsdSanitizeURI(printer->device_uri, sani_uri, sizeof(sani_uri)); snprintf(ppd, sizeof(ppd), "PPD=%s/ppd/%s.ppd", ServerRoot, printer->name); snprintf(printer_name, sizeof(printer_name), "PRINTER=%s", printer->name); snprintf(rip_max_cache, sizeof(rip_max_cache), "RIP_MAX_CACHE=%s", RIPCache); envc = cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0]))); envp[envc ++] = charset; envp[envc ++] = lang; #ifdef __APPLE__ envp[envc ++] = apple_language; #endif /* __APPLE__ */ envp[envc ++] = ppd; envp[envc ++] = rip_max_cache; envp[envc ++] = content_type; envp[envc ++] = device_uri; envp[envc ++] = printer_name; if (!printer->remote && !printer->raw) { filter = (mime_filter_t *)cupsArrayLast(filters); if (printer->port_monitor) filter = (mime_filter_t *)cupsArrayPrev(filters); if (filter && filter->dst) { snprintf(final_content_type, sizeof(final_content_type), "FINAL_CONTENT_TYPE=%s/%s", filter->dst->super, filter->dst->type); envp[envc ++] = final_content_type; } } if (Classification && !banner_page) { if ((attr = ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_NAME)) == NULL) snprintf(classification, sizeof(classification), "CLASSIFICATION=%s", Classification); else if (attr->num_values > 1 && strcmp(attr->values[1].string.text, "none") != 0) snprintf(classification, sizeof(classification), "CLASSIFICATION=%s", attr->values[1].string.text); else snprintf(classification, sizeof(classification), "CLASSIFICATION=%s", attr->values[0].string.text); envp[envc ++] = classification; } if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT)) { snprintf(class_name, sizeof(class_name), "CLASS=%s", job->dest); envp[envc ++] = class_name; } if (job->auth_username) envp[envc ++] = job->auth_username; if (job->auth_domain) envp[envc ++] = job->auth_domain; if (job->auth_password) envp[envc ++] = job->auth_password; #ifdef HAVE_GSSAPI if (job->ccname) envp[envc ++] = job->ccname; #endif /* HAVE_GSSAPI */ envp[envc] = NULL; for (i = 0; i < envc; i ++) if (!strncmp(envp[i], "AUTH_", 5)) cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] envp[%d]=\"AUTH_%c****\"", job->id, i, envp[i][5]); else if (strncmp(envp[i], "DEVICE_URI=", 11)) cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] envp[%d]=\"%s\"", job->id, i, envp[i]); else cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] envp[%d]=\"DEVICE_URI=%s\"", job->id, i, sani_uri); if (printer->remote) job->current_file = job->num_files; else job->current_file ++; /* * Now create processes for all of the filters... */ filterfds[0][0] = -1; filterfds[0][1] = -1; filterfds[1][0] = -1; filterfds[1][1] = -1; if (!job->status_buffer) { if (cupsdOpenPipe(job->status_pipes)) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unable to create job status pipes - %s.", job->id, strerror(errno)); snprintf(printer->state_message, sizeof(printer->state_message), "Unable to create status pipes - %s.", strerror(errno)); cupsdAddPrinterHistory(printer); cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, "Job canceled because the server could not create the job " "status pipes."); goto abort_job; } cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: status_pipes = [ %d %d ]", job->id, job->status_pipes[0], job->status_pipes[1]); job->status_buffer = cupsdStatBufNew(job->status_pipes[0], "[Job %d]", job->id); job->status_level = CUPSD_LOG_INFO; } job->status = 0; memset(job->filters, 0, sizeof(job->filters)); if (!job->profile) job->profile = cupsdCreateProfile(job->id); for (i = 0, slot = 0, filter = (mime_filter_t *)cupsArrayFirst(filters); filter; i ++, filter = (mime_filter_t *)cupsArrayNext(filters)) { if (filter->filter[0] != '/') snprintf(command, sizeof(command), "%s/filter/%s", ServerBin, filter->filter); else strlcpy(command, filter->filter, sizeof(command)); if (i < (cupsArrayCount(filters) - 1)) { if (cupsdOpenPipe(filterfds[slot])) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unable to create job filter pipes - %s.", job->id, strerror(errno)); snprintf(printer->state_message, sizeof(printer->state_message), "Unable to create filter pipes - %s.", strerror(errno)); cupsdAddPrinterHistory(printer); cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, "Job canceled because the server could not create the " "filter pipes."); goto abort_job; } } else { if (job->current_file == 1) { if (strncmp(printer->device_uri, "file:", 5) != 0) { if (cupsdOpenPipe(job->print_pipes)) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unable to create job backend pipes - %s.", job->id, strerror(errno)); snprintf(printer->state_message, sizeof(printer->state_message), "Unable to create backend pipes - %s.", strerror(errno)); cupsdAddPrinterHistory(printer); cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, "Job canceled because the server could not create " "the backend pipes."); goto abort_job; } } else { job->print_pipes[0] = -1; if (!strcmp(printer->device_uri, "file:/dev/null") || !strcmp(printer->device_uri, "file:///dev/null")) job->print_pipes[1] = -1; else { if (!strncmp(printer->device_uri, "file:/dev/", 10)) job->print_pipes[1] = open(printer->device_uri + 5, O_WRONLY | O_EXCL); else if (!strncmp(printer->device_uri, "file:///dev/", 12)) job->print_pipes[1] = open(printer->device_uri + 7, O_WRONLY | O_EXCL); else if (!strncmp(printer->device_uri, "file:///", 8)) job->print_pipes[1] = open(printer->device_uri + 7, O_WRONLY | O_CREAT | O_TRUNC, 0600); else job->print_pipes[1] = open(printer->device_uri + 5, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (job->print_pipes[1] < 0) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unable to open output file \"%s\" - %s.", job->id, printer->device_uri, strerror(errno)); snprintf(printer->state_message, sizeof(printer->state_message), "Unable to open output file \"%s\" - %s.", printer->device_uri, strerror(errno)); cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, "Job canceled because the server could not open the " "output file."); goto abort_job; } fcntl(job->print_pipes[1], F_SETFD, fcntl(job->print_pipes[1], F_GETFD) | FD_CLOEXEC); } } cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: print_pipes = [ %d %d ]", job->id, job->print_pipes[0], job->print_pipes[1]); } filterfds[slot][0] = job->print_pipes[0]; filterfds[slot][1] = job->print_pipes[1]; } cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: filter=\"%s\"", job->id, command); cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: filterfds[%d]=[ %d %d ]", job->id, slot, filterfds[slot][0], filterfds[slot][1]); pid = cupsdStartProcess(command, argv, envp, filterfds[!slot][0], filterfds[slot][1], job->status_pipes[1], job->back_pipes[0], job->side_pipes[0], 0, job->profile, job->filters + i); cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: Closing filter pipes for slot %d " "[ %d %d ]...", job->id, !slot, filterfds[!slot][0], filterfds[!slot][1]); cupsdClosePipe(filterfds[!slot]); if (pid == 0) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unable to start filter \"%s\" - %s.", job->id, filter->filter, strerror(errno)); snprintf(printer->state_message, sizeof(printer->state_message), "Unable to start filter \"%s\" - %s.", filter->filter, strerror(errno)); cupsdAddPrinterHistory(printer); cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, "Job canceled because the server could not execute a " "filter."); goto abort_job; } cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Started filter %s (PID %d)", job->id, command, pid); argv[6] = NULL; slot = !slot; } cupsArrayDelete(filters); /* * Finally, pipe the final output into a backend process if needed... */ if (strncmp(printer->device_uri, "file:", 5) != 0) { if (job->current_file == 1 || printer->remote) { sscanf(printer->device_uri, "%254[^:]", method); snprintf(command, sizeof(command), "%s/backend/%s", ServerBin, method); /* * See if the backend needs to run as root... */ if (RunUser) backroot = 0; else if (stat(command, &backinfo)) backroot = 0; else backroot = !(backinfo.st_mode & (S_IRWXG | S_IRWXO)); argv[0] = sani_uri; filterfds[slot][0] = -1; filterfds[slot][1] = -1; cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: backend=\"%s\"", job->id, command); cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: filterfds[%d] = [ %d %d ]", job->id, slot, filterfds[slot][0], filterfds[slot][1]); pid = cupsdStartProcess(command, argv, envp, filterfds[!slot][0], filterfds[slot][1], job->status_pipes[1], job->back_pipes[1], job->side_pipes[1], backroot, job->profile, &(job->backend)); if (pid == 0) { cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Unable to start backend \"%s\" - %s.", job->id, method, strerror(errno)); snprintf(printer->state_message, sizeof(printer->state_message), "Unable to start backend \"%s\" - %s.", method, strerror(errno)); cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, "Job canceled because the server could not execute " "the backend."); goto abort_job; } else { cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Started backend %s (PID %d)", job->id, command, pid); } } if (job->current_file == job->num_files) { cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: Closing print pipes [ %d %d ]...", job->id, job->print_pipes[0], job->print_pipes[1]); cupsdClosePipe(job->print_pipes); cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: Closing back pipes [ %d %d ]...", job->id, job->back_pipes[0], job->back_pipes[1]); cupsdClosePipe(job->back_pipes); cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: Closing side pipes [ %d %d ]...", job->id, job->side_pipes[0], job->side_pipes[1]); cupsdClosePipe(job->side_pipes); cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: Closing status output pipe %d...", job->id, job->status_pipes[1]); close(job->status_pipes[1]); job->status_pipes[1] = -1; } } else { filterfds[slot][0] = -1; filterfds[slot][1] = -1; if (job->current_file == job->num_files) { cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: Closing print pipes [ %d %d ]...", job->id, job->print_pipes[0], job->print_pipes[1]); cupsdClosePipe(job->print_pipes); cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: Closing status output pipe %d...", job->id, job->status_pipes[1]); close(job->status_pipes[1]); job->status_pipes[1] = -1; } } cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: Closing filter pipes for slot %d " "[ %d %d ]...", job->id, slot, filterfds[slot][0], filterfds[slot][1]); cupsdClosePipe(filterfds[slot]); if (printer->remote && job->num_files > 1) { for (i = 0; i < job->num_files; i ++) free(argv[i + 6]); } free(argv); cupsdAddSelect(job->status_buffer->fd, (cupsd_selfunc_t)update_job, NULL, job); cupsdAddEvent(CUPSD_EVENT_JOB_STATE, job->printer, job, "Job #%d started.", job->id); return; /* * If we get here, we need to abort the current job and close out all * files and pipes... */ abort_job: for (slot = 0; slot < 2; slot ++) { cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: Closing filter pipes for slot %d " "[ %d %d ]...", job->id, slot, filterfds[slot][0], filterfds[slot][1]); cupsdClosePipe(filterfds[slot]); } cupsdLogMessage(CUPSD_LOG_DEBUG2, "[Job %d] start_job: Closing status pipes [ %d %d ]...", job->id, job->status_pipes[0], job->status_pipes[1]); cupsdClosePipe(job->status_pipes); cupsdStatBufDelete(job->status_buffer); job->status_buffer = NULL; cupsArrayDelete(filters); if (printer->remote && job->num_files > 1) { for (i = 0; i < job->num_files; i ++) free(argv[i + 6]); } free(argv); cupsdStopJob(job, 0); } /* * 'unload_job()' - Unload a job from memory. */ static void unload_job(cupsd_job_t *job) /* I - Job */ { if (!job->attrs) return; cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Unloading...", job->id); ippDelete(job->attrs); job->attrs = NULL; job->state = NULL; job->sheets = NULL; job->job_sheets = NULL; job->printer_message = NULL; job->printer_reasons = NULL; } /* * 'update_job()' - Read a status update from a job's filters. */ void update_job(cupsd_job_t *job) /* I - Job to check */ { int i; /* Looping var */ int copies; /* Number of copies printed */ char message[1024], /* Message text */ *ptr; /* Pointer update... */ int loglevel, /* Log level for message */ event = 0; /* Events? */ while ((ptr = cupsdStatBufUpdate(job->status_buffer, &loglevel, message, sizeof(message))) != NULL) { /* * Process page and printer state messages as needed... */ if (loglevel == CUPSD_LOG_PAGE) { /* * Page message; send the message to the page_log file and update the * job sheet count... */ if (job->sheets) { if (!strncasecmp(message, "total ", 6)) { /* * Got a total count of pages from a backend or filter... */ copies = atoi(message + 6); copies -= job->sheets->values[0].integer; /* Just track the delta */ } else if (!sscanf(message, "%*d%d", &copies)) copies = 1; job->sheets->values[0].integer += copies; if (job->printer->page_limit) { cupsd_quota_t *q = cupsdUpdateQuota(job->printer, job->username, copies, 0); #ifdef __APPLE__ if (AppleQuotas && q->page_count == -3) { /* * Quota limit exceeded, cancel job in progress immediately... */ cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Canceled because pages exceed user %s " "quota limit on printer %s (%s).", job->id, job->username, job->printer->name, job->printer->info); cupsdCancelJob(job, 1, IPP_JOB_CANCELED); return; } #else (void)q; #endif /* __APPLE__ */ } } cupsdLogPage(job, message); if (job->sheets) cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job, "Printed %d page(s).", job->sheets->values[0].integer); } else if (loglevel == CUPSD_LOG_STATE) { if (!strcmp(message, "paused")) { cupsdStopPrinter(job->printer, 1); return; } else { cupsdSetPrinterReasons(job->printer, message); cupsdAddPrinterHistory(job->printer); event |= CUPSD_EVENT_PRINTER_STATE; } update_job_attrs(job); } else if (loglevel == CUPSD_LOG_ATTR) { /* * Set attribute(s)... */ int num_attrs; /* Number of attributes */ cups_option_t *attrs; /* Attributes */ const char *attr; /* Attribute */ num_attrs = cupsParseOptions(message, 0, &attrs); if ((attr = cupsGetOption("auth-info-required", num_attrs, attrs)) != NULL) { cupsdSetAuthInfoRequired(job->printer, attr, NULL); cupsdSetPrinterAttrs(job->printer); if (job->printer->type & CUPS_PRINTER_DISCOVERED) cupsdMarkDirty(CUPSD_DIRTY_REMOTE); else cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); } if ((attr = cupsGetOption("printer-alert", num_attrs, attrs)) != NULL) { cupsdSetString(&job->printer->alert, attr); event |= CUPSD_EVENT_PRINTER_STATE; } if ((attr = cupsGetOption("printer-alert-description", num_attrs, attrs)) != NULL) { cupsdSetString(&job->printer->alert_description, attr); event |= CUPSD_EVENT_PRINTER_STATE; } if ((attr = cupsGetOption("marker-colors", num_attrs, attrs)) != NULL) { cupsdSetPrinterAttr(job->printer, "marker-colors", (char *)attr); job->printer->marker_time = time(NULL); event |= CUPSD_EVENT_PRINTER_STATE; } if ((attr = cupsGetOption("marker-levels", num_attrs, attrs)) != NULL) { cupsdSetPrinterAttr(job->printer, "marker-levels", (char *)attr); job->printer->marker_time = time(NULL); event |= CUPSD_EVENT_PRINTER_STATE; } if ((attr = cupsGetOption("marker-names", num_attrs, attrs)) != NULL) { cupsdSetPrinterAttr(job->printer, "marker-names", (char *)attr); job->printer->marker_time = time(NULL); event |= CUPSD_EVENT_PRINTER_STATE; } if ((attr = cupsGetOption("marker-types", num_attrs, attrs)) != NULL) { cupsdSetPrinterAttr(job->printer, "marker-types", (char *)attr); job->printer->marker_time = time(NULL); event |= CUPSD_EVENT_PRINTER_STATE; } cupsFreeOptions(num_attrs, attrs); } else if (loglevel == CUPSD_LOG_PPD) { /* * Set attribute(s)... */ int num_keywords; /* Number of keywords */ cups_option_t *keywords; /* Keywords */ num_keywords = cupsParseOptions(message, 0, &keywords); if (cupsdUpdatePrinterPPD(job->printer, num_keywords, keywords)) cupsdSetPrinterAttrs(job->printer); cupsFreeOptions(num_keywords, keywords); } #ifdef __APPLE__ else if (!strncmp(message, "recoverable:", 12)) { cupsdSetPrinterReasons(job->printer, "+com.apple.print.recoverable-warning"); ptr = message + 12; while (isspace(*ptr & 255)) ptr ++; cupsdSetString(&job->printer->recoverable, ptr); cupsdAddPrinterHistory(job->printer); event |= CUPSD_EVENT_PRINTER_STATE; } else if (!strncmp(message, "recovered:", 10)) { cupsdSetPrinterReasons(job->printer, "-com.apple.print.recoverable-warning"); ptr = message + 10; while (isspace(*ptr & 255)) ptr ++; cupsdSetString(&job->printer->recoverable, ptr); cupsdAddPrinterHistory(job->printer); event |= CUPSD_EVENT_PRINTER_STATE; } #endif /* __APPLE__ */ else if (loglevel <= job->status_level) { /* * Some message to show in the printer-state-message attribute... */ if (loglevel != CUPSD_LOG_NOTICE) job->status_level = loglevel; strlcpy(job->printer->state_message, message, sizeof(job->printer->state_message)); cupsdAddPrinterHistory(job->printer); if (loglevel <= CUPSD_LOG_INFO) event |= CUPSD_EVENT_PRINTER_STATE; update_job_attrs(job); } if (!strchr(job->status_buffer->buffer, '\n')) break; } if (event & CUPSD_EVENT_PRINTER_STATE) cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, job->printer, NULL, (job->printer->type & CUPS_PRINTER_CLASS) ? "Class \"%s\" state changed." : "Printer \"%s\" state changed.", job->printer->name); if (ptr == NULL && !job->status_buffer->bufused) { /* * See if all of the filters and the backend have returned their * exit statuses. */ for (i = 0; job->filters[i] < 0; i ++); if (job->filters[i]) return; if (job->current_file >= job->num_files && job->backend > 0) return; /* * Handle the end of job stuff... */ cupsdFinishJob(job); } } /* * 'update_job_attrs()' - Update the job-printer-* attributes. */ void update_job_attrs(cupsd_job_t *job) /* I - Job to update */ { int i; /* Looping var */ int num_reasons; /* Actual number of reasons */ const char * const *reasons; /* Reasons */ static const char *none = "none", /* "none" */ *paused = "paused"; /* "paused" */ /* * Get/create the job-printer-state-* attributes... */ if (!job->printer_message) { if ((job->printer_message = ippFindAttribute(job->attrs, "job-printer-state-message", IPP_TAG_TEXT)) == NULL) job->printer_message = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_TEXT, "job-printer-state-message", NULL, ""); } if (!job->printer_reasons) job->printer_reasons = ippFindAttribute(job->attrs, "job-printer-state-reasons", IPP_TAG_KEYWORD); /* * If the job isn't printing, return now... */ if (!job->printer) return; /* * Otherwise copy the printer-state-message value... */ if (job->printer->state_message[0]) cupsdSetString(&(job->printer_message->values[0].string.text), job->printer->state_message); /* * ... and the printer-state-reasons value... */ if (job->printer->num_reasons == 0) { num_reasons = 1; reasons = job->printer->state == IPP_PRINTER_STOPPED ? &paused : &none; } else { num_reasons = job->printer->num_reasons; reasons = (const char * const *)job->printer->reasons; } if (!job->printer_reasons || job->printer_reasons->num_values != num_reasons) { ippDeleteAttribute(job->attrs, job->printer_reasons); job->printer_reasons = ippAddStrings(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD, "job-printer-state-reasons", num_reasons, NULL, NULL); } for (i = 0; i < num_reasons; i ++) cupsdSetString(&(job->printer_reasons->values[i].string.text), reasons[i]); } /* * End of "$Id: job.c 7005 2007-10-01 23:45:48Z mike $". */