diff options
Diffstat (limited to 'lib/pathsrch.c')
-rw-r--r-- | lib/pathsrch.c | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/lib/pathsrch.c b/lib/pathsrch.c new file mode 100644 index 0000000..e513df5 --- /dev/null +++ b/lib/pathsrch.c @@ -0,0 +1,509 @@ +/* pathsrch.c: look for files based on paths, i.e., colon-separated + lists of directories. + + We should allow % specifiers in the paths for the resolution, mode + name, etc. + +Copyright (C) 1992, 93 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "config.h" + +#include <kpathsea/c-stat.h> +#include "c-pathch.h" +#include "c-namemx.h" +#include "c-pathmx.h" +#include "paths.h" + +#include <kpathsea/c-ctype.h> +#if !defined (DOS) && !defined (VMS) && !defined (VMCMS) +#include <pwd.h> +#endif +#include "dirio.h" +#include "pathsrch.h" + +static boolean absolute_p P1H(string filename); +static void add_directory P3H(string **, unsigned *, string); +static void expand_subdir P3H(string **, unsigned *, string); +static string *find_dir_list P1H(string); +static string readable P1H(string); +static void save_dir_list P2H(string, string *); +static string truncate_pathname P1H(string); + +/* If FILENAME is absolute or explicitly relative (i.e., starts with + `/', `./', or `../'), or if DIR_LIST is null, we return whether + FILENAME is readable as-is. Otherwise, we test if FILENAME is in any of + the directories listed in DIR_LIST. (The last entry of DIR_LIST must + be null.) We return the complete path if found, NULL else. + + In the interests of doing minimal work here, we assume that each + element of DIR_LIST already ends with a `/'. + + DIR_LIST is most conveniently made by calling `initialize_path_list'. + This is a separate routine because we allow recursive searching, and + it may take some time to discover the list of directories. + We do not want to incur that overhead every time we want to look for + a file. + + (Actually, `/' is not hardwired into this routine; we use PATH_SEP, + defined above.) */ + +string +find_path_filename P2C(string, filename, string *, dir_list) +{ + string found_name = NULL; + + /* Do this before testing for absolute-ness, as a leading ~ will be an + absolute pathname. */ + filename = expand_tilde (filename); + + /* If FILENAME is absolute or explicitly relative, or if DIR_LIST is + null, only check if FILENAME is readable. */ + if (absolute_p (filename) || dir_list == NULL) + found_name = readable (filename); + else + { /* Test if FILENAME is in any of the directories in DIR_LIST. */ + string save_filename = filename; + + while (*dir_list != NULL) + { + filename = concat (*dir_list, save_filename); + found_name = readable (filename); + if (found_name == NULL) + { + free (filename); + dir_list++; + } + else + { + if (found_name != filename) + free (filename); + break; + } + } + } + + return found_name; +} + + +/* If NAME is readable, return it. If the error is ENAMETOOLONG, + truncate any too-long path components and return the result (unless + there were no too-long components, i.e., a overall too-long name + caused the error, in which case return NULL). On any other error, + return NULL. + + POSIX invented this brain-damage of not necessarily truncating + pathname components; the system's behavior is defined by the value of + the symbol _POSIX_NO_TRUNC, but you can't change it dynamically! */ + +static string +readable (name) + string name; +{ + string ret; + + if (access (name, R_OK) == 0 && !dir_p (name)) + ret = name; +#ifdef ENAMETOOLONG + else if (errno == ENAMETOOLONG) + { + ret = truncate_pathname (name); + + /* Perhaps some other error will occur with the truncated name, so + let's call access again. */ + if (!(access (ret, R_OK) == 0 && !dir_p (ret))) + { /* Failed. */ + free (ret); + ret = NULL; + } + } +#endif + else + ret = NULL; /* Some other error. */ + + return ret; +} + + + +/* Truncate any too-long path components in NAME, returning the result. */ + +static string +truncate_pathname (name) + string name; +{ + unsigned c_len = 0; /* Length of current component. */ + unsigned ret_len = 0; /* Length of constructed result. */ + string ret = (string) xmalloc (PATH_MAX + 1); + + for (; *name; name++) + { + if (IS_PATH_SEP (*name)) + { /* At a directory delimiter, reset component length. */ + c_len = 0; + } + else if (c_len > NAME_MAX) + { /* If past the max for a component, ignore this character. */ + continue; + } + + /* If we've already copied the max, give up. */ + if (ret_len == PATH_MAX) + { + free (ret); + return NULL; + } + + /* Copy this character. */ + ret[ret_len++] = *name; + c_len++; + } + ret[ret_len] = 0; + + return ret; +} + + +/* Return true if FILENAME is absolute or explicitly relative, else false. */ + +static boolean +absolute_p P1C(string, filename) +{ + boolean absolute = IS_PATH_SEP (*filename) +#ifdef DOS + || ISALPHA (*filename) && filename[1] == ':' +#endif + ; + boolean explicit_relative + = (*filename == '.' + && (IS_PATH_SEP (filename[1]) + || (filename[1] == '.' && IS_PATH_SEP (filename[2])))); + + return absolute || explicit_relative; +} + +/* Return a NULL-terminated array of directory names, each name ending + with PATH_SEP, created by parsing the PATH_DELIMITER-separated list + in the value of the environment variable ENV_NAME, or DEFAULT_PATH if + the envvar is not set. + + A leading or trailing colon in the value of ENV_NAME is replaced by + DEFAULT_PATH. + + Any element of the path that ends with double PATH_SEP characters + (e.g., `foo//') is replaced by all its subdirectories. + + If ENV_NAME is null, only parse DEFAULT_PATH. If both are null, do + nothing and return NULL. */ + +string * +initialize_path_list P2C(string, env_name, string, default_path) +{ + string dir, path; + string *dir_list; + unsigned dir_count = 0; + string env_value = env_name ? getenv (env_name) : NULL; + string orig_path = expand_default (env_value, default_path); + + if (orig_path == NULL || *orig_path == 0) + return NULL; + + /* If we've already seen this colon-separated list, then just get it + back instead of going back to the filesystem. */ + dir_list = find_dir_list (orig_path); + if (dir_list != NULL) + return dir_list; + + /* Be sure `path' is in writable memory. */ + path = (orig_path == env_value || orig_path == default_path + ? xstrdup (orig_path) : orig_path); + + /* Find each element in the path in turn. */ + for (dir = strtok (path, PATH_DELIMITER_STRING); dir != NULL; + dir = strtok (NULL, PATH_DELIMITER_STRING)) + { + int len; + /* If the path starts with ~ or ~user, expand it. Do this + before calling `expand_subdir' or `add_directory', so that + 1) we don't expand the same ~ for every subdirectory; and + 2) pathnames in `expand_subdir' don't have a `~' in them + (since the system won't grok `~/foo' as a directory). */ + dir = expand_tilde (dir); + len = strlen (dir); + + /* If `dir' is the empty string, ignore it. */ + if (len == 0) + continue; + + /* If `dir' ends in double slashes, do subdirectories (and remove + the second slash, so the final pathnames we return don't look + like foo//bar). Because we obviously want to do subdirectories + of `dir', we don't check if it is a leaf. This means that if + `dir' is `foo//', and `foo' contains only symlinks (so our leaf + test below would be true), the symlinks are chased. */ + if (len > 2 && IS_PATH_SEP (dir[len - 1]) && IS_PATH_SEP (dir[len - 2])) + { + dir[len - 1] = 0; + if (dir_p (dir)) + { + add_directory (&dir_list, &dir_count, dir); + expand_subdir (&dir_list, &dir_count, dir); + } + } + else + { /* Don't bother to add the directory if it doesn't exist. */ + if (dir_p (dir)) + add_directory (&dir_list, &dir_count, dir); + } + } + + /* Add the terminating null entry to `dir_list'. */ + dir_count++; + XRETALLOC (dir_list, dir_count, string); + dir_list[dir_count - 1] = NULL; + + /* Save the directory list we just found. */ + save_dir_list (orig_path, dir_list); + + return dir_list; +} + +/* Subroutines for `initialize_path_list'. */ + +/* Add a newly-allocated copy of DIR to the end of the array pointed to + by DIR_LIST_PTR. Increment DIR_COUNT_PTR. Append a `/' to DIR if + necessary. We assume DIR is a directory, to avoid unnecessary an + unnecessary call to `stat'. */ + +static void +add_directory (dir_list_ptr, dir_count_ptr, dir) + string **dir_list_ptr; + unsigned *dir_count_ptr; + string dir; +{ + /* If `dir' does not end with a `/', add it. We can't just + write it in place, since that would overwrite the null that + strtok may have put in. So we always make a copy of DIR. */ + dir = (IS_PATH_SEP (dir[strlen (dir) - 1]) ? xstrdup (dir) + : concat (dir, PATH_SEP_STRING)); + + /* Add `dir' to the list of the directories. */ + (*dir_count_ptr)++; + XRETALLOC (*dir_list_ptr, *dir_count_ptr, string); + (*dir_list_ptr)[*dir_count_ptr - 1] = dir; +} + + +/* Add DIRNAME to DIR_LIST and look for subdirectories, recursively. We + assume DIRNAME is the name of a directory. */ + +static void +expand_subdir (dir_list_ptr, dir_count_ptr, dirname) + string **dir_list_ptr; + unsigned *dir_count_ptr; + string dirname; +{ + DIR *dir; + struct dirent *e; + unsigned length; + char potential[PATH_MAX]; + struct stat st; + + /* We will be looking at its contents. */ + dir = opendir (dirname); + if (dir == NULL) + return; + + /* Compute the length of DIRNAME, since it's loop-invariant. */ + length = strlen (dirname); + + /* Construct the part of the pathname that doesn't change. */ + strcpy (potential, dirname); + if (!IS_PATH_SEP (potential[length - 1])) + { + potential[length] = PATH_SEP; + potential[length + 1] = 0; + length++; + } + + while ((e = readdir (dir)) != NULL) + { /* If it's . or .., never mind. */ + if (!(e->d_name[0] == '.' + && (e->d_name[1] == 0 + || (e->d_name[1] == '.' && e->d_name[2] == 0)))) + { /* If it's not a directory, we will skip it on the + recursive call. */ + strcat (potential, e->d_name); + + /* If we can't stat it, or if it isn't a directory, continue. */ + if (stat (potential, &st) == 0 && S_ISDIR (st.st_mode)) + { /* It's a subdirectory; add `potential' to the list. */ + add_directory (dir_list_ptr, dir_count_ptr, potential); + + /* If it's not a leaf, quit. Assume that leaf + directories have two links (one for . and one for ..). + This means that symbolic links to directories do not affect + the leaf-ness. This is arguably wrong, but the only + alternative I know of is to stat every entry in the + directory, and that is unacceptably slow. + + The #ifdef here at least makes this configurable at + compile-time, so that if we're using VMS directories or + some such, we can still find subdirectories, even if it + is much slower. */ +#ifdef UNIX_ST_NLINK + if (st.st_nlink > 2) +#endif + { /* All criteria are met; find subdirectories. */ + expand_subdir (dir_list_ptr, dir_count_ptr, potential); + } + } + + /* ``Remove'' the directory entry name. */ + potential[length] = 0; + } + } + + closedir (dir); +} + +/* These routines, while not strictly needed to be exported, are + plausibly useful to be called by outsiders. */ + +/* Replace a leading or trailing `:' in ENV_PATH with DEFAULT_PATH. If + neither is present, return ENV_PATH if that is non-null, else + DEFAULT_PATH. */ + +string +expand_default (env_path, default_path) + string env_path; + string default_path; +{ + string expansion; + + if (env_path == NULL) + expansion = default_path; + else if (*env_path == PATH_DELIMITER) + expansion = concat (default_path, env_path); + else if (env_path[strlen (env_path) - 1] == PATH_DELIMITER) + expansion = concat (env_path, default_path); + else + expansion = env_path; + + return expansion; +} + + +/* Expand a leading ~ or ~user, Unix-style, unless we are some weirdo + operating system. */ + +string +expand_tilde P1C(string, name) +{ +#if defined (DOS) || defined (VMS) || defined (VMCMS) + return name; +#else + string expansion; + string home; + + /* If no leading tilde, do nothing. */ + if (*name != '~') + expansion = name; + + /* If `~' or `~/', use $HOME if it exists, or `.' if it doesn't. */ + else if (IS_PATH_SEP (name[1]) || name[1] == 0) + { + home = getenv ("HOME"); + if (home == NULL) + home = "."; + + expansion + = name[1] == 0 ? home : concat3 (home, PATH_SEP_STRING, name + 2); + } + + /* If `~user' or `~user/', look up user in the passwd database. */ + else + { + struct passwd *p; + string user; + unsigned c = 2; + while (!IS_PATH_SEP (name[c]) && name[c] != 0) + c++; + + user = (string) xmalloc (c); + strncpy (user, name + 1, c - 1); + user[c - 1] = 0; + + /* We only need the cast here for those (old deficient) systems + which do not declare `getpwnam' in <pwd.h>. */ + p = (struct passwd *) getpwnam (user); + free (user); + /* If no such user, just use `.'. */ + home = p == NULL ? "." : p->pw_dir; + + expansion = name[c] == 0 ? home : concat (home, name + c); + } + + return expansion; +#endif /* not (DOS or VMS or VM/CMS) */ +} + +/* Routines to save and retrieve a directory list keyed by the original + colon-separated path. This is useful because 1) it can take a + significant amount of time to discover all the subdirectories of a + given directory, and 2) many paths all have the same basic default, + and thus would recompute the directory list. */ + +typedef struct +{ + string path; + string *dir_list; +} saved_path_entry; + +static saved_path_entry *saved_paths = NULL; +static unsigned saved_paths_length = 0; + + +/* We implement the data structure as a simple linear list, since it's + unlikely to ever be more than a dozen or so elements long. We don't + bother to check here if PATH has already been saved; we always add it + to our list. */ + +static void +save_dir_list P2C(string, path, string *, dir_list) +{ + saved_paths_length++; + XRETALLOC (saved_paths, saved_paths_length, saved_path_entry); + saved_paths[saved_paths_length - 1].path = path; + saved_paths[saved_paths_length - 1].dir_list = dir_list; +} + +/* When we retrieve, just check the list in order. */ + +static string * +find_dir_list P1C(string, path) +{ + unsigned p; + + for (p = 0; p < saved_paths_length; p++) + { + if (strcmp (saved_paths[p].path, path) == 0) + return saved_paths[p].dir_list; + } + + return NULL; +} |