From 014f0e14a3c6a044d99a67c8f4e1c4065452479e Mon Sep 17 00:00:00 2001 From: Didier Raboud Date: Sun, 23 May 2010 00:05:04 +0200 Subject: Imported Upstream version 4.0-20090301 --- foomaticrip.c | 1570 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1570 insertions(+) create mode 100644 foomaticrip.c (limited to 'foomaticrip.c') diff --git a/foomaticrip.c b/foomaticrip.c new file mode 100644 index 0000000..37d0822 --- /dev/null +++ b/foomaticrip.c @@ -0,0 +1,1570 @@ + +#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 + + +/* 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, *nextopt, *pagerange, *key, *value; + option_t *opt, *opt2; + int optset; + char tmp [256]; + + for (nextopt = extract_next_option(job->optstr->data, &pagerange, &key, &value); + key; + nextopt = extract_next_option(nextopt, &pagerange, &key, &value)) + { + 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); + + opt = find_option(key); + 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) { + /* 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 { + /* Various non-standard printer-specific options */ + if ((opt = find_option(key))) { + if (!option_set_value(opt, optset, value)) { + _log(" invalid choice \"%s\", using \"%s\" instead\n", + value, option_get_value(opt, optset)); + } + } + 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 */ + } + /* Standard bool args: + landscape; what to do here? + duplex; we should just handle this one OK now? */ + else if (!prefixcasecmp(key, "no") && (opt = find_option(&key[2]))) + option_set_value(opt, optset, "0"); + else if ((opt = find_option(key))) + option_set_value(opt, optset, "1"); + else + _log("Unknown boolean option \"%s\".\n", key); + } +} + +/* 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; + + p = memchr(begin, '%', len); + if (!p) + return UNKNOWN_FILE; + *startpos = p - begin; + if (!memcmp(p, "%!", 2)) + return PS_FILE; + else if (!memcmp(p, "%PDF-1.", 7)) + return PDF_FILE; + *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), file); + buf[n] = '\0'; + type = guess_file_type(buf, n, &startpos); + 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; + + _log("Driver does not understand PDF input, " + "converting to PostScript\n"); + + pdfconvertedtops = 1; + snprintf(pdf2ps_cmd, PATH_MAX, + "gs -q -sstdout=%%stderr -sDEVICE=pswrite -sOutputFile=- " + "-dBATCH -dNOPAUSE -dPARANOIDSAFER %s", + file == stdin ? "-" : filename); + + renderer_pid = start_system_process("pdf-to-ps", pdf2ps_cmd, &in, &out); + + if (file == stdin) + { + fwrite(buf, 1, n, in); + while ((n = fread(buf, 1, sizeof(buf), file))) + fwrite(buf, 1, n, in); + fclose(in); + } + + 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) + logh = fopen(LOG_FILE ".log", "w"); /* insecure, use for debugging only */ + 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 (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') { + if (isprint(*str) && strchr(shellescapes, *str) == NULL) + *p++ = *str; + str++; + } + } + } + + /* PPD file name given via the command line + allow duplicates, and use the last specified one */ + if (spooler != SPOOLER_LPRNG) { + 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"); + } + + /* 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; + + /* 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_LPD: + case SPOOLER_LPRNG: + 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(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; + } + } + if (!havepstoraster) { + strcpy(pstoraster, "gs -dQUIET -dDEBUG -dPARANOIDSAFER -dNOPAUSE -dBATCH -dNOMEDIAATTRS -sDEVICE=cups -sOutputFile=-%W -"); + } + + /* build Ghostscript/CUPS driver command line */ + snprintf(cmd, 1024, "%s | %s", pstoraster, cupsfilter); + + /* 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 */ + } + + /* In debug mode save the data supposed to be fed into the + renderer also into a file, reset the file here */ + if (debug) + run_system_process("reset-file", "> " LOG_FILE ".ps"); + + 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")); + + print_file(filename, 1); + 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 0; +} + -- cgit v1.2.3