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