X-Git-Url: http://git.ipfire.org/?p=thirdparty%2Fcups.git;a=blobdiff_plain;f=scheduler%2Fjob.c;h=59e06dd55843bcf5feefcde1df6c37385fa9576d;hp=1fa6352aa0bc937cdabaebcd520aeda6e3f89f73;hb=85dda01c84763aecc4d31e15246d0ac5986eedae;hpb=c5571a1d68de6e20e127c9745aa7a4dbf4e4474d diff --git a/scheduler/job.c b/scheduler/job.c index 1fa6352aa..59e06dd55 100644 --- a/scheduler/job.c +++ b/scheduler/job.c @@ -1,9 +1,9 @@ /* - * "$Id: job.c 7682 2008-06-21 00:06:02Z mike $" + * "$Id: job.c 7902 2008-09-03 14:20:17Z mike $" * - * Job management routines for the Common UNIX Printing System (CUPS). + * Job management routines for the CUPS scheduler. * - * Copyright 2007-2008 by Apple Inc. + * Copyright 2007-2011 by Apple Inc. * Copyright 1997-2007 by Easy Software Products, all rights reserved. * * These coded instructions, statements, and computer programs are the @@ -14,49 +14,53 @@ * * Contents: * - * cupsdAddJob() - Add a new job to the job queue... - * cupsdCancelJob() - Cancel the specified print job. + * 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. + * 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. - * 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. + * 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... + * 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. + * 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. - * 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. + * 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_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... + * 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 jobs filters. + * update_job() - Read a status update from a job's filters. * update_job_attrs() - Update the job-printer-* attributes. */ @@ -68,6 +72,84 @@ #include #include #include +#ifdef __APPLE__ +# include +# ifdef HAVE_IOKIT_PWR_MGT_IOPMLIBPRIVATE_H +# include +# endif /* HAVE_IOKIT_PWR_MGT_IOPMLIBPRIVATE_H */ +#endif /* __APPLE__ */ + + +/* + * Design Notes for Job Management + * ------------------------------- + * + * STATE CHANGES + * + * pending Do nothing/check jobs + * pending-held Send SIGTERM to filters and backend + * processing Do nothing/start job + * stopped Send SIGKILL to filters and backend + * canceled Send SIGTERM to filters and backend + * aborted Finalize + * completed Finalize + * + * Finalize clears the printer <-> job association, deletes the status + * buffer, closes all of the pipes, etc. and doesn't get run until all of + * the print processes are finished. + * + * UNLOADING OF JOBS (cupsdUnloadCompletedJobs) + * + * We unload the job attributes when they are not needed to reduce overall + * memory consumption. We don't unload jobs where job->state_value < + * IPP_JOB_STOPPED, job->printer != NULL, or job->access_time is recent. + * + * STARTING OF JOBS (start_job) + * + * When a job is started, a status buffer, several pipes, a security + * profile, and a backend process are created for the life of that job. + * These are shared for every file in a job. For remote print jobs, the + * IPP backend is provided with every file in the job and no filters are + * run. + * + * The job->printer member tracks which printer is printing a job, which + * can be different than the destination in job->dest for classes. The + * printer object also has a job pointer to track which job is being + * printed. + * + * PRINTING OF JOB FILES (cupsdContinueJob) + * + * Each file in a job is filtered by 0 or more programs. After getting the + * list of filters needed and the total cost, the job is either passed or + * put back to the processing state until the current FilterLevel comes down + * enough to allow printing. + * + * If we can print, we build a string for the print options and run each of + * the filters, piping the output from one into the next. + * + * JOB STATUS UPDATES (update_job) + * + * The update_job function gets called whenever there are pending messages + * on the status pipe. These generally are updates to the marker-*, + * printer-state-message, or printer-state-reasons attributes. On EOF, + * finalize_job is called to clean up. + * + * FINALIZING JOBS (finalize_job) + * + * When all filters and the backend are done, we set the job state to + * completed (no errors), aborted (filter errors or abort-job policy), + * pending-held (auth required or retry-job policy), or pending + * (retry-current-job or stop-printer policies) as appropriate. + * + * Then we close the pipes and free the status buffers and profiles. + * + * JOB FILE COMPLETION (process_children in main.c) + * + * For multiple-file jobs, process_children (in main.c) sees that all + * filters have exited and calls in to print the next file if there are + * more files in the job, otherwise it waits for the backend to exit and + * update_job to do the cleanup. + */ /* @@ -89,20 +171,26 @@ static mime_filter_t gziptoany_filter = static int compare_active_jobs(void *first, void *second, void *data); static int compare_jobs(void *first, void *second, void *data); -static int ipp_length(ipp_t *ipp); +static void dump_job_history(cupsd_job_t *job); +static void finalize_job(cupsd_job_t *job, int set_job_state); +static void free_job_history(cupsd_job_t *job); +static char *get_options(cupsd_job_t *job, int banner_page, char *copies, + size_t copies_size, char *title, + size_t title_size); +static size_t ipp_length(ipp_t *ipp); static void load_job_cache(const char *filename); static void load_next_job_id(const char *filename); static void load_request_root(void); static void set_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 stop_job(cupsd_job_t *job, cupsd_jobaction_t action); 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, int do_message); /* - * 'cupsdAddJob()' - Add a new job to the job queue... + * 'cupsdAddJob()' - Add a new job to the job queue. */ cupsd_job_t * /* O - New job record */ @@ -140,175 +228,7 @@ cupsdAddJob(int priority, /* I - Job priority */ /* - * '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... + * 'cupsdCancelJobs()' - Cancel all jobs for the given destination/user. */ void @@ -323,20 +243,22 @@ cupsdCancelJobs(const char *dest, /* I - Destination to cancel */ job; job = (cupsd_job_t *)cupsArrayNext(Jobs)) { - if (!job->dest || !job->username) - cupsdLoadJob(job); - - if (!job->dest || !job->username) + if ((!job->dest || !job->username) && !cupsdLoadJob(job)) continue; - if ((dest == NULL || !strcmp(job->dest, dest)) && - (username == NULL || !strcmp(job->username, username))) + if ((!dest || !strcmp(job->dest, dest)) && + (!username || !strcmp(job->username, username))) { /* * Cancel all jobs matching this destination/user... */ - cupsdCancelJob(job, purge, IPP_JOB_CANCELED); + if (purge) + cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_PURGE, + "Job purged by user."); + else if (job->state_value < IPP_JOB_CANCELED) + cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_DEFAULT, + "Job canceled by user."); } } @@ -356,59 +278,86 @@ cupsdCheckJobs(void) cupsd_printer_t *printer, /* Printer destination */ *pclass; /* Printer class destination */ ipp_attribute_t *attr; /* Job attribute */ + time_t curtime; /* Current time */ - DEBUG_puts("cupsdCheckJobs()"); - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCheckJobs: %d active jobs, sleeping=%d, reload=%d", cupsArrayCount(ActiveJobs), Sleeping, NeedReload); + curtime = time(NULL); + for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs); job; job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) { /* - * Start held jobs if they are ready... + * Kill jobs if they are unresponsive... */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, - "cupsdCheckJobs: Job %d: dest=%s, dtype=%x, " - "state_value=%d, loaded=%s", job->id, job->dest, job->dtype, - job->state_value, job->attrs ? "yes" : "no"); + if (job->kill_time && job->kill_time <= curtime) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Stopping unresponsive job!", + job->id); + + stop_job(job, CUPSD_JOB_FORCE); + continue; + } + + /* + * Start held jobs if they are ready... + */ if (job->state_value == IPP_JOB_HELD && job->hold_until && - job->hold_until < time(NULL)) + job->hold_until < curtime) { if (job->pending_timeout) { - /* Add trailing banner as needed */ + /* + * This job is pending; check that we don't have an active Send-Document + * operation in progress on any of the client connections, then timeout + * the job so we can start printing... + */ + + cupsd_client_t *con; /* Current client connection */ + + + for (con = (cupsd_client_t *)cupsArrayFirst(Clients); + con; + con = (cupsd_client_t *)cupsArrayNext(Clients)) + if (con->request && + con->request->request.op.operation_id == IPP_SEND_DOCUMENT) + break; + + if (con) + continue; + if (cupsdTimeoutJob(job)) continue; } - job->state->values[0].integer = IPP_JOB_PENDING; - job->state_value = IPP_JOB_PENDING; + cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT, + "Job submission timed out."); + } - if ((attr = ippFindAttribute(job->attrs, "job-hold-until", - IPP_TAG_KEYWORD)) == NULL) - attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); + /* + * Continue jobs that are waiting on the FilterLimit... + */ - if (attr) - { - attr->value_tag = IPP_TAG_KEYWORD; - cupsdSetString(&(attr->values[0].string.text), "no-hold"); - job->dirty = 1; - cupsdMarkDirty(CUPSD_DIRTY_JOBS); - } - } + if (job->pending_cost > 0 && + ((FilterLevel + job->pending_cost) < FilterLimit || FilterLevel == 0)) + cupsdContinueJob(job); /* * Start pending jobs if the destination is available... */ - if (job->state_value == IPP_JOB_PENDING && !NeedReload && !Sleeping) + if (job->state_value == IPP_JOB_PENDING && !NeedReload && +#ifndef kIOPMAssertionTypeDenySystemSleep + !Sleeping && +#endif /* !kIOPMAssertionTypeDenySystemSleep */ + !DoingShutdown && !job->printer) { printer = cupsdFindDest(job->dest); pclass = NULL; @@ -437,17 +386,11 @@ cupsdCheckJobs(void) * cancel the job... */ - cupsdLogJob(job, CUPSD_LOG_WARN, - "Printer/class %s has gone away; canceling job!", - 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); + cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_PURGE, + "Job aborted because the destination printer/class " + "has gone away."); } - else if (printer) + else if (printer && !printer->holding_new_jobs) { /* * See if the printer is available or remote and not printing a job; @@ -473,18 +416,10 @@ cupsdCheckJobs(void) } if ((!(printer->type & CUPS_PRINTER_DISCOVERED) && /* Printer is local */ - printer->state == IPP_PRINTER_IDLE) || /* and idle */ + printer->state == IPP_PRINTER_IDLE) || /* and idle, OR */ ((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... */ @@ -507,652 +442,1230 @@ cupsdCleanJobs(void) cupsd_job_t *job; /* Current job */ - if (MaxJobs <= 0) + if (MaxJobs <= 0 && JobHistory) return; for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); - job && cupsArrayCount(Jobs) >= MaxJobs; + job && (cupsArrayCount(Jobs) >= MaxJobs || !JobHistory); job = (cupsd_job_t *)cupsArrayNext(Jobs)) - if (job->state_value >= IPP_JOB_CANCELED) - cupsdCancelJob(job, 1, IPP_JOB_CANCELED); + if (job->state_value >= IPP_JOB_CANCELED && !job->printer) + cupsdDeleteJob(job, CUPSD_JOB_PURGE); } /* - * 'cupsdDeleteJob()' - Free all memory used by a job. + * 'cupsdContinueJob()' - Continue printing with the next file in a job. */ void -cupsdDeleteJob(cupsd_job_t *job) /* I - Job */ +cupsdContinueJob(cupsd_job_t *job) /* I - Job */ { - cupsdClearString(&job->username); - cupsdClearString(&job->dest); - cupsdClearString(&job->auth_username); - cupsdClearString(&job->auth_domain); - cupsdClearString(&job->auth_password); + int i; /* Looping var */ + int slot; /* Pipe slot */ + cups_array_t *filters = NULL,/* Filters for job */ + *prefilters; /* Filters with prefilters */ + mime_filter_t *filter, /* Current filter */ + *prefilter, /* Prefilter */ + port_monitor; /* Port monitor filter */ + char scheme[255]; /* Device URI scheme */ + ipp_attribute_t *attr; /* Current attribute */ + const char *ptr, /* Pointer into value */ + *abort_message; /* Abort message */ + ipp_jstate_t abort_state = IPP_JOB_STOPPED; + /* New job state on abort */ + struct stat backinfo; /* Backend file information */ + int backroot; /* Run backend as root? */ + int pid; /* Process ID of new filter process */ + int banner_page; /* 1 if banner page, 0 otherwise */ + int filterfds[2][2] = { { -1, -1 }, { -1, -1 } }; + /* Pipes used between filters */ + int envc; /* Number of environment variables */ + struct stat fileinfo; /* Job file information */ + char **argv = NULL, /* Filter command-line arguments */ + filename[1024], /* Job filename */ + command[1024], /* Full path to command */ + jobid[255], /* Job ID string */ + title[IPP_MAX_NAME], + /* Job title string */ + copies[255], /* # copies string */ + *options, /* Options string */ + *envp[MAX_ENV + 21], + /* 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__ */ + auth_info_required[255], + /* AUTH_INFO_REQUIRED env variable */ + ppd[1024], /* PPD env variable */ + printer_info[255], + /* PRINTER_INFO env variable */ + printer_location[255], + /* PRINTER_LOCATION env variable */ + printer_name[255], + /* PRINTER env variable */ + *printer_state_reasons = NULL, + /* PRINTER_STATE_REASONS env var */ + rip_max_cache[255]; + /* RIP_MAX_CACHE env variable */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "cupsdContinueJob(job=%p(%d)): current_file=%d, num_files=%d", + job, job->id, job->current_file, job->num_files); -#ifdef HAVE_GSSAPI /* - * Destroy the credential cache and clear the KRB5CCNAME env var string. + * Figure out what filters are required to convert from + * the source to the destination type... */ - if (job->ccache) - { - krb5_cc_destroy(KerberosContext, job->ccache); - job->ccache = NULL; - } + FilterLevel -= job->cost; - cupsdClearString(&job->ccname); -#endif /* HAVE_GSSAPI */ + job->cost = 0; + job->pending_cost = 0; - if (job->num_files > 0) - { - free(job->compressions); - free(job->filetypes); - } + memset(job->filters, 0, sizeof(job->filters)); - ippDelete(job->attrs); - free(job); -} + if (job->printer->raw) + { + /* + * Remote jobs and raw queues go directly to the printer without + * filtering... + */ + cupsdLogJob(job, CUPSD_LOG_DEBUG, "Sending job to queue tagged as raw..."); + } + else + { + /* + * Local jobs get filtered... + */ -/* - * 'cupsdFinishJob()' - Finish a job. - */ + snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, + job->id, job->current_file + 1); + if (stat(filename, &fileinfo)) + fileinfo.st_size = 0; -void -cupsdFinishJob(cupsd_job_t *job) /* I - Job */ -{ - cupsd_printer_t *printer; /* Current printer */ - ipp_attribute_t *attr; /* job-hold-until attribute */ + filters = mimeFilter2(MimeDatabase, job->filetypes[job->current_file], + fileinfo.st_size, job->printer->filetype, + &(job->cost)); + if (!filters) + { + cupsdLogJob(job, CUPSD_LOG_ERROR, + "Unable to convert file %d to printable format!", + job->current_file); - cupsdLogJob(job, CUPSD_LOG_DEBUG, "File %d is complete.", - job->current_file - 1); + abort_message = "Aborting job because it cannot be printed."; + abort_state = IPP_JOB_ABORTED; - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "cupsdFinishJob: job->status is %d", - job->status); + goto abort_job; + } - if (job->status_buffer && - (job->status < 0 || job->current_file >= job->num_files)) - { /* - * Close the pipe and clear the input bit. + * Remove NULL ("-") filters... */ - cupsdRemoveSelect(job->status_buffer->fd); - - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "cupsdFinishJob: Closing status pipes [ %d %d ]...", - job->status_pipes[0], job->status_pipes[1]); - - cupsdClosePipe(job->status_pipes); - cupsdStatBufDelete(job->status_buffer); - - job->status_buffer = NULL; - } - - printer = job->printer; + for (filter = (mime_filter_t *)cupsArrayFirst(filters); + filter; + filter = (mime_filter_t *)cupsArrayNext(filters)) + if (!strcmp(filter->filter, "-")) + cupsArrayRemove(filters, filter); - update_job_attrs(job, 0); + if (cupsArrayCount(filters) == 0) + { + cupsArrayDelete(filters); + filters = NULL; + } - if (job->status < 0) - { /* - * Backend had errors; stop it... + * If this printer has any pre-filters, insert the required pre-filter + * in the filters array... */ - int exit_code; /* Exit code from backend */ + if (job->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, + job->printer->prefiltertype))) + { + cupsArrayAdd(prefilters, prefilter); + job->cost += prefilter->cost; + } - /* - * 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... - */ + cupsArrayAdd(prefilters, filter); + } - exit_code = -job->status; - if (WIFEXITED(exit_code)) - exit_code = WEXITSTATUS(exit_code); - else - exit_code = job->status; + cupsArrayDelete(filters); + filters = prefilters; + } + } - cupsdLogJob(job, CUPSD_LOG_INFO, "Backend returned status %d (%s)", - 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"); + /* + * 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) + { /* - * Do what needs to be done... + * Don't print this job quite yet... */ - switch (exit_code) - { - default : - case CUPS_BACKEND_FAILED : - /* - * Backend failure, use the error-policy to determine how to - * act... - */ - - cupsdStopJob(job, 0); + cupsArrayDelete(filters); - if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT)) - { - /* - * Mark the job as pending again - we'll retry on another - * printer... - */ + cupsdLogJob(job, CUPSD_LOG_INFO, + "Holding because filter limit has been reached."); + cupsdLogJob(job, CUPSD_LOG_DEBUG2, + "cupsdContinueJob: file=%d, cost=%d, level=%d, limit=%d", + job->current_file, job->cost, FilterLevel, + FilterLimit); - job->state->values[0].integer = IPP_JOB_PENDING; - job->state_value = IPP_JOB_PENDING; - } + job->pending_cost = job->cost; + job->cost = 0; + return; + } - job->dirty = 1; - cupsdMarkDirty(CUPSD_DIRTY_JOBS); + FilterLevel += job->cost; - /* - * If the job was queued to a class or the error policy is - * "retry-current-job", try requeuing it... For faxes and retry-job - * queues, hold the current job for 5 minutes. - */ + /* + * Add decompression/raw filter as needed... + */ - if ((job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT)) || - !strcmp(printer->error_policy, "retry-current-job")) - 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. - */ + if ((!job->printer->raw && job->compressions[job->current_file]) || + (!filters && !job->printer->remote && + (job->num_files > 1 || !strncmp(job->printer->device_uri, "file:", 5)))) + { + /* + * Add gziptoany filter to the front of the list... + */ - job->tries ++; + if (!filters) + filters = cupsArrayNew(NULL, NULL); - if (job->tries >= JobRetryLimit) - { - /* - * Too many tries... - */ + if (!cupsArrayInsert(filters, &gziptoany_filter)) + { + cupsdLogJob(job, CUPSD_LOG_DEBUG, + "Unable to add decompression filter - %s", strerror(errno)); - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Canceling job since it could not be " - "sent after %d tries.", - JobRetryLimit); + cupsArrayDelete(filters); - cupsdCancelJob(job, 0, IPP_JOB_ABORTED); - } - else - { - /* - * Try again in N seconds... - */ + abort_message = "Stopping job because the scheduler ran out of memory."; - set_hold_until(job, time(NULL) + JobRetryInterval); + goto abort_job; + } + } - 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); + /* + * Add port monitor, if any... + */ - cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job, - "Job stopped due to backend errors; please consult " - "the error_log file for details."); - } - break; + if (job->printer->port_monitor) + { + /* + * Add port monitor to the end of the list... + */ - case CUPS_BACKEND_CANCEL : - /* - * Cancel the job... - */ + if (!filters) + filters = cupsArrayNew(NULL, NULL); - cupsdCancelJob(job, 0, IPP_JOB_CANCELED); - break; + port_monitor.src = NULL; + port_monitor.dst = NULL; + port_monitor.cost = 0; - case CUPS_BACKEND_HOLD : - /* - * Hold the job... - */ + snprintf(port_monitor.filter, sizeof(port_monitor.filter), + "%s/monitor/%s", ServerBin, job->printer->port_monitor); - cupsdStopJob(job, 0); + if (!cupsArrayAdd(filters, &port_monitor)) + { + cupsdLogJob(job, CUPSD_LOG_DEBUG, + "Unable to add port monitor - %s", strerror(errno)); - cupsdSetJobHoldUntil(job, "indefinite"); + abort_message = "Stopping job because the scheduler ran out of memory."; - job->state->values[0].integer = IPP_JOB_HELD; - job->state_value = IPP_JOB_HELD; + goto abort_job; + } + } - job->dirty = 1; - cupsdMarkDirty(CUPSD_DIRTY_JOBS); + /* + * Make sure we don't go over the "MAX_FILTERS" limit... + */ - cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job, - "Job held due to backend errors; please consult " - "the error_log file for details."); - break; + if (cupsArrayCount(filters) > MAX_FILTERS) + { + cupsdLogJob(job, CUPSD_LOG_DEBUG, + "Too many filters (%d > %d), unable to print!", + cupsArrayCount(filters), MAX_FILTERS); - case CUPS_BACKEND_STOP : - /* - * Stop the printer... - */ + abort_message = "Aborting job because it needs too many filters to print."; + abort_state = IPP_JOB_ABORTED; - cupsdStopJob(job, 0); + goto abort_job; + } - job->state->values[0].integer = IPP_JOB_PENDING; - job->state_value = IPP_JOB_PENDING; + /* + * Determine if we are printing a banner page or not... + */ - job->dirty = 1; - cupsdMarkDirty(CUPSD_DIRTY_JOBS); + if (job->job_sheets == NULL) + { + cupsdLogJob(job, CUPSD_LOG_DEBUG, "No job-sheets attribute."); + 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!"); + } + else if (job->job_sheets->num_values == 1) + cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-sheets=%s", + job->job_sheets->values[0].string.text); + else + cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-sheets=%s,%s", + job->job_sheets->values[0].string.text, + job->job_sheets->values[1].string.text); - cupsdSetPrinterState(printer, IPP_PRINTER_STOPPED, 1); + if (job->printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT)) + banner_page = 0; + else if (job->job_sheets == NULL) + banner_page = 0; + else if (_cups_strcasecmp(job->job_sheets->values[0].string.text, "none") != 0 && + job->current_file == 0) + banner_page = 1; + else if (job->job_sheets->num_values > 1 && + _cups_strcasecmp(job->job_sheets->values[1].string.text, "none") != 0 && + job->current_file == (job->num_files - 1)) + banner_page = 1; + else + banner_page = 0; - cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job, - "Job stopped due to backend errors; please consult " - "the error_log file for details."); - break; + if ((options = get_options(job, banner_page, copies, sizeof(copies), title, + sizeof(title))) == NULL) + { + abort_message = "Stopping job because the scheduler ran out of memory."; - case CUPS_BACKEND_AUTH_REQUIRED : - cupsdStopJob(job, 0); + goto abort_job; + } - cupsdSetJobHoldUntil(job, "auth-info-required"); + /* + * 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 ((attr = ippFindAttribute(job->attrs, "job-hold-until", - IPP_TAG_KEYWORD)) == NULL) - attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); + if (job->printer->remote) + argv = calloc(7 + job->num_files, sizeof(char *)); + else + argv = calloc(8, sizeof(char *)); - if (attr) - { - attr->value_tag = IPP_TAG_KEYWORD; - cupsdSetString(&(attr->values[0].string.text), - "auth-info-required"); - } + if (!argv) + { + cupsdLogMessage(CUPSD_LOG_DEBUG, "Unable to allocate argument array - %s", + strerror(errno)); - job->state->values[0].integer = IPP_JOB_HELD; - job->state_value = IPP_JOB_HELD; + abort_message = "Stopping job because the scheduler ran out of memory."; - job->dirty = 1; - cupsdMarkDirty(CUPSD_DIRTY_JOBS); + goto abort_job; + } - cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, printer, job, - "Authentication is required for job %d.", job->id); - break; - } + sprintf(jobid, "%d", job->id); - /* - * Try printing another job... - */ + argv[0] = job->printer->name; + argv[1] = jobid; + argv[2] = job->username; + argv[3] = title; + argv[4] = copies; + argv[5] = options; - cupsdCheckJobs(); - } - else if (job->status > 0) + if (job->printer->remote && job->num_files > 1) { - /* - * Filter had errors; stop job... - */ - - cupsdLogJob(job, CUPSD_LOG_ERROR, "Job stopped due to filter errors."); - 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(); + 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 { - /* - * Job printed successfully; cancel it... - */ + snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, + job->id, job->current_file + 1); + argv[6] = filename; + } - if (job->current_file < job->num_files) - { - /* - * Start the next file in the job... - */ + for (i = 0; argv[i]; i ++) + cupsdLogJob(job, CUPSD_LOG_DEBUG, "argv[%d]=\"%s\"", i, argv[i]); - FilterLevel -= job->cost; - start_job(job, printer); - } - else - { - /* - * Close out this job... - */ + /* + * Create environment variable strings for the filters... + */ - cupsdLogJob(job, CUPSD_LOG_INFO, "Completed successfully."); - cupsdCancelJob(job, 0, IPP_JOB_COMPLETED); - cupsdCheckJobs(); - } - } -} + 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__ */ -/* - * 'cupsdFreeAllJobs()' - Free all jobs from memory. - */ + switch (strlen(attr->values[0].string.text)) + { + default : + /* + * This is an unknown or badly formatted language code; use + * the POSIX locale... + */ -void -cupsdFreeAllJobs(void) -{ - cupsd_job_t *job; /* Current job */ + strcpy(lang, "LANG=C"); + break; + case 2 : + /* + * Just the language code (ll)... + */ - if (!Jobs) - return; + snprintf(lang, sizeof(lang), "LANG=%s.UTF-8", + attr->values[0].string.text); + break; - cupsdHoldSignals(); + case 5 : + /* + * Language and country code (ll-cc)... + */ - cupsdStopAllJobs(1); - cupsdSaveAllJobs(); + snprintf(lang, sizeof(lang), "LANG=%c%c_%c%c.UTF-8", + 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; + } - for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); - job; - job = (cupsd_job_t *)cupsArrayNext(Jobs)) + if ((attr = ippFindAttribute(job->attrs, "document-format", + IPP_TAG_MIMETYPE)) != NULL && + (ptr = strstr(attr->values[0].string.text, "charset=")) != NULL) + snprintf(charset, sizeof(charset), "CHARSET=%s", ptr + 8); + else + strlcpy(charset, "CHARSET=utf-8", sizeof(charset)); + + 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", + job->printer->device_uri); + snprintf(ppd, sizeof(ppd), "PPD=%s/ppd/%s.ppd", ServerRoot, + job->printer->name); + snprintf(printer_info, sizeof(printer_name), "PRINTER_INFO=%s", + job->printer->info ? job->printer->info : ""); + snprintf(printer_location, sizeof(printer_name), "PRINTER_LOCATION=%s", + job->printer->location ? job->printer->location : ""); + snprintf(printer_name, sizeof(printer_name), "PRINTER=%s", job->printer->name); + if (job->printer->num_reasons > 0) { - cupsArrayRemove(Jobs, job); - cupsArrayRemove(ActiveJobs, job); - cupsArrayRemove(PrintingJobs, job); + char *psrptr; /* Pointer into PRINTER_STATE_REASONS */ + size_t psrlen; /* Size of PRINTER_STATE_REASONS */ + + for (psrlen = 22, i = 0; i < job->printer->num_reasons; i ++) + psrlen += strlen(job->printer->reasons[i]) + 1; + + if ((printer_state_reasons = malloc(psrlen)) != NULL) + { + /* + * All of these strcpy's are safe because we allocated the psr string... + */ - cupsdDeleteJob(job); + strcpy(printer_state_reasons, "PRINTER_STATE_REASONS="); + for (psrptr = printer_state_reasons + 22, i = 0; + i < job->printer->num_reasons; + i ++) + { + if (i) + *psrptr++ = ','; + strcpy(psrptr, job->printer->reasons[i]); + psrptr += strlen(psrptr); + } + } } + snprintf(rip_max_cache, sizeof(rip_max_cache), "RIP_MAX_CACHE=%s", RIPCache); - cupsdReleaseSignals(); -} + if (job->printer->num_auth_info_required == 1) + snprintf(auth_info_required, sizeof(auth_info_required), + "AUTH_INFO_REQUIRED=%s", + job->printer->auth_info_required[0]); + else if (job->printer->num_auth_info_required == 2) + snprintf(auth_info_required, sizeof(auth_info_required), + "AUTH_INFO_REQUIRED=%s,%s", + job->printer->auth_info_required[0], + job->printer->auth_info_required[1]); + else if (job->printer->num_auth_info_required == 3) + snprintf(auth_info_required, sizeof(auth_info_required), + "AUTH_INFO_REQUIRED=%s,%s,%s", + job->printer->auth_info_required[0], + job->printer->auth_info_required[1], + job->printer->auth_info_required[2]); + else if (job->printer->num_auth_info_required == 4) + snprintf(auth_info_required, sizeof(auth_info_required), + "AUTH_INFO_REQUIRED=%s,%s,%s,%s", + job->printer->auth_info_required[0], + job->printer->auth_info_required[1], + job->printer->auth_info_required[2], + job->printer->auth_info_required[3]); + else + strlcpy(auth_info_required, "AUTH_INFO_REQUIRED=none", + sizeof(auth_info_required)); + envc = cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0]))); -/* - * 'cupsdFindJob()' - Find the specified job. - */ + 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_info; + envp[envc ++] = printer_location; + envp[envc ++] = printer_name; + envp[envc ++] = printer_state_reasons ? printer_state_reasons : + "PRINTER_STATE_REASONS=none"; + envp[envc ++] = banner_page ? "CUPS_FILETYPE=job-sheet" : + "CUPS_FILETYPE=document"; -cupsd_job_t * /* O - Job data */ -cupsdFindJob(int id) /* I - Job ID */ -{ - cupsd_job_t key; /* Search key */ + if (!job->printer->remote && !job->printer->raw) + { + filter = (mime_filter_t *)cupsArrayLast(filters); + if (job->printer->port_monitor) + filter = (mime_filter_t *)cupsArrayPrev(filters); - key.id = id; + if (filter && filter->dst) + { + if ((ptr = strchr(filter->dst->type, '/')) != NULL) + snprintf(final_content_type, sizeof(final_content_type), + "FINAL_CONTENT_TYPE=%s", ptr + 1); + else + snprintf(final_content_type, sizeof(final_content_type), + "FINAL_CONTENT_TYPE=%s/%s", filter->dst->super, + filter->dst->type); + envp[envc ++] = final_content_type; + } + } - return ((cupsd_job_t *)cupsArrayFind(Jobs, &key)); -} + 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; + } -/* - * 'cupsdGetPrinterJobCount()' - Get the number of pending, processing, - * or held jobs in a printer or class. - */ + if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT)) + { + snprintf(class_name, sizeof(class_name), "CLASS=%s", job->dest); + envp[envc ++] = class_name; + } -int /* O - Job count */ -cupsdGetPrinterJobCount( - const char *dest) /* I - Printer or class name */ -{ - int count; /* Job count */ - cupsd_job_t *job; /* Current job */ + envp[envc ++] = auth_info_required; + for (i = 0; + i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0])); + i ++) + if (job->auth_env[i]) + envp[envc ++] = job->auth_env[i]; + else + break; - for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0; - job; - job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) - if (job->dest && !strcasecmp(job->dest, dest)) - count ++; + if (job->auth_uid) + envp[envc ++] = job->auth_uid; - return (count); -} + envp[envc] = NULL; + for (i = 0; i < envc; i ++) + if (!strncmp(envp[i], "AUTH_", 5)) + cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"AUTH_%c****\"", i, + envp[i][5]); + else if (strncmp(envp[i], "DEVICE_URI=", 11)) + cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"%s\"", i, envp[i]); + else + cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"DEVICE_URI=%s\"", i, + job->printer->sanitized_device_uri); -/* - * 'cupsdGetUserJobCount()' - Get the number of pending, processing, - * or held jobs for a user. - */ + if (job->printer->remote) + job->current_file = job->num_files; + else + job->current_file ++; -int /* O - Job count */ -cupsdGetUserJobCount( - const char *username) /* I - Username */ -{ - int count; /* Job count */ - cupsd_job_t *job; /* Current job */ + /* + * Now create processes for all of the filters... + */ + cupsdSetPrinterReasons(job->printer, "-cups-missing-filter-warning," + "cups-insecure-filter-warning"); - for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0; - job; - job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) - if (!strcasecmp(job->username, username)) - count ++; + 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)); - return (count); -} + if (i < (cupsArrayCount(filters) - 1)) + { + if (cupsdOpenPipe(filterfds[slot])) + { + abort_message = "Stopping job because the scheduler could not create " + "the filter pipes."; + goto abort_job; + } + } + else + { + if (job->current_file == 1 || + (job->printer->pc && job->printer->pc->single_file)) + { + if (strncmp(job->printer->device_uri, "file:", 5) != 0) + { + if (cupsdOpenPipe(job->print_pipes)) + { + abort_message = "Stopping job because the scheduler could not " + "create the backend pipes."; -/* - * 'cupsdHoldJob()' - Hold the specified job. - */ + goto abort_job; + } + } + else + { + job->print_pipes[0] = -1; + if (!strcmp(job->printer->device_uri, "file:/dev/null") || + !strcmp(job->printer->device_uri, "file:///dev/null")) + job->print_pipes[1] = -1; + else + { + if (!strncmp(job->printer->device_uri, "file:/dev/", 10)) + job->print_pipes[1] = open(job->printer->device_uri + 5, + O_WRONLY | O_EXCL); + else if (!strncmp(job->printer->device_uri, "file:///dev/", 12)) + job->print_pipes[1] = open(job->printer->device_uri + 7, + O_WRONLY | O_EXCL); + else if (!strncmp(job->printer->device_uri, "file:///", 8)) + job->print_pipes[1] = open(job->printer->device_uri + 7, + O_WRONLY | O_CREAT | O_TRUNC, 0600); + else + job->print_pipes[1] = open(job->printer->device_uri + 5, + O_WRONLY | O_CREAT | O_TRUNC, 0600); -void -cupsdHoldJob(cupsd_job_t *job) /* I - Job data */ -{ - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdHoldJob: id = %d", job->id); + if (job->print_pipes[1] < 0) + { + abort_message = "Stopping job because the scheduler could not " + "open the output file."; - if (job->state_value == IPP_JOB_PROCESSING) - cupsdStopJob(job, 0); - else - cupsdLoadJob(job); + goto abort_job; + } - DEBUG_puts("cupsdHoldJob: setting state to held..."); + fcntl(job->print_pipes[1], F_SETFD, + fcntl(job->print_pipes[1], F_GETFD) | FD_CLOEXEC); + } + } + } - job->state->values[0].integer = IPP_JOB_HELD; - job->state_value = IPP_JOB_HELD; - job->current_file = 0; + filterfds[slot][0] = job->print_pipes[0]; + filterfds[slot][1] = job->print_pipes[1]; + } - job->dirty = 1; - cupsdMarkDirty(CUPSD_DIRTY_JOBS); -} + 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, job->filters + i); + cupsdClosePipe(filterfds[!slot]); -/* - * 'cupsdLoadAllJobs()' - Load all jobs from disk. - */ + if (pid == 0) + { + cupsdLogJob(job, CUPSD_LOG_ERROR, "Unable to start filter \"%s\" - %s.", + filter->filter, strerror(errno)); -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 */ + abort_message = "Stopping job because the scheduler could not execute a " + "filter."; + + goto abort_job; + } + cupsdLogJob(job, CUPSD_LOG_INFO, "Started filter %s (PID %d)", command, + pid); + + argv[6] = NULL; + slot = !slot; + } + cupsArrayDelete(filters); + filters = NULL; /* - * Create the job arrays as needed... + * Finally, pipe the final output into a backend process if needed... */ - if (!Jobs) - Jobs = cupsArrayNew(compare_jobs, NULL); + if (strncmp(job->printer->device_uri, "file:", 5) != 0) + { + if (job->current_file == 1 || job->printer->remote || + (job->printer->pc && job->printer->pc->single_file)) + { + sscanf(job->printer->device_uri, "%254[^:]", scheme); + snprintf(command, sizeof(command), "%s/backend/%s", ServerBin, scheme); - if (!ActiveJobs) - ActiveJobs = cupsArrayNew(compare_active_jobs, NULL); + /* + * See if the backend needs to run as root... + */ - if (!PrintingJobs) - PrintingJobs = cupsArrayNew(compare_jobs, NULL); + if (RunUser) + backroot = 0; + else if (stat(command, &backinfo)) + backroot = 0; + else + backroot = !(backinfo.st_mode & (S_IRWXG | S_IRWXO)); - /* - * See whether the job.cache file is older than the RequestRoot directory... - */ + argv[0] = job->printer->sanitized_device_uri; - snprintf(filename, sizeof(filename), "%s/job.cache", CacheDir); + filterfds[slot][0] = -1; + filterfds[slot][1] = -1; - if (stat(filename, &fileinfo)) + pid = cupsdStartProcess(command, argv, envp, filterfds[!slot][0], + filterfds[slot][1], job->status_pipes[1], + job->back_pipes[1], job->side_pipes[1], + backroot, job->profile, job, &(job->backend)); + + if (pid == 0) + { + abort_message = "Stopping job because the sheduler could not execute " + "the backend."; + + goto abort_job; + } + else + { + cupsdLogJob(job, CUPSD_LOG_INFO, "Started backend %s (PID %d)", + command, pid); + } + } + + if (job->current_file == job->num_files || + (job->printer->pc && job->printer->pc->single_file)) + cupsdClosePipe(job->print_pipes); + + if (job->current_file == job->num_files) + { + cupsdClosePipe(job->back_pipes); + cupsdClosePipe(job->side_pipes); + + close(job->status_pipes[1]); + job->status_pipes[1] = -1; + } + } + else { - fileinfo.st_mtime = 0; + filterfds[slot][0] = -1; + filterfds[slot][1] = -1; - if (errno != ENOENT) - cupsdLogMessage(CUPSD_LOG_ERROR, - "Unable to get file information for \"%s\" - %s", - filename, strerror(errno)); + if (job->current_file == job->num_files || + (job->printer->pc && job->printer->pc->single_file)) + cupsdClosePipe(job->print_pipes); + + if (job->current_file == job->num_files) + { + close(job->status_pipes[1]); + job->status_pipes[1] = -1; + } } - if (stat(RequestRoot, &dirinfo)) - { - dirinfo.st_mtime = 0; + cupsdClosePipe(filterfds[slot]); - if (errno != ENOENT) - cupsdLogMessage(CUPSD_LOG_ERROR, - "Unable to get directory information for \"%s\" - %s", - RequestRoot, strerror(errno)); + if (job->printer->remote && job->num_files > 1) + { + for (i = 0; i < job->num_files; i ++) + free(argv[i + 6]); } + free(argv); + if (printer_state_reasons) + free(printer_state_reasons); + + 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; + + /* - * Load the most recent source for job data... + * If we get here, we need to abort the current job and close out all + * files and pipes... */ - if (dirinfo.st_mtime > fileinfo.st_mtime) + abort_job: + + FilterLevel -= job->cost; + job->cost = 0; + + for (slot = 0; slot < 2; slot ++) + cupsdClosePipe(filterfds[slot]); + + cupsArrayDelete(filters); + + if (argv) { - load_request_root(); + if (job->printer->remote && job->num_files > 1) + { + for (i = 0; i < job->num_files; i ++) + free(argv[i + 6]); + } - load_next_job_id(filename); + free(argv); } - else - load_job_cache(filename); + + if (printer_state_reasons) + free(printer_state_reasons); + + cupsdClosePipe(job->print_pipes); + cupsdClosePipe(job->back_pipes); + cupsdClosePipe(job->side_pipes); + + cupsdRemoveSelect(job->status_pipes[0]); + cupsdClosePipe(job->status_pipes); + cupsdStatBufDelete(job->status_buffer); + job->status_buffer = NULL; /* - * Clean out old jobs as needed... + * Update the printer and job state. */ - if (MaxJobs > 0 && cupsArrayCount(Jobs) >= MaxJobs) - cupsdCleanJobs(); + cupsdSetJobState(job, abort_state, CUPSD_JOB_DEFAULT, "%s", abort_message); + cupsdSetPrinterState(job->printer, IPP_PRINTER_IDLE, 0); + update_job_attrs(job, 0); + + if (job->history) + free_job_history(job); + + cupsArrayRemove(PrintingJobs, job); + + /* + * Clear the printer <-> job association... + */ + + job->printer->job = NULL; + job->printer = NULL; } /* - * 'cupsdLoadJob()' - Load a single job... + * 'cupsdDeleteJob()' - Free all memory used by a job. */ void -cupsdLoadJob(cupsd_job_t *job) /* I - Job */ +cupsdDeleteJob(cupsd_job_t *job, /* I - Job */ + cupsd_jobaction_t action)/* I - Action */ { - 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 */ + int i; /* Looping var */ + char filename[1024]; /* Job filename */ - if (job->attrs) + if (job->printer) + finalize_job(job, 1); + + if (action == CUPSD_JOB_PURGE) { - if (job->state_value > IPP_JOB_STOPPED) - job->access_time = time(NULL); + /* + * Remove the job info file... + */ - return; + snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, + job->id); + if (Classification) + cupsdRemoveFile(filename); + else + unlink(filename); } - if ((job->attrs = ippNew()) == NULL) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, "Ran out of memory for job attributes!"); - return; - } + cupsdClearString(&job->username); + cupsdClearString(&job->dest); + for (i = 0; + i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0])); + i ++) + cupsdClearString(job->auth_env + i); + cupsdClearString(&job->auth_uid); - /* - * Load job attributes... - */ + if (job->num_files > 0) + { + free(job->compressions); + free(job->filetypes); - cupsdLogJob(job, CUPSD_LOG_DEBUG, "Loading attributes..."); + 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); - snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, job->id); - if ((fp = cupsFileOpen(jobfile, "r")) == NULL) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Unable to open job control file \"%s\" - %s!", - jobfile, strerror(errno)); - ippDelete(job->attrs); - job->attrs = NULL; - return; + job->num_files --; + } + } + else + job->num_files = 0; } - if (ippReadIO(fp, (ipp_iocb_t)cupsFileRead, 1, NULL, job->attrs) != IPP_DATA) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Unable to read job control file \"%s\"!", - jobfile); - cupsFileClose(fp); - ippDelete(job->attrs); - job->attrs = NULL; - unlink(jobfile); - return; - } + if (job->history) + free_job_history(job); - cupsFileClose(fp); + unload_job(job); - /* - * Copy attribute data to the job object... - */ + cupsArrayRemove(Jobs, job); + cupsArrayRemove(ActiveJobs, job); + cupsArrayRemove(PrintingJobs, job); - if ((job->state = ippFindAttribute(job->attrs, "job-state", - IPP_TAG_ENUM)) == NULL) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Missing or bad job-state attribute in control file!"); - ippDelete(job->attrs); - job->attrs = NULL; - unlink(jobfile); - return; - } + free(job); +} - 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) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "No job-printer-uri attribute in control file!"); - ippDelete(job->attrs); - job->attrs = NULL; - unlink(jobfile); - return; - } +/* + * 'cupsdFreeAllJobs()' - Free all jobs from memory. + */ - if ((dest = cupsdValidateDest(attr->values[0].string.text, &(job->dtype), - &destptr)) == NULL) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Unable to queue job for destination \"%s\"!", - attr->values[0].string.text); - ippDelete(job->attrs); - job->attrs = NULL; - unlink(jobfile); - return; - } +void +cupsdFreeAllJobs(void) +{ + cupsd_job_t *job; /* Current job */ - cupsdSetString(&job->dest, dest); - } - else if ((destptr = cupsdFindDest(job->dest)) == NULL) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Unable to queue job for destination \"%s\"!", job->dest); - ippDelete(job->attrs); - job->attrs = NULL; - unlink(jobfile); + + if (!Jobs) return; - } - job->sheets = ippFindAttribute(job->attrs, "job-media-sheets-completed", - IPP_TAG_INTEGER); + cupsdHoldSignals(); + + cupsdStopAllJobs(CUPSD_JOB_FORCE, 0); + cupsdSaveAllJobs(); + + for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); + job; + job = (cupsd_job_t *)cupsArrayNext(Jobs)) + cupsdDeleteJob(job, CUPSD_JOB_DEFAULT); + + 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 && !_cups_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 (!_cups_strcasecmp(job->username, username)) + count ++; + + return (count); +} + + +/* + * '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. + */ + +int /* O - 1 on success, 0 on failure */ +cupsdLoadJob(cupsd_job_t *job) /* I - Job */ +{ + int i; /* Looping var */ + char jobfile[1024]; /* Job filename */ + cups_file_t *fp; /* Job file */ + int fileid; /* Current file ID */ + 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 (1); + } + + if ((job->attrs = ippNew()) == NULL) + { + cupsdLogJob(job, CUPSD_LOG_ERROR, "Ran out of memory for job attributes!"); + return (0); + } + + /* + * 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) + { + char newfile[1024]; /* New job filename */ + + snprintf(newfile, sizeof(newfile), "%s/c%05d.N", RequestRoot, job->id); + if ((fp = cupsFileOpen(newfile, "r")) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "[Job %d] Unable to open job control file \"%s\": %s", + job->id, jobfile, strerror(errno)); + goto error; + } + + unlink(jobfile); + rename(newfile, jobfile); + } + + if (ippReadIO(fp, (ipp_iocb_t)cupsFileRead, 1, NULL, job->attrs) != IPP_DATA) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "[Job %d] Unable to read job control file \"%s\".", job->id, + jobfile); + cupsFileClose(fp); + goto error; + } + + cupsFileClose(fp); + + /* + * Copy attribute data to the job object... + */ + + if (!ippFindAttribute(job->attrs, "time-at-creation", IPP_TAG_INTEGER)) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "[Job %d] Missing or bad time-at-creation attribute in " + "control file!", job->id); + goto error; + } + + if ((job->state = ippFindAttribute(job->attrs, "job-state", + IPP_TAG_ENUM)) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "[Job %d] Missing or bad job-state attribute in control " + "file!", job->id); + goto error; + } + + 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); + goto error; + } + + if ((dest = cupsdValidateDest(attr->values[0].string.text, &(job->dtype), + &destptr)) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "[Job %d] Unable to queue job for destination \"%s\"!", + job->id, attr->values[0].string.text); + goto error; + } + + 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); + goto error; + } + + job->sheets = ippFindAttribute(job->attrs, "job-media-sheets-completed", + IPP_TAG_INTEGER); job->job_sheets = ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_NAME); if (!job->priority) @@ -1160,12 +1673,10 @@ cupsdLoadJob(cupsd_job_t *job) /* I - Job */ if ((attr = ippFindAttribute(job->attrs, "job-priority", IPP_TAG_INTEGER)) == NULL) { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Missing or bad job-priority attribute in control file!"); - ippDelete(job->attrs); - job->attrs = NULL; - unlink(jobfile); - return; + cupsdLogMessage(CUPSD_LOG_ERROR, + "[Job %d] Missing or bad job-priority attribute in " + "control file!", job->id); + goto error; } job->priority = attr->values[0].integer; @@ -1176,13 +1687,10 @@ cupsdLoadJob(cupsd_job_t *job) /* I - Job */ if ((attr = ippFindAttribute(job->attrs, "job-originating-user-name", IPP_TAG_NAME)) == NULL) { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Missing or bad job-originating-user-name attribute in " - "control file!"); - ippDelete(job->attrs); - job->attrs = NULL; - unlink(jobfile); - return; + cupsdLogMessage(CUPSD_LOG_ERROR, + "[Job %d] Missing or bad job-originating-user-name " + "attribute in control file!", job->id); + goto error; } cupsdSetString(&job->username, attr->values[0].string.text); @@ -1199,7 +1707,7 @@ cupsdLoadJob(cupsd_job_t *job) /* I - Job */ attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); if (attr) - cupsdSetJobHoldUntil(job, attr->values[0].string.text); + cupsdSetJobHoldUntil(job, attr->values[0].string.text, CUPSD_JOB_DEFAULT); else { job->state->values[0].integer = IPP_JOB_PENDING; @@ -1226,8 +1734,9 @@ cupsdLoadJob(cupsd_job_t *job) /* I - Job */ if (access(jobfile, 0)) break; - cupsdLogJob(job, CUPSD_LOG_DEBUG, - "Auto-typing document file \"%s\"...", jobfile); + cupsdLogMessage(CUPSD_LOG_DEBUG, + "[Job %d] Auto-typing document file \"%s\"...", job->id, + jobfile); if (fileid > job->num_files) { @@ -1247,9 +1756,33 @@ cupsdLoadJob(cupsd_job_t *job) /* I - Job */ if (!compressions || !filetypes) { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Ran out of memory for job file types!"); - return; + cupsdLogMessage(CUPSD_LOG_ERROR, + "[Job %d] Ran out of memory for job file types!", + job->id); + + ippDelete(job->attrs); + job->attrs = NULL; + + if (compressions) + free(compressions); + + if (filetypes) + free(filetypes); + + if (job->compressions) + { + free(job->compressions); + job->compressions = NULL; + } + + if (job->filetypes) + { + free(job->filetypes); + job->filetypes = NULL; + } + + job->num_files = 0; + return (0); } job->compressions = compressions; @@ -1274,20 +1807,22 @@ cupsdLoadJob(cupsd_job_t *job) /* I - Job */ { snprintf(jobfile, sizeof(jobfile), "%s/a%05d", RequestRoot, job->id); - cupsdClearString(&job->auth_username); - cupsdClearString(&job->auth_domain); - cupsdClearString(&job->auth_password); + for (i = 0; + i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0])); + i ++) + cupsdClearString(job->auth_env + i); + cupsdClearString(&job->auth_uid); if ((fp = cupsFileOpen(jobfile, "r")) != NULL) { - int i, /* Looping var */ - bytes; /* Size of auth data */ - char line[255], /* Line from file */ - data[255]; /* Decoded data */ + int bytes; /* Size of auth data */ + char line[65536], /* Line from file */ + data[65536]; /* Decoded data */ for (i = 0; i < destptr->num_auth_info_required && + i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0])) && cupsFileGets(fp, line, sizeof(line)); i ++) { @@ -1295,17 +1830,54 @@ cupsdLoadJob(cupsd_job_t *job) /* I - Job */ httpDecode64_2(data, &bytes, line); if (!strcmp(destptr->auth_info_required[i], "username")) - cupsdSetStringf(&job->auth_username, "AUTH_USERNAME=%s", data); + cupsdSetStringf(job->auth_env + i, "AUTH_USERNAME=%s", data); else if (!strcmp(destptr->auth_info_required[i], "domain")) - cupsdSetStringf(&job->auth_domain, "AUTH_DOMAIN=%s", data); + cupsdSetStringf(job->auth_env + i, "AUTH_DOMAIN=%s", data); else if (!strcmp(destptr->auth_info_required[i], "password")) - cupsdSetStringf(&job->auth_password, "AUTH_PASSWORD=%s", data); + cupsdSetStringf(job->auth_env + i, "AUTH_PASSWORD=%s", data); + else if (!strcmp(destptr->auth_info_required[i], "negotiate")) + cupsdSetStringf(job->auth_env + i, "AUTH_NEGOTIATE=%s", line); } + if (cupsFileGets(fp, line, sizeof(line)) && isdigit(line[0] & 255)) + cupsdSetStringf(&job->auth_uid, "AUTH_UID=%s", line); + cupsFileClose(fp); } } + job->access_time = time(NULL); + return (1); + + /* + * If we get here then something bad happened... + */ + + error: + + 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); + + return (0); } @@ -1344,12 +1916,11 @@ cupsdMoveJob(cupsd_job_t *job, /* I - Job */ * Change the destination information... */ - if (job->state_value == IPP_JOB_PROCESSING) - cupsdStopJob(job, 0); - else - cupsdLoadJob(job); + if (job->state_value > IPP_JOB_HELD) + cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT, + "Stopping job prior to move."); - cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, oldp, job, + cupsdAddEvent(CUPSD_EVENT_JOB_CONFIG_CHANGED, oldp, job, "Job #%d moved from %s to %s.", job->id, olddest, p->name); @@ -1377,7 +1948,8 @@ cupsdMoveJob(cupsd_job_t *job, /* I - Job */ void cupsdReleaseJob(cupsd_job_t *job) /* I - Job */ { - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdReleaseJob: id = %d", job->id); + cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdReleaseJob(job=%p(%d))", job, + job->id); if (job->state_value == IPP_JOB_HELD) { @@ -1388,12 +1960,8 @@ cupsdReleaseJob(cupsd_job_t *job) /* I - Job */ if (job->pending_timeout) cupsdTimeoutJob(job); - 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); + cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT, + "Job released by user."); } } @@ -1405,27 +1973,12 @@ cupsdReleaseJob(cupsd_job_t *job) /* I - Job */ void cupsdRestartJob(cupsd_job_t *job) /* I - Job */ { - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdRestartJob: id = %d", job->id); + cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdRestartJob(job=%p(%d))", job, + 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); - } + cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT, + "Job restarted by user."); } @@ -1437,30 +1990,19 @@ void cupsdSaveAllJobs(void) { int i; /* Looping var */ - cups_file_t *fp; /* Job cache file */ - char temp[1024]; /* Temporary string */ + cups_file_t *fp; /* job.cache file */ + char filename[1024], /* job.cache filename */ + temp[1024]; /* Temporary string */ cupsd_job_t *job; /* Current job */ time_t curtime; /* Current time */ struct tm *curdate; /* Current date */ - snprintf(temp, sizeof(temp), "%s/job.cache", CacheDir); - if ((fp = cupsFileOpen(temp, "w")) == NULL) - { - cupsdLogMessage(CUPSD_LOG_ERROR, - "Unable to create job cache file \"%s\" - %s", - temp, strerror(errno)); + snprintf(filename, sizeof(filename), "%s/job.cache", CacheDir); + if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm)) == NULL) return; - } - - cupsdLogMessage(CUPSD_LOG_INFO, "Saving job cache file \"%s\"...", temp); - - /* - * Restrict access to the file... - */ - fchown(cupsFileNumber(fp), getuid(), Group); - fchmod(cupsFileNumber(fp), ConfigFilePerm); + cupsdLogMessage(CUPSD_LOG_INFO, "Saving job.cache..."); /* * Write a small header to the file... @@ -1485,6 +2027,7 @@ cupsdSaveAllJobs(void) cupsFilePrintf(fp, "\n", job->id); cupsFilePrintf(fp, "State %d\n", job->state_value); cupsFilePrintf(fp, "Priority %d\n", job->priority); + cupsFilePrintf(fp, "HoldUntil %d\n", (int)job->hold_until); cupsFilePrintf(fp, "Username %s\n", job->username); cupsFilePrintf(fp, "Destination %s\n", job->dest); cupsFilePrintf(fp, "DestType %d\n", job->dtype); @@ -1495,7 +2038,7 @@ cupsdSaveAllJobs(void) cupsFilePuts(fp, "\n"); } - cupsFileClose(fp); + cupsdCloseCreatedConfFile(fp, filename); } @@ -1506,7 +2049,8 @@ cupsdSaveAllJobs(void) void cupsdSaveJob(cupsd_job_t *job) /* I - Job */ { - char filename[1024]; /* Job control filename */ + char filename[1024], /* Job control filename */ + newfile[1024]; /* New job control filename */ cups_file_t *fp; /* Job file */ @@ -1514,12 +2058,13 @@ cupsdSaveJob(cupsd_job_t *job) /* I - Job */ job, job->id, job->attrs); snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, job->id); + snprintf(newfile, sizeof(newfile), "%s/c%05d.N", RequestRoot, job->id); - if ((fp = cupsFileOpen(filename, "w")) == NULL) + if ((fp = cupsFileOpen(newfile, "w")) == NULL) { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Unable to create job control file \"%s\" - %s.", - filename, strerror(errno)); + cupsdLogMessage(CUPSD_LOG_ERROR, + "[Job %d] Unable to create job control file \"%s\": %s", + job->id, newfile, strerror(errno)); return; } @@ -1530,33 +2075,84 @@ cupsdSaveJob(cupsd_job_t *job) /* I - Job */ if (ippWriteIO(fp, (ipp_iocb_t)cupsFileWrite, 1, NULL, job->attrs) != IPP_DATA) - cupsdLogJob(job, CUPSD_LOG_ERROR, "Unable to write job control file!"); - - cupsFileClose(fp); + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "[Job %d] Unable to write job control file.", job->id); + cupsFileClose(fp); + unlink(newfile); + return; + } - job->dirty = 0; + if (cupsFileClose(fp)) + cupsdLogMessage(CUPSD_LOG_ERROR, + "[Job %d] Unable to close job control file: %s", + job->id, strerror(errno)); + else + { + unlink(filename); + if (rename(newfile, filename)) + cupsdLogMessage(CUPSD_LOG_ERROR, + "[Job %d] Unable to finalize job control file: %s", + job->id, strerror(errno)); + else + job->dirty = 0; + } } /* - * 'cupsdSetJobHoldUntil()' - Set the hold time for a job... + * 'cupsdSetJobHoldUntil()' - Set the hold time for a job. */ void cupsdSetJobHoldUntil(cupsd_job_t *job, /* I - Job */ - const char *when) /* I - When to resume */ + const char *when, /* I - When to resume */ + int update)/* I - Update job-hold-until attr? */ { time_t curtime; /* Current time */ struct tm *curdate; /* Current date */ int hour; /* Hold hour */ int minute; /* Hold minute */ - int second; /* Hold second */ + int second = 0; /* Hold second */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "cupsdSetJobHoldUntil(job=%p(%d), when=\"%s\", update=%d)", + job, job->id, when, update); + + if (update) + { + /* + * Update the job-hold-until attribute... + */ + + ipp_attribute_t *attr; /* job-hold-until attribute */ + + if ((attr = ippFindAttribute(job->attrs, "job-hold-until", + IPP_TAG_KEYWORD)) == NULL) + attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); + + if (attr) + cupsdSetString(&(attr->values[0].string.text), when); + else + attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-hold-until", NULL, when); + if (attr) + { + if (isdigit(when[0] & 255)) + attr->value_tag = IPP_TAG_NAME; + else + attr->value_tag = IPP_TAG_KEYWORD; - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSetJobHoldUntil(%d, \"%s\")", - job->id, when); + job->dirty = 1; + cupsdMarkDirty(CUPSD_DIRTY_JOBS); + } + } - second = 0; + /* + * Update the hold time... + */ if (!strcmp(when, "indefinite") || !strcmp(when, "auth-info-required")) { @@ -1639,7 +2235,7 @@ cupsdSetJobHoldUntil(cupsd_job_t *job, /* I - Job */ curtime = time(NULL); curdate = localtime(&curtime); - if (curdate->tm_wday || curdate->tm_wday == 6) + if (curdate->tm_wday == 0 || curdate->tm_wday == 6) job->hold_until = curtime; else job->hold_until = curtime + @@ -1668,7 +2264,7 @@ cupsdSetJobHoldUntil(cupsd_job_t *job, /* I - Job */ job->hold_until += 24 * 60 * 60; } - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSetJobHoldUntil: hold_until = %d", + cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSetJobHoldUntil: hold_until=%d", (int)job->hold_until); } @@ -1716,103 +2312,291 @@ cupsdSetJobPriority( /* - * 'cupsdStopAllJobs()' - Stop all print jobs. + * 'cupsdSetJobState()' - Set the state of the specified print job. */ void -cupsdStopAllJobs(int force) /* I - 1 = Force all filters to stop */ +cupsdSetJobState( + cupsd_job_t *job, /* I - Job to cancel */ + ipp_jstate_t newstate, /* I - New job state */ + cupsd_jobaction_t action, /* I - Action to take */ + const char *message, /* I - Message to log */ + ...) /* I - Additional arguments as needed */ { - cupsd_job_t *job; /* Current job */ + int i; /* Looping var */ + ipp_jstate_t oldstate; /* Old state */ + char filename[1024]; /* Job filename */ + ipp_attribute_t *attr; /* Job attribute */ - DEBUG_puts("cupsdStopAllJobs()"); + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "cupsdSetJobState(job=%p(%d), state=%d, newstate=%d, " + "action=%d, message=\"%s\")", job, job->id, job->state_value, + newstate, action, message ? message : "(null)"); - 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; - } -} + /* + * Make sure we have the job attributes... + */ -/* - * 'cupsdStopJob()' - Stop a print job. - */ + if (!cupsdLoadJob(job)) + return; -void -cupsdStopJob(cupsd_job_t *job, /* I - Job */ - int force) /* I - 1 = Force all filters to stop */ -{ - int i; /* Looping var */ + /* + * Don't do anything if the state is unchanged and we aren't purging the + * job... + */ + oldstate = job->state_value; + if (newstate == oldstate && action != CUPSD_JOB_PURGE) + return; - cupsdLogJob(job, CUPSD_LOG_DEBUG2, "cupsdStopJob: force = %d", force); + /* + * Stop any processes that are working on the current job... + */ - if (job->state_value != IPP_JOB_PROCESSING) - return; + if (oldstate == IPP_JOB_PROCESSING) + stop_job(job, action); - FilterLevel -= job->cost; + /* + * Set the new job state... + */ - if (job->printer->state == IPP_PRINTER_PROCESSING) - cupsdSetPrinterState(job->printer, IPP_PRINTER_IDLE, 0); + job->state->values[0].integer = newstate; + job->state_value = newstate; - job->state->values[0].integer = IPP_JOB_STOPPED; - job->state_value = IPP_JOB_STOPPED; - job->printer->job = NULL; - job->printer = NULL; + switch (newstate) + { + case IPP_JOB_PENDING : + /* + * Update job-hold-until as needed... + */ - job->current_file --; + if ((attr = ippFindAttribute(job->attrs, "job-hold-until", + IPP_TAG_KEYWORD)) == NULL) + attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); - for (i = 0; job->filters[i]; i ++) - if (job->filters[i] > 0) - { - cupsdEndProcess(job->filters[i], force); - job->filters[i] = 0; - } + if (attr) + { + attr->value_tag = IPP_TAG_KEYWORD; + cupsdSetString(&(attr->values[0].string.text), "no-hold"); + } - if (job->backend > 0) + default : + break; + + case IPP_JOB_ABORTED : + case IPP_JOB_CANCELED : + case IPP_JOB_COMPLETED : + set_time(job, "time-at-completed"); + break; + } + + /* + * Log message as needed... + */ + + if (message) { - cupsdEndProcess(job->backend, force); - job->backend = 0; + char buffer[2048]; /* Message buffer */ + va_list ap; /* Pointer to additional arguments */ + + va_start(ap, message); + vsnprintf(buffer, sizeof(buffer), message, ap); + va_end(ap); + + if (newstate > IPP_JOB_STOPPED) + cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, "%s", buffer); + else + cupsdAddEvent(CUPSD_EVENT_JOB_STATE, job->printer, job, "%s", buffer); + + if (newstate == IPP_JOB_STOPPED || newstate == IPP_JOB_ABORTED) + cupsdLogJob(job, CUPSD_LOG_ERROR, "%s", buffer); + else + cupsdLogJob(job, CUPSD_LOG_INFO, "%s", buffer); } - cupsdDestroyProfile(job->profile); - job->profile = NULL; + /* + * Handle post-state-change actions... + */ - cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Closing print pipes [ %d %d ]...", - job->print_pipes[0], job->print_pipes[1]); + switch (newstate) + { + case IPP_JOB_PROCESSING : + /* + * Add the job to the "printing" list... + */ - cupsdClosePipe(job->print_pipes); + if (!cupsArrayFind(PrintingJobs, job)) + cupsArrayAdd(PrintingJobs, job); - cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Closing back pipes [ %d %d ]...", - job->back_pipes[0], job->back_pipes[1]); + /* + * Set the processing time... + */ - cupsdClosePipe(job->back_pipes); + set_time(job, "time-at-processing"); - cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Closing side pipes [ %d %d ]...", - job->side_pipes[0], job->side_pipes[1]); + case IPP_JOB_PENDING : + case IPP_JOB_HELD : + case IPP_JOB_STOPPED : + /* + * Make sure the job is in the active list... + */ - cupsdClosePipe(job->side_pipes); + if (!cupsArrayFind(ActiveJobs, job)) + cupsArrayAdd(ActiveJobs, job); - if (job->status_buffer) - { - /* - * Close the pipe and clear the input bit. - */ + /* + * Save the job state to disk... + */ - cupsdRemoveSelect(job->status_buffer->fd); + job->dirty = 1; + cupsdMarkDirty(CUPSD_DIRTY_JOBS); + break; - cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Closing status pipes [ %d %d ]...", - job->status_pipes[0], job->status_pipes[1]); + case IPP_JOB_ABORTED : + case IPP_JOB_CANCELED : + case IPP_JOB_COMPLETED : + if (newstate == IPP_JOB_CANCELED) + { + /* + * Remove the job from the active list if there are no processes still + * running for it... + */ - cupsdClosePipe(job->status_pipes); - cupsdStatBufDelete(job->status_buffer); + for (i = 0; job->filters[i] < 0; i++); - job->status_buffer = NULL; + if (!job->filters[i] && job->backend <= 0) + cupsArrayRemove(ActiveJobs, job); + } + else + { + /* + * Otherwise just remove the job from the active list immediately... + */ + + cupsArrayRemove(ActiveJobs, job); + } + + /* + * Expire job subscriptions since the job is now "completed"... + */ + + cupsdExpireSubscriptions(NULL, job); + +#ifdef __APPLE__ + /* + * If we are going to sleep and the PrintingJobs count is now 0, allow the + * sleep to happen immediately... + */ + + if (Sleeping && cupsArrayCount(PrintingJobs) == 0) + cupsdAllowSleep(); +#endif /* __APPLE__ */ + + /* + * 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)); + + for (i = 0; + i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0])); + i ++) + cupsdClearString(job->auth_env + i); + + cupsdClearString(&job->auth_uid); + + /* + * Remove the print file for good if we aren't preserving jobs or + * files... + */ + + 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; + } + } + + if (JobHistory && action != CUPSD_JOB_PURGE) + { + /* + * Save job state info... + */ + + job->dirty = 1; + cupsdMarkDirty(CUPSD_DIRTY_JOBS); + } + else if (!job->printer) + { + /* + * Delete the job immediately if not actively printing... + */ + + cupsdDeleteJob(job, CUPSD_JOB_PURGE); + job = NULL; + } + break; + } + + /* + * Finalize the job immediately if we forced things... + */ + + if (action >= CUPSD_JOB_FORCE && job && job->printer) + finalize_job(job, 0); + + /* + * Update the server "busy" state... + */ + + cupsdSetBusyState(); +} + + +/* + * 'cupsdStopAllJobs()' - Stop all print jobs. + */ + +void +cupsdStopAllJobs( + cupsd_jobaction_t action, /* I - Action */ + int kill_delay) /* I - Number of seconds before we kill */ +{ + cupsd_job_t *job; /* Current job */ + + + DEBUG_puts("cupsdStopAllJobs()"); + + for (job = (cupsd_job_t *)cupsArrayFirst(PrintingJobs); + job; + job = (cupsd_job_t *)cupsArrayNext(PrintingJobs)) + { + if (kill_delay) + job->kill_time = time(NULL) + kill_delay; + + cupsdSetJobState(job, IPP_JOB_PENDING, action, NULL); } } @@ -1833,7 +2617,7 @@ cupsdUnloadCompletedJobs(void) for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); job; job = (cupsd_job_t *)cupsArrayNext(Jobs)) - if (job->attrs && job->state_value >= IPP_JOB_STOPPED && + if (job->attrs && job->state_value >= IPP_JOB_STOPPED && !job->printer && job->access_time < expire) { if (job->dirty) @@ -1856,6 +2640,8 @@ compare_active_jobs(void *first, /* I - First job */ int diff; /* Difference */ + (void)data; + if ((diff = ((cupsd_job_t *)second)->priority - ((cupsd_job_t *)first)->priority) != 0) return (diff); @@ -1873,1017 +2659,775 @@ compare_jobs(void *first, /* I - First job */ void *second, /* I - Second job */ void *data) /* I - App data (not used) */ { + (void)data; + return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id); } /* - * 'ipp_length()' - Compute the size of the buffer needed to hold - * the textual IPP attributes. + * 'dump_job_history()' - Dump any debug messages for a job. */ -static int /* O - Size of attribute buffer */ -ipp_length(ipp_t *ipp) /* I - IPP request */ +static void +dump_job_history(cupsd_job_t *job) /* I - Job */ { - int bytes; /* Number of bytes */ - int i; /* Looping var */ - ipp_attribute_t *attr; /* Current attribute */ + int i, /* Looping var */ + oldsize; /* Current MaxLogSize */ + struct tm *date; /* Date/time value */ + cupsd_joblog_t *message; /* Current message */ + char temp[2048], /* Log message */ + *ptr, /* Pointer into log message */ + start[256], /* Start time */ + end[256]; /* End time */ + cupsd_printer_t *printer; /* Printer for job */ /* - * Loop through all attributes... + * See if we have anything to dump... */ - bytes = 0; + if (!job->history) + return; - for (attr = ipp->attrs; attr != NULL; attr = attr->next) - { - /* - * Skip attributes that won't be sent to filters... - */ + /* + * Disable log rotation temporarily... + */ - 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; + oldsize = MaxLogSize; + MaxLogSize = 0; - if (strncmp(attr->name, "time-", 5) == 0) - continue; + /* + * Copy the debug messages to the log... + */ - /* - * 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... - */ + message = (cupsd_joblog_t *)cupsArrayFirst(job->history); + date = localtime(&(message->time)); + strftime(start, sizeof(start), "%X", date); - bytes ++; /* " " separator */ - bytes += attr->num_values; /* "," separators */ + message = (cupsd_joblog_t *)cupsArrayLast(job->history); + date = localtime(&(message->time)); + strftime(end, sizeof(end), "%X", date); - /* - * Boolean attributes appear as "foo,nofoo,foo,nofoo", while - * other attributes appear as "foo=value1,value2,...,valueN". - */ + snprintf(temp, sizeof(temp), + "[Job %d] The following messages were recorded from %s to %s", + job->id, start, end); + cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp); - if (attr->value_tag != IPP_TAG_BOOLEAN) - bytes += strlen(attr->name); - else - bytes += attr->num_values * strlen(attr->name); + for (message = (cupsd_joblog_t *)cupsArrayFirst(job->history); + message; + message = (cupsd_joblog_t *)cupsArrayNext(job->history)) + cupsdWriteErrorLog(CUPSD_LOG_DEBUG, message->message); - /* - * Now add the size required for each value in the attribute... - */ + snprintf(temp, sizeof(temp), "[Job %d] End of messages", job->id); + cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp); - switch (attr->value_tag) + /* + * Log the printer state values... + */ + + if ((printer = job->printer) == NULL) + printer = cupsdFindDest(job->dest); + + if (printer) + { + snprintf(temp, sizeof(temp), "[Job %d] printer-state=%d(%s)", job->id, + printer->state, + printer->state == IPP_PRINTER_IDLE ? "idle" : + printer->state == IPP_PRINTER_PROCESSING ? "processing" : + "stopped"); + cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp); + + snprintf(temp, sizeof(temp), "[Job %d] printer-state-message=\"%s\"", + job->id, printer->state_message); + cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp); + + snprintf(temp, sizeof(temp), "[Job %d] printer-state-reasons=", job->id); + ptr = temp + strlen(temp); + if (printer->num_reasons == 0) + strlcpy(ptr, "none", sizeof(temp) - (ptr - temp)); + else { - case IPP_TAG_INTEGER : - case IPP_TAG_ENUM : - /* - * Minimum value of a signed integer is -2147483647, or 11 digits. - */ + for (i = 0; + i < printer->num_reasons && ptr < (temp + sizeof(temp) - 2); + i ++) + { + if (i) + *ptr++ = ','; - bytes += attr->num_values * 11; - break; + strlcpy(ptr, printer->reasons[i], sizeof(temp) - (ptr - temp)); + ptr += strlen(ptr); + } + } + cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp); + } - case IPP_TAG_BOOLEAN : - /* - * Add two bytes for each false ("no") value... - */ + /* + * Restore log file rotation... + */ - for (i = 0; i < attr->num_values; i ++) - if (!attr->values[i].boolean) - bytes += 2; - break; + MaxLogSize = oldsize; - case IPP_TAG_RANGE : - /* - * A range is two signed integers separated by a hyphen, or - * 23 characters max. - */ + /* + * Free all messages... + */ - bytes += attr->num_values * 23; - break; + free_job_history(job); +} - 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; +/* + * 'free_job_history()' - Free any log history. + */ - 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. - */ +static void +free_job_history(cupsd_job_t *job) /* I - Job */ +{ + char *message; /* Current message */ - for (i = 0; i < attr->num_values; i ++) - bytes += 2 * strlen(attr->values[i].string.text) + 2; - break; - default : - break; /* anti-compiler-warning-code */ - } - } + if (!job->history) + return; - return (bytes); + for (message = (char *)cupsArrayFirst(job->history); + message; + message = (char *)cupsArrayNext(job->history)) + free(message); + + cupsArrayDelete(job->history); + job->history = NULL; } /* - * 'load_job_cache()' - Load jobs from the job.cache file. + * 'finalize_job()' - Cleanup after job filter processes and support data. */ static void -load_job_cache(const char *filename) /* I - job.cache filename */ +finalize_job(cupsd_job_t *job, /* I - Job */ + int set_job_state) /* I - 1 = set the job state */ { - 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 */ + ipp_pstate_t printer_state; /* New printer state value */ + ipp_jstate_t job_state; /* New job state value */ + const char *message; /* Message for job state */ + char buffer[1024]; /* Buffer for formatted messages */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "finalize_job(job=%p(%d))", job, job->id); /* - * Open the job.cache file... + * Clear the "connecting-to-device" reason, which is only valid when a printer + * is processing, along with any remote printing job state... */ - if ((fp = cupsFileOpen(filename, "r")) == NULL) - { - if (errno != ENOENT) - cupsdLogMessage(CUPSD_LOG_ERROR, - "Unable to open job cache file \"%s\": %s", - filename, strerror(errno)); + cupsdSetPrinterReasons(job->printer, "-connecting-to-device," + "cups-remote-pending," + "cups-remote-pending-held," + "cups-remote-processing," + "cups-remote-stopped," + "cups-remote-canceled," + "cups-remote-aborted," + "cups-remote-completed"); - load_request_root(); + /* + * Similarly, clear the "offline-report" reason for non-USB devices since we + * rarely have current information for network devices... + */ - return; - } + if (strncmp(job->printer->device_uri, "usb:", 4)) + cupsdSetPrinterReasons(job->printer, "-offline-report"); /* - * Read entries from the job cache file and create jobs as needed. + * Free the security profile... */ - cupsdLogMessage(CUPSD_LOG_INFO, "Loading job cache file \"%s\"...", - filename); + cupsdDestroyProfile(job->profile); + job->profile = NULL; - linenum = 0; - job = NULL; + /* + * Clear the unresponsive job watchdog timer... + */ - 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; - } + job->kill_time = 0; - if (!value) - { - cupsdLogMessage(CUPSD_LOG_ERROR, "Missing job ID on line %d!", linenum); - continue; - } + /* + * Close pipes and status buffer... + */ - jobid = atoi(value); + cupsdClosePipe(job->print_pipes); + cupsdClosePipe(job->back_pipes); + cupsdClosePipe(job->side_pipes); - if (jobid < 1) - { - cupsdLogMessage(CUPSD_LOG_ERROR, "Bad job ID %d on line %d!", jobid, - linenum); - continue; - } + cupsdRemoveSelect(job->status_pipes[0]); + cupsdClosePipe(job->status_pipes); + cupsdStatBufDelete(job->status_buffer); + job->status_buffer = NULL; - 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; - } + /* + * Process the exit status... + */ - job = calloc(1, sizeof(cupsd_job_t)); - if (!job) - { - cupsdLogMessage(CUPSD_LOG_EMERG, - "[Job %d] Unable to allocate memory for job!", jobid); - break; - } + if (job->printer->state == IPP_PRINTER_PROCESSING) + printer_state = IPP_PRINTER_IDLE; + else + printer_state = job->printer->state; - 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; + switch (job_state = job->state_value) + { + case IPP_JOB_PENDING : + message = "Job paused."; + break; - cupsdLogJob(job, CUPSD_LOG_DEBUG, "Loading from cache..."); - } - else if (!job) - { - cupsdLogMessage(CUPSD_LOG_ERROR, - "Missing directive on line %d!", linenum); - continue; - } - else if (!strcasecmp(line, "")) - { - cupsArrayAdd(Jobs, job); + case IPP_JOB_HELD : + message = "Job held."; + break; - if (job->state_value <= IPP_JOB_STOPPED) - { - cupsArrayAdd(ActiveJobs, job); - cupsdLoadJob(job); - } + default : + case IPP_JOB_PROCESSING : + case IPP_JOB_COMPLETED : + job_state = IPP_JOB_COMPLETED; + message = "Job completed."; + break; - 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); + case IPP_JOB_STOPPED : + message = "Job stopped."; + break; - 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); + case IPP_JOB_CANCELED : + message = "Job canceled."; + break; - 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; - } + case IPP_JOB_ABORTED : + message = "Job aborted."; + break; + } - if (job->num_files > 0) - { - snprintf(jobfile, sizeof(jobfile), "%s/d%05d-001", RequestRoot, - job->id); - if (access(jobfile, 0)) - { - cupsdLogJob(job, CUPSD_LOG_INFO, "Data files have gone away!"); - job->num_files = 0; - continue; - } + if (job->status < 0) + { + /* + * Backend had errors... + */ - job->filetypes = calloc(job->num_files, sizeof(mime_type_t *)); - job->compressions = calloc(job->num_files, sizeof(int)); + int exit_code; /* Exit code from backend */ - if (!job->filetypes || !job->compressions) - { - cupsdLogJob(job, CUPSD_LOG_EMERG, - "Unable to allocate memory for %d files!", - job->num_files); - break; - } - } - } - else if (!strcasecmp(line, "File")) + /* + * 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; + + cupsdLogJob(job, CUPSD_LOG_INFO, "Backend returned status %d (%s)", + 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) { - int number, /* File number */ - compression; /* Compression value */ - char super[MIME_MAX_SUPER], /* MIME super type */ - type[MIME_MAX_TYPE]; /* MIME type */ + default : + case CUPS_BACKEND_FAILED : + /* + * Backend failure, use the error-policy to determine how to + * act... + */ + if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT)) + { + /* + * Queued on a class - mark the job as pending and we'll retry on + * another printer... + */ - 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 (job_state == IPP_JOB_COMPLETED) + { + job_state = IPP_JOB_PENDING; + message = "Retrying job on another printer."; + } + } + else if (!strcmp(job->printer->error_policy, "retry-current-job")) + { + /* + * The error policy is "retry-current-job" - mark the job as pending + * and we'll retry on the same printer... + */ - if (number < 1 || number > job->num_files) - { - cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File number %d on line %d!", - number, linenum); - continue; - } + if (job_state == IPP_JOB_COMPLETED) + { + job_state = IPP_JOB_PENDING; + message = "Retrying job on same printer."; + } + } + else if ((job->printer->type & CUPS_PRINTER_FAX) || + !strcmp(job->printer->error_policy, "retry-job")) + { + if (job_state == IPP_JOB_COMPLETED) + { + /* + * The job was queued on a fax or the error policy is "retry-job" - + * hold the job if the number of retries is less than the + * JobRetryLimit, otherwise abort the job. + */ - number --; + job->tries ++; - job->compressions[number] = compression; - job->filetypes[number] = mimeType(MimeDatabase, super, type); + if (job->tries > JobRetryLimit && JobRetryLimit > 0) + { + /* + * Too many tries... + */ + + snprintf(buffer, sizeof(buffer), + "Job aborted after %d unsuccessful attempts.", + JobRetryLimit); + job_state = IPP_JOB_ABORTED; + message = buffer; + } + else + { + /* + * Try again in N seconds... + */ - if (!job->filetypes[number]) - { - /* - * If the original MIME type is unknown, auto-type it! - */ + snprintf(buffer, sizeof(buffer), + "Job held for %d seconds since it could not be sent.", + JobRetryInterval); - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Unknown MIME type %s/%s for file %d!", - super, type, number + 1); + job->hold_until = time(NULL) + JobRetryInterval; + job_state = IPP_JOB_HELD; + message = buffer; + } + } + } + else if (!strcmp(job->printer->error_policy, "abort-job") && + job_state == IPP_JOB_COMPLETED) + { + job_state = IPP_JOB_ABORTED; + message = "Job aborted due to backend errors; please consult " + "the error_log file for details."; + } + else if (job->state_value == IPP_JOB_PROCESSING) + { + job_state = IPP_JOB_PENDING; + printer_state = IPP_PRINTER_STOPPED; + message = "Printer stopped due to backend errors; please " + "consult the error_log file for details."; + } + break; - snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot, - job->id, number + 1); - job->filetypes[number] = mimeFileType(MimeDatabase, jobfile, NULL, - job->compressions + number); + case CUPS_BACKEND_CANCEL : + /* + * Abort the job... + */ - /* - * If that didn't work, assume it is raw... - */ + if (job_state == IPP_JOB_COMPLETED) + { + job_state = IPP_JOB_ABORTED; + message = "Job aborted due to backend errors; please consult " + "the error_log file for details."; + } + break; - 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); - } + case CUPS_BACKEND_HOLD : + if (job_state == IPP_JOB_COMPLETED) + { + /* + * Hold the job... + */ - cupsFileClose(fp); -} + cupsdSetJobHoldUntil(job, "indefinite", 1); + job_state = IPP_JOB_HELD; + message = "Job held indefinitely due to backend errors; please " + "consult the error_log file for details."; + } + break; -/* - * 'load_next_job_id()' - Load the NextJobId value from the job.cache file. - */ + case CUPS_BACKEND_STOP : + /* + * Stop the printer... + */ -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 */ + printer_state = IPP_PRINTER_STOPPED; + message = "Printer stopped due to backend errors; please " + "consult the error_log file for details."; + if (job_state == IPP_JOB_COMPLETED) + job_state = IPP_JOB_PENDING; + break; - /* - * Read the NextJobId directive from the job.cache file and use - * the value (if any). - */ + case CUPS_BACKEND_AUTH_REQUIRED : + /* + * Hold the job for authentication... + */ - if ((fp = cupsFileOpen(filename, "r")) == NULL) - { - if (errno != ENOENT) - cupsdLogMessage(CUPSD_LOG_ERROR, - "Unable to open job cache file \"%s\": %s", - filename, strerror(errno)); + if (job_state == IPP_JOB_COMPLETED) + { + cupsdSetJobHoldUntil(job, "auth-info-required", 1); - return; - } + job_state = IPP_JOB_HELD; + message = "Job held for authentication."; + } + break; - cupsdLogMessage(CUPSD_LOG_INFO, - "Loading NextJobId from job cache file \"%s\"...", filename); + case CUPS_BACKEND_RETRY : + if (job_state == IPP_JOB_COMPLETED) + { + /* + * Hold the job if the number of retries is less than the + * JobRetryLimit, otherwise abort the job. + */ - linenum = 0; + job->tries ++; - while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) - { - if (!strcasecmp(line, "NextJobId")) - { - if (value) - { - next_job_id = atoi(value); + if (job->tries > JobRetryLimit && JobRetryLimit > 0) + { + /* + * Too many tries... + */ - if (next_job_id > NextJobId) - NextJobId = next_job_id; - } - break; - } - } + snprintf(buffer, sizeof(buffer), + "Job aborted after %d unsuccessful attempts.", + JobRetryLimit); + job_state = IPP_JOB_ABORTED; + message = buffer; + } + else + { + /* + * Try again in N seconds... + */ - cupsFileClose(fp); -} + snprintf(buffer, sizeof(buffer), + "Job held for %d seconds since it could not be sent.", + JobRetryInterval); + job->hold_until = time(NULL) + JobRetryInterval; + job_state = IPP_JOB_HELD; + message = buffer; + } + } + break; -/* - * 'load_request_root()' - Load jobs from the RequestRoot directory. - */ + case CUPS_BACKEND_RETRY_CURRENT : + /* + * Mark the job as pending and retry on the same printer... + */ -static void -load_request_root(void) -{ - cups_dir_t *dir; /* Directory */ - cups_dentry_t *dent; /* Directory entry */ - cupsd_job_t *job; /* New job */ + if (job_state == IPP_JOB_COMPLETED) + { + job_state = IPP_JOB_PENDING; + message = "Retrying job on same printer."; + } + break; + } + } + else if (job->status > 0) + { + /* + * Filter had errors; stop job... + */ + if (job_state == IPP_JOB_COMPLETED) + { + job_state = IPP_JOB_STOPPED; + message = "Job stopped due to filter errors; please consult the " + "error_log file for details."; + } + } /* - * Open the requests directory... + * Update the printer and job state. */ - cupsdLogMessage(CUPSD_LOG_DEBUG, "Scanning %s for jobs...", RequestRoot); + if (set_job_state && job_state != job->state_value) + cupsdSetJobState(job, job_state, CUPSD_JOB_DEFAULT, "%s", message); - if ((dir = cupsDirOpen(RequestRoot)) == NULL) + cupsdSetPrinterState(job->printer, printer_state, + printer_state == IPP_PRINTER_STOPPED); + update_job_attrs(job, 0); + + if (job->history) { - cupsdLogMessage(CUPSD_LOG_ERROR, - "Unable to open spool directory \"%s\": %s", - RequestRoot, strerror(errno)); - return; + if (job->status && + (job->state_value == IPP_JOB_ABORTED || + job->state_value == IPP_JOB_STOPPED)) + dump_job_history(job); + else + free_job_history(job); } + cupsArrayRemove(PrintingJobs, job); + /* - * Read all the c##### files... + * Clear the printer <-> job association... */ - 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); + job->printer->job = NULL; + job->printer = NULL; /* - * Either add the attribute or update the value of the existing one + * Try printing another job... */ - if (attr == NULL) - ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD, "job-hold-until", - NULL, holdstr); - else - cupsdSetString(&attr->values[0].string.text, holdstr); - - job->dirty = 1; - cupsdMarkDirty(CUPSD_DIRTY_JOBS); + if (printer_state != IPP_PRINTER_STOPPED) + cupsdCheckJobs(); } /* - * 'start_job()' - Start a print job. + * 'get_options()' - Get a string containing the job options. */ -static void -start_job(cupsd_job_t *job, /* I - Job ID */ - cupsd_printer_t *printer) /* I - Printer to print job */ +static char * /* O - Options string */ +get_options(cupsd_job_t *job, /* I - Job */ + int banner_page, /* I - Printing a banner page? */ + char *copies, /* I - Copies buffer */ + size_t copies_size, /* I - Size of copies buffer */ + char *title, /* I - Title buffer */ + size_t title_size) /* I - Size of title buffer */ { 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 */ + size_t newlength; /* New option buffer length */ + char *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 */ + _ppd_cache_t *pc; /* PPD cache and mapping data */ + int num_pwgppds; /* Number of PWG->PPD options */ + cups_option_t *pwgppds, /* PWG->PPD options */ + *pwgppd, /* Current PWG->PPD option */ + *preset; /* Current preset option */ + int print_color_mode, + /* Output mode (if any) */ + print_quality; /* Print quality (if any) */ + const char *ppd; /* PPD option choice */ + int exact; /* Did we get an exact match? */ static char *options = NULL;/* Full list of options */ - static int optlength = 0; /* Length of option buffer */ - - - cupsdLogJob(job, CUPSD_LOG_DEBUG2, "start_job: file = %d/%d", - job->current_file, job->num_files); + static size_t optlength = 0; /* Length of option buffer */ - if (job->num_files == 0) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, "No files, canceling job!"); - cupsdCancelJob(job, 0, IPP_JOB_ABORTED); - return; - } /* - * Figure out what filters are required to convert from - * the source to the destination type... + * 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 build an options array for any PWG->PPD mapped option/choice pairs. */ - filters = NULL; - job->cost = 0; - - if (printer->raw) + pc = job->printer->pc; + num_pwgppds = 0; + pwgppds = NULL; + + if (pc && + !ippFindAttribute(job->attrs, + "com.apple.print.DocumentTicket.PMSpoolFormat", + IPP_TAG_ZERO) && + !ippFindAttribute(job->attrs, "APPrinterPreset", IPP_TAG_ZERO) && + (ippFindAttribute(job->attrs, "output-mode", IPP_TAG_ZERO) || + ippFindAttribute(job->attrs, "print-color-mode", IPP_TAG_ZERO) || + ippFindAttribute(job->attrs, "print-quality", IPP_TAG_ZERO))) { /* - * Remote jobs and raw queues go directly to the printer without - * filtering... + * Map output-mode and print-quality to a preset... */ - cupsdLogJob(job, CUPSD_LOG_DEBUG, "Sending job to queue tagged as raw..."); + if ((attr = ippFindAttribute(job->attrs, "print-color-mode", + IPP_TAG_KEYWORD)) == NULL) + attr = ippFindAttribute(job->attrs, "output-mode", IPP_TAG_KEYWORD); - filters = NULL; - } - else - { - /* - * Local jobs get filtered... - */ + if (attr && !strcmp(attr->values[0].string.text, "monochrome")) + print_color_mode = _PWG_PRINT_COLOR_MODE_MONOCHROME; + else + print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR; - filters = mimeFilter(MimeDatabase, job->filetypes[job->current_file], - printer->filetype, &(job->cost)); + if ((attr = ippFindAttribute(job->attrs, "print-quality", + IPP_TAG_ENUM)) != NULL && + attr->values[0].integer >= IPP_QUALITY_DRAFT && + attr->values[0].integer <= IPP_QUALITY_HIGH) + print_quality = attr->values[0].integer - IPP_QUALITY_DRAFT; + else + print_quality = _PWG_PRINT_QUALITY_NORMAL; - if (!filters) + if (pc->num_presets[print_color_mode][print_quality] == 0) { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Unable to convert file %d to printable format!", - job->current_file); - 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); + /* + * Try to find a preset that works so that we maximize the chances of us + * getting a good print using IPP attributes. + */ - return; + if (pc->num_presets[print_color_mode][_PWG_PRINT_QUALITY_NORMAL] > 0) + print_quality = _PWG_PRINT_QUALITY_NORMAL; + else if (pc->num_presets[_PWG_PRINT_COLOR_MODE_COLOR][print_quality] > 0) + print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR; + else + { + print_quality = _PWG_PRINT_QUALITY_NORMAL; + print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR; + } } - /* - * Remove NULL ("-") filters... - */ + if (pc->num_presets[print_color_mode][print_quality] > 0) + { + /* + * Copy the preset options as long as the corresponding names are not + * already defined in the IPP request... + */ - for (filter = (mime_filter_t *)cupsArrayFirst(filters); - filter; - filter = (mime_filter_t *)cupsArrayNext(filters)) - if (!strcmp(filter->filter, "-")) - cupsArrayRemove(filters, filter); + for (i = pc->num_presets[print_color_mode][print_quality], + preset = pc->presets[print_color_mode][print_quality]; + i > 0; + i --, preset ++) + { + if (!ippFindAttribute(job->attrs, preset->name, IPP_TAG_ZERO)) + num_pwgppds = cupsAddOption(preset->name, preset->value, num_pwgppds, + &pwgppds); + } + } + } - if (cupsArrayCount(filters) == 0) + if (pc) + { + if (!ippFindAttribute(job->attrs, "InputSlot", IPP_TAG_ZERO) && + !ippFindAttribute(job->attrs, "HPPaperSource", IPP_TAG_ZERO)) { - cupsArrayDelete(filters); - filters = NULL; + if ((ppd = _ppdCacheGetInputSlot(pc, job->attrs, NULL)) != NULL) + num_pwgppds = cupsAddOption(pc->source_option, ppd, num_pwgppds, + &pwgppds); + else if (!ippFindAttribute(job->attrs, "AP_D_InputSlot", IPP_TAG_ZERO)) + num_pwgppds = cupsAddOption("AP_D_InputSlot", "", num_pwgppds, + &pwgppds); } + if (!ippFindAttribute(job->attrs, "MediaType", IPP_TAG_ZERO) && + (ppd = _ppdCacheGetMediaType(pc, job->attrs, NULL)) != NULL) + num_pwgppds = cupsAddOption("MediaType", ppd, num_pwgppds, &pwgppds); - /* - * If this printer has any pre-filters, insert the required pre-filter - * in the filters array... - */ + if (!ippFindAttribute(job->attrs, "PageRegion", IPP_TAG_ZERO) && + !ippFindAttribute(job->attrs, "PageSize", IPP_TAG_ZERO) && + (ppd = _ppdCacheGetPageSize(pc, job->attrs, NULL, &exact)) != NULL) + { + num_pwgppds = cupsAddOption("PageSize", ppd, num_pwgppds, &pwgppds); + + if (!ippFindAttribute(job->attrs, "media", IPP_TAG_ZERO)) + num_pwgppds = cupsAddOption("media", ppd, num_pwgppds, &pwgppds); + } - if (printer->prefiltertype && filters) + if (!ippFindAttribute(job->attrs, "OutputBin", IPP_TAG_ZERO) && + (attr = ippFindAttribute(job->attrs, "output-bin", + IPP_TAG_ZERO)) != NULL && + (attr->value_tag == IPP_TAG_KEYWORD || + attr->value_tag == IPP_TAG_NAME) && + (ppd = _ppdCacheGetOutputBin(pc, attr->values[0].string.text)) != NULL) { - prefilters = cupsArrayNew(NULL, NULL); + /* + * Map output-bin to OutputBin option... + */ - 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; - } + num_pwgppds = cupsAddOption("OutputBin", ppd, num_pwgppds, &pwgppds); + } - cupsArrayAdd(prefilters, filter); - } + if (pc->sides_option && + !ippFindAttribute(job->attrs, pc->sides_option, IPP_TAG_ZERO) && + (attr = ippFindAttribute(job->attrs, "sides", IPP_TAG_KEYWORD)) != NULL) + { + /* + * Map sides to duplex option... + */ - cupsArrayDelete(filters); - filters = prefilters; + if (!strcmp(attr->values[0].string.text, "one-sided")) + num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_1sided, + num_pwgppds, &pwgppds); + else if (!strcmp(attr->values[0].string.text, "two-sided-long-edge")) + num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_2sided_long, + num_pwgppds, &pwgppds); + else if (!strcmp(attr->values[0].string.text, "two-sided-short-edge")) + num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_2sided_short, + num_pwgppds, &pwgppds); } } /* - * Set a minimum cost of 100 for all jobs so that FilterLimit - * works with raw queues and other low-cost paths. + * Figure out how much room we need... */ - if (job->cost < 100) - job->cost = 100; + newlength = ipp_length(job->attrs); + + for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++) + newlength += 1 + strlen(pwgppd->name) + 1 + strlen(pwgppd->value); /* - * See if the filter cost is too high... + * Then allocate/reallocate the option buffer as needed... */ - if ((FilterLevel + job->cost) > FilterLimit && FilterLevel > 0 && - FilterLimit > 0) + if (newlength == 0) /* This can never happen, but Clang */ + newlength = 1; /* thinks it can... */ + + if (newlength > optlength || !options) { - /* - * Don't print this job quite yet... - */ + if (!options) + optptr = malloc(newlength); + else + optptr = realloc(options, newlength); - cupsArrayDelete(filters); + if (!optptr) + { + cupsdLogJob(job, CUPSD_LOG_CRIT, + "Unable to allocate " CUPS_LLFMT " bytes for option buffer!", + CUPS_LLCAST newlength); + return (NULL); + } - cupsdLogJob(job, CUPSD_LOG_INFO, - "Holding because filter limit has been reached."); - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "start_job: file=%d, cost=%d, level=%d, limit=%d", - job->current_file, job->cost, FilterLevel, - FilterLimit); - return; + options = optptr; + optlength = newlength; } - FilterLevel += job->cost; - /* - * Add decompression/raw filter as needed... + * Now loop through the attributes and convert them to the textual + * representation used by the filters... */ - 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... - */ + optptr = options; + *optptr = '\0'; - if (!filters) - filters = cupsArrayNew(NULL, NULL); + snprintf(title, title_size, "%s-%d", job->printer->name, job->id); + strlcpy(copies, "1", copies_size); - if (!cupsArrayInsert(filters, &gziptoany_filter)) + for (attr = job->attrs->attrs; attr != NULL; attr = attr->next) + { + if (!strcmp(attr->name, "copies") && + attr->value_tag == IPP_TAG_INTEGER) { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Unable to add decompression filter - %s", strerror(errno)); - - cupsArrayDelete(filters); - - job->current_file ++; - - if (job->current_file == job->num_files) - cupsdCancelJob(job, 0, IPP_JOB_ABORTED); + /* + * Don't use the # copies attribute if we are printing the job sheets... + */ - 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)) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Unable to add port monitor - %s", 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) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Too many filters (%d > %d), unable to print!", - 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->progress = 0; - 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) - { - cupsdLogJob(job, CUPSD_LOG_DEBUG, "No job-sheets attribute."); - 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!"); - } - else if (job->job_sheets->num_values == 1) - cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-sheets=%s", - job->job_sheets->values[0].string.text); - else - cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-sheets=%s,%s", - 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; - - cupsdLogJob(job, CUPSD_LOG_DEBUG, "banner_page = %d", 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 || !options) - { - if (optlength == 0) - optptr = malloc(i); - else - optptr = realloc(options, i); - - if (optptr == NULL) - { - cupsdLogJob(job, CUPSD_LOG_CRIT, - "Unable to allocate %d bytes for option buffer!", 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); + if (!banner_page) + snprintf(copies, copies_size, "%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)); + strlcpy(title, attr->values[0].string.text, title_size); else if (attr->group_tag == IPP_TAG_JOB) { /* * Filter out other unwanted attributes... */ - if (attr->value_tag == IPP_TAG_MIMETYPE || + if (attr->value_tag == IPP_TAG_NOVALUE || + attr->value_tag == IPP_TAG_MIMETYPE || attr->value_tag == IPP_TAG_NAMELANG || attr->value_tag == IPP_TAG_TEXTLANG || (attr->value_tag == IPP_TAG_URI && strcmp(attr->name, "job-uuid")) || @@ -2891,14 +3435,15 @@ start_job(cupsd_job_t *job, /* I - Job ID */ attr->value_tag == IPP_TAG_BEGIN_COLLECTION) /* Not yet supported */ continue; - if (!strncmp(attr->name, "time-", 5)) + if (!strcmp(attr->name, "job-hold-until")) continue; if (!strncmp(attr->name, "job-", 4) && - strcmp(attr->name, "job-uuid") && + strcmp(attr->name, "job-billing") && strcmp(attr->name, "job-impressions") && strcmp(attr->name, "job-originating-host-name") && - !(printer->type & CUPS_PRINTER_REMOTE)) + strcmp(attr->name, "job-uuid") && + !(job->printer->type & CUPS_PRINTER_REMOTE)) continue; if ((!strcmp(attr->name, "job-impressions") || @@ -2907,11 +3452,11 @@ start_job(cupsd_job_t *job, /* I - Job ID */ !strncmp(attr->name, "number-up", 9) || !strcmp(attr->name, "page-ranges") || !strcmp(attr->name, "page-set") || - !strcasecmp(attr->name, "AP_FIRSTPAGE_InputSlot") || - !strcasecmp(attr->name, "AP_FIRSTPAGE_ManualFeed") || - !strcasecmp(attr->name, "com.apple.print.PrintSettings." + !_cups_strcasecmp(attr->name, "AP_FIRSTPAGE_InputSlot") || + !_cups_strcasecmp(attr->name, "AP_FIRSTPAGE_ManualFeed") || + !_cups_strcasecmp(attr->name, "com.apple.print.PrintSettings." "PMTotalSidesImaged..n.") || - !strcasecmp(attr->name, "com.apple.print.PrintSettings." + !_cups_strcasecmp(attr->name, "com.apple.print.PrintSettings." "PMTotalBeginPages..n.")) && banner_page) continue; @@ -2948,7 +3493,6 @@ start_job(cupsd_job_t *job, /* I - Job ID */ if (!attr->values[i].boolean) strlcat(optptr, "no", optlength - (optptr - options)); - case IPP_TAG_NOVALUE : strlcat(optptr, attr->name, optlength - (optptr - options)); break; @@ -2998,564 +3542,743 @@ start_job(cupsd_job_t *job, /* I - Job ID */ } /* - * 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. + * Finally loop through the PWG->PPD mapped options and add them... */ - if (printer->remote && job->num_files > 1) - argv = calloc(7 + job->num_files, sizeof(char *)); - else - argv = calloc(8, sizeof(char *)); - - if (!argv) + for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++) { - cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to allocate argument array!"); - cupsArrayDelete(filters); + *optptr++ = ' '; + strcpy(optptr, pwgppd->name); + optptr += strlen(optptr); + *optptr++ = '='; + strcpy(optptr, pwgppd->value); + optptr += strlen(optptr); + } - FilterLevel -= job->cost; + cupsFreeOptions(num_pwgppds, pwgppds); - cupsdStopPrinter(printer, 0); - return; - } + /* + * Return the options string... + */ - sprintf(jobid, "%d", job->id); + return (options); +} - 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) +/* + * 'ipp_length()' - Compute the size of the buffer needed to hold + * the textual IPP attributes. + */ + +static size_t /* O - Size of attribute buffer */ +ipp_length(ipp_t *ipp) /* I - IPP request */ +{ + size_t 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) { - for (i = 0; i < job->num_files; i ++) + /* + * Skip attributes that won't be sent to filters... + */ + + if (attr->value_tag == IPP_TAG_NOVALUE || + attr->value_tag == IPP_TAG_MIMETYPE || + attr->value_tag == IPP_TAG_NAMELANG || + attr->value_tag == IPP_TAG_TEXTLANG || + attr->value_tag == IPP_TAG_URI || + attr->value_tag == IPP_TAG_URISCHEME) + continue; + + /* + * 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) { - snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, - job->id, i + 1); - argv[6 + i] = strdup(filename); + 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 */ } } - else + + 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 = cupsdOpenConfFile(filename)) == NULL) { - snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, - job->id, job->current_file + 1); - argv[6] = filename; + load_request_root(); + return; } - for (i = 0; argv[i]; i ++) - cupsdLogJob(job, CUPSD_LOG_DEBUG, - "argv[%d]=\"%s\"", i, argv[i]); - /* - * Create environment variable strings for the filters... + * Read entries from the job cache file and create jobs as needed. */ - attr = ippFindAttribute(job->attrs, "attributes-natural-language", - IPP_TAG_LANGUAGE); + cupsdLogMessage(CUPSD_LOG_INFO, "Loading job cache file \"%s\"...", + filename); + + linenum = 0; + job = NULL; + + while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) + { + if (!_cups_strcasecmp(line, "NextJobId")) + { + if (value) + NextJobId = atoi(value); + } + else if (!_cups_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)) + { + snprintf(jobfile, sizeof(jobfile), "%s/c%05d.N", RequestRoot, jobid); + if (access(jobfile, 0)) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Files have gone away!", + jobid); + continue; + } + } + + job = calloc(1, sizeof(cupsd_job_t)); + if (!job) + { + cupsdLogMessage(CUPSD_LOG_EMERG, + "[Job %d] Unable to allocate memory for job!", jobid); + 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 (!_cups_strcasecmp(line, "")) + { + cupsArrayAdd(Jobs, job); + + if (job->state_value <= IPP_JOB_STOPPED && cupsdLoadJob(job)) + cupsArrayAdd(ActiveJobs, job); + + job = NULL; + } + else if (!value) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d!", linenum); + continue; + } + else if (!_cups_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 (!_cups_strcasecmp(line, "HoldUntil")) + { + job->hold_until = atoi(value); + } + else if (!_cups_strcasecmp(line, "Priority")) + { + job->priority = atoi(value); + } + else if (!_cups_strcasecmp(line, "Username")) + { + cupsdSetString(&job->username, value); + } + else if (!_cups_strcasecmp(line, "Destination")) + { + cupsdSetString(&job->dest, value); + } + else if (!_cups_strcasecmp(line, "DestType")) + { + job->dtype = (cups_ptype_t)atoi(value); + } + else if (!_cups_strcasecmp(line, "NumFiles")) + { + job->num_files = atoi(value); + + if (job->num_files < 0) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Bad NumFiles value %d on line %d!", + job->num_files, linenum); + 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 (!_cups_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; + } -#ifdef __APPLE__ - strcpy(apple_language, "APPLE_LANGUAGE="); - _cupsAppleLanguage(attr->values[0].string.text, - apple_language + 15, sizeof(apple_language) - 15); -#endif /* __APPLE__ */ + if (number < 1 || number > job->num_files) + { + cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File number %d on line %d!", + number, linenum); + continue; + } - switch (strlen(attr->values[0].string.text)) - { - default : - /* - * This is an unknown or badly formatted language code; use - * the POSIX locale... - */ + number --; - strcpy(lang, "LANG=C"); - break; + job->compressions[number] = compression; + job->filetypes[number] = mimeType(MimeDatabase, super, type); - case 2 : + if (!job->filetypes[number]) + { /* - * Just the language code (ll)... + * If the original MIME type is unknown, auto-type it! */ - snprintf(lang, sizeof(lang), "LANG=%s.UTF8", - attr->values[0].string.text); - break; + 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); - case 5 : /* - * Language and country code (ll-cc)... + * If that didn't work, assume it is raw... */ - 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); + 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); } - 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); + cupsFileClose(fp); +} - 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; +/* + * 'load_next_job_id()' - Load the NextJobId value from the job.cache file. + */ - if (!printer->remote && !printer->raw) - { - filter = (mime_filter_t *)cupsArrayLast(filters); +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 */ - 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; - } - } + /* + * Read the NextJobId directive from the job.cache file and use + * the value (if any). + */ - if (Classification && !banner_page) + if ((fp = cupsFileOpen(filename, "r")) == NULL) { - 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); + if (errno != ENOENT) + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to open job cache file \"%s\": %s", + filename, strerror(errno)); - envp[envc ++] = classification; + return; } - if (job->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT)) + cupsdLogMessage(CUPSD_LOG_INFO, + "Loading NextJobId from job cache file \"%s\"...", filename); + + linenum = 0; + + while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) { - snprintf(class_name, sizeof(class_name), "CLASS=%s", job->dest); - envp[envc ++] = class_name; + if (!_cups_strcasecmp(line, "NextJobId")) + { + if (value) + { + next_job_id = atoi(value); + + if (next_job_id > NextJobId) + NextJobId = next_job_id; + } + break; + } } - 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; + cupsFileClose(fp); +} -#ifdef HAVE_GSSAPI - if (job->ccname) - envp[envc ++] = job->ccname; -#endif /* HAVE_GSSAPI */ - envp[envc] = NULL; +/* + * 'load_request_root()' - Load jobs from the RequestRoot directory. + */ - for (i = 0; i < envc; i ++) - if (!strncmp(envp[i], "AUTH_", 5)) - cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"AUTH_%c****\"", i, - envp[i][5]); - else if (strncmp(envp[i], "DEVICE_URI=", 11)) - cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"%s\"", i, envp[i]); - else - cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"DEVICE_URI=%s\"", i, - sani_uri); +static void +load_request_root(void) +{ + cups_dir_t *dir; /* Directory */ + cups_dentry_t *dent; /* Directory entry */ + cupsd_job_t *job; /* New job */ - if (printer->remote) - job->current_file = job->num_files; - else - job->current_file ++; /* - * Now create processes for all of the filters... + * Open the requests directory... */ - filterfds[0][0] = -1; - filterfds[0][1] = -1; - filterfds[1][0] = -1; - filterfds[1][1] = -1; + cupsdLogMessage(CUPSD_LOG_DEBUG, "Scanning %s for jobs...", RequestRoot); - if (!job->status_buffer) + if ((dir = cupsDirOpen(RequestRoot)) == NULL) { - if (cupsdOpenPipe(job->status_pipes)) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Unable to create job status pipes - %s.", 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; - } - - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "start_job: status_pipes = [ %d %d ]", - job->status_pipes[0], job->status_pipes[1]); - - job->status_buffer = cupsdStatBufNew(job->status_pipes[0], NULL); - job->status_level = CUPSD_LOG_INFO; + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to open spool directory \"%s\": %s", + RequestRoot, strerror(errno)); + return; } - 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)); + /* + * Read all the c##### files... + */ - if (i < (cupsArrayCount(filters) - 1)) + while ((dent = cupsDirRead(dir)) != NULL) + if (strlen(dent->filename) >= 6 && dent->filename[0] == 'c') { - if (cupsdOpenPipe(filterfds[slot])) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Unable to create job filter pipes - %s.", 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."); + /* + * Allocate memory for the job... + */ - goto abort_job; - } - } - else - { - if (job->current_file == 1) + if ((job = calloc(sizeof(cupsd_job_t), 1)) == NULL) { - if (strncmp(printer->device_uri, "file:", 5) != 0) - { - if (cupsdOpenPipe(job->print_pipes)) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Unable to create job backend pipes - %s.", - strerror(errno)); - snprintf(printer->state_message, sizeof(printer->state_message), - "Unable to create backend pipes - %s.", strerror(errno)); - cupsdAddPrinterHistory(printer); + cupsdLogMessage(CUPSD_LOG_ERROR, "Ran out of memory for jobs!"); + cupsDirClose(dir); + return; + } - cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, - "Job canceled because the server could not create " - "the backend pipes."); + /* + * Assign the job ID... + */ - 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); + 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->print_pipes[1] < 0) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, - "Unable to open output file \"%s\" - %s.", - 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)); + if (job->id >= NextJobId) + NextJobId = job->id + 1; - cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, - "Job canceled because the server could not open the " - "output file."); + /* + * Load the job... + */ - goto abort_job; - } + if (cupsdLoadJob(job)) + { + /* + * Insert the job into the array, sorting by job priority and ID... + */ - fcntl(job->print_pipes[1], F_SETFD, - fcntl(job->print_pipes[1], F_GETFD) | FD_CLOEXEC); - } - } + cupsArrayAdd(Jobs, job); - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "start_job: print_pipes = [ %d %d ]", - job->print_pipes[0], job->print_pipes[1]); + if (job->state_value <= IPP_JOB_STOPPED) + cupsArrayAdd(ActiveJobs, job); + else + unload_job(job); } - - filterfds[slot][0] = job->print_pipes[0]; - filterfds[slot][1] = job->print_pipes[1]; } - cupsdLogJob(job, CUPSD_LOG_DEBUG2, "start_job: filter=\"%s\"", command); - cupsdLogJob(job, CUPSD_LOG_DEBUG2, "start_job: filterfds[%d]=[ %d %d ]", - slot, filterfds[slot][0], filterfds[slot][1]); + cupsDirClose(dir); +} - 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); - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "start_job: Closing filter pipes for slot %d [ %d %d ]...", - !slot, filterfds[!slot][0], filterfds[!slot][1]); +/* + * 'set_time()' - Set one of the "time-at-xyz" attributes. + */ - cupsdClosePipe(filterfds[!slot]); +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 (pid == 0) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, "Unable to start filter \"%s\" - %s.", - filter->filter, strerror(errno)); - snprintf(printer->state_message, sizeof(printer->state_message), - "Unable to start filter \"%s\" - %s.", - filter->filter, strerror(errno)); - cupsdAddPrinterHistory(printer); + if ((attr = ippFindAttribute(job->attrs, name, IPP_TAG_ZERO)) != NULL) + { + attr->value_tag = IPP_TAG_INTEGER; + attr->values[0].integer = time(NULL); + } +} - cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, - "Job canceled because the server could not execute a " - "filter."); - goto abort_job; - } +/* + * 'start_job()' - Start a print job. + */ - cupsdLogJob(job, CUPSD_LOG_INFO, "Started filter %s (PID %d)", command, - pid); +static void +start_job(cupsd_job_t *job, /* I - Job ID */ + cupsd_printer_t *printer) /* I - Printer to print job */ +{ + cupsdLogMessage(CUPSD_LOG_DEBUG2, "start_job(job=%p(%d), printer=%p(%s))", + job, job->id, printer, printer->name); - argv[6] = NULL; - slot = !slot; - } + /* + * Make sure we have some files around before we try to print... + */ - cupsArrayDelete(filters); - filters = NULL; + if (job->num_files == 0) + { + cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_DEFAULT, + "Aborting job because it has no files."); + return; + } /* - * Finally, pipe the final output into a backend process if needed... + * Update the printer and job state to "processing"... */ - 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); + if (!cupsdLoadJob(job)) + return; - /* - * See if the backend needs to run as root... - */ + if (job->printer_message) + cupsdSetString(&(job->printer_message->values[0].string.text), ""); - if (RunUser) - backroot = 0; - else if (stat(command, &backinfo)) - backroot = 0; - else - backroot = !(backinfo.st_mode & (S_IRWXG | S_IRWXO)); + cupsdSetJobState(job, IPP_JOB_PROCESSING, CUPSD_JOB_DEFAULT, NULL); + cupsdSetPrinterState(printer, IPP_PRINTER_PROCESSING, 0); + cupsdSetPrinterReasons(printer, "-cups-remote-pending," + "cups-remote-pending-held," + "cups-remote-processing," + "cups-remote-stopped," + "cups-remote-canceled," + "cups-remote-aborted," + "cups-remote-completed"); + + job->cost = 0; + job->current_file = 0; + job->progress = 0; + job->printer = printer; + printer->job = job; - argv[0] = sani_uri; + /* + * Setup the last exit status and security profiles... + */ - filterfds[slot][0] = -1; - filterfds[slot][1] = -1; + job->status = 0; + job->profile = cupsdCreateProfile(job->id); - cupsdLogJob(job, CUPSD_LOG_DEBUG2, "start_job: backend=\"%s\"", command); - cupsdLogJob(job, CUPSD_LOG_DEBUG2, "start_job: filterfds[%d] = [ %d %d ]", - slot, filterfds[slot][0], filterfds[slot][1]); + /* + * Create the status pipes and buffer... + */ - 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 (cupsdOpenPipe(job->status_pipes)) + { + cupsdLogJob(job, CUPSD_LOG_DEBUG, + "Unable to create job status pipes - %s.", strerror(errno)); - if (pid == 0) - { - cupsdLogJob(job, CUPSD_LOG_ERROR, "Unable to start backend \"%s\" - %s.", - method, strerror(errno)); - snprintf(printer->state_message, sizeof(printer->state_message), - "Unable to start backend \"%s\" - %s.", method, - strerror(errno)); + cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT, + "Job stopped because the scheduler could not create the " + "job status pipes."); - cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, - "Job canceled because the server could not execute " - "the backend."); + cupsdDestroyProfile(job->profile); + job->profile = NULL; + return; + } - goto abort_job; - } - else - { - cupsdLogJob(job, CUPSD_LOG_INFO, "Started backend %s (PID %d)", - command, pid); - } - } + job->status_buffer = cupsdStatBufNew(job->status_pipes[0], NULL); + job->status_level = CUPSD_LOG_INFO; - if (job->current_file == job->num_files) - { - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "start_job: Closing print pipes [ %d %d ]...", - job->print_pipes[0], job->print_pipes[1]); + /* + * Create the backchannel pipes and make them non-blocking... + */ - cupsdClosePipe(job->print_pipes); + if (cupsdOpenPipe(job->back_pipes)) + { + cupsdLogJob(job, CUPSD_LOG_DEBUG, + "Unable to create back-channel pipes - %s.", strerror(errno)); - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "start_job: Closing back pipes [ %d %d ]...", - job->back_pipes[0], job->back_pipes[1]); + cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT, + "Job stopped because the scheduler could not create the " + "back-channel pipes."); - cupsdClosePipe(job->back_pipes); + cupsdClosePipe(job->status_pipes); + cupsdStatBufDelete(job->status_buffer); + job->status_buffer = NULL; - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "start_job: Closing side pipes [ %d %d ]...", - job->side_pipes[0], job->side_pipes[1]); + cupsdDestroyProfile(job->profile); + job->profile = NULL; + return; + } - cupsdClosePipe(job->side_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); - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "start_job: Closing status output pipe %d...", - job->status_pipes[1]); + /* + * Create the side-channel pipes and make them non-blocking... + */ - close(job->status_pipes[1]); - job->status_pipes[1] = -1; - } - } - else + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, job->side_pipes)) { - filterfds[slot][0] = -1; - filterfds[slot][1] = -1; + cupsdLogJob(job, CUPSD_LOG_DEBUG, + "Unable to create side-channel pipes - %s.", strerror(errno)); - if (job->current_file == job->num_files) - { - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "start_job: Closing print pipes [ %d %d ]...", - job->print_pipes[0], job->print_pipes[1]); + cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT, + "Job stopped because the scheduler could not create the " + "side-channel pipes."); - cupsdClosePipe(job->print_pipes); + cupsdClosePipe(job->back_pipes); - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "start_job: Closing status output pipe %d...", - job->status_pipes[1]); + cupsdClosePipe(job->status_pipes); + cupsdStatBufDelete(job->status_buffer); + job->status_buffer = NULL; - close(job->status_pipes[1]); - job->status_pipes[1] = -1; - } + cupsdDestroyProfile(job->profile); + job->profile = NULL; + return; } - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "start_job: Closing filter pipes for slot %d [ %d %d ]...", - slot, filterfds[slot][0], filterfds[slot][1]); - cupsdClosePipe(filterfds[slot]); + 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); - if (printer->remote && job->num_files > 1) - { - for (i = 0; i < job->num_files; i ++) - free(argv[i + 6]); - } + fcntl(job->side_pipes[0], F_SETFD, + fcntl(job->side_pipes[0], F_GETFD) | FD_CLOEXEC); + fcntl(job->side_pipes[1], F_SETFD, + fcntl(job->side_pipes[1], F_GETFD) | FD_CLOEXEC); - free(argv); + /* + * Now start the first file in the job... + */ - cupsdAddSelect(job->status_buffer->fd, (cupsd_selfunc_t)update_job, NULL, - job); + cupsdContinueJob(job); +} - cupsdAddEvent(CUPSD_EVENT_JOB_STATE, job->printer, job, "Job #%d started.", - job->id); - return; +/* + * 'stop_job()' - Stop a print job. + */ +static void +stop_job(cupsd_job_t *job, /* I - Job */ + cupsd_jobaction_t action) /* I - Action */ +{ + int i; /* Looping var */ - /* - * If we get here, we need to abort the current job and close out all - * files and pipes... - */ - abort_job: + cupsdLogMessage(CUPSD_LOG_DEBUG2, "stop_job(job=%p(%d), action=%d)", job, + job->id, action); - for (slot = 0; slot < 2; slot ++) - { - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "start_job: Closing filter pipes for slot %d [ %d %d ]...", - slot, filterfds[slot][0], filterfds[slot][1]); - cupsdClosePipe(filterfds[slot]); - } + FilterLevel -= job->cost; + job->cost = 0; - cupsdLogJob(job, CUPSD_LOG_DEBUG2, - "start_job: Closing status pipes [ %d %d ]...", - job->status_pipes[0], job->status_pipes[1]); - cupsdClosePipe(job->status_pipes); - cupsdStatBufDelete(job->status_buffer); + if (action == CUPSD_JOB_DEFAULT && !job->kill_time) + job->kill_time = time(NULL) + JobKillDelay; + else if (action >= CUPSD_JOB_FORCE) + job->kill_time = 0; - job->status_buffer = NULL; + for (i = 0; job->filters[i]; i ++) + if (job->filters[i] > 0) + { + cupsdEndProcess(job->filters[i], action >= CUPSD_JOB_FORCE); - cupsArrayDelete(filters); + if (action >= CUPSD_JOB_FORCE) + job->filters[i] = -job->filters[i]; + } - if (printer->remote && job->num_files > 1) + if (job->backend > 0) { - for (i = 0; i < job->num_files; i ++) - free(argv[i + 6]); + cupsdEndProcess(job->backend, action >= CUPSD_JOB_FORCE); + + if (action >= CUPSD_JOB_FORCE) + job->backend = -job->backend; } - free(argv); + if (action >= CUPSD_JOB_FORCE) + { + /* + * Clear job status... + */ - cupsdStopJob(job, 0); + job->status = 0; + } } @@ -3569,7 +4292,7 @@ unload_job(cupsd_job_t *job) /* I - Job */ if (!job->attrs) return; - cupsdLogJob(job, CUPSD_LOG_DEBUG, "Unloading..."); + cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job %d] Unloading...", job->id); ippDelete(job->attrs); @@ -3591,12 +4314,32 @@ update_job(cupsd_job_t *job) /* I - Job to check */ { int i; /* Looping var */ int copies; /* Number of copies printed */ - char message[1024], /* Message text */ + char message[CUPSD_SB_BUFFER_SIZE], + /* Message text */ *ptr; /* Pointer update... */ int loglevel, /* Log level for message */ event = 0; /* Events? */ + static const char * const levels[] = /* Log levels */ + { + "NONE", + "EMERG", + "ALERT", + "CRIT", + "ERROR", + "WARN", + "NOTICE", + "INFO", + "DEBUG", + "DEBUG2" + }; + /* + * Get the printer associated with this job; if the printer is stopped for + * any reason then job->printer will be reset to NULL, so make sure we have + * a valid pointer... + */ + while ((ptr = cupsdStatBufUpdate(job->status_buffer, &loglevel, message, sizeof(message))) != NULL) { @@ -3615,7 +4358,7 @@ update_job(cupsd_job_t *job) /* I - Job to check */ if (job->sheets) { - if (!strncasecmp(message, "total ", 6)) + if (!_cups_strncasecmp(message, "total ", 6)) { /* * Got a total count of pages from a backend or filter... @@ -3630,30 +4373,7 @@ update_job(cupsd_job_t *job) /* I - Job to check */ job->sheets->values[0].integer += copies; if (job->printer->page_limit) - { - cupsd_quota_t *q = cupsdUpdateQuota(job->printer, job->username, - copies, 0); - -#ifdef __APPLE__ - if (AppleQuotas && q->page_count == -3) - { - /* - * Quota limit exceeded, cancel job in progress immediately... - */ - - cupsdLogJob(job, CUPSD_LOG_INFO, - "Canceled because pages exceed user %s " - "quota limit on printer %s (%s).", - job->username, job->printer->name, - job->printer->info); - - cupsdCancelJob(job, 1, IPP_JOB_CANCELED); - return; - } -#else - (void)q; -#endif /* __APPLE__ */ - } + cupsdUpdateQuota(job->printer, job->username, copies, 0); } cupsdLogPage(job, message); @@ -3671,12 +4391,8 @@ update_job(cupsd_job_t *job) /* I - Job to check */ cupsdStopPrinter(job->printer, 1); return; } - else - { - cupsdSetPrinterReasons(job->printer, message); - cupsdAddPrinterHistory(job->printer); + else if (cupsdSetPrinterReasons(job->printer, message)) event |= CUPSD_EVENT_PRINTER_STATE; - } update_job_attrs(job, 0); } @@ -3742,6 +4458,7 @@ update_job(cupsd_job_t *job) /* I - Job to check */ cupsdSetPrinterAttr(job->printer, "marker-colors", (char *)attr); job->printer->marker_time = time(NULL); event |= CUPSD_EVENT_PRINTER_STATE; + cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); } if ((attr = cupsGetOption("marker-levels", num_attrs, attrs)) != NULL) @@ -3749,6 +4466,23 @@ update_job(cupsd_job_t *job) /* I - Job to check */ cupsdSetPrinterAttr(job->printer, "marker-levels", (char *)attr); job->printer->marker_time = time(NULL); event |= CUPSD_EVENT_PRINTER_STATE; + cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); + } + + if ((attr = cupsGetOption("marker-low-levels", num_attrs, attrs)) != NULL) + { + cupsdSetPrinterAttr(job->printer, "marker-low-levels", (char *)attr); + job->printer->marker_time = time(NULL); + event |= CUPSD_EVENT_PRINTER_STATE; + cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); + } + + if ((attr = cupsGetOption("marker-high-levels", num_attrs, attrs)) != NULL) + { + cupsdSetPrinterAttr(job->printer, "marker-high-levels", (char *)attr); + job->printer->marker_time = time(NULL); + event |= CUPSD_EVENT_PRINTER_STATE; + cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); } if ((attr = cupsGetOption("marker-message", num_attrs, attrs)) != NULL) @@ -3756,6 +4490,7 @@ update_job(cupsd_job_t *job) /* I - Job to check */ cupsdSetPrinterAttr(job->printer, "marker-message", (char *)attr); job->printer->marker_time = time(NULL); event |= CUPSD_EVENT_PRINTER_STATE; + cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); } if ((attr = cupsGetOption("marker-names", num_attrs, attrs)) != NULL) @@ -3763,6 +4498,7 @@ update_job(cupsd_job_t *job) /* I - Job to check */ cupsdSetPrinterAttr(job->printer, "marker-names", (char *)attr); job->printer->marker_time = time(NULL); event |= CUPSD_EVENT_PRINTER_STATE; + cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); } if ((attr = cupsGetOption("marker-types", num_attrs, attrs)) != NULL) @@ -3770,6 +4506,7 @@ update_job(cupsd_job_t *job) /* I - Job to check */ cupsdSetPrinterAttr(job->printer, "marker-types", (char *)attr); job->printer->marker_time = time(NULL); event |= CUPSD_EVENT_PRINTER_STATE; + cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); } cupsFreeOptions(num_attrs, attrs); @@ -3793,56 +4530,53 @@ update_job(cupsd_job_t *job) /* I - Job to check */ 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)) + else { - cupsdSetPrinterReasons(job->printer, - "-com.apple.print.recoverable-warning"); + /* + * Strip legacy message prefix... + */ - ptr = message + 10; - while (isspace(*ptr & 255)) - ptr ++; + if (!strncmp(message, "recoverable:", 12)) + { + ptr = message + 12; + while (isspace(*ptr & 255)) + ptr ++; + } + else if (!strncmp(message, "recovered:", 10)) + { + ptr = message + 10; + while (isspace(*ptr & 255)) + ptr ++; + } + else + ptr = message; - cupsdSetString(&job->printer->recoverable, ptr); - cupsdAddPrinterHistory(job->printer); - event |= CUPSD_EVENT_PRINTER_STATE; - } -#endif /* __APPLE__ */ - else - { - cupsdLogJob(job, loglevel, "%s", message); + cupsdLogJob(job, loglevel, "%s", ptr); - if (loglevel < CUPSD_LOG_DEBUG) + if (loglevel < CUPSD_LOG_DEBUG && + strcmp(job->printer->state_message, ptr)) { - strlcpy(job->printer->state_message, message, + strlcpy(job->printer->state_message, ptr, sizeof(job->printer->state_message)); - cupsdAddPrinterHistory(job->printer); - event |= CUPSD_EVENT_PRINTER_STATE; + event |= CUPSD_EVENT_PRINTER_STATE | CUPSD_EVENT_JOB_PROGRESS; - if (loglevel <= job->status_level) + if (loglevel <= job->status_level && job->status_level > CUPSD_LOG_ERROR) { /* - * Some messages show in the printer-state-message attribute... + * Some messages show in the job-printer-state-message attribute... */ if (loglevel != CUPSD_LOG_NOTICE) job->status_level = loglevel; update_job_attrs(job, 1); + + cupsdLogJob(job, CUPSD_LOG_DEBUG, + "Set job-printer-state-message to \"%s\", " + "current level=%s", + job->printer_message->values[0].string.text, + levels[job->status_level]); } } } @@ -3851,6 +4585,9 @@ update_job(cupsd_job_t *job) /* I - Job to check */ break; } + if (event & CUPSD_EVENT_JOB_PROGRESS) + cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job, + "%s", job->printer->state_message); if (event & CUPSD_EVENT_PRINTER_STATE) cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, job->printer, NULL, (job->printer->type & CUPS_PRINTER_CLASS) ? @@ -3858,6 +4595,7 @@ update_job(cupsd_job_t *job) /* I - Job to check */ "Printer \"%s\" state changed.", job->printer->name); + if (ptr == NULL && !job->status_buffer->bufused) { /* @@ -3868,16 +4606,36 @@ update_job(cupsd_job_t *job) /* I - Job to check */ for (i = 0; job->filters[i] < 0; i ++); if (job->filters[i]) + { + /* + * EOF but we haven't collected the exit status of all filters... + */ + + cupsdCheckProcess(); return; + } if (job->current_file >= job->num_files && job->backend > 0) + { + /* + * EOF but we haven't collected the exit status of the backend... + */ + + cupsdCheckProcess(); return; + } /* * Handle the end of job stuff... */ - cupsdFinishJob(job); + finalize_job(job, 1); + + /* + * Check for new jobs... + */ + + cupsdCheckJobs(); } } @@ -3888,14 +4646,12 @@ update_job(cupsd_job_t *job) /* I - Job to check */ void update_job_attrs(cupsd_job_t *job, /* I - Job to update */ - int do_message)/* I - 1 = update job-printer-state message */ + int do_message)/* I - 1 = copy job-printer-state message */ { 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" */ + static const char *none = "none"; /* "none" reason */ /* @@ -3918,18 +4674,13 @@ update_job_attrs(cupsd_job_t *job, /* I - Job to update */ IPP_TAG_KEYWORD); /* - * If the job isn't printing, return now... + * Copy or clear the printer-state-message value as needed... */ - if (!job->printer) - return; - - /* - * Otherwise copy the printer-state-message value... - */ - - if (job->printer->state_message[0] && - (do_message || !job->printer_message->values[0].string.text[0])) + if (job->state_value != IPP_JOB_PROCESSING && + job->status_level == CUPSD_LOG_INFO) + cupsdSetString(&(job->printer_message->values[0].string.text), ""); + else if (job->printer->state_message[0] && do_message) cupsdSetString(&(job->printer_message->values[0].string.text), job->printer->state_message); @@ -3940,7 +4691,7 @@ update_job_attrs(cupsd_job_t *job, /* I - Job to update */ if (job->printer->num_reasons == 0) { num_reasons = 1; - reasons = job->printer->state == IPP_PRINTER_STOPPED ? &paused : &none; + reasons = &none; } else { @@ -3950,6 +4701,10 @@ update_job_attrs(cupsd_job_t *job, /* I - Job to update */ if (!job->printer_reasons || job->printer_reasons->num_values != num_reasons) { + /* + * Replace/create a job-printer-state-reasons attribute... + */ + ippDeleteAttribute(job->attrs, job->printer_reasons); job->printer_reasons = ippAddStrings(job->attrs, @@ -3957,12 +4712,36 @@ update_job_attrs(cupsd_job_t *job, /* I - Job to update */ "job-printer-state-reasons", num_reasons, NULL, NULL); } + else + { + /* + * Don't bother clearing the reason strings if they are the same... + */ + + for (i = 0; i < num_reasons; i ++) + if (strcmp(job->printer_reasons->values[i].string.text, reasons[i])) + break; + + if (i >= num_reasons) + return; + + /* + * Not the same, so free the current strings... + */ + + for (i = 0; i < num_reasons; i ++) + _cupsStrFree(job->printer_reasons->values[i].string.text); + } + + /* + * Copy the reasons... + */ for (i = 0; i < num_reasons; i ++) - cupsdSetString(&(job->printer_reasons->values[i].string.text), reasons[i]); + job->printer_reasons->values[i].string.text = _cupsStrAlloc(reasons[i]); } /* - * End of "$Id: job.c 7682 2008-06-21 00:06:02Z mike $". + * End of "$Id: job.c 7902 2008-09-03 14:20:17Z mike $". */