summaryrefslogtreecommitdiffstats
path: root/lib/idwalk.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/idwalk.c')
-rw-r--r--lib/idwalk.c1189
1 files changed, 1189 insertions, 0 deletions
diff --git a/lib/idwalk.c b/lib/idwalk.c
new file mode 100644
index 0000000..77aacf2
--- /dev/null
+++ b/lib/idwalk.c
@@ -0,0 +1,1189 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#ifndef FNM_FILE_NAME
+#define FNM_FILE_NAME FNM_PATHNAME
+#endif
+
+#define DEBUG(args) /* printf args */
+
+#include <config.h>
+#include "system.h"
+#include "idfile.h"
+#include "error.h"
+#include "alloc.h"
+#include "dynvec.h"
+#include "strxtra.h"
+#include "scanners.h"
+#include "pathmax.h"
+
+int walk_dir __P((struct file_link *dir_link));
+void walk_flink __P((struct file_link *flink, struct dynvec *sub_dirs_vec));
+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));
+int classify_link __P((struct file_link *flink, struct stat *st));
+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));
+static int same_as_dot __P((char const *cwd));
+int chdir_to_link __P((struct file_link* dir_link));
+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 const *maybe_relative_path __P((char *buffer, struct file_link const *to_link, struct file_link const *from_link));
+char *fill_dot_dots __P((char *buf, int levels));
+char *absolute_path __P((char *buffer, struct file_link const *flink));
+static char *absolute_path_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));
+int file_link_qsort_compare __P((void const *x, void const *y));
+int links_depth __P((struct file_link const *flink));
+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));
+
+/* Interpret `HAVE_LINK' as meaning `UN*X style' directory structure
+ (e.g., A single root called `/', with `/' separating links), and
+ !HAVE_LINK as `DOS|OS/2|Windows style' (e.g., Multiple root volues
+ named `x:', with `\' separating links). */
+
+#if HAVE_LINK
+struct file_link *find_alias_link __P((struct file_link *flink, struct stat *st));
+struct member_file *maybe_get_member_file __P((struct file_link *flink, struct stat *st));
+struct member_file *find_member_file __P((struct file_link const *flink));
+# define IS_ABSOLUTE(_dir_) ((_dir_)[0] == '/')
+# define SLASH_STRING "/"
+# define SLASH_CHAR '/'
+# define DOT_DOT_SLASH "../"
+# define MAYBE_FNM_CASEFOLD 0
+#else
+/* NEEDSWORK: prefer forward-slashes as a user-configurable option. */
+# define IS_ABSOLUTE(_dir_) ((_dir_)[1] == ':')
+# define SLASH_STRING "\\/"
+# define SLASH_CHAR '\\'
+# define DOT_DOT_SLASH "..\\"
+# define MAYBE_FNM_CASEFOLD FNM_CASEFOLD
+#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;
+
+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)
+{
+ char buf[PATH_MAX];
+ int scannable_files;
+ struct dynvec *sub_dirs_vec;
+ DIR *dirp;
+
+ if (!chdir_to_link (dir_link))
+ return 0;
+ dirp = opendir (".");
+ if (dirp == 0)
+ {
+ error (0, errno, _("can't read directory `%s' (`.' from `%s')"),
+ absolute_path (buf, dir_link), 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);
+ 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)
+{
+ char buf[PATH_MAX];
+ 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))
+ error (0, 0, _("notice: `%s' was a %s, but is now a %s!"),
+ absolute_path (buf, flink),
+ (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))
+ {
+ if (sub_dirs_vec == 0)
+ walk_dir (flink);
+ else if (!(new_flags & FL_SYM_LINK)) /* NEEDSWORK: optinally ignore? */
+ 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;
+#if 0
+ member->mf_modify_time = st.st_mtime;
+ member->mf_access_time = st.st_atime;
+ if (member->mf_old_index < 0 || st.st_mtime > idh.idh_mod_time)
+ member->mf_scan_index = 0;
+#endif
+ }
+}
+
+/****************************************************************************/
+/* Serialize and write a file_link hierarchy. */
+
+void
+serialize_file_links (struct idhead *idhp)
+{
+ struct file_link **flinks_0;
+ struct file_link **flinks;
+ struct file_link **end;
+ struct file_link **parents_0;
+ struct file_link **parents;
+ unsigned long parent_index = 0;
+
+ flinks_0 = (struct file_link **) hash_dump (&idhp->idh_file_link_table,
+ 0, file_link_qsort_compare);
+ end = &flinks_0[idhp->idh_file_link_table.ht_fill];
+ parents = parents_0 = MALLOC (struct file_link *, idhp->idh_file_link_table.ht_fill);
+ for (flinks = flinks_0; flinks < end; flinks++)
+ {
+ struct file_link *flink = *flinks;
+ if (!(flink->fl_flags & FL_USED))
+ break;
+ io_write (idhp->idh_FILE, flink->fl_name, 0, IO_TYPE_STR);
+ io_write (idhp->idh_FILE, &flink->fl_flags, sizeof (flink->fl_flags), IO_TYPE_INT);
+ io_write (idhp->idh_FILE, (IS_ROOT_FILE_LINK (flink)
+ ? &parent_index : &flink->fl_parent->fl_index),
+ FL_PARENT_INDEX_BYTES, IO_TYPE_INT);
+ *parents++ = flink->fl_parent; /* save parent link before clobbering */
+ flink->fl_index = parent_index++;
+ }
+ /* restore parent links */
+ for ((flinks = flinks_0), (parents = parents_0); flinks < end; flinks++)
+ {
+ struct file_link *flink = *flinks;
+ if (!(flink->fl_flags & FL_USED))
+ break;
+ flink->fl_parent = *parents++;
+ }
+ free (parents_0);
+ free (flinks_0);
+ idhp->idh_file_links = parent_index;
+ idhp->idh_files = idhp->idh_member_file_table.ht_fill;
+}
+
+/* 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);
+}
+
+/* Read and reconstruct a serialized file_link hierarchy. */
+
+struct file_link **
+deserialize_file_links (struct idhead *idhp)
+{
+ struct file_link **flinks_0 = MALLOC (struct file_link *, idhp->idh_file_links);
+ struct file_link **flinks = flinks_0;
+ struct file_link **members_0 = MALLOC (struct file_link *, idhp->idh_files + 1);
+ struct file_link **members = members_0;
+ struct file_link *flink;
+ struct file_link **slot;
+ int i;
+
+ for (i = 0; i < idhp->idh_file_links; i++)
+ {
+ unsigned long parent_index;
+ int c;
+
+ obstack_blank (&idhp->idh_file_link_obstack, offsetof (struct file_link, fl_name));
+ if (obstack_room (&idhp->idh_file_link_obstack) >= idhp->idh_max_link)
+ do
+ {
+ c = getc (idhp->idh_FILE);
+ obstack_1grow_fast (&idhp->idh_file_link_obstack, c);
+ }
+ while (c);
+ else
+ do
+ {
+ c = getc (idhp->idh_FILE);
+ obstack_1grow (&idhp->idh_file_link_obstack, c);
+ }
+ while (c);
+ flink = (struct file_link *) obstack_finish (&idhp->idh_file_link_obstack);
+ *flinks = flink;
+ io_read (idhp->idh_FILE, &flink->fl_flags, sizeof (flink->fl_flags), IO_TYPE_INT);
+ io_read (idhp->idh_FILE, &parent_index, FL_PARENT_INDEX_BYTES, IO_TYPE_INT);
+ flink->fl_parent = flinks_0[parent_index];
+ slot = (struct file_link **) hash_find_slot (&idhp->idh_file_link_table, flink);
+ if (HASH_VACANT (*slot))
+ hash_insert_at (&idhp->idh_file_link_table, flink, slot);
+ else
+ {
+ obstack_free (&idhp->idh_file_link_obstack, flink);
+ (*slot)->fl_flags = flink->fl_flags;
+ flink = *flinks = *slot;
+ }
+ flinks++;
+ if (flink->fl_flags & FL_MEMBER)
+ *members++ = flink;
+ }
+ free (flinks_0);
+ *members = 0;
+ return 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 *st)
+{
+ char buf[PATH_MAX];
+ 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, st);
+ if (alias_link)
+ alias_member = find_member_file (alias_link);
+
+ if (member && alias_member)
+ {
+ char alias_buf[PATH_MAX];
+ int ancestry = symlink_ancestry (flink);
+ int alias_ancestry = symlink_ancestry (alias_link);
+ if (member->mf_lang_args != alias_member->mf_lang_args)
+ error (0, 0, _("warning: `%s' and `%s' are the same file, but yield different scans!"),
+ absolute_path (buf, flink), absolute_path (alias_buf, alias_link));
+ 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 *st)
+{
+ 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 = st->st_dev;
+ dev_ino->di_ino = st->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)
+{
+ char buf[PATH_MAX];
+ struct member_file *member;
+ struct member_file **slot;
+ struct lang_args const *args;
+
+ args = get_lang_args (flink);
+ if (args == 0)
+ {
+ DEBUG (("%s <IGNORE>\n", absolute_path (buf, flink)));
+ return 0;
+ }
+ DEBUG (("%s <%s> <%s>\n", absolute_path (buf, flink),
+ args->la_language->lg_name, (args->la_args_string
+ ? args->la_args_string : "")));
+
+ 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)
+ {
+ error (0, 0, _("notice: scan parameters changed for `%s'"),
+ absolute_path (buf, flink));
+ 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 buf[PATH_MAX];
+ absolute_path (buf, flink);
+ if (fnmatch (args->la_pattern, buf, 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->la_language ? lang_args_default : 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;
+
+ 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 ();
+
+ for (;;)
+ {
+ char const* link_name = strtok (file_name, SLASH_STRING);
+ if (link_name == 0)
+ break;
+ file_name = 0;
+ 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);
+ }
+ }
+ 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;
+
+ 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
+ for (;;)
+ {
+ struct stat st;
+ char const* link_name = strtok (cwd, SLASH_STRING);
+ if (link_name == 0)
+ break;
+ cwd = 0;
+ 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);
+ 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_buf[PATH_MAX];
+ char from_buf[PATH_MAX];
+
+ if (current_dir_link == dir_link)
+ return 1;
+
+ if (current_dir_link)
+ maybe_relative_path (to_buf, dir_link, current_dir_link);
+ else
+ absolute_path (to_buf, dir_link);
+ if (chdir (to_buf) < 0)
+ {
+ if (IS_ABSOLUTE (to_buf))
+ error (0, errno, _("can't chdir to `%s'"), to_buf);
+ else
+ error (0, errno, _("can't chdir to `%s' from `%s'"), to_buf,
+ absolute_path (from_buf, current_dir_link));
+ return 0;
+ }
+ else
+ {
+ current_dir_link = dir_link;
+ return 1;
+ }
+}
+
+/****************************************************************************/
+/* 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 *st)
+{
+ char buf[PATH_MAX];
+ unsigned int flags = 0;
+
+ if (!chdir_to_link (flink->fl_parent))
+ return 0;
+
+#ifdef S_IFLNK
+ if (lstat (flink->fl_name, st) < 0)
+ {
+ error (0, errno, _("can't lstat `%s' from `%s'"), flink->fl_name, xgetcwd ());
+ return 0;
+ }
+ if (S_ISLNK (st->st_mode))
+ {
+#endif
+ if (stat (flink->fl_name, st) < 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 (st->st_mode))
+ flags |= FL_TYPE_DIR;
+ else if (S_ISREG (st->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 const *
+maybe_relative_path (char *buf_0, struct file_link const *to_link, struct file_link const *from_link)
+{
+ struct file_link const *to_link_vec_0[PATH_MAX/2];
+ struct file_link const *from_link_vec_0[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;
+ char *buf;
+ int levels;
+
+ if (from_link == 0)
+ from_link = current_dir_link;
+
+ /* Optimize common cases. */
+ if (to_link == from_link)
+ return strcpy (buf_0, ".");
+ else if (to_link->fl_parent == from_link)
+ return strcpy (buf_0, to_link->fl_name);
+ else if (from_link->fl_flags & FL_SYM_LINK)
+ return absolute_path (buf_0, to_link);
+ else if (to_link == from_link->fl_parent)
+ return 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);
+ return buf_0;
+ }
+
+ 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)
+ return ".";
+ to_link_vec++;
+ from_link_vec++;
+ }
+ levels = from_link_end - from_link_vec;
+ if (levels >= (from_link_vec - from_link_vec_0))
+ return absolute_path (buf_0, to_link);
+ for (from_links = from_link_vec; *from_links; from_links++)
+ if ((*from_links)->fl_flags & FL_SYM_LINK)
+ return absolute_path (buf_0, to_link);
+ buf = fill_dot_dots (buf_0, levels);
+ while (*to_link_vec)
+ {
+ 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;
+ }
+ 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_path (char *buffer, struct file_link const *flink)
+{
+ char *end = absolute_path_1 (buffer, flink);
+ /* Clip the trailing slash. */
+#if HAVE_LINK
+ if (end > &buffer[1])
+ end--;
+#else
+ if (end > &buffer[3])
+ end--;
+#endif
+ *end = '\0';
+ return buffer;
+}
+
+static char *
+absolute_path_1 (char *buffer, struct file_link const *flink)
+{
+ char *end;
+ if (IS_ROOT_FILE_LINK (flink))
+ end = buffer;
+ else
+ end = absolute_path_1 (buffer, 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 **) x;
+ struct member_file const *mfy = *(struct member_file 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;
+}
+
+/* Collation sequence:
+ - Used before unused.
+ - Among used: breadth-first (dirs before files, parent dirs before children)
+ - Among files: collate by mf_index. */
+
+int
+file_link_qsort_compare (void const *x, void const *y)
+{
+ struct file_link const *flx = *(struct file_link const **) x;
+ struct file_link const *fly = *(struct file_link const **) y;
+ unsigned int x_flags = flx->fl_flags;
+ unsigned int y_flags = fly->fl_flags;
+ int result;
+
+ result = (y_flags & FL_USED) - (x_flags & FL_USED);
+ if (result)
+ return result;
+ if (!(x_flags & FL_USED)) /* If neither link is used, we don't care... */
+ return 0;
+ result = (y_flags & FL_TYPE_DIR) - (x_flags & FL_TYPE_DIR);
+ if (result)
+ return result;
+ result = (y_flags & FL_TYPE_MASK) - (x_flags & FL_TYPE_MASK);
+ if (result)
+ return result;
+ if (FL_IS_FILE (x_flags))
+ {
+ struct member_file *x_member = find_member_file (flx);
+ struct member_file *y_member = find_member_file (fly);
+ return x_member->mf_index - y_member->mf_index;
+ }
+ else
+ {
+ int x_depth = links_depth (flx);
+ int y_depth = links_depth (fly);
+ return (x_depth - y_depth);
+ }
+}
+
+/* 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
+
+/*******************************************************************/
+
+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
+}
+
+
+#if TEST_IDWALK
+/*******************************************************************/
+/* Test program. */
+
+char const *program_name;
+struct idhead idh;
+
+void print_member_file __P((void const *item));
+
+void
+print_member_file (void const *item)
+{
+ char buf[PATH_MAX];
+#define member ((struct member_file const *) item)
+#if 1
+ printf ("%s\n", maybe_relative_path (buf, member->mf_link, 0));
+#else
+ printf ("%ld %ld %s\n", member->mf_access_time, member->mf_modify_time,
+ maybe_relative_path (buf, member->mf_link, 0));
+#endif
+#undef member
+}
+
+void reset_walker (struct idhead *idhp);
+void print_hash_stats (FILE *stream, struct idhead *idhp);
+
+#define obstack_chunk_alloc xmalloc
+#define obstack_chunk_free free
+
+void
+reset_walker (struct idhead *idhp)
+{
+ hash_delete_items (&idhp->idh_member_file_table);
+ hash_delete_items (&idhp->idh_file_link_table);
+#if HAVE_LINK
+ hash_delete_items (&idhp->idh_dev_ino_table);
+#endif
+}
+
+void
+print_hash_stats (FILE *stream, struct idhead *idhp)
+{
+ fprintf (stream, _("Link Table: ")); hash_print_stats (&idhp->idh_file_link_table, stream);
+ fprintf (stream, _("\nFile Table: ")); hash_print_stats (&idhp->idh_member_file_table, stream);
+#if HAVE_LINK
+ fprintf (stream, _("\nDupl Table: ")); hash_print_stats (&idhp->idh_dev_ino_table, stream);
+#endif
+ fputc ('\n', stream);
+}
+
+int
+main (int argc, char **argv)
+{
+ struct file_link *cwd_link;
+
+ program_name = ((argc--, *argv++));
+
+ init_idh_obstacks (&idh);
+ init_idh_tables (&idh);
+
+ parse_language_map (0);
+ cwd_link = get_current_dir_link ();
+ while (argc--)
+ walk_flink (parse_file_name (*argv++, cwd_link), 0);
+
+ chdir_to_link (cwd_link);
+
+#if 0
+ idh.idh_file_name = "idwalk.serial";
+ idh.idh_FILE = fopen (idh.idh_file_name, "w+");
+ if (idh.idh_FILE == 0)
+ error (1, errno, _("can't open `%s' for writing"), idh.idh_file_name);
+
+ printf (">>>>>>>>>>>>>>>> Serialize <<<<<<<<<<<<<<<<\n");
+ hash_map (&idh.idh_member_file_table, print_member_file);
+ printf (">>>>>>>>>>>>>>>> Serialize Stats <<<<<<<<<<<<<<<<\n");
+ print_hash_stats (stdout, &idh);
+
+ serialize_file_links (&idh);
+ reset_walker (&idh);
+ deserialize_file_links (&idh);
+
+ printf (">>>>>>>>>>>>>>>> Deserialize <<<<<<<<<<<<<<<<\n");
+ hash_map (&idh.idh_member_file_table, print_member_file);
+ printf (">>>>>>>>>>>>>>>> Deserialize Stats <<<<<<<<<<<<<<<<\n");
+ print_hash_stats (stdout, &idh);
+
+ printf (">>>>>>>>>>>>>>>> End <<<<<<<<<<<<<<<<\n");
+ fclose (idh.idh_FILE);
+#endif
+ return 0;
+}
+
+#endif
+
+/*
+ TODO:
+ - stream I/O
+ */