1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498 |
- /* nodes.c -- how to get an Info file and node.
- $Id$
- Copyright 1993, 1998, 1999, 2000, 2002, 2003, 2004, 2006, 2007,
- 2008, 2009, 2011, 2012, 2013, 2014, 2015, 2016, 2017 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 <http://www.gnu.org/licenses/>.
- Originally written by Brian Fox. */
- #include "info.h"
- #include "nodes.h"
- #include "search.h"
- #include "filesys.h"
- #include "info-utils.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 += 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)
- 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;
- }
- else
- {
- int tags_index;
- long header_length;
- SEARCH_BINDING binding;
- /* 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. */
- {
- char *containing_dir = xstrdup (file_buffer->fullpath);
- char *temp = filename_non_directory (containing_dir);
- int len_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]);
- }
- file_buffer->subfiles = NULL;
- free_file_buffer_tags (file_buffer);
- 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);
- /* FIXME: Put the following in info_find_fullpath, or remove
- it altogether. */
- /* If the file referenced by the name returned from info_find_fullpath ()
- doesn't exist, then try again with the last part of the filename
- appearing in lowercase. */
- /* This is probably not needed at all on those systems which define
- FILENAME_CMP to be mbscasecmp. But let's do it anyway, lest some
- network redirector supports case sensitivity. */
- if (!fullpath)
- {
- char *lowered_name;
- char *tmp_basename;
- lowered_name = xstrdup (filename);
- tmp_basename = filename_non_directory (lowered_name);
- while (*tmp_basename)
- {
- if (isupper (*tmp_basename))
- *tmp_basename = tolower (*tmp_basename);
- tmp_basename++;
- }
- fullpath = info_find_fullpath (lowered_name, 0);
- free (lowered_name);
- }
- /* 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;
- /* 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, strlen (fullpath)))
- {
- 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);
- 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 get_filename_and_nodename (NODE *node,
- char **filename, char **nodename,
- char *filename_in, char *nodename_in);
- static void node_set_body_start (NODE *node);
- /* 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;
- get_filename_and_nodename (defaults, &filename, &nodename,
- filename_in, nodename_in);
- /* 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 (follow_strategy == FOLLOW_REMAIN
- && defaults && defaults->fullpath)
- {
- /* 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);
- if (file_in_same_dir)
- file_buffer = info_find_file (file_in_same_dir);
- free (file_in_same_dir);
- *p = saved_char;
- }
- }
- 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 (!file_buffer)
- {
- /* Try to find a man page with this name as a fall back. */
- node = get_manpage_node (filename);
- if (!node)
- {
- if (filesys_error_number)
- info_recent_file_error =
- filesys_error_string (filename, filesys_error_number);
- }
- }
- /* 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);
- }
- /* Get filename and nodename of node to load using defaults from NODE.
- Output values should be freed by caller. */
- static void
- get_filename_and_nodename (NODE *node,
- char **filename, char **nodename,
- char *filename_in, char *nodename_in)
- {
- *filename = filename_in;
- /* If FILENAME is not specified, it defaults to "dir". */
- if (filename_in)
- *filename = xstrdup (filename_in);
- else
- {
- if (node)
- *filename = xstrdup (node->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");
- }
- 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;
- }
- /* Convert any CRLF pairs in the SOURCE file and place the converted buffer in
- DESTINATION. DESTINATION->contents must be allocated on the heap and at
- least as big as SOURCE->contents, including a terminating null. DESTINATION
- is allowed to be the same as SOURCE to convert in place. */
- void
- convert_eols (FILE_BUFFER *destination, FILE_BUFFER *source)
- {
- register char *d = destination->contents;
- register char *s = source->contents;
- long textlen = source->filesize;
- while (textlen--)
- {
- if (*s == '\r' && textlen && s[1] == '\n')
- {
- s++;
- textlen--;
- }
- *d++ = *s++;
- }
- *d = '\0';
- destination->filesize = d - destination->contents;
- /* EOL conversion can shrink the text quite a bit. We don't
- want to waste storage. */
- destination->contents = xrealloc (destination->contents,
- d - destination->contents + 1);
- }
- /* 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, int slack)
- {
- 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 -= slack;
- s.end += slack;
- 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;
- int slack;
- TAG **t;
- WINDOW *w;
- /* Start off with a small fudge to reduce chance of finding a node and then
- later having to convert the EOL's, leaving us with the question of what to
- do with the existing buffer and the nodes that refer to it. */
- if (!(fb->flags & N_EOLs_Converted))
- slack = 4;
- else
- slack = DEFAULT_INFO_FUDGE;
- if (tag->nodestart_adjusted != -1)
- success = 1;
- else
- success = adjust_nodestart (fb, tag, slack);
- if (success)
- return success;
- if (fb->flags & N_EOLs_Converted || strict_node_location_p)
- return 0;
- /* Convert EOL's. If the Info file was produced under MS-Windows with
- some versions of makeinfo, it's possible that it has CR-LF line endings
- with the CR bytes not counted in the tag table. */
- convert_eols (fb, fb);
- fb->flags |= N_EOLs_Converted;
- /* Restore tags table to what was read from the file. */
- for (t = parent->tags; *t; t++)
- {
- /* For split files, only restore the part of the tag table for
- the subfile. */
- if (!FILENAME_CMP ((*t)->filename, fb->fullpath))
- {
- NODE *n = &(*t)->cache;
- int is_anchor = n->nodelen == 0;
- (*t)->nodestart_adjusted = -1;
- if (n->flags & N_WasRewritten)
- free (n->contents);
- info_free_references (n->references);
- free (n->next); free (n->prev); free (n->up);
- memset (n, 0, sizeof (NODE));
- if (!is_anchor)
- n->nodelen = -1;
- }
- }
- /* Look for the node again. */
- success = adjust_nodestart (fb, tag, DEFAULT_INFO_FUDGE);
- /* For each window, check for file buffer being used in window history,
- including currently displayed node, and amend it to refer properly to the
- converted file buffer. (Window history was set in info_set_node_of_window
- in session.c. )
- There is a chance that there is a NODE in some local variable
- somewhere, which we can't update. */
- for (w = windows; w; w = w->next)
- {
- WINDOW_STATE **h;
- if (!w->hist)
- continue;
- w->node = 0;
- for (h = w->hist; *h; h++)
- {
- NODE *n = (*h)->node;;
- if (!(n->flags & N_IsInternal)
- && (n->subfile ? (!FILENAME_CMP (n->subfile, fb->fullpath))
- : (!FILENAME_CMP (n->fullpath, fb->fullpath))))
- {
- /* The call to info_get_node is indirectly recursive, but it
- should not recurse twice because of the N_EOLs_Converted
- conditional above. */
- (*h)->node = info_get_node (n->fullpath, n->nodename);
- if ((*h)->node)
- {
- (*h)->node->active_menu = n->active_menu;
- free_history_node (n);
- }
- else
- {
- /* We found the node before, but now we can't. Just leave
- the node as it was (possibly with its contents pointer
- pointing to the wrong place). */
- (*h)->node = n;
- }
- }
- }
- if (h > w->hist)
- w->node = (*(h - 1))->node;
- }
- 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);
- if (!preprocess_nodes_p)
- 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);
- }
|