1 /* renderer.c
2 *
3 * Copyright (C) 2008 Till Kamppeter <till.kamppeter@gmail.com>
4 * Copyright (C) 2008 Lars Karlitski (formerly Uebernickel) <lars@karlitski.net>
5 *
6 * This file is part of foomatic-rip.
7 *
8 * Foomatic-rip is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * Foomatic-rip is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
22 */
23 #include <config.h>
24 #include <signal.h>
25 #include <ctype.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28
29 #include "foomaticrip.h"
30 #include "util.h"
31 #include "process.h"
32 #include "options.h"
33
34 /*
35 * Check whether we have a Ghostscript version with redirection of the standard
36 * output of the PostScript programs via '-sstdout=%stderr'
37 */
test_gs_output_redirection()38 int test_gs_output_redirection()
39 {
40 char gstestcommand[CMDLINE_MAX];
41 char output[10] = "";
42 int bytes;
43
44 snprintf(gstestcommand, CMDLINE_MAX,
45 "%s -dQUIET -dSAFER -dNOPAUSE "
46 "-dBATCH -dNOMEDIAATTRS -sDEVICE=ps2write -sstdout=%%stderr "
47 "-sOutputFile=/dev/null -c '(hello\n) print flush' 2>&1", gspath);
48
49 FILE *pd = popen(gstestcommand, "r");
50 if (!pd) {
51 _log("Failed to execute ghostscript!\n");
52 return 0;
53 }
54
55 bytes = fread_or_die(output, 1, 10, pd);
56 pclose(pd);
57
58 if (bytes > 0 && startswith(output, "hello"))
59 return 1;
60
61 return 0;
62 }
63
64 /*
65 * Massage arguments to make ghostscript execute properly as a filter, with
66 * output on stdout and errors on stderr etc. (This function does what
67 * foomatic-gswrapper used to do)
68 */
massage_gs_commandline(dstr_t * cmd)69 void massage_gs_commandline(dstr_t *cmd)
70 {
71 int gswithoutputredirection = test_gs_output_redirection();
72 size_t start, end;
73 dstr_t *gscmd, *cmdcopy;
74
75 extract_command(&start, &end, cmd->data, "gs");
76 if (start == end) /* cmd doesn't call ghostscript */
77 return;
78
79 gscmd = create_dstr();
80 dstrncpy(gscmd, &cmd->data[start], end - start);
81
82 /* If Ghostscript does not support redirecting the standard output
83 of the PostScript program to standard error with '-sstdout=%stderr', sen
84 the job output data to fd 3; errors will be on 2(stderr) and job ps
85 program interpreter output on 1(stdout). */
86 if (gswithoutputredirection)
87 dstrreplace(gscmd, "-sOutputFile=- ", "-sOutputFile=%stdout ", 0);
88 else
89 dstrreplace(gscmd, "-sOutputFile=- ", "-sOutputFile=/dev/fd/3 ", 0);
90
91 /* Use always buffered input. This works around a Ghostscript
92 bug which prevents printing encrypted PDF files with Adobe Reader 8.1.1
93 and Ghostscript built as shared library (Ghostscript bug #689577, Ubuntu
94 bug #172264) */
95 if (dstrendswith(gscmd, " -"))
96 dstrcat(gscmd, "_");
97 else
98 dstrreplace(gscmd, " - ", " -_ ", 0);
99 dstrreplace(gscmd, " /dev/fd/0", " -_ ", 0);
100
101 /* Turn *off* -q (quiet!); now that stderr is useful! :) */
102 dstrreplace(gscmd, " -q ", " ", 0);
103
104 /* Escape any quotes, and then quote everything just to be sure...
105 Escaping a single quote inside single quotes is a bit complex as the
106 shell takes everything literal there. So we have to assemble it by
107 concatinating different quoted strings. Finally we get e.g.: 'x'"'"'y'
108 or ''"'"'xy' or 'xy'"'"'' or ... */
109 /* dstrreplace(cmd, "'", "'\"'\"'"); TODO tbd */
110
111 dstrremove(gscmd, 0, 2); /* Remove 'gs' */
112 if (gswithoutputredirection)
113 {
114 dstrprepend(gscmd, " -sstdout=%stderr ");
115 dstrprepend(gscmd, gspath);
116 }
117 else
118 {
119 dstrprepend(gscmd, gspath);
120 dstrcat(gscmd, " 3>&1 1>&2");
121 }
122
123 /* put gscmd back into cmd, between 'start' and 'end' */
124 cmdcopy = create_dstr();
125 dstrcpy(cmdcopy, cmd->data);
126
127 dstrncpy(cmd, cmdcopy->data, start);
128 dstrcat(cmd, gscmd->data);
129 dstrcat(cmd, &cmdcopy->data[end]);
130
131 free_dstr(gscmd);
132 free_dstr(cmdcopy);
133
134 /* If the renderer command line contains the "echo" command, replace the
135 * "echo" by the user-chosen $myecho (important for non-GNU systems where
136 * GNU echo is in a special path */
137 dstrreplace(cmd, "echo", echopath, 0); /* TODO search for \wecho\w */
138 }
139
read_line(FILE * stream,size_t * readbytes)140 char * read_line(FILE *stream, size_t *readbytes)
141 {
142 char *line;
143 size_t alloc = 64, len = 0;
144 int c;
145
146 line = malloc(alloc);
147
148 while ((c = fgetc(stream)) != EOF) {
149 if (len >= alloc -1) {
150 alloc *= 2;
151 line = realloc(line, alloc);
152 }
153 line[len] = (char)c;
154 len++;
155 if (c == '\n')
156 break;
157 }
158
159 line[len] = '\0';
160 *readbytes = len;
161 return line;
162 }
163
write_binary_data(FILE * stream,const char * data,size_t bytes)164 void write_binary_data(FILE *stream, const char *data, size_t bytes)
165 {
166 int i;
167 for (i=0; i < bytes; i++)
168 {
169 fputc(data[i], stream);
170 }
171 }
172
173 /*
174 * Read all lines containing 'jclstr' from 'stream' (actually, one more) and
175 * return them in a zero terminated array.
176 */
read_jcl_lines(FILE * stream,const char * jclstr,size_t * readbinarybytes)177 static char ** read_jcl_lines(FILE *stream, const char *jclstr,
178 size_t *readbinarybytes)
179 {
180 char *line;
181 char **result;
182 size_t alloc = 8, cnt = 0;
183
184 result = malloc(alloc * sizeof(char *));
185
186 /* read from the renderer output until the first non-JCL line appears */
187 while ((line = read_line(stream, readbinarybytes)))
188 {
189 if (cnt >= alloc -1)
190 {
191 alloc *= 2;
192 result = realloc(result, alloc * sizeof(char *));
193 }
194 result[cnt] = line;
195 if (!strstr(line, jclstr))
196 break;
197 /* Remove newline from the end of a line containing JCL */
198 result[cnt][*readbinarybytes - 1] = '\0';
199 cnt++;
200 }
201
202 cnt++;
203 result[cnt] = NULL;
204 return result;
205 }
206
jcl_keywords_equal(const char * jclline1,const char * jclline2,const char * jclstr)207 static int jcl_keywords_equal(const char *jclline1, const char *jclline2,
208 const char *jclstr)
209 {
210 char *j1, *j2, *p1, *p2;
211
212 j1 = strstr(jclline1, jclstr);
213 if (!j1) return 0;
214 if (!(p1 = strchr(skip_whitespace(j1), '=')))
215 p1 = j1 + strlen(j1);
216 p1--;
217 while (p1 > j1 && isspace(*p1))
218 p1--;
219
220 j2 = strstr(jclline2, jclstr);
221 if (!j2) return 0;
222 if (!(p2 = strchr(skip_whitespace(j2), '=')))
223 p2 = j2 + strlen(j2);
224 p2--;
225 while (p2 > j2 && isspace(*p2))
226 p2--;
227
228 if (p1 - j1 != p2 - j2) return 0;
229 return strncmp(j1, j2, p1 - j1 + 1) == 0;
230 }
231
232 /*
233 * Finds the keyword of line in opts
234 */
jcl_options_find_keyword(char ** opts,const char * line,const char * jclstr)235 static const char * jcl_options_find_keyword(char **opts, const char *line,
236 const char *jclstr)
237 {
238 if (!opts)
239 return NULL;
240
241 while (*opts)
242 {
243 if (jcl_keywords_equal(*opts, line, jclstr))
244 return *opts;
245 opts++;
246 }
247 return NULL;
248 }
249
argv_write(FILE * stream,char ** argv,const char * sep)250 static void argv_write(FILE *stream, char **argv, const char *sep)
251 {
252 if (!argv)
253 return;
254
255 while (*argv)
256 fprintf(stream, "%s%s", *argv++, sep);
257 }
258
259 /*
260 * Merges 'original_opts' and 'pref_opts' and writes them to 'stream'. Header /
261 * footer is taken from 'original_opts'. If both have the same options, the one
262 * from 'pref_opts' is preferred
263 * Returns true, if original_opts was not empty
264 */
write_merged_jcl_options(FILE * stream,char ** original_opts,char ** opts,size_t readbinarybytes,const char * jclstr)265 static int write_merged_jcl_options(FILE *stream,
266 char **original_opts,
267 char **opts,
268 size_t readbinarybytes,
269 const char *jclstr)
270 {
271 char *p = strstr(original_opts[0], jclstr);
272 char header[128];
273 char **optsp1 = NULL, **optsp2 = NULL;
274
275 /* No JCL options in original_opts, just prepend opts */
276 if (argv_count(original_opts) == 1)
277 {
278 fprintf(stream, "%s", jclbegin);
279 argv_write(stream, opts, "\n");
280 write_binary_data(stream, original_opts[0], readbinarybytes);
281 return 0;
282 }
283
284 if (argv_count(original_opts) == 2)
285 {
286 /* If we have only one line of JCL it is probably something like the
287 * "@PJL ENTER LANGUAGE=..." line which has to be in the end, but it
288 * also contains the "<esc>%-12345X" which has to be in the beginning
289 * of the job */
290 if (p)
291 fwrite_or_die(original_opts[0], 1, p - original_opts[0], stream);
292 else
293 fprintf(stream, "%s\n", original_opts[0]);
294
295 argv_write(stream, opts, "\n");
296
297 if (p)
298 fprintf(stream, "%s\n", p);
299
300 write_binary_data(stream, original_opts[1], readbinarybytes);
301 return 1;
302 }
303
304 /* Write jcl header */
305 strncpy(header, original_opts[0], p - original_opts[0]);
306 header[p - original_opts[0]] = '\0';
307 fprintf(stream, "%s", header);
308
309 /* Insert the JCL commands from the PPD file right before the first
310 "@PJL SET ..." line from the, if there are no "@PJL SET ..." lines,
311 directly before "@PJL ENTER LANGUAGE ...", otherwise after the JCL
312 commands from the driver */
313 for (optsp1 = original_opts; *(optsp1 + 1); optsp1++) {
314 if (optsp2 == NULL &&
315 ((strstr(*optsp1, "ENTER LANGUAGE") != NULL) ||
316 (strncasecmp(*optsp1, "@PJL SET ", 9) == 0))) {
317 for (optsp2 = opts; *optsp2; optsp2++)
318 if (!jcl_options_find_keyword(original_opts, *optsp2, jclstr))
319 fprintf(stream, "%s\n", *optsp2);
320 }
321 if (optsp1 != original_opts) p = *optsp1;
322 if (!p)
323 _log("write_merged_jcl_options() dereferences NULL pointer p\n");
324 if (jcl_options_find_keyword(opts, p, jclstr))
325 fprintf(stream, "%s\n", jcl_options_find_keyword(opts, p, jclstr));
326 else
327 fprintf(stream, "%s\n", p);
328 }
329 if (optsp2 == NULL)
330 for (optsp2 = opts; *optsp2; optsp2++)
331 if (!jcl_options_find_keyword(original_opts, *optsp2, jclstr))
332 fprintf(stream, "%s\n", *optsp2);
333
334 write_binary_data(stream, *optsp1, readbinarybytes);
335
336 return 1;
337 }
338
log_jcl()339 void log_jcl()
340 {
341 char **opt;
342
343 _log("JCL: %s", jclbegin);
344 if (jclprepend)
345 for (opt = jclprepend; *opt; opt++)
346 _log("%s\n", *opt);
347
348 _log("<job data> %s\n\n", jclappend->data);
349 }
350
exec_kid4(FILE * in,FILE * out,void * user_arg)351 int exec_kid4(FILE *in, FILE *out, void *user_arg)
352 {
353 FILE *fileh = open_postpipe();
354 int driverjcl = 0;
355 size_t readbinarybytes;
356
357 log_jcl();
358
359 /* wrap the JCL around the job data, if there are any options specified...
360 * Should the driver already have inserted JCL commands we merge our JCL
361 * header with the one from the driver */
362 if (argv_count(jclprepend) > 0)
363 {
364 if (!isspace(jclprepend[0][0]))
365 {
366 char *jclstr, **jclheader;
367 size_t pos;
368
369 pos = strcspn(jclprepend[0], " \t\n\r");
370 jclstr = malloc(pos +1);
371 strncpy(jclstr, jclprepend[0], pos);
372 jclstr[pos] = '\0';
373
374 jclheader = read_jcl_lines(in, jclstr, &readbinarybytes);
375
376 driverjcl = write_merged_jcl_options(fileh,
377 jclheader,
378 jclprepend,
379 readbinarybytes,
380 jclstr);
381
382 free(jclstr);
383 argv_free(jclheader);
384 }
385 else
386 /* No merging of JCL header possible, simply prepend it */
387 argv_write(fileh, jclprepend, "\n");
388 }
389
390 /* The job data */
391 copy_file(fileh, in, NULL, 0);
392
393 /* A JCL trailer */
394 if (argv_count(jclprepend) > 0 && !driverjcl)
395 fwrite_or_die(jclappend->data, jclappend->len, 1, fileh);
396
397 fclose(in);
398 if (fclose(fileh) != 0)
399 {
400 _log("error closing postpipe\n");
401 return EXIT_PRNERR_NORETRY_BAD_SETTINGS;
402 }
403
404 return EXIT_PRINTED;
405 }
406
exec_kid3(FILE * in,FILE * out,void * user_arg)407 int exec_kid3(FILE *in, FILE *out, void *user_arg)
408 {
409 dstr_t *commandline;
410 int kid4;
411 FILE *kid4in;
412 int status;
413
414 commandline = create_dstr();
415 dstrcpy(commandline, (const char *)user_arg);
416
417 kid4 = start_process("kid4", exec_kid4, NULL, &kid4in, NULL);
418 if (kid4 < 0) {
419 free_dstr(commandline);
420 return EXIT_PRNERR_NORETRY_BAD_SETTINGS;
421 }
422
423 if (in && dup2(fileno(in), fileno(stdin)) < 0) {
424 _log("kid3: Could not dup stdin\n");
425 fclose(kid4in);
426 free_dstr(commandline);
427 return EXIT_PRNERR_NORETRY_BAD_SETTINGS;
428 }
429 if (dup2(fileno(kid4in), fileno(stdout)) < 0) {
430 _log("kid3: Could not dup stdout to kid4\n");
431 fclose(kid4in);
432 free_dstr(commandline);
433 return EXIT_PRNERR_NORETRY_BAD_SETTINGS;
434 }
435 if (debug)
436 {
437 if (!redirect_log_to_stderr()) {
438 fclose(kid4in);
439 free_dstr(commandline);
440 return EXIT_PRNERR_NORETRY_BAD_SETTINGS;
441 }
442
443 /* Save the data supposed to be fed into the renderer also into a file*/
444 dstrprepend(commandline, "tee $(mktemp " LOG_FILE "-XXXXXX.ps) | ( ");
445 dstrcat(commandline, ")");
446 }
447
448 /* Actually run the thing */
449 status = run_system_process("renderer", commandline->data);
450
451 if (in)
452 fclose(in);
453 fclose(kid4in);
454 fclose(stdin);
455 fclose(stdout);
456 free_dstr(commandline);
457
458 if (WIFEXITED(status)) {
459 switch (WEXITSTATUS(status)) {
460 case 0: /* Success! */
461 /* wait for postpipe/output child */
462 wait_for_process(kid4);
463 _log("kid3 finished\n");
464 return EXIT_PRINTED;
465 case 1:
466 _log("Possible error on renderer command line or PostScript error. Check options.");
467 return EXIT_JOBERR;
468 case 139:
469 _log("The renderer may have dumped core.");
470 return EXIT_JOBERR;
471 case 141:
472 _log("A filter used in addition to the renderer itself may have failed.");
473 return EXIT_PRNERR;
474 case 243:
475 case 255: /* PostScript error? */
476 return EXIT_JOBERR;
477 }
478 }
479 else if (WIFSIGNALED(status)) {
480 switch (WTERMSIG(status)) {
481 case SIGUSR1:
482 return EXIT_PRNERR;
483 case SIGUSR2:
484 return EXIT_PRNERR_NORETRY;
485 case SIGTTIN:
486 return EXIT_ENGAGED;
487 }
488 }
489 return EXIT_PRNERR;
490 }
491
492