/* foomaticrip.c * * Copyright (C) 2008 Till Kamppeter * Copyright (C) 2008 Lars Uebernickel * * This file is part of foomatic-rip. * * Foomatic-rip is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Foomatic-rip is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "foomaticrip.h" #include "util.h" #include "options.h" #include "pdf.h" #include "postscript.h" #include "process.h" #include "spooler.h" #include "renderer.h" #include "fileconverter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_DBUS #include "colord.h" #endif /* Logging */ FILE* logh = NULL; void _logv(const char *msg, va_list ap) { if (!logh) return; vfprintf(logh, msg, ap); fflush(logh); } void _log(const char* msg, ...) { va_list ap; va_start(ap, msg); _logv(msg, ap); va_end(ap); } void close_log() { if (logh && logh != stderr) fclose(logh); } int redirect_log_to_stderr() { if (dup2(fileno(logh), fileno(stderr)) < 0) { _log("Could not dup logh to stderr\n"); return 0; } return 1; } void rip_die(int status, const char *msg, ...) { va_list ap; _log("Process is dying with \""); va_start(ap, msg); _logv(msg, ap); va_end(ap); _log("\", exit stat %d\n", status); _log("Cleaning up...\n"); kill_all_processes(); exit(status); } jobparams_t *job = NULL; jobparams_t * get_current_job() { assert(job); return job; } dstr_t *postpipe; /* command into which the output of this filter should be piped */ FILE *postpipe_fh = NULL; FILE * open_postpipe() { const char *p; if (postpipe_fh) return postpipe_fh; if (isempty(postpipe->data)) return stdout; /* Delete possible '|' symbol in the beginning */ p = skip_whitespace(postpipe->data); if (*p && *p == '|') p += 1; if (start_system_process("postpipe", p, &postpipe_fh, NULL) < 0) rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Cannot execute postpipe %s\n", postpipe->data); return postpipe_fh; } char printer_model[256] = ""; const char *accounting_prolog = NULL; char attrpath[256] = ""; int spooler = SPOOLER_DIRECT; int do_docs = 0; int dontparse = 0; int jobhasjcl; int pdfconvertedtops; /* Variable for PPR's backend interface name (parallel, tcpip, atalk, ...) */ char backend [64]; /* Array to collect unknown options so that they can get passed to the backend interface of PPR. For other spoolers we ignore them. */ dstr_t *backendoptions = NULL; /* These variables were in 'dat' before */ char colorprofile [128]; char cupsfilter[256]; char **jclprepend = NULL; dstr_t *jclappend; /* Set debug to 1 to enable the debug logfile for this filter; it will appear * as defined by LOG_FILE. It will contain status from this filter, plus the * renderer's stderr output. You can also add a line "debug: 1" to your * /etc/foomatic/filter.conf to get all your Foomatic filters into debug mode. * WARNING: This logfile is a security hole; do not use in production. */ int debug = 0; /* Path to the GhostScript which foomatic-rip shall use */ char gspath[PATH_MAX] = "gs"; /* What 'echo' program to use. It needs -e and -n. Linux's builtin and regular echo work fine; non-GNU platforms may need to install gnu echo and put gecho here or something. */ char echopath[PATH_MAX] = "echo"; /* CUPS raster drivers are searched here */ char cupsfilterpath[PATH_MAX] = "/usr/local/lib/cups/filter:" "/usr/local/libexec/cups/filter:" "/opt/cups/filter:" "/usr/lib/cups/filter"; char modern_shell[64] = "/bin/bash"; void config_set_option(const char *key, const char *value) { if (strcmp(key, "debug") == 0) debug = atoi(value); /* What path to use for filter programs and such. Your printer driver must be * in the path, as must be the renderer, $enscriptcommand, and possibly other * stuff. The default path is often fine on Linux, but may not be on other * systems. */ else if (strcmp(key, "execpath") == 0 && !isempty(value)) setenv("PATH", value, 1); else if (strcmp(key, "cupsfilterpath") == 0) strlcpy(cupsfilterpath, value, PATH_MAX); else if (strcmp(key, "preferred_shell") == 0) strlcpy(modern_shell, value, 32); else if (strcmp(key, "textfilter") == 0) set_fileconverter(value); else if (strcmp(key, "gspath") == 0) strlcpy(gspath, value, PATH_MAX); else if (strcmp(key, "echo") == 0) strlcpy(echopath, value, PATH_MAX); } void config_from_file(const char *filename) { FILE *fh; char line[256]; char *key, *value; fh = fopen(filename, "r"); if (fh == NULL) return; /* no error here, only read config file if it is present */ while (fgets(line, 256, fh) != NULL) { key = strtok(line, " :\t\r\n"); if (key == NULL || key[0] == '#') continue; value = strtok(NULL, " \t\r\n#"); config_set_option(key, value); } fclose(fh); } const char * get_modern_shell() { return modern_shell; } /* returns position in 'str' after the option */ char * extract_next_option(char *str, char **pagerange, char **key, char **value) { char *p = str; char quotechar; *pagerange = NULL; *key = NULL; *value = NULL; if (!str) return NULL; /* skip whitespace and commas */ while (*p && (isspace(*p) || *p == ',')) p++; if (!*p) return NULL; /* read the pagerange if we have one */ if (prefixcmp(p, "even:") == 0 || prefixcmp(p, "odd:") == 0 || isdigit(*p)) { *pagerange = p; p = strchr(p, ':'); if (!p) return NULL; *p = '\0'; p++; } /* read the key */ if (*p == '\'' || *p == '\"') { quotechar = *p; *key = p +1; p = strchr(*key, quotechar); if (!p) return NULL; } else { *key = p; while (*p && *p != ':' && *p != '=' && *p != ' ') p++; } if (*p != ':' && *p != '=') { /* no value for this option */ if (!*p) return NULL; else if (isspace(*p)) { *p = '\0'; return p +1; } return p; } *p++ = '\0'; /* remove the separator sign */ if (*p == '\"' || *p == '\'') { quotechar = *p; *value = p +1; p = strchr(*value, quotechar); if (!p) return NULL; *p = '\0'; p++; } else { *value = p; while (*p && *p != ' ' && *p != ',') p++; if (*p == '\0') return NULL; *p = '\0'; p++; } return *p ? p : NULL; } /* processes job->optstr */ void process_cmdline_options() { char *p, *cmdlineopts, *nextopt, *pagerange, *key, *value; option_t *opt, *opt2; int optset; char tmp [256]; _log("Printing system options:\n"); cmdlineopts = strdup(job->optstr->data); for (nextopt = extract_next_option(cmdlineopts, &pagerange, &key, &value); key; nextopt = extract_next_option(nextopt, &pagerange, &key, &value)) { /* Consider only options which are not in the PPD file here */ if ((opt = find_option(key)) != NULL) continue; if (value) _log("Pondering option '%s=%s'\n", key, value); else _log("Pondering option '%s'\n", key); /* "docs" option to print help page */ if (!strcasecmp(key, "docs")) { do_docs = 1; continue; } /* "profile" option to supply a color correction profile to a CUPS raster driver */ if (!strcmp(key, "profile")) { strlcpy(colorprofile, value, 128); continue; } /* Solaris options that have no reason to be */ if (!strcmp(key, "nobanner") || !strcmp(key, "dest") || !strcmp(key, "protocol")) continue; if (pagerange) { snprintf(tmp, 256, "pages:%s", pagerange); optset = optionset(tmp); } else optset = optionset("userval"); if (value) { /* At first look for the "backend" option to determine the PPR backend to use */ if (spooler == SPOOLER_PPR_INT && !strcasecmp(key, "backend")) { /* backend interface name */ strlcpy(backend, value, 64); } else if (strcasecmp(key, "media") == 0) { /* Standard arguments? media=x,y,z sides=one|two-sided-long|short-edge Rummage around in the media= option for known media, source, etc types. We ought to do something sensible to make the common manual boolean option work when specified as a media= tray thing. Note that this fails miserably when the option value is in fact a number; they all look alike. It's unclear how many drivers do that. We may have to standardize the verbose names to make them work as selections, too. */ p = strtok(value, ","); do { if ((opt = find_option("PageSize")) && option_accepts_value(opt, p)) option_set_value(opt, optset, p); else if ((opt = find_option("MediaType")) && option_has_choice(opt, p)) option_set_value(opt, optset, p); else if ((opt = find_option("InputSlot")) && option_has_choice(opt, p)) option_set_value(opt, optset, p); else if (!strcasecmp(p, "manualfeed")) { /* Special case for our typical boolean manual feeder option if we didn't match an InputSlot above */ if ((opt = find_option("ManualFeed"))) option_set_value(opt, optset, "1"); } else _log("Unknown \"media\" component: \"%s\".\n", p); } while ((p = strtok(NULL, ","))); } else if (!strcasecmp(key, "sides")) { /* Handle the standard duplex option, mostly */ if (!prefixcasecmp(value, "two-sided")) { if ((opt = find_option("Duplex"))) { /* Default to long-edge binding here, for the case that there is no binding setting */ option_set_value(opt, optset, "DuplexNoTumble"); /* Check the binding: "long edge" or "short edge" */ if (strcasestr(value, "long-edge")) { if ((opt2 = find_option("Binding"))) option_set_value(opt2, optset, "LongEdge"); else option_set_value(opt, optset, "DuplexNoTumble"); } else if (strcasestr(value, "short-edge")) { if ((opt2 = find_option("Binding"))) option_set_value(opt2, optset, "ShortEdge"); else option_set_value(opt, optset, "DuplexNoTumble"); } } } else if (!prefixcasecmp(value, "one-sided")) { if ((opt = find_option("Duplex"))) option_set_value(opt, optset, "0"); } /* TODO We should handle the other half of this option - the BindEdge bit. Also, are there well-known ipp/cups options for Collate and StapleLocation? These may be here... */ } else if (spooler == SPOOLER_PPR_INT) { /* Unknown option, pass it to PPR's backend interface */ if (!backendoptions) backendoptions = create_dstr(); dstrcatf(backendoptions, "%s=%s ", key, value); } else _log("Unknown option %s=%s.\n", key, value); } /* Custom paper size */ else if ((opt = find_option("PageSize")) && option_set_value(opt, optset, key)) { /* do nothing, if the value could be set, it has been set */ } else _log("Unknown boolean option \"%s\".\n", key); } free(cmdlineopts); _log("Options from the PPD file:\n"); cmdlineopts = strdup(job->optstr->data); for (nextopt = extract_next_option(cmdlineopts, &pagerange, &key, &value); key; nextopt = extract_next_option(nextopt, &pagerange, &key, &value)) { /* Consider only PPD file options here */ if ((opt = find_option(key)) == NULL) continue; if (value) _log("Pondering option '%s=%s'\n", key, value); else _log("Pondering option '%s'\n", key); if (pagerange) { snprintf(tmp, 256, "pages:%s", pagerange); optset = optionset(tmp); if (opt && (option_get_section(opt) != SECTION_ANYSETUP && option_get_section(opt) != SECTION_PAGESETUP)) { _log("This option (%s) is not a \"PageSetup\" or \"AnySetup\" option, so it cannot be restricted to a page range.\n", key); continue; } } else optset = optionset("userval"); if (value) { /* Various non-standard printer-specific options */ if (!option_set_value(opt, optset, value)) { _log(" invalid choice \"%s\", using \"%s\" instead\n", value, option_get_value(opt, optset)); } } /* Standard bool args: landscape; what to do here? duplex; we should just handle this one OK now? */ else if (!prefixcasecmp(key, "no")) option_set_value(opt, optset, "0"); else option_set_value(opt, optset, "1"); } free(cmdlineopts); } /* checks whether a pdq driver declaration file should be build and returns an opened file handle if so */ FILE * check_pdq_file(list_t *arglist) { /* "--appendpdq=" appends the data to the , "--genpdq=" creates/overwrites for the data, and "--genpdq" writes to standard output */ listitem_t *i; char filename[256]; FILE *handle; char *p; int raw, append; if ((i = arglist_find_prefix(arglist, "--genpdq"))) { raw = 0; append = 0; } else if ((i = arglist_find_prefix(arglist, "--genrawpdq"))) { raw = 1; append = 0; } else if ((i = arglist_find_prefix(arglist, "--appendpdq"))) { raw = 0; append = 1; } else if ((i = arglist_find_prefix(arglist, "--appendrawpdq"))) { raw = 1; append = 1; } if (!i) return NULL; p = strchr((char*)i->data, '='); if (p) { strncpy_omit(filename, p +1, 256, omit_shellescapes); handle = fopen(filename, append ? "a" : "w"); if (!handle) rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Cannot write PDQ driver declaration file.\n"); } else if (!append) handle = stdout; else return NULL; /* remove option from args */ list_remove(arglist, i); /* Do we have a pdq driver declaration for a raw printer */ if (raw) { fprintf(handle, "driver \"Raw-Printer-%u\" {\n" " # This PDQ driver declaration file was generated automatically by\n" " # foomatic-rip to allow raw (filter-less) printing.\n" " language_driver all {\n" " # We accept all file types and pass them through without any changes\n" " filetype_regx \"\"\n" " convert_exec {\n" " ln -s $INPUT $OUTPUT\n" " }\n" " }\n" " filter_exec {\n" " ln -s $INPUT $OUTPUT\n" " }\n" "}", (unsigned int)job->time); if (handle != stdout) { fclose(handle); handle = NULL; } exit(EXIT_PRINTED); } return handle; } /* Build a PDQ driver description file to use the given PPD file together with foomatic-rip with the PDQ printing system and output it into 'pdqfile' */ void print_pdq_driver(FILE *pdqfile, int optset) { } #if 0 option_t *opt; value_t *val; setting_t *setting, *setting_true, *setting_false; /* Construct option list */ dstr_t *driveropts = create_dstr(); /* Do we have a "Custom" setting for the page size? Then we have to insert the following into the filter_exec script. */ dstr_t *setcustompagesize = create_dstr(); dstr_t *tmp = create_dstr(); dstr_t *cmdline = create_dstr(); dstr_t *psfilter = create_dstr(); /* 1, if setting "PageSize=Custom" was found Then we must add options for page width and height */ int custompagesize = 0; /* Data for a custom page size, to allow a custom size as default */ int pagewidth = 612; int pageheight = 792; char pageunit[2] = "pt"; char def [128]; def[0] = '\0'; for (opt = optionlist; opt; opt = opt->next) { if (opt->type == TYPE_ENUM) { /* Option with only one choice, omit it, foomatic-rip will set this choice anyway */ if (option_setting_count(opt) <= 1) continue; /* Omit "PageRegion" option, it does the same as "PageSize" */ if (!strcmp(opt->name, "PageRegion")) continue; /* 1, if setting "PageSize=Custom" was found Then we must add options for page width and height */ custompagesize = 0; if ((val = option_get_value(opt, optset))) strlcpy(def, val->value, 128); #if 0 TODO not used ?! /* If the default is a custom size we have to set also defaults for the width, height, and units of the page */ if (!strcmp(opt->name, "PageSize") && val && value_has_custom_setting(val)) strcpy(def, "Custom"); #endif dstrcatf(driveropts, " option {\n" " var = \"%s\"\n" " desc = \"%s\"\n", opt->varname, option_text(opt)); /* get enumeration values for each enum arg */ dstrclear(tmp); for (setting = opt->settinglist; setting; setting = setting->next) { dstrcatf(tmp, " choice \"%s_%s\" {\n" " desc = \"%s\"\n" " value = \" -o %s=%s\"\n" " }\n", opt->name, setting->value, isempty(setting->comment) ? setting->value : setting->comment, opt->name, setting->value); if (!strcmp(opt->name, "PageSize") && !strcmp(setting->value, "Custom")) { custompagesize = 1; if (isempty(setcustompagesize->data)) { dstrcatf(setcustompagesize, " # Custom page size settings\n" " # We aren't really checking for legal vals.\n" " if [ \"x${%s}\" = 'x -o %s=%s' ]; then\n" " %s=\"${%s}.${PageWidth}x${PageHeight}${PageSizeUnit}\"\n" " fi\n\n", opt->varname, opt->varname, setting->value, opt->varname, opt->varname); } } } dstrcatf(driveropts, " default_choice \"%s_%s\"\n", opt->name, def); dstrcatf(driveropts, tmp->data); dstrcatf(driveropts, " }\n\n"); if (custompagesize) { /* Add options to set the custom page size */ dstrcatf(driveropts, " argument {\n" " var = \"PageWidth\"\n" " desc = \"Page Width (for \\\"Custom\\\" page size)\"\n" " def_value \"%d\"\n" /* pagewidth */ " help = \"Minimum value: 0, Maximum value: 100000\"\n" " }\n\n" " argument {\n" " var = \"PageHeight\"\n" " desc = \"Page Height (for \\\"Custom\\\" page size)\"\n" " def_value \"%d\"\n" /* pageheight */ " help = \"Minimum value: 0, Maximum value: 100000\"\n" " }\n\n" " option {\n" " var = \"PageSizeUnit\"\n" " desc = \"Unit (for \\\"Custom\\\" page size)\"\n" " default_choice \"PageSizeUnit_%.2s\"\n" /* pageunit */ " choice \"PageSizeUnit_pt\" {\n" " desc = \"Points (1/72 inch)\"\n" " value = \"pt\"\n" " }\n" " choice \"PageSizeUnit_in\" {\n" " desc = \"Inches\"\n" " value = \"in\"\n" " }\n" " choice \"PageSizeUnit_cm\" {\n" " desc = \"cm\"\n" " value = \"cm\"\n" " }\n" " choice \"PageSizeUnit_mm\" {\n" " desc = \"mm\"\n" " value = \"mm\"\n" " }\n" " }\n\n", pagewidth, pageheight, pageunit); } } else if (opt->type == TYPE_INT || opt->type == TYPE_FLOAT) { /* Assure that the comment is not emtpy */ if (isempty(opt->comment)) strcpy(opt->comment, opt->name); if ((val = option_get_value(opt, optset))) strlcpy(def, val->value, 128); strcpy(opt->varname, opt->name); strrepl(opt->varname, "-/.", '_'); dstrcatf(driveropts, " argument {\n" " var = \"%s\"\n" " desc = \"%s\"\n" " def_value \"%s\"\n" " help = \"Minimum value: %s, Maximum value: %s\"\n" " }\n\n", opt->varname, opt->comment, def, opt->min, opt->max); } else if (opt->type == TYPE_BOOL) { /* Assure that the comment is not emtpy */ if (isempty(opt->comment)) strcpy(opt->comment, opt->name); if ((val = option_get_value(opt, optset))) strlcpy(def, val->value, 128); strcpy(opt->varname, opt->name); strrepl(opt->varname, "-/.", '_'); setting_true = option_find_setting(opt, "true"); setting_false = option_find_setting(opt, "false"); dstrcatf(driveropts, " option {\n" " var = \"%s\"\n" " desc = \"%s\"\n", opt->varname, opt->comment); if (!isempty(def) && !strcasecmp(def, "true")) dstrcatf(driveropts, " default_choice \"%s\"\n", def); else dstrcatf(driveropts, " default_choice \"no%s\"\n", def); dstrcatf(driveropts, " choice \"%s\" {\n" " desc = \"%s\"\n" " value = \" -o %s=True\"\n" " }\n" " choice \"no%s\" {\n" " desc = \"%s\"\n" " value = \" -o %s=False\"\n" " }\n" " }\n\n", opt->name, setting_true->comment, opt->name, opt->name, setting_false->comment, opt->name); } else if (opt->type == TYPE_STRING) { /* Assure that the comment is not emtpy */ if (isempty(opt->comment)) strcpy(opt->comment, opt->name); if ((val = option_get_value(opt, optset))) strlcpy(def, val->value, 128); strcpy(opt->varname, opt->name); strrepl_nodups(opt->varname, "-/.", '_'); dstrclear(tmp); if (opt->maxlength) dstrcatf(tmp, "Maximum Length: %s characters, ", opt->maxlength); dstrcatf(tmp, "Examples/special settings: "); for (setting = opt->settinglist; setting; setting = setting->next) { /* Retrieve the original string from the prototype and the driverval */ /* TODO perl code for this part doesn't make sense to me */ } } } /* Define the "docs" option to print the driver documentation page */ dstrcatf(driveropts, " option {\n" " var = \"DRIVERDOCS\"\n" " desc = \"Print driver usage information\"\n" " default_choice \"nodocs\"\n" " choice \"docs\" {\n" " desc = \"Yes\"\n" " value = \" -o docs\"\n" " }\n" " choice \"nodocs\" {\n" " desc = \"No\"\n" " value = \"\"\n" " }\n" " }\n\n"); /* Build the foomatic-rip command line */ dstrcatf(cmdline, "foomatic-rip --pdq"); if (!isempty(printer)) { dstrcatf(cmdline, " -P %s", printer); } else { /* Make sure that the PPD file is entered with an absolute path */ make_absolute_path(job->ppdfile, 256); dstrcatf(cmdline, " --ppd=%s", job->ppdfile); } for (opt = optionlist; opt; opt = opt->next) { if (!isempty(opt->varname)) dstrcatf(cmdline, "${%s}", opt->varname); } dstrcatf(cmdline, "${DRIVERDOCS} $INPUT > $OUTPUT"); /* Now we generate code to build the command line snippets for the numerical options */ for (opt = optionlist; opt; opt = opt->next) { /* Only numerical and string options need to be treated here */ if (opt->type != TYPE_INT && opt->type != TYPE_FLOAT && opt->type != TYPE_STRING) continue; /* If the option's variable is non-null, put in the argument. Otherwise this option is the empty string. Error checking? */ dstrcatf(psfilter, " # %s\n", opt->comment); if (opt->type == TYPE_INT || opt->type == TYPE_FLOAT) { dstrcatf(psfilter, " # We aren't really checking for max/min,\n" " # this is done by foomatic-rip\n" " if [ \"x${%s}\" != 'x' ]; then\n ", opt->varname); } dstrcatf(psfilter, " %s=\" -o %s='${%s}'\"\n", opt->varname, opt->name, opt->varname); if (opt->type == TYPE_INT || opt->type == TYPE_FLOAT) dstrcatf(psfilter, " fi\n"); dstrcatf(psfilter, "\n"); } /* Command execution */ dstrcatf(psfilter, " if ! test -e $INPUT.ok; then\n" " sh -c \"%s\"\n" " if ! test -e $OUTPUT; then \n" " echo 'Error running foomatic-rip; no output!'\n" " exit 1\n" " fi\n" " else\n" " ln -s $INPUT $OUTPUT\n" " fi\n\n", cmdline->data); dstrclear(tmp); dstrcatf(tmp, "%s", printer_model); strrepl_nodups(tmp->data, " \t\n.,;/()[]{}+*", '-'); tmp->len = strlen(tmp->data); /* length could have changed */ if (tmp->data[tmp->len -1] == '-') { tmp->data[--tmp->len] = '\0'; } fprintf(pdqfile, "driver \"%s-%u\" {\n\n" " # This PDQ driver declaration file was generated automatically by\n" " # foomatic-rip from information in the file %s.\n" /* ppdfile */ " # It allows printing with PDQ on the %s.\n" /* model */ "\n" " requires \"foomatic-rip\"\n\n" "%s" /* driveropts */ " language_driver all {\n" " # We accept all file types and pass them to foomatic-rip\n" " # (invoked in \"filter_exec {}\" section) without\n" " # pre-filtering\n" " filetype_regx \"\"\n" " convert_exec {\n" " ln -s $INPUT $OUTPUT\n" " }\n" " }\n\n" " filter_exec {\n" "%s" /* setcustompagesize */ "%s" /* psfilter */ " }\n" "}\n", tmp->data, /* cleaned printer_model */ (unsigned int)job->time, job->ppdfile, printer_model, driveropts->data, setcustompagesize->data, psfilter->data); free_dstr(setcustompagesize); free_dstr(driveropts); free_dstr(tmp); free_dstr(cmdline); free_dstr(psfilter); } #endif /* Functions to let foomatic-rip fork to do several tasks in parallel. To do the filtering without loading the whole file into memory we work on a data stream, we read the data line by line analyse it to decide what filters to use and start the filters if we have found out which we need. We buffer the data only as long as we didn't determing which filters to use for this piece of data and with which options. There are no temporary files used. foomatic-rip splits into up to 6 parallel processes to do the whole filtering (listed in the order of the data flow): KID0: Generate documentation pages (only jobs with "docs" option) KID2: Put together already read data and current input stream for feeding into the file conversion filter (only non-PostScript and "docs" jobs) KID1: Run the file conversion filter to convert non-PostScript input into PostScript (only non-PostScript and "docs" jobs) MAIN: Prepare the job auto-detecting the spooler, reading the PPD, extracting the options from the command line, and parsing the job data itself. It analyses the job data to check whether it is PostScript and starts KID1/KID2 if not, it also stuffs PostScript code from option settings into the PostScript data stream. It starts the renderer (KID3/KID4) as soon as it knows its command line and restarts it when page-specific option settings need another command line or different JCL commands. KID3: The rendering process. In most cases Ghostscript, "cat" for native PostScript printers with their manufacturer's PPD files. KID4: Put together the JCL commands and the renderer's output and send all that either to STDOUT or pipe it into the command line defined with $postpipe. */ void write_output(void *data, size_t len) { const char *p = (const char *)data; size_t left = len; FILE *postpipe = open_postpipe(); /* Remove leading whitespace */ while (isspace(*p++) && left-- > 0) ; fwrite((void *)p, left, 1, postpipe); fflush(postpipe); } enum FileType { UNKNOWN_FILE, PDF_FILE, PS_FILE }; int guess_file_type(const char *begin, size_t len, int *startpos) { const char * p, * end; p = begin; end = begin + len; while (p < end) { p = memchr(p, '%', end - p); if (!p) return UNKNOWN_FILE; *startpos = p - begin; if ((end - p) > 2 && !memcmp(p, "%!", 2)) return PS_FILE; else if ((end - p) > 7 && !memcmp(p, "%PDF-1.", 7)) return PDF_FILE; ++ p; } *startpos = 0; return UNKNOWN_FILE; } /* * Prints 'filename'. If 'convert' is true, the file will be converted if it is * not postscript or pdf */ int print_file(const char *filename, int convert) { FILE *file; char buf[8192]; int type; int startpos; size_t n; FILE *fchandle = NULL; int fcpid = 0, ret; if (!strcasecmp(filename, "")) file = stdin; else { file = fopen(filename, "r"); if (!file) { _log("Could not open \"%s\" for reading\n", filename); return 0; } } n = fread(buf, 1, sizeof(buf) - 1, file); buf[n] = '\0'; type = guess_file_type(buf, n, &startpos); /* We do not use any JCL preceeded to the inputr data, as it is simply the PJL commands from the PPD file, and these commands we can also generate, end we even merge them with PJl from the driver */ /*if (startpos > 0) { jobhasjcl = 1; write_output(buf, startpos); }*/ if (file != stdin) rewind(file); if (convert) pdfconvertedtops = 0; switch (type) { case PDF_FILE: _log("Filetype: PDF\n"); if (!ppd_supports_pdf()) { char pdf2ps_cmd[PATH_MAX]; FILE *out, *in; int renderer_pid; char tmpfilename[PATH_MAX] = ""; _log("Driver does not understand PDF input, " "converting to PostScript\n"); pdfconvertedtops = 1; /* If reading from stdin, write everything into a temporary file */ if (file == stdin) { int fd; FILE *tmpfile; snprintf(tmpfilename, PATH_MAX, "%s/foomatic-XXXXXX", temp_dir()); fd = mkstemp(tmpfilename); if (fd < 0) { _log("Could not create temporary file: %s\n", strerror(errno)); return EXIT_PRNERR_NORETRY_BAD_SETTINGS; } tmpfile = fdopen(fd, "r+"); copy_file(tmpfile, stdin, buf, n); fclose(tmpfile); filename = tmpfilename; } /* If the spooler is CUPS we use the pdftops filter of CUPS, to have always the same PDF->PostScript conversion method in the whole printing environment, including incompatibility workarounds in the CUPS filter (so this way we also have to maintain all these quirks only once). The "-dNOINTERPOLATE" makes Ghostscript rendering significantly faster. Note that Ghostscript's "pswrite" output device turns text into bitmaps and therefore produces huge PostScript files. In addition, this output device is deprecated. Therefore we use "ps2write". We give priority to Ghostscript here and use Poppler if Ghostscript is not available. */ if (spooler == SPOOLER_CUPS) snprintf(pdf2ps_cmd, PATH_MAX, "pdftops '%s' '%s' '%s' '%s' '%s' '%s'", job->id, job->user, job->title, "1", job->optstr->data, filename); else snprintf(pdf2ps_cmd, PATH_MAX, "gs -q -sstdout=%%stderr -sDEVICE=ps2write -sOutputFile=- " "-dBATCH -dNOPAUSE -dPARANOIDSAFER -dNOINTERPOLATE %s 2>/dev/null || " "pdftops -level2 -origpagesizes %s - 2>/dev/null", filename, filename); renderer_pid = start_system_process("pdf-to-ps", pdf2ps_cmd, &in, &out); if (dup2(fileno(out), fileno(stdin)) < 0) rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Couldn't dup stdout of pdf-to-ps\n"); ret = print_file("", 0); wait_for_process(renderer_pid); return ret; } if (file == stdin) return print_pdf(stdin, buf, n, filename, startpos); else return print_pdf(file, NULL, 0, filename, startpos); case PS_FILE: _log("Filetype: PostScript\n"); if (file == stdin) return print_ps(stdin, buf, n, filename); else return print_ps(file, NULL, 0, filename); case UNKNOWN_FILE: if (spooler == SPOOLER_CUPS) { _log("Cannot process \"%s\": Unknown filetype.\n", filename); return 0; } _log("Filetype unknown, trying to convert ...\n"); get_fileconverter_handle(buf, &fchandle, &fcpid); /* Read further data from the file converter and not from STDIN */ if (dup2(fileno(fchandle), fileno(stdin)) < 0) rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Couldn't dup fileconverterhandle\n"); ret = print_file("", 0); if (close_fileconverter_handle(fchandle, fcpid) != EXIT_PRINTED) rip_die(ret, "Error closing file converter\n"); return ret; } fclose(file); return 1; } void signal_terminate(int signal) { rip_die(EXIT_PRINTED, "Caught termination signal: Job canceled\n"); } jobparams_t * create_job() { jobparams_t *job = calloc(1, sizeof(jobparams_t)); struct passwd *passwd; job->optstr = create_dstr(); job->time = time(NULL); strcpy(job->copies, "1"); gethostname(job->host, 128); passwd = getpwuid(getuid()); if (passwd) strlcpy(job->user, passwd->pw_name, 128); snprintf(job->title, 128, "%s@%s", job->user, job->host); return job; } void free_job(jobparams_t *job) { free_dstr(job->optstr); free(job); } int main(int argc, char** argv) { int i; int verbose = 0, quiet = 0, showdocs = 0; const char* str; char *p, *filename; const char *path; FILE *genpdqfile = NULL; FILE *ppdfh = NULL; char tmp[1024], pstoraster[256]; int havefilter, havepstoraster; dstr_t *filelist; list_t * arglist; arglist = list_create_from_array(argc -1, (void**)&argv[1]); if (argc == 2 && (arglist_find(arglist, "--version") || arglist_find(arglist, "--help") || arglist_find(arglist, "-v") || arglist_find(arglist, "-h"))) { printf("foomatic rip version "VERSION"\n"); printf("\"man foomatic-rip\" for help.\n"); list_free(arglist); return 0; } filelist = create_dstr(); job = create_job(); jclprepend = NULL; jclappend = create_dstr(); postpipe = create_dstr(); options_init(); signal(SIGTERM, signal_terminate); signal(SIGINT, signal_terminate); config_from_file(CONFIG_PATH "/filter.conf"); /* Command line options for verbosity */ if (arglist_remove_flag(arglist, "-v")) verbose = 1; if (arglist_remove_flag(arglist, "-q")) quiet = 1; if (arglist_remove_flag(arglist, "-d")) showdocs = 1; if (arglist_remove_flag(arglist, "--debug")) debug = 1; if (debug) { int fd = mkstemp (LOG_FILE "-XXXXXX.log"); if (fd != -1) logh = fdopen(fd, "w"); else logh = stderr; } else if (quiet && !verbose) logh = NULL; /* Quiet mode, do not log */ else logh = stderr; /* Default: log to stderr */ /* Start debug logging */ if (debug) { /* If we are not in debug mode, we do this later, as we must find out at first which spooler is used. When printing without spooler we suppress logging because foomatic-rip is called directly on the command line and so we avoid logging onto the console. */ _log("foomatic-rip version "VERSION" running...\n"); /* Print the command line only in debug mode, Mac OS X adds very many options so that CUPS cannot handle the output of the command line in its log files. If CUPS encounters a line with more than 1024 characters sent into its log files, it aborts the job with an error. */ if (spooler != SPOOLER_CUPS) { _log("called with arguments: "); for (i = 1; i < argc -1; i++) _log("\'%s\', ", argv[i]); _log("\'%s\'\n", argv[i]); } } if (getenv("PPD")) { strncpy(job->ppdfile, getenv("PPD"), 256); spooler = SPOOLER_CUPS; } if (getenv("SPOOLER_KEY")) { spooler = SPOOLER_SOLARIS; /* set the printer name from the ppd file name */ strncpy_omit(job->ppdfile, getenv("PPD"), 256, omit_specialchars); file_basename(job->printer, job->ppdfile, 256); /* TODO read attribute file*/ } if (getenv("PPR_VERSION")) spooler = SPOOLER_PPR; if (getenv("PPR_RIPOPTS")) { /* PPR 1.5 allows the user to specify options for the PPR RIP with the "--ripopts" option on the "ppr" command line. They are provided to the RIP via the "PPR_RIPOPTS" environment variable. */ dstrcatf(job->optstr, "%s ", getenv("PPR_RIPOPTS")); spooler = SPOOLER_PPR; } if (getenv("LPOPTS")) { /* "LPOPTS": Option settings for some LPD implementations (ex: GNUlpr) */ spooler = SPOOLER_GNULPR; dstrcatf(job->optstr, "%s ", getenv("LPOPTS")); } /* Check for LPRng first so we do not pick up bogus ppd files by the -ppd option */ if (spooler != SPOOLER_CUPS && spooler != SPOOLER_PPR && spooler != SPOOLER_PPR_INT) { if (arglist_remove_flag(arglist, "--lprng")) spooler = SPOOLER_LPRNG; } /* 'PRINTCAP_ENTRY' environment variable is : LPRng the :ppd=/path/to/ppdfile printcap entry should be used */ if (getenv("PRINTCAP_ENTRY")) { spooler = SPOOLER_LPRNG; if ((str = strstr(getenv("PRINTCAP_ENTRY"), "ppd="))) str += 4; else if ((str = strstr(getenv("PRINTCAP_ENTRY"), "ppdfile="))) str += 8; if (str) { while (isspace(*str)) str++; p = job->ppdfile; while (*str != '\0' && !isspace(*str) && *str != '\n' && *str != ':') { if (isprint(*str) && strchr(shellescapes, *str) == NULL) *p++ = *str; str++; } } } /* CUPS calls foomatic-rip only with 5 or 6 positional parameters, not with named options, like for example "-p ". Also PPR does not used named options. */ if (spooler != SPOOLER_CUPS && spooler != SPOOLER_PPR && spooler != SPOOLER_PPR_INT) { /* Check for LPD/GNUlpr by typical options which the spooler puts onto the filter's command line (options "-w": text width, "-l": text length, "-i": indent, "-x", "-y": graphics size, "-c": raw printing, "-n": user name, "-h": host name) */ if ((str = arglist_get_value(arglist, "-h"))) { if (spooler != SPOOLER_GNULPR && spooler != SPOOLER_LPRNG) spooler = SPOOLER_LPD; strncpy(job->host, str, 127); job->host[127] = '\0'; arglist_remove(arglist, "-h"); } if ((str = arglist_get_value(arglist, "-n"))) { if (spooler != SPOOLER_GNULPR && spooler != SPOOLER_LPRNG) spooler = SPOOLER_LPD; strncpy(job->user, str, 127); job->user[127] = '\0'; arglist_remove(arglist, "-n"); } if (arglist_remove(arglist, "-w") || arglist_remove(arglist, "-l") || arglist_remove(arglist, "-x") || arglist_remove(arglist, "-y") || arglist_remove(arglist, "-i") || arglist_remove_flag(arglist, "-c")) { if (spooler != SPOOLER_GNULPR && spooler != SPOOLER_LPRNG) spooler = SPOOLER_LPD; } /* LPRng delivers the option settings via the "-Z" argument */ if ((str = arglist_get_value(arglist, "-Z"))) { spooler = SPOOLER_LPRNG; dstrcatf(job->optstr, "%s ", str); arglist_remove(arglist, "-Z"); } /* Job title and options for stock LPD */ if ((str = arglist_get_value(arglist, "-j")) || (str = arglist_get_value(arglist, "-J"))) { strncpy_omit(job->title, str, 128, omit_shellescapes); if (spooler == SPOOLER_LPD) dstrcatf(job->optstr, "%s ", job->title); if (!arglist_remove(arglist, "-j")) arglist_remove(arglist, "-J"); } /* Check for CPS */ if (arglist_remove_flag(arglist, "--cps") > 0) spooler = SPOOLER_CPS; /* PPD file name given via the command line allow duplicates, and use the last specified one */ if (spooler != SPOOLER_GNULPR && spooler != SPOOLER_LPRNG && spooler != SPOOLER_LPD) { while ((str = arglist_get_value(arglist, "-p"))) { strncpy(job->ppdfile, str, 256); arglist_remove(arglist, "-p"); } while ((str = arglist_get_value(arglist, "--ppd"))) { strncpy(job->ppdfile, str, 256); arglist_remove(arglist, "--ppd"); } } /* Options for spooler-less printing, CPS, or PDQ */ while ((str = arglist_get_value(arglist, "-o"))) { strncpy_omit(tmp, str, 1024, omit_shellescapes); dstrcatf(job->optstr, "%s ", tmp); arglist_remove(arglist, "-o"); /* If we don't print as PPR RIP or as CPS filter, we print without spooler (we check for PDQ later) */ if (spooler != SPOOLER_PPR && spooler != SPOOLER_CPS) spooler = SPOOLER_DIRECT; } /* Printer for spooler-less printing or PDQ */ if ((str = arglist_get_value(arglist, "-d"))) { strncpy_omit(job->printer, str, 256, omit_shellescapes); arglist_remove(arglist, "-d"); } /* Printer for spooler-less printing, PDQ, or LPRng */ if ((str = arglist_get_value(arglist, "-P"))) { strncpy_omit(job->printer, str, 256, omit_shellescapes); arglist_remove(arglist, "-P"); } /* Were we called from a PDQ wrapper? */ if (arglist_remove_flag(arglist, "--pdq")) spooler = SPOOLER_PDQ; /* Were we called to build the PDQ driver declaration file? */ genpdqfile = check_pdq_file(arglist); if (genpdqfile) spooler = SPOOLER_PDQ; } /* spooler specific initialization */ switch (spooler) { case SPOOLER_PPR: init_ppr(arglist, job); break; case SPOOLER_CUPS: init_cups(arglist, filelist, job); break; case SPOOLER_LPRNG: if (job->ppdfile[0] != '\0') break; case SPOOLER_LPD: case SPOOLER_GNULPR: /* Get PPD file name as the last command line argument */ if (arglist->last) strncpy(job->ppdfile, (char*)arglist->last->data, 256); break; case SPOOLER_DIRECT: case SPOOLER_CPS: case SPOOLER_PDQ: init_direct_cps_pdq(arglist, filelist, job); break; } /* Files to be printed (can be more than one for spooler-less printing) */ /* Empty file list -> print STDIN */ dstrtrim(filelist); if (filelist->len == 0) dstrcpyf(filelist, ""); /* Check filelist */ p = strtok(strdup(filelist->data), " "); while (p) { if (strcmp(p, "") != 0) { if (p[0] == '-') rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Invalid argument: %s", p); else if (access(p, R_OK) != 0) { _log("File %s does not exist/is not readable\n", p); strclr(p); } } p = strtok(NULL, " "); } /* When we print without spooler or with CPS do not log onto STDERR unless the "-v" ('Verbose') is set or the debug mode is used */ if ((spooler == SPOOLER_DIRECT || spooler == SPOOLER_CPS || genpdqfile) && !verbose && !debug) { if (logh && logh != stderr) fclose(logh); logh = NULL; } /* If we are in debug mode, we do this earlier. */ if (!debug) { _log("foomatic-rip version " VERSION " running...\n"); /* Print the command line only in debug mode, Mac OS X adds very many options so that CUPS cannot handle the output of the command line in its log files. If CUPS encounters a line with more than 1024 characters sent into its log files, it aborts the job with an error. */ if (spooler != SPOOLER_CUPS) { _log("called with arguments: "); for (i = 1; i < argc -1; i++) _log("\'%s\', ", argv[i]); _log("\'%s\'\n", argv[i]); } } /* PPD File */ /* Load the PPD file and build a data structure for the renderer's command line and the options */ if (!(ppdfh = fopen(job->ppdfile, "r"))) rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Unable to open PPD file %s\n", job->ppdfile); read_ppd_file(job->ppdfile); /* We do not need to parse the PostScript job when we don't have any options. If we have options, we must check whether the default settings from the PPD file are valid and correct them if nexessary. */ if (option_count() == 0) { /* We don't have any options, so we do not need to parse the PostScript data */ dontparse = 1; } /* Is our PPD for a CUPS raster driver */ if (!isempty(cupsfilter)) { /* Search the filter in cupsfilterpath The %Y is a placeholder for the option settings */ havefilter = 0; path = cupsfilterpath; while ((path = strncpy_tochar(tmp, path, 1024, ":"))) { strlcat(tmp, "/", 1024); strlcat(tmp, cupsfilter, 1024); if (access(tmp, X_OK) == 0) { havefilter = 1; strlcpy(cupsfilter, tmp, 256); strlcat(cupsfilter, " 0 '' '' 0 '%Y%X'", 256); break; } } if (!havefilter) { /* We do not have the required filter, so we assume that rendering this job is supposed to be done on a remote server. So we do not define a renderer command line and embed only the option settings (as we had a PostScript printer). This way the settings are taken into account when the job is rendered on the server.*/ _log("CUPS filter for this PPD file not found - assuming that job will " "be rendered on a remote server. Only the PostScript of the options" "will be inserted into the PostScript data stream.\n"); } else { /* use pstoraster script if available, otherwise run Ghostscript directly */ havepstoraster = 0; path = cupsfilterpath; while ((path = strncpy_tochar(tmp, path, 1024, ":"))) { strlcat(tmp, "/pstoraster", 1024); if (access(tmp, X_OK) == 0) { havepstoraster = 1; strlcpy(pstoraster, tmp, 256); strlcat(pstoraster, " 0 '' '' 0 '%X'", 256); break; } /* gstoraster is the new name for pstoraster */ strlcat(tmp, "/gstoraster", 1024); if (access(tmp, X_OK) == 0) { havepstoraster = 1; strlcpy(pstoraster, tmp, 256); strlcat(pstoraster, " 0 '' '' 0 '%X'", 256); break; } } if (!havepstoraster) { const char **qualifier = NULL; const char *icc_profile = NULL; qualifier = get_ppd_qualifier(); _log("INFO: Using qualifer: '%s.%s.%s'\n", qualifier[0], qualifier[1], qualifier[2]); #ifdef HAVE_DBUS /* ask colord for the profile */ icc_profile = colord_get_profile_for_device_id ((const char *) getenv("PRINTER"), qualifier); #endif /* fall back to PPD */ if (icc_profile == NULL) { _log("INFO: need to look in PPD for matching qualifer\n"); icc_profile = get_icc_profile_for_qualifier(qualifier); } if (icc_profile != NULL) snprintf(cmd, sizeof(cmd), "-sOutputICCProfile='%s'", icc_profile); else cmd[0] = '\0'; snprintf(pstoraster, sizeof(pstoraster), "gs -dQUIET -dDEBUG -dPARANOIDSAFER -dNOPAUSE -dBATCH -dNOINTERPOLATE -dNOMEDIAATTRS -sDEVICE=cups %s -sOutputFile=- -", cmd); } /* build Ghostscript/CUPS driver command line */ snprintf(cmd, 1024, "%s | %s", pstoraster, cupsfilter); _log("INFO: Using command line: %s\n", cmd); /* Set environment variables */ setenv("PPD", job->ppdfile, 1); } } /* Was the RIP command line defined in the PPD file? If not, we assume a PostScript printer and do not render/translate the input data */ if (isempty(cmd)) { strcpy(cmd, "cat%A%B%C%D%E%F%G%H%I%J%K%L%M%Z"); if (dontparse) { /* No command line, no options, we have a raw queue, don't check whether the input is PostScript and ignore the "docs" option, simply pass the input data to the backend.*/ dontparse = 2; strcpy(printer_model, "Raw queue"); } } /* Summary for debugging */ _log("\nParameter Summary\n" "-----------------\n\n" "Spooler: %s\n" "Printer: %s\n" "Shell: %s\n" "PPD file: %s\n" "ATTR file: %s\n" "Printer model: %s\n", spooler_name(spooler), job->printer, get_modern_shell(), job->ppdfile, attrpath, printer_model); /* Print the options string only in debug mode, Mac OS X adds very many options so that CUPS cannot handle the output of the option string in its log files. If CUPS encounters a line with more than 1024 characters sent into its log files, it aborts the job with an error.*/ if (debug || spooler != SPOOLER_CUPS) _log("Options: %s\n", job->optstr->data); _log("Job title: %s\n", job->title); _log("File(s) to be printed:\n"); _log("%s\n\n", filelist->data); if (getenv("GS_LIB")) _log("Ghostscript extra search path ('GS_LIB'): %s\n", getenv("GS_LIB")); /* Process options from command line, but save the defaults for printing documentation pages first */ optionset_copy_values(optionset("default"), optionset("userval")); process_cmdline_options(); /* Were we called to build the PDQ driver declaration file? */ if (genpdqfile) { print_pdq_driver(genpdqfile, optionset("userval")); fclose(genpdqfile); exit(EXIT_PRINTED); } if (spooler == SPOOLER_PPR_INT) { snprintf(tmp, 1024, "interfaces/%s", backend); if (access(tmp, X_OK) != 0) rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "The backend interface " "/interfaces/%s does not exist/ is not executable!\n", backend); /* foomatic-rip cannot use foomatic-rip as backend */ if (!strcmp(backend, "foomatic-rip")) rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "\"foomatic-rip\" cannot " "use itself as backend interface!\n"); /* Put the backend interface into the postpipe */ /* TODO $postpipe = "| ( interfaces/$backend \"$ppr_printer\" ". "\"$ppr_address\" \"" . join(" ",@backendoptions) . "\" \"$ppr_jobbreak\" \"$ppr_feedback\" " . "\"$ppr_codes\" \"$ppr_jobname\" \"$ppr_routing\" " . "\"$ppr_for\" \"\" )"; */ } /* no postpipe for CUPS or PDQ, even if one is defined in the PPD file */ if (spooler == SPOOLER_CUPS || spooler == SPOOLER_PDQ) dstrclear(postpipe); /* CPS always needs a postpipe, set the default one for local printing if none is set */ if (spooler == SPOOLER_CPS && !postpipe->len) dstrcpy(postpipe, "| cat - > $LPDDEV"); if (postpipe->len) _log("Ouput will be redirected to:\n%s\n", postpipe); /* Print documentation page when asked for */ if (do_docs) { /* Don't print the supplied files, STDIN will be redirected to the documentation page generator */ dstrcpyf(filelist, ""); /* Start the documentation page generator */ /* TODO tbd */ } filename = strtok_r(filelist->data, " ", &p); while (filename) { _log("\n================================================\n\n" "File: %s\n\n" "================================================\n\n", filename); /* Do we have a raw queue? */ if (dontparse == 2) { /* Raw queue, simply pass the input into the postpipe (or to STDOUT when there is no postpipe) */ _log("Raw printing, executing \"cat %s\"\n\n"); snprintf(tmp, 1024, "cat %s", postpipe->data); run_system_process("raw-printer", tmp); continue; } /* First, for arguments with a default, stick the default in as the initial value for the "header" option set, this option set consists of the PPD defaults, the options specified on the command line, and the options set in the header part of the PostScript file (all before the first page begins). */ optionset_copy_values(optionset("userval"), optionset("header")); if (!print_file(filename, 1)) rip_die(EXIT_PRNERR_NORETRY, "Could not print file %s\n", filename); filename = strtok_r(NULL, " ", &p); } /* Close documentation page generator */ /* if (docgenerator_pid) { retval = close_docgenerator_handle(dogenerator_handle, docgenerator_pid); if (!retval != EXIT_PRINTED) { _log("Error closing documentation page generator\n"); exit(retval); } docgenerator_pid = 0; } */ /* Close the last input file */ fclose(stdin); /* TODO dump everything in $dat when debug is turned on (necessary?) */ _log("\nClosing foomatic-rip.\n"); /* Cleanup */ free_job(job); if (genpdqfile && genpdqfile != stdout) fclose(genpdqfile); free_dstr(filelist); options_free(); close_log(); argv_free(jclprepend); free_dstr(jclappend); if (backendoptions) free_dstr(backendoptions); list_free(arglist); return EXIT_PRINTED; }