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