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