Line data Source code
1 : /* Copyright 1989-2024 Free Software Foundation, Inc.
2 : Written by James Clark (jjc@jclark.com)
3 :
4 : This file is part of groff, the GNU roff typesetting system.
5 :
6 : groff is free software; you can redistribute it and/or modify it under
7 : the terms of the GNU General Public License as published by the Free
8 : Software Foundation, either version 3 of the License, or
9 : (at your option) any later version.
10 :
11 : groff is distributed in the hope that it will be useful, but WITHOUT ANY
12 : WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 : FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 : for more details.
15 :
16 : You should have received a copy of the GNU General Public License
17 : along with this program. If not, see <http://www.gnu.org/licenses/>. */
18 :
19 : // A front end for GNU troff.
20 :
21 : #ifdef HAVE_CONFIG_H
22 : #include <config.h>
23 : #endif
24 :
25 : #include <assert.h>
26 : #include <errno.h>
27 : #include <stdio.h> // EOF, FILE, fflush(), setbuf(), stderr, stdout
28 : #include <stdlib.h> // exit(), EXIT_SUCCESS, free(), getenv(), putenv()
29 : #include <string.h> // strerror(), strsignal()
30 :
31 : #include <getopt.h> // getopt_long()
32 : //
33 : // TODO: operating system services proper (cf. the standard C/C++
34 : // runtime and libraries) should be abstracted through posix.h/
35 : // nonposix.h (if gnulib doesn't handle it for us).
36 : #include <sys/types.h> // pid_t
37 : #include <signal.h> // kill()
38 :
39 : // needed for close(), dup(), execvp(), _exit(), fork(), pipe(), wait()
40 : #include "posix.h"
41 : #include "nonposix.h"
42 :
43 : #include "lib.h"
44 :
45 : #include "errarg.h"
46 : #include "error.h"
47 : #include "stringclass.h"
48 : #include "cset.h"
49 : #include "font.h"
50 : #include "device.h"
51 : #include "pipeline.h"
52 : #include "relocate.h"
53 : #include "defs.h"
54 :
55 : #define GXDITVIEW "gxditview"
56 :
57 : // troff will be passed an argument of -rXREG=1 if the -X option is
58 : // specified
59 : #define XREG ".X"
60 :
61 : // The number of commands must be in sync with MAX_COMMANDS in
62 : // pipeline.h.
63 :
64 : // grap, chem, and ideal must come before pic;
65 : // tbl must come before eqn
66 : const int PRECONV_INDEX = 0;
67 : const int SOELIM_INDEX = PRECONV_INDEX + 1;
68 : const int REFER_INDEX = SOELIM_INDEX + 1;
69 : const int GRAP_INDEX = REFER_INDEX + 1;
70 : const int CHEM_INDEX = GRAP_INDEX + 1;
71 : const int IDEAL_INDEX = CHEM_INDEX + 1;
72 : const int PIC_INDEX = IDEAL_INDEX + 1;
73 : const int TBL_INDEX = PIC_INDEX + 1;
74 : const int GRN_INDEX = TBL_INDEX + 1;
75 : const int EQN_INDEX = GRN_INDEX + 1;
76 : const int TROFF_INDEX = EQN_INDEX + 1;
77 : const int POST_INDEX = TROFF_INDEX + 1;
78 : const int SPOOL_INDEX = POST_INDEX + 1;
79 :
80 : const int NCOMMANDS = SPOOL_INDEX + 1;
81 :
82 : class possible_command {
83 : char *name;
84 : string args;
85 : char **argv;
86 :
87 : void build_argv();
88 : public:
89 : possible_command();
90 : ~possible_command();
91 : void clear_name();
92 : void set_name(const char *);
93 : void set_name(const char *, const char *);
94 : const char *get_name();
95 : void append_arg(const char *, const char * = 0 /* nullptr */);
96 : void insert_arg(const char *);
97 : void insert_args(string s);
98 : void clear_args();
99 : char **get_argv();
100 : void print(int is_last, FILE *fp);
101 : };
102 :
103 : extern "C" const char *Version_string;
104 :
105 : int lflag = 0;
106 : char *spooler = 0 /* nullptr */;
107 : char *postdriver = 0 /* nullptr */;
108 : char *predriver = 0 /* nullptr */;
109 : bool need_postdriver = true;
110 : char *saved_path = 0 /* nullptr */;
111 : char *groff_bin_path = 0 /* nullptr */;
112 : char *groff_font_path = 0 /* nullptr */;
113 :
114 : possible_command commands[NCOMMANDS];
115 :
116 : int run_commands(bool no_pipe);
117 : void print_commands(FILE *);
118 : void append_arg_to_string(const char *arg, string &str);
119 : void handle_unknown_desc_command(const char *command, const char *arg,
120 : const char *filename, int lineno);
121 : const char *xbasename(const char *);
122 :
123 : void usage(FILE *stream);
124 :
125 6839 : static char *xstrdup(const char *s) {
126 6839 : if (0 /* nullptr */ == s)
127 754 : return const_cast<char *>(s);
128 6085 : char *str = strdup(s);
129 6085 : if (0 /* nullptr */ == str)
130 0 : fatal("unable to copy string: %1", strerror(errno));
131 6085 : return str;
132 : }
133 :
134 2926 : static void xputenv(const char *s) {
135 2926 : if (putenv(const_cast<char *>(s)) != 0)
136 0 : fatal("cannot update process environment: %1", strerror(errno));
137 2926 : return;
138 : }
139 :
140 1437 : static void xexit(int status) {
141 1437 : free(spooler);
142 1437 : free(predriver);
143 1437 : free(postdriver);
144 1437 : free(saved_path);
145 1437 : free(groff_bin_path);
146 1437 : free(groff_font_path);
147 1437 : exit(status);
148 : }
149 :
150 1437 : int main(int argc, char **argv)
151 : {
152 1437 : program_name = argv[0];
153 : static char stderr_buf[BUFSIZ];
154 1437 : setbuf(stderr, stderr_buf);
155 : assert(NCOMMANDS <= MAX_COMMANDS);
156 1437 : string Pargs, Largs, Fargs;
157 1437 : int Kflag = 0;
158 1437 : bool want_version_info = false;
159 1437 : int Vflag = 0;
160 1437 : int zflag = 0;
161 1437 : int iflag = 0;
162 1437 : int Xflag = 0;
163 1437 : int oflag = 0;
164 1437 : bool is_safer_mode_locked = false; // made true if `-S` explicit
165 1437 : bool want_unsafe_mode = false;
166 1437 : int is_xhtml = 0;
167 1437 : int eflag = 0;
168 1437 : int need_pic = 0;
169 : int opt;
170 1437 : const char *command_prefix = getenv("GROFF_COMMAND_PREFIX");
171 1437 : const char *encoding = getenv("GROFF_ENCODING");
172 1437 : if (!command_prefix)
173 0 : command_prefix = PROG_PREFIX;
174 1437 : commands[TROFF_INDEX].set_name(command_prefix, "troff");
175 : static const struct option long_options[] = {
176 : { "help", no_argument, 0 /* nullptr */, 'h' },
177 : { "version", no_argument, 0 /* nullptr */, 'v' },
178 : { 0 /* nullptr */, 0, 0 /* nullptr */, 0 }
179 : };
180 6267 : while ((opt = getopt_long(argc, argv,
181 : ":abcCd:D:eEf:F:gGhiI:jJkK:lL:m:M:"
182 : "n:No:pP:r:RsStT:UvVw:W:XzZ",
183 : long_options, 0 /* nullptr */))
184 6267 : != EOF) {
185 : char buf[3];
186 4830 : buf[0] = '-';
187 4830 : buf[1] = opt;
188 4830 : buf[2] = '\0';
189 4830 : switch (opt) {
190 0 : case 'i':
191 0 : iflag = 1;
192 0 : break;
193 27 : case 'I':
194 27 : commands[GRN_INDEX].set_name(command_prefix, "grn");
195 27 : commands[GRN_INDEX].append_arg("-M", optarg);
196 27 : commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
197 27 : commands[SOELIM_INDEX].append_arg(buf, optarg);
198 : // .psbb may need to search for files
199 27 : commands[TROFF_INDEX].append_arg(buf, optarg);
200 : // \X'ps:import' may need to search for files
201 27 : Pargs += buf;
202 27 : Pargs += optarg;
203 27 : Pargs += '\0';
204 27 : break;
205 0 : case 'D':
206 0 : commands[PRECONV_INDEX].set_name("preconv");
207 0 : commands[PRECONV_INDEX].append_arg("-D", optarg);
208 0 : break;
209 31 : case 'K':
210 31 : commands[PRECONV_INDEX].append_arg("-e", optarg);
211 31 : Kflag = 1;
212 : // fall through
213 33 : case 'k':
214 33 : commands[PRECONV_INDEX].set_name("preconv");
215 33 : break;
216 131 : case 't':
217 131 : commands[TBL_INDEX].set_name(command_prefix, "tbl");
218 131 : break;
219 0 : case 'J':
220 : // commands[IDEAL_INDEX].set_name(command_prefix, "gideal");
221 : // need_pic = 1;
222 0 : break;
223 1 : case 'j':
224 1 : commands[CHEM_INDEX].set_name(command_prefix, "chem");
225 1 : need_pic = 1;
226 1 : break;
227 44 : case 'p':
228 44 : commands[PIC_INDEX].set_name(command_prefix, "pic");
229 44 : break;
230 2 : case 'g':
231 2 : commands[GRN_INDEX].set_name(command_prefix, "grn");
232 2 : break;
233 0 : case 'G':
234 0 : commands[GRAP_INDEX].set_name(command_prefix, "grap");
235 0 : need_pic = 1;
236 0 : break;
237 50 : case 'e':
238 50 : eflag = 1;
239 50 : commands[EQN_INDEX].set_name(command_prefix, "eqn");
240 50 : break;
241 0 : case 's':
242 0 : commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
243 0 : break;
244 1 : case 'R':
245 1 : commands[REFER_INDEX].set_name(command_prefix, "refer");
246 1 : break;
247 134 : case 'z':
248 : case 'a':
249 134 : commands[TROFF_INDEX].append_arg(buf);
250 : // fall through
251 377 : case 'Z':
252 377 : zflag++;
253 377 : need_postdriver = false;
254 377 : break;
255 0 : case 'l':
256 0 : lflag++;
257 0 : break;
258 0 : case 'V':
259 0 : Vflag++;
260 0 : break;
261 2 : case 'v':
262 2 : want_version_info = true;
263 2 : printf("GNU groff version %s\n", Version_string);
264 2 : puts(
265 : "Copyright 1989-2026 Free Software Foundation, Inc. and others\n"
266 : "\n"
267 : "This is free software, distributed under the terms of the GNU General"
268 : " Public\n"
269 : "License, version 3, or any later version, at your option. There is NO"
270 : " warranty;\n"
271 : "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
272 : "\n"
273 : "See the \"COPYING\", \"FDL\", and \"LICENSES\" files in the groff"
274 : " distribution or\n"
275 : "package for additional notices and permissions."
276 : );
277 2 : puts("\nprograms in constructed pipeline:\n");
278 2 : fflush(stdout);
279 : // Pass -v to all possible subprograms
280 2 : commands[PRECONV_INDEX].append_arg(buf);
281 2 : commands[CHEM_INDEX].append_arg(buf);
282 2 : commands[IDEAL_INDEX].append_arg(buf);
283 2 : commands[POST_INDEX].append_arg(buf);
284 : // fall through
285 320 : case 'C':
286 320 : commands[SOELIM_INDEX].append_arg(buf);
287 320 : commands[REFER_INDEX].append_arg(buf);
288 320 : commands[PIC_INDEX].append_arg(buf);
289 320 : commands[GRAP_INDEX].append_arg(buf);
290 320 : commands[TBL_INDEX].append_arg(buf);
291 320 : commands[GRN_INDEX].append_arg(buf);
292 320 : commands[EQN_INDEX].append_arg(buf);
293 320 : commands[TROFF_INDEX].append_arg(buf);
294 320 : break;
295 0 : case 'N':
296 0 : commands[EQN_INDEX].append_arg(buf);
297 0 : break;
298 0 : case 'h':
299 0 : usage(stdout);
300 0 : exit(EXIT_SUCCESS);
301 : break;
302 82 : case 'E':
303 : case 'b':
304 82 : commands[TROFF_INDEX].append_arg(buf);
305 82 : break;
306 0 : case 'c':
307 0 : commands[TROFF_INDEX].append_arg(buf);
308 0 : break;
309 1 : case 'S':
310 1 : is_safer_mode_locked = true;
311 1 : want_unsafe_mode = false;
312 1 : break;
313 22 : case 'U':
314 22 : if (is_safer_mode_locked)
315 1 : warning("ignoring '-U' option; '-S' already specified");
316 : else
317 21 : want_unsafe_mode = true;
318 22 : break;
319 1382 : case 'T':
320 1382 : if (strcmp(optarg, "xhtml") == 0) {
321 : // force soelim to aid the html preprocessor
322 10 : commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
323 10 : Pargs += "-x";
324 10 : Pargs += '\0';
325 10 : Pargs += 'x';
326 10 : Pargs += '\0';
327 10 : is_xhtml = 1;
328 10 : device = "html";
329 10 : break;
330 : }
331 1372 : if (strcmp(optarg, "html") == 0)
332 : // force soelim to aid the html preprocessor
333 52 : commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
334 1372 : if (strcmp(optarg, "Xps") == 0) {
335 0 : warning("-TXps option is obsolete: use -X -Tps instead");
336 0 : device = "ps";
337 0 : Xflag++;
338 : }
339 : else
340 1372 : device = optarg;
341 1372 : break;
342 103 : case 'F':
343 103 : font::command_line_font_dir(optarg);
344 103 : if (Fargs.length() > 0) {
345 51 : Fargs += PATH_SEP_CHAR;
346 51 : Fargs += optarg;
347 : }
348 : else
349 52 : Fargs = optarg;
350 103 : break;
351 0 : case 'o':
352 0 : oflag = 1;
353 : // fall through
354 1596 : case 'f':
355 : case 'm':
356 : case 'r':
357 : case 'd':
358 : case 'n':
359 : case 'w':
360 : case 'W':
361 1596 : commands[TROFF_INDEX].append_arg(buf, optarg);
362 1596 : break;
363 150 : case 'M':
364 150 : commands[EQN_INDEX].append_arg(buf, optarg);
365 150 : commands[GRAP_INDEX].append_arg(buf, optarg);
366 150 : commands[GRN_INDEX].append_arg(buf, optarg);
367 150 : commands[TROFF_INDEX].append_arg(buf, optarg);
368 150 : break;
369 508 : case 'P':
370 508 : Pargs += optarg;
371 508 : Pargs += '\0';
372 508 : break;
373 0 : case 'L':
374 0 : append_arg_to_string(optarg, Largs);
375 0 : break;
376 0 : case 'X':
377 0 : Xflag++;
378 0 : need_postdriver = false;
379 0 : break;
380 0 : case '?':
381 0 : if (optopt != 0)
382 0 : error("unrecognized command-line option '%1'", char(optopt));
383 : else
384 0 : error("unrecognized command-line option '%1'",
385 0 : argv[(optind - 1)]);
386 0 : usage(stderr);
387 0 : xexit(2);
388 0 : break;
389 0 : case ':':
390 0 : error("command-line option '%1' requires an argument",
391 0 : char(optopt));
392 0 : usage(stderr);
393 0 : xexit(2);
394 0 : break;
395 0 : default:
396 0 : assert(0 == "no case to handle option character");
397 : break;
398 : }
399 : }
400 1437 : if (need_pic)
401 1 : commands[PIC_INDEX].set_name(command_prefix, "pic");
402 1437 : if (encoding) {
403 0 : commands[PRECONV_INDEX].set_name("preconv");
404 0 : if (!Kflag && *encoding)
405 0 : commands[PRECONV_INDEX].append_arg("-e", encoding);
406 : }
407 1437 : if (is_safer_mode_locked) {
408 1 : commands[TROFF_INDEX].insert_arg("-S");
409 1 : commands[PIC_INDEX].append_arg("-S");
410 : }
411 1436 : else if (want_unsafe_mode) {
412 21 : commands[TROFF_INDEX].insert_arg("-U");
413 21 : commands[PIC_INDEX].append_arg("-U");
414 : }
415 1437 : font::set_unknown_desc_command_handler(handle_unknown_desc_command);
416 1437 : const char *desc = font::load_desc();
417 1437 : if (0 /* nullptr */ == desc)
418 0 : fatal("cannot load 'DESC' description file for device '%1'",
419 0 : device);
420 1437 : if (need_postdriver && (0 /* nullptr */ == postdriver))
421 0 : fatal_with_file_and_line(desc, 0, "device description file missing"
422 : " 'postpro' directive");
423 1437 : if (predriver && !zflag) {
424 17 : commands[TROFF_INDEX].insert_arg(commands[TROFF_INDEX].get_name());
425 17 : commands[TROFF_INDEX].insert_arg("--");
426 17 : commands[TROFF_INDEX].set_name(predriver);
427 : // pass the device arguments to the predrivers as well
428 17 : commands[TROFF_INDEX].insert_args(Pargs);
429 17 : if (eflag && is_xhtml)
430 0 : commands[TROFF_INDEX].insert_arg("-e");
431 17 : if (want_version_info)
432 0 : commands[TROFF_INDEX].insert_arg("-v");
433 : }
434 1437 : const char *real_driver = 0 /* nullptr */;
435 1437 : if (Xflag) {
436 0 : real_driver = postdriver;
437 0 : postdriver = xstrdup(GXDITVIEW); // so we can free() it in xexit()
438 0 : commands[TROFF_INDEX].append_arg("-r" XREG "=", "1");
439 : }
440 1437 : if (postdriver)
441 1437 : commands[POST_INDEX].set_name(postdriver);
442 1437 : int gxditview_flag = postdriver
443 1437 : && strcmp(xbasename(postdriver), GXDITVIEW) == 0;
444 1437 : if (gxditview_flag && argc - optind == 1) {
445 0 : commands[POST_INDEX].append_arg("-title");
446 0 : commands[POST_INDEX].append_arg(argv[optind]);
447 0 : commands[POST_INDEX].append_arg("-xrm");
448 0 : commands[POST_INDEX].append_arg("*iconName:", argv[optind]);
449 0 : string filename_string("|");
450 0 : append_arg_to_string(argv[0], filename_string);
451 0 : append_arg_to_string("-Z", filename_string);
452 0 : for (int i = 1; i < argc; i++)
453 0 : append_arg_to_string(argv[i], filename_string);
454 0 : filename_string += '\0';
455 0 : commands[POST_INDEX].append_arg("-filename");
456 0 : commands[POST_INDEX].append_arg(filename_string.contents());
457 : }
458 1437 : if (gxditview_flag && Xflag) {
459 0 : string print_string(real_driver);
460 0 : if (spooler) {
461 0 : print_string += " | ";
462 0 : print_string += spooler;
463 0 : print_string += Largs;
464 : }
465 0 : print_string += '\0';
466 0 : commands[POST_INDEX].append_arg("-printCommand");
467 0 : commands[POST_INDEX].append_arg(print_string.contents());
468 : }
469 1437 : const char *p = Pargs.contents();
470 1437 : const char *end = p + Pargs.length();
471 1992 : while (p < end) {
472 555 : commands[POST_INDEX].append_arg(p);
473 555 : p = strchr(p, '\0') + 1;
474 : }
475 1437 : if (gxditview_flag)
476 40 : commands[POST_INDEX].append_arg("-");
477 1437 : if (lflag && !want_version_info && !Xflag && spooler) {
478 0 : commands[SPOOL_INDEX].set_name(BSHELL);
479 0 : commands[SPOOL_INDEX].append_arg(BSHELL_DASH_C);
480 0 : Largs += '\0';
481 0 : Largs = spooler + Largs;
482 0 : commands[SPOOL_INDEX].append_arg(Largs.contents());
483 : }
484 1437 : if (zflag) {
485 377 : commands[POST_INDEX].set_name(0 /* nullptr */);
486 377 : commands[SPOOL_INDEX].set_name(0 /* nullptr */);
487 : }
488 1437 : commands[TROFF_INDEX].append_arg("-T", device);
489 1437 : if (strcmp(device, "html") == 0) {
490 62 : if (is_xhtml) {
491 10 : if (oflag)
492 0 : fatal("'-o' option is invalid with device 'xhtml'");
493 10 : if (zflag)
494 10 : commands[EQN_INDEX].append_arg("-Tmathml:xhtml");
495 0 : else if (eflag)
496 0 : commands[EQN_INDEX].clear_name();
497 : }
498 : else {
499 52 : if (oflag)
500 0 : fatal("'-o' option is invalid with device 'html'");
501 : // html renders equations as images via ps
502 52 : commands[EQN_INDEX].append_arg("-Tps:html");
503 : }
504 : }
505 : else
506 1375 : commands[EQN_INDEX].append_arg("-T", device);
507 :
508 1437 : commands[GRN_INDEX].append_arg("-T", device);
509 :
510 : int first_index;
511 14508 : for (first_index = 0; first_index < TROFF_INDEX; first_index++)
512 13277 : if (commands[first_index].get_name() != 0 /* nullptr */)
513 206 : break;
514 1437 : if (optind < argc) {
515 74 : if (argv[optind][0] == '-' && argv[optind][1] != '\0')
516 0 : commands[first_index].append_arg("--");
517 350 : for (int i = optind; i < argc; i++)
518 276 : commands[first_index].append_arg(argv[i]);
519 74 : if (iflag)
520 0 : commands[first_index].append_arg("-");
521 : }
522 1437 : if (Fargs.length() > 0) {
523 104 : string e = "GROFF_FONT_PATH";
524 52 : e += '=';
525 52 : e += Fargs;
526 52 : char *fontpath = getenv("GROFF_FONT_PATH");
527 52 : if (fontpath && *fontpath) {
528 30 : e += PATH_SEP_CHAR;
529 30 : e += fontpath;
530 : }
531 52 : e += '\0';
532 52 : groff_font_path = xstrdup(e.contents());
533 52 : xputenv(groff_font_path);
534 : }
535 : {
536 : // we save the original path in GROFF_PATH__ and put it into the
537 : // environment -- troff will pick it up later.
538 1437 : char *path = getenv("PATH");
539 2874 : string g = "GROFF_PATH__";
540 1437 : g += '=';
541 1437 : if (path && *path)
542 1437 : g += path;
543 1437 : g += '\0';
544 1437 : saved_path = xstrdup(g.contents());
545 1437 : xputenv(saved_path);
546 1437 : char *binpath = getenv("GROFF_BIN_PATH");
547 2874 : string f = "PATH";
548 1437 : f += '=';
549 1437 : if (binpath && *binpath)
550 1437 : f += binpath;
551 : else {
552 0 : binpath = relocatep(BINPATH);
553 0 : f += binpath;
554 : }
555 1437 : if (path && *path) {
556 1437 : f += PATH_SEP_CHAR;
557 1437 : f += path;
558 : }
559 1437 : f += '\0';
560 1437 : groff_bin_path = xstrdup(f.contents());
561 1437 : xputenv(groff_bin_path);
562 : }
563 1437 : if (Vflag)
564 0 : print_commands(Vflag == 1 ? stdout : stderr);
565 1437 : if (Vflag == 1)
566 0 : xexit(EXIT_SUCCESS);
567 : // We need the lower two bits of the exit status for ourselves.
568 1437 : int status = run_commands(want_version_info) << 2;
569 1437 : assert(status < 65 || 0 == "run_commands() returned too many bits");
570 1437 : xexit(status);
571 0 : }
572 :
573 1437 : const char *xbasename(const char *s)
574 : {
575 1437 : if (!s)
576 0 : return 0 /* nullptr */;
577 : // DIR_SEPS[] are possible directory separator characters; see
578 : // nonposix.h. We want the rightmost separator of all possible ones.
579 : // Example: d:/foo\\bar.
580 1437 : const char *p = strrchr(s, DIR_SEPS[0]), *p1;
581 1437 : const char *sep = &DIR_SEPS[1];
582 :
583 1437 : while (*sep)
584 : {
585 0 : p1 = strrchr(s, *sep);
586 0 : if (p1 && (!p || p1 > p))
587 0 : p = p1;
588 0 : sep++;
589 : }
590 1437 : return p ? p + 1 : s;
591 : }
592 :
593 1858 : void handle_unknown_desc_command(const char *command, const char *arg,
594 : const char *filename, int lineno)
595 : {
596 1858 : if (strcmp(command, "print") == 0) {
597 173 : if (arg == 0 /* nullptr */)
598 0 : error_with_file_and_line(filename, lineno, "'print' directive"
599 : " requires an argument");
600 : else
601 173 : spooler = xstrdup(arg);
602 173 : return;
603 : }
604 1685 : if (strcmp(command, "prepro") == 0) {
605 62 : if (arg == 0 /* nullptr */)
606 0 : error("'prepro' directive requires an argument");
607 : else {
608 744 : for (const char *p = arg; *p; p++)
609 682 : if (csspace(*p)) {
610 0 : error_with_file_and_line(filename, lineno, "invalid 'prepro'"
611 : " directive argument '%1': program"
612 0 : " name required", arg);
613 : }
614 62 : predriver = xstrdup(arg);
615 : }
616 62 : return;
617 : }
618 1623 : if (strcmp(command, "postpro") == 0) {
619 1437 : if (arg == 0 /* nullptr */)
620 0 : error_with_file_and_line(filename, lineno, "'postpro' directive"
621 : " requires an argument");
622 : else {
623 10417 : for (const char *p = arg; *p; p++)
624 8980 : if (csspace(*p)) {
625 0 : error_with_file_and_line(filename, lineno, "invalid 'postpro'"
626 : " directive argument '%1': program"
627 0 : " name required", arg);
628 0 : return;
629 : }
630 1437 : postdriver = xstrdup(arg);
631 : }
632 1437 : return;
633 : }
634 : }
635 :
636 0 : void print_commands(FILE *fp)
637 : {
638 : int last;
639 0 : for (last = SPOOL_INDEX; last >= 0; last--)
640 0 : if (commands[last].get_name() != 0 /* nullptr */)
641 0 : break;
642 0 : for (int i = 0; i <= last; i++)
643 0 : if (commands[i].get_name() != 0 /* nullptr */)
644 0 : commands[i].print(i == last, fp);
645 0 : }
646 :
647 : // Run the commands. Return the code with which to exit.
648 :
649 1437 : int run_commands(bool no_pipe)
650 : {
651 : char **v[NCOMMANDS]; // vector of argv arrays to pipe together
652 1437 : int ncommands = 0;
653 20118 : for (int i = 0; i < NCOMMANDS; i++)
654 18681 : if (commands[i].get_name() != 0 /* nullptr */)
655 2850 : v[ncommands++] = commands[i].get_argv();
656 2874 : return run_pipeline(ncommands, v, no_pipe);
657 : }
658 :
659 18681 : possible_command::possible_command()
660 18681 : : name(0), argv(0)
661 : {
662 18681 : }
663 :
664 18681 : possible_command::~possible_command()
665 : {
666 18681 : free(name);
667 18681 : delete[] argv;
668 18681 : }
669 :
670 2241 : void possible_command::set_name(const char *s)
671 : {
672 2241 : free(name);
673 2241 : name = xstrdup(s);
674 2241 : }
675 :
676 0 : void possible_command::clear_name()
677 : {
678 0 : delete[] name;
679 0 : delete[] argv;
680 0 : name = NULL;
681 0 : argv = NULL;
682 0 : }
683 :
684 1783 : void possible_command::set_name(const char *s1, const char *s2)
685 : {
686 1783 : free(name);
687 1783 : name = (char*)malloc(strlen(s1) + strlen(s2) + 1);
688 1783 : strcpy(name, s1);
689 1783 : strcat(name, s2);
690 1783 : }
691 :
692 31975 : const char *possible_command::get_name()
693 : {
694 31975 : return name;
695 : }
696 :
697 0 : void possible_command::clear_args()
698 : {
699 0 : args.clear();
700 0 : }
701 :
702 10296 : void possible_command::append_arg(const char *s, const char *t)
703 : {
704 10296 : args += s;
705 10296 : if (t)
706 6557 : args += t;
707 10296 : args += '\0';
708 10296 : }
709 :
710 68 : void possible_command::insert_arg(const char *s)
711 : {
712 136 : string str(s);
713 68 : str += '\0';
714 68 : str += args;
715 68 : args = str;
716 68 : }
717 :
718 17 : void possible_command::insert_args(string s)
719 : {
720 17 : const char *p = s.contents();
721 17 : const char *end = p + s.length();
722 17 : int l = 0;
723 17 : if (p >= end)
724 11 : return;
725 : // find the total number of arguments in our string
726 6 : do {
727 12 : l++;
728 12 : p = strchr(p, '\0') + 1;
729 12 : } while (p < end);
730 : // now insert each argument preserving the order
731 18 : for (int i = l - 1; i >= 0; i--) {
732 12 : p = s.contents();
733 25 : for (int j = 0; j < i; j++)
734 13 : p = strchr(p, '\0') + 1;
735 12 : insert_arg(p);
736 : }
737 : }
738 :
739 2850 : void possible_command::build_argv()
740 : {
741 2850 : if (argv)
742 0 : return;
743 : // Count the number of arguments.
744 2850 : int len = args.length();
745 2850 : int argc = 1;
746 2850 : char *p = 0 /* nullptr */;
747 2850 : if (len > 0) {
748 2047 : p = &args[0];
749 53078 : for (int i = 0; i < len; i++)
750 51031 : if (p[i] == '\0')
751 4920 : argc++;
752 : }
753 : // Build an argument vector.
754 2850 : argv = new char *[argc + 1];
755 2850 : argv[0] = name;
756 7770 : for (int i = 1; i < argc; i++) {
757 4920 : argv[i] = p;
758 4920 : p = strchr(p, '\0') + 1;
759 : }
760 2850 : argv[argc] = 0 /* nullptr */;
761 : }
762 :
763 0 : void possible_command::print(int is_last, FILE *fp)
764 : {
765 0 : build_argv();
766 0 : if (IS_BSHELL(argv[0])
767 0 : && argv[1] != 0 /* nullptr */
768 0 : && strcmp(argv[1], BSHELL_DASH_C) == 0
769 0 : && argv[2] != 0 /* nullptr */ && argv[3] == 0 /* nullptr */)
770 0 : fputs(argv[2], fp);
771 : else {
772 0 : fputs(argv[0], fp);
773 0 : string str;
774 0 : for (int i = 1; argv[i] != 0 /* nullptr */; i++) {
775 0 : str.clear();
776 0 : append_arg_to_string(argv[i], str);
777 0 : put_string(str, fp);
778 : }
779 : }
780 0 : if (is_last)
781 0 : putc('\n', fp);
782 : else
783 0 : fputs(" | ", fp);
784 0 : }
785 :
786 0 : void append_arg_to_string(const char *arg, string &str)
787 : {
788 0 : str += ' ';
789 0 : int needs_quoting = 0;
790 : // Native Windows programs don't support '..' style of quoting, so
791 : // always behave as if ARG included the single quote character.
792 : #if defined(_WIN32) && !defined(__CYGWIN__)
793 : int contains_single_quote = 1;
794 : #else
795 0 : int contains_single_quote = 0;
796 : #endif
797 : const char*p;
798 0 : for (p = arg; *p != '\0'; p++)
799 0 : switch (*p) {
800 0 : case ';':
801 : case '&':
802 : case '(':
803 : case ')':
804 : case '|':
805 : case '^':
806 : case '<':
807 : case '>':
808 : case '\n':
809 : case ' ':
810 : case '\t':
811 : case '\\':
812 : case '"':
813 : case '$':
814 : case '?':
815 : case '*':
816 0 : needs_quoting = 1;
817 0 : break;
818 0 : case '\'':
819 0 : contains_single_quote = 1;
820 0 : break;
821 : }
822 0 : if (contains_single_quote || arg[0] == '\0') {
823 0 : str += '"';
824 0 : for (p = arg; *p != '\0'; p++)
825 0 : switch (*p) {
826 : #if !(defined(_WIN32) && !defined(__CYGWIN__))
827 0 : case '"':
828 : case '\\':
829 : case '$':
830 0 : str += '\\';
831 : #else
832 : case '"':
833 : case '\\':
834 : if (*p == '"' || (*p == '\\' && p[1] == '"'))
835 : str += '\\';
836 : #endif
837 : // fall through
838 0 : default:
839 0 : str += *p;
840 0 : break;
841 : }
842 0 : str += '"';
843 : }
844 0 : else if (needs_quoting) {
845 0 : str += '\'';
846 0 : str += arg;
847 0 : str += '\'';
848 : }
849 : else
850 0 : str += arg;
851 0 : }
852 :
853 2850 : char **possible_command::get_argv()
854 : {
855 2850 : build_argv();
856 2850 : return argv;
857 : }
858 :
859 0 : void usage(FILE *stream)
860 : {
861 : // Add `J` to the cluster if we ever get ideal(1) support.
862 0 : fprintf(stream,
863 : "usage: %s [-abcCeEgGijklNpRsStUVXzZ] [-d ctext] [-d string=text]"
864 : " [-D fallback-encoding] [-f font-family] [-F font-directory]"
865 : " [-I inclusion-directory] [-K input-encoding] [-L spooler-argument]"
866 : " [-m macro-package] [-M macro-directory] [-n page-number]"
867 : " [-o page-list] [-P postprocessor-argument] [-r cnumeric-expression]"
868 : " [-r register=numeric-expression] [-T output-device]"
869 : " [-w warning-category] [-W warning-category]"
870 : " [file ...]\n"
871 : "usage: %s {-v | --version}\n"
872 : "usage: %s {-h | --help}\n",
873 : program_name, program_name, program_name);
874 0 : if (stdout == stream)
875 0 : fputs("\n"
876 : "groff (GNU roff) is a typesetting system that reads plain text input\n"
877 : "files that include formatting commands to produce output in\n"
878 : "PostScript, PDF, HTML, or DVI formats or for display to a terminal.\n"
879 : "See the groff(1) manual page.\n",
880 : stream);
881 0 : }
882 :
883 : extern "C" {
884 :
885 0 : void c_error(const char *format, const char *arg1, const char *arg2,
886 : const char *arg3)
887 : {
888 0 : error(format, arg1, arg2, arg3);
889 0 : }
890 :
891 0 : void c_fatal(const char *format, const char *arg1, const char *arg2,
892 : const char *arg3)
893 : {
894 0 : fatal(format, arg1, arg2, arg3);
895 0 : }
896 :
897 : }
898 :
899 : // Local Variables:
900 : // fill-column: 72
901 : // mode: C++
902 : // End:
903 : // vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
|