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
main(int argc,char ** argv)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
add_file_directory_to_path(char * filename)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
info_error(char * format,void * arg1,void * arg2)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
info_short_help(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
init_messages(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