1 /* info.c -- Display nodes of Info files in multiple windows. 2 $Id: info.c,v 1.10 2015/11/14 23:06:06 deraadt Exp $ 3 4 Copyright (C) 1993, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 5 2004 Free Software Foundation, Inc. 6 7 This program is free software; you can redistribute it and/or modify 8 it under the terms of the GNU General Public License as published by 9 the Free Software Foundation; either version 2, or (at your option) 10 any later version. 11 12 This program is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with this program; if not, write to the Free Software 19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 20 21 Written by Brian Fox (bfox@ai.mit.edu). */ 22 23 #include "info.h" 24 #include "indices.h" 25 #include "dribble.h" 26 #include "getopt.h" 27 #if defined (HANDLE_MAN_PAGES) 28 # include "man.h" 29 #endif /* HANDLE_MAN_PAGES */ 30 31 static char *program_name = "info"; 32 33 /* Non-zero means search all indices for APROPOS_SEARCH_STRING. */ 34 static int apropos_p = 0; 35 36 /* Variable containing the string to search for when apropos_p is non-zero. */ 37 static char *apropos_search_string = (char *)NULL; 38 39 /* Non-zero means search all indices for INDEX_SEARCH_STRING. Unlike 40 apropos, this puts the user at the node, running info. */ 41 static int index_search_p = 0; 42 43 /* Non-zero means look for the node which describes the invocation 44 and command-line options of the program, and start the info 45 session at that node. */ 46 static int goto_invocation_p = 0; 47 48 /* Variable containing the string to search for when index_search_p is 49 non-zero. */ 50 static char *index_search_string = (char *)NULL; 51 52 /* Non-zero means print version info only. */ 53 static int print_version_p = 0; 54 55 /* Non-zero means print a short description of the options. */ 56 static int print_help_p = 0; 57 58 /* Array of the names of nodes that the user specified with "--node" on the 59 command line. */ 60 static char **user_nodenames = (char **)NULL; 61 static int user_nodenames_index = 0; 62 static int user_nodenames_slots = 0; 63 64 /* String specifying the first file to load. This string can only be set 65 by the user specifying "--file" on the command line. */ 66 static char *user_filename = (char *)NULL; 67 68 /* String specifying the name of the file to dump nodes to. This value is 69 filled if the user speficies "--output" on the command line. */ 70 static char *user_output_filename = (char *)NULL; 71 72 /* Non-zero indicates that when "--output" is specified, all of the menu 73 items of the specified nodes (and their subnodes as well) should be 74 dumped in the order encountered. This basically can print a book. */ 75 int dump_subnodes = 0; 76 77 /* Non-zero means make default keybindings be loosely modeled on vi(1). */ 78 int vi_keys_p = 0; 79 80 /* Non-zero means don't remove ANSI escape sequences. */ 81 int raw_escapes_p = 1; 82 83 /* Non-zero means print the absolute location of the file to be loaded. */ 84 static int print_where_p = 0; 85 86 #ifdef __MSDOS__ 87 /* Non-zero indicates that screen output should be made 'speech-friendly'. 88 Since on MSDOS the usual behavior is to write directly to the video 89 memory, speech synthesizer software cannot grab the output. Therefore, 90 we provide a user option which tells us to avoid direct screen output 91 and use stdout instead (which loses the color output). */ 92 int speech_friendly = 0; 93 #endif 94 95 /* Structure describing the options that Info accepts. We pass this structure 96 to getopt_long (). If you add or otherwise change this structure, you must 97 also change the string which follows it. */ 98 #define APROPOS_OPTION 1 99 #define DRIBBLE_OPTION 2 100 #define RESTORE_OPTION 3 101 #define IDXSRCH_OPTION 4 102 static struct option long_options[] = { 103 { "apropos", 1, 0, APROPOS_OPTION }, 104 { "directory", 1, 0, 'd' }, 105 { "dribble", 1, 0, DRIBBLE_OPTION }, 106 { "file", 1, 0, 'f' }, 107 { "help", 0, &print_help_p, 1 }, 108 { "index-search", 1, 0, IDXSRCH_OPTION }, 109 { "location", 0, &print_where_p, 1 }, 110 { "node", 1, 0, 'n' }, 111 { "output", 1, 0, 'o' }, 112 { "raw-escapes", 0, &raw_escapes_p, 1 }, 113 { "no-raw-escapes", 0, &raw_escapes_p, 0 }, 114 { "restore", 1, 0, RESTORE_OPTION }, 115 { "show-options", 0, 0, 'O' }, 116 { "subnodes", 0, &dump_subnodes, 1 }, 117 { "usage", 0, 0, 'O' }, 118 { "version", 0, &print_version_p, 1 }, 119 { "vi-keys", 0, &vi_keys_p, 1 }, 120 { "where", 0, &print_where_p, 1 }, 121 #ifdef __MSDOS__ 122 { "speech-friendly", 0, &speech_friendly, 1 }, 123 #endif 124 {NULL, 0, NULL, 0} 125 }; 126 127 /* String describing the shorthand versions of the long options found above. */ 128 #ifdef __MSDOS__ 129 static char *short_options = "d:n:f:ho:ORswb"; 130 #else 131 static char *short_options = "d:n:f:ho:ORws"; 132 #endif 133 134 /* When non-zero, the Info window system has been initialized. */ 135 int info_windows_initialized_p = 0; 136 137 /* Some "forward" declarations. */ 138 static void info_short_help (void); 139 static void init_messages (void); 140 141 142 /* **************************************************************** */ 143 /* */ 144 /* Main Entry Point to the Info Program */ 145 /* */ 146 /* **************************************************************** */ 147 148 int 149 main (int argc, char **argv) 150 { 151 int getopt_long_index; /* Index returned by getopt_long (). */ 152 NODE *initial_node; /* First node loaded by Info. */ 153 154 #ifdef HAVE_SETLOCALE 155 /* Set locale via LC_ALL. */ 156 setlocale (LC_ALL, ""); 157 #endif 158 159 if (pledge ("stdio rpath wpath getpw cpath tty proc exec", NULL) == -1) { 160 fprintf (stderr, _("%s: pledge\n"), program_name); 161 exit (1); 162 } 163 164 #ifdef ENABLE_NLS 165 /* Set the text message domain. */ 166 bindtextdomain (PACKAGE, LOCALEDIR); 167 textdomain (PACKAGE); 168 #endif 169 170 init_messages (); 171 172 while (1) 173 { 174 int option_character; 175 176 option_character = getopt_long 177 (argc, argv, short_options, long_options, &getopt_long_index); 178 179 /* getopt_long returns EOF when there are no more long options. */ 180 if (option_character == EOF) 181 break; 182 183 /* If this is a long option, then get the short version of it. */ 184 if (option_character == 0 && long_options[getopt_long_index].flag == 0) 185 option_character = long_options[getopt_long_index].val; 186 187 /* Case on the option that we have received. */ 188 switch (option_character) 189 { 190 case 0: 191 break; 192 193 /* User wants to add a directory. */ 194 case 'd': 195 info_add_path (optarg, INFOPATH_PREPEND); 196 break; 197 198 /* User is specifying a particular node. */ 199 case 'n': 200 add_pointer_to_array (optarg, user_nodenames_index, user_nodenames, 201 user_nodenames_slots, 10, char *); 202 break; 203 204 /* User is specifying a particular Info file. */ 205 case 'f': 206 if (user_filename) 207 free (user_filename); 208 209 user_filename = xstrdup (optarg); 210 break; 211 212 /* Treat -h like --help. */ 213 case 'h': 214 print_help_p = 1; 215 break; 216 217 /* User is specifying the name of a file to output to. */ 218 case 'o': 219 if (user_output_filename) 220 free (user_output_filename); 221 user_output_filename = xstrdup (optarg); 222 break; 223 224 /* User has specified that she wants to find the "Options" 225 or "Invocation" node for the program. */ 226 case 'O': 227 goto_invocation_p = 1; 228 break; 229 230 /* User has specified that she wants the escape sequences 231 in man pages to be passed thru unaltered. */ 232 case 'R': 233 raw_escapes_p = 1; 234 break; 235 236 /* User is specifying that she wishes to dump the subnodes of 237 the node that she is dumping. */ 238 case 's': 239 dump_subnodes = 1; 240 break; 241 242 /* For compatibility with man, -w is --where. */ 243 case 'w': 244 print_where_p = 1; 245 break; 246 247 #ifdef __MSDOS__ 248 /* User wants speech-friendly output. */ 249 case 'b': 250 speech_friendly = 1; 251 break; 252 #endif /* __MSDOS__ */ 253 254 /* User has specified a string to search all indices for. */ 255 case APROPOS_OPTION: 256 apropos_p = 1; 257 maybe_free (apropos_search_string); 258 apropos_search_string = xstrdup (optarg); 259 break; 260 261 /* User has specified a dribble file to receive keystrokes. */ 262 case DRIBBLE_OPTION: 263 close_dribble_file (); 264 open_dribble_file (optarg); 265 break; 266 267 /* User has specified an alternate input stream. */ 268 case RESTORE_OPTION: 269 info_set_input_from_file (optarg); 270 break; 271 272 /* User has specified a string to search all indices for. */ 273 case IDXSRCH_OPTION: 274 index_search_p = 1; 275 maybe_free (index_search_string); 276 index_search_string = xstrdup (optarg); 277 break; 278 279 default: 280 fprintf (stderr, _("Try --help for more information.\n")); 281 xexit (1); 282 } 283 } 284 285 /* If the output device is not a terminal, and no output filename has been 286 specified, make user_output_filename be "-", so that the info is written 287 to stdout, and turn on the dumping of subnodes. */ 288 if ((!isatty (fileno (stdout))) && (user_output_filename == (char *)NULL)) 289 { 290 user_output_filename = xstrdup ("-"); 291 dump_subnodes = 1; 292 } 293 294 /* If the user specified --version, then show the version and exit. */ 295 if (print_version_p) 296 { 297 printf ("%s (GNU %s) %s\n", program_name, PACKAGE, VERSION); 298 puts (""); 299 puts ("Copyright (C) 2004 Free Software Foundation, Inc."); 300 printf (_("There is NO warranty. You may redistribute this software\n\ 301 under the terms of the GNU General Public License.\n\ 302 For more information about these matters, see the files named COPYING.\n")); 303 xexit (0); 304 } 305 306 /* If the `--help' option was present, show the help and exit. */ 307 if (print_help_p) 308 { 309 info_short_help (); 310 xexit (0); 311 } 312 313 /* If the user hasn't specified a path for Info files, default it. 314 Lowest priority is our messy hardwired list in filesys.h. 315 Then comes the user's INFODIR from the Makefile. 316 Highest priority is the environment variable, if set. */ 317 if (!infopath) 318 { 319 char *path_from_env = getenv ("INFOPATH"); 320 321 if (path_from_env) 322 { 323 unsigned len = strlen (path_from_env); 324 /* Trailing : on INFOPATH means insert the default path. */ 325 if (len && path_from_env[len - 1] == PATH_SEP[0]) 326 { 327 path_from_env[len - 1] = 0; 328 info_add_path (DEFAULT_INFOPATH, INFOPATH_PREPEND); 329 } 330 #ifdef INFODIR /* from the Makefile */ 331 info_add_path (INFODIR, INFOPATH_PREPEND); 332 #endif 333 info_add_path (path_from_env, INFOPATH_PREPEND); 334 } 335 else 336 { 337 info_add_path (DEFAULT_INFOPATH, INFOPATH_PREPEND); 338 #ifdef INFODIR /* from the Makefile */ 339 info_add_path (INFODIR, INFOPATH_PREPEND); 340 #endif 341 #ifdef INFODIR2 /* from the Makefile, too */ 342 # ifdef INFODIR 343 if (!STREQ (INFODIR, INFODIR2)) 344 # endif 345 info_add_path (INFODIR2, INFOPATH_PREPEND); 346 #endif 347 } 348 } 349 350 /* If the user specified a particular filename, add the path of that 351 file to the contents of INFOPATH. */ 352 if (user_filename) 353 add_file_directory_to_path (user_filename); 354 355 /* If the user wants to search every known index for a given string, 356 do that now, and report the results. */ 357 if (apropos_p) 358 { 359 info_apropos (apropos_search_string); 360 xexit (0); 361 } 362 363 /* Get the initial Info node. It is either "(dir)Top", or what the user 364 specified with values in user_filename and user_nodenames. */ 365 initial_node = info_get_node (user_filename, 366 user_nodenames ? user_nodenames[0] : 0); 367 368 /* If we couldn't get the initial node, this user is in trouble. */ 369 if (!initial_node) 370 { 371 if (info_recent_file_error) 372 info_error (info_recent_file_error, NULL, NULL); 373 else 374 info_error ((char *) msg_cant_find_node, 375 user_nodenames ? user_nodenames[0] : "Top", NULL); 376 xexit (1); 377 } 378 379 /* Special cases for when the user specifies multiple nodes. If we 380 are dumping to an output file, dump all of the nodes specified. 381 Otherwise, attempt to create enough windows to handle the nodes 382 that this user wants displayed. */ 383 if (user_nodenames_index > 1) 384 { 385 free (initial_node); 386 387 if (print_where_p) 388 printf ("%s\n", user_filename ? user_filename : "unknown?!"); 389 else if (user_output_filename) 390 dump_nodes_to_file 391 (user_filename, user_nodenames, user_output_filename, dump_subnodes); 392 else 393 begin_multiple_window_info_session (user_filename, user_nodenames); 394 395 xexit (0); 396 } 397 398 /* If there are arguments remaining, they are the names of menu items 399 in sequential info files starting from the first one loaded. That 400 file name is either "dir", or the contents of user_filename if one 401 was specified. */ 402 { 403 const char *errstr; 404 char *errarg1, *errarg2; 405 406 NODE *new_initial_node = info_follow_menus (initial_node, argv + optind, 407 &errstr, &errarg1, &errarg2); 408 409 if (new_initial_node && new_initial_node != initial_node) 410 initial_node = new_initial_node; 411 412 if (print_where_p) 413 { 414 if (initial_node->parent) 415 printf ("%s\n", initial_node->parent); 416 else if (initial_node->filename 417 && !is_dir_name (filename_non_directory (initial_node->filename))) 418 printf ("%s\n", initial_node->filename); 419 else 420 xexit (1); 421 xexit (0); 422 } 423 424 /* If the user specified that this node should be output, then do that 425 now. Otherwise, start the Info session with this node. Or act 426 accordingly if the initial node was not found. */ 427 if (user_output_filename && !goto_invocation_p) 428 { 429 if (!errstr) 430 dump_node_to_file (initial_node, user_output_filename, 431 dump_subnodes); 432 else 433 info_error ((char *) errstr, errarg1, errarg2); 434 } 435 else 436 { 437 438 if (errstr) 439 begin_info_session_with_error (initial_node, (char *) errstr, 440 errarg1, errarg2); 441 /* If the user specified `--index-search=STRING' or 442 --show-options, start the info session in the node 443 corresponding to what they want. */ 444 else if (index_search_p || goto_invocation_p) 445 { 446 int status = 0; 447 448 initialize_info_session (initial_node, 0); 449 450 if (goto_invocation_p 451 || index_entry_exists (windows, index_search_string)) 452 { 453 terminal_prep_terminal (); 454 terminal_clear_screen (); 455 info_last_executed_command = (VFunction *)NULL; 456 457 if (index_search_p) 458 do_info_index_search (windows, 0, index_search_string); 459 else 460 { 461 /* If they said "info --show-options foo bar baz", 462 the last of the arguments is the program whose 463 options they want to see. */ 464 char **p = argv + optind; 465 char *program; 466 467 if (*p) 468 { 469 while (p[1]) 470 p++; 471 program = xstrdup (*p); 472 } 473 else if (user_filename) 474 /* If there's no command-line arguments to 475 supply the program name, use the Info file 476 name (sans extension and leading directories) 477 instead. */ 478 program = program_name_from_file_name (user_filename); 479 else 480 program = xstrdup (""); 481 482 info_intuit_options_node (windows, initial_node, program); 483 free (program); 484 } 485 486 if (user_output_filename) 487 { 488 dump_node_to_file (windows->node, user_output_filename, 489 dump_subnodes); 490 } 491 else 492 info_read_and_dispatch (); 493 494 /* On program exit, leave the cursor at the bottom of the 495 window, and restore the terminal IO. */ 496 terminal_goto_xy (0, screenheight - 1); 497 terminal_clear_to_eol (); 498 fflush (stdout); 499 terminal_unprep_terminal (); 500 } 501 else 502 { 503 fprintf (stderr, _("no index entries found for `%s'\n"), 504 index_search_string); 505 status = 2; 506 } 507 508 close_dribble_file (); 509 xexit (status); 510 } 511 else 512 begin_info_session (initial_node); 513 } 514 515 xexit (0); 516 } 517 518 return 0; /* Avoid bogus warnings. */ 519 } 520 521 void 522 add_file_directory_to_path (char *filename) 523 { 524 char *directory_name = xstrdup (filename); 525 char *temp = filename_non_directory (directory_name); 526 527 if (temp != directory_name) 528 { 529 if (HAVE_DRIVE (directory_name) && temp == directory_name + 2) 530 { 531 /* The directory of "d:foo" is stored as "d:.", to avoid 532 mixing it with "d:/" when a slash is appended. */ 533 *temp = '.'; 534 temp += 2; 535 } 536 temp[-1] = 0; 537 info_add_path (directory_name, INFOPATH_PREPEND); 538 } 539 540 free (directory_name); 541 } 542 543 544 /* Error handling. */ 545 546 /* Non-zero if an error has been signalled. */ 547 int info_error_was_printed = 0; 548 549 /* Non-zero means ring terminal bell on errors. */ 550 int info_error_rings_bell_p = 1; 551 552 /* Print FORMAT with ARG1 and ARG2. If the window system was initialized, 553 then the message is printed in the echo area. Otherwise, a message is 554 output to stderr. */ 555 void 556 info_error (char *format, void *arg1, void *arg2) 557 { 558 info_error_was_printed = 1; 559 560 if (!info_windows_initialized_p || display_inhibited) 561 { 562 fprintf (stderr, "%s: ", program_name); 563 fprintf (stderr, format, arg1, arg2); 564 fprintf (stderr, "\n"); 565 fflush (stderr); 566 } 567 else 568 { 569 if (!echo_area_is_active) 570 { 571 if (info_error_rings_bell_p) 572 terminal_ring_bell (); 573 window_message_in_echo_area (format, arg1, arg2); 574 } 575 else 576 { 577 NODE *temp; 578 579 temp = build_message_node (format, arg1, arg2); 580 if (info_error_rings_bell_p) 581 terminal_ring_bell (); 582 inform_in_echo_area (temp->contents); 583 free (temp->contents); 584 free (temp); 585 } 586 } 587 } 588 589 590 /* Produce a scaled down description of the available options to Info. */ 591 static void 592 info_short_help (void) 593 { 594 #ifdef __MSDOS__ 595 static const char speech_friendly_string[] = N_("\ 596 -b, --speech-friendly be friendly to speech synthesizers.\n"); 597 #else 598 static const char speech_friendly_string[] = ""; 599 #endif 600 601 602 printf (_("\ 603 Usage: %s [OPTION]... [MENU-ITEM...]\n\ 604 \n\ 605 Read documentation in Info format.\n\ 606 \n\ 607 Options:\n\ 608 --apropos=STRING look up STRING in all indices of all manuals.\n\ 609 -d, --directory=DIR add DIR to INFOPATH.\n\ 610 --dribble=FILENAME remember user keystrokes in FILENAME.\n\ 611 -f, --file=FILENAME specify Info file to visit.\n\ 612 -h, --help display this help and exit.\n\ 613 --index-search=STRING go to node pointed by index entry STRING.\n\ 614 -n, --node=NODENAME specify nodes in first visited Info file.\n\ 615 -o, --output=FILENAME output selected nodes to FILENAME.\n\ 616 -R, --raw-escapes output \"raw\" ANSI escapes (default).\n\ 617 --no-raw-escapes output escapes as literal text.\n\ 618 --restore=FILENAME read initial keystrokes from FILENAME.\n\ 619 -O, --show-options, --usage go to command-line options node.\n%s\ 620 --subnodes recursively output menu items.\n\ 621 -w, --where, --location print physical location of Info file.\n\ 622 --vi-keys use vi-like and less-like key bindings.\n\ 623 --version display version information and exit.\n\ 624 \n\ 625 The first non-option argument, if present, is the menu entry to start from;\n\ 626 it is searched for in all `dir' files along INFOPATH.\n\ 627 If it is not present, info merges all `dir' files and shows the result.\n\ 628 Any remaining arguments are treated as the names of menu\n\ 629 items relative to the initial node visited.\n\ 630 \n\ 631 Examples:\n\ 632 info show top-level dir menu\n\ 633 info emacs start at emacs node from top-level dir\n\ 634 info emacs buffers start at buffers node within emacs manual\n\ 635 info --show-options emacs start at node with emacs' command line options\n\ 636 info -f ./foo.info show file ./foo.info, not searching dir\n\ 637 "), 638 program_name, speech_friendly_string); 639 640 puts (_("\n\ 641 Email bug reports to bug-texinfo@gnu.org,\n\ 642 general questions and discussion to help-texinfo@gnu.org.\n\ 643 Texinfo home page: http://www.gnu.org/software/texinfo/")); 644 645 xexit (0); 646 } 647 648 649 /* Initialize strings for gettext. Because gettext doesn't handle N_ or 650 _ within macro definitions, we put shared messages into variables and 651 use them that way. This also has the advantage that there's only one 652 copy of the strings. */ 653 654 const char *msg_cant_find_node; 655 const char *msg_cant_file_node; 656 const char *msg_cant_find_window; 657 const char *msg_cant_find_point; 658 const char *msg_cant_kill_last; 659 const char *msg_no_menu_node; 660 const char *msg_no_foot_node; 661 const char *msg_no_xref_node; 662 const char *msg_no_pointer; 663 const char *msg_unknown_command; 664 const char *msg_term_too_dumb; 665 const char *msg_at_node_bottom; 666 const char *msg_at_node_top; 667 const char *msg_one_window; 668 const char *msg_win_too_small; 669 const char *msg_cant_make_help; 670 671 static void 672 init_messages (void) 673 { 674 msg_cant_find_node = _("Cannot find node `%s'."); 675 msg_cant_file_node = _("Cannot find node `(%s)%s'."); 676 msg_cant_find_window = _("Cannot find a window!"); 677 msg_cant_find_point = _("Point doesn't appear within this window's node!"); 678 msg_cant_kill_last = _("Cannot delete the last window."); 679 msg_no_menu_node = _("No menu in this node."); 680 msg_no_foot_node = _("No footnotes in this node."); 681 msg_no_xref_node = _("No cross references in this node."); 682 msg_no_pointer = _("No `%s' pointer for this node."); 683 msg_unknown_command = _("Unknown Info command `%c'; try `?' for help."); 684 msg_term_too_dumb = _("Terminal type `%s' is not smart enough to run Info."); 685 msg_at_node_bottom = _("You are already at the last page of this node."); 686 msg_at_node_top = _("You are already at the first page of this node."); 687 msg_one_window = _("Only one window."); 688 msg_win_too_small = _("Resulting window would be too small."); 689 msg_cant_make_help = _("Not enough room for a help window, please delete a window."); 690 } 691