/*- * This code is derived from software copyrighted by the Free Software * Foundation. * * Modified 1991 by Donn Seeley at UUNET Technologies, Inc. */ #ifndef lint static char sccsid[] = "@(#)groff.cc 6.5 (Berkeley) 5/8/91"; #endif /* not lint */ // -*- C++ -*- /* Copyright (C) 1990, 1991 Free Software Foundation, Inc. Written by James Clark (jjc@jclark.uucp) This file is part of groff. groff is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. groff is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with groff; see the file LICENSE. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ // A C++ implementation of groff.sh. #include <stdio.h> #include <string.h> #include <osfcn.h> #include <stdlib.h> #ifdef HAVE_SYS_SIGLIST #include <signal.h> #endif /* HAVE_SYS_SIGLIST */ #include "lib.h" #include "assert.h" #include "errarg.h" #include "error.h" #include "stringclass.h" #include "font.h" const char *strsignal(int); // HAVE_UNION_WAIT exists only because Sun's osfcn.h includes sys/wait.h. extern "C" { #ifdef HAVE_UNION_WAIT int wait(union wait *); #else /* !HAVE_UNION_WAIT */ int wait(int *); #endif /* !HAVE_UNION_WAIT */ } #ifndef DEVICE #define DEVICE "ps" #endif #ifndef PSPRINT #define PSPRINT "lpr", #endif /* !PSPRINT */ #ifndef DVIPRINT #define DVIPRINT "lpr", "-d", #endif /* !DVIPRINT */ const char *ps_print[] = { PSPRINT 0 }; const char *dvi_print[] = { DVIPRINT 0 }; const char *grops[] = { "grops", 0 }; const char *grotty[] = { "grotty", 0 }; const char *grodvi[] = { "grodvi", 0 }; const char *gxditview[] = { "gxditview", "-", 0 }; struct { const char *name; // Name of the device. const char *description; // Description of device (used in help message). const char **driver; // Driver argument vector. unsigned flags; // Bitwise OR of #define EQN_D_OPTION 01 /* Add -D option to eqn. */ #define PIC_X_OPTION 02 /* Add -x option to pic. */ #define PIC_P_OPTION 04 /* Add -p option to pic. */ #define GROFF_OPTIONS 010 /* Driver understands -F and -v options. */ #define XT_OPTION 020 /* Driver understands -title and -xrm options. */ const char *macro_file; // -m option to pass to troff const char **spooler; // Spooler argument vector. } device_table[] = { { "ps", "PostScript", grops, EQN_D_OPTION|PIC_X_OPTION|PIC_P_OPTION|GROFF_OPTIONS, "ps", ps_print, }, { "ascii", "ASCII", grotty, GROFF_OPTIONS, "tty", 0 }, { "latin1", "ISO Latin-1", grotty, GROFF_OPTIONS, "tty", 0 }, { "dvi", "TeX dvi format", grodvi, GROFF_OPTIONS|PIC_X_OPTION, "dvi", dvi_print, }, { "X100", "X11 previewer at 100dpi", gxditview, EQN_D_OPTION|PIC_X_OPTION|XT_OPTION, "X", 0 }, { "X75", "X11 previewer at 75dpi", gxditview, EQN_D_OPTION|PIC_X_OPTION|XT_OPTION, "X", 0 }, { "X100-12", "X11 previewer at 100dpi (optimized for 12 point text)", gxditview, EQN_D_OPTION|PIC_X_OPTION|XT_OPTION, "X", 0 }, { "X75-12", "X11 previewer at 75dpi (optimized for 12 point text)", gxditview, EQN_D_OPTION|PIC_X_OPTION|XT_OPTION, "X", 0 }, }; const int SOELIM_INDEX = 0; const int PIC_INDEX = SOELIM_INDEX + 1; const int TBL_INDEX = PIC_INDEX + 1; const int EQN_INDEX = TBL_INDEX + 1; const int TROFF_INDEX = EQN_INDEX + 1; const int POST_INDEX = TROFF_INDEX + 1; const int SPOOL_INDEX = POST_INDEX + 1; const int NCOMMANDS = SPOOL_INDEX + 1; // Children exit with this status if the execvp failed. const int EXEC_FAILED_EXIT_STATUS = 0xff; class possible_command { char *name; string args; char **argv; public: int pid; possible_command(); ~possible_command(); void set_name(const char *); const char *get_name(); void append_arg(const char *, const char * = 0); void execp(); void clear_args(); void build_argv(); void print(int is_last, FILE *fp); }; int lflag = 0; possible_command commands[NCOMMANDS]; int run_commands(); void print_commands(); void usage(); void help(); void devices(); void sys_fatal(const char *); int main(int argc, char **argv) { program_name = argv[0]; static char stderr_buf[BUFSIZ]; setbuf(stderr, stderr_buf); string Pargs, Largs; int Vflag = 0; int zflag = 0; int iflag = 0; int opt; const char *device = getenv("GROFF_TYPESETTER"); if (!device) device = DEVICE; commands[TROFF_INDEX].set_name("troff"); while ((opt = getopt(argc, argv, "itpeszavVhblCENZH:F:m:T:f:w:W:M:d:r:n:o:P:L:")) != EOF) { char buf[2]; buf[0] = '-'; buf[1] = opt; buf[2] = '\0'; switch (opt) { case 'i': iflag = 1; break; case 't': commands[TBL_INDEX].set_name("tbl"); break; case 'p': commands[PIC_INDEX].set_name("pic"); break; case 'e': commands[EQN_INDEX].set_name("eqn"); break; case 's': commands[SOELIM_INDEX].set_name("soelim"); break; case 'z': case 'a': commands[TROFF_INDEX].append_arg(buf); // fall through case 'Z': zflag++; break; case 'l': lflag++; break; case 'V': Vflag++; break; case 'v': commands[POST_INDEX].append_arg(buf); // fall through case 'C': commands[SOELIM_INDEX].append_arg(buf); commands[PIC_INDEX].append_arg(buf); commands[TBL_INDEX].append_arg(buf); commands[EQN_INDEX].append_arg(buf); commands[TROFF_INDEX].append_arg(buf); break; case 'N': commands[EQN_INDEX].append_arg(buf); break; case 'h': help(); break; case 'E': case 'b': commands[TROFF_INDEX].append_arg(buf); break; case 'T': device = optarg; break; case 'F': font::command_line_font_dir(optarg); commands[POST_INDEX].append_arg(buf, optarg); // fall through case 'f': case 'o': case 'm': case 'r': case 'M': case 'H': case 'd': case 'n': case 'w': case 'W': commands[TROFF_INDEX].append_arg(buf, optarg); break; case 'P': Pargs += optarg; Pargs += '\0'; break; case 'L': Largs += optarg; Largs += '\0'; break; case '?': usage(); break; default: assert(0); break; } } int found = 0; for (int device_index = 0; device_index < sizeof(device_table)/sizeof(device_table[0]); device_index++) if (strcmp(device, device_table[device_index].name) == 0) { found = 1; break; } if (!found) { error("unknown device `%1'", device); devices(); exit(1); } if (device_table[device_index].macro_file != 0) commands[TROFF_INDEX].append_arg("-m", device_table[device_index].macro_file); commands[POST_INDEX].set_name(device_table[device_index].driver[0]); if (!(device_table[device_index].flags & GROFF_OPTIONS)) commands[POST_INDEX].clear_args(); if ((device_table[device_index].flags & XT_OPTION) && argc - optind == 1) { commands[POST_INDEX].append_arg("-title"); commands[POST_INDEX].append_arg(argv[optind]); commands[POST_INDEX].append_arg("-xrm"); commands[POST_INDEX].append_arg("*iconName:", argv[optind]); } const char *p = Pargs.contents(); const char *end = p + Pargs.length(); while (p < end) { commands[POST_INDEX].append_arg(p); p = strchr(p, '\0') + 1; } int i; for (i = 1; device_table[device_index].driver[i]; i++) commands[POST_INDEX].append_arg(device_table[device_index].driver[i]); if (device_table[device_index].flags & PIC_X_OPTION) commands[PIC_INDEX].append_arg("-x"); if (device_table[device_index].flags & PIC_P_OPTION) commands[PIC_INDEX].append_arg("-p"); if (device_table[device_index].flags & EQN_D_OPTION) commands[EQN_INDEX].append_arg("-D"); if (lflag && device_table[device_index].spooler) { commands[SPOOL_INDEX].set_name(device_table[device_index].spooler[0]); p = Largs.contents(); end = p + Largs.length(); while (p < end) { commands[SPOOL_INDEX].append_arg(p); p = strchr(p, '\0') + 1; } for (i = 1; device_table[device_index].spooler[i]; i++) commands[SPOOL_INDEX].append_arg(device_table[device_index].spooler[i]); } if (zflag) { commands[POST_INDEX].set_name(0); commands[SPOOL_INDEX].set_name(0); } commands[TROFF_INDEX].append_arg("-T", device); int have_eqnchar = 0; if (commands[EQN_INDEX].get_name() != 0) { commands[EQN_INDEX].append_arg("-T", device); font::set_device_name(device); char *path = 0; FILE *fp = font::open_file("eqnchar", &path); if (fp) { fclose(fp); if (path[0] == '-' && path[1] != '\0') commands[EQN_INDEX].append_arg("--"); commands[EQN_INDEX].append_arg(path); delete path; have_eqnchar = 1; } } for (int first_index = 0; first_index < TROFF_INDEX; first_index++) if (commands[first_index].get_name() != 0) break; if (optind < argc) { if (argv[optind][0] == '-' && argv[optind][1] != '\0' && (!have_eqnchar || first_index != EQN_INDEX)) commands[first_index].append_arg("--"); for (i = optind; i < argc; i++) commands[first_index].append_arg(argv[i]); if (iflag) commands[first_index].append_arg("-"); } if (have_eqnchar && (first_index != EQN_INDEX || optind >= argc)) commands[EQN_INDEX].append_arg("-"); if (Vflag) { print_commands(); exit(0); } exit(run_commands()); } void print_commands() { for (int last = SPOOL_INDEX; last >= 0; last--) if (commands[last].get_name() != 0) break; for (int i = 0; i <= last; i++) if (commands[i].get_name() != 0) commands[i].print(i == last, stdout); } // Run the commands. Return the code with which to exit. int run_commands() { for (int last = SPOOL_INDEX; last >= 0; last--) if (commands[last].get_name() != 0) break; int last_input = 0; int proc_count = 0; for (int i = 0; i <= last; i++) if (commands[i].get_name() != 0) { proc_count++; int pdes[2]; if (i != last) { if (pipe(pdes) < 0) sys_fatal("pipe"); } #ifdef HAVE_VFORK int pid = vfork(); if (pid < 0) sys_fatal("vfork"); #else /* !HAVE_VFORK */ int pid = fork(); if (pid < 0) sys_fatal("fork"); #endif /* !HAVE_VFORK */ if (pid == 0) { // child if (last_input != 0) { if (close(0) < 0) sys_fatal("close"); if (dup(last_input) < 0) sys_fatal("dup"); if (close(last_input) < 0) sys_fatal("close"); } if (i != last) { if (close(1) < 0) sys_fatal("close"); if (dup(pdes[1]) < 0) sys_fatal("dup"); if (close(pdes[1]) < 0) sys_fatal("close"); if (close(pdes[0])) sys_fatal("close"); } commands[i].execp(); } // in the parent if (last_input != 0) { if (close(last_input) < 0) sys_fatal("close"); } if (i != last) { if (close(pdes[1]) < 0) sys_fatal("close"); last_input = pdes[0]; } commands[i].pid = pid; } int ret = 0; while (proc_count > 0) { int status; #ifdef HAVE_UNION_WAIT // union wait is just syntactic sugar: it's really just an int. int pid = wait((union wait *)&status); #else /* !HAVE_UNION_WAIT */ int pid = wait(&status); #endif /* !HAVE_UNION_WAIT */ if (pid < 0) sys_fatal("wait"); for (i = 0; i < NCOMMANDS; i++) if (commands[i].pid == pid) { --proc_count; if ((status & 0xffff) != 0) { if ((status & 0xff) != 0) { error("%1: %2%3", commands[i].get_name(), strsignal(status & 0x7f), (status & 0x80) ? " (core dumped)" : ""); ret |= 2; } else { int exit_status = (status >> 8) & 0xff; if (exit_status == EXEC_FAILED_EXIT_STATUS) ret |= 4; else if (exit_status != 0) ret |= 1; } } break; } } return ret; } possible_command::possible_command() : pid(-1), name(0), argv(0) { } possible_command::~possible_command() { delete name; delete argv; } void possible_command::set_name(const char *s) { name = strsave(s); } const char *possible_command::get_name() { return name; } void possible_command::clear_args() { args.clear(); } void possible_command::append_arg(const char *s, const char *t) { args += s; if (t) args += t; args += '\0'; } void possible_command::build_argv() { // Count the number of arguments. int len = args.length(); int argc = 1; char *p = 0; if (len > 0) { p = &args[0]; for (int i = 0; i < len; i++) if (p[i] == '\0') argc++; } // Build an argument vector. argv = new char *[argc + 1]; argv[0] = name; for (int i = 1; i < argc; i++) { argv[i] = p; p = strchr(p, '\0') + 1; } argv[argc] = 0; } void possible_command::print(int is_last, FILE *fp) { build_argv(); fputs(argv[0], fp); for (int i = 1; argv[i] != 0; i++) { int needs_quoting = 0; int contains_single_quote = 0; for (const char *p = argv[i]; *p != '\0'; p++) switch (*p) { case ';': case '&': case '(': case ')': case '|': case '^': case '<': case '>': case '\n': case ' ': case '\t': case '\\': case '"': case '$': needs_quoting = 1; break; case '\'': contains_single_quote = 1; break; } if (contains_single_quote || argv[i][0] == '\0') { putc(' ', fp); putc('"', fp); for (p = argv[i]; *p != '\0'; p++) switch (*p) { case '"': case '\\': case '$': putc('\\', fp); // fall through default: putc(*p, fp); break; } putc('"', fp); } else if (needs_quoting) fprintf(fp, " '%s'", argv[i]); else fprintf(fp, " %s", argv[i]); } if (is_last) putc('\n', fp); else fputs(" | ", fp); } void possible_command::execp() { build_argv(); execvp(name, argv); error("couldn't exec %1: %2", name, strerror(errno)); #ifdef HAVE_VFORK // vfork(2) says not to call exit when execve fails after vforking. _exit(EXEC_FAILED_EXIT_STATUS); #else exit(EXEC_FAILED_EXIT_STATUS); #endif } void synopsis() { fprintf(stderr, "usage: %s [-abehilpstvzCENVZ] [-Hfile] [-Fdir] [-mname] [-Tdev] [-ffam]\n" " [-wname] [-Wname] [ -Mdir] [-dcs] [-rcn] [-nnum] [-olist] [-Parg]\n" " [-Larg] [files...]\n", program_name); } void devices() { fputs("Available devices are:\n", stderr); for (int i = 0; i < sizeof(device_table)/sizeof(device_table[0]); i++) fprintf(stderr, "%s\t%s\n", device_table[i].name, device_table[i].description); } void help() { synopsis(); fputs("\n" "-h\tprint this message\n" "-t\tpreprocess with tbl\n" "-p\tpreprocess with pic\n" "-e\tpreprocess with eqn\n" "-s\tpreprocess with soelim\n" "-Tdev\tuse device dev\n" "-mname\tread macros tmac.name\n" "-dcs\tdefine a string c as s\n" "-rcn\tdefine a number register c as n\n" "-nnum\tnumber first page n\n" "-olist\toutput only pages in list\n" "-ffam\tuse fam as the default font family\n" "-Fdir\tsearch directory dir for device directories\n" "-Mdir\tsearch dir for macro files\n" "-Hfile\tread hyphenation patterns from file\n" "-v\tprint version number\n" "-z\tsuppress formatted output\n" "-Z\tdon't postprocess\n" "-a\tproduce ASCII description of output\n" "-i\tread standard input after named input files\n" "-wname\tenable warning name\n" "-Wname\tinhibit warning name\n" "-E\tinhibit all errors\n" "-b\tprint backtraces with errors or warnings\n" "-l\tspool the output\n" "-C\tenable compatibility mode\n" "-V\tprint commands on stdout instead of running them\n" "-Parg\tpass arg to the postprocessor\n" "-Larg\tpass arg to the spooler\n" "-N\tdon't allow newlines within eqn delimiters\n" "\n", stderr); devices(); exit(0); } void usage() { synopsis(); fprintf(stderr, "%s -h gives more help\n", program_name); exit(1); } void sys_fatal(const char *s) { fatal("%1: %2", s, strerror(errno)); } #ifdef HAVE_SYS_SIGLIST extern "C" { extern char *sys_siglist[]; } #endif /* HAVE_SYS_SIGLIST */ const char *strsignal(int n) { static char buf[sizeof("Signal ") + 1 + INT_DIGITS]; #ifdef HAVE_SYS_SIGLIST if (n >= 0 && n < NSIG && sys_siglist[n] != 0) return sys_siglist[n]; #endif /* HAVE_SYS_SIGLIST */ sprintf(buf, "Signal %d", n); return buf; }