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