diff options
Diffstat (limited to 'renderer.c')
-rw-r--r-- | renderer.c | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/renderer.c b/renderer.c new file mode 100644 index 0000000..57f7281 --- /dev/null +++ b/renderer.c @@ -0,0 +1,450 @@ + +#define _GNU_SOURCE + +#include <signal.h> +#include <ctype.h> +#include <stdlib.h> +#include <unistd.h> + +#include "foomaticrip.h" +#include "util.h" +#include "process.h" +#include "options.h" + +/* + * Check whether we have a Ghostscript version with redirection of the standard + * output of the PostScript programs via '-sstdout=%stderr' + */ +int test_gs_output_redirection() +{ + char gstestcommand[PATH_MAX]; + char output[10] = ""; + + snprintf(gstestcommand, PATH_MAX, "%s -dQUIET -dPARANOIDSAFER -dNOPAUSE " + "-dBATCH -dNOMEDIAATTRS -sDEVICE=pswrite -sstdout=%%stderr " + "-sOutputFile=/dev/null -c '(hello\n) print flush' 2>&1", gspath); + + FILE *pd = popen(gstestcommand, "r"); + if (!pd) { + _log("Failed to execute ghostscript!\n"); + return 0; + } + + fread(output, 1, 10, pd); + pclose(pd); + + if (startswith(output, "hello")) + return 1; + + return 0; +} + +/* + * Massage arguments to make ghostscript execute properly as a filter, with + * output on stdout and errors on stderr etc. (This function does what + * foomatic-gswrapper used to do) + */ +void massage_gs_commandline(dstr_t *cmd) +{ + int gswithoutputredirection = test_gs_output_redirection(); + size_t start, end; + dstr_t *gscmd, *cmdcopy; + + extract_command(&start, &end, cmd->data, "gs"); + if (start == end) /* cmd doesn't call ghostscript */ + return; + + gscmd = create_dstr(); + dstrncpy(gscmd, &cmd->data[start], end - start); + + /* If Ghostscript does not support redirecting the standard output + of the PostScript program to standard error with '-sstdout=%stderr', sen + the job output data to fd 3; errors will be on 2(stderr) and job ps + program interpreter output on 1(stdout). */ + if (gswithoutputredirection) + dstrreplace(gscmd, "-sOutputFile=- ", "-sOutputFile=%stdout ", 0); + else + dstrreplace(gscmd, "-sOutputFile=- ", "-sOutputFile=/dev/fd/3 ", 0); + + /* Use always buffered input. This works around a Ghostscript + bug which prevents printing encrypted PDF files with Adobe Reader 8.1.1 + and Ghostscript built as shared library (Ghostscript bug #689577, Ubuntu + bug #172264) */ + if (dstrendswith(gscmd, " -")) + dstrcat(gscmd, "_"); + else + dstrreplace(gscmd, " - ", " -_ ", 0); + dstrreplace(gscmd, " /dev/fd/0", " -_ ", 0); + + /* Turn *off* -q (quiet!); now that stderr is useful! :) */ + dstrreplace(gscmd, " -q ", " ", 0); + + /* Escape any quotes, and then quote everything just to be sure... + Escaping a single quote inside single quotes is a bit complex as the + shell takes everything literal there. So we have to assemble it by + concatinating different quoted strings. Finally we get e.g.: 'x'"'"'y' + or ''"'"'xy' or 'xy'"'"'' or ... */ + /* dstrreplace(cmd, "'", "'\"'\"'"); TODO tbd */ + + dstrremove(gscmd, start, 2); /* Remove 'gs' */ + if (gswithoutputredirection) + { + dstrprepend(gscmd, " -sstdout=%stderr "); + dstrprepend(gscmd, gspath); + } + else + { + dstrprepend(gscmd, gspath); + dstrcat(gscmd, " 3>&1 1>&2"); + } + + /* put gscmd back into cmd, between 'start' and 'end' */ + cmdcopy = create_dstr(); + dstrcpy(cmdcopy, cmd->data); + + dstrncpy(cmd, cmdcopy->data, start); + dstrcat(cmd, gscmd->data); + dstrcat(cmd, &cmdcopy->data[end]); + + free_dstr(gscmd); + free_dstr(cmdcopy); + + /* If the renderer command line contains the "echo" command, replace the + * "echo" by the user-chosen $myecho (important for non-GNU systems where + * GNU echo is in a special path */ + dstrreplace(cmd, "echo", echopath, 0); /* TODO search for \wecho\w */ +} + +char * read_line(FILE *stream, size_t *readbytes) +{ + char *line; + size_t alloc = 64, len = 0; + int c; + + line = malloc(alloc); + + while ((c = fgetc(stream)) != EOF) { + if (len >= alloc -1) { + alloc *= 2; + line = realloc(line, alloc); + } + line[len] = (char)c; + len++; + if (c == '\n') + break; + } + + line[len] = '\0'; + *readbytes = len; + return line; +} + +write_binary_data(FILE *stream, const char *data, size_t bytes) +{ + int i; + for (i=0; i < bytes; i++) + { + fputc(data[i], stream); + } +} + +/* + * Read all lines containing 'jclstr' from 'stream' (actually, one more) and + * return them in a zero terminated array. + */ +static char ** read_jcl_lines(FILE *stream, const char *jclstr, + size_t *readbinarybytes) +{ + char *line; + char **result; + size_t alloc = 8, cnt = 0; + + result = malloc(alloc * sizeof(char *)); + + /* read from the renderer output until the first non-JCL line appears */ + while ((line = read_line(stream, readbinarybytes))) + { + if (cnt >= alloc -1) + { + alloc *= 2; + result = realloc(result, alloc * sizeof(char *)); + } + result[cnt] = line; + if (!strstr(line, jclstr)) + break; + /* Remove newline from the end of a line containing JCL */ + result[cnt][*readbinarybytes - 1] = '\0'; + cnt++; + } + + cnt++; + result[cnt] = NULL; + return result; +} + +static int jcl_keywords_equal(const char *jclline1, const char *jclline2, + const char *jclstr) +{ + char *j1, *j2, *p1, *p2; + + j1 = strstr(jclline1, jclstr); + if (!j1) return 0; + if (!(p1 = strchr(skip_whitespace(j1), '='))) + p1 = j1[strlen(j1)]; + p1--; + while (p1 > j1 && isspace(*p1)) + p1--; + + j2 = strstr(jclline2, jclstr); + if (!j2) return 0; + if (!(p2 = strchr(skip_whitespace(j2), '='))) + p2 = j2[strlen(j2)]; + p2--; + while (p2 > j2 && isspace(*p2)) + p2--; + + if (p1 - j1 != p2 - j2) return 0; + return strncmp(j1, j2, p1 - j1 + 1) == 0; +} + +/* + * Finds the keyword of line in opts + */ +static const char * jcl_options_find_keyword(char **opts, const char *line, + const char *jclstr) +{ + if (!opts) + return NULL; + + while (*opts) + { + if (jcl_keywords_equal(*opts, line, jclstr)) + return *opts; + opts++; + } + return NULL; +} + +static void argv_write(FILE *stream, char **argv, const char *sep) +{ + if (!argv) + return; + + while (*argv) + fprintf(stream, "%s%s", *argv++, sep); +} + +/* + * Merges 'original_opts' and 'pref_opts' and writes them to 'stream'. Header / + * footer is taken from 'original_opts'. If both have the same options, the one + * from 'pref_opts' is preferred + * Returns true, if original_opts was not empty + */ +static int write_merged_jcl_options(FILE *stream, + char **original_opts, + char **opts, + size_t readbinarybytes, + const char *jclstr) +{ + char *p = strstr(original_opts[0], jclstr); + char header[128]; + char **optsp; + + /* No JCL options in original_opts, just prepend opts */ + if (argv_count(original_opts) == 1) + { + fprintf(stream, "%s", jclbegin); + argv_write(stream, opts, "\n"); + write_binary_data(stream, original_opts[0], readbinarybytes); + return 0; + } + + if (argv_count(original_opts) == 2) + { + /* If we have only one line of JCL it is probably something like the + * "@PJL ENTER LANGUAGE=..." line which has to be in the end, but it + * also contains the "<esc>%-12345X" which has to be in the beginning + * of the job */ + if (p) + fwrite(original_opts[0], 1, p - original_opts[0], stream); + else + fprintf(stream, "%s\n", original_opts[0]); + + argv_write(stream, opts, "\n"); + + if (p) + fprintf(stream, "%s\n", p); + + write_binary_data(stream, original_opts[1], readbinarybytes); + return 1; + } + + /* Write jcl header */ + strncpy(header, original_opts[0], p - original_opts[0]); + header[p - original_opts[0]] = '\0'; + fprintf(stream, "%s", header); + + for (optsp = opts; *optsp; optsp++) + if (!jcl_options_find_keyword(original_opts, *optsp, jclstr)) + fprintf(stream, "%s\n", *optsp); + + for (optsp = original_opts; *(optsp + 1); optsp++) { + if (optsp != original_opts) p = *optsp; + if (jcl_options_find_keyword(opts, p, jclstr)) + fprintf(stream, "%s\n", jcl_options_find_keyword(opts, p, jclstr)); + else + fprintf(stream, "%s\n", p); + } + write_binary_data(stream, *optsp, readbinarybytes); + + return 1; +} + +void log_jcl() +{ + char **opt; + + _log("JCL: %s", jclbegin); + if (jclprepend) + for (opt = jclprepend; *opt; opt++) + _log("%s\n", *opt); + + _log("<job data> %s\n\n", jclappend->data); +} + +int exec_kid4(FILE *in, FILE *out, void *user_arg) +{ + FILE *fileh = open_postpipe(); + int driverjcl; + size_t readbinarybytes; + + log_jcl(); + + /* wrap the JCL around the job data, if there are any options specified... + * Should the driver already have inserted JCL commands we merge our JCL + * header with the one from the driver */ + if (argv_count(jclprepend) > 0) + { + if (!isspace(jclprepend[0][0])) + { + char *jclstr, **jclheader; + size_t pos; + + pos = strcspn(jclprepend[0], " \t\n\r"); + jclstr = malloc(pos +1); + strncpy(jclstr, jclprepend[0], pos); + jclstr[pos] = '\0'; + + jclheader = read_jcl_lines(in, jclstr, &readbinarybytes); + + driverjcl = write_merged_jcl_options(fileh, + jclheader, + jclprepend, + readbinarybytes, + jclstr); + + free(jclstr); + argv_free(jclheader); + } + else + /* No merging of JCL header possible, simply prepend it */ + argv_write(fileh, jclprepend, "\n"); + } + + /* The job data */ + copy_file(fileh, in, NULL, 0); + + /* A JCL trailer */ + if (argv_count(jclprepend) > 0 && !driverjcl) + fwrite(jclappend->data, jclappend->len, 1, fileh); + + fclose(in); + if (fclose(fileh) != 0) + { + _log("error closing postpipe\n"); + return EXIT_PRNERR_NORETRY_BAD_SETTINGS; + } + + return EXIT_PRINTED; +} + +int exec_kid3(FILE *in, FILE *out, void *user_arg) +{ + dstr_t *commandline; + int kid4; + FILE *kid4in; + int status; + + commandline = create_dstr(); + dstrcpy(commandline, (const char *)user_arg); + + kid4 = start_process("kid4", exec_kid4, NULL, &kid4in, NULL); + if (kid4 < 0) + return EXIT_PRNERR_NORETRY_BAD_SETTINGS; + + if (in && dup2(fileno(in), fileno(stdin)) < 0) { + _log("kid3: Could not dup stdin\n"); + fclose(kid4in); + return EXIT_PRNERR_NORETRY_BAD_SETTINGS; + } + if (dup2(fileno(kid4in), fileno(stdout)) < 0) { + _log("kid3: Could not dup stdout to kid4\n"); + fclose(kid4in); + return EXIT_PRNERR_NORETRY_BAD_SETTINGS; + } + if (debug) + { + if (!redirect_log_to_stderr()) { + fclose(kid4in); + return EXIT_PRNERR_NORETRY_BAD_SETTINGS; + } + + /* Save the data supposed to be fed into the renderer also into a file*/ + dstrprepend(commandline, "tee -a " LOG_FILE ".ps | ( "); + dstrcat(commandline, ")"); + } + + /* Actually run the thing */ + status = run_system_process("renderer", commandline->data); + + if (in) + fclose(in); + fclose(kid4in); + fclose(stdin); + fclose(stdout); + free_dstr(commandline); + + if (WIFEXITED(status)) { + switch (WEXITSTATUS(status)) { + case 0: /* Success! */ + /* wait for postpipe/output child */ + wait_for_process(kid4); + _log("kid3 finished\n"); + return EXIT_PRINTED; + case 1: + _log("Possible error on renderer command line or PostScript error. Check options."); + return EXIT_JOBERR; + case 139: + _log("The renderer may have dumped core."); + return EXIT_JOBERR; + case 141: + _log("A filter used in addition to the renderer itself may have failed."); + return EXIT_PRNERR; + case 243: + case 255: /* PostScript error? */ + return EXIT_JOBERR; + } + } + else if (WIFSIGNALED(status)) { + switch (WTERMSIG(status)) { + case SIGUSR1: + return EXIT_PRNERR; + case SIGUSR2: + return EXIT_PRNERR_NORETRY; + case SIGTTIN: + return EXIT_ENGAGED; + } + } + return EXIT_PRNERR; +} + |