1 /* spooler.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 "spooler.h"
25 #include "foomaticrip.h"
26 #include "util.h"
27 #include "options.h"
28 #include <stdlib.h>
29 #include <unistd.h>
30
spooler_name(int spooler)31 const char *spooler_name(int spooler)
32 {
33 switch (spooler) {
34 case SPOOLER_CUPS: return "cups";
35 case SPOOLER_SOLARIS: return "solaris";
36 case SPOOLER_LPD: return "lpd";
37 case SPOOLER_LPRNG: return "lprng";
38 case SPOOLER_GNULPR: return "gnu lpr";
39 case SPOOLER_PPR: return "ppr";
40 case SPOOLER_PPR_INT: return "ppr interface";
41 case SPOOLER_CPS: return "cps";
42 case SPOOLER_PDQ: return "pdq";
43 case SPOOLER_DIRECT: return "direct";
44 };
45 return "<unknown>";
46 }
47
48 /* This piece of PostScript code (initial idea 2001 by Michael
49 Allerhand (michael.allerhand at ed dot ac dot uk, vastly
50 improved by Till Kamppeter in 2002) lets Ghostscript output
51 the page accounting information which CUPS needs on standard
52 error.
53 Redesign by Helge Blischke (2004-11-17):
54 - As the PostScript job itself may define BeginPage and/or EndPage
55 procedures, or the alternate pstops filter may have inserted
56 such procedures, we make sure that the accounting routine
57 will safely coexist with those. To achieve this, we force
58 - the accountint stuff to be inserted at the very end of the
59 PostScript job's setup section,
60 - the accounting stuff just using the return value of the
61 existing EndPage procedure, if any (and providing a default one
62 if not).
63 - As PostScript jobs may contain calls to setpagedevice "between"
64 pages, e.g. to change media type, do in-job stapling, etc.,
65 we cannot rely on the "showpage count since last pagedevice
66 activation" but instead count the physical pages by ourselves
67 (in a global dictionary).
68 */
69 const char *accounting_prolog_code =
70 "[{\n"
71 "%% Code for writing CUPS accounting tags on standard error\n"
72 "\n"
73 "/cupsPSLevel2 % Determine whether we can do PostScript level 2 or newer\n"
74 " systemdict/languagelevel 2 copy\n"
75 " known{get exec}{pop pop 1}ifelse 2 ge\n"
76 "def\n"
77 "\n"
78 "cupsPSLevel2\n"
79 "{ % in case of level 2 or higher\n"
80 " currentglobal true setglobal % define a dictioary foomaticDict\n"
81 " globaldict begin % in global VM and establish a\n"
82 " /foomaticDict % pages count key there\n"
83 " <<\n"
84 " /PhysPages 0\n"
85 " >>def\n"
86 " end\n"
87 " setglobal\n"
88 "}if\n"
89 "\n"
90 "/cupsGetNumCopies { % Read the number of Copies requested for the current\n"
91 " % page\n"
92 " cupsPSLevel2\n"
93 " {\n"
94 " % PS Level 2+: Get number of copies from Page Device dictionary\n"
95 " currentpagedevice /NumCopies get\n"
96 " }\n"
97 " {\n"
98 " % PS Level 1: Number of copies not in Page Device dictionary\n"
99 " null\n"
100 " }\n"
101 " ifelse\n"
102 " % Check whether the number is defined, if it is \"null\" use #copies \n"
103 " % instead\n"
104 " dup null eq {\n"
105 " pop #copies\n"
106 " }\n"
107 " if\n"
108 " % Check whether the number is defined now, if it is still \"null\" use 1\n"
109 " % instead\n"
110 " dup null eq {\n"
111 " pop 1\n"
112 " } if\n"
113 "} bind def\n"
114 "\n"
115 "/cupsWrite { % write a string onto standard error\n"
116 " (%stderr) (w) file\n"
117 " exch writestring\n"
118 "} bind def\n"
119 "\n"
120 "/cupsFlush % flush standard error to make it sort of unbuffered\n"
121 "{\n"
122 " (%stderr)(w)file flushfile\n"
123 "}bind def\n"
124 "\n"
125 "cupsPSLevel2\n"
126 "{ % In language level 2, we try to do something reasonable\n"
127 " <<\n"
128 " /EndPage\n"
129 " [ % start the array that becomes the procedure\n"
130 " currentpagedevice/EndPage 2 copy known\n"
131 " {get} % get the existing EndPage procedure\n"
132 " {pop pop {exch pop 2 ne}bind}ifelse % there is none, define the default\n"
133 " /exec load % make sure it will be executed, whatever it is\n"
134 " /dup load % duplicate the result value\n"
135 " { % true: a sheet gets printed, do accounting\n"
136 " currentglobal true setglobal % switch to global VM ...\n"
137 " foomaticDict begin % ... and access our special dictionary\n"
138 " PhysPages 1 add % count the sheets printed (including this one)\n"
139 " dup /PhysPages exch def % and save the value\n"
140 " end % leave our dict\n"
141 " exch setglobal % return to previous VM\n"
142 " (PAGE: )cupsWrite % assemble and print the accounting string ...\n"
143 " 16 string cvs cupsWrite % ... the sheet count ...\n"
144 " ( )cupsWrite % ... a space ...\n"
145 " cupsGetNumCopies % ... the number of copies ...\n"
146 " 16 string cvs cupsWrite % ...\n"
147 " (\\n)cupsWrite % ... a newline\n"
148 " cupsFlush\n"
149 " }/if load\n"
150 " % false: current page gets discarded; do nothing \n"
151 " ]cvx bind % make the array executable and apply bind\n"
152 " >>setpagedevice\n"
153 "}\n"
154 "{\n"
155 " % In language level 1, we do no accounting currently, as there is no global VM\n"
156 " % the contents of which are undesturbed by save and restore. \n"
157 " % If we may be sure that showpage never gets called inside a page related save / restore pair\n"
158 " % we might implement an hack with showpage similar to the one above.\n"
159 "}ifelse\n"
160 "\n"
161 "} stopped cleartomark\n";
162
163
init_ppr(list_t * arglist,jobparams_t * job)164 void init_ppr(list_t *arglist, jobparams_t *job)
165 {
166 size_t arg_count = list_item_count(arglist);
167 char ppr_printer [256];
168 char ppr_address [128];
169 char ppr_options [1024];
170 char ppr_jobbreak [128];
171 char ppr_feedback [128];
172 char ppr_codes [128];
173 char ppr_jobname [128];
174 char ppr_routing [128];
175 char ppr_for [128] = "";
176 char ppr_filetype [128] = "";
177 char ppr_filetoprint [128] = "";
178 FILE *ph;
179 char tmp[256];
180 char *p;
181
182
183 /* TODO read interface.sh and signal.sh for exit and signal codes respectively */
184
185 /* Check whether we run as a PPR interface (if not, we run as a PPR RIP)
186 PPR calls interfaces with many command line parameters,
187 where the forth and the sixth is a small integer
188 number. In addition, we have 8 (PPR <= 1.31), 10
189 (PPR>=1.32), 11 (PPR >= 1.50) command line parameters.
190 We also check whether the current working directory is a
191 PPR directory. */
192 if ((arg_count == 11 || arg_count == 10 || arg_count == 8) &&
193 atoi(arglist_get(arglist, 3)) < 100 && atoi(arglist_get(arglist, 5)) < 100)
194 {
195 /* get all command line parameters */
196 strncpy_omit(ppr_printer, arglist_get(arglist, 0), 256, omit_shellescapes);
197 strlcpy(ppr_address, arglist_get(arglist, 1), 128);
198 strncpy_omit(ppr_options, arglist_get(arglist, 2), 1024, omit_shellescapes);
199 strlcpy(ppr_jobbreak, arglist_get(arglist, 3), 128);
200 strlcpy(ppr_feedback, arglist_get(arglist, 4), 128);
201 strlcpy(ppr_codes, arglist_get(arglist, 5), 128);
202 strncpy_omit(ppr_jobname, arglist_get(arglist, 6), 128, omit_shellescapes);
203 strncpy_omit(ppr_routing, arglist_get(arglist, 7), 128, omit_shellescapes);
204 if (arg_count >= 8) {
205 strlcpy(ppr_for, arglist_get(arglist, 8), 128);
206 strlcpy(ppr_filetype, arglist_get(arglist, 9), 128);
207 if (arg_count >= 10)
208 strncpy_omit(ppr_filetoprint, arglist_get(arglist, 10), 128, omit_shellescapes);
209 }
210
211 /* Common job parameters */
212 strcpy(job->printer, ppr_printer);
213 strcpy(job->title, ppr_jobname);
214 if (isempty(job->title) && !isempty(ppr_filetoprint))
215 strcpy(job->title, ppr_filetoprint);
216 dstrcatf(job->optstr, " %s %s", ppr_options, ppr_routing);
217
218 /* Get the path of the PPD file from the queue configuration */
219 snprintf(tmp, 255, "LANG=en_US; ppad show %s | grep PPDFile", ppr_printer);
220 tmp[255] = '\0';
221 ph = popen(tmp, "r");
222 if (ph) {
223 fgets(tmp, 255, ph);
224 tmp[255] = '\0';
225 pclose(ph);
226
227 strncpy_omit(job->ppdfile, tmp, 255, omit_shellescapes);
228 if (job->ppdfile[0] == '/') {
229 strcpy(tmp, job->ppdfile);
230 strcpy(job->ppdfile, "../../share/ppr/PPDFiles/");
231 strncat(job->ppdfile, tmp, 200);
232 }
233 if ((p = strrchr(job->ppdfile, '\n')))
234 *p = '\0';
235 }
236 else {
237 job->ppdfile[0] = '\0';
238 }
239
240 /* We have PPR and run as an interface */
241 spooler = SPOOLER_PPR_INT;
242 }
243 }
244
init_cups(list_t * arglist,dstr_t * filelist,jobparams_t * job)245 void init_cups(list_t *arglist, dstr_t *filelist, jobparams_t *job)
246 {
247 char path [1024] = "";
248 char cups_jobid [128];
249 char cups_user [128];
250 char cups_jobtitle [128];
251 char cups_copies [128];
252 int cups_options_len;
253 char *cups_options;
254 char cups_filename [256];
255 char texttopspath[PATH_MAX];
256
257 if (getenv("CUPS_FONTPATH"))
258 strcpy(path, getenv("CUPS_FONTPATH"));
259 else if (getenv("CUPS_DATADIR")) {
260 strcpy(path, getenv("CUPS_DATADIR"));
261 strcat(path, "/fonts");
262 }
263 if (getenv("GS_LIB")) {
264 strcat(path, ":");
265 strcat(path, getenv("GS_LIB"));
266 }
267 setenv("GS_LIB", path, 1);
268
269 /* Get all command line parameters */
270 strncpy_omit(cups_jobid, arglist_get(arglist, 0), 128, omit_shellescapes);
271 strncpy_omit(cups_user, arglist_get(arglist, 1), 128, omit_shellescapes);
272 strncpy_omit(cups_jobtitle, arglist_get(arglist, 2), 128, omit_shellescapes);
273 strncpy_omit(cups_copies, arglist_get(arglist, 3), 128, omit_shellescapes);
274
275 cups_options_len = strlen(arglist_get(arglist, 4));
276 cups_options = malloc(cups_options_len + 1);
277 strncpy_omit(cups_options, arglist_get(arglist, 4), cups_options_len + 1, omit_shellescapes);
278
279 /* Common job parameters */
280 strcpy(job->id, cups_jobid);
281 strcpy(job->title, cups_jobtitle);
282 strcpy(job->user, cups_user);
283 strcpy(job->copies, cups_copies);
284 dstrcatf(job->optstr, " %s", cups_options);
285
286 /* Check for and handle inputfile vs stdin */
287 if (list_item_count(arglist) > 4) {
288 strncpy_omit(cups_filename, arglist_get(arglist, 5), 256, omit_shellescapes);
289 if (cups_filename[0] != '-') {
290 /* We get input from a file */
291 dstrcatf(filelist, "%s ", cups_filename);
292 _log("Getting input from file %s\n", cups_filename);
293 }
294 }
295
296 accounting_prolog = accounting_prolog_code;
297
298 /* On which queue are we printing?
299 CUPS gives the PPD file the same name as the printer queue,
300 so we can get the queue name from the name of the PPD file. */
301 file_basename(job->printer, job->ppdfile, 256);
302
303 /* Use cups' texttops if no fileconverter is set
304 * Apply "pstops" when having used a file converter under CUPS, so CUPS
305 * can stuff the default settings into the PostScript output of the file
306 * converter (so all CUPS settings get also applied when one prints the
307 * documentation pages (all other files we get already converted to
308 * PostScript by CUPS). */
309 if (isempty(fileconverter)) {
310 if (find_in_path("texttops", cupsfilterpath, texttopspath)) {
311 snprintf(fileconverter, PATH_MAX, "%s/texttops '%s' '%s' '%s' '%s' "
312 "page-top=36 page-bottom=36 page-left=36 page-right=36 "
313 "nolandscape cpi=12 lpi=7 columns=1 wrap %s'"
314 "| %s/pstops '%s' '%s' '%s' '%s' '%s'",
315 texttopspath, cups_jobid, cups_user, cups_jobtitle, cups_copies, cups_options,
316 texttopspath, cups_jobid, cups_user, cups_jobtitle, cups_copies, cups_options);
317 }
318 }
319
320 free(cups_options);
321 }
322
init_solaris(list_t * arglist,dstr_t * filelist,jobparams_t * job)323 void init_solaris(list_t *arglist, dstr_t *filelist, jobparams_t *job)
324 {
325 char *str;
326 int len;
327 listitem_t *i;
328
329 /* Get all command line parameters */
330 strncpy_omit(job->title, arglist_get(arglist, 2), 128, omit_shellescapes);
331
332 len = strlen(arglist_get(arglist, 4));
333 str = malloc(len +1);
334 strncpy_omit(str, arglist_get(arglist, 4), len, omit_shellescapes);
335 dstrcatf(job->optstr, " %s", str);
336 free(str);
337
338 for (i = arglist->first; i; i = i->next)
339 dstrcatf(filelist, "%s ", (char*)i->data);
340 }
341
342 /* used by init_direct_cps_pdq to find a ppd file */
find_ppdfile(const char * user_default_path,jobparams_t * job)343 int find_ppdfile(const char *user_default_path, jobparams_t *job)
344 {
345 /* Search also common spooler-specific locations, this way a printer
346 configured under a certain spooler can also be used without spooler */
347
348 strcpy(job->ppdfile, job->printer);
349 if (access(job->ppdfile, R_OK) == 0)
350 return 1;
351
352 /* CPS can have the PPD in the spool directory */
353 if (spooler == SPOOLER_CPS) {
354 snprintf(job->ppdfile, 256, "/var/spool/lpd/%s/%s.ppd", job->printer, job->printer);
355 if (access(job->ppdfile, R_OK) == 0)
356 return 1;
357 snprintf(job->ppdfile, 256, "/var/local/spool/lpd/%s/%s.ppd", job->printer, job->printer);
358 if (access(job->ppdfile, R_OK) == 0)
359 return 1;
360 snprintf(job->ppdfile, 256, "/var/local/lpd/%s/%s.ppd", job->printer, job->printer);
361 if (access(job->ppdfile, R_OK) == 0)
362 return 1;
363 snprintf(job->ppdfile, 256, "/var/spool/lpd/%s.ppd", job->printer);
364 if (access(job->ppdfile, R_OK) == 0)
365 return 1;
366 snprintf(job->ppdfile, 256, "/var/local/spool/lpd/%s.ppd", job->printer);
367 if (access(job->ppdfile, R_OK) == 0)
368 return 1;
369 snprintf(job->ppdfile, 256, "/var/local/lpd/%s.ppd", job->printer);
370 if (access(job->ppdfile, R_OK) == 0)
371 return 1;
372 }
373 snprintf(job->ppdfile, 256, "%s.ppd", job->printer); /* current dir */
374 if (access(job->ppdfile, R_OK) == 0)
375 return 1;
376 snprintf(job->ppdfile, 256, "%s/%s.ppd", user_default_path, job->printer); /* user dir */
377 if (access(job->ppdfile, R_OK) == 0)
378 return 1;
379 snprintf(job->ppdfile, 256, "%s/direct/%s.ppd", CONFIG_PATH, job->printer); /* system dir */
380 if (access(job->ppdfile, R_OK) == 0)
381 return 1;
382 snprintf(job->ppdfile, 256, "%s/%s.ppd", CONFIG_PATH, job->printer); /* system dir */
383 if (access(job->ppdfile, R_OK) == 0)
384 return 1;
385 snprintf(job->ppdfile, 256, "/etc/cups/ppd/%s.ppd", job->printer); /* CUPS config dir */
386 if (access(job->ppdfile, R_OK) == 0)
387 return 1;
388 snprintf(job->ppdfile, 256, "/usr/local/etc/cups/ppd/%s.ppd", job->printer); /* CUPS config dir */
389 if (access(job->ppdfile, R_OK) == 0)
390 return 1;
391 snprintf(job->ppdfile, 256, "/usr/share/ppr/PPDFiles/%s.ppd", job->printer); /* PPR PPDs */
392 if (access(job->ppdfile, R_OK) == 0)
393 return 1;
394 snprintf(job->ppdfile, 256, "/usr/local/share/ppr/PPDFiles/%s.ppd", job->printer); /* PPR PPDs */
395 if (access(job->ppdfile, R_OK) == 0)
396 return 1;
397
398 /* nothing found */
399 job->ppdfile[0] = '\0';
400 return 0;
401 }
402
403 /* search 'configfile' for 'key', copy value into dest, return success */
configfile_find_option(const char * configfile,const char * key,char * dest,size_t destsize)404 int configfile_find_option(const char *configfile, const char *key, char *dest, size_t destsize)
405 {
406 FILE *fh;
407 char line [1024];
408 char *p;
409
410 dest[0] = '\0';
411
412 if (!(fh = fopen(configfile, "r")))
413 return 0;
414
415 while (fgets(line, 1024, fh)) {
416 if (!prefixcmp(line, "default")) {
417 p = strchr(line, ':');
418 if (p) {
419 strncpy_omit(dest, p + 1, destsize, omit_whitespace_newline);
420 if (dest[0])
421 break;
422 }
423 }
424 }
425 fclose(fh);
426 return dest[0] != '\0';
427 }
428
429 /* tries to find a default printer name in various config files and copies the
430 * result into the global var 'printer'. Returns success */
find_default_printer(const char * user_default_path,jobparams_t * job)431 int find_default_printer(const char *user_default_path, jobparams_t *job)
432 {
433 char configfile [1024];
434 char *key = "default";
435
436 if (configfile_find_option("./.directconfig", key, job->printer, 256))
437 return 1;
438 if (configfile_find_option("./directconfig", key, job->printer, 256))
439 return 1;
440 if (configfile_find_option("./.config", key, job->printer, 256))
441 return 1;
442 strlcpy(configfile, user_default_path, 1024);
443 strlcat(configfile, "/direct/.config", 1024);
444 if (configfile_find_option(configfile, key, job->printer, 256))
445 return 1;
446 strlcpy(configfile, user_default_path, 1024);
447 strlcat(configfile, "/direct.conf", 1024);
448 if (configfile_find_option(configfile, key, job->printer, 256))
449 return 1;
450 if (configfile_find_option(CONFIG_PATH "/direct/.config", key, job->printer, 256))
451 return 1;
452 if (configfile_find_option(CONFIG_PATH "/direct.conf", key, job->printer, 256))
453 return 1;
454
455 return 0;
456 }
457
init_direct_cps_pdq(list_t * arglist,dstr_t * filelist,jobparams_t * job)458 void init_direct_cps_pdq(list_t *arglist, dstr_t *filelist, jobparams_t *job)
459 {
460 char tmp [1024];
461 listitem_t *i;
462 char user_default_path [PATH_MAX];
463
464 strlcpy(user_default_path, getenv("HOME"), 256);
465 strlcat(user_default_path, "/.foomatic/", 256);
466
467 /* Which files do we want to print? */
468 for (i = arglist->first; i; i = i->next) {
469 strncpy_omit(tmp, (char*)i->data, 1024, omit_shellescapes);
470 dstrcatf(filelist, "%s ", tmp);
471 }
472
473 if (job->ppdfile[0] == '\0') {
474 if (job->printer[0] == '\0') {
475 /* No printer definition file selected, check whether we have a
476 default printer defined */
477 find_default_printer(user_default_path, job);
478 }
479
480 /* Neither in a config file nor on the command line a printer was selected */
481 if (!job->printer[0]) {
482 _log("No printer definition (option \"-P <name>\") specified!\n");
483 exit(EXIT_PRNERR_NORETRY_BAD_SETTINGS);
484 }
485
486 /* Search for the PPD file */
487 if (!find_ppdfile(user_default_path, job)) {
488 _log("There is no readable PPD file for the printer %s, is it configured?\n", job->printer);
489 exit(EXIT_PRNERR_NORETRY_BAD_SETTINGS);
490 }
491 }
492 }
493
494