summaryrefslogtreecommitdiffstats
path: root/libidu/walker.c
diff options
context:
space:
mode:
Diffstat (limited to 'libidu/walker.c')
-rw-r--r--libidu/walker.c1130
1 files changed, 1130 insertions, 0 deletions
diff --git a/libidu/walker.c b/libidu/walker.c
new file mode 100644
index 0000000..0ba2d89
--- /dev/null
+++ b/libidu/walker.c
@@ -0,0 +1,1130 @@
+/* walker.c -- nifty file-tree walker
+ Copyright (C) 1986, 1995, 1996 Free Software Foundation, Inc.
+ Written by Greg McGary <gkm@gnu.ai.mit.edu>
+
+ 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.
+*/
+
+#include <config.h>
+#include "xsysstat.h"
+#include <stdio.h>
+#include "xstdlib.h"
+#include "xstddef.h"
+#include "xunistd.h"
+#include "xstring.h"
+#include "xfnmatch.h"
+#include "xdirent.h"
+#include "xnls.h"
+#include "idfile.h"
+#include "error.h"
+#include "xmalloc.h"
+#include "dynvec.h"
+#include "scanners.h"
+#include "pathmax.h"
+#include "xalloca.h"
+
+int walk_dir __P((struct file_link *dir_link));
+struct member_file *get_member_file __P((struct file_link *flink));
+struct lang_args *get_lang_args __P((struct file_link const *flink));
+int walk_sub_dirs __P((struct dynvec *sub_dirs_vec));
+void reparent_children __P((struct file_link *dlink, struct file_link *slink));
+int classify_link __P((struct file_link *flink, struct stat *stp));
+struct file_link *get_link_from_dirent __P((struct dirent *dirent, struct file_link *parent));
+struct file_link *make_link_from_dirent __P((struct dirent *dirent, struct file_link *parent));
+struct file_link *get_link_from_string __P((char const *name, struct file_link *parent));
+struct file_link *make_link_from_string __P((char const *name, struct file_link *parent));
+int lang_wanted __P((char const *lang_name));
+char **append_strings_to_vector __P((char **vector_0, char *string, char *delimiter_class));
+int vector_length __P((char **vector));
+int string_in_vector __P((char const *string, char **vector));
+static int same_as_dot __P((char const *cwd));
+struct file_link const **fill_link_vector __P((struct file_link const **vec_buf, struct file_link const *flink));
+struct file_link const **fill_link_vector_1 __P((struct file_link const **vec_buf, struct file_link const *flink));
+char *fill_dot_dots __P((char *buf, int levels));
+static char *absolute_file_name_1 __P((char *buffer, struct file_link const *flink));
+unsigned long member_file_hash_1 __P((void const *key));
+unsigned long member_file_hash_2 __P((void const *key));
+int member_file_hash_compare __P((void const *x, void const *y));
+unsigned long file_link_hash_1 __P((void const *key));
+unsigned long file_link_hash_2 __P((void const *key));
+int file_link_hash_compare __P((void const *x, void const *y));
+unsigned long dev_ino_hash_1 __P((void const *key));
+unsigned long dev_ino_hash_2 __P((void const *key));
+int dev_ino_hash_compare __P((void const *x, void const *y));
+int symlink_ancestry __P((struct file_link *flink));
+
+#if HAVE_LINK
+struct file_link *find_alias_link __P((struct file_link *flink, struct stat *stp));
+struct member_file *maybe_get_member_file __P((struct file_link *flink, struct stat *stp));
+#endif
+
+#define IS_DOT(s) ((s)[0] == '.' && (s)[1] == '\0')
+#define IS_DOT_DOT(s) ((s)[0] == '.' && (s)[1] == '.' && (s)[2] == '\0')
+#define IS_DOT_or_DOT_DOT(s) \
+ (((s)[0] == '.') && (((s)[1] == '\0') || ((s)[1] == '.' && (s)[2] == '\0')))
+
+static struct file_link *current_dir_link = 0;
+
+static char white_space[] = " \t\r\n\v\f";
+
+char* xgetcwd __P((void));
+
+
+/****************************************************************************/
+/* Walk the file-system tree rooted at `dir_link', looking for files
+ that are eligible for scanning. */
+
+int
+walk_dir (struct file_link *dir_link)
+{
+ int scannable_files;
+ struct dynvec *sub_dirs_vec;
+ DIR *dirp;
+
+ if (!chdir_to_link (dir_link))
+ return 0;
+ dirp = opendir (".");
+ if (dirp == 0)
+ {
+ char *file_name = ALLOCA (char, PATH_MAX);
+ absolute_file_name (file_name, dir_link);
+ error (0, errno, _("can't read directory `%s' (`.' from `%s')"), file_name, xgetcwd ());
+ return 0;
+ }
+ sub_dirs_vec = make_dynvec (32);
+ scannable_files = 0;
+ for (;;)
+ {
+ struct file_link *flink;
+ struct dirent *dirent = readdir (dirp);
+
+ if (dirent == 0)
+ break;
+ if (IS_DOT_or_DOT_DOT (dirent->d_name))
+ continue;
+
+ flink = get_link_from_dirent (dirent, dir_link);
+ if (!(flink->fl_flags & FL_PRUNE))
+ walk_flink (flink, sub_dirs_vec);
+ }
+ closedir (dirp);
+
+ scannable_files += walk_sub_dirs (sub_dirs_vec);
+ dynvec_free (sub_dirs_vec);
+ return scannable_files;
+}
+
+/* Walk the directories found by walk_dir, calling walk_dir
+ recursively for each directory. */
+
+int
+walk_sub_dirs (struct dynvec *sub_dirs_vec)
+{
+ struct file_link **sub_dirs;
+ struct file_link **sub_dirs_end;
+ int total_scannable_files = 0;
+
+ dynvec_freeze (sub_dirs_vec);
+ sub_dirs_end = (struct file_link **)
+ &sub_dirs_vec->dv_vec[sub_dirs_vec->dv_fill];
+ sub_dirs = (struct file_link **) sub_dirs_vec->dv_vec;
+ for ( ; sub_dirs < sub_dirs_end; sub_dirs++)
+ {
+ struct file_link *sub_dir_link = *sub_dirs;
+ int scannable_files = walk_dir (sub_dir_link);
+ if (scannable_files)
+ total_scannable_files += scannable_files;
+ }
+ return total_scannable_files;
+}
+
+void
+walk_flink (struct file_link *flink, struct dynvec *sub_dirs_vec)
+{
+ struct stat st;
+ unsigned int old_flags;
+ unsigned int new_flags;
+
+ new_flags = classify_link (flink, &st);
+ if (new_flags == 0)
+ return;
+
+ old_flags = flink->fl_flags;
+ if ((old_flags & FL_TYPE_MASK)
+ && (old_flags & FL_TYPE_MASK) != (new_flags & FL_TYPE_MASK))
+ {
+ char *file_name = ALLOCA (char, PATH_MAX);
+ absolute_file_name (file_name, flink);
+ error (0, 0, _("notice: `%s' was a %s, but is now a %s!"), file_name,
+ (FL_IS_FILE (old_flags) ? _("file") : _("directory")),
+ (FL_IS_FILE (new_flags) ? _("file") : _("directory")));
+ }
+
+
+ flink->fl_flags = (old_flags & ~(FL_TYPE_MASK|FL_SYM_LINK)) | new_flags;
+ if (FL_IS_DIR (new_flags))
+ {
+ struct file_link *alias_link = find_alias_link (flink, &st);
+
+ if (alias_link)
+ {
+ if (!(new_flags & FL_SYM_LINK))
+ reparent_children (flink, alias_link);
+ }
+ else if (sub_dirs_vec == 0)
+ walk_dir (flink);
+ else
+ dynvec_append (sub_dirs_vec, flink);
+ }
+ else
+ {
+ struct member_file *member;
+#if HAVE_LINK
+ member = maybe_get_member_file (flink, &st);
+#else
+ member = get_member_file (flink);
+#endif
+ if (member == 0)
+ return;
+ }
+}
+
+/* Take child file_link nodes from a symlinked directory and give them
+ to a hard linked directory. This is something of a pain since a
+ file_link's parent node is part of its hash-table key. We must
+ search the entire hash-table for the children. With each child, we
+ must delete it, reset its parent link, then reinsert. */
+
+void
+reparent_children (struct file_link *dlink, struct file_link *slink)
+{
+ void **slot = idh.idh_file_link_table.ht_vec;
+ void **end = &idh.idh_file_link_table.ht_vec[idh.idh_file_link_table.ht_size];
+
+ for ( ; slot < end; slot++)
+ {
+ if (!HASH_VACANT (*slot))
+ {
+ struct file_link *child = (struct file_link *) *slot;
+ if (child->fl_parent == slink)
+ {
+ void **new_slot;
+ *slot = hash_deleted_item;
+ child->fl_parent = dlink;
+ new_slot = hash_find_slot (&idh.idh_file_link_table, child);
+ *new_slot = child;
+ }
+ }
+ }
+}
+
+
+/****************************************************************************/
+/* Separate the wheat from the chaff. Mark those file_links that are
+ components in member files. */
+
+void
+mark_member_file_links (struct idhead *idhp)
+{
+ struct member_file **members_0
+ = (struct member_file **) hash_dump (&idhp->idh_member_file_table,
+ 0, member_file_qsort_compare);
+ struct member_file **end = &members_0[idhp->idh_member_file_table.ht_fill];
+ struct member_file **members;
+ int new_index = 0;
+
+ for (members = members_0; members < end; members++)
+ {
+ struct member_file *member = *members;
+ struct file_link *flink;
+ member->mf_index = new_index++;
+ for (flink = member->mf_link;
+ !(flink->fl_flags & FL_USED); flink = flink->fl_parent)
+ flink->fl_flags |= FL_USED;
+ }
+ free (members_0);
+}
+
+
+#if HAVE_LINK
+
+/****************************************************************************/
+/* Return a `member_file' for this `flink' *if* the filename matches
+ some scan pattern, and no alias for the file takes precedence ([1]
+ hard-links dominate symbolic-links; [2] for two hard-links: first
+ come, first served). */
+
+struct member_file *
+maybe_get_member_file (struct file_link *flink, struct stat *stp)
+{
+ struct file_link *alias_link;
+ struct member_file *member;
+ struct member_file *alias_member = 0;
+
+ member = get_member_file (flink);
+ alias_link = find_alias_link (flink, stp);
+ if (alias_link)
+ alias_member = find_member_file (alias_link);
+
+ if (member && alias_member)
+ {
+ int ancestry = symlink_ancestry (flink);
+ int alias_ancestry = symlink_ancestry (alias_link);
+ if (member->mf_lang_args != alias_member->mf_lang_args)
+ {
+ char *file_name = ALLOCA (char, PATH_MAX);
+ char *alias_file_name = ALLOCA (char, PATH_MAX);
+ absolute_file_name (file_name, flink);
+ absolute_file_name (alias_file_name, alias_link);
+ error (0, 0, _("warning: `%s' and `%s' are the same file, but yield different scans!"),
+ file_name, alias_file_name);
+ }
+ else if (alias_ancestry > ancestry)
+ {
+ hash_delete (&idh.idh_member_file_table, member);
+ member->mf_link->fl_flags &= ~FL_MEMBER;
+ return 0;
+ }
+ else
+ {
+ hash_delete (&idh.idh_member_file_table, alias_member);
+ alias_member->mf_link->fl_flags &= ~FL_MEMBER;
+ }
+ }
+ return member;
+}
+
+/* Return a previously registered alias for `flink', if any. */
+
+struct file_link *
+find_alias_link (struct file_link *flink, struct stat *stp)
+{
+ struct dev_ino *dev_ino;
+ struct dev_ino **slot;
+
+ dev_ino = (struct dev_ino *) obstack_alloc (&idh.idh_dev_ino_obstack, sizeof (struct dev_ino));
+ dev_ino->di_dev = stp->st_dev;
+ dev_ino->di_ino = stp->st_ino;
+ slot = (struct dev_ino **) hash_find_slot (&idh.idh_dev_ino_table, dev_ino);
+ if (HASH_VACANT (*slot))
+ {
+ dev_ino->di_link = flink;
+ hash_insert_at (&idh.idh_dev_ino_table, dev_ino, slot);
+ return 0;
+ }
+ else
+ {
+ obstack_free (&idh.idh_dev_ino_obstack, dev_ino);
+ return (*slot)->di_link;
+ }
+}
+
+/* Return the distance from `flink' to a symbolic-link ancestor
+ directory. PATH_MAX is considered an infinite distance (e.g.,
+ there are no symlinks between `flink' and the root). */
+
+int
+symlink_ancestry (struct file_link *flink)
+{
+ int ancestry = 0;
+ while (!IS_ROOT_FILE_LINK (flink))
+ {
+ if (flink->fl_flags & FL_SYM_LINK)
+ return ancestry;
+ ancestry++;
+ flink = flink->fl_parent;
+ }
+ return PATH_MAX;
+}
+
+#endif /* HAVE_LINK */
+
+struct member_file *
+get_member_file (struct file_link *flink)
+{
+ struct member_file *member;
+ struct member_file **slot;
+ struct lang_args const *args;
+
+ args = get_lang_args (flink);
+ if (args == 0)
+ return 0;
+ if (!lang_wanted (args->la_language->lg_name))
+ return 0;
+
+ member = (struct member_file *) obstack_alloc (&idh.idh_member_file_obstack,
+ sizeof (struct member_file));
+ member->mf_link = flink;
+ slot = (struct member_file **) hash_find_slot (&idh.idh_member_file_table, member);
+ if (HASH_VACANT (*slot))
+ {
+ member->mf_index = -1;
+ hash_insert_at (&idh.idh_member_file_table, member, slot);
+ flink->fl_flags |= FL_MEMBER;
+ }
+ else
+ {
+ obstack_free (&idh.idh_member_file_obstack, member);
+#if 0
+ if (member->mf_lang_args != args)
+ {
+ char *file_name = ALLOCA (char, PATH_MAX);
+ absolute_file_name (file_name, flink);
+ error (0, 0, _("notice: scan parameters changed for `%s'"), file_name);
+ member->mf_old_index = -1;
+ }
+#endif
+ member = *slot;
+ }
+ member->mf_lang_args = args;
+ return *slot;
+}
+
+struct member_file *
+find_member_file (struct file_link const *flink)
+{
+ struct member_file key;
+ struct member_file **slot;
+
+ key.mf_link = (struct file_link *) flink;
+ slot = (struct member_file **) hash_find_slot (&idh.idh_member_file_table, &key);
+ if (HASH_VACANT (*slot))
+ return 0;
+ return *slot;
+}
+
+/* March down the list of lang_args, and return the first one whose
+ pattern matches FLINK. Return the matching lang_args, if a
+ scanner exists for that language, otherwise return 0. */
+
+struct lang_args *
+get_lang_args (struct file_link const *flink)
+{
+ struct lang_args *args = lang_args_list;
+
+ while (args)
+ {
+ if (strchr (args->la_pattern, SLASH_CHAR))
+ {
+ char *file_name = ALLOCA (char, PATH_MAX);
+ absolute_file_name (file_name, flink);
+ if (fnmatch (args->la_pattern, file_name, MAYBE_FNM_CASEFOLD | FNM_FILE_NAME) == 0)
+ return (args->la_language ? args : 0);
+ }
+ else
+ {
+ if (fnmatch (args->la_pattern, flink->fl_name, MAYBE_FNM_CASEFOLD) == 0)
+ return (args->la_language ? args : 0);
+ }
+ args = args->la_next;
+ }
+ return ((lang_args_default && lang_args_default->la_language)
+ ? lang_args_default : 0);
+}
+
+
+/****************************************************************************/
+
+static char **langs_included;
+static char **langs_excluded;
+
+int
+lang_wanted (char const *lang_name)
+{
+ if (langs_excluded)
+ return !string_in_vector (lang_name, langs_excluded);
+ else if (langs_included)
+ return string_in_vector (lang_name, langs_included);
+ else
+ return 1;
+}
+
+void
+include_languages (char *lang_names)
+{
+ if (langs_excluded)
+ error (1, 0, "can't mix --include and --exclude options");
+ langs_included = append_strings_to_vector (langs_included, lang_names, white_space);
+}
+
+void
+exclude_languages (char *lang_names)
+{
+ if (langs_excluded)
+ error (1, 0, "can't mix --include and --exclude options");
+ langs_excluded = append_strings_to_vector (langs_excluded, lang_names, white_space);
+}
+
+char **
+append_strings_to_vector (char **vector_0, char *string, char *delimiter_class)
+{
+ char **vector;
+ if (vector_0)
+ {
+ int length = vector_length (vector_0);
+ vector_0 = REALLOC (vector_0, char *,
+ length + 2 + strlen (string) / 2);
+ vector = &vector_0[length];
+ }
+ else
+ vector = vector_0 = MALLOC (char *, 2 + strlen (string) / 2);
+
+ *vector = strtok (string, delimiter_class);
+ while (*vector)
+ *++vector = strtok (0, delimiter_class);
+ return REALLOC (vector_0, char *, ++vector - vector_0);
+}
+
+int
+vector_length (char **vector)
+{
+ int length = 0;
+ while (*vector++)
+ length++;
+ return length;
+}
+
+int
+string_in_vector (char const *string, char **vector)
+{
+ while (*vector)
+ if (strequ (string, *vector++))
+ return 1;
+ return 0;
+}
+
+
+/****************************************************************************/
+/* Convert a file name string to an absolute chain of `file_link's. */
+
+struct file_link *
+parse_file_name (char *file_name, struct file_link *relative_dir_link)
+{
+ struct file_link *flink;
+ char **links_0;
+ char **links;
+
+ if (IS_ABSOLUTE (file_name))
+ {
+#if HAVE_LINK
+ flink = get_link_from_string (SLASH_STRING, 0);
+#else
+ flink = 0;
+#endif
+ }
+ else if (relative_dir_link)
+ flink = relative_dir_link;
+ else if (current_dir_link)
+ flink = current_dir_link;
+ else
+ flink = get_current_dir_link ();
+
+ links = links_0 = vectorize_string (file_name, SLASH_STRING);
+ while (*links)
+ {
+ char const* link_name = *links++;
+ if (*link_name == '\0' || IS_DOT (link_name))
+ ;
+ else if (IS_DOT_DOT (link_name))
+ flink = flink->fl_parent;
+ else
+ {
+ struct stat st;
+ flink = get_link_from_string (link_name, flink);
+ if (!flink->fl_flags)
+ {
+ flink->fl_flags = classify_link (flink, &st);
+ if (!flink->fl_flags)
+ return 0;
+ }
+ }
+ }
+ free (links_0);
+ return flink;
+}
+
+/* Return an absolute chain of `file_link's representing the current
+ working directory. */
+
+struct file_link *
+get_current_dir_link (void)
+{
+ struct file_link *dir_link;
+ char *cwd_0;
+ char *cwd;
+ char *xcwd = 0;
+ char **links_0;
+ char **links;
+
+ if (current_dir_link)
+ return current_dir_link;
+
+ cwd_0 = getenv ("PWD");
+ if (cwd_0)
+ cwd_0 = strdup (cwd_0);
+ if (!same_as_dot (cwd_0))
+ cwd_0 = xcwd = xgetcwd ();
+ if (cwd_0 == 0)
+ error (1, errno, _("can't get working directory"));
+ cwd = cwd_0;
+#if HAVE_LINK
+ dir_link = get_link_from_string (SLASH_STRING, 0);
+ dir_link->fl_flags = (dir_link->fl_flags & ~FL_TYPE_MASK) | FL_TYPE_DIR;
+#else
+ dir_link = 0;
+#endif
+ links = links_0 = vectorize_string (cwd, SLASH_STRING);
+ while (*links)
+ {
+ struct stat st;
+ char const* link_name = *links++;
+ dir_link = get_link_from_string (link_name, dir_link);
+ if (!dir_link->fl_flags)
+ dir_link->fl_flags = classify_link (dir_link, &st);
+ }
+ chdir_to_link (dir_link);
+ free (links_0);
+ if (xcwd)
+ free (xcwd);
+ current_dir_link = dir_link;
+ return dir_link;
+}
+
+static int
+same_as_dot (char const *cwd)
+{
+ struct stat cwd_st;
+ struct stat dot_st;
+
+ if (cwd == 0 || *cwd != '/'
+ || stat (cwd, &cwd_st) < 0
+ || stat (".", &dot_st) < 0)
+ return 0;
+ return ((cwd_st.st_ino == dot_st.st_ino) && (cwd_st.st_dev == dot_st.st_dev));
+}
+
+/* Change the working directory to the directory represented by
+ `dir_link'. Choose the shortest path to the destination based on
+ the cached value of the current directory. */
+
+int
+chdir_to_link (struct file_link *dir_link)
+{
+ char *to_dir_name = ALLOCA (char, PATH_MAX);
+
+ if (current_dir_link == dir_link)
+ return 1;
+
+ if (current_dir_link)
+ maybe_relative_file_name (to_dir_name, dir_link, current_dir_link);
+ else
+ absolute_file_name (to_dir_name, dir_link);
+ if (chdir (to_dir_name) < 0)
+ {
+ if (IS_ABSOLUTE (to_dir_name))
+ error (0, errno, _("can't chdir to `%s'"), to_dir_name);
+ else
+ {
+ char *from_dir_name = ALLOCA (char, PATH_MAX);
+ absolute_file_name (from_dir_name, current_dir_link);
+ error (0, errno, _("can't chdir to `%s' from `%s'"), to_dir_name, from_dir_name);
+ }
+ return 0;
+ }
+ else
+ {
+ current_dir_link = dir_link;
+ return 1;
+ }
+}
+
+char **
+vectorize_string (char *string, char *delimiter_class)
+{
+ char **vector_0 = MALLOC (char *, 2 + strlen (string) / 2);
+ char **vector = vector_0;
+
+ *vector = strtok (string, delimiter_class);
+ while (*vector)
+ *++vector = strtok (0, delimiter_class);
+ return REALLOC (vector_0, char *, ++vector - vector_0);
+}
+
+void
+prune_file_names (char *str, struct file_link *from_link)
+{
+ char **file_names_0 = vectorize_string (str, white_space);
+ char **file_names = file_names_0;
+
+ while (*file_names)
+ {
+ struct file_link *flink = parse_file_name (*file_names++, from_link);
+ if (flink)
+ flink->fl_flags |= FL_PRUNE;
+ }
+ free (file_names_0);
+}
+
+
+/****************************************************************************/
+/* Gather information about the link at `flink'. If it's a good
+ file or directory, return its mod-time and type. */
+
+int
+classify_link (struct file_link *flink, struct stat *stp)
+{
+ unsigned int flags = 0;
+
+ if (!chdir_to_link (flink->fl_parent))
+ return 0;
+
+#ifdef S_IFLNK
+ if (lstat (flink->fl_name, stp) < 0)
+ {
+ error (0, errno, _("can't lstat `%s' from `%s'"), flink->fl_name, xgetcwd ());
+ return 0;
+ }
+ if (S_ISLNK (stp->st_mode))
+ {
+#endif
+ if (stat (flink->fl_name, stp) < 0)
+ {
+ error (0, errno, _("can't stat `%s' from `%s'"), flink->fl_name, xgetcwd ());
+ return 0;
+ }
+#ifdef S_IFLNK
+ flags |= FL_SYM_LINK;
+ }
+#endif
+ if (S_ISDIR (stp->st_mode))
+ flags |= FL_TYPE_DIR;
+ else if (S_ISREG (stp->st_mode))
+ flags |= FL_TYPE_FILE;
+ else
+ return 0;
+ return flags;
+}
+
+
+/****************************************************************************/
+/* Retrieve an existing flink; or if none exists, create one. */
+
+struct file_link *
+get_link_from_dirent (struct dirent *dirent, struct file_link *parent)
+{
+ struct file_link **slot;
+ struct file_link *new_link;
+
+ new_link = make_link_from_dirent (dirent, parent);
+ slot = (struct file_link **) hash_find_slot (&idh.idh_file_link_table, new_link);
+ if (HASH_VACANT (*slot))
+ hash_insert_at (&idh.idh_file_link_table, new_link, slot);
+ else
+ obstack_free (&idh.idh_file_link_obstack, new_link);
+ return *slot;
+}
+
+struct file_link *
+get_link_from_string (char const *name, struct file_link *parent)
+{
+ struct file_link **slot;
+ struct file_link *new_link;
+
+ new_link = make_link_from_string (name, parent);
+ slot = (struct file_link **) hash_find_slot (&idh.idh_file_link_table, new_link);
+ if (HASH_VACANT (*slot))
+ hash_insert_at (&idh.idh_file_link_table, new_link, slot);
+ else
+ obstack_free (&idh.idh_file_link_obstack, new_link);
+ return *slot;
+}
+
+struct file_link *
+make_link_from_dirent (struct dirent* dirent, struct file_link *parent)
+{
+ struct file_link *flink;
+
+ flink = (struct file_link *) obstack_alloc (&idh.idh_file_link_obstack,
+ sizeof (struct file_link) + strlen (dirent->d_name));
+ strcpy (flink->fl_name, dirent->d_name);
+ flink->fl_parent = parent ? parent : flink;
+ flink->fl_flags = 0;
+
+ return flink;
+}
+
+struct file_link *
+make_link_from_string (char const* name, struct file_link *parent)
+{
+ struct file_link *flink;
+
+ flink = (struct file_link *) obstack_alloc (&idh.idh_file_link_obstack,
+ sizeof (struct file_link) + strlen (name));
+ strcpy (flink->fl_name, name);
+ flink->fl_parent = parent ? parent : flink;
+ flink->fl_flags = 0;
+
+ return flink;
+}
+
+
+/****************************************************************************/
+/* Convert a `file_link' chain to a vector of component `file_link's,
+ with the root at [0]. Return a pointer beyond the final component. */
+
+struct file_link const **
+fill_link_vector (struct file_link const **vec_buf, struct file_link const *flink)
+{
+ vec_buf = fill_link_vector_1 (vec_buf, flink);
+ *vec_buf = 0;
+ return vec_buf;
+}
+
+struct file_link const **
+fill_link_vector_1 (struct file_link const **vec_buf, struct file_link const *flink)
+{
+ if (!IS_ROOT_FILE_LINK (flink))
+ vec_buf = fill_link_vector_1 (vec_buf, flink->fl_parent);
+ *vec_buf++ = flink;
+ return vec_buf;
+}
+
+
+/****************************************************************************/
+/* Fill BUF_0 with a path to TO_LINK. If a relative path from
+ FROM_LINK is possible (i.e., no intervening symbolic-links) and
+ shorter, return the relative path; otherwise, return an absolute
+ path. */
+
+char *
+maybe_relative_file_name (char *buf_0, struct file_link const *to_link, struct file_link const *from_link)
+{
+ struct file_link const **to_link_vec_0 = ALLOCA (struct file_link const *, PATH_MAX/2);
+ struct file_link const **from_link_vec_0 = ALLOCA (struct file_link const *, PATH_MAX/2);
+ struct file_link const **to_link_vec = to_link_vec_0;
+ struct file_link const **from_link_vec = from_link_vec_0;
+ struct file_link const **from_link_end;
+ struct file_link const **from_links;
+ int alloc_me = (buf_0 == 0);
+ char *buf;
+ int levels;
+
+ if (from_link == 0)
+ from_link = current_dir_link;
+
+ if (alloc_me)
+ buf_0 = MALLOC (char, PATH_MAX);
+
+ /* Optimize common cases. */
+ if (to_link == from_link)
+ strcpy (buf_0, ".");
+ else if (to_link->fl_parent == from_link)
+ strcpy (buf_0, to_link->fl_name);
+ else if (from_link->fl_flags & FL_SYM_LINK)
+ absolute_file_name (buf_0, to_link);
+ else if (to_link == from_link->fl_parent)
+ strcpy (buf_0, "..");
+ else if (to_link->fl_parent == from_link->fl_parent)
+ {
+ strcpy (buf_0, DOT_DOT_SLASH);
+ strcpy (&buf_0[3], to_link->fl_name);
+ }
+ else
+ {
+ from_link_end = fill_link_vector (from_link_vec, from_link);
+ fill_link_vector (to_link_vec, to_link);
+ while (*to_link_vec == *from_link_vec)
+ {
+ if (*to_link_vec == 0)
+ {
+ strcpy (buf_0, ".");
+ goto out;
+ }
+ to_link_vec++;
+ from_link_vec++;
+ }
+ levels = from_link_end - from_link_vec;
+ if (levels >= (from_link_vec - from_link_vec_0))
+ {
+ absolute_file_name (buf_0, to_link);
+ goto out;
+ }
+ for (from_links = from_link_vec; *from_links; from_links++)
+ if ((*from_links)->fl_flags & FL_SYM_LINK)
+ {
+ absolute_file_name (buf_0, to_link);
+ goto out;
+ }
+ buf = fill_dot_dots (buf_0, levels);
+ if (*to_link_vec == 0)
+ *--buf = '\0';
+ else
+ {
+ do
+ {
+ strcpy (buf, (*to_link_vec)->fl_name);
+ buf += strlen (buf);
+ if ((*to_link_vec)->fl_name[0] != SLASH_CHAR && *++to_link_vec)
+ *buf++ = SLASH_CHAR;
+ }
+ while (*to_link_vec);
+ }
+ }
+out:
+ if (alloc_me)
+ buf_0 = REALLOC (buf_0, char, 1 + strlen (buf_0));
+ return buf_0;
+}
+
+/* Fill `buf' with sequences of "../" in order to ascend so many
+ `levels' in a directory tree. */
+
+char *
+fill_dot_dots (char *buf, int levels)
+{
+ while (levels--)
+ {
+ strcpy (buf, DOT_DOT_SLASH);
+ buf += 3;
+ }
+ return buf;
+}
+
+
+/****************************************************************************/
+/* Fill `buffer' with the absolute path to `flink'. */
+
+char *
+absolute_file_name (char *buf_0, struct file_link const *flink)
+{
+ char *end;
+ int alloc_me = (buf_0 == 0);
+
+ if (alloc_me)
+ buf_0 = MALLOC (char, PATH_MAX);
+ end = absolute_file_name_1 (buf_0, flink);
+ /* Clip the trailing slash. */
+#if HAVE_LINK
+ if (end > &buf_0[1])
+ end--;
+#else
+ if (end > &buf_0[3])
+ end--;
+#endif
+ *end++ = '\0';
+ if (alloc_me)
+ buf_0 = REALLOC (buf_0, char, end - buf_0);
+ return buf_0;
+}
+
+static char *
+absolute_file_name_1 (char *buf, struct file_link const *flink)
+{
+ char *end;
+ if (IS_ROOT_FILE_LINK (flink))
+ end = buf;
+ else
+ end = absolute_file_name_1 (buf, flink->fl_parent);
+ strcpy (end, flink->fl_name);
+ if (*end == SLASH_CHAR)
+ end++;
+ else
+ {
+ end += strlen (end);
+ *end++ = SLASH_CHAR;
+ }
+ return end;
+}
+
+
+/****************************************************************************/
+/* Hash stuff for `struct member_file'. */
+
+unsigned long
+member_file_hash_1 (void const *key)
+{
+ return_ADDRESS_HASH_1 (((struct member_file const *) key)->mf_link);
+}
+
+unsigned long
+member_file_hash_2 (void const *key)
+{
+ return_ADDRESS_HASH_2 (((struct member_file const *) key)->mf_link);
+}
+
+int
+member_file_hash_compare (void const *x, void const *y)
+{
+ return_ADDRESS_COMPARE (((struct member_file const *) x)->mf_link,
+ ((struct member_file const *) y)->mf_link);
+}
+
+/* Collating sequence:
+ - language.map index
+ - mf_link: breadth-first, alphabetical */
+
+int
+member_file_qsort_compare (void const *x, void const *y)
+{
+ struct member_file const *mfx = *(struct member_file const *const *) x;
+ struct member_file const *mfy = *(struct member_file const *const *) y;
+ int result;
+
+ INTEGER_COMPARE (mfx->mf_lang_args->la_index, mfy->mf_lang_args->la_index, result);
+ if (result)
+ return result;
+ else
+ {
+ struct file_link const *flx = mfx->mf_link;
+ struct file_link const *fly = mfy->mf_link;
+ if (flx->fl_parent == fly->fl_parent)
+ return strcmp (flx->fl_name, fly->fl_name);
+ result = (links_depth (flx) - links_depth (fly));
+ if (result)
+ return result;
+ while (flx->fl_parent != fly->fl_parent)
+ {
+ flx = flx->fl_parent;
+ fly = fly->fl_parent;
+ }
+ return strcmp (flx->fl_name, fly->fl_name);
+ }
+}
+
+/****************************************************************************/
+/* Hash stuff for `struct file_link'. */
+
+unsigned long
+file_link_hash_1 (void const *key)
+{
+ unsigned long result = 0;
+ struct file_link const *parent = (IS_ROOT_FILE_LINK (((struct file_link const *) key))
+ ? 0 : ((struct file_link const *) key)->fl_parent);
+ STRING_HASH_1 (((struct file_link const *) key)->fl_name, result);
+ ADDRESS_HASH_1 (parent, result);
+ return result;
+}
+
+unsigned long
+file_link_hash_2 (void const *key)
+{
+ unsigned long result = 0;
+ struct file_link const *parent = (IS_ROOT_FILE_LINK (((struct file_link const *) key))
+ ? 0 : ((struct file_link const *) key)->fl_parent);
+ STRING_HASH_2 (((struct file_link const *) key)->fl_name, result);
+ ADDRESS_HASH_2 (parent, result);
+ return result;
+}
+
+int
+file_link_hash_compare (void const *x, void const *y)
+{
+ int result;
+ struct file_link const *x_parent = (IS_ROOT_FILE_LINK (((struct file_link const *) x))
+ ? 0 : ((struct file_link const *) x)->fl_parent);
+ struct file_link const *y_parent = (IS_ROOT_FILE_LINK (((struct file_link const *) y))
+ ? 0 : ((struct file_link const *) y)->fl_parent);
+ ADDRESS_COMPARE (x_parent, y_parent, result);
+ if (result)
+ return result;
+ STRING_COMPARE (((struct file_link const *) x)->fl_name,
+ ((struct file_link const *) y)->fl_name, result);
+ return result;
+}
+
+/* Count directory components between flink and its root. */
+
+int
+links_depth (struct file_link const *flink)
+{
+ int depth = 0;
+ while (!IS_ROOT_FILE_LINK (flink))
+ {
+ depth++;
+ flink = flink->fl_parent;
+ }
+ return depth;
+}
+
+#if HAVE_LINK
+
+/****************************************************************************/
+/* Hash stuff for `struct dev_ino'. */
+
+unsigned long
+dev_ino_hash_1 (void const *key)
+{
+ unsigned long result = 0;
+ INTEGER_HASH_1 (((struct dev_ino const *) key)->di_dev, result);
+ INTEGER_HASH_1 (((struct dev_ino const *) key)->di_ino, result);
+ return result;
+}
+
+unsigned long
+dev_ino_hash_2 (void const *key)
+{
+ unsigned long result = 0;
+ INTEGER_HASH_2 (((struct dev_ino const *) key)->di_dev, result);
+ INTEGER_HASH_2 (((struct dev_ino const *) key)->di_ino, result);
+ return result;
+}
+
+int
+dev_ino_hash_compare (void const *x, void const *y)
+{
+ int result;
+ INTEGER_COMPARE (((struct dev_ino const *) x)->di_ino,
+ ((struct dev_ino const *) y)->di_ino, result);
+ if (result)
+ return result;
+ INTEGER_COMPARE (((struct dev_ino const *) x)->di_dev,
+ ((struct dev_ino const *) y)->di_dev, result);
+ return result;
+}
+
+#endif
+
+/*******************************************************************/
+
+struct file_link *
+init_walker (struct idhead *idhp)
+{
+ init_idh_obstacks (idhp);
+ init_idh_tables (idhp);
+ return get_current_dir_link ();
+}
+
+void
+init_idh_obstacks (struct idhead *idhp)
+{
+ obstack_init (&idhp->idh_member_file_obstack);
+ obstack_init (&idhp->idh_file_link_obstack);
+#if HAVE_LINK
+ obstack_init (&idhp->idh_dev_ino_obstack);
+#endif
+}
+
+void
+init_idh_tables (struct idhead *idhp)
+{
+ hash_init (&idhp->idh_member_file_table, 16*1024,
+ member_file_hash_1, member_file_hash_2, member_file_hash_compare);
+ hash_init (&idhp->idh_file_link_table, 16*1024,
+ file_link_hash_1, file_link_hash_2, file_link_hash_compare);
+#if HAVE_LINK
+ hash_init (&idhp->idh_dev_ino_table, 16*1024,
+ dev_ino_hash_1, dev_ino_hash_2, dev_ino_hash_compare);
+#endif
+}