1// -*- C++ -*-
2/* Copyright (C) 1989-2000, 2001, 2002, 2003, 2004
3   Free Software Foundation, Inc.
4     Written by James Clark (jjc@jclark.com)
5
6This file is part of groff.
7
8groff is free software; you can redistribute it and/or modify it under
9the terms of the GNU General Public License as published by the Free
10Software Foundation; either version 2, or (at your option) any later
11version.
12
13groff is distributed in the hope that it will be useful, but WITHOUT ANY
14WARRANTY; without even the implied warranty of MERCHANTABILITY or
15FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16for more details.
17
18You should have received a copy of the GNU General Public License along
19with groff; see the file COPYING.  If not, write to the Free Software
20Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
21
22// A front end for groff.
23
24#include "lib.h"
25
26#include <stdlib.h>
27#include <signal.h>
28#include <errno.h>
29
30#include "assert.h"
31#include "errarg.h"
32#include "error.h"
33#include "stringclass.h"
34#include "cset.h"
35#include "font.h"
36#include "device.h"
37#include "pipeline.h"
38#include "nonposix.h"
39#include "defs.h"
40
41#define GXDITVIEW "gxditview"
42
43// troff will be passed an argument of -rXREG=1 if the -X option is
44// specified
45#define XREG ".X"
46
47#ifdef NEED_DECLARATION_PUTENV
48extern "C" {
49  int putenv(const char *);
50}
51#endif /* NEED_DECLARATION_PUTENV */
52
53// The number of commands must be in sync with MAX_COMMANDS in pipeline.h
54const int SOELIM_INDEX = 0;
55const int REFER_INDEX = SOELIM_INDEX + 1;
56const int GRAP_INDEX = REFER_INDEX + 1;
57const int PIC_INDEX = GRAP_INDEX + 1;
58const int TBL_INDEX = PIC_INDEX + 1;
59const int GRN_INDEX = TBL_INDEX + 1;
60const int EQN_INDEX = GRN_INDEX + 1;
61const int TROFF_INDEX = EQN_INDEX + 1;
62const int POST_INDEX = TROFF_INDEX + 1;
63const int SPOOL_INDEX = POST_INDEX + 1;
64
65const int NCOMMANDS = SPOOL_INDEX + 1;
66
67class possible_command {
68  char *name;
69  string args;
70  char **argv;
71
72  void build_argv();
73public:
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 insert_arg(const char *);
81  void insert_args(string s);
82  void clear_args();
83  char **get_argv();
84  void print(int is_last, FILE *fp);
85};
86
87extern "C" const char *Version_string;
88
89int lflag = 0;
90char *spooler = 0;
91char *postdriver = 0;
92char *predriver = 0;
93
94possible_command commands[NCOMMANDS];
95
96int run_commands(int no_pipe);
97void print_commands(FILE *);
98void append_arg_to_string(const char *arg, string &str);
99void handle_unknown_desc_command(const char *command, const char *arg,
100				 const char *filename, int lineno);
101const char *xbasename(const char *);
102
103void usage(FILE *stream);
104void help();
105
106int main(int argc, char **argv)
107{
108  program_name = argv[0];
109  static char stderr_buf[BUFSIZ];
110  setbuf(stderr, stderr_buf);
111  assert(NCOMMANDS <= MAX_COMMANDS);
112  string Pargs, Largs, Fargs;
113  int vflag = 0;
114  int Vflag = 0;
115  int zflag = 0;
116  int iflag = 0;
117  int Xflag = 0;
118  int oflag = 0;
119  int safer_flag = 1;
120  int opt;
121  const char *command_prefix = getenv("GROFF_COMMAND_PREFIX");
122  if (!command_prefix)
123    command_prefix = PROG_PREFIX;
124  commands[TROFF_INDEX].set_name(command_prefix, "troff");
125  static const struct option long_options[] = {
126    { "help", no_argument, 0, 'h' },
127    { "version", no_argument, 0, 'v' },
128    { NULL, 0, 0, 0 }
129  };
130  while ((opt = getopt_long(argc, argv,
131			    "abcCd:eEf:F:gGhiI:lL:m:M:n:No:pP:r:RsStT:UvVw:W:XzZ",
132			    long_options, NULL))
133	 != EOF) {
134    char buf[3];
135    buf[0] = '-';
136    buf[1] = opt;
137    buf[2] = '\0';
138    switch (opt) {
139    case 'i':
140      iflag = 1;
141      break;
142    case 'I':
143      commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
144      commands[SOELIM_INDEX].append_arg(buf, optarg);
145      // .psbb may need to search for files
146      commands[TROFF_INDEX].append_arg(buf, optarg);
147      // \X'ps:import' may need to search for files
148      Pargs += buf;
149      Pargs += optarg;
150      Pargs += '\0';
151      break;
152    case 't':
153      commands[TBL_INDEX].set_name(command_prefix, "tbl");
154      break;
155    case 'p':
156      commands[PIC_INDEX].set_name(command_prefix, "pic");
157      break;
158    case 'g':
159      commands[GRN_INDEX].set_name(command_prefix, "grn");
160      break;
161    case 'G':
162      commands[GRAP_INDEX].set_name(command_prefix, "grap");
163      break;
164    case 'e':
165      commands[EQN_INDEX].set_name(command_prefix, "eqn");
166      break;
167    case 's':
168      commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
169      break;
170    case 'R':
171      commands[REFER_INDEX].set_name(command_prefix, "refer");
172      break;
173    case 'z':
174    case 'a':
175      commands[TROFF_INDEX].append_arg(buf);
176      // fall through
177    case 'Z':
178      zflag++;
179      break;
180    case 'l':
181      lflag++;
182      break;
183    case 'V':
184      Vflag++;
185      break;
186    case 'v':
187      vflag = 1;
188      {
189	printf("GNU groff version %s\n", Version_string);
190	printf("Copyright (C) 2004 Free Software Foundation, Inc.\n"
191	       "GNU groff comes with ABSOLUTELY NO WARRANTY.\n"
192	       "You may redistribute copies of groff and its subprograms\n"
193	       "under the terms of the GNU General Public License.\n"
194	       "For more information about these matters, see the file named COPYING.\n");
195	printf("\ncalled subprograms:\n\n");
196        fflush(stdout);
197      }
198      commands[POST_INDEX].append_arg(buf);
199      // fall through
200    case 'C':
201      commands[SOELIM_INDEX].append_arg(buf);
202      commands[REFER_INDEX].append_arg(buf);
203      commands[PIC_INDEX].append_arg(buf);
204      commands[GRAP_INDEX].append_arg(buf);
205      commands[TBL_INDEX].append_arg(buf);
206      commands[GRN_INDEX].append_arg(buf);
207      commands[EQN_INDEX].append_arg(buf);
208      commands[TROFF_INDEX].append_arg(buf);
209      break;
210    case 'N':
211      commands[EQN_INDEX].append_arg(buf);
212      break;
213    case 'h':
214      help();
215      break;
216    case 'E':
217    case 'b':
218      commands[TROFF_INDEX].append_arg(buf);
219      break;
220    case 'c':
221      commands[TROFF_INDEX].append_arg(buf);
222      break;
223    case 'S':
224      safer_flag = 1;
225      break;
226    case 'U':
227      safer_flag = 0;
228      break;
229    case 'T':
230      if (strcmp(optarg, "html") == 0) {
231	// force soelim to aid the html preprocessor
232	commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
233      }
234      if (strcmp(optarg, "Xps") == 0) {
235	warning("-TXps option is obsolete: use -X -Tps instead");
236	device = "ps";
237	Xflag++;
238      }
239      else
240	device = optarg;
241      break;
242    case 'F':
243      font::command_line_font_dir(optarg);
244      if (Fargs.length() > 0) {
245	Fargs += PATH_SEP_CHAR;
246	Fargs += optarg;
247      }
248      else
249	Fargs = optarg;
250      break;
251    case 'o':
252      oflag = 1;
253    case 'f':
254    case 'm':
255    case 'r':
256    case 'd':
257    case 'n':
258    case 'w':
259    case 'W':
260      commands[TROFF_INDEX].append_arg(buf, optarg);
261      break;
262    case 'M':
263      commands[EQN_INDEX].append_arg(buf, optarg);
264      commands[GRAP_INDEX].append_arg(buf, optarg);
265      commands[GRN_INDEX].append_arg(buf, optarg);
266      commands[TROFF_INDEX].append_arg(buf, optarg);
267      break;
268    case 'P':
269      Pargs += optarg;
270      Pargs += '\0';
271      break;
272    case 'L':
273      append_arg_to_string(optarg, Largs);
274      break;
275    case 'X':
276      Xflag++;
277      break;
278    case '?':
279      usage(stderr);
280      exit(1);
281      break;
282    default:
283      assert(0);
284      break;
285    }
286  }
287  if (safer_flag)
288    commands[PIC_INDEX].append_arg("-S");
289  else
290    commands[TROFF_INDEX].insert_arg("-U");
291  font::set_unknown_desc_command_handler(handle_unknown_desc_command);
292  if (!font::load_desc())
293    fatal("invalid device `%1'", device);
294  if (!postdriver)
295    fatal("no `postpro' command in DESC file for device `%1'", device);
296  if (predriver && !zflag) {
297    commands[TROFF_INDEX].insert_arg(commands[TROFF_INDEX].get_name());
298    commands[TROFF_INDEX].set_name(predriver);
299    // pass the device arguments to the predrivers as well
300    commands[TROFF_INDEX].insert_args(Pargs);
301    if (vflag)
302      commands[TROFF_INDEX].insert_arg("-v");
303  }
304  const char *real_driver = 0;
305  if (Xflag) {
306    real_driver = postdriver;
307    postdriver = (char *)GXDITVIEW;
308    commands[TROFF_INDEX].append_arg("-r" XREG "=", "1");
309  }
310  if (postdriver)
311    commands[POST_INDEX].set_name(postdriver);
312  int gxditview_flag = postdriver && strcmp(xbasename(postdriver), GXDITVIEW) == 0;
313  if (gxditview_flag && argc - optind == 1) {
314    commands[POST_INDEX].append_arg("-title");
315    commands[POST_INDEX].append_arg(argv[optind]);
316    commands[POST_INDEX].append_arg("-xrm");
317    commands[POST_INDEX].append_arg("*iconName:", argv[optind]);
318    string filename_string("|");
319    append_arg_to_string(argv[0], filename_string);
320    append_arg_to_string("-Z", filename_string);
321    for (int i = 1; i < argc; i++)
322      append_arg_to_string(argv[i], filename_string);
323    filename_string += '\0';
324    commands[POST_INDEX].append_arg("-filename");
325    commands[POST_INDEX].append_arg(filename_string.contents());
326  }
327  if (gxditview_flag && Xflag) {
328    string print_string(real_driver);
329    if (spooler) {
330      print_string += " | ";
331      print_string += spooler;
332      print_string += Largs;
333    }
334    print_string += '\0';
335    commands[POST_INDEX].append_arg("-printCommand");
336    commands[POST_INDEX].append_arg(print_string.contents());
337  }
338  const char *p = Pargs.contents();
339  const char *end = p + Pargs.length();
340  while (p < end) {
341    commands[POST_INDEX].append_arg(p);
342    p = strchr(p, '\0') + 1;
343  }
344  if (gxditview_flag)
345    commands[POST_INDEX].append_arg("-");
346  if (lflag && !vflag && !Xflag && spooler) {
347    commands[SPOOL_INDEX].set_name(BSHELL);
348    commands[SPOOL_INDEX].append_arg(BSHELL_DASH_C);
349    Largs += '\0';
350    Largs = spooler + Largs;
351    commands[SPOOL_INDEX].append_arg(Largs.contents());
352  }
353  if (zflag) {
354    commands[POST_INDEX].set_name(0);
355    commands[SPOOL_INDEX].set_name(0);
356  }
357  commands[TROFF_INDEX].append_arg("-T", device);
358  // html renders equations as images via ps
359  if (strcmp(device, "html") == 0) {
360    if (oflag)
361      fatal("`-o' option is invalid with device `html'");
362    commands[EQN_INDEX].append_arg("-Tps:html");
363  }
364  else
365    commands[EQN_INDEX].append_arg("-T", device);
366
367  commands[GRN_INDEX].append_arg("-T", device);
368
369  int first_index;
370  for (first_index = 0; first_index < TROFF_INDEX; first_index++)
371    if (commands[first_index].get_name() != 0)
372      break;
373  if (optind < argc) {
374    if (argv[optind][0] == '-' && argv[optind][1] != '\0')
375      commands[first_index].append_arg("--");
376    for (int i = optind; i < argc; i++)
377      commands[first_index].append_arg(argv[i]);
378    if (iflag)
379      commands[first_index].append_arg("-");
380  }
381  if (Fargs.length() > 0) {
382    string e = "GROFF_FONT_PATH";
383    e += '=';
384    e += Fargs;
385    char *fontpath = getenv("GROFF_FONT_PATH");
386    if (fontpath && *fontpath) {
387      e += PATH_SEP_CHAR;
388      e += fontpath;
389    }
390    e += '\0';
391    if (putenv(strsave(e.contents())))
392      fatal("putenv failed");
393  }
394  {
395    // we save the original path in GROFF_PATH__ and put it into the
396    // environment -- troff will pick it up later.
397    char *path = getenv("PATH");
398    string e = "GROFF_PATH__";
399    e += '=';
400    if (path && *path)
401      e += path;
402    e += '\0';
403    if (putenv(strsave(e.contents())))
404      fatal("putenv failed");
405    char *binpath = getenv("GROFF_BIN_PATH");
406    string f = "PATH";
407    f += '=';
408    if (binpath && *binpath)
409      f += binpath;
410    else
411      f += BINPATH;
412    if (path && *path) {
413      f += PATH_SEP_CHAR;
414      f += path;
415    }
416    f += '\0';
417    if (putenv(strsave(f.contents())))
418      fatal("putenv failed");
419  }
420  if (Vflag)
421    print_commands(Vflag == 1 ? stdout : stderr);
422  if (Vflag == 1)
423    exit(0);
424  return run_commands(vflag);
425}
426
427const char *xbasename(const char *s)
428{
429  if (!s)
430    return 0;
431  // DIR_SEPS[] are possible directory separator characters, see nonposix.h
432  // We want the rightmost separator of all possible ones.
433  // Example: d:/foo\\bar.
434  const char *p = strrchr(s, DIR_SEPS[0]), *p1;
435  const char *sep = &DIR_SEPS[1];
436
437  while (*sep)
438    {
439      p1 = strrchr(s, *sep);
440      if (p1 && (!p || p1 > p))
441	p = p1;
442      sep++;
443    }
444  return p ? p + 1 : s;
445}
446
447void handle_unknown_desc_command(const char *command, const char *arg,
448				 const char *filename, int lineno)
449{
450  if (strcmp(command, "print") == 0) {
451    if (arg == 0)
452      error_with_file_and_line(filename, lineno,
453			       "`print' command requires an argument");
454    else
455      spooler = strsave(arg);
456  }
457  if (strcmp(command, "prepro") == 0) {
458    if (arg == 0)
459      error_with_file_and_line(filename, lineno,
460			       "`prepro' command requires an argument");
461    else {
462      for (const char *p = arg; *p; p++)
463	if (csspace(*p)) {
464	  error_with_file_and_line(filename, lineno,
465				   "invalid `prepro' argument `%1'"
466				   ": program name required", arg);
467	  return;
468	}
469      predriver = strsave(arg);
470    }
471  }
472  if (strcmp(command, "postpro") == 0) {
473    if (arg == 0)
474      error_with_file_and_line(filename, lineno,
475			       "`postpro' command requires an argument");
476    else {
477      for (const char *p = arg; *p; p++)
478	if (csspace(*p)) {
479	  error_with_file_and_line(filename, lineno,
480				   "invalid `postpro' argument `%1'"
481				   ": program name required", arg);
482	  return;
483	}
484      postdriver = strsave(arg);
485    }
486  }
487}
488
489void print_commands(FILE *fp)
490{
491  int last;
492  for (last = SPOOL_INDEX; last >= 0; last--)
493    if (commands[last].get_name() != 0)
494      break;
495  for (int i = 0; i <= last; i++)
496    if (commands[i].get_name() != 0)
497      commands[i].print(i == last, fp);
498}
499
500// Run the commands. Return the code with which to exit.
501
502int run_commands(int no_pipe)
503{
504  char **v[NCOMMANDS];
505  int j = 0;
506  for (int i = 0; i < NCOMMANDS; i++)
507    if (commands[i].get_name() != 0)
508      v[j++] = commands[i].get_argv();
509  return run_pipeline(j, v, no_pipe);
510}
511
512possible_command::possible_command()
513: name(0), argv(0)
514{
515}
516
517possible_command::~possible_command()
518{
519  a_delete name;
520  a_delete argv;
521}
522
523void possible_command::set_name(const char *s)
524{
525  a_delete name;
526  name = strsave(s);
527}
528
529void possible_command::set_name(const char *s1, const char *s2)
530{
531  a_delete name;
532  name = new char[strlen(s1) + strlen(s2) + 1];
533  strcpy(name, s1);
534  strcat(name, s2);
535}
536
537const char *possible_command::get_name()
538{
539  return name;
540}
541
542void possible_command::clear_args()
543{
544  args.clear();
545}
546
547void possible_command::append_arg(const char *s, const char *t)
548{
549  args += s;
550  if (t)
551    args += t;
552  args += '\0';
553}
554
555void possible_command::insert_arg(const char *s)
556{
557  string str(s);
558  str += '\0';
559  str += args;
560  args = str;
561}
562
563void possible_command::insert_args(string s)
564{
565  const char *p = s.contents();
566  const char *end = p + s.length();
567  int l = 0;
568  if (p >= end)
569    return;
570  // find the total number of arguments in our string
571  do {
572    l++;
573    p = strchr(p, '\0') + 1;
574  } while (p < end);
575  // now insert each argument preserving the order
576  for (int i = l - 1; i >= 0; i--) {
577    p = s.contents();
578    for (int j = 0; j < i; j++)
579      p = strchr(p, '\0') + 1;
580    insert_arg(p);
581  }
582}
583
584void possible_command::build_argv()
585{
586  if (argv)
587    return;
588  // Count the number of arguments.
589  int len = args.length();
590  int argc = 1;
591  char *p = 0;
592  if (len > 0) {
593    p = &args[0];
594    for (int i = 0; i < len; i++)
595      if (p[i] == '\0')
596	argc++;
597  }
598  // Build an argument vector.
599  argv = new char *[argc + 1];
600  argv[0] = name;
601  for (int i = 1; i < argc; i++) {
602    argv[i] = p;
603    p = strchr(p, '\0') + 1;
604  }
605  argv[argc] = 0;
606}
607
608void possible_command::print(int is_last, FILE *fp)
609{
610  build_argv();
611  if (IS_BSHELL(argv[0])
612      && argv[1] != 0 && strcmp(argv[1], BSHELL_DASH_C) == 0
613      && argv[2] != 0 && argv[3] == 0)
614    fputs(argv[2], fp);
615  else {
616    fputs(argv[0], fp);
617    string str;
618    for (int i = 1; argv[i] != 0; i++) {
619      str.clear();
620      append_arg_to_string(argv[i], str);
621      put_string(str, fp);
622    }
623  }
624  if (is_last)
625    putc('\n', fp);
626  else
627    fputs(" | ", fp);
628}
629
630void append_arg_to_string(const char *arg, string &str)
631{
632  str += ' ';
633  int needs_quoting = 0;
634  int contains_single_quote = 0;
635  const char*p;
636  for (p = arg; *p != '\0'; p++)
637    switch (*p) {
638    case ';':
639    case '&':
640    case '(':
641    case ')':
642    case '|':
643    case '^':
644    case '<':
645    case '>':
646    case '\n':
647    case ' ':
648    case '\t':
649    case '\\':
650    case '"':
651    case '$':
652    case '?':
653    case '*':
654      needs_quoting = 1;
655      break;
656    case '\'':
657      contains_single_quote = 1;
658      break;
659    }
660  if (contains_single_quote || arg[0] == '\0') {
661    str += '"';
662    for (p = arg; *p != '\0'; p++)
663      switch (*p) {
664      case '"':
665      case '\\':
666      case '$':
667	str += '\\';
668	// fall through
669      default:
670	str += *p;
671	break;
672      }
673    str += '"';
674  }
675  else if (needs_quoting) {
676    str += '\'';
677    str += arg;
678    str += '\'';
679  }
680  else
681    str += arg;
682}
683
684char **possible_command::get_argv()
685{
686  build_argv();
687  return argv;
688}
689
690void synopsis(FILE *stream)
691{
692  fprintf(stream,
693"usage: %s [-abceghilpstvzCENRSUVXZ] [-Fdir] [-mname] [-Tdev] [-ffam]\n"
694"       [-wname] [-Wname] [-Mdir] [-dcs] [-rcn] [-nnum] [-olist] [-Parg]\n"
695"       [-Larg] [-Idir] [files...]\n",
696	  program_name);
697}
698
699void help()
700{
701  synopsis(stdout);
702  fputs("\n"
703"-h\tprint this message\n"
704"-t\tpreprocess with tbl\n"
705"-p\tpreprocess with pic\n"
706"-e\tpreprocess with eqn\n"
707"-g\tpreprocess with grn\n"
708"-G\tpreprocess with grap\n"
709"-s\tpreprocess with soelim\n"
710"-R\tpreprocess with refer\n"
711"-Tdev\tuse device dev\n"
712"-X\tuse X11 previewer rather than usual postprocessor\n"
713"-mname\tread macros tmac.name\n"
714"-dcs\tdefine a string c as s\n"
715"-rcn\tdefine a number register c as n\n"
716"-nnum\tnumber first page n\n"
717"-olist\toutput only pages in list\n"
718"-ffam\tuse fam as the default font family\n"
719"-Fdir\tsearch dir for device directories\n"
720"-Mdir\tsearch dir for macro files\n"
721"-v\tprint version number\n"
722"-z\tsuppress formatted output\n"
723"-Z\tdon't postprocess\n"
724"-a\tproduce ASCII description of output\n"
725"-i\tread standard input after named input files\n"
726"-wname\tenable warning name\n"
727"-Wname\tinhibit warning name\n"
728"-E\tinhibit all errors\n"
729"-b\tprint backtraces with errors or warnings\n"
730"-l\tspool the output\n"
731"-c\tdisable color output\n"
732"-C\tenable compatibility mode\n"
733"-V\tprint commands on stdout instead of running them\n"
734"-Parg\tpass arg to the postprocessor\n"
735"-Larg\tpass arg to the spooler\n"
736"-N\tdon't allow newlines within eqn delimiters\n"
737"-S\tenable safer mode (the default)\n"
738"-U\tenable unsafe mode\n"
739"-Idir\tsearch dir for soelim, troff, and grops.  Implies -s\n"
740"\n",
741	stdout);
742  exit(0);
743}
744
745void usage(FILE *stream)
746{
747  synopsis(stream);
748  fprintf(stream, "%s -h gives more help\n", program_name);
749}
750
751extern "C" {
752
753void c_error(const char *format, const char *arg1, const char *arg2,
754	     const char *arg3)
755{
756  error(format, arg1, arg2, arg3);
757}
758
759void c_fatal(const char *format, const char *arg1, const char *arg2,
760	     const char *arg3)
761{
762  fatal(format, arg1, arg2, arg3);
763}
764
765}
766