+ FilterLevel -= job->cost;
+
+ if (job->printer->state == IPP_PRINTER_PROCESSING)
+ cupsdSetPrinterState(job->printer, IPP_PRINTER_IDLE, 0);
+
+ job->state->values[0].integer = IPP_JOB_STOPPED;
+ job->state_value = IPP_JOB_STOPPED;
+ job->printer->job = NULL;
+ job->printer = NULL;
+
+ job->current_file --;
+
+ for (i = 0; job->filters[i]; i ++)
+ if (job->filters[i] > 0)
+ {
+ cupsdEndProcess(job->filters[i], force);
+ job->filters[i] = 0;
+ }
+
+ if (job->backend > 0)
+ {
+ cupsdEndProcess(job->backend, force);
+ job->backend = 0;
+ }
+
+ cupsdDestroyProfile(job->profile);
+ job->profile = NULL;
+
+ cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Closing print pipes [ %d %d ]...",
+ job->print_pipes[0], job->print_pipes[1]);
+
+ cupsdClosePipe(job->print_pipes);
+
+ cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Closing back pipes [ %d %d ]...",
+ job->back_pipes[0], job->back_pipes[1]);
+
+ cupsdClosePipe(job->back_pipes);
+
+ cupsdLogJob(job, CUPSD_LOG_DEBUG2, "Closing side pipes [ %d %d ]...",
+ job->side_pipes[0], job->side_pipes[1]);
+
+ cupsdClosePipe(job->side_pipes);
+
+ if (job->status_buffer)
+ {
+ /*
+ * Close the pipe and clear the input bit.
+ */
+
+ cupsdRemoveSelect(job->status_buffer->fd);
+
+ cupsdLogJob(job, CUPSD_LOG_DEBUG2, "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;
+ }
+}
+
+
+/*
+ * 'cupsdUnloadCompletedJobs()' - Flush completed job history from memory.
+ */
+
+void
+cupsdUnloadCompletedJobs(void)
+{
+ cupsd_job_t *job; /* Current job */
+ time_t expire; /* Expiration time */
+
+
+ expire = time(NULL) - 60;
+
+ for (job = (cupsd_job_t *)cupsArrayFirst(Jobs);
+ job;
+ job = (cupsd_job_t *)cupsArrayNext(Jobs))
+ if (job->attrs && job->state_value >= IPP_JOB_STOPPED &&
+ job->access_time < expire)
+ {
+ if (job->dirty)
+ cupsdSaveJob(job);
+
+ unload_job(job);
+ }
+}
+
+
+/*
+ * 'compare_active_jobs()' - Compare the job IDs and priorities of two jobs.
+ */
+
+static int /* O - Difference */
+compare_active_jobs(void *first, /* I - First job */
+ void *second, /* I - Second job */
+ void *data) /* I - App data (not used) */
+{
+ int diff; /* Difference */
+
+
+ if ((diff = ((cupsd_job_t *)second)->priority -
+ ((cupsd_job_t *)first)->priority) != 0)
+ return (diff);
+ else
+ return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
+}
+
+
+/*
+ * 'compare_jobs()' - Compare the job IDs of two jobs.
+ */
+
+static int /* O - Difference */
+compare_jobs(void *first, /* I - First job */
+ void *second, /* I - Second job */
+ void *data) /* I - App data (not used) */
+{
+ return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id);
+}
+
+
+/*
+ * 'ipp_length()' - Compute the size of the buffer needed to hold
+ * the textual IPP attributes.
+ */
+
+static int /* O - Size of attribute buffer */
+ipp_length(ipp_t *ipp) /* I - IPP request */
+{
+ int bytes; /* Number of bytes */
+ int i; /* Looping var */
+ ipp_attribute_t *attr; /* Current attribute */
+
+
+ /*
+ * Loop through all attributes...
+ */
+
+ bytes = 0;
+
+ for (attr = ipp->attrs; attr != NULL; attr = attr->next)
+ {
+ /*
+ * Skip attributes that won't be sent to filters...
+ */
+
+ if (attr->value_tag == IPP_TAG_MIMETYPE ||
+ attr->value_tag == IPP_TAG_NAMELANG ||
+ attr->value_tag == IPP_TAG_TEXTLANG ||
+ attr->value_tag == IPP_TAG_URI ||
+ attr->value_tag == IPP_TAG_URISCHEME)
+ continue;
+
+ if (strncmp(attr->name, "time-", 5) == 0)
+ continue;
+
+ /*
+ * Add space for a leading space and commas between each value.
+ * For the first attribute, the leading space isn't used, so the
+ * extra byte can be used as the nul terminator...
+ */
+
+ bytes ++; /* " " separator */
+ bytes += attr->num_values; /* "," separators */
+
+ /*
+ * Boolean attributes appear as "foo,nofoo,foo,nofoo", while
+ * other attributes appear as "foo=value1,value2,...,valueN".
+ */
+
+ if (attr->value_tag != IPP_TAG_BOOLEAN)
+ bytes += strlen(attr->name);
+ else
+ bytes += attr->num_values * strlen(attr->name);
+
+ /*
+ * Now add the size required for each value in the attribute...
+ */
+
+ switch (attr->value_tag)
+ {
+ case IPP_TAG_INTEGER :
+ case IPP_TAG_ENUM :
+ /*
+ * Minimum value of a signed integer is -2147483647, or 11 digits.
+ */
+
+ bytes += attr->num_values * 11;
+ break;
+
+ case IPP_TAG_BOOLEAN :
+ /*
+ * Add two bytes for each false ("no") value...
+ */
+
+ for (i = 0; i < attr->num_values; i ++)
+ if (!attr->values[i].boolean)
+ bytes += 2;
+ break;
+
+ case IPP_TAG_RANGE :
+ /*
+ * A range is two signed integers separated by a hyphen, or
+ * 23 characters max.
+ */
+
+ bytes += attr->num_values * 23;
+ break;
+
+ case IPP_TAG_RESOLUTION :
+ /*
+ * A resolution is two signed integers separated by an "x" and
+ * suffixed by the units, or 26 characters max.
+ */
+
+ bytes += attr->num_values * 26;
+ break;
+
+ case IPP_TAG_STRING :
+ case IPP_TAG_TEXT :
+ case IPP_TAG_NAME :
+ case IPP_TAG_KEYWORD :
+ case IPP_TAG_CHARSET :
+ case IPP_TAG_LANGUAGE :
+ case IPP_TAG_URI :
+ /*
+ * Strings can contain characters that need quoting. We need
+ * at least 2 * len + 2 characters to cover the quotes and
+ * any backslashes in the string.
+ */
+
+ for (i = 0; i < attr->num_values; i ++)
+ bytes += 2 * strlen(attr->values[i].string.text) + 2;
+ break;
+
+ default :
+ break; /* anti-compiler-warning-code */
+ }
+ }
+
+ return (bytes);
+}
+
+
+/*
+ * 'load_job_cache()' - Load jobs from the job.cache file.
+ */
+
+static void
+load_job_cache(const char *filename) /* I - job.cache filename */
+{
+ cups_file_t *fp; /* job.cache file */
+ char line[1024], /* Line buffer */
+ *value; /* Value on line */
+ int linenum; /* Line number in file */
+ cupsd_job_t *job; /* Current job */
+ int jobid; /* Job ID */
+ char jobfile[1024]; /* Job filename */
+
+
+ /*
+ * Open the job.cache file...
+ */
+
+ if ((fp = cupsFileOpen(filename, "r")) == NULL)
+ {
+ if (errno != ENOENT)
+ cupsdLogMessage(CUPSD_LOG_ERROR,
+ "Unable to open job cache file \"%s\": %s",
+ filename, strerror(errno));
+
+ load_request_root();
+
+ return;
+ }
+
+ /*
+ * Read entries from the job cache file and create jobs as needed.
+ */
+
+ cupsdLogMessage(CUPSD_LOG_INFO, "Loading job cache file \"%s\"...",
+ filename);
+
+ linenum = 0;
+ job = NULL;
+
+ while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
+ {
+ if (!strcasecmp(line, "NextJobId"))
+ {
+ if (value)
+ NextJobId = atoi(value);
+ }
+ else if (!strcasecmp(line, "<Job"))
+ {
+ if (job)
+ {
+ cupsdLogMessage(CUPSD_LOG_ERROR, "Missing </Job> directive on line %d!",
+ linenum);
+ continue;
+ }
+
+ if (!value)
+ {
+ cupsdLogMessage(CUPSD_LOG_ERROR, "Missing job ID on line %d!", linenum);
+ continue;
+ }
+
+ jobid = atoi(value);
+
+ if (jobid < 1)
+ {
+ cupsdLogMessage(CUPSD_LOG_ERROR, "Bad job ID %d on line %d!", jobid,
+ linenum);
+ continue;
+ }
+
+ snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, jobid);
+ if (access(jobfile, 0))
+ {
+ cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Files have gone away!",
+ jobid);
+ continue;
+ }
+
+ job = calloc(1, sizeof(cupsd_job_t));
+ if (!job)
+ {
+ cupsdLogMessage(CUPSD_LOG_EMERG,
+ "[Job %d] Unable to allocate memory for job!", jobid);
+ break;
+ }
+
+ job->id = jobid;
+ job->back_pipes[0] = -1;
+ job->back_pipes[1] = -1;
+ job->print_pipes[0] = -1;
+ job->print_pipes[1] = -1;
+ job->side_pipes[0] = -1;
+ job->side_pipes[1] = -1;
+ job->status_pipes[0] = -1;
+ job->status_pipes[1] = -1;
+
+ cupsdLogJob(job, CUPSD_LOG_DEBUG, "Loading from cache...");
+ }
+ else if (!job)
+ {
+ cupsdLogMessage(CUPSD_LOG_ERROR,
+ "Missing <Job #> directive on line %d!", linenum);
+ continue;
+ }
+ else if (!strcasecmp(line, "</Job>"))
+ {
+ cupsArrayAdd(Jobs, job);
+
+ if (job->state_value <= IPP_JOB_STOPPED)
+ {
+ cupsArrayAdd(ActiveJobs, job);
+ cupsdLoadJob(job);
+ }
+
+ job = NULL;
+ }
+ else if (!value)
+ {
+ cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d!", linenum);
+ continue;
+ }
+ else if (!strcasecmp(line, "State"))
+ {
+ job->state_value = (ipp_jstate_t)atoi(value);
+
+ if (job->state_value < IPP_JOB_PENDING)
+ job->state_value = IPP_JOB_PENDING;
+ else if (job->state_value > IPP_JOB_COMPLETED)
+ job->state_value = IPP_JOB_COMPLETED;
+ }
+ else if (!strcasecmp(line, "Priority"))
+ {
+ job->priority = atoi(value);
+ }
+ else if (!strcasecmp(line, "Username"))
+ {
+ cupsdSetString(&job->username, value);
+ }
+ else if (!strcasecmp(line, "Destination"))
+ {
+ cupsdSetString(&job->dest, value);
+ }
+ else if (!strcasecmp(line, "DestType"))
+ {
+ job->dtype = (cups_ptype_t)atoi(value);
+ }
+ else if (!strcasecmp(line, "NumFiles"))
+ {
+ job->num_files = atoi(value);
+
+ if (job->num_files < 0)
+ {
+ cupsdLogMessage(CUPSD_LOG_ERROR, "Bad NumFiles value %d on line %d!",
+ job->num_files, linenum);
+ job->num_files = 0;
+ continue;
+ }
+
+ if (job->num_files > 0)
+ {
+ snprintf(jobfile, sizeof(jobfile), "%s/d%05d-001", RequestRoot,
+ job->id);
+ if (access(jobfile, 0))
+ {
+ cupsdLogJob(job, CUPSD_LOG_INFO, "Data files have gone away!");
+ job->num_files = 0;
+ continue;
+ }
+
+ job->filetypes = calloc(job->num_files, sizeof(mime_type_t *));
+ job->compressions = calloc(job->num_files, sizeof(int));
+
+ 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"))
+ {
+ int number, /* File number */
+ compression; /* Compression value */
+ char super[MIME_MAX_SUPER], /* MIME super type */
+ type[MIME_MAX_TYPE]; /* MIME type */
+
+
+ if (sscanf(value, "%d%*[ \t]%15[^/]/%255s%d", &number, super, type,
+ &compression) != 4)
+ {
+ cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File on line %d!", linenum);
+ continue;
+ }
+
+ if (number < 1 || number > job->num_files)
+ {
+ cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File number %d on line %d!",
+ number, linenum);
+ continue;
+ }
+
+ number --;
+
+ job->compressions[number] = compression;
+ job->filetypes[number] = mimeType(MimeDatabase, super, type);
+
+ if (!job->filetypes[number])
+ {
+ /*
+ * If the original MIME type is unknown, auto-type it!
+ */
+
+ cupsdLogJob(job, CUPSD_LOG_ERROR,
+ "Unknown MIME type %s/%s for file %d!",
+ super, type, number + 1);
+
+ snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot,
+ job->id, number + 1);
+ job->filetypes[number] = mimeFileType(MimeDatabase, jobfile, NULL,
+ job->compressions + number);
+
+ /*
+ * If that didn't work, assume it is raw...
+ */
+
+ if (!job->filetypes[number])
+ job->filetypes[number] = mimeType(MimeDatabase, "application",
+ "vnd.cups-raw");
+ }
+ }
+ else
+ cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown %s directive on line %d!",
+ line, linenum);
+ }
+
+ cupsFileClose(fp);
+}
+
+
+/*
+ * 'load_next_job_id()' - Load the NextJobId value from the job.cache file.
+ */
+
+static void
+load_next_job_id(const char *filename) /* I - job.cache filename */
+{
+ cups_file_t *fp; /* job.cache file */
+ char line[1024], /* Line buffer */
+ *value; /* Value on line */
+ int linenum; /* Line number in file */
+ int next_job_id; /* NextJobId value from line */
+
+
+ /*
+ * Read the NextJobId directive from the job.cache file and use
+ * the value (if any).
+ */
+
+ if ((fp = cupsFileOpen(filename, "r")) == NULL)
+ {
+ if (errno != ENOENT)
+ cupsdLogMessage(CUPSD_LOG_ERROR,
+ "Unable to open job cache file \"%s\": %s",
+ filename, strerror(errno));
+
+ return;
+ }
+
+ cupsdLogMessage(CUPSD_LOG_INFO,
+ "Loading NextJobId from job cache file \"%s\"...", filename);
+
+ linenum = 0;
+
+ while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
+ {
+ if (!strcasecmp(line, "NextJobId"))
+ {
+ if (value)
+ {
+ next_job_id = atoi(value);
+
+ if (next_job_id > NextJobId)
+ NextJobId = next_job_id;
+ }
+ break;
+ }
+ }
+
+ cupsFileClose(fp);
+}
+
+
+/*
+ * 'load_request_root()' - Load jobs from the RequestRoot directory.
+ */
+
+static void
+load_request_root(void)
+{
+ cups_dir_t *dir; /* Directory */
+ cups_dentry_t *dent; /* Directory entry */
+ cupsd_job_t *job; /* New job */
+
+
+ /*
+ * Open the requests directory...
+ */
+
+ cupsdLogMessage(CUPSD_LOG_DEBUG, "Scanning %s for jobs...", RequestRoot);
+
+ if ((dir = cupsDirOpen(RequestRoot)) == NULL)
+ {
+ cupsdLogMessage(CUPSD_LOG_ERROR,
+ "Unable to open spool directory \"%s\": %s",
+ RequestRoot, strerror(errno));
+ return;
+ }
+
+ /*
+ * Read all the c##### files...
+ */
+
+ while ((dent = cupsDirRead(dir)) != NULL)
+ if (strlen(dent->filename) >= 6 && dent->filename[0] == 'c')
+ {
+ /*
+ * Allocate memory for the job...
+ */
+
+ if ((job = calloc(sizeof(cupsd_job_t), 1)) == NULL)
+ {
+ cupsdLogMessage(CUPSD_LOG_ERROR, "Ran out of memory for jobs!");
+ cupsDirClose(dir);
+ return;
+ }
+
+ /*
+ * Assign the job ID...
+ */
+
+ job->id = atoi(dent->filename + 1);
+ job->back_pipes[0] = -1;
+ job->back_pipes[1] = -1;
+ job->print_pipes[0] = -1;
+ job->print_pipes[1] = -1;
+ job->side_pipes[0] = -1;
+ job->side_pipes[1] = -1;
+ job->status_pipes[0] = -1;
+ job->status_pipes[1] = -1;
+
+ if (job->id >= NextJobId)
+ NextJobId = job->id + 1;
+
+ /*
+ * Load the job...
+ */
+
+ cupsdLoadJob(job);
+
+ /*
+ * Insert the job into the array, sorting by job priority and ID...
+ */
+
+ cupsArrayAdd(Jobs, job);
+
+ if (job->state_value <= IPP_JOB_STOPPED)
+ cupsArrayAdd(ActiveJobs, job);
+ else
+ unload_job(job);
+ }
+
+ cupsDirClose(dir);
+}
+
+
+/*
+ * 'set_time()' - Set one of the "time-at-xyz" attributes...
+ */
+
+static void
+set_time(cupsd_job_t *job, /* I - Job to update */
+ const char *name) /* I - Name of attribute */
+{
+ ipp_attribute_t *attr; /* Time attribute */
+
+
+ if ((attr = ippFindAttribute(job->attrs, name, IPP_TAG_ZERO)) != NULL)
+ {
+ attr->value_tag = IPP_TAG_INTEGER;
+ attr->values[0].integer = time(NULL);
+ }
+}
+
+
+/*
+ * 'set_hold_until()' - Set the hold time and update job-hold-until attribute...
+ */
+
+static void
+set_hold_until(cupsd_job_t *job, /* I - Job to update */
+ time_t holdtime) /* I - Hold until time */
+{
+ ipp_attribute_t *attr; /* job-hold-until attribute */
+ struct tm *holddate; /* Hold date */
+ char holdstr[64]; /* Hold time */
+
+
+ /*
+ * Set the hold_until value and hold the job...
+ */
+
+ cupsdLogMessage(CUPSD_LOG_DEBUG, "set_hold_until: hold_until = %d",
+ (int)holdtime);
+
+ job->state->values[0].integer = IPP_JOB_HELD;
+ job->state_value = IPP_JOB_HELD;
+ job->hold_until = holdtime;
+
+ /*
+ * Update the job-hold-until attribute with a string representing GMT
+ * time (HH:MM:SS)...
+ */
+
+ holddate = gmtime(&holdtime);
+ snprintf(holdstr, sizeof(holdstr), "%d:%d:%d", holddate->tm_hour,
+ holddate->tm_min, holddate->tm_sec);
+
+ if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
+ IPP_TAG_KEYWORD)) == NULL)
+ attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
+
+ /*
+ * Either add the attribute or update the value of the existing one
+ */
+
+ if (attr == NULL)
+ ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD, "job-hold-until",
+ NULL, holdstr);
+ else
+ cupsdSetString(&attr->values[0].string.text, holdstr);
+
+ job->dirty = 1;
+ cupsdMarkDirty(CUPSD_DIRTY_JOBS);
+}
+
+
+/*
+ * 'start_job()' - Start a print job.
+ */
+
+static void
+start_job(cupsd_job_t *job, /* I - Job ID */
+ cupsd_printer_t *printer) /* I - Printer to print job */
+{
+ int i; /* Looping var */
+ int slot; /* Pipe slot */
+ cups_array_t *filters, /* Filters for job */
+ *prefilters; /* Filters with prefilters */
+ mime_filter_t *filter, /* Current filter */
+ *prefilter, /* Prefilter */
+ port_monitor; /* Port monitor filter */
+ char method[255], /* Method for output */
+ *optptr, /* Pointer to options */
+ *valptr; /* Pointer in value string */
+ ipp_attribute_t *attr; /* Current attribute */
+ struct stat backinfo; /* Backend file information */
+ int backroot; /* Run backend as root? */
+ int pid; /* Process ID of new filter process */
+ int banner_page; /* 1 if banner page, 0 otherwise */
+ int filterfds[2][2];/* Pipes used between filters */
+ int envc; /* Number of environment variables */
+ char **argv, /* Filter command-line arguments */
+ 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 + 19],
+ /* 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_info[255],
+ /* PRINTER_INFO env variable */
+ printer_location[255],
+ /* PRINTER_LOCATION env variable */
+ printer_name[255],
+ /* PRINTER env variable */
+ rip_max_cache[255];
+ /* RIP_MAX_CACHE env variable */
+ static char *options = NULL;/* Full list of options */
+ static int optlength = 0; /* Length of option buffer */
+
+
+ cupsdLogJob(job, CUPSD_LOG_DEBUG2, "start_job: file = %d/%d",
+ job->current_file, job->num_files);
+
+ 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...
+ */
+
+ filters = NULL;
+ job->cost = 0;
+
+ if (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...");
+
+ filters = NULL;
+ }
+ else
+ {
+ /*
+ * Local jobs get filtered...
+ */