1 /* fileconverter.c
2  *
3  * Copyright (C) 2008 Till Kamppeter <till.kamppeter@gmail.com>
4  * Copyright (C) 2008 Lars Uebernickel <larsuebernickel@gmx.de>
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 
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <signal.h>
28 #include <unistd.h>
29 
30 #include "foomaticrip.h"
31 #include "options.h"
32 #include "process.h"
33 
34 
35 /*
36  * One of these fileconverters is used if the 'textfilter' option in the config file
37  * is not set. (Except if the spooler is CUPS, then 'texttops' is used
38  */
39 const char *fileconverters[][2] = {
40     { "a2ps", "a2ps -1 @@--medium=@@PAGESIZE@@ @@--center-title=@@JOBTITLE@@ -o -" },
41     { "enscript", "enscript -G @@-M @@PAGESIZE@@ @@-b \"Page $%|@@JOBTITLE@@ --margins=36:36:36:36 --mark-wrapped-lines=arrow --word-wrap -p-" },
42     { "mpage", "mpage -o -1 @@-b @@PAGESIZE@@ @@-H -h @@JOBTITLE@@ -m36l36b36t36r -f -P- -" },
43     { NULL, NULL }
44 };
45 
46 char fileconverter[PATH_MAX] = "";
47 
set_fileconverter(const char * fc)48 void set_fileconverter(const char *fc)
49 {
50     int i;
51     for (i = 0; fileconverters[i][0]; i++) {
52       if (!strcmp(fc, fileconverters[i][0])) {
53             strlcpy(fileconverter, fileconverters[i][1], PATH_MAX);
54 	    break;
55       }
56     }
57     if (!fileconverters[i][0])
58         strlcpy(fileconverter, fc, PATH_MAX);
59 }
60 
guess_fileconverter()61 int guess_fileconverter()
62 {
63     int i;
64     for (i = 0; fileconverters[i][0]; i++) {
65         if (find_in_path(fileconverters[i][0], getenv("PATH"), NULL)) {
66             strlcpy(fileconverter, fileconverters[i][1], PATH_MAX);
67             return 1;
68         }
69     }
70     return 0;
71 }
72 
73 /*
74  * Replace @@...@@PAGESIZE@@ and @@...@@JOBTITLE@@ with 'pagesize' and
75  * 'jobtitle' (if they are are not NULL). Returns a newly malloced string.
76  */
fileconverter_from_template(const char * fileconverter,const char * pagesize,const char * jobtitle)77 char * fileconverter_from_template(const char *fileconverter,
78         const char *pagesize, const char *jobtitle)
79 {
80     char *templstart, *templname;
81     const char *last = fileconverter;
82     char *res;
83     size_t len;
84 
85     len = strlen(fileconverter) +
86             (pagesize ? strlen(pagesize) : 0) +
87             (jobtitle ? strlen(jobtitle) : 0) +1;
88     res = malloc(len);
89     res[0] = '\0';
90 
91     while ((templstart = strstr(last, "@@"))) {
92         strncat(res, last, templstart - last);
93         templstart += 2;
94         templname = strstr(templstart, "@@");
95         if (!templname)
96             break;
97         if (startswith(templname, "@@PAGESIZE@@") && pagesize) {
98             strncat(res, templstart, templname - templstart);
99             strcat(res, pagesize);
100             last = templname + 12;
101         }
102         else if (startswith(templname, "@@JOBTITLE@@") && jobtitle) {
103             strncat(res, templstart, templname - templstart);
104             strcat(res, jobtitle);
105             while (templstart != templname) {
106                 if (*templstart == '\"') {
107                     strcat(res, "\"");
108                     break;
109                 }
110                 templstart++;
111             }
112             last = templname + 12;
113         }
114         else
115             last += strlen(res);
116     }
117     strlcat(res, last, len);
118 
119     return res;
120 }
121 
exec_kid2(FILE * in,FILE * out,void * user_arg)122 int exec_kid2(FILE *in, FILE *out, void *user_arg)
123 {
124     int n;
125     const char *alreadyread = (const char *)user_arg;
126     char tmp[1024];
127 
128     /* child, first part of the pipe, reading in the data from standard input
129      * and stuffing it into the file converter after putting in the already
130      * read data (in alreadyread) */
131 
132     _log("kid2: writing alreadyread\n");
133 
134     /* At first pass the data which we already read to the filter */
135     fwrite(alreadyread, 1, strlen(alreadyread), out);
136 
137     _log("kid2: Then read the rest from standard input\n");
138     /* Then read the rest from standard input */
139     while ((n = fread(tmp, 1, 1024, stdin)) > 0)
140         fwrite(tmp, 1, n, out);
141 
142     _log("kid2: Close out and stdin\n");
143     fclose(out);
144     fclose(stdin);
145 
146     _log("kid2 finished\n");
147     return EXIT_PRINTED;
148 }
149 
150 typedef struct {
151     const char *fileconv;
152     const char *alreadyread;
153 } kid1_userdata_t;
154 
exec_kid1(FILE * in,FILE * out,void * user_arg)155 int exec_kid1(FILE *in, FILE *out, void *user_arg)
156 {
157     int kid2;
158     FILE *kid2out;
159     int status;
160     const char *fileconv = ((kid1_userdata_t *)user_arg)->fileconv;
161     const char *alreadyread = ((kid1_userdata_t *)user_arg)->alreadyread;
162 
163     kid2 = start_process("kid2", exec_kid2, (void *)alreadyread, NULL, &kid2out);
164     if (kid2 < 0)
165         return EXIT_PRNERR_NORETRY_BAD_SETTINGS;
166 
167     if (spooler != SPOOLER_CUPS)
168         _log("file converter command: %s\n", fileconv);
169 
170     if (dup2(fileno(kid2out), fileno(stdin)) < 0) {
171         _log("kid1: Could not dup stdin\n");
172         fclose(kid2out);
173         return EXIT_PRNERR_NORETRY_BAD_SETTINGS;
174     }
175     if (dup2(fileno(out), fileno(stdout)) < 0) {
176         _log("kid1: Could not dup stdout\n");
177         fclose(kid2out);
178         return EXIT_PRNERR_NORETRY_BAD_SETTINGS;
179     }
180     if (debug && !redirect_log_to_stderr()) {
181         _log("Could not dup logh to stderr\n");
182         fclose(kid2out);
183         return EXIT_PRNERR_NORETRY_BAD_SETTINGS;
184     }
185 
186     /* Actually run the thing... */
187     status = run_system_process("fileconverter", fileconv);
188     fclose(out);
189     fclose(kid2out);
190     fclose(stdin);
191     fclose(stdout);
192 
193     if (WIFEXITED(status)) {
194         if (WEXITSTATUS(status) == 0) {
195             wait_for_process(kid2);
196             _log("kid1 finished\n");
197             return EXIT_PRINTED;
198         }
199     }
200     else if (WIFSIGNALED(status)) {
201         switch (WTERMSIG(status)) {
202             case SIGUSR1: return EXIT_PRNERR;
203             case SIGUSR2: return EXIT_PRNERR_NORETRY;
204             case SIGTTIN: return EXIT_ENGAGED;
205         }
206     }
207     return EXIT_PRNERR;
208 }
209 
210 
211 /*
212  *  This function is only used when the input data is not PostScript. Then it
213  *  runs a filter which converts non-PostScript files into PostScript. The user
214  *  can choose which filter he wants to use. The filter command line is
215  *  provided by 'fileconverter'.
216  */
get_fileconverter_handle(const char * alreadyread,FILE ** fd,pid_t * pid)217 void get_fileconverter_handle(const char *alreadyread, FILE **fd, pid_t *pid)
218 {
219     pid_t kid1;
220     FILE *kid1out;
221     const char *pagesize;
222     char *fileconv;
223     kid1_userdata_t kid1_userdata;
224 
225     _log("\nStarting converter for non-PostScript files\n");
226 
227     if (isempty(fileconverter) && !guess_fileconverter())
228         rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Cannot convert file to "
229                 "Postscript (missing fileconverter).");
230 
231     /* Use wider margins so that the pages come out completely on every printer
232      * model (especially HP inkjets) */
233     pagesize = option_get_value(find_option("PageSize"), optionset("header"));
234     if (pagesize && startswith(fileconverter, "a2ps")) {
235         if (!strcasecmp(pagesize, "letter"))
236             pagesize = "Letterdj";
237         else if (!strcasecmp(pagesize, "a4"))
238             pagesize = "A4dj";
239     }
240 
241     if (do_docs)
242         snprintf(get_current_job()->title, 128, "Documentation for the %s", printer_model);
243 
244     fileconv = fileconverter_from_template(fileconverter, pagesize, get_current_job()->title);
245 
246     kid1_userdata.fileconv = fileconv;
247     kid1_userdata.alreadyread = alreadyread;
248     kid1 = start_process("kid1", exec_kid1, &kid1_userdata, NULL, &kid1out);
249     if (kid1 < 0)
250         rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Cannot convert file to "
251                 "Postscript (Cannot fork for kid1!)\n");
252 
253     *fd = kid1out;
254     *pid = kid1;
255 
256     free(fileconv);
257 }
258 
close_fileconverter_handle(FILE * fileconverter_handle,pid_t fileconverter_pid)259 int close_fileconverter_handle(FILE *fileconverter_handle, pid_t fileconverter_pid)
260 {
261     int status;
262 
263     _log("\nClosing file converter\n");
264     fclose(fileconverter_handle);
265 
266     status = wait_for_process(fileconverter_pid);
267     if (WIFEXITED(status))
268         return WEXITSTATUS(status);
269     else
270         return EXIT_PRNERR_NORETRY_BAD_SETTINGS;
271 }
272 
273