291 lines
9.1 KiB
C
291 lines
9.1 KiB
C
/* dir.c -- how to build a special "dir" node from "localdir" files.
|
|
$Id: dir.c,v 1.3 2004/04/11 17:56:45 karl Exp $
|
|
|
|
Copyright (C) 1993, 1997, 1998, 2004 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 2, 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, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
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", (char *)NULL
|
|
};
|
|
|
|
|
|
/* Return zero if the file represented in the stat structure TEST has
|
|
already been seen, nonzero else. */
|
|
|
|
typedef struct
|
|
{
|
|
unsigned long device;
|
|
unsigned long 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 = (char *)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;
|
|
}
|