1/*	$NetBSD: nodes.c,v 1.2 2016/01/14 00:34:52 christos Exp $	*/
2
3/* nodes.c -- how to get an Info file and node.
4   Id: nodes.c,v 1.4 2004/04/11 17:56:46 karl Exp
5
6   Copyright (C) 1993, 1998, 1999, 2000, 2002, 2003, 2004 Free Software
7   Foundation, Inc.
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 2, or (at your option)
12   any later version.
13
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19   You should have received a copy of the GNU General Public License
20   along with this program; if not, write to the Free Software
21   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
23   Originally written by Brian Fox (bfox@ai.mit.edu). */
24
25#include "info.h"
26
27#include "nodes.h"
28#include "search.h"
29#include "filesys.h"
30#include "info-utils.h"
31
32#if defined (HANDLE_MAN_PAGES)
33#  include "man.h"
34#endif /* HANDLE_MAN_PAGES */
35
36static void forget_info_file (char *filename);
37static void remember_info_file (FILE_BUFFER *file_buffer);
38static void free_file_buffer_tags (FILE_BUFFER *file_buffer);
39static void free_info_tag (TAG *tag);
40static void get_nodes_of_tags_table (FILE_BUFFER *file_buffer,
41    SEARCH_BINDING *buffer_binding);
42static void get_nodes_of_info_file (FILE_BUFFER *file_buffer);
43static void get_tags_of_indirect_tags_table (FILE_BUFFER *file_buffer,
44    SEARCH_BINDING *indirect_binding, SEARCH_BINDING *tags_binding);
45static void info_reload_file_buffer_contents (FILE_BUFFER *fb);
46static char *adjust_nodestart (NODE *node, int min, int max);
47static FILE_BUFFER *info_load_file_internal (char *filename, int get_tags);
48static FILE_BUFFER *info_find_file_internal (char *filename, int get_tags);
49static NODE *info_node_of_file_buffer_tags (FILE_BUFFER *file_buffer,
50    char *nodename);
51
52static long get_node_length (SEARCH_BINDING *binding);
53
54/* Magic number that RMS used to decide how much a tags table pointer could
55   be off by.  I feel that it should be much smaller, like 4.  */
56#define DEFAULT_INFO_FUDGE 1000
57
58/* Passed to *_internal functions.  INFO_GET_TAGS says to do what is
59   neccessary to fill in the nodes or tags arrays in FILE_BUFFER. */
60#define INFO_NO_TAGS  0
61#define INFO_GET_TAGS 1
62
63/* Global variables.  */
64
65/* When non-zero, this is a string describing the recent file error. */
66char *info_recent_file_error = NULL;
67
68/* The list of already loaded nodes. */
69FILE_BUFFER **info_loaded_files = NULL;
70
71/* The number of slots currently allocated to LOADED_FILES. */
72int info_loaded_files_slots = 0;
73
74/* Public functions for node manipulation.  */
75
76/* Used to build `dir' menu from `localdir' files found in INFOPATH. */
77extern void maybe_build_dir_node (char *dirname);
78
79/* Return a pointer to a NODE structure for the Info node (FILENAME)NODENAME.
80   If FILENAME is NULL, `dir' is used.
81   IF NODENAME is NULL, `Top' is used.
82   If the node cannot be found, return NULL. */
83NODE *
84info_get_node (char *filename, char *nodename)
85{
86  NODE *node;
87  FILE_BUFFER *file_buffer = NULL;
88
89  info_recent_file_error = NULL;
90  info_parse_node (nodename, DONT_SKIP_NEWLINES);
91  nodename = NULL;
92
93  if (info_parsed_filename)
94    filename = info_parsed_filename;
95
96  if (info_parsed_nodename)
97    nodename = info_parsed_nodename;
98
99  /* If FILENAME is not specified, it defaults to "dir". */
100  if (!filename)
101    filename = "dir";
102
103  /* If the file to be looked up is "dir", build the contents from all of
104     the "dir"s and "localdir"s found in INFOPATH. */
105  if (is_dir_name (filename))
106    maybe_build_dir_node (filename);
107
108  /* Find the correct info file, or give up.  */
109  file_buffer = info_find_file (filename);
110  if (!file_buffer)
111    {
112      if (filesys_error_number)
113        info_recent_file_error =
114          filesys_error_string (filename, filesys_error_number);
115      return NULL;
116    }
117
118  /* Look for the node.  */
119  node = info_get_node_of_file_buffer (nodename, file_buffer);
120
121  /* If the node not found was "Top", try again with different case.  */
122  if (!node && (nodename == NULL || strcasecmp (nodename, "Top") == 0))
123    {
124      node = info_get_node_of_file_buffer ("Top", file_buffer);
125      if (!node)
126        node = info_get_node_of_file_buffer ("top", file_buffer);
127      if (!node)
128        node = info_get_node_of_file_buffer ("TOP", file_buffer);
129    }
130
131  return node;
132}
133
134/* Return a pointer to a NODE structure for the Info node NODENAME in
135   FILE_BUFFER.  NODENAME can be passed as NULL, in which case the
136   nodename of "Top" is used.  If the node cannot be found, return a
137   NULL pointer. */
138NODE *
139info_get_node_of_file_buffer (char *nodename, FILE_BUFFER *file_buffer)
140{
141  NODE *node = NULL;
142
143  /* If we are unable to find the file, we have to give up.  There isn't
144     anything else we can do. */
145  if (!file_buffer)
146    return NULL;
147
148  /* If the file buffer was gc'ed, reload the contents now. */
149  if (!file_buffer->contents)
150    info_reload_file_buffer_contents (file_buffer);
151
152  /* If NODENAME is not specified, it defaults to "Top". */
153  if (!nodename)
154    nodename = "Top";
155
156  /* If the name of the node that we wish to find is exactly "*", then the
157     node body is the contents of the entire file.  Create and return such
158     a node. */
159  if (strcmp (nodename, "*") == 0)
160    {
161      node = (NODE *)xmalloc (sizeof (NODE));
162      node->filename = file_buffer->fullpath;
163      node->parent   = NULL;
164      node->nodename = xstrdup ("*");
165      node->contents = file_buffer->contents;
166      node->nodelen = file_buffer->filesize;
167      node->flags = 0;
168      node->display_pos = 0;
169    }
170#if defined (HANDLE_MAN_PAGES)
171  /* If the file buffer is the magic one associated with manpages, call
172     the manpage node finding function instead. */
173  else if (file_buffer->flags & N_IsManPage)
174    {
175        node = get_manpage_node (file_buffer, nodename);
176    }
177#endif /* HANDLE_MAN_PAGES */
178  /* If this is the "main" info file, it might contain a tags table.  Search
179     the tags table for an entry which matches the node that we want.  If
180     there is a tags table, get the file which contains this node, but don't
181     bother building a node list for it. */
182  else if (file_buffer->tags)
183    {
184      node = info_node_of_file_buffer_tags (file_buffer, nodename);
185    }
186
187  /* Return the results of our node search. */
188  return node;
189}
190
191/* Locate the file named by FILENAME, and return the information structure
192   describing this file.  The file may appear in our list of loaded files
193   already, or it may not.  If it does not already appear, find the file,
194   and add it to the list of loaded files.  If the file cannot be found,
195   return a NULL FILE_BUFFER *. */
196FILE_BUFFER *
197info_find_file (char *filename)
198{
199  return info_find_file_internal (filename, INFO_GET_TAGS);
200}
201
202/* Load the info file FILENAME, remembering information about it in a
203   file buffer. */
204FILE_BUFFER *
205info_load_file (char *filename)
206{
207  return info_load_file_internal (filename, INFO_GET_TAGS);
208}
209
210
211/* Private functions implementation.  */
212
213/* The workhorse for info_find_file ().  Non-zero 2nd argument says to
214   try to build a tags table (or otherwise glean the nodes) for this
215   file once found.  By default, we build the tags table, but when this
216   function is called by info_get_node () when we already have a valid
217   tags table describing the nodes, it is unnecessary. */
218static FILE_BUFFER *
219info_find_file_internal (char *filename, int get_tags)
220{
221  int i;
222  FILE_BUFFER *file_buffer;
223
224  /* First try to find the file in our list of already loaded files. */
225  if (info_loaded_files)
226    {
227      for (i = 0; (file_buffer = info_loaded_files[i]); i++)
228        if ((FILENAME_CMP (filename, file_buffer->filename) == 0)
229            || (FILENAME_CMP (filename, file_buffer->fullpath) == 0)
230            || (!IS_ABSOLUTE (filename)
231                && FILENAME_CMP (filename,
232                                filename_non_directory (file_buffer->fullpath))
233                    == 0))
234          {
235            struct stat new_info, *old_info;
236
237            /* This file is loaded.  If the filename that we want is
238               specifically "dir", then simply return the file buffer. */
239            if (is_dir_name (filename_non_directory (filename)))
240              return file_buffer;
241
242#if defined (HANDLE_MAN_PAGES)
243            /* Do the same for the magic MANPAGE file. */
244            if (file_buffer->flags & N_IsManPage)
245              return file_buffer;
246#endif /* HANDLE_MAN_PAGES */
247
248            /* The file appears to be already loaded, and is not "dir".  Check
249               to see if it's changed since the last time it was loaded.  */
250            if (stat (file_buffer->fullpath, &new_info) == -1)
251              {
252                filesys_error_number = errno;
253                return NULL;
254              }
255
256            old_info = &file_buffer->finfo;
257
258            if (new_info.st_size != old_info->st_size
259                || new_info.st_mtime != old_info->st_mtime)
260              {
261                /* The file has changed.  Forget that we ever had loaded it
262                   in the first place. */
263                forget_info_file (filename);
264                break;
265              }
266            else
267              {
268                /* The info file exists, and has not changed since the last
269                   time it was loaded.  If the caller requested a nodes list
270                   for this file, and there isn't one here, build the nodes
271                   for this file_buffer.  In any case, return the file_buffer
272                   object. */
273                if (!file_buffer->contents)
274                  {
275                    /* The file's contents have been gc'ed.  Reload it.  */
276                    info_reload_file_buffer_contents (file_buffer);
277                    if (!file_buffer->contents)
278                      return NULL;
279                  }
280
281                if (get_tags && !file_buffer->tags)
282                  build_tags_and_nodes (file_buffer);
283
284                return file_buffer;
285              }
286          }
287    }
288
289  /* The file wasn't loaded.  Try to load it now. */
290#if defined (HANDLE_MAN_PAGES)
291  /* If the name of the file that we want is our special file buffer for
292     Unix manual pages, then create the file buffer, and return it now. */
293  if (strcasecmp (filename, MANPAGE_FILE_BUFFER_NAME) == 0)
294    file_buffer = create_manpage_file_buffer ();
295  else
296#endif /* HANDLE_MAN_PAGES */
297    file_buffer = info_load_file_internal (filename, get_tags);
298
299  /* If the file was loaded, remember the name under which it was found. */
300  if (file_buffer)
301    remember_info_file (file_buffer);
302
303  return file_buffer;
304}
305
306/* The workhorse function for info_load_file ().  Non-zero second argument
307   says to build a list of tags (or nodes) for this file.  This is the
308   default behaviour when info_load_file () is called, but it is not
309   necessary when loading a subfile for which we already have tags. */
310static FILE_BUFFER *
311info_load_file_internal (char *filename, int get_tags)
312{
313  char *fullpath, *contents;
314  long filesize;
315  struct stat finfo;
316  int retcode, compressed;
317  FILE_BUFFER *file_buffer = NULL;
318
319  /* Get the full pathname of this file, as known by the info system.
320     That is to say, search along INFOPATH and expand tildes, etc. */
321  fullpath = info_find_fullpath (filename);
322
323  /* Did we actually find the file? */
324  retcode = stat (fullpath, &finfo);
325
326  /* If the file referenced by the name returned from info_find_fullpath ()
327     doesn't exist, then try again with the last part of the filename
328     appearing in lowercase. */
329  /* This is probably not needed at all on those systems which define
330     FILENAME_CMP to be strcasecmp.  But let's do it anyway, lest some
331     network redirector supports case sensitivity.  */
332  if (retcode < 0)
333    {
334      char *lowered_name;
335      char *tmp_basename;
336
337      lowered_name = xstrdup (filename);
338      tmp_basename = filename_non_directory (lowered_name);
339
340      while (*tmp_basename)
341        {
342          if (isupper (*tmp_basename))
343            *tmp_basename = tolower (*tmp_basename);
344
345          tmp_basename++;
346        }
347
348      fullpath = info_find_fullpath (lowered_name);
349
350      retcode = stat (fullpath, &finfo);
351      free (lowered_name);
352    }
353
354  /* If the file wasn't found, give up, returning a NULL pointer. */
355  if (retcode < 0)
356    {
357      filesys_error_number = errno;
358      return NULL;
359    }
360
361  /* Otherwise, try to load the file. */
362  contents = filesys_read_info_file (fullpath, &filesize, &finfo, &compressed);
363
364  if (!contents)
365    return NULL;
366
367  /* The file was found, and can be read.  Allocate FILE_BUFFER and fill
368     in the various members. */
369  file_buffer = make_file_buffer ();
370  file_buffer->filename = xstrdup (filename);
371  file_buffer->fullpath = xstrdup (fullpath);
372  file_buffer->finfo = finfo;
373  file_buffer->filesize = filesize;
374  file_buffer->contents = contents;
375  if (compressed)
376    file_buffer->flags |= N_IsCompressed;
377
378  /* If requested, build the tags and nodes for this file buffer. */
379  if (get_tags)
380    build_tags_and_nodes (file_buffer);
381
382  return file_buffer;
383}
384
385/* Grovel FILE_BUFFER->contents finding tags and nodes, and filling in the
386   various slots.  This can also be used to rebuild a tag or node table. */
387void
388build_tags_and_nodes (FILE_BUFFER *file_buffer)
389{
390  SEARCH_BINDING binding;
391  long position;
392
393  free_file_buffer_tags (file_buffer);
394  file_buffer->flags &= ~N_HasTagsTable;
395
396  /* See if there is a tags table in this info file. */
397  binding.buffer = file_buffer->contents;
398  binding.start = file_buffer->filesize;
399  binding.end = binding.start - 1000;
400  if (binding.end < 0)
401    binding.end = 0;
402  binding.flags = S_FoldCase;
403
404  position = search_backward (TAGS_TABLE_END_LABEL, &binding);
405
406  /* If there is a tag table, find the start of it, and grovel over it
407     extracting tag information. */
408  if (position != -1)
409    while (1)
410      {
411        long tags_table_begin, tags_table_end;
412
413        binding.end = position;
414        binding.start = binding.end - 5 - strlen (TAGS_TABLE_END_LABEL);
415        if (binding.start < 0)
416          binding.start = 0;
417
418        position = find_node_separator (&binding);
419
420        /* For this test, (and all others here) failure indicates a bogus
421           tags table.  Grovel the file. */
422        if (position == -1)
423          break;
424
425        /* Remember the end of the tags table. */
426        binding.start = position;
427        tags_table_end = binding.start;
428        binding.end = 0;
429
430        /* Locate the start of the tags table. */
431        position = search_backward (TAGS_TABLE_BEG_LABEL, &binding);
432
433        if (position == -1)
434          break;
435
436        binding.end = position;
437        binding.start = binding.end - 5 - strlen (TAGS_TABLE_BEG_LABEL);
438        position = find_node_separator (&binding);
439
440        if (position == -1)
441          break;
442
443        /* The file contains a valid tags table.  Fill the FILE_BUFFER's
444           tags member. */
445        file_buffer->flags |= N_HasTagsTable;
446        tags_table_begin = position;
447
448        /* If this isn't an indirect tags table, just remember the nodes
449           described locally in this tags table.  Note that binding.end
450           is pointing to just after the beginning label. */
451        binding.start = binding.end;
452        binding.end = file_buffer->filesize;
453
454        if (!looking_at (TAGS_TABLE_IS_INDIRECT_LABEL, &binding))
455          {
456            binding.start = tags_table_begin;
457            binding.end = tags_table_end;
458            get_nodes_of_tags_table (file_buffer, &binding);
459            return;
460          }
461        else
462          {
463            /* This is an indirect tags table.  Build TAGS member. */
464            SEARCH_BINDING indirect;
465
466            indirect.start = tags_table_begin;
467            indirect.end = 0;
468            indirect.buffer = binding.buffer;
469            indirect.flags = S_FoldCase;
470
471            position = search_backward (INDIRECT_TAGS_TABLE_LABEL, &indirect);
472
473            if (position == -1)
474              {
475                /* This file is malformed.  Give up. */
476                return;
477              }
478
479            indirect.start = position;
480            indirect.end = tags_table_begin;
481            binding.start = tags_table_begin;
482            binding.end = tags_table_end;
483            get_tags_of_indirect_tags_table (file_buffer, &indirect, &binding);
484            return;
485          }
486      }
487
488  /* This file doesn't contain any kind of tags table.  Grovel the
489     file and build node entries for it. */
490  get_nodes_of_info_file (file_buffer);
491}
492
493/* Search through FILE_BUFFER->contents building an array of TAG *,
494   one entry per each node present in the file.  Store the tags in
495   FILE_BUFFER->tags, and the number of allocated slots in
496   FILE_BUFFER->tags_slots. */
497static void
498get_nodes_of_info_file (FILE_BUFFER *file_buffer)
499{
500  long nodestart;
501  int tags_index = 0;
502  SEARCH_BINDING binding;
503
504  binding.buffer = file_buffer->contents;
505  binding.start = 0;
506  binding.end = file_buffer->filesize;
507  binding.flags = S_FoldCase;
508
509  while ((nodestart = find_node_separator (&binding)) != -1)
510    {
511      int start, end;
512      char *nodeline;
513      TAG *entry;
514      int anchor = 0;
515
516      /* Skip past the characters just found. */
517      binding.start = nodestart;
518      binding.start += skip_node_separator (binding.buffer + binding.start);
519
520      /* Move to the start of the line defining the node. */
521      nodeline = binding.buffer + binding.start;
522
523      /* Find "Node:" */
524      start = string_in_line (INFO_NODE_LABEL, nodeline);
525      /* No Node:.  Maybe it's a Ref:.  */
526      if (start == -1)
527        {
528          start = string_in_line (INFO_REF_LABEL, nodeline);
529          if (start != -1)
530            anchor = 1;
531        }
532
533      /* If not there, this is not the start of a node. */
534      if (start == -1)
535        continue;
536
537      /* Find the start of the nodename. */
538      start += skip_whitespace (nodeline + start);
539
540      /* Find the end of the nodename. */
541      end = start +
542        skip_node_characters (nodeline + start, DONT_SKIP_NEWLINES);
543
544      /* Okay, we have isolated the node name, and we know where the
545         node starts.  Remember this information. */
546      entry = xmalloc (sizeof (TAG));
547      entry->nodename = xmalloc (1 + (end - start));
548      strncpy (entry->nodename, nodeline + start, end - start);
549      entry->nodename[end - start] = 0;
550      entry->nodestart = nodestart;
551      if (anchor)
552        entry->nodelen = 0;
553      else
554        {
555          SEARCH_BINDING node_body;
556
557          node_body.buffer = binding.buffer + binding.start;
558          node_body.start = 0;
559          node_body.end = binding.end - binding.start;
560          node_body.flags = S_FoldCase;
561          entry->nodelen = get_node_length (&node_body);
562        }
563
564      entry->filename = file_buffer->fullpath;
565
566      /* Add this tag to the array of tag structures in this FILE_BUFFER. */
567      add_pointer_to_array (entry, tags_index, file_buffer->tags,
568                            file_buffer->tags_slots, 100, TAG *);
569    }
570}
571
572/* Return the length of the node which starts at BINDING. */
573static long
574get_node_length (SEARCH_BINDING *binding)
575{
576  int i;
577  char *body;
578
579  /* [A node] ends with either a ^_, a ^L, or end of file.  */
580  for (i = binding->start, body = binding->buffer; i < binding->end; i++)
581    {
582      if (body[i] == INFO_FF || body[i] == INFO_COOKIE)
583        break;
584    }
585  return i - binding->start;
586}
587
588/* Build and save the array of nodes in FILE_BUFFER by searching through the
589   contents of BUFFER_BINDING for a tags table, and groveling the contents. */
590static void
591get_nodes_of_tags_table (FILE_BUFFER *file_buffer,
592    SEARCH_BINDING *buffer_binding)
593{
594  int name_offset;
595  SEARCH_BINDING *tmp_search;
596  long position;
597  int tags_index = 0;
598
599  tmp_search = copy_binding (buffer_binding);
600
601  /* Find the start of the tags table. */
602  position = find_tags_table (tmp_search);
603
604  /* If none, we're all done. */
605  if (position == -1)
606    return;
607
608  /* Move to one character before the start of the actual table. */
609  tmp_search->start = position;
610  tmp_search->start += skip_node_separator
611    (tmp_search->buffer + tmp_search->start);
612  tmp_search->start += strlen (TAGS_TABLE_BEG_LABEL);
613  tmp_search->start--;
614
615  /* The tag table consists of lines containing node names and positions.
616     Do each line until we find one that doesn't contain a node name. */
617  while ((position = search_forward ("\n", tmp_search)) != -1)
618    {
619      TAG *entry;
620      char *nodedef;
621      unsigned p;
622      int anchor = 0;
623
624      /* Prepare to skip this line. */
625      tmp_search->start = position;
626      tmp_search->start++;
627
628      /* Skip past informative "(Indirect)" tags table line. */
629      if (!tags_index && looking_at (TAGS_TABLE_IS_INDIRECT_LABEL, tmp_search))
630        continue;
631
632      /* Find the label preceding the node name. */
633      name_offset =
634        string_in_line (INFO_NODE_LABEL, tmp_search->buffer + tmp_search->start);
635
636      /* If no node label, maybe it's an anchor.  */
637      if (name_offset == -1)
638        {
639          name_offset = string_in_line (INFO_REF_LABEL,
640              tmp_search->buffer + tmp_search->start);
641          if (name_offset != -1)
642            anchor = 1;
643        }
644
645      /* If not there, not a defining line, so we must be out of the
646         tags table.  */
647      if (name_offset == -1)
648        break;
649
650      entry = xmalloc (sizeof (TAG));
651
652      /* Find the beginning of the node definition. */
653      tmp_search->start += name_offset;
654      nodedef = tmp_search->buffer + tmp_search->start;
655      nodedef += skip_whitespace (nodedef);
656
657      /* Move past the node's name in this tag to the TAGSEP character. */
658      for (p = 0; nodedef[p] && nodedef[p] != INFO_TAGSEP; p++)
659        ;
660      if (nodedef[p] != INFO_TAGSEP)
661        continue;
662
663      entry->nodename = xmalloc (p + 1);
664      strncpy (entry->nodename, nodedef, p);
665      entry->nodename[p] = 0;
666      p++;
667      entry->nodestart = atol (nodedef + p);
668
669      /* If a node, we don't know the length yet, but if it's an
670         anchor, the length is 0. */
671      entry->nodelen = anchor ? 0 : -1;
672
673      /* The filename of this node is currently known as the same as the
674         name of this file. */
675      entry->filename = file_buffer->fullpath;
676
677      /* Add this node structure to the array of node structures in this
678         FILE_BUFFER. */
679      add_pointer_to_array (entry, tags_index, file_buffer->tags,
680                            file_buffer->tags_slots, 100, TAG *);
681    }
682  free (tmp_search);
683}
684
685/* A structure used only in `get_tags_of_indirect_tags_table' to hold onto
686   an intermediate value. */
687typedef struct {
688  char *filename;
689  long first_byte;
690} SUBFILE;
691
692/* Remember in FILE_BUFFER the nodenames, subfilenames, and offsets within the
693   subfiles of every node which appears in TAGS_BINDING.  The 2nd argument is
694   a binding surrounding the indirect files list. */
695static void
696get_tags_of_indirect_tags_table (FILE_BUFFER *file_buffer,
697    SEARCH_BINDING *indirect_binding, SEARCH_BINDING *tags_binding)
698{
699  int i;
700  SUBFILE **subfiles = NULL;
701  int subfiles_index = 0, subfiles_slots = 0;
702  TAG *entry;
703
704  /* First get the list of tags from the tags table.  Then lookup the
705     associated file in the indirect list for each tag, and update it. */
706  get_nodes_of_tags_table (file_buffer, tags_binding);
707
708  /* We have the list of tags in file_buffer->tags.  Get the list of
709     subfiles from the indirect table. */
710  {
711    char *start, *end, *line;
712    SUBFILE *subfile;
713
714    start = indirect_binding->buffer + indirect_binding->start;
715    end = indirect_binding->buffer + indirect_binding->end;
716    line = start;
717
718    while (line < end)
719      {
720        int colon;
721
722        colon = string_in_line (":", line);
723
724        if (colon == -1)
725          break;
726
727        subfile = (SUBFILE *)xmalloc (sizeof (SUBFILE));
728        subfile->filename = (char *)xmalloc (colon);
729        strncpy (subfile->filename, line, colon - 1);
730        subfile->filename[colon - 1] = 0;
731        subfile->first_byte = (long) atol (line + colon);
732
733        add_pointer_to_array
734          (subfile, subfiles_index, subfiles, subfiles_slots, 10, SUBFILE *);
735
736        while (*line++ != '\n');
737      }
738  }
739
740  /* If we have successfully built the indirect files table, then
741     merge the information in the two tables. */
742  if (!subfiles)
743    {
744      free_file_buffer_tags (file_buffer);
745      return;
746    }
747  else
748    {
749      int tags_index;
750      long header_length;
751      SEARCH_BINDING binding;
752
753      /* Find the length of the header of the file containing the indirect
754         tags table.  This header appears at the start of every file.  We
755         want the absolute position of each node within each subfile, so
756         we subtract the start of the containing subfile from the logical
757         position of the node, and then add the length of the header in. */
758      binding.buffer = file_buffer->contents;
759      binding.start = 0;
760      binding.end = file_buffer->filesize;
761      binding.flags = S_FoldCase;
762
763      header_length = find_node_separator (&binding);
764      if (header_length == -1)
765        header_length = 0;
766
767      /* Build the file buffer's list of subfiles. */
768      {
769        char *containing_dir = xstrdup (file_buffer->fullpath);
770        char *temp = filename_non_directory (containing_dir);
771        int len_containing_dir;
772
773        if (temp > containing_dir)
774          {
775            if (HAVE_DRIVE (file_buffer->fullpath) &&
776                temp == containing_dir + 2)
777              {
778                /* Avoid converting "d:foo" into "d:/foo" below.  */
779                *temp = '.';
780                temp += 2;
781              }
782            temp[-1] = 0;
783          }
784
785        len_containing_dir = strlen (containing_dir);
786
787        for (i = 0; subfiles[i]; i++);
788
789        file_buffer->subfiles = (char **) xmalloc ((1 + i) * sizeof (char *));
790
791        for (i = 0; subfiles[i]; i++)
792          {
793            char *fullpath;
794
795            fullpath = (char *) xmalloc
796              (2 + strlen (subfiles[i]->filename) + len_containing_dir);
797
798            sprintf (fullpath, "%s/%s",
799                     containing_dir, subfiles[i]->filename);
800
801            file_buffer->subfiles[i] = fullpath;
802          }
803        file_buffer->subfiles[i] = NULL;
804        free (containing_dir);
805      }
806
807      /* For each node in the file's tags table, remember the starting
808         position. */
809      for (tags_index = 0; (entry = file_buffer->tags[tags_index]);
810           tags_index++)
811        {
812          for (i = 0;
813               subfiles[i] && entry->nodestart >= subfiles[i]->first_byte;
814               i++);
815
816          /* If the Info file containing the indirect tags table is
817             malformed, then give up. */
818          if (!i)
819            {
820              /* The Info file containing the indirect tags table is
821                 malformed.  Give up. */
822              for (i = 0; subfiles[i]; i++)
823                {
824                  free (subfiles[i]->filename);
825                  free (subfiles[i]);
826                  free (file_buffer->subfiles[i]);
827                }
828              file_buffer->subfiles = NULL;
829              free_file_buffer_tags (file_buffer);
830              return;
831            }
832
833          /* SUBFILES[i] is the index of the first subfile whose logical
834             first byte is greater than the logical offset of this node's
835             starting position.  This means that the subfile directly
836             preceding this one is the one containing the node. */
837
838          entry->filename = file_buffer->subfiles[i - 1];
839          entry->nodestart -= subfiles[i - 1]->first_byte;
840          entry->nodestart += header_length;
841        }
842
843      /* We have successfully built the tags table.  Remember that it
844         was indirect. */
845      file_buffer->flags |= N_TagsIndirect;
846    }
847
848  /* Free the structures assigned to SUBFILES.  Free the names as well
849     as the structures themselves, then finally, the array. */
850  for (i = 0; subfiles[i]; i++)
851    {
852      free (subfiles[i]->filename);
853      free (subfiles[i]);
854    }
855  free (subfiles);
856}
857
858
859/* Return the node that contains TAG in FILE_BUFFER, else
860   (pathologically) NULL.  Called from info_node_of_file_buffer_tags.  */
861static NODE *
862find_node_of_anchor (FILE_BUFFER *file_buffer, TAG *tag)
863{
864  int anchor_pos, node_pos;
865  TAG *node_tag;
866  NODE *node;
867
868  /* Look through the tag list for the anchor.  */
869  for (anchor_pos = 0; file_buffer->tags[anchor_pos]; anchor_pos++)
870    {
871      TAG *t = file_buffer->tags[anchor_pos];
872      if (t->nodestart == tag->nodestart)
873        break;
874    }
875
876  /* Should not happen, because we should always find the anchor.  */
877  if (!file_buffer->tags[anchor_pos])
878    return NULL;
879
880  /* We've found the anchor.  Look backwards in the tag table for the
881     preceding node (we're assuming the tags are given in order),
882     skipping over any preceding anchors.  */
883  for (node_pos = anchor_pos - 1;
884       node_pos >= 0 && file_buffer->tags[node_pos]->nodelen == 0;
885       node_pos--)
886    ;
887
888  /* An info file with an anchor before any nodes is pathological, but
889     it's possible, so don't crash.  */
890  if (node_pos < 0)
891    return NULL;
892
893  /* We have the tag for the node that contained the anchor tag.  */
894  node_tag = file_buffer->tags[node_pos];
895
896  /* Look up the node name in the tag table to get the actual node.
897     This is a recursive call, but it can't recurse again, because we
898     call it with a real node.  */
899  node = info_node_of_file_buffer_tags (file_buffer, node_tag->nodename);
900
901  /* Start displaying the node at the anchor position.  */
902  if (node)
903    { /* The nodestart for real nodes is three characters before the `F'
904         in the `File:' line (a newline, the CTRL-_, and another
905         newline).  The nodestart for anchors is the actual position.
906         But we offset by only 2, rather than 3, because if an anchor is
907         at the beginning of a paragraph, it's nicer for it to end up on
908         the beginning of the first line of the paragraph rather than
909         the blank line before it.  (makeinfo has no way of knowing that
910         a paragraph is going to start, so we can't fix it there.)  */
911      node->display_pos = file_buffer->tags[anchor_pos]->nodestart
912                          - (node_tag->nodestart + 2);
913
914      /* Otherwise an anchor at the end of a node ends up displaying at
915         the end of the last line of the node (way over on the right of
916         the screen), which looks wrong.  */
917      if (node->display_pos >= (unsigned long) node->nodelen)
918        node->display_pos = node->nodelen - 1;
919
920      /* Don't search in the node for the xref text, it's not there.  */
921      node->flags |= N_FromAnchor;
922    }
923
924  return node;
925}
926
927
928/* Return the node from FILE_BUFFER which matches NODENAME by searching
929   the tags table in FILE_BUFFER, or NULL.  */
930static NODE *
931info_node_of_file_buffer_tags (FILE_BUFFER *file_buffer, char *nodename)
932{
933  TAG *tag;
934  int i;
935
936  /* If no tags at all (possibly a misformatted info file), quit.  */
937  if (!file_buffer->tags) {
938    return NULL;
939  }
940
941  for (i = 0; (tag = file_buffer->tags[i]); i++)
942    if (strcmp (nodename, tag->nodename) == 0)
943      {
944        FILE_BUFFER *subfile = info_find_file_internal (tag->filename,
945                                                        INFO_NO_TAGS);
946        if (!subfile)
947          return NULL;
948
949        if (!subfile->contents)
950          {
951            info_reload_file_buffer_contents (subfile);
952            if (!subfile->contents)
953              return NULL;
954          }
955
956        /* If we were able to find this file and load it, then return
957           the node within it. */
958        {
959          NODE *node = xmalloc (sizeof (NODE));
960          node->filename    = subfile->fullpath;
961          node->parent      = NULL;
962          node->nodename    = tag->nodename;
963          node->contents    = subfile->contents + tag->nodestart;
964          node->display_pos = 0;
965          node->flags       = 0;
966
967          if (file_buffer->flags & N_HasTagsTable)
968            {
969              node->flags |= N_HasTagsTable;
970
971              if (file_buffer->flags & N_TagsIndirect)
972                {
973                  node->flags |= N_TagsIndirect;
974                  node->parent = file_buffer->fullpath;
975                }
976            }
977
978          if (subfile->flags & N_IsCompressed)
979            node->flags |= N_IsCompressed;
980
981          /* If TAG->nodelen hasn't been calculated yet, then we aren't
982             in a position to trust the entry pointer.  Adjust things so
983             that ENTRY->nodestart gets the exact address of the start of
984             the node separator which starts this node, and NODE->contents
985             gets the address of the line defining this node.  If we cannot
986             do that, the node isn't really here. */
987          if (tag->nodelen == -1)
988            {
989              int min, max;
990              char *node_sep;
991              SEARCH_BINDING node_body;
992              char *buff_end;
993
994              min = max = DEFAULT_INFO_FUDGE;
995
996              if (tag->nodestart < DEFAULT_INFO_FUDGE)
997                min = tag->nodestart;
998
999              if (DEFAULT_INFO_FUDGE >
1000                  (subfile->filesize - tag->nodestart))
1001                max = subfile->filesize - tag->nodestart;
1002
1003              /* NODE_SEP gets the address of the separator which defines
1004                 this node, or NULL if the node wasn't found.
1005                 NODE->contents is side-effected to point to right after
1006                 the separator. */
1007              node_sep = adjust_nodestart (node, min, max);
1008              if (node_sep == NULL)
1009                {
1010                  free (node);
1011                  return NULL;
1012                }
1013              /* Readjust tag->nodestart. */
1014              tag->nodestart = node_sep - subfile->contents;
1015
1016              /* Calculate the length of the current node. */
1017              buff_end = subfile->contents + subfile->filesize;
1018
1019              node_body.buffer = node->contents;
1020              node_body.start = 0;
1021              node_body.end = buff_end - node_body.buffer;
1022              node_body.flags = 0;
1023              tag->nodelen = get_node_length (&node_body);
1024              node->nodelen = tag->nodelen;
1025            }
1026
1027          else if (tag->nodelen == 0) /* anchor, return containing node */
1028            {
1029              free (node);
1030              node = find_node_of_anchor (file_buffer, tag);
1031            }
1032
1033          else
1034            {
1035              /* Since we know the length of this node, we have already
1036                 adjusted tag->nodestart to point to the exact start of
1037                 it.  Simply skip the node separator. */
1038              node->contents += skip_node_separator (node->contents);
1039              node->nodelen = tag->nodelen;
1040            }
1041
1042          return node;
1043        }
1044      }
1045
1046  /* There was a tag table for this file, and the node wasn't found.
1047     Return NULL, since this file doesn't contain the desired node. */
1048  return NULL;
1049}
1050
1051/* Managing file_buffers, nodes, and tags.  */
1052
1053/* Create a new, empty file buffer. */
1054FILE_BUFFER *
1055make_file_buffer (void)
1056{
1057  FILE_BUFFER *file_buffer = xmalloc (sizeof (FILE_BUFFER));
1058
1059  file_buffer->filename = file_buffer->fullpath = NULL;
1060  file_buffer->contents = NULL;
1061  file_buffer->tags = NULL;
1062  file_buffer->subfiles = NULL;
1063  file_buffer->tags_slots = 0;
1064  file_buffer->flags = 0;
1065
1066  return file_buffer;
1067}
1068
1069/* Add FILE_BUFFER to our list of already loaded info files. */
1070static void
1071remember_info_file (FILE_BUFFER *file_buffer)
1072{
1073  int i;
1074
1075  for (i = 0; info_loaded_files && info_loaded_files[i]; i++)
1076    ;
1077
1078  add_pointer_to_array (file_buffer, i, info_loaded_files,
1079                        info_loaded_files_slots, 10, FILE_BUFFER *);
1080}
1081
1082/* Forget the contents, tags table, nodes list, and names of FILENAME. */
1083static void
1084forget_info_file (char *filename)
1085{
1086  int i;
1087  FILE_BUFFER *file_buffer;
1088
1089  if (!info_loaded_files)
1090    return;
1091
1092  for (i = 0; (file_buffer = info_loaded_files[i]); i++)
1093    if (FILENAME_CMP (filename, file_buffer->filename) == 0
1094        || FILENAME_CMP (filename, file_buffer->fullpath) == 0)
1095      {
1096        free (file_buffer->filename);
1097        free (file_buffer->fullpath);
1098
1099        if (file_buffer->contents)
1100          free (file_buffer->contents);
1101
1102        /* free_file_buffer_tags () also kills the subfiles list, since
1103           the subfiles list is only of use in conjunction with tags. */
1104        free_file_buffer_tags (file_buffer);
1105
1106        /* Move rest of list down.  */
1107        while (info_loaded_files[i + 1])
1108          {
1109            info_loaded_files[i] = info_loaded_files[i + 1];
1110            i++;
1111          }
1112        info_loaded_files[i] = 0;
1113
1114        break;
1115      }
1116}
1117
1118/* Free the tags (if any) associated with FILE_BUFFER. */
1119static void
1120free_file_buffer_tags (FILE_BUFFER *file_buffer)
1121{
1122  int i;
1123
1124  if (file_buffer->tags)
1125    {
1126      TAG *tag;
1127
1128      for (i = 0; (tag = file_buffer->tags[i]); i++)
1129        free_info_tag (tag);
1130
1131      free (file_buffer->tags);
1132      file_buffer->tags = NULL;
1133      file_buffer->tags_slots = 0;
1134    }
1135
1136  if (file_buffer->subfiles)
1137    {
1138      for (i = 0; file_buffer->subfiles[i]; i++)
1139        free (file_buffer->subfiles[i]);
1140
1141      free (file_buffer->subfiles);
1142      file_buffer->subfiles = NULL;
1143    }
1144}
1145
1146/* Free the data associated with TAG, as well as TAG itself. */
1147static void
1148free_info_tag (TAG *tag)
1149{
1150  free (tag->nodename);
1151
1152  /* We don't free tag->filename, because that filename is part of the
1153     subfiles list for the containing FILE_BUFFER.  free_info_tags ()
1154     will free the subfiles when it is appropriate. */
1155
1156  free (tag);
1157}
1158
1159/* Load the contents of FILE_BUFFER->contents.  This function is called
1160   when a file buffer was loaded, and then in order to conserve memory, the
1161   file buffer's contents were freed and the pointer was zero'ed.  Note that
1162   the file was already loaded at least once successfully, so the tags and/or
1163   nodes members are still correctly filled. */
1164static void
1165info_reload_file_buffer_contents (FILE_BUFFER *fb)
1166{
1167  int is_compressed;
1168
1169#if defined (HANDLE_MAN_PAGES)
1170  /* If this is the magic manpage node, don't try to reload, just give up. */
1171  if (fb->flags & N_IsManPage)
1172    return;
1173#endif
1174
1175  fb->flags &= ~N_IsCompressed;
1176
1177  /* Let the filesystem do all the work for us. */
1178  fb->contents =
1179    filesys_read_info_file (fb->fullpath, &(fb->filesize), &(fb->finfo),
1180                            &is_compressed);
1181  if (is_compressed)
1182    fb->flags |= N_IsCompressed;
1183}
1184
1185/* Return the actual starting memory location of NODE, side-effecting
1186   NODE->contents.  MIN and MAX are bounds for a search if one is necessary.
1187   Because of the way that tags are implemented, the physical nodestart may
1188   not actually be where the tag says it is.  If that is the case, but the
1189   node was found anyway, set N_UpdateTags in NODE->flags.  If the node is
1190   found, return non-zero.  NODE->contents is returned positioned right after
1191   the node separator that precedes this node, while the return value is
1192   position directly on the separator that precedes this node.  If the node
1193   could not be found, return a NULL pointer. */
1194static char *
1195adjust_nodestart (NODE *node, int min, int max)
1196{
1197  long position;
1198  SEARCH_BINDING node_body;
1199
1200  /* Define the node body. */
1201  node_body.buffer = node->contents;
1202  node_body.start = 0;
1203  node_body.end = max;
1204  node_body.flags = 0;
1205
1206  /* Try the optimal case first.  Who knows?  This file may actually be
1207     formatted (mostly) correctly. */
1208  if (node_body.buffer[0] != INFO_COOKIE && min > 2)
1209    node_body.buffer -= 3;
1210
1211  position = find_node_separator (&node_body);
1212
1213  /* If we found a node start, then check it out. */
1214  if (position != -1)
1215    {
1216      int sep_len;
1217
1218      sep_len = skip_node_separator (node->contents);
1219
1220      /* If we managed to skip a node separator, then check for this node
1221         being the right one. */
1222      if (sep_len != 0)
1223        {
1224          char *nodedef, *nodestart;
1225          int offset;
1226
1227          nodestart = node_body.buffer + position + sep_len;
1228          nodedef = nodestart;
1229          offset = string_in_line (INFO_NODE_LABEL, nodedef);
1230
1231          if (offset != -1)
1232            {
1233              nodedef += offset;
1234              nodedef += skip_whitespace (nodedef);
1235              offset = skip_node_characters (nodedef, DONT_SKIP_NEWLINES);
1236              if (((unsigned int) offset == strlen (node->nodename)) &&
1237                  (strncmp (node->nodename, nodedef, offset) == 0))
1238                {
1239                  node->contents = nodestart;
1240                  return node_body.buffer + position;
1241                }
1242            }
1243        }
1244    }
1245
1246  /* Oh well, I guess we have to try to find it in a larger area. */
1247  node_body.buffer = node->contents - min;
1248  node_body.start = 0;
1249  node_body.end = min + max;
1250  node_body.flags = 0;
1251
1252  position = find_node_in_binding (node->nodename, &node_body);
1253
1254  /* If the node couldn't be found, we lose big. */
1255  if (position == -1)
1256    return NULL;
1257
1258  /* Otherwise, the node was found, but the tags table could need updating
1259     (if we used a tag to get here, that is).  Set the flag in NODE->flags. */
1260  node->contents = node_body.buffer + position;
1261  node->contents += skip_node_separator (node->contents);
1262  if (node->flags & N_HasTagsTable)
1263    node->flags |= N_UpdateTags;
1264  return node_body.buffer + position;
1265}
1266