(root)/
man-db-2.12.0/
src/
globbing.c
       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  }