156160Sru/* nodes.c -- how to get an Info file and node.
2146515Sru   $Id: nodes.c,v 1.4 2004/04/11 17:56:46 karl Exp $
321495Sjmacd
4146515Sru   Copyright (C) 1993, 1998, 1999, 2000, 2002, 2003, 2004 Free Software
5116525Sru   Foundation, Inc.
621495Sjmacd
721495Sjmacd   This program is free software; you can redistribute it and/or modify
821495Sjmacd   it under the terms of the GNU General Public License as published by
921495Sjmacd   the Free Software Foundation; either version 2, or (at your option)
1021495Sjmacd   any later version.
1121495Sjmacd
1221495Sjmacd   This program is distributed in the hope that it will be useful,
1321495Sjmacd   but WITHOUT ANY WARRANTY; without even the implied warranty of
1421495Sjmacd   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1521495Sjmacd   GNU General Public License for more details.
1621495Sjmacd
1721495Sjmacd   You should have received a copy of the GNU General Public License
1821495Sjmacd   along with this program; if not, write to the Free Software
1921495Sjmacd   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
2021495Sjmacd
21146515Sru   Originally written by Brian Fox (bfox@ai.mit.edu). */
2221495Sjmacd
2342660Smarkm#include "info.h"
2442660Smarkm
2521495Sjmacd#include "nodes.h"
2621495Sjmacd#include "search.h"
2721495Sjmacd#include "filesys.h"
2821495Sjmacd#include "info-utils.h"
2921495Sjmacd
3021495Sjmacd#if defined (HANDLE_MAN_PAGES)
3121495Sjmacd#  include "man.h"
3221495Sjmacd#endif /* HANDLE_MAN_PAGES */
3321495Sjmacd
34146515Srustatic void forget_info_file (char *filename);
35146515Srustatic void remember_info_file (FILE_BUFFER *file_buffer);
36146515Srustatic void free_file_buffer_tags (FILE_BUFFER *file_buffer);
37146515Srustatic void free_info_tag (TAG *tag);
38146515Srustatic void get_nodes_of_tags_table (FILE_BUFFER *file_buffer,
39146515Sru    SEARCH_BINDING *buffer_binding);
40146515Srustatic void get_nodes_of_info_file (FILE_BUFFER *file_buffer);
41146515Srustatic void get_tags_of_indirect_tags_table (FILE_BUFFER *file_buffer,
42146515Sru    SEARCH_BINDING *indirect_binding, SEARCH_BINDING *tags_binding);
43146515Srustatic void info_reload_file_buffer_contents (FILE_BUFFER *fb);
44146515Srustatic char *adjust_nodestart (NODE *node, int min, int max);
45146515Srustatic FILE_BUFFER *info_load_file_internal (char *filename, int get_tags);
46146515Srustatic FILE_BUFFER *info_find_file_internal (char *filename, int get_tags);
47146515Srustatic NODE *info_node_of_file_buffer_tags (FILE_BUFFER *file_buffer,
48146515Sru    char *nodename);
4921495Sjmacd
50146515Srustatic long get_node_length (SEARCH_BINDING *binding);
5121495Sjmacd
5221495Sjmacd/* Magic number that RMS used to decide how much a tags table pointer could
5356160Sru   be off by.  I feel that it should be much smaller, like 4.  */
5421495Sjmacd#define DEFAULT_INFO_FUDGE 1000
5521495Sjmacd
5621495Sjmacd/* Passed to *_internal functions.  INFO_GET_TAGS says to do what is
5721495Sjmacd   neccessary to fill in the nodes or tags arrays in FILE_BUFFER. */
5821495Sjmacd#define INFO_NO_TAGS  0
5921495Sjmacd#define INFO_GET_TAGS 1
6021495Sjmacd
6156160Sru/* Global variables.  */
6221495Sjmacd
6321495Sjmacd/* When non-zero, this is a string describing the recent file error. */
6456160Sruchar *info_recent_file_error = NULL;
6521495Sjmacd
6621495Sjmacd/* The list of already loaded nodes. */
6756160SruFILE_BUFFER **info_loaded_files = NULL;
6821495Sjmacd
6921495Sjmacd/* The number of slots currently allocated to LOADED_FILES. */
7021495Sjmacdint info_loaded_files_slots = 0;
7121495Sjmacd
7256160Sru/* Public functions for node manipulation.  */
7321495Sjmacd
7456160Sru/* Used to build `dir' menu from `localdir' files found in INFOPATH. */
75146515Sruextern void maybe_build_dir_node (char *dirname);
7621495Sjmacd
7721495Sjmacd/* Return a pointer to a NODE structure for the Info node (FILENAME)NODENAME.
7856160Sru   If FILENAME is NULL, `dir' is used.
7956160Sru   IF NODENAME is NULL, `Top' is used.
8056160Sru   If the node cannot be found, return NULL. */
8121495SjmacdNODE *
82146515Sruinfo_get_node (char *filename, char *nodename)
8321495Sjmacd{
8421495Sjmacd  NODE *node;
8556160Sru  FILE_BUFFER *file_buffer = NULL;
8621495Sjmacd
8756160Sru  info_recent_file_error = NULL;
8821495Sjmacd  info_parse_node (nodename, DONT_SKIP_NEWLINES);
8956160Sru  nodename = NULL;
9021495Sjmacd
9121495Sjmacd  if (info_parsed_filename)
9221495Sjmacd    filename = info_parsed_filename;
9321495Sjmacd
9421495Sjmacd  if (info_parsed_nodename)
9521495Sjmacd    nodename = info_parsed_nodename;
9621495Sjmacd
9721495Sjmacd  /* If FILENAME is not specified, it defaults to "dir". */
9821495Sjmacd  if (!filename)
9921495Sjmacd    filename = "dir";
10021495Sjmacd
10121495Sjmacd  /* If the file to be looked up is "dir", build the contents from all of
10221495Sjmacd     the "dir"s and "localdir"s found in INFOPATH. */
10356160Sru  if (is_dir_name (filename))
10421495Sjmacd    maybe_build_dir_node (filename);
10521495Sjmacd
10656160Sru  /* Find the correct info file, or give up.  */
10721495Sjmacd  file_buffer = info_find_file (filename);
10821495Sjmacd  if (!file_buffer)
10921495Sjmacd    {
11021495Sjmacd      if (filesys_error_number)
11142660Smarkm        info_recent_file_error =
11242660Smarkm          filesys_error_string (filename, filesys_error_number);
11356160Sru      return NULL;
11421495Sjmacd    }
11521495Sjmacd
11656160Sru  /* Look for the node.  */
11721495Sjmacd  node = info_get_node_of_file_buffer (nodename, file_buffer);
11856160Sru
11956160Sru  /* If the node not found was "Top", try again with different case.  */
12021495Sjmacd  if (!node && (nodename == NULL || strcasecmp (nodename, "Top") == 0))
12121495Sjmacd    {
12221495Sjmacd      node = info_get_node_of_file_buffer ("Top", file_buffer);
12321495Sjmacd      if (!node)
12442660Smarkm        node = info_get_node_of_file_buffer ("top", file_buffer);
12521495Sjmacd      if (!node)
12642660Smarkm        node = info_get_node_of_file_buffer ("TOP", file_buffer);
12721495Sjmacd    }
12856160Sru
12956160Sru  return node;
13021495Sjmacd}
13121495Sjmacd
13221495Sjmacd/* Return a pointer to a NODE structure for the Info node NODENAME in
13321495Sjmacd   FILE_BUFFER.  NODENAME can be passed as NULL, in which case the
13421495Sjmacd   nodename of "Top" is used.  If the node cannot be found, return a
13521495Sjmacd   NULL pointer. */
13621495SjmacdNODE *
137146515Sruinfo_get_node_of_file_buffer (char *nodename, FILE_BUFFER *file_buffer)
13821495Sjmacd{
13956160Sru  NODE *node = NULL;
14021495Sjmacd
14121495Sjmacd  /* If we are unable to find the file, we have to give up.  There isn't
14221495Sjmacd     anything else we can do. */
14321495Sjmacd  if (!file_buffer)
14456160Sru    return NULL;
14521495Sjmacd
14621495Sjmacd  /* If the file buffer was gc'ed, reload the contents now. */
14721495Sjmacd  if (!file_buffer->contents)
14821495Sjmacd    info_reload_file_buffer_contents (file_buffer);
14921495Sjmacd
15021495Sjmacd  /* If NODENAME is not specified, it defaults to "Top". */
15121495Sjmacd  if (!nodename)
15221495Sjmacd    nodename = "Top";
15321495Sjmacd
15421495Sjmacd  /* If the name of the node that we wish to find is exactly "*", then the
15521495Sjmacd     node body is the contents of the entire file.  Create and return such
15621495Sjmacd     a node. */
15721495Sjmacd  if (strcmp (nodename, "*") == 0)
15821495Sjmacd    {
15921495Sjmacd      node = (NODE *)xmalloc (sizeof (NODE));
16021495Sjmacd      node->filename = file_buffer->fullpath;
16156160Sru      node->parent   = NULL;
16242660Smarkm      node->nodename = xstrdup ("*");
16321495Sjmacd      node->contents = file_buffer->contents;
16421495Sjmacd      node->nodelen = file_buffer->filesize;
16521495Sjmacd      node->flags = 0;
16656160Sru      node->display_pos = 0;
16721495Sjmacd    }
16821495Sjmacd#if defined (HANDLE_MAN_PAGES)
16921495Sjmacd  /* If the file buffer is the magic one associated with manpages, call
17021495Sjmacd     the manpage node finding function instead. */
17121495Sjmacd  else if (file_buffer->flags & N_IsManPage)
17221495Sjmacd    {
17342660Smarkm        node = get_manpage_node (file_buffer, nodename);
17421495Sjmacd    }
17521495Sjmacd#endif /* HANDLE_MAN_PAGES */
17621495Sjmacd  /* If this is the "main" info file, it might contain a tags table.  Search
17721495Sjmacd     the tags table for an entry which matches the node that we want.  If
17821495Sjmacd     there is a tags table, get the file which contains this node, but don't
17921495Sjmacd     bother building a node list for it. */
18021495Sjmacd  else if (file_buffer->tags)
18121495Sjmacd    {
18221495Sjmacd      node = info_node_of_file_buffer_tags (file_buffer, nodename);
18321495Sjmacd    }
18421495Sjmacd
18521495Sjmacd  /* Return the results of our node search. */
18656160Sru  return node;
18721495Sjmacd}
18821495Sjmacd
18921495Sjmacd/* Locate the file named by FILENAME, and return the information structure
19021495Sjmacd   describing this file.  The file may appear in our list of loaded files
19121495Sjmacd   already, or it may not.  If it does not already appear, find the file,
19221495Sjmacd   and add it to the list of loaded files.  If the file cannot be found,
19321495Sjmacd   return a NULL FILE_BUFFER *. */
19421495SjmacdFILE_BUFFER *
195146515Sruinfo_find_file (char *filename)
19621495Sjmacd{
19756160Sru  return info_find_file_internal (filename, INFO_GET_TAGS);
19821495Sjmacd}
19921495Sjmacd
20021495Sjmacd/* Load the info file FILENAME, remembering information about it in a
20121495Sjmacd   file buffer. */
20221495SjmacdFILE_BUFFER *
203146515Sruinfo_load_file (char *filename)
20421495Sjmacd{
20556160Sru  return info_load_file_internal (filename, INFO_GET_TAGS);
20621495Sjmacd}
20721495Sjmacd
20821495Sjmacd
20956160Sru/* Private functions implementation.  */
21021495Sjmacd
21121495Sjmacd/* The workhorse for info_find_file ().  Non-zero 2nd argument says to
21221495Sjmacd   try to build a tags table (or otherwise glean the nodes) for this
21321495Sjmacd   file once found.  By default, we build the tags table, but when this
21421495Sjmacd   function is called by info_get_node () when we already have a valid
21521495Sjmacd   tags table describing the nodes, it is unnecessary. */
21621495Sjmacdstatic FILE_BUFFER *
217146515Sruinfo_find_file_internal (char *filename, int get_tags)
21821495Sjmacd{
21956160Sru  int i;
22056160Sru  FILE_BUFFER *file_buffer;
22121495Sjmacd
22221495Sjmacd  /* First try to find the file in our list of already loaded files. */
22321495Sjmacd  if (info_loaded_files)
22421495Sjmacd    {
22542660Smarkm      for (i = 0; (file_buffer = info_loaded_files[i]); i++)
22693139Sru        if ((FILENAME_CMP (filename, file_buffer->filename) == 0)
22793139Sru            || (FILENAME_CMP (filename, file_buffer->fullpath) == 0)
22893139Sru            || (!IS_ABSOLUTE (filename)
22993139Sru                && FILENAME_CMP (filename,
23093139Sru                                filename_non_directory (file_buffer->fullpath))
23193139Sru                    == 0))
23242660Smarkm          {
23342660Smarkm            struct stat new_info, *old_info;
23421495Sjmacd
23542660Smarkm            /* This file is loaded.  If the filename that we want is
23642660Smarkm               specifically "dir", then simply return the file buffer. */
23756160Sru            if (is_dir_name (filename_non_directory (filename)))
23856160Sru              return file_buffer;
23921495Sjmacd
24021495Sjmacd#if defined (HANDLE_MAN_PAGES)
24142660Smarkm            /* Do the same for the magic MANPAGE file. */
24242660Smarkm            if (file_buffer->flags & N_IsManPage)
24356160Sru              return file_buffer;
24421495Sjmacd#endif /* HANDLE_MAN_PAGES */
24521495Sjmacd
24693139Sru            /* The file appears to be already loaded, and is not "dir".  Check
24793139Sru               to see if it's changed since the last time it was loaded.  */
24842660Smarkm            if (stat (file_buffer->fullpath, &new_info) == -1)
24942660Smarkm              {
25042660Smarkm                filesys_error_number = errno;
25156160Sru                return NULL;
25242660Smarkm              }
25321495Sjmacd
25442660Smarkm            old_info = &file_buffer->finfo;
25521495Sjmacd
25693139Sru            if (new_info.st_size != old_info->st_size
25793139Sru                || new_info.st_mtime != old_info->st_mtime)
25842660Smarkm              {
25942660Smarkm                /* The file has changed.  Forget that we ever had loaded it
26042660Smarkm                   in the first place. */
26142660Smarkm                forget_info_file (filename);
26242660Smarkm                break;
26342660Smarkm              }
26442660Smarkm            else
26542660Smarkm              {
26642660Smarkm                /* The info file exists, and has not changed since the last
26742660Smarkm                   time it was loaded.  If the caller requested a nodes list
26842660Smarkm                   for this file, and there isn't one here, build the nodes
26942660Smarkm                   for this file_buffer.  In any case, return the file_buffer
27042660Smarkm                   object. */
27193139Sru                if (!file_buffer->contents)
27293139Sru                  {
27393139Sru                    /* The file's contents have been gc'ed.  Reload it.  */
27493139Sru                    info_reload_file_buffer_contents (file_buffer);
27593139Sru                    if (!file_buffer->contents)
27693139Sru                      return NULL;
27793139Sru                  }
27856160Sru
27942660Smarkm                if (get_tags && !file_buffer->tags)
28042660Smarkm                  build_tags_and_nodes (file_buffer);
28121495Sjmacd
28256160Sru                return file_buffer;
28342660Smarkm              }
28442660Smarkm          }
28521495Sjmacd    }
28621495Sjmacd
28721495Sjmacd  /* The file wasn't loaded.  Try to load it now. */
28821495Sjmacd#if defined (HANDLE_MAN_PAGES)
28921495Sjmacd  /* If the name of the file that we want is our special file buffer for
29021495Sjmacd     Unix manual pages, then create the file buffer, and return it now. */
29121495Sjmacd  if (strcasecmp (filename, MANPAGE_FILE_BUFFER_NAME) == 0)
29221495Sjmacd    file_buffer = create_manpage_file_buffer ();
29321495Sjmacd  else
29421495Sjmacd#endif /* HANDLE_MAN_PAGES */
29521495Sjmacd    file_buffer = info_load_file_internal (filename, get_tags);
29621495Sjmacd
29721495Sjmacd  /* If the file was loaded, remember the name under which it was found. */
29821495Sjmacd  if (file_buffer)
29921495Sjmacd    remember_info_file (file_buffer);
30021495Sjmacd
30156160Sru  return file_buffer;
30221495Sjmacd}
30321495Sjmacd
30421495Sjmacd/* The workhorse function for info_load_file ().  Non-zero second argument
30521495Sjmacd   says to build a list of tags (or nodes) for this file.  This is the
30621495Sjmacd   default behaviour when info_load_file () is called, but it is not
30721495Sjmacd   necessary when loading a subfile for which we already have tags. */
30821495Sjmacdstatic FILE_BUFFER *
309146515Sruinfo_load_file_internal (char *filename, int get_tags)
31021495Sjmacd{
31121495Sjmacd  char *fullpath, *contents;
31221495Sjmacd  long filesize;
31321495Sjmacd  struct stat finfo;
31456160Sru  int retcode, compressed;
31556160Sru  FILE_BUFFER *file_buffer = NULL;
31621495Sjmacd
31721495Sjmacd  /* Get the full pathname of this file, as known by the info system.
31821495Sjmacd     That is to say, search along INFOPATH and expand tildes, etc. */
31921495Sjmacd  fullpath = info_find_fullpath (filename);
32021495Sjmacd
32121495Sjmacd  /* Did we actually find the file? */
32221495Sjmacd  retcode = stat (fullpath, &finfo);
32321495Sjmacd
32421495Sjmacd  /* If the file referenced by the name returned from info_find_fullpath ()
32521495Sjmacd     doesn't exist, then try again with the last part of the filename
32621495Sjmacd     appearing in lowercase. */
32756160Sru  /* This is probably not needed at all on those systems which define
32856160Sru     FILENAME_CMP to be strcasecmp.  But let's do it anyway, lest some
32956160Sru     network redirector supports case sensitivity.  */
33021495Sjmacd  if (retcode < 0)
33121495Sjmacd    {
33221495Sjmacd      char *lowered_name;
333146515Sru      char *tmp_basename;
33421495Sjmacd
33542660Smarkm      lowered_name = xstrdup (filename);
336146515Sru      tmp_basename = filename_non_directory (lowered_name);
33721495Sjmacd
338146515Sru      while (*tmp_basename)
33942660Smarkm        {
340146515Sru          if (isupper (*tmp_basename))
341146515Sru            *tmp_basename = tolower (*tmp_basename);
34221495Sjmacd
343146515Sru          tmp_basename++;
34442660Smarkm        }
34521495Sjmacd
34621495Sjmacd      fullpath = info_find_fullpath (lowered_name);
34721495Sjmacd
34821495Sjmacd      retcode = stat (fullpath, &finfo);
349146515Sru      free (lowered_name);
35021495Sjmacd    }
35121495Sjmacd
35221495Sjmacd  /* If the file wasn't found, give up, returning a NULL pointer. */
35321495Sjmacd  if (retcode < 0)
35421495Sjmacd    {
35521495Sjmacd      filesys_error_number = errno;
35656160Sru      return NULL;
35721495Sjmacd    }
35821495Sjmacd
35921495Sjmacd  /* Otherwise, try to load the file. */
36056160Sru  contents = filesys_read_info_file (fullpath, &filesize, &finfo, &compressed);
36121495Sjmacd
36221495Sjmacd  if (!contents)
36356160Sru    return NULL;
36421495Sjmacd
36521495Sjmacd  /* The file was found, and can be read.  Allocate FILE_BUFFER and fill
36621495Sjmacd     in the various members. */
36721495Sjmacd  file_buffer = make_file_buffer ();
36842660Smarkm  file_buffer->filename = xstrdup (filename);
36942660Smarkm  file_buffer->fullpath = xstrdup (fullpath);
37021495Sjmacd  file_buffer->finfo = finfo;
37121495Sjmacd  file_buffer->filesize = filesize;
37221495Sjmacd  file_buffer->contents = contents;
37356160Sru  if (compressed)
37421495Sjmacd    file_buffer->flags |= N_IsCompressed;
37521495Sjmacd
37621495Sjmacd  /* If requested, build the tags and nodes for this file buffer. */
37721495Sjmacd  if (get_tags)
37821495Sjmacd    build_tags_and_nodes (file_buffer);
37921495Sjmacd
38056160Sru  return file_buffer;
38121495Sjmacd}
38256160Sru
38321495Sjmacd/* Grovel FILE_BUFFER->contents finding tags and nodes, and filling in the
38421495Sjmacd   various slots.  This can also be used to rebuild a tag or node table. */
38521495Sjmacdvoid
386146515Srubuild_tags_and_nodes (FILE_BUFFER *file_buffer)
38721495Sjmacd{
38821495Sjmacd  SEARCH_BINDING binding;
38921495Sjmacd  long position;
39021495Sjmacd
39121495Sjmacd  free_file_buffer_tags (file_buffer);
39221495Sjmacd  file_buffer->flags &= ~N_HasTagsTable;
39321495Sjmacd
39421495Sjmacd  /* See if there is a tags table in this info file. */
39521495Sjmacd  binding.buffer = file_buffer->contents;
39621495Sjmacd  binding.start = file_buffer->filesize;
39721495Sjmacd  binding.end = binding.start - 1000;
39821495Sjmacd  if (binding.end < 0)
39921495Sjmacd    binding.end = 0;
40021495Sjmacd  binding.flags = S_FoldCase;
40121495Sjmacd
40221495Sjmacd  position = search_backward (TAGS_TABLE_END_LABEL, &binding);
40321495Sjmacd
40421495Sjmacd  /* If there is a tag table, find the start of it, and grovel over it
40521495Sjmacd     extracting tag information. */
40621495Sjmacd  if (position != -1)
40721495Sjmacd    while (1)
40821495Sjmacd      {
40942660Smarkm        long tags_table_begin, tags_table_end;
41021495Sjmacd
41142660Smarkm        binding.end = position;
41242660Smarkm        binding.start = binding.end - 5 - strlen (TAGS_TABLE_END_LABEL);
41342660Smarkm        if (binding.start < 0)
41442660Smarkm          binding.start = 0;
41521495Sjmacd
41642660Smarkm        position = find_node_separator (&binding);
41721495Sjmacd
41842660Smarkm        /* For this test, (and all others here) failure indicates a bogus
41942660Smarkm           tags table.  Grovel the file. */
42042660Smarkm        if (position == -1)
42142660Smarkm          break;
42221495Sjmacd
42342660Smarkm        /* Remember the end of the tags table. */
42442660Smarkm        binding.start = position;
42542660Smarkm        tags_table_end = binding.start;
42642660Smarkm        binding.end = 0;
42721495Sjmacd
42842660Smarkm        /* Locate the start of the tags table. */
42942660Smarkm        position = search_backward (TAGS_TABLE_BEG_LABEL, &binding);
43021495Sjmacd
43142660Smarkm        if (position == -1)
43242660Smarkm          break;
43321495Sjmacd
43442660Smarkm        binding.end = position;
43542660Smarkm        binding.start = binding.end - 5 - strlen (TAGS_TABLE_BEG_LABEL);
43642660Smarkm        position = find_node_separator (&binding);
43721495Sjmacd
43842660Smarkm        if (position == -1)
43942660Smarkm          break;
44021495Sjmacd
44142660Smarkm        /* The file contains a valid tags table.  Fill the FILE_BUFFER's
44242660Smarkm           tags member. */
44342660Smarkm        file_buffer->flags |= N_HasTagsTable;
44442660Smarkm        tags_table_begin = position;
44521495Sjmacd
44642660Smarkm        /* If this isn't an indirect tags table, just remember the nodes
44742660Smarkm           described locally in this tags table.  Note that binding.end
44842660Smarkm           is pointing to just after the beginning label. */
44942660Smarkm        binding.start = binding.end;
45042660Smarkm        binding.end = file_buffer->filesize;
45121495Sjmacd
45242660Smarkm        if (!looking_at (TAGS_TABLE_IS_INDIRECT_LABEL, &binding))
45342660Smarkm          {
45442660Smarkm            binding.start = tags_table_begin;
45542660Smarkm            binding.end = tags_table_end;
45642660Smarkm            get_nodes_of_tags_table (file_buffer, &binding);
45742660Smarkm            return;
45842660Smarkm          }
45942660Smarkm        else
46042660Smarkm          {
46142660Smarkm            /* This is an indirect tags table.  Build TAGS member. */
46242660Smarkm            SEARCH_BINDING indirect;
46321495Sjmacd
46442660Smarkm            indirect.start = tags_table_begin;
46542660Smarkm            indirect.end = 0;
46642660Smarkm            indirect.buffer = binding.buffer;
46742660Smarkm            indirect.flags = S_FoldCase;
46821495Sjmacd
46942660Smarkm            position = search_backward (INDIRECT_TAGS_TABLE_LABEL, &indirect);
47021495Sjmacd
47142660Smarkm            if (position == -1)
47242660Smarkm              {
47342660Smarkm                /* This file is malformed.  Give up. */
47442660Smarkm                return;
47542660Smarkm              }
47621495Sjmacd
47742660Smarkm            indirect.start = position;
47842660Smarkm            indirect.end = tags_table_begin;
47942660Smarkm            binding.start = tags_table_begin;
48042660Smarkm            binding.end = tags_table_end;
48142660Smarkm            get_tags_of_indirect_tags_table (file_buffer, &indirect, &binding);
48242660Smarkm            return;
48342660Smarkm          }
48421495Sjmacd      }
48521495Sjmacd
48621495Sjmacd  /* This file doesn't contain any kind of tags table.  Grovel the
48721495Sjmacd     file and build node entries for it. */
48821495Sjmacd  get_nodes_of_info_file (file_buffer);
48921495Sjmacd}
49021495Sjmacd
49121495Sjmacd/* Search through FILE_BUFFER->contents building an array of TAG *,
49221495Sjmacd   one entry per each node present in the file.  Store the tags in
49321495Sjmacd   FILE_BUFFER->tags, and the number of allocated slots in
49421495Sjmacd   FILE_BUFFER->tags_slots. */
49521495Sjmacdstatic void
496146515Sruget_nodes_of_info_file (FILE_BUFFER *file_buffer)
49721495Sjmacd{
49821495Sjmacd  long nodestart;
49921495Sjmacd  int tags_index = 0;
50021495Sjmacd  SEARCH_BINDING binding;
50121495Sjmacd
50221495Sjmacd  binding.buffer = file_buffer->contents;
50321495Sjmacd  binding.start = 0;
50421495Sjmacd  binding.end = file_buffer->filesize;
50521495Sjmacd  binding.flags = S_FoldCase;
50621495Sjmacd
50721495Sjmacd  while ((nodestart = find_node_separator (&binding)) != -1)
50821495Sjmacd    {
50921495Sjmacd      int start, end;
51021495Sjmacd      char *nodeline;
51121495Sjmacd      TAG *entry;
51256160Sru      int anchor = 0;
51321495Sjmacd
51421495Sjmacd      /* Skip past the characters just found. */
51521495Sjmacd      binding.start = nodestart;
51621495Sjmacd      binding.start += skip_node_separator (binding.buffer + binding.start);
51721495Sjmacd
51821495Sjmacd      /* Move to the start of the line defining the node. */
51921495Sjmacd      nodeline = binding.buffer + binding.start;
52021495Sjmacd
52121495Sjmacd      /* Find "Node:" */
52221495Sjmacd      start = string_in_line (INFO_NODE_LABEL, nodeline);
52356160Sru      /* No Node:.  Maybe it's a Ref:.  */
52456160Sru      if (start == -1)
52556160Sru        {
52656160Sru          start = string_in_line (INFO_REF_LABEL, nodeline);
52756160Sru          if (start != -1)
52856160Sru            anchor = 1;
52956160Sru        }
53021495Sjmacd
53121495Sjmacd      /* If not there, this is not the start of a node. */
53221495Sjmacd      if (start == -1)
53342660Smarkm        continue;
53421495Sjmacd
53521495Sjmacd      /* Find the start of the nodename. */
53621495Sjmacd      start += skip_whitespace (nodeline + start);
53721495Sjmacd
53821495Sjmacd      /* Find the end of the nodename. */
53921495Sjmacd      end = start +
54042660Smarkm        skip_node_characters (nodeline + start, DONT_SKIP_NEWLINES);
54121495Sjmacd
54221495Sjmacd      /* Okay, we have isolated the node name, and we know where the
54356160Sru         node starts.  Remember this information. */
54456160Sru      entry = xmalloc (sizeof (TAG));
54556160Sru      entry->nodename = xmalloc (1 + (end - start));
54621495Sjmacd      strncpy (entry->nodename, nodeline + start, end - start);
54756160Sru      entry->nodename[end - start] = 0;
54821495Sjmacd      entry->nodestart = nodestart;
54956160Sru      if (anchor)
55056160Sru        entry->nodelen = 0;
55156160Sru      else
55256160Sru        {
55356160Sru          SEARCH_BINDING node_body;
55421495Sjmacd
55556160Sru          node_body.buffer = binding.buffer + binding.start;
55656160Sru          node_body.start = 0;
55756160Sru          node_body.end = binding.end - binding.start;
55856160Sru          node_body.flags = S_FoldCase;
55956160Sru          entry->nodelen = get_node_length (&node_body);
56056160Sru        }
56121495Sjmacd
56221495Sjmacd      entry->filename = file_buffer->fullpath;
56321495Sjmacd
56421495Sjmacd      /* Add this tag to the array of tag structures in this FILE_BUFFER. */
56521495Sjmacd      add_pointer_to_array (entry, tags_index, file_buffer->tags,
56642660Smarkm                            file_buffer->tags_slots, 100, TAG *);
56721495Sjmacd    }
56821495Sjmacd}
56921495Sjmacd
57021495Sjmacd/* Return the length of the node which starts at BINDING. */
57121495Sjmacdstatic long
572146515Sruget_node_length (SEARCH_BINDING *binding)
57321495Sjmacd{
57456160Sru  int i;
57521495Sjmacd  char *body;
57621495Sjmacd
57756160Sru  /* [A node] ends with either a ^_, a ^L, or end of file.  */
57821495Sjmacd  for (i = binding->start, body = binding->buffer; i < binding->end; i++)
57921495Sjmacd    {
58021495Sjmacd      if (body[i] == INFO_FF || body[i] == INFO_COOKIE)
58142660Smarkm        break;
58221495Sjmacd    }
58356160Sru  return i - binding->start;
58421495Sjmacd}
58521495Sjmacd
58621495Sjmacd/* Build and save the array of nodes in FILE_BUFFER by searching through the
58721495Sjmacd   contents of BUFFER_BINDING for a tags table, and groveling the contents. */
58821495Sjmacdstatic void
589146515Sruget_nodes_of_tags_table (FILE_BUFFER *file_buffer,
590146515Sru    SEARCH_BINDING *buffer_binding)
59121495Sjmacd{
59256160Sru  int name_offset;
593146515Sru  SEARCH_BINDING *tmp_search;
59421495Sjmacd  long position;
59556160Sru  int tags_index = 0;
59621495Sjmacd
597146515Sru  tmp_search = copy_binding (buffer_binding);
59821495Sjmacd
59921495Sjmacd  /* Find the start of the tags table. */
600146515Sru  position = find_tags_table (tmp_search);
60121495Sjmacd
60221495Sjmacd  /* If none, we're all done. */
60321495Sjmacd  if (position == -1)
60421495Sjmacd    return;
60521495Sjmacd
60621495Sjmacd  /* Move to one character before the start of the actual table. */
607146515Sru  tmp_search->start = position;
608146515Sru  tmp_search->start += skip_node_separator
609146515Sru    (tmp_search->buffer + tmp_search->start);
610146515Sru  tmp_search->start += strlen (TAGS_TABLE_BEG_LABEL);
611146515Sru  tmp_search->start--;
61221495Sjmacd
61321495Sjmacd  /* The tag table consists of lines containing node names and positions.
61421495Sjmacd     Do each line until we find one that doesn't contain a node name. */
615146515Sru  while ((position = search_forward ("\n", tmp_search)) != -1)
61621495Sjmacd    {
61721495Sjmacd      TAG *entry;
61821495Sjmacd      char *nodedef;
61956160Sru      unsigned p;
62056160Sru      int anchor = 0;
62121495Sjmacd
62221495Sjmacd      /* Prepare to skip this line. */
623146515Sru      tmp_search->start = position;
624146515Sru      tmp_search->start++;
62521495Sjmacd
62621495Sjmacd      /* Skip past informative "(Indirect)" tags table line. */
627146515Sru      if (!tags_index && looking_at (TAGS_TABLE_IS_INDIRECT_LABEL, tmp_search))
62842660Smarkm        continue;
62921495Sjmacd
63021495Sjmacd      /* Find the label preceding the node name. */
63156160Sru      name_offset =
632146515Sru        string_in_line (INFO_NODE_LABEL, tmp_search->buffer + tmp_search->start);
63321495Sjmacd
63456160Sru      /* If no node label, maybe it's an anchor.  */
63556160Sru      if (name_offset == -1)
63656160Sru        {
63756160Sru          name_offset = string_in_line (INFO_REF_LABEL,
638146515Sru              tmp_search->buffer + tmp_search->start);
63956160Sru          if (name_offset != -1)
64056160Sru            anchor = 1;
64156160Sru        }
64256160Sru
64321495Sjmacd      /* If not there, not a defining line, so we must be out of the
64456160Sru         tags table.  */
64556160Sru      if (name_offset == -1)
64642660Smarkm        break;
64721495Sjmacd
64856160Sru      entry = xmalloc (sizeof (TAG));
64956160Sru
65056160Sru      /* Find the beginning of the node definition. */
651146515Sru      tmp_search->start += name_offset;
652146515Sru      nodedef = tmp_search->buffer + tmp_search->start;
65321495Sjmacd      nodedef += skip_whitespace (nodedef);
65421495Sjmacd
65556160Sru      /* Move past the node's name in this tag to the TAGSEP character. */
65656160Sru      for (p = 0; nodedef[p] && nodedef[p] != INFO_TAGSEP; p++)
65756160Sru        ;
65856160Sru      if (nodedef[p] != INFO_TAGSEP)
65942660Smarkm        continue;
66021495Sjmacd
66156160Sru      entry->nodename = xmalloc (p + 1);
66256160Sru      strncpy (entry->nodename, nodedef, p);
66356160Sru      entry->nodename[p] = 0;
66456160Sru      p++;
66556160Sru      entry->nodestart = atol (nodedef + p);
66621495Sjmacd
66756160Sru      /* If a node, we don't know the length yet, but if it's an
66856160Sru         anchor, the length is 0. */
66956160Sru      entry->nodelen = anchor ? 0 : -1;
67021495Sjmacd
67121495Sjmacd      /* The filename of this node is currently known as the same as the
67242660Smarkm         name of this file. */
67321495Sjmacd      entry->filename = file_buffer->fullpath;
67421495Sjmacd
67521495Sjmacd      /* Add this node structure to the array of node structures in this
67642660Smarkm         FILE_BUFFER. */
67721495Sjmacd      add_pointer_to_array (entry, tags_index, file_buffer->tags,
67842660Smarkm                            file_buffer->tags_slots, 100, TAG *);
67921495Sjmacd    }
680146515Sru  free (tmp_search);
68121495Sjmacd}
68221495Sjmacd
68356160Sru/* A structure used only in `get_tags_of_indirect_tags_table' to hold onto
68421495Sjmacd   an intermediate value. */
68521495Sjmacdtypedef struct {
68621495Sjmacd  char *filename;
68721495Sjmacd  long first_byte;
68821495Sjmacd} SUBFILE;
68921495Sjmacd
69021495Sjmacd/* Remember in FILE_BUFFER the nodenames, subfilenames, and offsets within the
69121495Sjmacd   subfiles of every node which appears in TAGS_BINDING.  The 2nd argument is
69221495Sjmacd   a binding surrounding the indirect files list. */
69321495Sjmacdstatic void
694146515Sruget_tags_of_indirect_tags_table (FILE_BUFFER *file_buffer,
695146515Sru    SEARCH_BINDING *indirect_binding, SEARCH_BINDING *tags_binding)
69621495Sjmacd{
69756160Sru  int i;
69856160Sru  SUBFILE **subfiles = NULL;
69921495Sjmacd  int subfiles_index = 0, subfiles_slots = 0;
70021495Sjmacd  TAG *entry;
70121495Sjmacd
70221495Sjmacd  /* First get the list of tags from the tags table.  Then lookup the
70321495Sjmacd     associated file in the indirect list for each tag, and update it. */
70421495Sjmacd  get_nodes_of_tags_table (file_buffer, tags_binding);
70521495Sjmacd
70621495Sjmacd  /* We have the list of tags in file_buffer->tags.  Get the list of
70721495Sjmacd     subfiles from the indirect table. */
70821495Sjmacd  {
70921495Sjmacd    char *start, *end, *line;
71021495Sjmacd    SUBFILE *subfile;
71121495Sjmacd
71221495Sjmacd    start = indirect_binding->buffer + indirect_binding->start;
71321495Sjmacd    end = indirect_binding->buffer + indirect_binding->end;
71421495Sjmacd    line = start;
71521495Sjmacd
71621495Sjmacd    while (line < end)
71721495Sjmacd      {
71842660Smarkm        int colon;
71921495Sjmacd
72042660Smarkm        colon = string_in_line (":", line);
72121495Sjmacd
72242660Smarkm        if (colon == -1)
72342660Smarkm          break;
72421495Sjmacd
72542660Smarkm        subfile = (SUBFILE *)xmalloc (sizeof (SUBFILE));
72642660Smarkm        subfile->filename = (char *)xmalloc (colon);
72742660Smarkm        strncpy (subfile->filename, line, colon - 1);
72856160Sru        subfile->filename[colon - 1] = 0;
72942660Smarkm        subfile->first_byte = (long) atol (line + colon);
73021495Sjmacd
73142660Smarkm        add_pointer_to_array
73242660Smarkm          (subfile, subfiles_index, subfiles, subfiles_slots, 10, SUBFILE *);
73321495Sjmacd
73442660Smarkm        while (*line++ != '\n');
73521495Sjmacd      }
73621495Sjmacd  }
73721495Sjmacd
73821495Sjmacd  /* If we have successfully built the indirect files table, then
73921495Sjmacd     merge the information in the two tables. */
74021495Sjmacd  if (!subfiles)
74121495Sjmacd    {
74221495Sjmacd      free_file_buffer_tags (file_buffer);
74321495Sjmacd      return;
74421495Sjmacd    }
74521495Sjmacd  else
74621495Sjmacd    {
74756160Sru      int tags_index;
74821495Sjmacd      long header_length;
74921495Sjmacd      SEARCH_BINDING binding;
75021495Sjmacd
75121495Sjmacd      /* Find the length of the header of the file containing the indirect
75242660Smarkm         tags table.  This header appears at the start of every file.  We
75342660Smarkm         want the absolute position of each node within each subfile, so
75442660Smarkm         we subtract the start of the containing subfile from the logical
75542660Smarkm         position of the node, and then add the length of the header in. */
75621495Sjmacd      binding.buffer = file_buffer->contents;
75721495Sjmacd      binding.start = 0;
75821495Sjmacd      binding.end = file_buffer->filesize;
75921495Sjmacd      binding.flags = S_FoldCase;
76021495Sjmacd
76121495Sjmacd      header_length = find_node_separator (&binding);
76221495Sjmacd      if (header_length == -1)
76342660Smarkm        header_length = 0;
76421495Sjmacd
76521495Sjmacd      /* Build the file buffer's list of subfiles. */
76621495Sjmacd      {
76756160Sru        char *containing_dir = xstrdup (file_buffer->fullpath);
76893139Sru        char *temp = filename_non_directory (containing_dir);
76942660Smarkm        int len_containing_dir;
77021495Sjmacd
77193139Sru        if (temp > containing_dir)
77293139Sru          {
77393139Sru            if (HAVE_DRIVE (file_buffer->fullpath) &&
77493139Sru                temp == containing_dir + 2)
77593139Sru              {
77693139Sru                /* Avoid converting "d:foo" into "d:/foo" below.  */
77793139Sru                *temp = '.';
77893139Sru                temp += 2;
77993139Sru              }
78093139Sru            temp[-1] = 0;
78193139Sru          }
78221495Sjmacd
78342660Smarkm        len_containing_dir = strlen (containing_dir);
78421495Sjmacd
78542660Smarkm        for (i = 0; subfiles[i]; i++);
78621495Sjmacd
78742660Smarkm        file_buffer->subfiles = (char **) xmalloc ((1 + i) * sizeof (char *));
78821495Sjmacd
78942660Smarkm        for (i = 0; subfiles[i]; i++)
79042660Smarkm          {
79142660Smarkm            char *fullpath;
79221495Sjmacd
79342660Smarkm            fullpath = (char *) xmalloc
79442660Smarkm              (2 + strlen (subfiles[i]->filename) + len_containing_dir);
79521495Sjmacd
79642660Smarkm            sprintf (fullpath, "%s/%s",
79742660Smarkm                     containing_dir, subfiles[i]->filename);
79821495Sjmacd
79942660Smarkm            file_buffer->subfiles[i] = fullpath;
80042660Smarkm          }
80156160Sru        file_buffer->subfiles[i] = NULL;
80242660Smarkm        free (containing_dir);
80321495Sjmacd      }
80421495Sjmacd
80521495Sjmacd      /* For each node in the file's tags table, remember the starting
80642660Smarkm         position. */
80742660Smarkm      for (tags_index = 0; (entry = file_buffer->tags[tags_index]);
80842660Smarkm           tags_index++)
80942660Smarkm        {
81042660Smarkm          for (i = 0;
81142660Smarkm               subfiles[i] && entry->nodestart >= subfiles[i]->first_byte;
81242660Smarkm               i++);
81321495Sjmacd
81442660Smarkm          /* If the Info file containing the indirect tags table is
81542660Smarkm             malformed, then give up. */
81642660Smarkm          if (!i)
81742660Smarkm            {
81842660Smarkm              /* The Info file containing the indirect tags table is
81942660Smarkm                 malformed.  Give up. */
82042660Smarkm              for (i = 0; subfiles[i]; i++)
82142660Smarkm                {
82242660Smarkm                  free (subfiles[i]->filename);
82342660Smarkm                  free (subfiles[i]);
82442660Smarkm                  free (file_buffer->subfiles[i]);
82542660Smarkm                }
82656160Sru              file_buffer->subfiles = NULL;
82742660Smarkm              free_file_buffer_tags (file_buffer);
82842660Smarkm              return;
82942660Smarkm            }
83021495Sjmacd
83142660Smarkm          /* SUBFILES[i] is the index of the first subfile whose logical
83242660Smarkm             first byte is greater than the logical offset of this node's
83342660Smarkm             starting position.  This means that the subfile directly
83442660Smarkm             preceding this one is the one containing the node. */
83521495Sjmacd
83642660Smarkm          entry->filename = file_buffer->subfiles[i - 1];
83756160Sru          entry->nodestart -= subfiles[i - 1]->first_byte;
83842660Smarkm          entry->nodestart += header_length;
83942660Smarkm        }
84021495Sjmacd
84121495Sjmacd      /* We have successfully built the tags table.  Remember that it
84242660Smarkm         was indirect. */
84321495Sjmacd      file_buffer->flags |= N_TagsIndirect;
84421495Sjmacd    }
84521495Sjmacd
84621495Sjmacd  /* Free the structures assigned to SUBFILES.  Free the names as well
84721495Sjmacd     as the structures themselves, then finally, the array. */
84821495Sjmacd  for (i = 0; subfiles[i]; i++)
84921495Sjmacd    {
85021495Sjmacd      free (subfiles[i]->filename);
85121495Sjmacd      free (subfiles[i]);
85221495Sjmacd    }
85321495Sjmacd  free (subfiles);
85421495Sjmacd}
85521495Sjmacd
85656160Sru
85756160Sru/* Return the node that contains TAG in FILE_BUFFER, else
85856160Sru   (pathologically) NULL.  Called from info_node_of_file_buffer_tags.  */
85956160Srustatic NODE *
860146515Srufind_node_of_anchor (FILE_BUFFER *file_buffer, TAG *tag)
86156160Sru{
86256160Sru  int anchor_pos, node_pos;
86356160Sru  TAG *node_tag;
86456160Sru  NODE *node;
865116525Sru
86656160Sru  /* Look through the tag list for the anchor.  */
86756160Sru  for (anchor_pos = 0; file_buffer->tags[anchor_pos]; anchor_pos++)
86856160Sru    {
86956160Sru      TAG *t = file_buffer->tags[anchor_pos];
87056160Sru      if (t->nodestart == tag->nodestart)
871116525Sru        break;
87256160Sru    }
873116525Sru
87456160Sru  /* Should not happen, because we should always find the anchor.  */
87556160Sru  if (!file_buffer->tags[anchor_pos])
87656160Sru    return NULL;
877116525Sru
87856160Sru  /* We've found the anchor.  Look backwards in the tag table for the
87956160Sru     preceding node (we're assuming the tags are given in order),
88056160Sru     skipping over any preceding anchors.  */
88156160Sru  for (node_pos = anchor_pos - 1;
88256160Sru       node_pos >= 0 && file_buffer->tags[node_pos]->nodelen == 0;
88356160Sru       node_pos--)
88456160Sru    ;
885116525Sru
88656160Sru  /* An info file with an anchor before any nodes is pathological, but
88756160Sru     it's possible, so don't crash.  */
88856160Sru  if (node_pos < 0)
88956160Sru    return NULL;
890116525Sru
89156160Sru  /* We have the tag for the node that contained the anchor tag.  */
892116525Sru  node_tag = file_buffer->tags[node_pos];
89356160Sru
89456160Sru  /* Look up the node name in the tag table to get the actual node.
89556160Sru     This is a recursive call, but it can't recurse again, because we
89656160Sru     call it with a real node.  */
89756160Sru  node = info_node_of_file_buffer_tags (file_buffer, node_tag->nodename);
898116525Sru
89956160Sru  /* Start displaying the node at the anchor position.  */
90056160Sru  if (node)
90156160Sru    { /* The nodestart for real nodes is three characters before the `F'
90256160Sru         in the `File:' line (a newline, the CTRL-_, and another
90356160Sru         newline).  The nodestart for anchors is the actual position.
90456160Sru         But we offset by only 2, rather than 3, because if an anchor is
90556160Sru         at the beginning of a paragraph, it's nicer for it to end up on
90656160Sru         the beginning of the first line of the paragraph rather than
90756160Sru         the blank line before it.  (makeinfo has no way of knowing that
90856160Sru         a paragraph is going to start, so we can't fix it there.)  */
90956160Sru      node->display_pos = file_buffer->tags[anchor_pos]->nodestart
91056160Sru                          - (node_tag->nodestart + 2);
91156160Sru
91256160Sru      /* Otherwise an anchor at the end of a node ends up displaying at
91356160Sru         the end of the last line of the node (way over on the right of
91456160Sru         the screen), which looks wrong.  */
915146515Sru      if (node->display_pos >= (unsigned long) node->nodelen)
91656160Sru        node->display_pos = node->nodelen - 1;
917116525Sru
91856160Sru      /* Don't search in the node for the xref text, it's not there.  */
91956160Sru      node->flags |= N_FromAnchor;
92056160Sru    }
921116525Sru
92256160Sru  return node;
92356160Sru}
92456160Sru
92556160Sru
92621495Sjmacd/* Return the node from FILE_BUFFER which matches NODENAME by searching
92756160Sru   the tags table in FILE_BUFFER, or NULL.  */
92821495Sjmacdstatic NODE *
929146515Sruinfo_node_of_file_buffer_tags (FILE_BUFFER *file_buffer, char *nodename)
93021495Sjmacd{
93121495Sjmacd  TAG *tag;
93256160Sru  int i;
93321495Sjmacd
93493139Sru  /* If no tags at all (possibly a misformatted info file), quit.  */
93593139Sru  if (!file_buffer->tags) {
93693139Sru    return NULL;
93793139Sru  }
938116525Sru
939116525Sru  for (i = 0; (tag = file_buffer->tags[i]); i++)
94021495Sjmacd    if (strcmp (nodename, tag->nodename) == 0)
94121495Sjmacd      {
94293139Sru        FILE_BUFFER *subfile = info_find_file_internal (tag->filename,
94393139Sru                                                        INFO_NO_TAGS);
94442660Smarkm        if (!subfile)
94556160Sru          return NULL;
94621495Sjmacd
94742660Smarkm        if (!subfile->contents)
94842660Smarkm          {
94942660Smarkm            info_reload_file_buffer_contents (subfile);
95042660Smarkm            if (!subfile->contents)
95156160Sru              return NULL;
95242660Smarkm          }
95321495Sjmacd
95442660Smarkm        /* If we were able to find this file and load it, then return
95542660Smarkm           the node within it. */
95642660Smarkm        {
95756160Sru          NODE *node = xmalloc (sizeof (NODE));
95856160Sru          node->filename    = subfile->fullpath;
95956160Sru          node->parent      = NULL;
96056160Sru          node->nodename    = tag->nodename;
96156160Sru          node->contents    = subfile->contents + tag->nodestart;
96256160Sru          node->display_pos = 0;
96356160Sru          node->flags       = 0;
96421495Sjmacd
96542660Smarkm          if (file_buffer->flags & N_HasTagsTable)
96642660Smarkm            {
96742660Smarkm              node->flags |= N_HasTagsTable;
96821495Sjmacd
96942660Smarkm              if (file_buffer->flags & N_TagsIndirect)
97042660Smarkm                {
97142660Smarkm                  node->flags |= N_TagsIndirect;
97242660Smarkm                  node->parent = file_buffer->fullpath;
97342660Smarkm                }
97442660Smarkm            }
97521495Sjmacd
97642660Smarkm          if (subfile->flags & N_IsCompressed)
97742660Smarkm            node->flags |= N_IsCompressed;
97821495Sjmacd
97942660Smarkm          /* If TAG->nodelen hasn't been calculated yet, then we aren't
98042660Smarkm             in a position to trust the entry pointer.  Adjust things so
98142660Smarkm             that ENTRY->nodestart gets the exact address of the start of
98242660Smarkm             the node separator which starts this node, and NODE->contents
98342660Smarkm             gets the address of the line defining this node.  If we cannot
98442660Smarkm             do that, the node isn't really here. */
98542660Smarkm          if (tag->nodelen == -1)
98642660Smarkm            {
98742660Smarkm              int min, max;
98842660Smarkm              char *node_sep;
98942660Smarkm              SEARCH_BINDING node_body;
99042660Smarkm              char *buff_end;
99121495Sjmacd
99242660Smarkm              min = max = DEFAULT_INFO_FUDGE;
99321495Sjmacd
99442660Smarkm              if (tag->nodestart < DEFAULT_INFO_FUDGE)
99542660Smarkm                min = tag->nodestart;
99621495Sjmacd
99742660Smarkm              if (DEFAULT_INFO_FUDGE >
99842660Smarkm                  (subfile->filesize - tag->nodestart))
99942660Smarkm                max = subfile->filesize - tag->nodestart;
100021495Sjmacd
100142660Smarkm              /* NODE_SEP gets the address of the separator which defines
100256160Sru                 this node, or NULL if the node wasn't found.
100342660Smarkm                 NODE->contents is side-effected to point to right after
100442660Smarkm                 the separator. */
100542660Smarkm              node_sep = adjust_nodestart (node, min, max);
100656160Sru              if (node_sep == NULL)
100742660Smarkm                {
100842660Smarkm                  free (node);
100956160Sru                  return NULL;
101042660Smarkm                }
101142660Smarkm              /* Readjust tag->nodestart. */
101242660Smarkm              tag->nodestart = node_sep - subfile->contents;
101321495Sjmacd
101442660Smarkm              /* Calculate the length of the current node. */
101542660Smarkm              buff_end = subfile->contents + subfile->filesize;
101621495Sjmacd
101742660Smarkm              node_body.buffer = node->contents;
101842660Smarkm              node_body.start = 0;
101942660Smarkm              node_body.end = buff_end - node_body.buffer;
102042660Smarkm              node_body.flags = 0;
102142660Smarkm              tag->nodelen = get_node_length (&node_body);
102256160Sru              node->nodelen = tag->nodelen;
102342660Smarkm            }
102456160Sru
102556160Sru          else if (tag->nodelen == 0) /* anchor, return containing node */
102656160Sru            {
102756160Sru              free (node);
102856160Sru              node = find_node_of_anchor (file_buffer, tag);
102956160Sru            }
1030116525Sru
103142660Smarkm          else
103242660Smarkm            {
103342660Smarkm              /* Since we know the length of this node, we have already
103442660Smarkm                 adjusted tag->nodestart to point to the exact start of
103542660Smarkm                 it.  Simply skip the node separator. */
103642660Smarkm              node->contents += skip_node_separator (node->contents);
103756160Sru              node->nodelen = tag->nodelen;
103842660Smarkm            }
103921495Sjmacd
104056160Sru          return node;
104142660Smarkm        }
104221495Sjmacd      }
104321495Sjmacd
104421495Sjmacd  /* There was a tag table for this file, and the node wasn't found.
104521495Sjmacd     Return NULL, since this file doesn't contain the desired node. */
104656160Sru  return NULL;
104721495Sjmacd}
104821495Sjmacd
104956160Sru/* Managing file_buffers, nodes, and tags.  */
105021495Sjmacd
105121495Sjmacd/* Create a new, empty file buffer. */
105221495SjmacdFILE_BUFFER *
1053146515Srumake_file_buffer (void)
105421495Sjmacd{
105556160Sru  FILE_BUFFER *file_buffer = xmalloc (sizeof (FILE_BUFFER));
105621495Sjmacd
105756160Sru  file_buffer->filename = file_buffer->fullpath = NULL;
105856160Sru  file_buffer->contents = NULL;
105956160Sru  file_buffer->tags = NULL;
106056160Sru  file_buffer->subfiles = NULL;
106121495Sjmacd  file_buffer->tags_slots = 0;
106221495Sjmacd  file_buffer->flags = 0;
106321495Sjmacd
106456160Sru  return file_buffer;
106521495Sjmacd}
106621495Sjmacd
106721495Sjmacd/* Add FILE_BUFFER to our list of already loaded info files. */
106821495Sjmacdstatic void
1069146515Sruremember_info_file (FILE_BUFFER *file_buffer)
107021495Sjmacd{
107121495Sjmacd  int i;
107221495Sjmacd
107321495Sjmacd  for (i = 0; info_loaded_files && info_loaded_files[i]; i++)
107421495Sjmacd    ;
107521495Sjmacd
107621495Sjmacd  add_pointer_to_array (file_buffer, i, info_loaded_files,
107742660Smarkm                        info_loaded_files_slots, 10, FILE_BUFFER *);
107821495Sjmacd}
107921495Sjmacd
108021495Sjmacd/* Forget the contents, tags table, nodes list, and names of FILENAME. */
108121495Sjmacdstatic void
1082146515Sruforget_info_file (char *filename)
108321495Sjmacd{
108456160Sru  int i;
108521495Sjmacd  FILE_BUFFER *file_buffer;
108621495Sjmacd
108721495Sjmacd  if (!info_loaded_files)
108821495Sjmacd    return;
108921495Sjmacd
1090116525Sru  for (i = 0; (file_buffer = info_loaded_files[i]); i++)
109156160Sru    if (FILENAME_CMP (filename, file_buffer->filename) == 0
109256160Sru        || FILENAME_CMP (filename, file_buffer->fullpath) == 0)
109321495Sjmacd      {
109442660Smarkm        free (file_buffer->filename);
109542660Smarkm        free (file_buffer->fullpath);
109621495Sjmacd
109742660Smarkm        if (file_buffer->contents)
109842660Smarkm          free (file_buffer->contents);
1099116525Sru
110056160Sru        /* free_file_buffer_tags () also kills the subfiles list, since
110156160Sru           the subfiles list is only of use in conjunction with tags. */
110242660Smarkm        free_file_buffer_tags (file_buffer);
110321495Sjmacd
110456160Sru        /* Move rest of list down.  */
110556160Sru        while (info_loaded_files[i + 1])
110656160Sru          {
110756160Sru            info_loaded_files[i] = info_loaded_files[i + 1];
110856160Sru            i++;
110956160Sru          }
111056160Sru        info_loaded_files[i] = 0;
111121495Sjmacd
111242660Smarkm        break;
111321495Sjmacd      }
111421495Sjmacd}
111521495Sjmacd
111621495Sjmacd/* Free the tags (if any) associated with FILE_BUFFER. */
111721495Sjmacdstatic void
1118146515Srufree_file_buffer_tags (FILE_BUFFER *file_buffer)
111921495Sjmacd{
112056160Sru  int i;
112121495Sjmacd
112221495Sjmacd  if (file_buffer->tags)
112321495Sjmacd    {
112456160Sru      TAG *tag;
112521495Sjmacd
112642660Smarkm      for (i = 0; (tag = file_buffer->tags[i]); i++)
112742660Smarkm        free_info_tag (tag);
112821495Sjmacd
112921495Sjmacd      free (file_buffer->tags);
113056160Sru      file_buffer->tags = NULL;
113121495Sjmacd      file_buffer->tags_slots = 0;
113221495Sjmacd    }
113321495Sjmacd
113421495Sjmacd  if (file_buffer->subfiles)
113521495Sjmacd    {
113621495Sjmacd      for (i = 0; file_buffer->subfiles[i]; i++)
113742660Smarkm        free (file_buffer->subfiles[i]);
113821495Sjmacd
113921495Sjmacd      free (file_buffer->subfiles);
114056160Sru      file_buffer->subfiles = NULL;
114121495Sjmacd    }
114221495Sjmacd}
114321495Sjmacd
114421495Sjmacd/* Free the data associated with TAG, as well as TAG itself. */
114521495Sjmacdstatic void
1146146515Srufree_info_tag (TAG *tag)
114721495Sjmacd{
114821495Sjmacd  free (tag->nodename);
114921495Sjmacd
115021495Sjmacd  /* We don't free tag->filename, because that filename is part of the
115121495Sjmacd     subfiles list for the containing FILE_BUFFER.  free_info_tags ()
115221495Sjmacd     will free the subfiles when it is appropriate. */
115321495Sjmacd
115421495Sjmacd  free (tag);
115521495Sjmacd}
115621495Sjmacd
115721495Sjmacd/* Load the contents of FILE_BUFFER->contents.  This function is called
115821495Sjmacd   when a file buffer was loaded, and then in order to conserve memory, the
115921495Sjmacd   file buffer's contents were freed and the pointer was zero'ed.  Note that
116021495Sjmacd   the file was already loaded at least once successfully, so the tags and/or
116121495Sjmacd   nodes members are still correctly filled. */
116221495Sjmacdstatic void
1163146515Sruinfo_reload_file_buffer_contents (FILE_BUFFER *fb)
116421495Sjmacd{
116556160Sru  int is_compressed;
116621495Sjmacd
116721495Sjmacd#if defined (HANDLE_MAN_PAGES)
116821495Sjmacd  /* If this is the magic manpage node, don't try to reload, just give up. */
116921495Sjmacd  if (fb->flags & N_IsManPage)
117021495Sjmacd    return;
117121495Sjmacd#endif
117221495Sjmacd
117321495Sjmacd  fb->flags &= ~N_IsCompressed;
117421495Sjmacd
117521495Sjmacd  /* Let the filesystem do all the work for us. */
117621495Sjmacd  fb->contents =
117756160Sru    filesys_read_info_file (fb->fullpath, &(fb->filesize), &(fb->finfo),
117893139Sru                            &is_compressed);
117956160Sru  if (is_compressed)
118021495Sjmacd    fb->flags |= N_IsCompressed;
118121495Sjmacd}
118221495Sjmacd
118321495Sjmacd/* Return the actual starting memory location of NODE, side-effecting
118421495Sjmacd   NODE->contents.  MIN and MAX are bounds for a search if one is necessary.
118521495Sjmacd   Because of the way that tags are implemented, the physical nodestart may
118621495Sjmacd   not actually be where the tag says it is.  If that is the case, but the
118721495Sjmacd   node was found anyway, set N_UpdateTags in NODE->flags.  If the node is
118821495Sjmacd   found, return non-zero.  NODE->contents is returned positioned right after
118921495Sjmacd   the node separator that precedes this node, while the return value is
119021495Sjmacd   position directly on the separator that precedes this node.  If the node
119121495Sjmacd   could not be found, return a NULL pointer. */
119221495Sjmacdstatic char *
1193146515Sruadjust_nodestart (NODE *node, int min, int max)
119421495Sjmacd{
119521495Sjmacd  long position;
119621495Sjmacd  SEARCH_BINDING node_body;
119721495Sjmacd
119821495Sjmacd  /* Define the node body. */
119921495Sjmacd  node_body.buffer = node->contents;
120021495Sjmacd  node_body.start = 0;
120121495Sjmacd  node_body.end = max;
120221495Sjmacd  node_body.flags = 0;
120321495Sjmacd
120421495Sjmacd  /* Try the optimal case first.  Who knows?  This file may actually be
120521495Sjmacd     formatted (mostly) correctly. */
120621495Sjmacd  if (node_body.buffer[0] != INFO_COOKIE && min > 2)
120721495Sjmacd    node_body.buffer -= 3;
120821495Sjmacd
120921495Sjmacd  position = find_node_separator (&node_body);
121021495Sjmacd
121121495Sjmacd  /* If we found a node start, then check it out. */
121221495Sjmacd  if (position != -1)
121321495Sjmacd    {
121421495Sjmacd      int sep_len;
121521495Sjmacd
121621495Sjmacd      sep_len = skip_node_separator (node->contents);
121721495Sjmacd
121821495Sjmacd      /* If we managed to skip a node separator, then check for this node
121942660Smarkm         being the right one. */
122021495Sjmacd      if (sep_len != 0)
122142660Smarkm        {
122242660Smarkm          char *nodedef, *nodestart;
122342660Smarkm          int offset;
122421495Sjmacd
122542660Smarkm          nodestart = node_body.buffer + position + sep_len;
122642660Smarkm          nodedef = nodestart;
122742660Smarkm          offset = string_in_line (INFO_NODE_LABEL, nodedef);
122821495Sjmacd
122942660Smarkm          if (offset != -1)
123042660Smarkm            {
123142660Smarkm              nodedef += offset;
123242660Smarkm              nodedef += skip_whitespace (nodedef);
123342660Smarkm              offset = skip_node_characters (nodedef, DONT_SKIP_NEWLINES);
1234146515Sru              if (((unsigned int) offset == strlen (node->nodename)) &&
123542660Smarkm                  (strncmp (node->nodename, nodedef, offset) == 0))
123642660Smarkm                {
123742660Smarkm                  node->contents = nodestart;
123856160Sru                  return node_body.buffer + position;
123942660Smarkm                }
124042660Smarkm            }
124142660Smarkm        }
124221495Sjmacd    }
124321495Sjmacd
124421495Sjmacd  /* Oh well, I guess we have to try to find it in a larger area. */
124521495Sjmacd  node_body.buffer = node->contents - min;
124621495Sjmacd  node_body.start = 0;
124721495Sjmacd  node_body.end = min + max;
124821495Sjmacd  node_body.flags = 0;
124921495Sjmacd
125021495Sjmacd  position = find_node_in_binding (node->nodename, &node_body);
125121495Sjmacd
125221495Sjmacd  /* If the node couldn't be found, we lose big. */
125321495Sjmacd  if (position == -1)
125456160Sru    return NULL;
125521495Sjmacd
125621495Sjmacd  /* Otherwise, the node was found, but the tags table could need updating
125721495Sjmacd     (if we used a tag to get here, that is).  Set the flag in NODE->flags. */
125821495Sjmacd  node->contents = node_body.buffer + position;
125921495Sjmacd  node->contents += skip_node_separator (node->contents);
126021495Sjmacd  if (node->flags & N_HasTagsTable)
126121495Sjmacd    node->flags |= N_UpdateTags;
126256160Sru  return node_body.buffer + position;
126321495Sjmacd}
1264