1 /*
2 * globbing.c: interface to the POSIX glob routines
3 *
4 * Copyright (C) 1995 Graeme W. Wilford. (Wilf.)
5 * Copyright (C) 2001, 2002, 2003, 2006, 2007, 2008 Colin Watson.
6 *
7 * This file is part of man-db.
8 *
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.
13 *
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.
18 *
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
22 *
23 * Mon Mar 13 20:27:36 GMT 1995 Wilf. (G.Wilford@ee.surrey.ac.uk)
24 */
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif /* HAVE_CONFIG_H */
29
30 #include <assert.h>
31 #include <stdbool.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <ctype.h>
35 #include <glob.h>
36 #include <sys/types.h>
37 #include <dirent.h>
38
39 #include "error.h"
40 #include "fnmatch.h"
41 #include "gl_array_list.h"
42 #include "gl_hash_map.h"
43 #include "gl_xlist.h"
44 #include "gl_xmap.h"
45 #include "regex.h"
46 #include "xalloc.h"
47 #include "xstrndup.h"
48 #include "xvasprintf.h"
49
50 #include "manconfig.h"
51
52 #include "appendstr.h"
53 #include "cleanup.h"
54 #include "debug.h"
55 #include "glcontainers.h"
56 #include "util.h"
57 #include "xregcomp.h"
58
59 #include "globbing.h"
60
61 const char *extension;
62 static const char *mandir_layout = MANDIR_LAYOUT;
63
64 static char *make_pattern (const char *name, const char *sec, int opts)
65 {
66 char *pattern;
67
68 if (opts & LFF_REGEX) {
69 if (extension) {
70 char *esc_ext = escape_shell (extension);
71 pattern = xasprintf ("%s\\..*%s.*", name, esc_ext);
72 free (esc_ext);
73 } else {
74 char *esc_sec = escape_shell (sec);
75 pattern = xasprintf ("%s\\.%s.*", name, esc_sec);
76 free (esc_sec);
77 }
78 } else {
79 if (extension)
80 pattern = xasprintf ("%s.*%s*", name, extension);
81 else
82 pattern = xasprintf ("%s.%s*", name, sec);
83 }
84
85 return pattern;
86 }
87
88 #define LAYOUT_GNU 1
89 #define LAYOUT_HPUX 2
90 #define LAYOUT_IRIX 4
91 #define LAYOUT_SOLARIS 8
92 #define LAYOUT_BSD 16
93
94 static int parse_layout (const char *layout)
95 {
96 if (!*layout)
97 return LAYOUT_GNU | LAYOUT_HPUX | LAYOUT_IRIX |
98 LAYOUT_SOLARIS | LAYOUT_BSD;
99 else {
100 int flags = 0;
101
102 char *upper_layout = xstrdup (layout);
103 char *layoutp;
104 for (layoutp = upper_layout; *layoutp; layoutp++)
105 *layoutp = CTYPE (toupper, *layoutp);
106
107 if (strstr (upper_layout, "GNU"))
108 flags |= LAYOUT_GNU;
109 if (strstr (upper_layout, "HPUX"))
110 flags |= LAYOUT_HPUX;
111 if (strstr (upper_layout, "IRIX"))
112 flags |= LAYOUT_IRIX;
113 if (strstr (upper_layout, "SOLARIS"))
114 flags |= LAYOUT_SOLARIS;
115 if (strstr (upper_layout, "BSD"))
116 flags |= LAYOUT_BSD;
117
118 free (upper_layout);
119 return flags;
120 }
121 }
122
123 struct dirent_names {
124 char **names;
125 size_t names_len, names_max;
126 };
127
128 static void dirent_names_free (const void *value)
129 {
130 struct dirent_names *cache = (struct dirent_names *) value;
131 size_t i;
132
133 for (i = 0; i < cache->names_len; ++i)
134 free (cache->names[i]);
135 free (cache->names);
136 free (cache);
137 }
138
139 static gl_map_t dirent_map = NULL;
140
141 static int cache_compare (const void *a, const void *b)
142 {
143 const char *left = *(const char **) a;
144 const char *right = *(const char **) b;
145 return strcasecmp (left, right);
146 }
147
148 static struct dirent_names *update_directory_cache (const char *path)
149 {
150 struct dirent_names *cache;
151 DIR *dir;
152 struct dirent *entry;
153
154 if (!dirent_map) {
155 dirent_map = new_string_map (GL_HASH_MAP, dirent_names_free);
156 push_cleanup ((cleanup_fun) gl_map_free, dirent_map, 0);
157 }
158 cache = (struct dirent_names *) gl_map_get (dirent_map, path);
159
160 /* Check whether we've got this one already. */
161 if (cache) {
162 debug ("update_directory_cache %s: hit\n", path);
163 return cache;
164 }
165
166 debug ("update_directory_cache %s: miss\n", path);
167
168 dir = opendir (path);
169 if (!dir) {
170 debug_error ("can't open directory %s", path);
171 return NULL;
172 }
173
174 cache = XMALLOC (struct dirent_names);
175 cache->names_len = 0;
176 cache->names_max = 1024;
177 cache->names = XNMALLOC (cache->names_max, char *);
178
179 /* Dump all the entries into cache->names, resizing if necessary. */
180 for (entry = readdir (dir); entry; entry = readdir (dir)) {
181 if (cache->names_len >= cache->names_max) {
182 cache->names_max *= 2;
183 cache->names =
184 xnrealloc (cache->names, cache->names_max,
185 sizeof (char *));
186 }
187 cache->names[cache->names_len++] = xstrdup (entry->d_name);
188 }
189
190 qsort (cache->names, cache->names_len, sizeof *cache->names,
191 &cache_compare);
192
193 gl_map_put (dirent_map, xstrdup (path), cache);
194 closedir (dir);
195
196 return cache;
197 }
198
199 struct pattern_bsearch {
200 char *pattern;
201 size_t len;
202 };
203
204 static int pattern_compare (const void *a, const void *b)
205 {
206 const struct pattern_bsearch *key = a;
207 const char *memb = *(const char **) b;
208 return strncasecmp (key->pattern, memb, key->len);
209 }
210
211 static void match_regex_in_directory (const char *path, const char *pattern,
212 int opts, gl_list_t matched,
213 struct dirent_names *cache)
214 {
215 int flags;
216 regex_t preg;
217 size_t i;
218
219 debug ("matching regex in %s: %s\n", path, pattern);
220
221 flags = REG_EXTENDED | REG_NOSUB |
222 ((opts & LFF_MATCHCASE) ? 0 : REG_ICASE);
223
224 xregcomp (&preg, pattern, flags);
225
226 for (i = 0; i < cache->names_len; ++i) {
227 if (regexec (&preg, cache->names[i], 0, NULL, 0) != 0)
228 continue;
229
230 debug ("matched: %s/%s\n", path, cache->names[i]);
231
232 gl_list_add_last (matched,
233 xasprintf ("%s/%s", path, cache->names[i]));
234 }
235
236 regfree (&preg);
237 }
238
239 static void match_wildcard_in_directory (const char *path, const char *pattern,
240 int opts, gl_list_t matched,
241 struct dirent_names *cache)
242 {
243 int flags;
244 struct pattern_bsearch pattern_start = { NULL, -1 };
245 char **bsearched;
246 size_t i;
247
248 debug ("matching wildcard in %s: %s\n", path, pattern);
249
250 flags = (opts & LFF_MATCHCASE) ? 0 : FNM_CASEFOLD;
251
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,
257 &pattern_compare);
258 if (!bsearched) {
259 free (pattern_start.pattern);
260 return;
261 }
262 while (bsearched > cache->names &&
263 !strncasecmp (pattern_start.pattern, *(bsearched - 1),
264 pattern_start.len))
265 --bsearched;
266
267 for (i = bsearched - cache->names; i < cache->names_len; ++i) {
268 assert (pattern_start.pattern);
269 if (strncasecmp (pattern_start.pattern,
270 cache->names[i], pattern_start.len))
271 break;
272
273 if (fnmatch (pattern, cache->names[i], flags) != 0)
274 continue;
275
276 debug ("matched: %s/%s\n", path, cache->names[i]);
277
278 gl_list_add_last (matched,
279 xasprintf ("%s/%s", path, cache->names[i]));
280 }
281
282 free (pattern_start.pattern);
283 }
284
285 static void match_in_directory (const char *path, const char *pattern,
286 int opts, gl_list_t matched)
287 {
288 struct dirent_names *cache;
289
290 cache = update_directory_cache (path);
291 if (!cache) {
292 debug ("directory cache update failed\n");
293 return;
294 }
295
296 if (opts & LFF_REGEX)
297 match_regex_in_directory (path, pattern, opts, matched, cache);
298 else
299 match_wildcard_in_directory (path, pattern, opts, matched,
300 cache);
301 }
302
303 gl_list_t look_for_file (const char *hier, const char *sec,
304 const char *unesc_name, bool cat, int opts)
305 {
306 gl_list_t matched;
307 char *pattern, *path = NULL;
308 static int layout = -1;
309 char *name;
310
311 matched = new_string_list (GL_ARRAY_LIST, false);
312
313 /* This routine only does a minimum amount of matching. It does not
314 find cat files in the alternate cat directory. */
315
316 if (layout == -1) {
317 layout = parse_layout (mandir_layout);
318 debug ("Layout is %s (%d)\n", mandir_layout, layout);
319 }
320
321 if (opts & (LFF_REGEX | LFF_WILDCARD))
322 name = xstrdup (unesc_name);
323 else
324 name = escape_shell (unesc_name);
325
326 /* allow lookups like "3x foo" to match "../man3/foo.3x" */
327
328 if (layout & LAYOUT_GNU) {
329 gl_list_t dirs;
330 const char *dir;
331
332 dirs = new_string_list (GL_ARRAY_LIST, false);
333 pattern = xasprintf ("%s\t*", cat ? "cat" : "man");
334 assert (pattern);
335 *strrchr (pattern, '\t') = *sec;
336 match_in_directory (hier, pattern, LFF_MATCHCASE, dirs);
337 free (pattern);
338
339 pattern = make_pattern (name, sec, opts);
340 GL_LIST_FOREACH (dirs, dir) {
341 if (path)
342 *path = '\0';
343 match_in_directory (dir, pattern, opts, matched);
344 }
345 free (pattern);
346 gl_list_free (dirs);
347 }
348
349 /* Try HPUX style compressed man pages */
350 if ((layout & LAYOUT_HPUX) && gl_list_size (matched) == 0) {
351 if (path)
352 *path = '\0';
353 path = appendstr (path, hier, cat ? "/cat" : "/man",
354 sec, ".Z", (void *) 0);
355 pattern = make_pattern (name, sec, opts);
356
357 match_in_directory (path, pattern, opts, matched);
358 free (pattern);
359 }
360
361 /* Try man pages without the section extension --- IRIX man pages */
362 if ((layout & LAYOUT_IRIX) && gl_list_size (matched) == 0) {
363 if (path)
364 *path = '\0';
365 path = appendstr (path, hier, cat ? "/cat" : "/man", sec,
366 (void *) 0);
367 if (opts & LFF_REGEX)
368 pattern = xasprintf ("%s\\..*", name);
369 else
370 pattern = xasprintf ("%s.*", name);
371
372 match_in_directory (path, pattern, opts, matched);
373 free (pattern);
374 }
375
376 /* Try Solaris style man page directories */
377 if ((layout & LAYOUT_SOLARIS) && gl_list_size (matched) == 0) {
378 if (path)
379 *path = '\0';
380 /* TODO: This needs to be man/sec*, not just man/sec. */
381 path = appendstr (path, hier, cat ? "/cat" : "/man", sec,
382 (void *) 0);
383 pattern = make_pattern (name, sec, opts);
384
385 match_in_directory (path, pattern, opts, matched);
386 free (pattern);
387 }
388
389 /* BSD cat pages take the extension .0 */
390 if ((layout & LAYOUT_BSD) && gl_list_size (matched) == 0) {
391 if (path)
392 *path = '\0';
393 if (cat) {
394 path = appendstr (path, hier, "/cat", sec, (void *) 0);
395 if (opts & LFF_REGEX)
396 pattern = xasprintf ("%s\\.0.*", name);
397 else
398 pattern = xasprintf ("%s.0*", name);
399 } else {
400 path = appendstr (path, hier, "/man", sec, (void *) 0);
401 pattern = make_pattern (name, sec, opts);
402 }
403 match_in_directory (path, pattern, opts, matched);
404 free (pattern);
405 }
406
407 free (name);
408 free (path);
409
410 return matched;
411 }
412
413 gl_list_t expand_path (const char *path)
414 {
415 int res = 0;
416 gl_list_t result;
417 glob_t globbuf;
418
419 result = new_string_list (GL_ARRAY_LIST, false);
420
421 res = glob (path, GLOB_NOCHECK, NULL, &globbuf);
422 /* if glob failed, return the given path */
423 if (res != 0)
424 gl_list_add_last (result, xstrdup (path));
425 else {
426 size_t i;
427 for (i = 0; i < globbuf.gl_pathc; ++i)
428 gl_list_add_last (result,
429 xstrdup (globbuf.gl_pathv[i]));
430 }
431
432 globfree (&globbuf);
433
434 return result;
435 }