• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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