2 * globbing.c: interface to the POSIX glob routines
4 * Copyright (C) 1995 Graeme W. Wilford. (Wilf.)
5 * Copyright (C) 2001, 2002, 2003, 2006, 2007, 2008 Colin Watson.
7 * This file is part of man-db.
9 * man-db is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * man-db is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with man-db; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 * Mon Mar 13 20:27:36 GMT 1995 Wilf. (G.Wilford@ee.surrey.ac.uk)
28 #endif /* HAVE_CONFIG_H */
34 #include <sys/types.h>
39 #include "xvasprintf.h"
41 #include "manconfig.h"
44 #include "hashtable.h"
50 const char *extension;
51 static const char *mandir_layout = MANDIR_LAYOUT;
53 static char *make_pattern (const char *name, const char *sec, int opts)
57 if (opts & LFF_REGEX) {
59 char *esc_ext = escape_shell (extension);
60 pattern = xasprintf ("%s\\..*%s.*", name, esc_ext);
63 char *esc_sec = escape_shell (sec);
64 pattern = xasprintf ("%s\\.%s.*", name, esc_sec);
69 pattern = xasprintf ("%s.*%s*", name, extension);
71 pattern = xasprintf ("%s.%s*", name, sec);
80 #define LAYOUT_SOLARIS 8
83 static int parse_layout (const char *layout)
86 return LAYOUT_GNU | LAYOUT_HPUX | LAYOUT_IRIX |
87 LAYOUT_SOLARIS | LAYOUT_BSD;
91 char *upper_layout = xstrdup (layout);
93 for (layoutp = upper_layout; *layoutp; layoutp++)
94 *layoutp = CTYPE (toupper, *layoutp);
96 if (strstr (upper_layout, "GNU"))
98 if (strstr (upper_layout, "HPUX"))
100 if (strstr (upper_layout, "IRIX"))
101 flags |= LAYOUT_IRIX;
102 if (strstr (upper_layout, "SOLARIS"))
103 flags |= LAYOUT_SOLARIS;
104 if (strstr (upper_layout, "BSD"))
112 struct dirent_hashent {
114 size_t names_len, names_max;
117 static void dirent_hashtable_free (void *defn)
119 struct dirent_hashent *hashent = defn;
122 for (i = 0; i < hashent->names_len; ++i)
123 free (hashent->names[i]);
124 free (hashent->names);
128 static struct hashtable *dirent_hash = NULL;
130 static int cache_compare (const void *a, const void *b)
132 const char *left = *(const char **) a;
133 const char *right = *(const char **) b;
134 return strcasecmp (left, right);
137 static struct dirent_hashent *update_directory_cache (const char *path)
139 struct dirent_hashent *cache;
141 struct dirent *entry;
144 dirent_hash = hashtable_create (&dirent_hashtable_free);
145 push_cleanup ((cleanup_fun) hashtable_free, dirent_hash, 0);
147 cache = hashtable_lookup (dirent_hash, path, strlen (path));
149 /* Check whether we've got this one already. */
151 debug ("update_directory_cache %s: hit\n", path);
155 debug ("update_directory_cache %s: miss\n", path);
157 dir = opendir (path);
159 debug_error ("can't open directory %s", path);
163 cache = XMALLOC (struct dirent_hashent);
164 cache->names_len = 0;
165 cache->names_max = 1024;
166 cache->names = XNMALLOC (cache->names_max, char *);
168 /* Dump all the entries into cache->names, resizing if necessary. */
169 for (entry = readdir (dir); entry; entry = readdir (dir)) {
170 if (cache->names_len >= cache->names_max) {
171 cache->names_max *= 2;
173 xnrealloc (cache->names, cache->names_max,
176 cache->names[cache->names_len++] = xstrdup (entry->d_name);
179 qsort (cache->names, cache->names_len, sizeof *cache->names,
182 hashtable_install (dirent_hash, path, strlen (path), cache);
188 struct pattern_bsearch {
193 static int pattern_compare (const void *a, const void *b)
195 const struct pattern_bsearch *key = a;
196 const char *memb = *(const char **) b;
197 return strncasecmp (key->pattern, memb, key->len);
200 static void clear_glob (glob_t *pglob)
202 /* look_for_file declares this static, so it's zero-initialised.
203 * globfree() can deal with checking it before freeing.
208 pglob->gl_pathv = NULL;
212 static void match_in_directory (const char *path, const char *pattern, int opts,
213 glob_t *pglob, size_t *allocated)
215 struct dirent_hashent *cache;
216 size_t my_allocated = 0;
219 struct pattern_bsearch pattern_start = { NULL, -1 };
224 allocated = &my_allocated;
228 cache = update_directory_cache (path);
230 debug ("directory cache update failed\n");
234 debug ("globbing pattern in %s: %s\n", path, pattern);
238 pglob->gl_pathv = XNMALLOC (*allocated, char *);
239 pglob->gl_pathv[0] = NULL;
242 if (opts & LFF_REGEX)
243 flags = REG_EXTENDED | REG_NOSUB |
244 ((opts & LFF_MATCHCASE) ? 0 : REG_ICASE);
246 flags = (opts & LFF_MATCHCASE) ? 0 : FNM_CASEFOLD;
248 if (opts & LFF_REGEX) {
249 xregcomp (&preg, pattern, flags);
250 bsearched = cache->names;
252 pattern_start.pattern = xstrndup (pattern,
253 strcspn (pattern, "?*{}\\"));
254 pattern_start.len = strlen (pattern_start.pattern);
255 bsearched = bsearch (&pattern_start, cache->names,
256 cache->names_len, sizeof *cache->names,
259 free (pattern_start.pattern);
262 while (bsearched > cache->names &&
263 !strncasecmp (pattern_start.pattern, *(bsearched - 1),
268 for (i = bsearched - cache->names; i < cache->names_len; ++i) {
269 if (opts & LFF_REGEX) {
270 if (regexec (&preg, cache->names[i], 0, NULL, 0) != 0)
273 if (strncasecmp (pattern_start.pattern,
274 cache->names[i], pattern_start.len))
277 if (fnmatch (pattern, cache->names[i], flags) != 0)
281 debug ("matched: %s/%s\n", path, cache->names[i]);
283 if (pglob->gl_pathc >= *allocated) {
285 pglob->gl_pathv = xnrealloc (
286 pglob->gl_pathv, *allocated, sizeof (char *));
288 pglob->gl_pathv[pglob->gl_pathc++] =
289 xasprintf ("%s/%s", path, cache->names[i]);
292 if (opts & LFF_REGEX)
295 free (pattern_start.pattern);
297 if (pglob->gl_pathc >= *allocated) {
299 pglob->gl_pathv = xnrealloc (pglob->gl_pathv,
300 *allocated, sizeof (char *));
302 pglob->gl_pathv[pglob->gl_pathc] = NULL;
307 char **look_for_file (const char *hier, const char *sec,
308 const char *unesc_name, int cat, int opts)
310 char *pattern, *path = NULL;
312 static int cleanup_installed = 0;
313 static int layout = -1;
316 if (!cleanup_installed) {
317 /* appease valgrind */
318 push_cleanup ((cleanup_fun) globfree, &gbuf, 0);
319 cleanup_installed = 1;
324 /* This routine only does a minimum amount of matching. It does not
325 find cat files in the alternate cat directory. */
328 layout = parse_layout (mandir_layout);
329 debug ("Layout is %s (%d)\n", mandir_layout, layout);
332 if (opts & (LFF_REGEX | LFF_WILDCARD))
333 name = xstrdup (unesc_name);
335 name = escape_shell (unesc_name);
337 /* allow lookups like "3x foo" to match "../man3/foo.3x" */
339 if (layout & LAYOUT_GNU) {
342 size_t allocated = 0;
344 memset (&dirs, 0, sizeof (dirs));
345 pattern = xasprintf ("%s\t*", cat ? "cat" : "man");
346 *strrchr (pattern, '\t') = *sec;
347 match_in_directory (hier, pattern, LFF_MATCHCASE, &dirs, NULL);
350 pattern = make_pattern (name, sec, opts);
351 for (i = 0; i < dirs.gl_pathc; ++i) {
354 match_in_directory (dirs.gl_pathv[i], pattern, opts,
361 /* Try HPUX style compressed man pages */
362 if ((layout & LAYOUT_HPUX) && gbuf.gl_pathc == 0) {
365 path = appendstr (path, hier, cat ? "/cat" : "/man",
366 sec, ".Z", (void *) 0);
367 pattern = make_pattern (name, sec, opts);
369 match_in_directory (path, pattern, opts, &gbuf, NULL);
373 /* Try man pages without the section extension --- IRIX man pages */
374 if ((layout & LAYOUT_IRIX) && gbuf.gl_pathc == 0) {
377 path = appendstr (path, hier, cat ? "/cat" : "/man", sec,
379 if (opts & LFF_REGEX)
380 pattern = xasprintf ("%s\\..*", name);
382 pattern = xasprintf ("%s.*", name);
384 match_in_directory (path, pattern, opts, &gbuf, NULL);
388 /* Try Solaris style man page directories */
389 if ((layout & LAYOUT_SOLARIS) && gbuf.gl_pathc == 0) {
392 /* TODO: This needs to be man/sec*, not just man/sec. */
393 path = appendstr (path, hier, cat ? "/cat" : "/man", sec,
395 pattern = make_pattern (name, sec, opts);
397 match_in_directory (path, pattern, opts, &gbuf, NULL);
401 /* BSD cat pages take the extension .0 */
402 if ((layout & LAYOUT_BSD) && gbuf.gl_pathc == 0) {
406 path = appendstr (path, hier, "/cat", sec, (void *) 0);
407 if (opts & LFF_REGEX)
408 pattern = xasprintf ("%s\\.0.*", name);
410 pattern = xasprintf ("%s.0*", name);
412 path = appendstr (path, hier, "/man", sec, (void *) 0);
413 pattern = make_pattern (name, sec, opts);
415 match_in_directory (path, pattern, opts, &gbuf, NULL);
422 if (gbuf.gl_pathc == 0)
425 return gbuf.gl_pathv;
428 char **expand_path (const char *path)
431 char **result = NULL;
435 res = glob (path, GLOB_NOCHECK, NULL, &globbuf);
436 /* if glob failed, return the given path */
438 result = XNMALLOC (2, char *);
439 result[0] = xstrdup (path);
444 result = XNMALLOC (globbuf.gl_pathc + 1, char *);
445 for (i = 0; i < globbuf.gl_pathc; i++) {
446 result[i] = xstrdup (globbuf.gl_pathv[i]);
448 result[globbuf.gl_pathc] = NULL;