]> git.ipfire.org Git - thirdparty/cups.git/blob - scheduler/cupsfilter.c
Load cups into easysw/current.
[thirdparty/cups.git] / scheduler / cupsfilter.c
1 /*
2 * "$Id: cupsfilter.c 6668 2007-07-13 23:09:49Z mike $"
3 *
4 * CUPS filtering program for the Common UNIX Printing System (CUPS).
5 *
6 * Copyright 2007 by Apple Inc.
7 * Copyright 1997-2006 by Easy Software Products, all rights reserved.
8 *
9 * These coded instructions, statements, and computer programs are the
10 * property of Apple Inc. and are protected by Federal copyright
11 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
12 * which should have been included with this file. If this file is
13 * file is missing or damaged, see the license at "http://www.cups.org/".
14 *
15 * Contents:
16 *
17 * main() - Main entry for the test program.
18 * compare_pids() - Compare two filter PIDs...
19 * escape_options() - Convert an options array to a string.
20 * exec_filter() - Execute a single filter.
21 * exec_filters() - Execute filters for the given file and options.
22 * open_pipe() - Create a pipe which is closed on exec.
23 * read_cupsd_conf() - Read the cupsd.conf file to get the filter settings.
24 * set_string() - Copy and set a string.
25 * usage() - Show program usage...
26 */
27
28 /*
29 * Include necessary headers...
30 */
31
32 #include <cups/cups.h>
33 #include <cups/i18n.h>
34 #include <cups/string.h>
35 #include <errno.h>
36 #include "mime.h"
37 #include <stdlib.h>
38 #include <unistd.h>
39 #include <fcntl.h>
40 #include <sys/wait.h>
41 #if defined(__APPLE__)
42 # include <libgen.h>
43 #endif /* __APPLE__ */
44
45
46 /*
47 * Local globals...
48 */
49
50 static char *DataDir = NULL;/* CUPS_DATADIR environment variable */
51 static char *FontPath = NULL;
52 /* CUPS_FONTPATH environment variable */
53 static mime_filter_t GZIPFilter = /* gziptoany filter */
54 {
55 NULL, /* Source type */
56 NULL, /* Destination type */
57 0, /* Cost */
58 "gziptoany" /* Filter program to run */
59 };
60 static char *Path = NULL; /* PATH environment variable */
61 static char *ServerBin = NULL;
62 /* CUPS_SERVERBIN environment variable */
63 static char *ServerRoot = NULL;
64 /* CUPS_SERVERROOT environment variable */
65 static char *RIPCache = NULL;
66 /* RIP_CACHE environment variable */
67
68
69 /*
70 * Local functions...
71 */
72
73 static int compare_pids(mime_filter_t *a, mime_filter_t *b);
74 static char *escape_options(int num_options, cups_option_t *options);
75 static int exec_filter(const char *filter, char **argv, char **envp,
76 int infd, int outfd);
77 static int exec_filters(cups_array_t *filters, const char *filename,
78 const char *ppdfile, const char *title,
79 int num_options, cups_option_t *options);
80 static int open_pipe(int *fds);
81 static int read_cupsd_conf(const char *filename);
82 static void set_string(char **s, const char *val);
83 static void usage(const char *opt);
84
85
86 /*
87 * 'main()' - Main entry for the test program.
88 */
89
90 int /* O - Exit status */
91 main(int argc, /* I - Number of command-line args */
92 char *argv[]) /* I - Command-line arguments */
93 {
94 int i; /* Looping vars */
95 const char *opt; /* Current option */
96 char super[MIME_MAX_SUPER], /* Super-type name */
97 type[MIME_MAX_TYPE]; /* Type name */
98 int compression; /* Compression of file */
99 int cost; /* Cost of filters */
100 mime_t *mime; /* MIME database */
101 char *filename; /* File to filter */
102 char cupsdconf[1024]; /* cupsd.conf file */
103 const char *server_root; /* CUPS_SERVERROOT environment variable */
104 mime_type_t *src, /* Source type */
105 *dst; /* Destination type */
106 cups_array_t *filters; /* Filters for the file */
107 int num_options; /* Number of options */
108 cups_option_t *options; /* Options */
109 const char *ppdfile; /* PPD file */
110 const char *title; /* Title string */
111
112
113 /*
114 * Setup defaults...
115 */
116
117 mime = NULL;
118 src = NULL;
119 dst = NULL;
120 filename = NULL;
121 num_options = 0;
122 options = NULL;
123 ppdfile = NULL;
124 title = NULL;
125 super[0] = '\0';
126 type[0] = '\0';
127
128 if ((server_root = getenv("CUPS_SERVERROOT")) == NULL)
129 server_root = CUPS_SERVERROOT;
130
131 snprintf(cupsdconf, sizeof(cupsdconf), "%s/cupsd.conf", server_root);
132
133 /*
134 * Process command-line arguments...
135 */
136
137 _cupsSetLocale(argv);
138
139 for (i = 1; i < argc; i ++)
140 if (argv[i][0] == '-')
141 {
142 for (opt = argv[i] + 1; *opt; opt ++)
143 switch (*opt)
144 {
145 case '-' : /* Next argument is a filename... */
146 i ++;
147 if (i < argc && !filename)
148 filename = argv[i];
149 else
150 usage(opt);
151 break;
152
153 case 'c' : /* Specify cupsd.conf file location... */
154 i ++;
155 if (i < argc)
156 strlcpy(cupsdconf, argv[i], sizeof(cupsdconf));
157 else
158 usage(opt);
159 break;
160
161 case 'm' : /* Specify destination MIME type... */
162 i ++;
163 if (i < argc)
164 {
165 if (sscanf(argv[i], "%15[^/]/%255s", super, type) != 2)
166 usage(opt);
167 }
168 else
169 usage(opt);
170 break;
171
172 case 'n' : /* Specify number of copies... */
173 i ++;
174 if (i < argc)
175 num_options = cupsAddOption("copies", argv[i], num_options,
176 &options);
177 else
178 usage(opt);
179 break;
180
181 case 'o' : /* Specify option... */
182 i ++;
183 if (i < argc)
184 num_options = cupsParseOptions(argv[i], num_options, &options);
185 else
186 usage(opt);
187 break;
188
189 case 'p' : /* Specify PPD file... */
190 i ++;
191 if (i < argc)
192 ppdfile = argv[i];
193 else
194 usage(opt);
195 break;
196
197 case 't' : /* Specify number of copies... */
198 i ++;
199 if (i < argc)
200 title = argv[i];
201 else
202 usage(opt);
203 break;
204
205 default : /* Something we don't understand... */
206 usage(opt);
207 break;
208 }
209 }
210 else if (!filename)
211 filename = argv[i];
212 else
213 {
214 _cupsLangPuts(stderr,
215 _("cupsfilter: Only one filename can be specified!\n"));
216 usage(NULL);
217 }
218
219 if (!filename || !super[0] || !type[0])
220 usage(NULL);
221
222 if (!title)
223 {
224 if ((title = strrchr(filename, '/')) != NULL)
225 title ++;
226 else
227 title = filename;
228 }
229
230 /*
231 * Load the cupsd.conf file and create the MIME database...
232 */
233
234 if (read_cupsd_conf(cupsdconf))
235 return (1);
236
237 if ((mime = mimeLoad(ServerRoot, Path)) == NULL)
238 {
239 _cupsLangPrintf(stderr,
240 _("cupsfilter: Unable to read MIME database from \"%s\"!\n"),
241 ServerRoot);
242 return (1);
243 }
244
245 /*
246 * Get the source and destination types...
247 */
248
249 if ((src = mimeFileType(mime, filename, filename, &compression)) == NULL)
250 {
251 _cupsLangPrintf(stderr,
252 _("cupsfilter: Unable to determine MIME type of \"%s\"!\n"),
253 filename);
254 return (1);
255 }
256
257 if ((dst = mimeType(mime, super, type)) == NULL)
258 {
259 _cupsLangPrintf(stderr,
260 _("cupsfilter: Unknown destination MIME type %s/%s!\n"),
261 super, type);
262 return (1);
263 }
264
265 /*
266 * Figure out how to filter the file...
267 */
268
269 if (src == dst)
270 {
271 /*
272 * Special case - no filtering needed...
273 */
274
275 filters = cupsArrayNew(NULL, NULL);
276 cupsArrayAdd(filters, &GZIPFilter);
277 }
278 else if ((filters = mimeFilter(mime, src, dst, &cost)) == NULL)
279 {
280 _cupsLangPrintf(stderr,
281 _("cupsfilter: No filter to convert from %s/%s to %s/%s!\n"),
282 src->super, src->type, dst->super, dst->type);
283 return (1);
284 }
285 else if (compression)
286 cupsArrayInsert(filters, &GZIPFilter);
287
288 /*
289 * Do it!
290 */
291
292 return (exec_filters(filters, filename, ppdfile, title, num_options, options));
293 }
294
295
296 /*
297 * 'compare_pids()' - Compare two filter PIDs...
298 */
299
300 static int /* O - Result of comparison */
301 compare_pids(mime_filter_t *a, /* I - First filter */
302 mime_filter_t *b) /* I - Second filter */
303 {
304 /*
305 * Because we're particularly lazy, we store the process ID in the "cost"
306 * variable...
307 */
308
309 return (a->cost - b->cost);
310 }
311
312
313 /*
314 * 'escape_options()' - Convert an options array to a string.
315 */
316
317 static char * /* O - Option string */
318 escape_options(
319 int num_options, /* I - Number of options */
320 cups_option_t *options) /* I - Options */
321 {
322 int i; /* Looping var */
323 cups_option_t *option; /* Current option */
324 int bytes; /* Number of bytes needed */
325 char *s, /* Option string */
326 *sptr, /* Pointer into string */
327 *vptr; /* Pointer into value */
328
329
330 /*
331 * Figure out the worst-case number of bytes we need for the option string.
332 */
333
334 for (i = num_options, option = options, bytes = 1; i > 0; i --, option ++)
335 bytes += 2 * (strlen(option->name) + strlen(option->value)) + 2;
336
337 s = malloc(bytes);
338
339 /*
340 * Copy the options to the string...
341 */
342
343 for (i = num_options, option = options, sptr = s; i > 0; i --, option ++)
344 {
345 if (!strcmp(option->name, "copies"))
346 continue;
347
348 if (sptr > s)
349 *sptr++ = ' ';
350
351 strcpy(sptr, option->name);
352 sptr += strlen(sptr);
353 *sptr++ = '=';
354
355 for (vptr = option->value; *vptr;)
356 {
357 if (strchr("\\ \t\n", *vptr))
358 *sptr++ = '\\';
359
360 *sptr++ = *vptr++;
361 }
362 }
363
364 *sptr = '\0';
365
366 fprintf(stderr, "DEBUG: options=\"%s\"\n", s);
367
368 return (s);
369 }
370
371
372 /*
373 * 'exec_filter()' - Execute a single filter.
374 */
375
376 static int /* O - Process ID or -1 on error */
377 exec_filter(const char *filter, /* I - Filter to execute */
378 char **argv, /* I - Argument list */
379 char **envp, /* I - Environment list */
380 int infd, /* I - Stdin file descriptor */
381 int outfd) /* I - Stdout file descriptor */
382 {
383 int pid; /* Process ID */
384 #if defined(__APPLE__)
385 char processPath[1024], /* CFProcessPath environment variable */
386 linkpath[1024]; /* Link path for symlinks... */
387 int linkbytes; /* Bytes for link path */
388
389
390 /*
391 * Add special voodoo magic for MacOS X - this allows MacOS X
392 * programs to access their bundle resources properly...
393 */
394
395 if ((linkbytes = readlink(filter, linkpath, sizeof(linkpath) - 1)) > 0)
396 {
397 /*
398 * Yes, this is a symlink to the actual program, nul-terminate and
399 * use it...
400 */
401
402 linkpath[linkbytes] = '\0';
403
404 if (linkpath[0] == '/')
405 snprintf(processPath, sizeof(processPath), "CFProcessPath=%s",
406 linkpath);
407 else
408 snprintf(processPath, sizeof(processPath), "CFProcessPath=%s/%s",
409 dirname((char *)filter), linkpath);
410 }
411 else
412 snprintf(processPath, sizeof(processPath), "CFProcessPath=%s", filter);
413
414 envp[0] = processPath; /* Replace <CFProcessPath> string */
415 #endif /* __APPLE__ */
416
417 if ((pid = fork()) == 0)
418 {
419 /*
420 * Child process goes here...
421 *
422 * Update stdin/stdout/stderr as needed...
423 */
424
425 if (infd != 0)
426 {
427 close(0);
428 if (infd > 0)
429 dup(infd);
430 else
431 open("/dev/null", O_RDONLY);
432 }
433
434 if (outfd != 1)
435 {
436 close(1);
437 if (outfd > 0)
438 dup(outfd);
439 else
440 open("/dev/null", O_WRONLY);
441 }
442
443 close(3);
444 open("/dev/null", O_RDWR);
445 fcntl(3, F_SETFL, O_NDELAY);
446
447 close(4);
448 open("/dev/null", O_RDWR);
449 fcntl(4, F_SETFL, O_NDELAY);
450
451 /*
452 * Execute command...
453 */
454
455 execve(filter, argv, envp);
456
457 perror(filter);
458
459 exit(errno);
460 }
461
462 return (pid);
463 }
464
465
466 /*
467 * 'exec_filters()' - Execute filters for the given file and options.
468 */
469
470 static int /* O - 0 on success, 1 on error */
471 exec_filters(cups_array_t *filters, /* I - Array of filters to run */
472 const char *filename, /* I - File to filter */
473 const char *ppdfile, /* I - PPD file, if any */
474 const char *title, /* I - Job title */
475 int num_options, /* I - Number of filter options */
476 cups_option_t *options) /* I - Filter options */
477 {
478 const char *argv[8], /* Command-line arguments */
479 *envp[11], /* Environment variables */
480 *temp; /* Temporary string */
481 char *optstr, /* Filter options */
482 cups_datadir[1024], /* CUPS_DATADIR */
483 cups_fontpath[1024], /* CUPS_FONTPATH */
484 cups_serverbin[1024], /* CUPS_SERVERBIN */
485 cups_serverroot[1024], /* CUPS_SERVERROOT */
486 lang[1024], /* LANG */
487 path[1024], /* PATH */
488 ppd[1024], /* PPD */
489 rip_cache[1024], /* RIP_CACHE */
490 user[1024], /* USER */
491 program[1024]; /* Program to run */
492 mime_filter_t *filter, /* Current filter */
493 *next; /* Next filter */
494 int current, /* Current filter */
495 filterfds[2][2], /* Pipes for filters */
496 pid, /* Process ID of filter */
497 status, /* Exit status */
498 retval; /* Return value */
499 cups_array_t *pids; /* Executed filters array */
500 mime_filter_t key; /* Search key for filters */
501
502
503 /*
504 * Setup the filter environment and command-line...
505 */
506
507 optstr = escape_options(num_options, options);
508
509 snprintf(cups_datadir, sizeof(cups_datadir), "CUPS_DATADIR=%s", DataDir);
510 snprintf(cups_fontpath, sizeof(cups_fontpath), "CUPS_FONTPATH=%s", FontPath);
511 snprintf(cups_serverbin, sizeof(cups_serverbin), "CUPS_SERVERBIN=%s",
512 ServerBin);
513 snprintf(cups_serverroot, sizeof(cups_serverroot), "CUPS_SERVERROOT=%s",
514 ServerRoot);
515 if ((temp = getenv("LANG")) != NULL)
516 snprintf(lang, sizeof(lang), "LANG=%s", temp);
517 else if ((temp = getenv("LC_ALL")) != NULL)
518 snprintf(lang, sizeof(lang), "LC_ALL=%s", temp);
519 else
520 strcpy(lang, "LANG=C");
521 snprintf(path, sizeof(path), "PATH=%s", Path);
522 if (ppdfile)
523 snprintf(ppd, sizeof(ppd), "PPD=%s", ppdfile);
524 else if ((temp = getenv("PPD")) != NULL)
525 snprintf(ppd, sizeof(ppd), "PPD=%s", temp);
526 else
527 snprintf(ppd, sizeof(ppd), "PPD=%s/model/laserjet.ppd", DataDir);
528 snprintf(rip_cache, sizeof(rip_cache), "RIP_CACHE=%s", RIPCache);
529 snprintf(user, sizeof(user), "USER=%s", cupsUser());
530
531 argv[0] = "cupsfilter";
532 argv[1] = "0";
533 argv[2] = cupsUser();
534 argv[3] = title;
535 argv[4] = cupsGetOption("copies", num_options, options);
536 argv[5] = optstr;
537 argv[6] = filename;
538 argv[7] = NULL;
539
540 if (!argv[4])
541 argv[4] = "1";
542
543 envp[0] = "<CFProcessPath>";
544 envp[1] = cups_datadir;
545 envp[2] = cups_fontpath;
546 envp[3] = cups_serverbin;
547 envp[4] = cups_serverroot;
548 envp[5] = lang;
549 envp[6] = path;
550 envp[7] = ppd;
551 envp[8] = rip_cache;
552 envp[9] = user;
553 envp[10] = NULL;
554
555 /*
556 * Execute all of the filters...
557 */
558
559 pids = cupsArrayNew((cups_array_func_t)compare_pids, NULL);
560 current = 0;
561 filterfds[0][0] = -1;
562 filterfds[0][1] = -1;
563 filterfds[1][0] = -1;
564 filterfds[1][1] = -1;
565
566 for (filter = (mime_filter_t *)cupsArrayFirst(filters);
567 filter;
568 filter = next, current = 1 - current)
569 {
570 next = (mime_filter_t *)cupsArrayNext(filters);
571
572 if (filter->filter[0] == '/')
573 strlcpy(program, filter->filter, sizeof(program));
574 else
575 snprintf(program, sizeof(program), "%s/filter/%s", ServerBin,
576 filter->filter);
577
578 if (filterfds[!current][1] > 1)
579 {
580 close(filterfds[1 - current][0]);
581 close(filterfds[1 - current][1]);
582
583 filterfds[1 - current][0] = -1;
584 filterfds[1 - current][0] = -1;
585 }
586
587 if (next)
588 open_pipe(filterfds[1 - current]);
589 else
590 filterfds[1 - current][1] = 1;
591
592 pid = exec_filter(program, (char **)argv, (char **)envp,
593 filterfds[current][0], filterfds[1 - current][1]);
594
595 if (pid > 0)
596 {
597 fprintf(stderr, "INFO: %s (PID %d) started.\n", filter->filter, pid);
598
599 filter->cost = pid;
600 cupsArrayAdd(pids, filter);
601 }
602 else
603 break;
604
605 argv[6] = NULL;
606 }
607
608 /*
609 * Close remaining pipes...
610 */
611
612 if (filterfds[0][1] > 1)
613 {
614 close(filterfds[0][0]);
615 close(filterfds[0][1]);
616 }
617
618 if (filterfds[1][1] > 1)
619 {
620 close(filterfds[1][0]);
621 close(filterfds[1][1]);
622 }
623
624 /*
625 * Wait for the children to exit...
626 */
627
628 retval = 0;
629
630 while (cupsArrayCount(pids) > 0)
631 {
632 if ((pid = wait(&status)) < 0)
633 continue;
634
635 key.cost = pid;
636 if ((filter = (mime_filter_t *)cupsArrayFind(pids, &key)) != NULL)
637 {
638 cupsArrayRemove(pids, filter);
639
640 if (status)
641 {
642 if (WIFEXITED(status))
643 fprintf(stderr, "ERROR: %s (PID %d) stopped with status %d!\n",
644 filter->filter, pid, WEXITSTATUS(status));
645 else
646 fprintf(stderr, "ERROR: %s (PID %d) crashed on signal %d!\n",
647 filter->filter, pid, WTERMSIG(status));
648
649 retval = 1;
650 }
651 else
652 fprintf(stderr, "INFO: %s (PID %d) exited with no errors.\n",
653 filter->filter, pid);
654 }
655 }
656
657 return (retval);
658 }
659
660
661 /*
662 * 'open_pipe()' - Create a pipe which is closed on exec.
663 */
664
665 int /* O - 0 on success, -1 on error */
666 open_pipe(int *fds) /* O - Pipe file descriptors (2) */
667 {
668 /*
669 * Create the pipe...
670 */
671
672 if (pipe(fds))
673 {
674 fds[0] = -1;
675 fds[1] = -1;
676
677 return (-1);
678 }
679
680 /*
681 * Set the "close on exec" flag on each end of the pipe...
682 */
683
684 if (fcntl(fds[0], F_SETFD, fcntl(fds[0], F_GETFD) | FD_CLOEXEC))
685 {
686 close(fds[0]);
687 close(fds[1]);
688
689 fds[0] = -1;
690 fds[1] = -1;
691
692 return (-1);
693 }
694
695 if (fcntl(fds[1], F_SETFD, fcntl(fds[1], F_GETFD) | FD_CLOEXEC))
696 {
697 close(fds[0]);
698 close(fds[1]);
699
700 fds[0] = -1;
701 fds[1] = -1;
702
703 return (-1);
704 }
705
706 /*
707 * Return 0 indicating success...
708 */
709
710 return (0);
711 }
712
713
714 /*
715 * 'read_cupsd_conf()' - Read the cupsd.conf file to get the filter settings.
716 */
717
718 static int /* O - 0 on success, 1 on error */
719 read_cupsd_conf(const char *filename) /* I - File to read */
720 {
721 const char *temp; /* Temporary string */
722 char line[1024], /* Line from file */
723 *ptr; /* Pointer into line */
724
725
726 if ((temp = getenv("CUPS_DATADIR")) != NULL)
727 set_string(&DataDir, temp);
728 else
729 set_string(&DataDir, CUPS_DATADIR);
730
731 if ((temp = getenv("CUPS_FONTPATH")) != NULL)
732 set_string(&FontPath, temp);
733 else
734 set_string(&FontPath, CUPS_FONTPATH);
735
736 if ((temp = getenv("CUPS_SERVERBIN")) != NULL)
737 set_string(&ServerBin, temp);
738 else
739 set_string(&ServerBin, CUPS_SERVERBIN);
740
741 strlcpy(line, filename, sizeof(line));
742 if ((ptr = strrchr(line, '/')) != NULL)
743 *ptr = '\0';
744 else
745 getcwd(line, sizeof(line));
746
747 set_string(&ServerRoot, line);
748
749 snprintf(line, sizeof(line),
750 "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR ":/bin/usr/bin",
751 ServerBin);
752 set_string(&Path, line);
753
754 return (0);
755 }
756
757
758 /*
759 * 'set_string()' - Copy and set a string.
760 */
761
762 static void
763 set_string(char **s, /* O - Copy of string */
764 const char *val) /* I - String to copy */
765 {
766 if (*s)
767 free(*s);
768
769 *s = strdup(val);
770 }
771
772
773 /*
774 * 'usage()' - Show program usage...
775 */
776
777 static void
778 usage(const char *opt) /* I - Incorrect option, if any */
779 {
780 if (opt)
781 _cupsLangPrintf(stderr, _("%s: Unknown option '%c'!\n"), "cupsfilter",
782 *opt);
783
784 _cupsLangPuts(stdout,
785 _("Usage: cupsfilter -m mime/type [ options ] filename(s)\n"
786 "\n"
787 "Options:\n"
788 "\n"
789 " -c cupsd.conf Set cupsd.conf file to use\n"
790 " -n copies Set number of copies\n"
791 " -o name=value Set option(s)\n"
792 " -p filename.ppd Set PPD file\n"
793 " -t title Set title\n"));
794
795 exit(1);
796 }
797
798
799 /*
800 * End of "$Id: cupsfilter.c 6668 2007-07-13 23:09:49Z mike $".
801 */