1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
3      Written by James Clark (jjc@jclark.com)
4 
5 This file is part of groff.
6 
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
11 
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16 
17 You should have received a copy of the GNU General Public License along
18 with groff; see the file COPYING.  If not, write to the Free Software
19 Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
20 
21 // A front end for groff.
22 
23 #include <stdio.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <stdlib.h>
27 #include <signal.h>
28 #include <errno.h>
29 
30 #include "lib.h"
31 #include "assert.h"
32 #include "errarg.h"
33 #include "error.h"
34 #include "stringclass.h"
35 #include "cset.h"
36 #include "font.h"
37 #include "device.h"
38 #include "pipeline.h"
39 #include "defs.h"
40 
41 #define BSHELL "/bin/sh"
42 #define GXDITVIEW "gxditview"
43 
44 // troff will be passed an argument of -rXREG=1 if the -X option is
45 // specified
46 #define XREG ".X"
47 
48 #ifndef STDLIB_H_DECLARES_PUTENV
49 extern "C" {
50   int putenv(const char *);
51 }
52 #endif /* not STDLIB_H_DECLARES_PUTENV */
53 
54 const char *strsignal(int);
55 
56 const int SOELIM_INDEX = 0;
57 const int REFER_INDEX = SOELIM_INDEX + 1;
58 const int PIC_INDEX = REFER_INDEX + 1;
59 const int TBL_INDEX = PIC_INDEX + 1;
60 const int EQN_INDEX = TBL_INDEX + 1;
61 const int TROFF_INDEX = EQN_INDEX + 1;
62 const int POST_INDEX = TROFF_INDEX + 1;
63 const int SPOOL_INDEX = POST_INDEX + 1;
64 
65 const int NCOMMANDS = SPOOL_INDEX + 1;
66 
67 class possible_command {
68   char *name;
69   string args;
70   char **argv;
71 
72   void build_argv();
73 public:
74   possible_command();
75   ~possible_command();
76   void set_name(const char *);
77   void set_name(const char *, const char *);
78   const char *get_name();
79   void append_arg(const char *, const char * = 0);
80   void clear_args();
81   char **get_argv();
82   void print(int is_last, FILE *fp);
83 };
84 
85 int lflag = 0;
86 char *spooler = 0;
87 char *driver = 0;
88 
89 possible_command commands[NCOMMANDS];
90 
91 int run_commands();
92 void print_commands();
93 void append_arg_to_string(const char *arg, string &str);
94 void handle_unknown_desc_command(const char *command, const char *arg,
95 				 const char *filename, int lineno);
96 const char *basename(const char *);
97 
98 void usage();
99 void help();
100 
101 int main(int argc, char **argv)
102 {
103   program_name = argv[0];
104   static char stderr_buf[BUFSIZ];
105   setbuf(stderr, stderr_buf);
106   assert(NCOMMANDS <= MAX_COMMANDS);
107   string Pargs, Largs, Fargs;
108   int Vflag = 0;
109   int zflag = 0;
110   int iflag = 0;
111   int Xflag = 0;
112   int opt;
113   const char *command_prefix = getenv("GROFF_COMMAND_PREFIX");
114   if (!command_prefix)
115     command_prefix = PROG_PREFIX;
116   commands[TROFF_INDEX].set_name(command_prefix, "troff");
117   while ((opt = getopt(argc, argv,
118 		       "itpeRszavVhblCENXZF:m:T:f:w:W:M:d:r:n:o:P:L:"))
119 	 != EOF) {
120     char buf[3];
121     buf[0] = '-';
122     buf[1] = opt;
123     buf[2] = '\0';
124     switch (opt) {
125     case 'i':
126       iflag = 1;
127       break;
128     case 't':
129       commands[TBL_INDEX].set_name(command_prefix, "tbl");
130       break;
131     case 'p':
132       commands[PIC_INDEX].set_name(command_prefix, "pic");
133       break;
134     case 'e':
135       commands[EQN_INDEX].set_name(command_prefix, "eqn");
136       break;
137     case 's':
138       commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
139       break;
140     case 'R':
141       commands[REFER_INDEX].set_name(command_prefix, "refer");
142       break;
143     case 'z':
144     case 'a':
145       commands[TROFF_INDEX].append_arg(buf);
146       // fall through
147     case 'Z':
148       zflag++;
149       break;
150     case 'l':
151       lflag++;
152       break;
153     case 'V':
154       Vflag++;
155       break;
156     case 'v':
157     case 'C':
158       commands[SOELIM_INDEX].append_arg(buf);
159       commands[PIC_INDEX].append_arg(buf);
160       commands[TBL_INDEX].append_arg(buf);
161       commands[EQN_INDEX].append_arg(buf);
162       commands[TROFF_INDEX].append_arg(buf);
163       break;
164     case 'N':
165       commands[EQN_INDEX].append_arg(buf);
166       break;
167     case 'h':
168       help();
169       break;
170     case 'E':
171     case 'b':
172       commands[TROFF_INDEX].append_arg(buf);
173       break;
174     case 'T':
175       if (strcmp(optarg, "Xps") == 0) {
176 	warning("-TXps option is obsolete: use -X -Tps instead");
177 	device = "ps";
178 	Xflag++;
179       }
180       else
181 	device = optarg;
182       break;
183     case 'F':
184       font::command_line_font_dir(optarg);
185       if (Fargs.length() > 0) {
186 	Fargs += ':';
187 	Fargs += optarg;
188       }
189       else
190 	Fargs = optarg;
191       break;
192     case 'f':
193     case 'o':
194     case 'm':
195     case 'r':
196     case 'd':
197     case 'n':
198     case 'w':
199     case 'W':
200       commands[TROFF_INDEX].append_arg(buf, optarg);
201       break;
202     case 'M':
203       commands[EQN_INDEX].append_arg(buf, optarg);
204       commands[TROFF_INDEX].append_arg(buf, optarg);
205       break;
206     case 'P':
207       Pargs += optarg;
208       Pargs += '\0';
209       break;
210     case 'L':
211       append_arg_to_string(optarg, Largs);
212       break;
213     case 'X':
214       Xflag++;
215       break;
216     case '?':
217       usage();
218       break;
219     default:
220       assert(0);
221       break;
222     }
223   }
224   font::set_unknown_desc_command_handler(handle_unknown_desc_command);
225   if (!font::load_desc())
226     fatal("invalid device `%1'", device);
227   if (!driver)
228     fatal("no `postpro' command in DESC file for device `%1'", device);
229   const char *real_driver = 0;
230   if (Xflag) {
231     real_driver = driver;
232     driver = GXDITVIEW;
233     commands[TROFF_INDEX].append_arg("-r" XREG "=", "1");
234   }
235   if (driver)
236     commands[POST_INDEX].set_name(driver);
237   int gxditview_flag = driver && strcmp(basename(driver), GXDITVIEW) == 0;
238   if (gxditview_flag && argc - optind == 1) {
239     commands[POST_INDEX].append_arg("-title");
240     commands[POST_INDEX].append_arg(argv[optind]);
241     commands[POST_INDEX].append_arg("-xrm");
242     commands[POST_INDEX].append_arg("*iconName:", argv[optind]);
243     string filename_string("|");
244     append_arg_to_string(argv[0], filename_string);
245     append_arg_to_string("-Z", filename_string);
246     for (int i = 1; i < argc; i++)
247       append_arg_to_string(argv[i], filename_string);
248     filename_string += '\0';
249     commands[POST_INDEX].append_arg("-filename");
250     commands[POST_INDEX].append_arg(filename_string.contents());
251   }
252   if (gxditview_flag && Xflag) {
253     string print_string(real_driver);
254     if (spooler) {
255       print_string += " | ";
256       print_string += spooler;
257       print_string += Largs;
258     }
259     print_string += '\0';
260     commands[POST_INDEX].append_arg("-printCommand");
261     commands[POST_INDEX].append_arg(print_string.contents());
262   }
263   const char *p = Pargs.contents();
264   const char *end = p + Pargs.length();
265   while (p < end) {
266     commands[POST_INDEX].append_arg(p);
267     p = strchr(p, '\0') + 1;
268   }
269   if (gxditview_flag)
270     commands[POST_INDEX].append_arg("-");
271   if (lflag && !Xflag && spooler) {
272     commands[SPOOL_INDEX].set_name(BSHELL);
273     commands[SPOOL_INDEX].append_arg("-c");
274     Largs += '\0';
275     Largs = spooler + Largs;
276     commands[SPOOL_INDEX].append_arg(Largs.contents());
277   }
278   if (zflag) {
279     commands[POST_INDEX].set_name(0);
280     commands[SPOOL_INDEX].set_name(0);
281   }
282   commands[TROFF_INDEX].append_arg("-T", device);
283   commands[EQN_INDEX].append_arg("-T", device);
284 
285   for (int first_index = 0; first_index < TROFF_INDEX; first_index++)
286     if (commands[first_index].get_name() != 0)
287       break;
288   if (optind < argc) {
289     if (argv[optind][0] == '-' && argv[optind][1] != '\0')
290       commands[first_index].append_arg("--");
291     for (int i = optind; i < argc; i++)
292       commands[first_index].append_arg(argv[i]);
293     if (iflag)
294       commands[first_index].append_arg("-");
295   }
296   if (Fargs.length() > 0) {
297     string e = "GROFF_FONT_PATH";
298     e += '=';
299     e += Fargs;
300     char *fontpath = getenv("GROFF_FONT_PATH");
301     if (fontpath && *fontpath) {
302       e += ':';
303       e += fontpath;
304     }
305     e += '\0';
306     if (putenv(strsave(e.contents())))
307       fatal("putenv failed");
308   }
309   if (Vflag) {
310     print_commands();
311     exit(0);
312   }
313   exit(run_commands());
314 }
315 
316 const char *basename(const char *s)
317 {
318   if (!s)
319     return 0;
320   const char *p = strrchr(s, '/');
321   return p ? p + 1 : s;
322 }
323 
324 void handle_unknown_desc_command(const char *command, const char *arg,
325 				 const char *filename, int lineno)
326 {
327   if (strcmp(command, "print") == 0) {
328     if (arg == 0)
329       error_with_file_and_line(filename, lineno,
330 			       "`print' command requires an argument");
331     else
332       spooler = strsave(arg);
333   }
334   if (strcmp(command, "postpro") == 0) {
335     if (arg == 0)
336       error_with_file_and_line(filename, lineno,
337 			       "`postpro' command requires an argument");
338     else {
339       for (const char *p = arg; *p; p++)
340 	if (csspace(*p)) {
341 	  error_with_file_and_line(filename, lineno,
342 				   "invalid `postpro' argument `%1'"
343 				   ": program name required", arg);
344 	  return;
345 	}
346       driver = strsave(arg);
347     }
348   }
349 }
350 
351 void print_commands()
352 {
353   for (int last = SPOOL_INDEX; last >= 0; last--)
354     if (commands[last].get_name() != 0)
355       break;
356   for (int i = 0; i <= last; i++)
357     if (commands[i].get_name() != 0)
358       commands[i].print(i == last, stdout);
359 }
360 
361 // Run the commands. Return the code with which to exit.
362 
363 int run_commands()
364 {
365   char **v[NCOMMANDS];
366   int j = 0;
367   for (int i = 0; i < NCOMMANDS; i++)
368     if (commands[i].get_name() != 0)
369       v[j++] = commands[i].get_argv();
370   return run_pipeline(j, v);
371 }
372 
373 possible_command::possible_command()
374 : name(0), argv(0)
375 {
376 }
377 
378 possible_command::~possible_command()
379 {
380   a_delete name;
381   a_delete argv;
382 }
383 
384 void possible_command::set_name(const char *s)
385 {
386   a_delete name;
387   name = strsave(s);
388 }
389 
390 void possible_command::set_name(const char *s1, const char *s2)
391 {
392   a_delete name;
393   name = new char[strlen(s1) + strlen(s2) + 1];
394   strcpy(name, s1);
395   strcat(name, s2);
396 }
397 
398 const char *possible_command::get_name()
399 {
400   return name;
401 }
402 
403 void possible_command::clear_args()
404 {
405   args.clear();
406 }
407 
408 void possible_command::append_arg(const char *s, const char *t)
409 {
410   args += s;
411   if (t)
412     args += t;
413   args += '\0';
414 }
415 
416 void possible_command::build_argv()
417 {
418   if (argv)
419     return;
420   // Count the number of arguments.
421   int len = args.length();
422   int argc = 1;
423   char *p = 0;
424   if (len > 0) {
425     p = &args[0];
426     for (int i = 0; i < len; i++)
427       if (p[i] == '\0')
428 	argc++;
429   }
430   // Build an argument vector.
431   argv = new char *[argc + 1];
432   argv[0] = name;
433   for (int i = 1; i < argc; i++) {
434     argv[i] = p;
435     p = strchr(p, '\0') + 1;
436   }
437   argv[argc] = 0;
438 }
439 
440 void possible_command::print(int is_last, FILE *fp)
441 {
442   build_argv();
443   if (argv[0] != 0 && strcmp(argv[0], BSHELL) == 0
444       && argv[1] != 0 && strcmp(argv[1], "-c") == 0
445       && argv[2] != 0 && argv[3] == 0)
446     fputs(argv[2], fp);
447   else {
448     fputs(argv[0], fp);
449     string str;
450     for (int i = 1; argv[i] != 0; i++) {
451       str.clear();
452       append_arg_to_string(argv[i], str);
453       put_string(str, fp);
454     }
455   }
456   if (is_last)
457     putc('\n', fp);
458   else
459     fputs(" | ", fp);
460 }
461 
462 void append_arg_to_string(const char *arg, string &str)
463 {
464   str += ' ';
465   int needs_quoting = 0;
466   int contains_single_quote = 0;
467   for (const char *p = arg; *p != '\0'; p++)
468     switch (*p) {
469     case ';':
470     case '&':
471     case '(':
472     case ')':
473     case '|':
474     case '^':
475     case '<':
476     case '>':
477     case '\n':
478     case ' ':
479     case '\t':
480     case '\\':
481     case '"':
482     case '$':
483     case '?':
484     case '*':
485       needs_quoting = 1;
486       break;
487     case '\'':
488       contains_single_quote = 1;
489       break;
490     }
491   if (contains_single_quote || arg[0] == '\0') {
492     str += '"';
493     for (p = arg; *p != '\0'; p++)
494       switch (*p) {
495       case '"':
496       case '\\':
497       case '$':
498 	str += '\\';
499 	// fall through
500       default:
501 	str += *p;
502 	break;
503       }
504     str += '"';
505   }
506   else if (needs_quoting) {
507     str += '\'';
508     str += arg;
509     str += '\'';
510   }
511   else
512     str += arg;
513 }
514 
515 char **possible_command::get_argv()
516 {
517   build_argv();
518   return argv;
519 }
520 
521 void synopsis()
522 {
523   fprintf(stderr,
524 "usage: %s [-abehilpstvzCENRVXZ] [-Fdir] [-mname] [-Tdev] [-ffam] [-wname]\n"
525 "       [-Wname] [ -Mdir] [-dcs] [-rcn] [-nnum] [-olist] [-Parg] [-Larg]\n"
526 "       [files...]\n",
527 	  program_name);
528 }
529 
530 void help()
531 {
532   synopsis();
533   fputs("\n"
534 "-h\tprint this message\n"
535 "-t\tpreprocess with tbl\n"
536 "-p\tpreprocess with pic\n"
537 "-e\tpreprocess with eqn\n"
538 "-s\tpreprocess with soelim\n"
539 "-R\tpreprocess with refer\n"
540 "-Tdev\tuse device dev\n"
541 "-X\tuse X11 previewer rather than usual postprocessor\n"
542 "-mname\tread macros tmac.name\n"
543 "-dcs\tdefine a string c as s\n"
544 "-rcn\tdefine a number register c as n\n"
545 "-nnum\tnumber first page n\n"
546 "-olist\toutput only pages in list\n"
547 "-ffam\tuse fam as the default font family\n"
548 "-Fdir\tsearch directory dir for device directories\n"
549 "-Mdir\tsearch dir for macro files\n"
550 "-v\tprint version number\n"
551 "-z\tsuppress formatted output\n"
552 "-Z\tdon't postprocess\n"
553 "-a\tproduce ASCII description of output\n"
554 "-i\tread standard input after named input files\n"
555 "-wname\tenable warning name\n"
556 "-Wname\tinhibit warning name\n"
557 "-E\tinhibit all errors\n"
558 "-b\tprint backtraces with errors or warnings\n"
559 "-l\tspool the output\n"
560 "-C\tenable compatibility mode\n"
561 "-V\tprint commands on stdout instead of running them\n"
562 "-Parg\tpass arg to the postprocessor\n"
563 "-Larg\tpass arg to the spooler\n"
564 "-N\tdon't allow newlines within eqn delimiters\n"
565 "\n",
566 	stderr);
567   exit(0);
568 }
569 
570 void usage()
571 {
572   synopsis();
573   fprintf(stderr, "%s -h gives more help\n", program_name);
574   exit(1);
575 }
576 
577 extern "C" {
578 
579 void c_error(const char *format, const char *arg1, const char *arg2,
580 	     const char *arg3)
581 {
582   error(format, arg1, arg2, arg3);
583 }
584 
585 void c_fatal(const char *format, const char *arg1, const char *arg2,
586 	     const char *arg3)
587 {
588   fatal(format, arg1, arg2, arg3);
589 }
590 
591 }
592