summaryrefslogtreecommitdiff
path: root/info/dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'info/dir.c')
-rw-r--r--info/dir.c290
1 files changed, 290 insertions, 0 deletions
diff --git a/info/dir.c b/info/dir.c
new file mode 100644
index 0000000..8dbc239
--- /dev/null
+++ b/info/dir.c
@@ -0,0 +1,290 @@
+/* dir.c -- how to build a special "dir" node from "localdir" files.
+ $Id: dir.c,v 1.8 2008/06/11 09:55:41 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 2004, 2007,
+ 2008 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/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "info-utils.h"
+#include "filesys.h"
+#include "tilde.h"
+
+/* The "dir" node can be built from the contents of a file called "dir",
+ with the addition of the menus of every file named in the array
+ dirs_to_add which are found in INFOPATH. */
+
+static void add_menu_to_file_buffer (char *contents, long int size,
+ FILE_BUFFER *fb);
+static void insert_text_into_fb_at_binding (FILE_BUFFER *fb,
+ SEARCH_BINDING *binding, char *text, int textlen);
+void maybe_build_dir_node (char *dirname);
+
+static char *dirs_to_add[] = {
+ "dir", "localdir", NULL
+};
+
+
+/* Return zero if the file represented in the stat structure TEST has
+ already been seen, nonzero else. */
+
+typedef struct
+{
+ dev_t device;
+ ino_t inode;
+} dir_file_list_entry_type;
+
+static int
+new_dir_file_p (struct stat *test)
+{
+ static unsigned dir_file_list_len = 0;
+ static dir_file_list_entry_type *dir_file_list = NULL;
+ unsigned i;
+
+ for (i = 0; i < dir_file_list_len; i++)
+ {
+ dir_file_list_entry_type entry;
+ entry = dir_file_list[i];
+ if (entry.device == test->st_dev && entry.inode == test->st_ino)
+ return 0;
+ }
+
+ dir_file_list_len++;
+ dir_file_list = xrealloc (dir_file_list,
+ dir_file_list_len * sizeof (dir_file_list_entry_type));
+ dir_file_list[dir_file_list_len - 1].device = test->st_dev;
+ dir_file_list[dir_file_list_len - 1].inode = test->st_ino;
+ return 1;
+}
+
+
+void
+maybe_build_dir_node (char *dirname)
+{
+ int path_index, update_tags;
+ char *this_dir;
+ FILE_BUFFER *dir_buffer = info_find_file (dirname);
+
+ /* If there is no "dir" in the current info path, we cannot build one
+ from nothing. */
+ if (!dir_buffer)
+ return;
+
+ /* If this directory has already been built, return now. */
+ if (dir_buffer->flags & N_CannotGC)
+ return;
+
+ /* Initialize the list we use to avoid reading the same dir file twice
+ with the dir file just found. */
+ new_dir_file_p (&dir_buffer->finfo);
+
+ path_index = update_tags = 0;
+
+ /* Using each element of the path, check for one of the files in
+ DIRS_TO_ADD. Do not check for "localdir.info.Z" or anything else.
+ Only files explictly named are eligible. This is a design decision.
+ There can be an info file name "localdir.info" which contains
+ information on the setting up of "localdir" files. */
+ while ((this_dir = extract_colon_unit (infopath, &path_index)))
+ {
+ register int da_index;
+ char *from_file;
+
+ /* Expand a leading tilde if one is present. */
+ if (*this_dir == '~')
+ {
+ char *tilde_expanded_dirname;
+
+ tilde_expanded_dirname = tilde_expand_word (this_dir);
+ if (tilde_expanded_dirname != this_dir)
+ {
+ free (this_dir);
+ this_dir = tilde_expanded_dirname;
+ }
+ }
+
+ /* For every different file named in DIRS_TO_ADD found in the
+ search path, add that file's menu to our "dir" node. */
+ for (da_index = 0; (from_file = dirs_to_add[da_index]); da_index++)
+ {
+ struct stat finfo;
+ int statable;
+ int namelen = strlen (from_file);
+ char *fullpath = xmalloc (3 + strlen (this_dir) + namelen);
+
+ strcpy (fullpath, this_dir);
+ if (!IS_SLASH (fullpath[strlen (fullpath) - 1]))
+ strcat (fullpath, "/");
+ strcat (fullpath, from_file);
+
+ statable = (stat (fullpath, &finfo) == 0);
+
+ /* Only add this file if we have not seen it before. */
+ if (statable && S_ISREG (finfo.st_mode) && new_dir_file_p (&finfo))
+ {
+ long filesize;
+ int compressed;
+ char *contents = filesys_read_info_file (fullpath, &filesize,
+ &finfo, &compressed);
+ if (contents)
+ {
+ update_tags++;
+ add_menu_to_file_buffer (contents, filesize, dir_buffer);
+ free (contents);
+ }
+ }
+
+ free (fullpath);
+ }
+ free (this_dir);
+ }
+
+ if (update_tags)
+ build_tags_and_nodes (dir_buffer);
+
+ /* Flag that the dir buffer has been built. */
+ dir_buffer->flags |= N_CannotGC;
+}
+
+/* Given CONTENTS and FB (a file buffer), add the menu found in CONTENTS
+ to the menu found in FB->contents. Second argument SIZE is the total
+ size of CONTENTS. */
+static void
+add_menu_to_file_buffer (char *contents, long int size, FILE_BUFFER *fb)
+{
+ SEARCH_BINDING contents_binding, fb_binding;
+ long contents_offset, fb_offset;
+
+ contents_binding.buffer = contents;
+ contents_binding.start = 0;
+ contents_binding.end = size;
+ contents_binding.flags = S_FoldCase | S_SkipDest;
+
+ fb_binding.buffer = fb->contents;
+ fb_binding.start = 0;
+ fb_binding.end = fb->filesize;
+ fb_binding.flags = S_FoldCase | S_SkipDest;
+
+ /* Move to the start of the menus in CONTENTS and FB. */
+ contents_offset = search_forward (INFO_MENU_LABEL, &contents_binding);
+ fb_offset = search_forward (INFO_MENU_LABEL, &fb_binding);
+
+ /* If there is no menu in CONTENTS, quit now. */
+ if (contents_offset == -1)
+ return;
+
+ /* There is a menu in CONTENTS, and contents_offset points to the first
+ character following the menu starter string. Skip all whitespace
+ and newline characters. */
+ contents_offset += skip_whitespace_and_newlines (contents + contents_offset);
+
+ /* If there is no menu in FB, make one. */
+ if (fb_offset == -1)
+ {
+ /* Find the start of the second node in this file buffer. If there
+ is only one node, we will be adding the contents to the end of
+ this node. */
+ fb_offset = find_node_separator (&fb_binding);
+
+ /* If not even a single node separator, give up. */
+ if (fb_offset == -1)
+ return;
+
+ fb_binding.start = fb_offset;
+ fb_binding.start +=
+ skip_node_separator (fb_binding.buffer + fb_binding.start);
+
+ /* Try to find the next node separator. */
+ fb_offset = find_node_separator (&fb_binding);
+
+ /* If found one, consider that the start of the menu. Otherwise, the
+ start of this menu is the end of the file buffer (i.e., fb->size). */
+ if (fb_offset != -1)
+ fb_binding.start = fb_offset;
+ else
+ fb_binding.start = fb_binding.end;
+
+ insert_text_into_fb_at_binding
+ (fb, &fb_binding, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL));
+
+ fb_binding.buffer = fb->contents;
+ fb_binding.start = 0;
+ fb_binding.end = fb->filesize;
+ fb_offset = search_forward (INFO_MENU_LABEL, &fb_binding);
+ if (fb_offset == -1)
+ abort ();
+ }
+
+ /* CONTENTS_OFFSET and FB_OFFSET point to the starts of the menus that
+ appear in their respective buffers. Add the remainder of CONTENTS
+ to the end of FB's menu. */
+ fb_binding.start = fb_offset;
+ fb_offset = find_node_separator (&fb_binding);
+ if (fb_offset != -1)
+ fb_binding.start = fb_offset;
+ else
+ fb_binding.start = fb_binding.end;
+
+ /* Leave exactly one blank line between directory entries. */
+ {
+ int num_found = 0;
+
+ while ((fb_binding.start > 0) &&
+ (whitespace_or_newline (fb_binding.buffer[fb_binding.start - 1])))
+ {
+ num_found++;
+ fb_binding.start--;
+ }
+
+ /* Optimize if possible. */
+ if (num_found >= 2)
+ {
+ fb_binding.buffer[fb_binding.start++] = '\n';
+ fb_binding.buffer[fb_binding.start++] = '\n';
+ }
+ else
+ {
+ /* Do it the hard way. */
+ insert_text_into_fb_at_binding (fb, &fb_binding, "\n\n", 2);
+ fb_binding.start += 2;
+ }
+ }
+
+ /* Insert the new menu. */
+ insert_text_into_fb_at_binding
+ (fb, &fb_binding, contents + contents_offset, size - contents_offset);
+}
+
+static void
+insert_text_into_fb_at_binding (FILE_BUFFER *fb,
+ SEARCH_BINDING *binding, char *text, int textlen)
+{
+ char *contents;
+ long start, end;
+
+ start = binding->start;
+ end = fb->filesize;
+
+ contents = xmalloc (fb->filesize + textlen + 1);
+ memcpy (contents, fb->contents, start);
+ memcpy (contents + start, text, textlen);
+ memcpy (contents + start + textlen, fb->contents + start, end - start);
+ free (fb->contents);
+ fb->contents = contents;
+ fb->filesize += textlen;
+ fb->finfo.st_size = fb->filesize;
+}