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