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