#include "info.h"
#include "nodes.h"
#include "search.h"
#include "filesys.h"
#include "info-utils.h"
#if defined (HANDLE_MAN_PAGES)
# include "man.h"
#endif
static void forget_info_file (), remember_info_file ();
static void free_file_buffer_tags (), free_info_tag ();
static void get_nodes_of_tags_table (), get_nodes_of_info_file ();
static void get_tags_of_indirect_tags_table ();
static void info_reload_file_buffer_contents ();
static char *adjust_nodestart ();
static FILE_BUFFER *info_load_file_internal (), *info_find_file_internal ();
static NODE *info_node_of_file_buffer_tags ();
static long get_node_length ();
#define DEFAULT_INFO_FUDGE 1000
#define INFO_NO_TAGS 0
#define INFO_GET_TAGS 1
char *info_recent_file_error = (char *)NULL;
FILE_BUFFER **info_loaded_files = (FILE_BUFFER **)NULL;
int info_loaded_files_slots = 0;
extern void maybe_build_dir_node ();
NODE *
info_get_node (filename, nodename)
char *filename, *nodename;
{
FILE_BUFFER *file_buffer;
NODE *node;
file_buffer = (FILE_BUFFER *)NULL;
info_recent_file_error = (char *)NULL;
info_parse_node (nodename, DONT_SKIP_NEWLINES);
nodename = (char *)NULL;
if (info_parsed_filename)
filename = info_parsed_filename;
if (info_parsed_nodename)
nodename = info_parsed_nodename;
if (!filename)
filename = "dir";
if (strcasecmp (filename, "dir") == 0)
maybe_build_dir_node (filename);
file_buffer = info_find_file (filename);
if (!file_buffer)
{
if (filesys_error_number)
info_recent_file_error =
filesys_error_string (filename, filesys_error_number);
return ((NODE *)NULL);
}
node = info_get_node_of_file_buffer (nodename, file_buffer);
if (!node && (nodename == NULL || strcasecmp (nodename, "Top") == 0))
{
node = info_get_node_of_file_buffer ("Top", file_buffer);
if (!node)
node = info_get_node_of_file_buffer ("top", file_buffer);
if (!node)
node = info_get_node_of_file_buffer ("TOP", file_buffer);
}
return (node);
}
NODE *
info_get_node_of_file_buffer (nodename, file_buffer)
char *nodename;
FILE_BUFFER *file_buffer;
{
NODE *node = (NODE *)NULL;
if (!file_buffer)
return ((NODE *)NULL);
if (!file_buffer->contents)
info_reload_file_buffer_contents (file_buffer);
if (!nodename)
nodename = "Top";
if (strcmp (nodename, "*") == 0)
{
node = (NODE *)xmalloc (sizeof (NODE));
node->filename = file_buffer->fullpath;
node->parent = (char *)NULL;
node->nodename = xstrdup ("*");
node->contents = file_buffer->contents;
node->nodelen = file_buffer->filesize;
node->flags = 0;
}
#if defined (HANDLE_MAN_PAGES)
else if (file_buffer->flags & N_IsManPage)
{
node = get_manpage_node (file_buffer, nodename);
}
#endif
else if (file_buffer->tags)
{
node = info_node_of_file_buffer_tags (file_buffer, nodename);
}
return (node);
}
FILE_BUFFER *
info_find_file (filename)
char *filename;
{
return (info_find_file_internal (filename, INFO_GET_TAGS));
}
FILE_BUFFER *
info_load_file (filename)
char *filename;
{
return (info_load_file_internal (filename, INFO_GET_TAGS));
}
static FILE_BUFFER *
info_find_file_internal (filename, get_tags)
char *filename;
int get_tags;
{
register int i;
register FILE_BUFFER *file_buffer;
if (info_loaded_files)
{
for (i = 0; (file_buffer = info_loaded_files[i]); i++)
if ((strcmp (filename, file_buffer->filename) == 0) ||
(strcmp (filename, file_buffer->fullpath) == 0) ||
((*filename != '/') &&
strcmp (filename,
filename_non_directory (file_buffer->fullpath)) == 0))
{
struct stat new_info, *old_info;
if (strcasecmp (filename_non_directory (filename), "dir") == 0)
return (file_buffer);
#if defined (HANDLE_MAN_PAGES)
if (file_buffer->flags & N_IsManPage)
return (file_buffer);
#endif
if (stat (file_buffer->fullpath, &new_info) == -1)
{
filesys_error_number = errno;
return ((FILE_BUFFER *)NULL);
}
old_info = &file_buffer->finfo;
if ((new_info.st_size != old_info->st_size) ||
(new_info.st_mtime != old_info->st_mtime))
{
forget_info_file (filename);
break;
}
else
{
if (get_tags && !file_buffer->tags)
build_tags_and_nodes (file_buffer);
return (file_buffer);
}
}
}
#if defined (HANDLE_MAN_PAGES)
if (strcasecmp (filename, MANPAGE_FILE_BUFFER_NAME) == 0)
file_buffer = create_manpage_file_buffer ();
else
#endif
file_buffer = info_load_file_internal (filename, get_tags);
if (file_buffer)
remember_info_file (file_buffer);
return (file_buffer);
}
static FILE_BUFFER *
info_load_file_internal (filename, get_tags)
char *filename;
int get_tags;
{
char *fullpath, *contents;
long filesize;
struct stat finfo;
int retcode;
FILE_BUFFER *file_buffer = (FILE_BUFFER *)NULL;
fullpath = info_find_fullpath (filename);
retcode = stat (fullpath, &finfo);
if (retcode < 0)
{
char *lowered_name;
char *basename;
lowered_name = xstrdup (filename);
basename = (char *) strrchr (lowered_name, '/');
if (basename)
basename++;
else
basename = lowered_name;
while (*basename)
{
if (isupper (*basename))
*basename = tolower (*basename);
basename++;
}
fullpath = info_find_fullpath (lowered_name);
free (lowered_name);
retcode = stat (fullpath, &finfo);
}
if (retcode < 0)
{
filesys_error_number = errno;
return ((FILE_BUFFER *)NULL);
}
contents = filesys_read_info_file (fullpath, &filesize, &finfo);
if (!contents)
return ((FILE_BUFFER *)NULL);
file_buffer = make_file_buffer ();
file_buffer->filename = xstrdup (filename);
file_buffer->fullpath = xstrdup (fullpath);
file_buffer->finfo = finfo;
file_buffer->filesize = filesize;
file_buffer->contents = contents;
if (file_buffer->filesize != file_buffer->finfo.st_size)
file_buffer->flags |= N_IsCompressed;
if (get_tags)
build_tags_and_nodes (file_buffer);
return (file_buffer);
}
void
build_tags_and_nodes (file_buffer)
FILE_BUFFER *file_buffer;
{
SEARCH_BINDING binding;
long position;
free_file_buffer_tags (file_buffer);
file_buffer->flags &= ~N_HasTagsTable;
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 = search_backward (TAGS_TABLE_END_LABEL, &binding);
if (position != -1)
while (1)
{
long tags_table_begin, tags_table_end;
binding.end = position;
binding.start = binding.end - 5 - strlen (TAGS_TABLE_END_LABEL);
if (binding.start < 0)
binding.start = 0;
position = find_node_separator (&binding);
if (position == -1)
break;
binding.start = position;
tags_table_end = binding.start;
binding.end = 0;
position = search_backward (TAGS_TABLE_BEG_LABEL, &binding);
if (position == -1)
break;
binding.end = position;
binding.start = binding.end - 5 - strlen (TAGS_TABLE_BEG_LABEL);
position = find_node_separator (&binding);
if (position == -1)
break;
file_buffer->flags |= N_HasTagsTable;
tags_table_begin = position;
binding.start = binding.end;
binding.end = file_buffer->filesize;
if (!looking_at (TAGS_TABLE_IS_INDIRECT_LABEL, &binding))
{
binding.start = tags_table_begin;
binding.end = tags_table_end;
get_nodes_of_tags_table (file_buffer, &binding);
return;
}
else
{
SEARCH_BINDING indirect;
indirect.start = tags_table_begin;
indirect.end = 0;
indirect.buffer = binding.buffer;
indirect.flags = S_FoldCase;
position = search_backward (INDIRECT_TAGS_TABLE_LABEL, &indirect);
if (position == -1)
{
return;
}
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;
}
}
get_nodes_of_info_file (file_buffer);
}
static void
get_nodes_of_info_file (file_buffer)
FILE_BUFFER *file_buffer;
{
long nodestart;
int 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, end;
char *nodeline;
TAG *entry;
binding.start = nodestart;
binding.start += skip_node_separator (binding.buffer + binding.start);
nodeline = binding.buffer + binding.start;
start = string_in_line (INFO_NODE_LABEL, nodeline);
if (start == -1)
continue;
start += skip_whitespace (nodeline + start);
end = start +
skip_node_characters (nodeline + start, DONT_SKIP_NEWLINES);
entry = (TAG *)xmalloc (sizeof (TAG));
entry->nodename = (char *)xmalloc (1 + (end - start));
strncpy (entry->nodename, nodeline + start, end - start);
entry->nodename[end - start] = '\0';
entry->nodestart = nodestart;
{
SEARCH_BINDING node_body;
node_body.buffer = binding.buffer + binding.start;
node_body.start = 0;
node_body.end = binding.end - binding.start;
node_body.flags = S_FoldCase;
entry->nodelen = get_node_length (&node_body);
}
entry->filename = file_buffer->fullpath;
add_pointer_to_array (entry, tags_index, file_buffer->tags,
file_buffer->tags_slots, 100, TAG *);
}
}
static long
get_node_length (binding)
SEARCH_BINDING *binding;
{
register int i;
char *body;
for (i = binding->start, body = binding->buffer; i < binding->end; i++)
{
if (body[i] == INFO_FF || body[i] == INFO_COOKIE)
break;
}
return ((long) i - binding->start);
}
static void
get_nodes_of_tags_table (file_buffer, buffer_binding)
FILE_BUFFER *file_buffer;
SEARCH_BINDING *buffer_binding;
{
int offset, tags_index = 0;
SEARCH_BINDING *search;
long position;
search = copy_binding (buffer_binding);
position = find_tags_table (search);
if (position == -1)
return;
search->start = position;
search->start += skip_node_separator (search->buffer + search->start);
search->start += strlen (TAGS_TABLE_BEG_LABEL);
search->start--;
while ((position = search_forward ("\n", search)) != -1)
{
TAG *entry;
char *nodedef;
search->start = position;
search->start++;
if (!tags_index && looking_at (TAGS_TABLE_IS_INDIRECT_LABEL, search))
continue;
offset =
string_in_line (INFO_NODE_LABEL, search->buffer + search->start);
if (offset == -1)
break;
search->start += offset;
nodedef = search->buffer + search->start;
nodedef += skip_whitespace (nodedef);
for (offset = 0;
(nodedef[offset]) && (nodedef[offset] != INFO_TAGSEP);
offset++);
if (nodedef[offset] != INFO_TAGSEP)
continue;
entry = (TAG *)xmalloc (sizeof (TAG));
entry->nodename = (char *)xmalloc (1 + offset);
strncpy (entry->nodename, nodedef, offset);
entry->nodename[offset] = '\0';
offset++;
entry->nodestart = (long) atol (nodedef + offset);
entry->nodelen = -1;
entry->filename = file_buffer->fullpath;
add_pointer_to_array (entry, tags_index, file_buffer->tags,
file_buffer->tags_slots, 100, TAG *);
}
free (search);
}
typedef struct {
char *filename;
long first_byte;
} SUBFILE;
static void
get_tags_of_indirect_tags_table (file_buffer, indirect_binding, tags_binding)
FILE_BUFFER *file_buffer;
SEARCH_BINDING *indirect_binding, *tags_binding;
{
register int i;
SUBFILE **subfiles = (SUBFILE **)NULL;
int subfiles_index = 0, subfiles_slots = 0;
TAG *entry;
get_nodes_of_tags_table (file_buffer, tags_binding);
{
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 = (SUBFILE *)xmalloc (sizeof (SUBFILE));
subfile->filename = (char *)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, SUBFILE *);
while (*line++ != '\n');
}
}
if (!subfiles)
{
free_file_buffer_tags (file_buffer);
return;
}
else
{
register int tags_index;
long header_length;
SEARCH_BINDING binding;
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;
{
char *containing_dir, *temp;
int len_containing_dir;
containing_dir = xstrdup (file_buffer->fullpath);
temp = (char *) strrchr (containing_dir, '/');
if (temp)
*temp = '\0';
len_containing_dir = strlen (containing_dir);
for (i = 0; subfiles[i]; i++);
file_buffer->subfiles = (char **) xmalloc ((1 + i) * sizeof (char *));
for (i = 0; subfiles[i]; i++)
{
char *fullpath;
fullpath = (char *) 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] = (char *)NULL;
free (containing_dir);
}
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 (!i)
{
for (i = 0; subfiles[i]; i++)
{
free (subfiles[i]->filename);
free (subfiles[i]);
free (file_buffer->subfiles[i]);
}
file_buffer->subfiles = (char **)NULL;
free_file_buffer_tags (file_buffer);
return;
}
entry->filename = file_buffer->subfiles[i - 1];
entry->nodestart -= subfiles[i -1]->first_byte;
entry->nodestart += header_length;
entry->nodelen = -1;
}
file_buffer->flags |= N_TagsIndirect;
}
for (i = 0; subfiles[i]; i++)
{
free (subfiles[i]->filename);
free (subfiles[i]);
}
free (subfiles);
}
static NODE *
info_node_of_file_buffer_tags (file_buffer, nodename)
FILE_BUFFER *file_buffer;
char *nodename;
{
register int i;
TAG *tag;
for (i = 0; (tag = file_buffer->tags[i]); i++)
if (strcmp (nodename, tag->nodename) == 0)
{
FILE_BUFFER *subfile;
subfile = info_find_file_internal (tag->filename, INFO_NO_TAGS);
if (!subfile)
return ((NODE *)NULL);
if (!subfile->contents)
{
info_reload_file_buffer_contents (subfile);
if (!subfile->contents)
return ((NODE *)NULL);
}
{
NODE *node;
node = (NODE *)xmalloc (sizeof (NODE));
node->filename = (subfile->fullpath);
node->nodename = tag->nodename;
node->contents = subfile->contents + tag->nodestart;
node->flags = 0;
node->parent = (char *)NULL;
if (file_buffer->flags & N_HasTagsTable)
{
node->flags |= N_HasTagsTable;
if (file_buffer->flags & N_TagsIndirect)
{
node->flags |= N_TagsIndirect;
node->parent = file_buffer->fullpath;
}
}
if (subfile->flags & N_IsCompressed)
node->flags |= N_IsCompressed;
if (tag->nodelen == -1)
{
int min, max;
char *node_sep;
SEARCH_BINDING node_body;
char *buff_end;
min = max = DEFAULT_INFO_FUDGE;
if (tag->nodestart < DEFAULT_INFO_FUDGE)
min = tag->nodestart;
if (DEFAULT_INFO_FUDGE >
(subfile->filesize - tag->nodestart))
max = subfile->filesize - tag->nodestart;
node_sep = adjust_nodestart (node, min, max);
if (node_sep == (char *)NULL)
{
free (node);
return ((NODE *)NULL);
}
tag->nodestart = node_sep - subfile->contents;
buff_end = subfile->contents + subfile->filesize;
node_body.buffer = node->contents;
node_body.start = 0;
node_body.end = buff_end - node_body.buffer;
node_body.flags = 0;
tag->nodelen = get_node_length (&node_body);
}
else
{
node->contents += skip_node_separator (node->contents);
}
node->nodelen = tag->nodelen;
return (node);
}
}
return ((NODE *)NULL);
}
FILE_BUFFER *
make_file_buffer ()
{
FILE_BUFFER *file_buffer;
file_buffer = (FILE_BUFFER *)xmalloc (sizeof (FILE_BUFFER));
file_buffer->filename = file_buffer->fullpath = (char *)NULL;
file_buffer->contents = (char *)NULL;
file_buffer->tags = (TAG **)NULL;
file_buffer->subfiles = (char **)NULL;
file_buffer->tags_slots = 0;
file_buffer->flags = 0;
return (file_buffer);
}
static void
remember_info_file (file_buffer)
FILE_BUFFER *file_buffer;
{
int i;
for (i = 0; info_loaded_files && info_loaded_files[i]; i++)
;
add_pointer_to_array (file_buffer, i, info_loaded_files,
info_loaded_files_slots, 10, FILE_BUFFER *);
}
static void
forget_info_file (filename)
char *filename;
{
register int i;
FILE_BUFFER *file_buffer;
if (!info_loaded_files)
return;
for (i = 0; (file_buffer = info_loaded_files[i]); i++)
if ((strcmp (filename, file_buffer->filename) == 0) ||
(strcmp (filename, file_buffer->fullpath) == 0))
{
free (file_buffer->filename);
free (file_buffer->fullpath);
if (file_buffer->contents)
free (file_buffer->contents);
free_file_buffer_tags (file_buffer);
while ((info_loaded_files[i] = info_loaded_files[++i]))
;
break;
}
}
static void
free_file_buffer_tags (file_buffer)
FILE_BUFFER *file_buffer;
{
register int i;
if (file_buffer->tags)
{
register TAG *tag;
for (i = 0; (tag = file_buffer->tags[i]); i++)
free_info_tag (tag);
free (file_buffer->tags);
file_buffer->tags = (TAG **)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 = (char **)NULL;
}
}
static void
free_info_tag (tag)
TAG *tag;
{
free (tag->nodename);
free (tag);
}
static void
info_reload_file_buffer_contents (fb)
FILE_BUFFER *fb;
{
#if defined (HANDLE_MAN_PAGES)
if (fb->flags & N_IsManPage)
return;
#endif
fb->flags &= ~N_IsCompressed;
fb->contents =
filesys_read_info_file (fb->fullpath, &(fb->filesize), &(fb->finfo));
if (fb->filesize != (long) (fb->finfo.st_size))
fb->flags |= N_IsCompressed;
}
static char *
adjust_nodestart (node, min, max)
NODE *node;
int min, max;
{
long position;
SEARCH_BINDING node_body;
node_body.buffer = node->contents;
node_body.start = 0;
node_body.end = max;
node_body.flags = 0;
if (node_body.buffer[0] != INFO_COOKIE && min > 2)
node_body.buffer -= 3;
position = find_node_separator (&node_body);
if (position != -1)
{
int sep_len;
sep_len = skip_node_separator (node->contents);
if (sep_len != 0)
{
char *nodedef, *nodestart;
int offset;
nodestart = node_body.buffer + position + sep_len;
nodedef = nodestart;
offset = string_in_line (INFO_NODE_LABEL, nodedef);
if (offset != -1)
{
nodedef += offset;
nodedef += skip_whitespace (nodedef);
offset = skip_node_characters (nodedef, DONT_SKIP_NEWLINES);
if ((offset == strlen (node->nodename)) &&
(strncmp (node->nodename, nodedef, offset) == 0))
{
node->contents = nodestart;
return (node_body.buffer + position);
}
}
}
}
node_body.buffer = node->contents - min;
node_body.start = 0;
node_body.end = min + max;
node_body.flags = 0;
position = find_node_in_binding (node->nodename, &node_body);
if (position == -1)
return ((char *)NULL);
node->contents = node_body.buffer + position;
node->contents += skip_node_separator (node->contents);
if (node->flags & N_HasTagsTable)
node->flags |= N_UpdateTags;
return (node_body.buffer + position);
}