/* nodes.c -- how to get an Info file and node. Copyright 1993-2023 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Originally written by Brian Fox. */ #include "info.h" #include "nodes.h" #include "search.h" #include "filesys.h" #include "scan.h" #include "util.h" #include "tag.h" #include "man.h" #include "variables.h" /* Global variables. */ /* When non-zero, this is a string describing the recent file error. */ char *info_recent_file_error = NULL; /* The list of already loaded files. */ FILE_BUFFER **info_loaded_files = NULL; /* Number of loaded files. */ size_t info_loaded_files_index = 0; /* The number of slots currently allocated to LOADED_FILES. */ size_t info_loaded_files_slots = 0; /* Functions for tag table creation and destruction. */ static void build_tag_table (FILE_BUFFER *file_buffer); static void get_nodes_of_tags_table (FILE_BUFFER *file_buffer, SEARCH_BINDING *buffer_binding); static void get_tags_of_indirect_tags_table (FILE_BUFFER *file_buffer, SEARCH_BINDING *indirect_binding, SEARCH_BINDING *tags_binding); static void free_file_buffer_tags (FILE_BUFFER *file_buffer); static void free_info_tag (TAG *tag); /* Grovel FILE_BUFFER->contents finding tags and nodes, and filling in the various slots. This can also be used to rebuild a tag or node table. */ void build_tags_and_nodes (FILE_BUFFER *file_buffer) { SEARCH_BINDING binding; long position; long tags_table_begin, tags_table_end; free_file_buffer_tags (file_buffer); file_buffer->flags &= ~N_HasTagsTable; /* See if there is a tags table in this info file. */ binding.buffer = file_buffer->contents; binding.start = file_buffer->filesize; binding.end = binding.start - 1000; if (binding.end < 0) binding.end = 0; binding.flags = S_FoldCase; position = find_file_section (&binding, TAGS_TABLE_END_LABEL); if (position == -1) goto no_tags_table; /* If there is a tag table, find the start of it, and grovel over it extracting tag information. */ /* Remember the end of the tags table. */ if (position == 0) goto no_tags_table; else tags_table_end = position - 1; /* Locate the start of the tags table. */ binding.start = tags_table_end; binding.end = 0; position = find_file_section (&binding, TAGS_TABLE_BEG_LABEL); if (position == -1) goto no_tags_table; /* The file contains a valid tags table. Fill the FILE_BUFFER's tags member. */ file_buffer->flags |= N_HasTagsTable; tags_table_begin = position; position += skip_node_separator (file_buffer->contents + position); position += strlen (TAGS_TABLE_BEG_LABEL); position += strspn (file_buffer->contents + position, "\r\n"); if (!looking_at_line (TAGS_TABLE_IS_INDIRECT_LABEL, file_buffer->contents + position)) { /* If this isn't an indirect tags table, just remember the nodes described locally in this tags table. */ binding.start = tags_table_begin; binding.end = tags_table_end; get_nodes_of_tags_table (file_buffer, &binding); } else { /* This is an indirect tags table. Find the indirect table preceding the tags table. */ SEARCH_BINDING indirect; indirect.start = tags_table_begin; indirect.end = 0; indirect.buffer = file_buffer->contents; indirect.flags = S_FoldCase; position = find_file_section (&indirect, INDIRECT_TABLE_LABEL); if (position == -1) /* This file is malformed. Give up. */ return; /* Skip "Indirect:" line. */ position += skip_node_separator (file_buffer->contents + position); position += strlen (INDIRECT_TABLE_LABEL); position += strspn (file_buffer->contents + position, "\r\n"); indirect.start = position; indirect.end = tags_table_begin; binding.start = tags_table_begin; binding.end = tags_table_end; get_tags_of_indirect_tags_table (file_buffer, &indirect, &binding); } return; no_tags_table: /* This file doesn't have a tag table. */ build_tag_table (file_buffer); } /* Set fields on new tag table entry. */ static void init_file_buffer_tag (FILE_BUFFER *fb, TAG *entry) { if (fb->flags & N_HasTagsTable) { entry->flags |= N_HasTagsTable; entry->filename = fb->fullpath; if (fb->flags & N_TagsIndirect) entry->flags |= N_TagsIndirect; } } /* Search through FILE_BUFFER->contents building an array of NODE *, one entry per each node present in the file. Store the tags in FILE_BUFFER->tags, and the number of allocated slots in FILE_BUFFER->tags_slots. */ static void build_tag_table (FILE_BUFFER *file_buffer) { long nodestart; size_t tags_index = 0; SEARCH_BINDING binding; binding.buffer = file_buffer->contents; binding.start = 0; binding.end = file_buffer->filesize; binding.flags = S_FoldCase; while ((nodestart = find_node_separator (&binding)) != -1) { int start; char *nodeline; TAG *entry; int anchor = 0; /* Skip past the characters just found. */ binding.start = nodestart; binding.start += skip_node_separator (binding.buffer + binding.start); /* Move to the start of the line defining the node. */ nodeline = binding.buffer + binding.start; /* Find "Node:" */ start = string_in_line (INFO_NODE_LABEL, nodeline); /* No Node:. Maybe it's a Ref:. */ if (start == -1) { start = string_in_line (INFO_REF_LABEL, nodeline); if (start != -1) anchor = 1; } /* If not there, this is not the start of a node. */ if (start == -1) continue; /* Find the start of the nodename. */ start += skip_whitespace (nodeline + start); /* Record nodename and nodestart. */ entry = info_create_tag (); read_quoted_string (nodeline + start, ",\n\r\t", 0, &entry->nodename); if (!entry->nodename || !*entry->nodename) { free (entry); continue; } entry->nodestart = nodestart; init_file_buffer_tag (file_buffer, entry); if (anchor) entry->cache.nodelen = 0; else /* Record that the length is unknown. */ entry->cache.nodelen = -1; entry->filename = file_buffer->fullpath; /* Add this tag to the array of tag structures in this FILE_BUFFER. */ add_pointer_to_array (entry, tags_index, file_buffer->tags, file_buffer->tags_slots, 100); } } /* Build and save the array of nodes in FILE_BUFFER by searching through the contents of BUFFER_BINDING for a tags table, and groveling the contents. */ static void get_nodes_of_tags_table (FILE_BUFFER *file_buffer, SEARCH_BINDING *buffer_binding) { int name_offset; SEARCH_BINDING s; long position; size_t tags_index = 0; /* Copy buffer_binding */ s = *buffer_binding; /* Find the start of the tags table. */ position = buffer_binding->start; /* If none, we're all done. */ if (position == -1) return; /* Move to one character before the start of the actual table. */ s.start = position; s.start += skip_node_separator (s.buffer + s.start); s.start += strlen (TAGS_TABLE_BEG_LABEL); /* The tag table consists of lines containing node names and positions. Do each line until we find one that doesn't contain a node name. */ while (search_forward ("\n", &s, &position) == search_success) { TAG *entry; char *nodedef; unsigned p; int anchor = 0; /* Prepare to skip this line. */ s.start = position; s.start++; /* Skip past informative "(Indirect)" tags table line. */ if (!tags_index && looking_at (TAGS_TABLE_IS_INDIRECT_LABEL, &s)) continue; /* Find the label preceding the node name. */ name_offset = string_in_line (INFO_NODE_LABEL, s.buffer + s.start); /* If no node label, maybe it's an anchor. */ if (name_offset == -1) { name_offset = string_in_line (INFO_REF_LABEL, s.buffer + s.start); if (name_offset != -1) anchor = 1; } /* If not there, not a defining line, so we must be out of the tags table. */ if (name_offset == -1) break; entry = info_create_tag (); init_file_buffer_tag (file_buffer, entry); /* Find the beginning of the node definition. */ s.start += name_offset; nodedef = s.buffer + s.start; nodedef += skip_whitespace (nodedef); /* Move past the node's name in this tag to the TAGSEP character. */ for (p = 0; nodedef[p] && nodedef[p] != INFO_TAGSEP; p++) ; if (nodedef[p] != INFO_TAGSEP) { free (entry); continue; } entry->nodename = xmalloc (p + 1); strncpy (entry->nodename, nodedef, p); entry->nodename[p] = 0; p++; entry->nodestart = atol (nodedef + p); /* If a node, we don't know the length yet, but if it's an anchor, the length is 0. */ entry->cache.nodelen = anchor ? 0 : -1; /* The filename of this node is currently known as the same as the name of this file. */ entry->filename = file_buffer->fullpath; /* Add this node structure to the array of node structures in this FILE_BUFFER. */ add_pointer_to_array (entry, tags_index, file_buffer->tags, file_buffer->tags_slots, 100); } } /* Remember in FILE_BUFFER the nodenames, subfilenames, and offsets within the subfiles of every node which appears in the tags table at TAGS_BINDING. The indirect files list is at INDIRECT_BINDING. */ static void get_tags_of_indirect_tags_table (FILE_BUFFER *file_buffer, SEARCH_BINDING *indirect_binding, SEARCH_BINDING *tags_binding) { int i; /* A structure used only in `get_tags_of_indirect_tags_table' to hold onto an intermediate value. */ typedef struct { char *filename; long first_byte; } SUBFILE; SUBFILE **subfiles = NULL; size_t subfiles_index = 0, subfiles_slots = 0; TAG *entry; /* Remember that tags table was indirect. */ file_buffer->flags |= N_TagsIndirect; /* First get the list of tags from the tags table. Then lookup the associated file in the indirect list for each tag, and update it. */ get_nodes_of_tags_table (file_buffer, tags_binding); if (!file_buffer->tags) return; /* We have the list of tags in file_buffer->tags. Get the list of subfiles from the indirect table. */ { char *start, *end, *line; SUBFILE *subfile; start = indirect_binding->buffer + indirect_binding->start; end = indirect_binding->buffer + indirect_binding->end; line = start; while (line < end) { int colon; colon = string_in_line (":", line); if (colon == -1) break; subfile = xmalloc (sizeof (SUBFILE)); subfile->filename = xmalloc (colon); strncpy (subfile->filename, line, colon - 1); subfile->filename[colon - 1] = 0; subfile->first_byte = (long) atol (line + colon); add_pointer_to_array (subfile, subfiles_index, subfiles, subfiles_slots, 10); while (*line++ != '\n'); } } /* If we have successfully built the indirect files table, then merge the information in the two tables. */ if (!subfiles) { free_file_buffer_tags (file_buffer); return; } { int tags_index; long header_length; SEARCH_BINDING binding; char *containing_dir; char *temp; int len_containing_dir; /* Find the length of the header of the file containing the indirect tags table. This header appears at the start of every file. We want the absolute position of each node within each subfile, so we subtract the start of the containing subfile from the logical position of the node, and then add the length of the header in. */ binding.buffer = file_buffer->contents; binding.start = 0; binding.end = file_buffer->filesize; binding.flags = S_FoldCase; header_length = find_node_separator (&binding); if (header_length == -1) header_length = 0; /* Build the file buffer's list of subfiles. */ containing_dir = xstrdup (file_buffer->fullpath); temp = filename_non_directory (containing_dir); if (temp > containing_dir) { if (HAVE_DRIVE (file_buffer->fullpath) && temp == containing_dir + 2) { /* Avoid converting "d:foo" into "d:/foo" below. */ *temp = '.'; temp += 2; } temp[-1] = 0; } len_containing_dir = strlen (containing_dir); for (i = 0; subfiles[i]; i++); file_buffer->subfiles = xmalloc ((1 + i) * sizeof (char *)); for (i = 0; subfiles[i]; i++) { char *fullpath; fullpath = xmalloc (2 + strlen (subfiles[i]->filename) + len_containing_dir); sprintf (fullpath, "%s/%s", containing_dir, subfiles[i]->filename); file_buffer->subfiles[i] = fullpath; } file_buffer->subfiles[i] = NULL; free (containing_dir); /* For each node in the file's tags table, remember the starting position. */ for (tags_index = 0; (entry = file_buffer->tags[tags_index]); tags_index++) { for (i = 0; subfiles[i] && entry->nodestart >= subfiles[i]->first_byte; i++); /* If the Info file containing the indirect tags table is malformed, then give up. */ if (!i) { /* The Info file containing the indirect tags table is malformed. Give up. */ for (i = 0; subfiles[i]; i++) { free (subfiles[i]->filename); free (subfiles[i]); free (file_buffer->subfiles[i]); } free (file_buffer->subfiles); file_buffer->subfiles = NULL; free_file_buffer_tags (file_buffer); free (subfiles); return; } /* SUBFILES[i] is the index of the first subfile whose logical first byte is greater than the logical offset of this node's starting position. This means that the subfile directly preceding this one is the one containing the node. */ entry->filename = file_buffer->subfiles[i - 1]; entry->nodestart -= subfiles[i - 1]->first_byte; entry->nodestart += header_length; } } /* Free the structures assigned to SUBFILES. Free the names as well as the structures themselves, then finally, the array. */ for (i = 0; subfiles[i]; i++) { free (subfiles[i]->filename); free (subfiles[i]); } free (subfiles); } /* Free the tags (if any) associated with FILE_BUFFER. */ static void free_file_buffer_tags (FILE_BUFFER *file_buffer) { int i; if (file_buffer->tags) { TAG *tag; for (i = 0; (tag = file_buffer->tags[i]); i++) free_info_tag (tag); free (file_buffer->tags); file_buffer->tags = NULL; file_buffer->tags_slots = 0; } if (file_buffer->subfiles) { for (i = 0; file_buffer->subfiles[i]; i++) free (file_buffer->subfiles[i]); free (file_buffer->subfiles); file_buffer->subfiles = NULL; } } /* Free the data associated with TAG, as well as TAG itself. */ static void free_info_tag (TAG *tag) { free (tag->nodename); /* We don't free tag->filename, because that filename is part of the subfiles list for the containing FILE_BUFFER. free_info_tags () will free the subfiles when it is appropriate. */ free (tag); } /* Functions for retrieving files. */ static FILE_BUFFER *info_load_file (char *fullpath, int get_tags); static void get_file_character_encoding (FILE_BUFFER *fb); static void forget_info_file (FILE_BUFFER *file_buffer); static void info_reload_file_buffer_contents (FILE_BUFFER *fb); /* Try to find a file in our list of already loaded files. */ FILE_BUFFER * check_loaded_file (char *filename) { int is_fullpath, i; FILE_BUFFER *file_buffer; /* If full path to the file has been given, we must find it exactly. */ is_fullpath = IS_ABSOLUTE (filename) || filename[0] == '.' && IS_SLASH(filename[1]); if (info_loaded_files) { for (i = 0; (file_buffer = info_loaded_files[i]); i++) if ( (FILENAME_CMP (filename, file_buffer->fullpath) == 0) || (!is_fullpath && (FILENAME_CMP (filename, file_buffer->filename) == 0))) { struct stat new_info, *old_info; old_info = &file_buffer->finfo; if ( stat (file_buffer->fullpath, &new_info) == -1 || new_info.st_size != old_info->st_size || new_info.st_mtime != old_info->st_mtime) { /* The file has changed. Forget that we ever had loaded it in the first place. */ forget_info_file (file_buffer); break; } /* The info file exists, and has not changed since the last time it was loaded. If the caller requested a nodes list for this file, and there isn't one here, build the nodes for this file_buffer. In any case, return the file_buffer object. */ if (!file_buffer->contents) { /* The file's contents have been gc'ed. Reload it. */ info_reload_file_buffer_contents (file_buffer); if (!file_buffer->contents) return NULL; } if (!file_buffer->tags) build_tags_and_nodes (file_buffer); return file_buffer; } } return 0; } /* Locate the file named by FILENAME, and return the information structure describing this file. The file may appear in our list of loaded files already, or it may not. If it does not already appear, find the file, and add it to the list of loaded files. If the file cannot be found, return a NULL FILE_BUFFER *. */ FILE_BUFFER * info_find_file (char *filename) { FILE_BUFFER *file_buffer; char *fullpath; int is_fullpath; file_buffer = check_loaded_file (filename); if (file_buffer) return file_buffer; /* The file wasn't loaded. Try to load it now. */ /* Get the full pathname of this file, as known by the info system. That is to say, search along INFOPATH and expand tildes, etc. */ is_fullpath = IS_ABSOLUTE (filename) || filename[0] == '.' && IS_SLASH(filename[1]); if (!is_fullpath) fullpath = info_find_fullpath (filename, 0); else fullpath = xstrdup (filename); /* If the file wasn't found, give up, returning a NULL pointer. */ if (!fullpath) return NULL; file_buffer = info_load_file (fullpath, 0); free (fullpath); return file_buffer; } /* Find a subfile of a split file. This differs from info_load_file in that it does not fill in a tag table for the file. */ FILE_BUFFER * info_find_subfile (char *fullpath) { char *with_extension = 0; int i; FILE_BUFFER *file_buffer = 0; int fullpath_len = strlen (fullpath); /* First try to find the file in our list of already loaded files. */ if (info_loaded_files) { for (i = 0; (file_buffer = info_loaded_files[i]); i++) /* Check if fullpath starts the name of the recorded file (extra extensions like ".info.gz" could be added.) */ if (!strncmp (file_buffer->fullpath, fullpath, fullpath_len) && (file_buffer->fullpath[fullpath_len] == '\0' || file_buffer->fullpath[fullpath_len] == '.')) { struct stat new_info, *old_info; old_info = &file_buffer->finfo; if ( stat (file_buffer->fullpath, &new_info) == -1 || new_info.st_size != old_info->st_size || new_info.st_mtime != old_info->st_mtime) { /* The file has changed. Forget that we ever had loaded it in the first place. */ forget_info_file (file_buffer); break; } return file_buffer; } } /* The file wasn't loaded. Try to load it now. */ with_extension = info_find_fullpath (fullpath, 0); if (with_extension) { file_buffer = info_load_file (with_extension, 1); free (with_extension); } return file_buffer; } /* Load the file with path FULLPATH, and return the information structure describing this file, even if the file was already loaded. IS_SUBFILE says whether this file is the subfile of a split file. If it is, mark the FILE_BUFFER object as such and do not build a list of nodes for this file. */ static FILE_BUFFER * info_load_file (char *fullpath, int is_subfile) { char *contents; size_t filesize; struct stat finfo; int compressed; FILE_BUFFER *file_buffer = NULL; contents = filesys_read_info_file (fullpath, &filesize, &finfo, &compressed); if (!contents) return NULL; /* The file was found, and can be read. Allocate FILE_BUFFER and fill in the various members. */ file_buffer = make_file_buffer (); file_buffer->fullpath = xstrdup (fullpath); file_buffer->filename = filename_non_directory (file_buffer->fullpath); file_buffer->filename = xstrdup (file_buffer->filename); /* Strip off a file extension, so we can find it again in info_find_file. */ { char *p = strchr (file_buffer->filename, '.'); if (p) *p = '\0'; } file_buffer->finfo = finfo; file_buffer->filesize = filesize; file_buffer->contents = contents; if (compressed) file_buffer->flags |= N_IsCompressed; /* Find encoding of file, if set */ get_file_character_encoding (file_buffer); if (!is_subfile) { build_tags_and_nodes (file_buffer); if (!file_buffer->tags) { free (file_buffer->fullpath); free (file_buffer->filename); free (file_buffer->contents); free (file_buffer->encoding); free (file_buffer); return 0; } } else file_buffer->flags |= N_Subfile; /* If the file was loaded, remember the name under which it was found. */ if (file_buffer) add_pointer_to_array (file_buffer, info_loaded_files_index, info_loaded_files, info_loaded_files_slots, 10); return file_buffer; } /* Look for local variables section in FB and set encoding */ static void get_file_character_encoding (FILE_BUFFER *fb) { SEARCH_BINDING binding; long position; long int enc_start, enc_len; char *enc_string; /* See if there is a local variables section in this info file. */ binding.buffer = fb->contents; binding.start = fb->filesize; binding.end = binding.start - 1000; if (binding.end < 0) binding.end = 0; binding.flags = S_FoldCase; /* Null means the encoding is unknown. */ fb->encoding = 0; if (search_backward (LOCAL_VARIABLES_LABEL, &binding, &position) != search_success) return; binding.start = position; binding.end = fb->filesize; if (search_forward (CHARACTER_ENCODING_LABEL, &binding, &enc_start) != search_success) return; enc_start += strlen(CHARACTER_ENCODING_LABEL); /* Skip to after "coding:" */ enc_start += skip_whitespace(fb->contents + enc_start); enc_len = strcspn (fb->contents + enc_start, "\r\n"); enc_string = xmalloc (enc_len + 1); strncpy (enc_string, fb->contents + enc_start, enc_len); enc_string[enc_len] = '\0'; fb->encoding = enc_string; } /* Create a new, empty file buffer. */ FILE_BUFFER * make_file_buffer (void) { FILE_BUFFER *file_buffer = xmalloc (sizeof (FILE_BUFFER)); file_buffer->filename = file_buffer->fullpath = NULL; file_buffer->contents = NULL; file_buffer->tags = NULL; file_buffer->subfiles = NULL; file_buffer->tags_slots = 0; file_buffer->flags = 0; file_buffer->encoding = 0; return file_buffer; } /* Prevent this file buffer being used again. */ static void forget_info_file (FILE_BUFFER *file_buffer) { file_buffer->flags |= N_Gone; file_buffer->filename[0] = '\0'; file_buffer->fullpath = ""; memset (&file_buffer->finfo, 0, sizeof (struct stat)); } /* Load the contents of FILE_BUFFER->contents. This function is called when a file buffer was loaded, and then in order to conserve memory, the file buffer's contents were freed and the pointer was zero'ed. Note that the file was already loaded at least once successfully, so the tags and/or nodes members are still correctly filled. */ static void info_reload_file_buffer_contents (FILE_BUFFER *fb) { int is_compressed; fb->flags &= ~N_IsCompressed; /* Let the filesystem do all the work for us. */ fb->contents = filesys_read_info_file (fb->fullpath, &(fb->filesize), &(fb->finfo), &is_compressed); if (is_compressed) fb->flags |= N_IsCompressed; } /* Functions for node creation and retrieval. */ static long get_node_length (SEARCH_BINDING *binding); static void node_set_body_start (NODE *node); static int adjust_nodestart (FILE_BUFFER *file_buffer, TAG *tag); /* Return a pointer to a newly allocated TAG structure, with fields filled in. */ TAG * info_create_tag (void) { TAG *t = xmalloc (sizeof (TAG)); memset (t, 0, sizeof (TAG)); t->filename = 0; t->nodename = 0; t->nodestart = -1; t->nodestart_adjusted = -1; t->cache.nodelen = -1; return t; } /* Return a pointer to a newly allocated NODE structure, with fields filled in. */ NODE * info_create_node (void) { NODE *n = xmalloc (sizeof (NODE)); n->fullpath = 0; n->subfile = 0; n->nodename = 0; n->contents = 0; n->nodelen = -1; n->display_pos = 0; n->body_start = 0; n->flags = 0; n->references = 0; n->up = 0; n->prev = 0; n->next = 0; return n; } /* Return the length of the node which starts at BINDING. */ static long get_node_length (SEARCH_BINDING *binding) { int i; char *body; /* [A node] ends with either a ^_, a ^L, or end of file. */ for (i = binding->start, body = binding->buffer; i < binding->end; i++) { if (body[i] == INFO_FF || body[i] == INFO_COOKIE) break; } return i - binding->start; } #define FOLLOW_REMAIN 0 #define FOLLOW_PATH 1 int follow_strategy; /* Return a pointer to a NODE structure for the Info node (FILENAME)NODENAME, using DEFAULTS for defaults. If DEFAULTS is null, the defaults are: - If FILENAME is NULL, `dir' is used. - If NODENAME is NULL, `Top' is used. If the node cannot be found, return NULL. */ NODE * info_get_node_with_defaults (char *filename_in, char *nodename_in, NODE *defaults) { NODE *node = 0; FILE_BUFFER *file_buffer = NULL; char *filename = 0, *nodename = 0; info_recent_file_error = NULL; filename = filename_in; if (filename_in) { filename = xstrdup (filename_in); if (follow_strategy == FOLLOW_REMAIN && defaults && defaults->fullpath && filename_in) { /* Find the directory in the filename for defaults, and look in that directory first. */ char *file_in_same_dir; char saved_char, *p; p = defaults->fullpath + strlen (defaults->fullpath); while (p > defaults->fullpath && !IS_SLASH (*p)) p--; if (p > defaults->fullpath) { saved_char = *p; *p = 0; file_in_same_dir = info_add_extension (defaults->fullpath, filename, 0); *p = saved_char; if (file_in_same_dir) file_buffer = info_find_file (file_in_same_dir); free (file_in_same_dir); } } } else { if (defaults) filename = xstrdup (defaults->fullpath); else filename = xstrdup ("dir"); } if (nodename_in && *nodename_in) nodename = xstrdup (nodename_in); else /* If NODENAME is not specified, it defaults to "Top". */ nodename = xstrdup ("Top"); /* If the file to be looked up is "dir", build the contents from all of the "dir"s and "localdir"s found in INFOPATH. */ if (is_dir_name (filename)) { node = get_dir_node (); goto cleanup_and_exit; } if (mbscasecmp (filename, MANPAGE_FILE_BUFFER_NAME) == 0) { node = get_manpage_node (nodename); goto cleanup_and_exit; } if (!file_buffer) file_buffer = info_find_file (filename); if (file_buffer) { /* Look for the node. */ node = info_get_node_of_file_buffer (file_buffer, nodename); } /* If the node not found was "Top", try again with different case. */ if (!node && (nodename && mbscasecmp (nodename, "Top") == 0)) { node = info_get_node_of_file_buffer (file_buffer, "Top"); if (!node) node = info_get_node_of_file_buffer (file_buffer, "top"); if (!node) node = info_get_node_of_file_buffer (file_buffer, "TOP"); } cleanup_and_exit: free (filename); free (nodename); return node; } /* Return NODE specified with FILENAME_IN and NODENAME_IN. Return value should be freed by caller, but none of its fields should be. */ NODE * info_get_node (char *filename_in, char *nodename_in) { return info_get_node_with_defaults (filename_in, nodename_in, 0); } static void node_set_body_start (NODE *node) { int n = skip_node_separator (node->contents); node->body_start = strcspn(node->contents + n, "\n"); node->body_start += n; } /* Return a pointer to a NODE structure for the Info node NODENAME in FILE_BUFFER. NODENAME can be passed as NULL, in which case the nodename of "Top" is used. If the node cannot be found, return a NULL pointer. Return value should be freed by caller, but none of its fields should be. */ NODE * info_get_node_of_file_buffer (FILE_BUFFER *file_buffer, char *nodename) { NODE *node = NULL; /* If we are unable to find the file, we have to give up. There isn't anything else we can do. */ if (!file_buffer) return NULL; /* If the file buffer was gc'ed, reload the contents now. */ if (!file_buffer->contents) info_reload_file_buffer_contents (file_buffer); /* If the name of the node that we wish to find is exactly "*", then the node body is the contents of the entire file. Create and return such a node. */ if (strcmp (nodename, "*") == 0) { node = info_create_node (); node->fullpath = file_buffer->fullpath; node->nodename = xstrdup ("*"); node->contents = file_buffer->contents; node->nodelen = file_buffer->filesize; node->body_start = 0; } /* Search the tags table for an entry which matches the node that we want. */ else { TAG *tag; int i; /* If no tags at all (possibly a misformatted info file), quit. */ if (!file_buffer->tags) return NULL; for (i = 0; (tag = file_buffer->tags[i]); i++) if (strcmp (nodename, tag->nodename) == 0) { node = info_node_of_tag (file_buffer, &file_buffer->tags[i]); break; } } /* Return the results of our node search. */ return node; } /* Magic number that RMS used to decide how much a tags table pointer could be off by. I feel that it should be much smaller, like 4. */ #define DEFAULT_INFO_FUDGE 1000 /* Find the actual starting memory location of NODE. Because of the way that tags are implemented, the physical nodestart may not actually be where the tag says it is. If that is the case, set N_UpdateTags in NODE->flags. If the node is found, return non-zero. Set NODE->nodestart_adjusted directly on the separator that precedes this node. If the node could not be found, return 0. */ static int adjust_nodestart (FILE_BUFFER *fb, TAG *node) { long position = -1; SEARCH_BINDING s; /* Try the optimal case first. Who knows? This file may actually be formatted (mostly) correctly. */ s.buffer = fb->contents; s.start = node->nodestart; s.end = s.start + 1; /* Check that the given nodestart is in fact inside the file buffer. */ if (s.start >= 0 && s.start < fb->filesize) { /* Check for node separator at node->nodestart introducting this node. */ position = find_node_in_binding (node->nodename, &s); } if (position == -1) { if (strict_node_location_p) return 0; /* Oh well, I guess we have to try to find it in a larger area. */ s.start -= DEFAULT_INFO_FUDGE; s.end += DEFAULT_INFO_FUDGE; if (s.start < 0) s.start = 0; else if (s.start > fb->filesize) s.start = fb->filesize; if (s.end > fb->filesize) s.end = fb->filesize; position = find_node_in_binding (node->nodename, &s); /* If the node still couldn't be found, we lose big. */ if (position == -1) return 0; /* Set the flag in NODE->flags to say that the the tags table could need updating (if we used a tag to get here, that is). */ if (node->flags & N_HasTagsTable) node->flags |= N_UpdateTags; } node->nodestart_adjusted = s.buffer + position - fb->contents; return 1; } /* Look in the contents of *FB_PTR for a node referred to with TAG. Set the location if found in TAG->nodestart_adjusted. PARENT->tags contains the tags table for the whole file. If file is non-split, PARENT should be the same as FB. */ static int find_node_from_tag (FILE_BUFFER *parent, FILE_BUFFER *fb, TAG *tag) { int success; if (tag->nodestart_adjusted != -1) success = 1; else success = adjust_nodestart (fb, tag); if (success) return success; return 0; } /* Calculate the length of the node. */ static void set_tag_nodelen (FILE_BUFFER *subfile, TAG *tag) { SEARCH_BINDING node_body; node_body.buffer = subfile->contents; node_body.start = tag->nodestart_adjusted; node_body.end = subfile->filesize; node_body.flags = 0; node_body.start += skip_node_separator (node_body.buffer + node_body.start); tag->cache.nodelen = get_node_length (&node_body); } /* Return the node described by *TAG_PTR, retrieving contents from subfile if the file is split. Return 0 on failure. If FAST, don't process the node to find cross-references, a menu, or perform character encoding conversion. */ static NODE * info_node_of_tag_ext (FILE_BUFFER *fb, TAG **tag_ptr, int fast) { TAG *tag = *tag_ptr; NODE *node; int is_anchor; TAG *anchor_tag; int node_pos, anchor_pos; FILE_BUFFER *parent; /* File containing tag table. */ FILE_BUFFER *subfile; /* File containing node. */ if (!FILENAME_CMP (fb->fullpath, tag->filename)) parent = subfile = fb; else { /* This is a split file. */ parent = fb; subfile = info_find_subfile (tag->filename); } if (!subfile) return NULL; if (!subfile->contents) { info_reload_file_buffer_contents (subfile); if (!subfile->contents) return NULL; } /* If we were able to find this file and load it, then return the node within it. */ if (!(tag->nodestart >= 0 && tag->nodestart < subfile->filesize)) return NULL; node = 0; is_anchor = tag->cache.nodelen == 0; if (is_anchor) { anchor_pos = tag_ptr - fb->tags; /* Look backwards in the tag table for the node preceding the anchor (we're assuming the tags are given in order), skipping over any preceding anchors. */ for (node_pos = anchor_pos - 1; node_pos >= 0 && fb->tags[node_pos]->cache.nodelen == 0; node_pos--) ; /* An info file with an anchor before any nodes is pathological, but it's possible, so don't crash. */ if (node_pos < 0) return NULL; anchor_tag = tag; tag = fb->tags[node_pos]; tag_ptr = &fb->tags[node_pos]; } /* We haven't checked the entry pointer yet. Look for the node around about it and adjust it if necessary. */ if (tag->cache.nodelen == -1) { if (!find_node_from_tag (parent, subfile, tag)) return NULL; /* Node not found. */ set_tag_nodelen (subfile, tag); } node = xmalloc (sizeof (NODE)); memset (node, 0, sizeof (NODE)); if (tag->cache.references) { /* Initialize the node from the cache. */ *node = tag->cache; if (!node->contents) { node->contents = subfile->contents + tag->nodestart_adjusted; node->contents += skip_node_separator (node->contents); } } else { /* Data for node has not been generated yet. */ node->contents = subfile->contents + tag->nodestart_adjusted; node->contents += skip_node_separator (node->contents); node->nodelen = tag->cache.nodelen; node->nodename = tag->nodename; node->flags = tag->flags; node->fullpath = parent->fullpath; if (parent != subfile) node->subfile = tag->filename; if (fast) node->flags |= N_Simple; else { /* Read locations of references in node and similar. Strip Info file syntax from node if preprocess_nodes=On. Adjust the offsets of anchors that occur within the node. */ scan_node_contents (node, parent, tag_ptr); node_set_body_start (node); tag->cache = *node; if (!(node->flags & N_WasRewritten)) tag->cache.contents = 0; /* Pointer into file buffer is not saved. */ } } /* We can't set this when tag table is built, because if file is split, we don't know which of the sub-files are compressed. */ if (subfile->flags & N_IsCompressed) node->flags |= N_IsCompressed; if (is_anchor) { /* Start displaying the node at the anchor position. */ node->display_pos = anchor_tag->nodestart_adjusted - (tag->nodestart_adjusted + skip_node_separator (subfile->contents + tag->nodestart_adjusted)); /* Otherwise an anchor at the end of a node ends up displaying at the end of the last line of the node (way over on the right of the screen), which looks wrong. */ if (node->display_pos >= (unsigned long) node->nodelen) node->display_pos = node->nodelen - 1; else if (node->display_pos < 0) node->display_pos = 0; /* Shouldn't happen. */ } return node; } NODE * info_node_of_tag (FILE_BUFFER *fb, TAG **tag_ptr) { return info_node_of_tag_ext (fb, tag_ptr, 0); } NODE * info_node_of_tag_fast (FILE_BUFFER *fb, TAG **tag_ptr) { return info_node_of_tag_ext (fb, tag_ptr, 1); }