diff options
Diffstat (limited to 'src/globbing.c')
-rw-r--r-- | src/globbing.c | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/src/globbing.c b/src/globbing.c new file mode 100644 index 0000000..2fc8e24 --- /dev/null +++ b/src/globbing.c @@ -0,0 +1,453 @@ +/* + * globbing.c: interface to the POSIX glob routines + * + * Copyright (C) 1995 Graeme W. Wilford. (Wilf.) + * Copyright (C) 2001, 2002, 2003, 2006, 2007, 2008 Colin Watson. + * + * This file is part of man-db. + * + * man-db 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 of the License, or + * (at your option) any later version. + * + * man-db 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 man-db; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Mon Mar 13 20:27:36 GMT 1995 Wilf. (G.Wilford@ee.surrey.ac.uk) + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <glob.h> +#include <sys/types.h> +#include <dirent.h> + +#include "fnmatch.h" +#include "regex.h" +#include "xvasprintf.h" + +#include "manconfig.h" + +#include "error.h" +#include "hashtable.h" +#include "cleanup.h" +#include "xregcomp.h" + +#include "globbing.h" + +const char *extension; +static const char *mandir_layout = MANDIR_LAYOUT; + +static char *make_pattern (const char *name, const char *sec, int opts) +{ + char *pattern; + + if (opts & LFF_REGEX) { + if (extension) { + char *esc_ext = escape_shell (extension); + pattern = xasprintf ("%s\\..*%s.*", name, esc_ext); + free (esc_ext); + } else { + char *esc_sec = escape_shell (sec); + pattern = xasprintf ("%s\\.%s.*", name, esc_sec); + free (esc_sec); + } + } else { + if (extension) + pattern = xasprintf ("%s.*%s*", name, extension); + else + pattern = xasprintf ("%s.%s*", name, sec); + } + + return pattern; +} + +#define LAYOUT_GNU 1 +#define LAYOUT_HPUX 2 +#define LAYOUT_IRIX 4 +#define LAYOUT_SOLARIS 8 +#define LAYOUT_BSD 16 + +static int parse_layout (const char *layout) +{ + if (!*layout) + return LAYOUT_GNU | LAYOUT_HPUX | LAYOUT_IRIX | + LAYOUT_SOLARIS | LAYOUT_BSD; + else { + int flags = 0; + + char *upper_layout = xstrdup (layout); + char *layoutp; + for (layoutp = upper_layout; *layoutp; layoutp++) + *layoutp = CTYPE (toupper, *layoutp); + + if (strstr (upper_layout, "GNU")) + flags |= LAYOUT_GNU; + if (strstr (upper_layout, "HPUX")) + flags |= LAYOUT_HPUX; + if (strstr (upper_layout, "IRIX")) + flags |= LAYOUT_IRIX; + if (strstr (upper_layout, "SOLARIS")) + flags |= LAYOUT_SOLARIS; + if (strstr (upper_layout, "BSD")) + flags |= LAYOUT_BSD; + + free (upper_layout); + return flags; + } +} + +struct dirent_hashent { + char **names; + size_t names_len, names_max; +}; + +static void dirent_hashtable_free (void *defn) +{ + struct dirent_hashent *hashent = defn; + size_t i; + + for (i = 0; i < hashent->names_len; ++i) + free (hashent->names[i]); + free (hashent->names); + free (hashent); +} + +static struct hashtable *dirent_hash = NULL; + +static int cache_compare (const void *a, const void *b) +{ + const char *left = *(const char **) a; + const char *right = *(const char **) b; + return strcasecmp (left, right); +} + +static struct dirent_hashent *update_directory_cache (const char *path) +{ + struct dirent_hashent *cache; + DIR *dir; + struct dirent *entry; + + if (!dirent_hash) { + dirent_hash = hashtable_create (&dirent_hashtable_free); + push_cleanup ((cleanup_fun) hashtable_free, dirent_hash, 0); + } + cache = hashtable_lookup (dirent_hash, path, strlen (path)); + + /* Check whether we've got this one already. */ + if (cache) { + debug ("update_directory_cache %s: hit\n", path); + return cache; + } + + debug ("update_directory_cache %s: miss\n", path); + + dir = opendir (path); + if (!dir) { + debug_error ("can't open directory %s", path); + return NULL; + } + + cache = XMALLOC (struct dirent_hashent); + cache->names_len = 0; + cache->names_max = 1024; + cache->names = XNMALLOC (cache->names_max, char *); + + /* Dump all the entries into cache->names, resizing if necessary. */ + for (entry = readdir (dir); entry; entry = readdir (dir)) { + if (cache->names_len >= cache->names_max) { + cache->names_max *= 2; + cache->names = + xnrealloc (cache->names, cache->names_max, + sizeof (char *)); + } + cache->names[cache->names_len++] = xstrdup (entry->d_name); + } + + qsort (cache->names, cache->names_len, sizeof *cache->names, + &cache_compare); + + hashtable_install (dirent_hash, path, strlen (path), cache); + closedir (dir); + + return cache; +} + +struct pattern_bsearch { + char *pattern; + size_t len; +}; + +static int pattern_compare (const void *a, const void *b) +{ + const struct pattern_bsearch *key = a; + const char *memb = *(const char **) b; + return strncasecmp (key->pattern, memb, key->len); +} + +static void clear_glob (glob_t *pglob) +{ + /* look_for_file declares this static, so it's zero-initialised. + * globfree() can deal with checking it before freeing. + */ + globfree (pglob); + + pglob->gl_pathc = 0; + pglob->gl_pathv = NULL; + pglob->gl_offs = 0; +} + +static void match_in_directory (const char *path, const char *pattern, int opts, + glob_t *pglob, size_t *allocated) +{ + struct dirent_hashent *cache; + size_t my_allocated = 0; + int flags; + regex_t preg; + struct pattern_bsearch pattern_start = { NULL, -1 }; + char **bsearched; + size_t i; + + if (!allocated) + allocated = &my_allocated; + if (!*allocated) + clear_glob (pglob); + + cache = update_directory_cache (path); + if (!cache) { + debug ("directory cache update failed\n"); + return; + } + + debug ("globbing pattern in %s: %s\n", path, pattern); + + if (!*allocated) { + *allocated = 4; + pglob->gl_pathv = XNMALLOC (*allocated, char *); + pglob->gl_pathv[0] = NULL; + } + + if (opts & LFF_REGEX) + flags = REG_EXTENDED | REG_NOSUB | + ((opts & LFF_MATCHCASE) ? 0 : REG_ICASE); + else + flags = (opts & LFF_MATCHCASE) ? 0 : FNM_CASEFOLD; + + if (opts & LFF_REGEX) { + xregcomp (&preg, pattern, flags); + bsearched = cache->names; + } else { + pattern_start.pattern = xstrndup (pattern, + strcspn (pattern, "?*{}\\")); + pattern_start.len = strlen (pattern_start.pattern); + bsearched = bsearch (&pattern_start, cache->names, + cache->names_len, sizeof *cache->names, + &pattern_compare); + if (!bsearched) { + free (pattern_start.pattern); + return; + } + while (bsearched > cache->names && + !strncasecmp (pattern_start.pattern, *(bsearched - 1), + pattern_start.len)) + --bsearched; + } + + for (i = bsearched - cache->names; i < cache->names_len; ++i) { + if (opts & LFF_REGEX) { + if (regexec (&preg, cache->names[i], 0, NULL, 0) != 0) + continue; + } else { + if (strncasecmp (pattern_start.pattern, + cache->names[i], pattern_start.len)) + break; + + if (fnmatch (pattern, cache->names[i], flags) != 0) + continue; + } + + debug ("matched: %s/%s\n", path, cache->names[i]); + + if (pglob->gl_pathc >= *allocated) { + *allocated *= 2; + pglob->gl_pathv = xnrealloc ( + pglob->gl_pathv, *allocated, sizeof (char *)); + } + pglob->gl_pathv[pglob->gl_pathc++] = + xasprintf ("%s/%s", path, cache->names[i]); + } + + if (opts & LFF_REGEX) + regfree (&preg); + else + free (pattern_start.pattern); + + if (pglob->gl_pathc >= *allocated) { + *allocated *= 2; + pglob->gl_pathv = xnrealloc (pglob->gl_pathv, + *allocated, sizeof (char *)); + } + pglob->gl_pathv[pglob->gl_pathc] = NULL; + + return; +} + +char **look_for_file (const char *hier, const char *sec, + const char *unesc_name, int cat, int opts) +{ + char *pattern, *path = NULL; + static glob_t gbuf; + static int cleanup_installed = 0; + static int layout = -1; + char *name; + + if (!cleanup_installed) { + /* appease valgrind */ + push_cleanup ((cleanup_fun) globfree, &gbuf, 0); + cleanup_installed = 1; + } + + clear_glob (&gbuf); + + /* This routine only does a minimum amount of matching. It does not + find cat files in the alternate cat directory. */ + + if (layout == -1) { + layout = parse_layout (mandir_layout); + debug ("Layout is %s (%d)\n", mandir_layout, layout); + } + + if (opts & (LFF_REGEX | LFF_WILDCARD)) + name = xstrdup (unesc_name); + else + name = escape_shell (unesc_name); + + /* allow lookups like "3x foo" to match "../man3/foo.3x" */ + + if (layout & LAYOUT_GNU) { + glob_t dirs; + size_t i; + size_t allocated = 0; + + memset (&dirs, 0, sizeof (dirs)); + pattern = xasprintf ("%s\t*", cat ? "cat" : "man"); + *strrchr (pattern, '\t') = *sec; + match_in_directory (hier, pattern, LFF_MATCHCASE, &dirs, NULL); + free (pattern); + + pattern = make_pattern (name, sec, opts); + for (i = 0; i < dirs.gl_pathc; ++i) { + if (path) + *path = '\0'; + match_in_directory (dirs.gl_pathv[i], pattern, opts, + &gbuf, &allocated); + } + free (pattern); + globfree (&dirs); + } + + /* Try HPUX style compressed man pages */ + if ((layout & LAYOUT_HPUX) && gbuf.gl_pathc == 0) { + if (path) + *path = '\0'; + path = appendstr (path, hier, cat ? "/cat" : "/man", + sec, ".Z", (void *) 0); + pattern = make_pattern (name, sec, opts); + + match_in_directory (path, pattern, opts, &gbuf, NULL); + free (pattern); + } + + /* Try man pages without the section extension --- IRIX man pages */ + if ((layout & LAYOUT_IRIX) && gbuf.gl_pathc == 0) { + if (path) + *path = '\0'; + path = appendstr (path, hier, cat ? "/cat" : "/man", sec, + (void *) 0); + if (opts & LFF_REGEX) + pattern = xasprintf ("%s\\..*", name); + else + pattern = xasprintf ("%s.*", name); + + match_in_directory (path, pattern, opts, &gbuf, NULL); + free (pattern); + } + + /* Try Solaris style man page directories */ + if ((layout & LAYOUT_SOLARIS) && gbuf.gl_pathc == 0) { + if (path) + *path = '\0'; + /* TODO: This needs to be man/sec*, not just man/sec. */ + path = appendstr (path, hier, cat ? "/cat" : "/man", sec, + (void *) 0); + pattern = make_pattern (name, sec, opts); + + match_in_directory (path, pattern, opts, &gbuf, NULL); + free (pattern); + } + + /* BSD cat pages take the extension .0 */ + if ((layout & LAYOUT_BSD) && gbuf.gl_pathc == 0) { + if (path) + *path = '\0'; + if (cat) { + path = appendstr (path, hier, "/cat", sec, (void *) 0); + if (opts & LFF_REGEX) + pattern = xasprintf ("%s\\.0.*", name); + else + pattern = xasprintf ("%s.0*", name); + } else { + path = appendstr (path, hier, "/man", sec, (void *) 0); + pattern = make_pattern (name, sec, opts); + } + match_in_directory (path, pattern, opts, &gbuf, NULL); + free (pattern); + } + + free (name); + free (path); + + if (gbuf.gl_pathc == 0) + return NULL; + else + return gbuf.gl_pathv; +} + +char **expand_path (const char *path) +{ + int res = 0; + char **result = NULL; + glob_t globbuf; + size_t i; + + res = glob (path, GLOB_NOCHECK, NULL, &globbuf); + /* if glob failed, return the given path */ + if (res != 0) { + result = XNMALLOC (2, char *); + result[0] = xstrdup (path); + result[1] = NULL; + return result; + } + + result = XNMALLOC (globbuf.gl_pathc + 1, char *); + for (i = 0; i < globbuf.gl_pathc; i++) { + result[i] = xstrdup (globbuf.gl_pathv[i]); + } + result[globbuf.gl_pathc] = NULL; + + globfree (&globbuf); + + return result; +} |