(root)/
man-db-2.12.0/
src/
manp.c
       1  /*
       2   * manp.c: Manpath calculations
       3   *
       4   * Copyright (C) 1990, 1991 John W. Eaton.
       5   * Copyright (C) 1994, 1995 Graeme W. Wilford. (Wilf.)
       6   * Copyright (C) 2001, 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011,
       7   *               2012 Colin Watson.
       8   *
       9   * This file is part of man-db.
      10   *
      11   * man-db is free software; you can redistribute it and/or modify it
      12   * under the terms of the GNU General Public License as published by
      13   * the Free Software Foundation; either version 2 of the License, or
      14   * (at your option) any later version.
      15   *
      16   * man-db is distributed in the hope that it will be useful, but
      17   * WITHOUT ANY WARRANTY; without even the implied warranty of
      18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      19   * GNU General Public License for more details.
      20   *
      21   * You should have received a copy of the GNU General Public License
      22   * along with man-db; if not, write to the Free Software Foundation,
      23   * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      24   *
      25   * John W. Eaton
      26   * jwe@che.utexas.edu
      27   * Department of Chemical Engineering
      28   * The University of Texas at Austin
      29   * Austin, Texas  78712
      30   *
      31   * unpack_locale_bits is derived from _nl_explode_name in libintl:
      32   * Copyright (C) 1995-1998, 2000-2001, 2003, 2005 Free Software Foundation,
      33   * Inc.
      34   * Contributed by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.
      35   * This was originally LGPL v2 or later, but I (Colin Watson) hereby
      36   * exercise my option under section 3 of LGPL v2 to distribute it under the
      37   * GPL v2 or later as above.
      38   *
      39   * Wed May  4 15:44:47 BST 1994 Wilf. (G.Wilford@ee.surrey.ac.uk): changes
      40   * to get_dirlist() and manpath().
      41   *
      42   * This whole code segment is unfriendly and could do with a complete
      43   * overhaul.
      44   */
      45  
      46  #ifdef HAVE_CONFIG_H
      47  #  include "config.h"
      48  #endif /* HAVE_CONFIG_H */
      49  
      50  #include <stdbool.h>
      51  #include <stdio.h>
      52  #include <ctype.h>
      53  #include <sys/types.h>
      54  #include <sys/stat.h>
      55  #include <assert.h>
      56  #include <errno.h>
      57  #include <dirent.h>
      58  #include <stdbool.h>
      59  #include <stdlib.h>
      60  #include <string.h>
      61  #include <unistd.h>
      62  
      63  #include "attribute.h"
      64  #include "canonicalize.h"
      65  #include "error.h"
      66  #include "gl_array_list.h"
      67  #include "gl_linkedhash_list.h"
      68  #include "gl_xlist.h"
      69  #include "xalloc.h"
      70  #include "xgetcwd.h"
      71  #include "xstrndup.h"
      72  #include "xvasprintf.h"
      73  
      74  #include "gettext.h"
      75  #define _(String) gettext (String)
      76  
      77  #include "manconfig.h"
      78  
      79  #include "appendstr.h"
      80  #include "cleanup.h"
      81  #include "debug.h"
      82  #include "fatal.h"
      83  #include "glcontainers.h"
      84  #include "security.h"
      85  #include "util.h"
      86  
      87  #include "manp.h"
      88  #include "globbing.h"
      89  
      90  enum config_flag {
      91  	MANDATORY,
      92  	MANPATH_MAP,
      93  	MANDB_MAP,
      94  	MANDB_MAP_USER,
      95  	DEFINE,
      96  	DEFINE_USER,
      97  	SECTION,
      98  	SECTION_USER
      99  };
     100  
     101  struct config_item {
     102  	char *key;
     103  	char *cont;
     104  	enum config_flag flag;
     105  };
     106  
     107  static gl_list_t config;
     108  
     109  char *user_config_file = NULL;
     110  bool disable_cache;
     111  int min_cat_width = 80, max_cat_width = 80, cat_width = 0;
     112  
     113  static void add_man_subdirs (gl_list_t list, const char *p);
     114  static char *fsstnd (const char *path);
     115  static char *def_path (enum config_flag flag);
     116  static void add_dir_to_list (gl_list_t list, const char *dir);
     117  static void add_dir_to_path_list (gl_list_t list, const char *p);
     118  
     119  
     120  static void config_item_free (const void *elt)
     121  {
     122  	/* gl_list declares the argument as const, but there doesn't seem to
     123  	 * be a good reason for this.
     124  	 */
     125  	struct config_item *item = (struct config_item *) elt;
     126  	free (item->key);
     127  	free (item->cont);
     128  	free (item);
     129  }
     130  
     131  static void add_config (const char *key, const char *cont,
     132  			enum config_flag flag)
     133  {
     134  	struct config_item *item = XMALLOC (struct config_item);
     135  	item->key = xstrdup (key);
     136  	item->cont = xstrdup (cont);
     137  	item->flag = flag;
     138  	gl_list_add_last (config, item);
     139  }
     140  
     141  static const char * ATTRIBUTE_PURE get_config (const char *key,
     142  					       enum config_flag flag)
     143  {
     144  	const struct config_item *item;
     145  	char *cont = NULL;
     146  
     147  	GL_LIST_FOREACH (config, item)
     148  		if (flag == item->flag && STREQ (key, item->key)) {
     149  			cont = item->cont;
     150  			break;
     151  		}
     152  
     153  	return cont;
     154  }
     155  
     156  /* Must not return DEFINEs set in ~/.manpath. This is used to fetch
     157   * definitions used in raised-privilege code; if in doubt, be conservative!
     158   *
     159   * If not setuid, this is identical to get_def_user.
     160   */
     161  const char * ATTRIBUTE_PURE get_def (const char *thing, const char *def)
     162  {
     163  	const char *config_def;
     164  
     165  	if (!running_setuid ())
     166  		return get_def_user (thing, def);
     167  
     168  	config_def = get_config (thing, DEFINE);
     169  	return config_def ? config_def : def;
     170  }
     171  
     172  const char * ATTRIBUTE_PURE get_def_user (const char *thing, const char *def)
     173  {
     174  	const char *config_def = get_config (thing, DEFINE_USER);
     175  	if (!config_def)
     176  		config_def = get_config (thing, DEFINE);
     177  	return config_def ? config_def : def;
     178  }
     179  
     180  static void add_sections (char *sections, bool user)
     181  {
     182  	char *section_list = xstrdup (sections);
     183  	char *sect;
     184  	bool first = true;
     185  
     186  	debug ("  Added sections: ");
     187  	for (sect = strtok (section_list, " "); sect;
     188  	     sect = strtok (NULL, " ")) {
     189  		add_config (sect, "", user ? SECTION_USER : SECTION);
     190  		if (!first)
     191  			debug (", ");
     192  		debug ("`%s'", sect);
     193  		first = false;
     194  	}
     195  	debug (".\n");
     196  	free (section_list);
     197  }
     198  
     199  gl_list_t get_sections (void)
     200  {
     201  	const struct config_item *item;
     202  	int length_user = 0, length = 0;
     203  	gl_list_t sections;
     204  	enum config_flag flag;
     205  
     206  	GL_LIST_FOREACH (config, item) {
     207  		if (item->flag == SECTION_USER)
     208  			length_user++;
     209  		else if (item->flag == SECTION)
     210  			length++;
     211  	}
     212  	sections = new_string_list (GL_ARRAY_LIST, true);
     213  	if (length_user)
     214  		flag = SECTION_USER;
     215  	else
     216  		flag = SECTION;
     217  	GL_LIST_FOREACH (config, item)
     218  		if (item->flag == flag)
     219  			gl_list_add_last (sections, xstrdup (item->key));
     220  	return sections;
     221  }
     222  
     223  static void add_def (const char *thing, const char *config_def, bool user)
     224  {
     225  	add_config (thing, config_def, user ? DEFINE_USER : DEFINE);
     226  
     227  	debug ("  Defined `%s' as `%s'.\n", thing, config_def);
     228  }
     229  
     230  static void add_manpath_map (const char *path, const char *mandir)
     231  {
     232  	if (!path || !mandir)
     233  		return;
     234  
     235  	add_config (path, mandir, MANPATH_MAP);
     236  
     237  	debug ("  Path `%s' mapped to mandir `%s'.\n", path, mandir);
     238  }
     239  
     240  static void add_mandb_map (const char *mandir, const char *catdir, bool user)
     241  {
     242  	char *tmpcatdir;
     243  
     244  	if (!mandir)
     245  		return;
     246  
     247  	if (STREQ (catdir, "FSSTND"))
     248  		tmpcatdir = fsstnd (mandir);
     249  	else
     250  		tmpcatdir = xstrdup (catdir);
     251  
     252  	if (!tmpcatdir)
     253  		return;
     254  
     255  	add_config (mandir, tmpcatdir, user ? MANDB_MAP_USER : MANDB_MAP);
     256  
     257  	debug ("  %s mandir `%s', catdir `%s'.\n",
     258  	       user ? "User" : "Global", mandir, tmpcatdir);
     259  
     260  	free (tmpcatdir);
     261  }
     262  
     263  static void add_mandatory (const char *mandir)
     264  {
     265  	if (!mandir)
     266  		return;
     267  
     268  	add_config (mandir, "", MANDATORY);
     269  
     270  	debug ("  Mandatory mandir `%s'.\n", mandir);
     271  }
     272  
     273  /* accept (NULL or oldpath) and new path component. return new path */
     274  static char *pathappend (char *oldpath, const char *appendage)
     275  {
     276  	assert ((!oldpath || *oldpath) && appendage);
     277  	/* Remove duplicates */
     278  	if (oldpath) {
     279  		char *oldpathtok = xstrdup (oldpath), *tok;
     280  		char *app_dedup = xstrdup (appendage);
     281  		char *oldpathtok_ptr = oldpathtok;
     282  		for (tok = strsep (&oldpathtok_ptr, ":"); tok;
     283  		     tok = strsep (&oldpathtok_ptr, ":")) {
     284  			char *search;
     285  			if (!*tok)	    /* ignore empty fields */
     286  				continue;
     287  			search = strstr (app_dedup, tok);
     288  			while (search) {
     289  				char *terminator = search + strlen (tok);
     290  				if (search > app_dedup && search[-1] != ':')
     291  					/* Ignore suffix matches. */
     292  					;
     293  				else if (!*terminator) {
     294  					/* End of the string, so chop here. */
     295  					*search = 0;
     296  					while (search > app_dedup &&
     297  					       *--search == ':')
     298  						*search = 0;
     299  					break;
     300  				} else if (*terminator == ':') {
     301  					char *newapp;
     302  					*search = 0;
     303  					newapp = xasprintf ("%s%s", app_dedup,
     304  							    terminator + 1);
     305  					assert (newapp);
     306  					free (app_dedup);
     307  					app_dedup = newapp;
     308  				}
     309  				search = strstr (terminator, tok);
     310  			}
     311  		}
     312  		free (oldpathtok);
     313  		if (!STREQ (appendage, app_dedup))
     314  			debug ("%s:%s reduced to %s%s%s\n",
     315  			       oldpath, appendage,
     316  			       oldpath, *app_dedup ? ":" : "", app_dedup);
     317  		if (*app_dedup)
     318  			oldpath = appendstr (oldpath, ":", app_dedup,
     319  					     (void *) 0);
     320  		free (app_dedup);
     321  		return oldpath;
     322  	} else
     323  		return xstrdup (appendage);
     324  }
     325  
     326  static void gripe_reading_mp_config (const char *file)
     327  {
     328  	error (FAIL, 0,
     329  	       _("can't make sense of the manpath configuration file %s"),
     330  	       file);
     331  }
     332  
     333  static void gripe_stat_file (const char *file)
     334  {
     335  	debug_error (_("warning: %s"), file);
     336  }
     337  
     338  static void gripe_not_directory (const char *dir)
     339  {
     340  	if (!quiet)
     341  		error (0, 0, _("warning: %s isn't a directory"), dir);
     342  }
     343  
     344  /* accept a manpath list, separated with ':', return the associated
     345     catpath list */
     346  char *cat_manpath (char *manp)
     347  {
     348  	char *catp = NULL;
     349  	const char *path, *catdir;
     350  
     351  	for (path = strsep (&manp, ":"); path; path = strsep (&manp, ":")) {
     352  		catdir = get_config (path, MANDB_MAP_USER);
     353  		if (!catdir)
     354  			catdir = get_config (path, MANDB_MAP);
     355  		catp = catdir ? pathappend (catp, catdir)
     356  			      : pathappend (catp, path);
     357  	}
     358  
     359  	return catp;
     360  }
     361  
     362  /* Unpack a glibc-style locale into its component parts.
     363   *
     364   * This function was inspired by _nl_explode_name in libintl; I've rewritten
     365   * it here with extensive modifications in order not to require libintl or
     366   * glibc internals, because this API is more convenient for man-db, and to
     367   * be consistent with surrounding style. I also dropped the normalised
     368   * codeset handling, which we don't need here.
     369   */
     370  void unpack_locale_bits (const char *locale, struct locale_bits *bits)
     371  {
     372  	const char *p, *start;
     373  
     374  	bits->language = NULL;
     375  	bits->territory = NULL;
     376  	bits->codeset = NULL;
     377  	bits->modifier = NULL;
     378  
     379  	/* Now we determine the single parts of the locale name. First look
     380  	 * for the language. Termination symbols are '_', '.', and '@'.
     381  	 */
     382  	p = locale;
     383  	while (*p && *p != '_' && *p != '.' && *p != '@')
     384  		++p;
     385  	if (p == locale) {
     386  		/* This does not make sense: language has to be specified.
     387  		 * Use this entry as it is without exploding. Perhaps it is
     388  		 * an alias.
     389  		 */
     390  		bits->language = xstrdup (locale);
     391  		goto out;
     392  	}
     393  	bits->language = xstrndup (locale, p - locale);
     394  
     395  	if (*p == '_') {
     396  		/* Next is the territory. */
     397  		start = ++p;
     398  		while (*p && *p != '.' && *p != '@')
     399  			++p;
     400  		bits->territory = xstrndup (start, p - start);
     401  	}
     402  
     403  	if (*p == '.') {
     404  		/* Next is the codeset. */
     405  		start = ++p;
     406  		while (*p && *p != '@')
     407  			++p;
     408  		bits->codeset = xstrndup (start, p - start);
     409  	}
     410  
     411  	if (*p == '@')
     412  		/* Next is the modifier. */
     413  		bits->modifier = xstrdup (++p);
     414  
     415  out:
     416  	if (!bits->territory)
     417  		bits->territory = xstrdup ("");
     418  	if (!bits->codeset)
     419  		bits->codeset = xstrdup ("");
     420  	if (!bits->modifier)
     421  		bits->modifier = xstrdup ("");
     422  }
     423  
     424  /* Free the contents of a locale_bits structure populated by
     425   * unpack_locale_bits. Does not free the pointer argument.
     426   */
     427  void free_locale_bits (struct locale_bits *bits)
     428  {
     429  	free (bits->language);
     430  	free (bits->territory);
     431  	free (bits->codeset);
     432  	free (bits->modifier);
     433  }
     434  
     435  
     436  static char *get_nls_manpath (const char *manpathlist, const char *locale)
     437  {
     438  	struct locale_bits lbits;
     439  	char *manpath = NULL;
     440  	char *manpathlist_copy, *path, *manpathlist_ptr;
     441  
     442  	unpack_locale_bits (locale, &lbits);
     443  	if (STREQ (lbits.language, "C") || STREQ (lbits.language, "POSIX")) {
     444  		free_locale_bits (&lbits);
     445  		return xstrdup (manpathlist);
     446  	}
     447  
     448  	manpathlist_copy = xstrdup (manpathlist);
     449  	manpathlist_ptr = manpathlist_copy;
     450  	for (path = strsep (&manpathlist_ptr, ":"); path;
     451  	     path = strsep (&manpathlist_ptr, ":")) {
     452  		DIR *mandir = opendir (path);
     453  		struct dirent *mandirent;
     454  
     455  		if (!mandir)
     456  			continue;
     457  
     458  		while ((mandirent = readdir (mandir)) != NULL) {
     459  			const char *name;
     460  			struct locale_bits mbits;
     461  			char *fullpath;
     462  
     463  			name = mandirent->d_name;
     464  			if (STREQ (name, ".") || STREQ (name, ".."))
     465  				continue;
     466  			if (STRNEQ (name, "man", 3))
     467  				continue;
     468  			fullpath = xasprintf ("%s/%s", path, name);
     469  			if (is_directory (fullpath) != 1) {
     470  				free (fullpath);
     471  				continue;
     472  			}
     473  
     474  			unpack_locale_bits (name, &mbits);
     475  			if (STREQ (lbits.language, mbits.language) &&
     476  			    (!*mbits.territory ||
     477  			     STREQ (lbits.territory, mbits.territory)) &&
     478  			    (!*mbits.modifier ||
     479  			     STREQ (lbits.modifier, mbits.modifier)))
     480  				manpath = pathappend (manpath, fullpath);
     481  			free_locale_bits (&mbits);
     482  			free (fullpath);
     483  		}
     484  
     485  		if (STREQ (lbits.language, "en"))
     486  			/* For English, we look in the subdirectories as
     487  			 * above just in case there's something like
     488  			 * en_GB.UTF-8, but it's more probable that English
     489  			 * manual pages reside at the top level.
     490  			 */
     491  			manpath = pathappend (manpath, path);
     492  
     493  		closedir (mandir);
     494  	}
     495  	free (manpathlist_copy);
     496  
     497  	free_locale_bits (&lbits);
     498  	return manpath;
     499  }
     500  
     501  char *add_nls_manpaths (const char *manpathlist, const char *locales)
     502  {
     503  	char *manpath = NULL;
     504  	char *locales_copy, *tok, *locales_ptr;
     505  	char *locale_manpath;
     506  
     507  	debug ("add_nls_manpaths(): processing %s\n", manpathlist);
     508  
     509  	if (locales == NULL || *locales == '\0')
     510  		return xstrdup (manpathlist);
     511  
     512  	/* For each locale, we iterate over the manpath and find appropriate
     513  	 * locale directories for each item. We then concatenate the results
     514  	 * for all locales. In other words, LANGUAGE=fr:de and
     515  	 * manpath=/usr/share/man:/usr/local/share/man could result in
     516  	 * something like this list:
     517  	 *
     518  	 *   /usr/share/man/fr
     519  	 *   /usr/local/share/man/fr
     520  	 *   /usr/share/man/de
     521  	 *   /usr/local/share/man/de
     522  	 *   /usr/share/man
     523  	 *   /usr/local/share/man
     524  	 *
     525  	 * This assumes that it's more important to have documentation in
     526  	 * the preferred language than to have documentation for the correct
     527  	 * object (in the case where there are different versions of a
     528  	 * program in different hierarchies, for example). It is not
     529  	 * entirely obvious that this is the right assumption, but on the
     530  	 * other hand the other choice is not entirely obvious either. We
     531  	 * tie-break on "we've always done it this way", and people can use
     532  	 * 'man -a' or whatever in the occasional case where we get it
     533  	 * wrong.
     534  	 *
     535  	 * We go to no special effort to de-duplicate directories here.
     536  	 * create_pathlist will sort it out later; note that it preserves
     537  	 * order in that it keeps the first of any duplicate set in its
     538  	 * original position.
     539  	 */
     540  
     541  	locales_copy = xstrdup (locales);
     542  	locales_ptr = locales_copy;
     543  	for (tok = strsep (&locales_ptr, ":"); tok;
     544  	     tok = strsep (&locales_ptr, ":")) {
     545  		if (!*tok)	/* ignore empty fields */
     546  			continue;
     547  		debug ("checking for locale %s\n", tok);
     548  
     549  		locale_manpath = get_nls_manpath (manpathlist, tok);
     550  		if (locale_manpath) {
     551  			if (manpath)
     552  				manpath = appendstr (manpath, ":",
     553  						     locale_manpath,
     554  						     (void *) 0);
     555  			else
     556  				manpath = xstrdup (locale_manpath);
     557  			free (locale_manpath);
     558  		}
     559  	}
     560  	free (locales_copy);
     561  
     562  	/* Always try untranslated pages as a last resort. */
     563  	locale_manpath = get_nls_manpath (manpathlist, "C");
     564  	if (locale_manpath) {
     565  		if (manpath)
     566  			manpath = appendstr (manpath, ":",
     567  					     locale_manpath, (void *) 0);
     568  		else
     569  			manpath = xstrdup (locale_manpath);
     570  		free (locale_manpath);
     571  	}
     572  
     573  	return manpath;
     574  }
     575  
     576  static char *add_system_manpath (const char *systems, const char *manpathlist)
     577  {
     578  	char *one_system;
     579  	char *manpath = NULL;
     580  	char *tmpsystems;
     581  
     582  	if (!systems)
     583  		systems = getenv ("SYSTEM");
     584  
     585  	if (!systems || !*systems)
     586  		return xstrdup (manpathlist);
     587  
     588  	/* Avoid breaking the environment. */
     589  	tmpsystems = xstrdup (systems);
     590  
     591  	/* For each systems component */
     592  
     593  	for (one_system = strtok (tmpsystems, ",:"); one_system;
     594  	     one_system = strtok (NULL, ",:")) {
     595  
     596  		/* For each manpathlist component */
     597  
     598  		if (!STREQ (one_system, "man")) {
     599  			const char *next, *path;
     600  			char *newdir = NULL;
     601  			for (path = manpathlist; path; path = next) {
     602  				int status;
     603  				char *element;
     604  
     605  				next = strchr (path, ':');
     606  				if (next) {
     607  					element = xstrndup (path, next - path);
     608  					++next;
     609  				} else
     610  					element = xstrdup (path);
     611  				newdir = appendstr (newdir, element, "/",
     612  						    one_system, (void *) 0);
     613  				free (element);
     614  
     615  				status = is_directory (newdir);
     616  
     617  				if (status == 0)
     618  					gripe_not_directory (newdir);
     619  				else if (status == 1) {
     620  					debug ("adding %s to manpathlist\n",
     621  					       newdir);
     622  					manpath = pathappend (manpath, newdir);
     623  				} else
     624  					debug_error ("can't stat %s", newdir);
     625  				/* reset newdir */
     626  				*newdir = '\0';
     627  			}
     628  			free (newdir);
     629  		} else
     630  			manpath = pathappend (manpath, manpathlist);
     631  	}
     632  	free (tmpsystems);
     633  
     634  	/*
     635  	 * Thu, 21 Nov 1996 22:24:19 +0200 fpolacco@debian.org
     636  	 * bug#5534 (man fails if env var SYSTEM is defined)
     637  	 * with error [man: internal manpath equates to NULL]
     638  	 * the reason: is_directory (newdir); returns -1
     639  	 */
     640  	if (!manpath) {
     641  		debug ("add_system_manpath(): "
     642  		       "internal manpath equates to NULL\n");
     643  		return xstrdup (manpathlist);
     644  	}
     645  	return manpath;
     646  }
     647  
     648  /*
     649   * Always add system and locale directories to pathlist.
     650   * If the environment variable MANPATH is set, return it.
     651   * If the environment variable PATH is set and has a nonzero length,
     652   * try to determine the corresponding manpath, otherwise, return the
     653   * default manpath.
     654   *
     655   * The man_db.config file is used to map system wide /bin directories
     656   * to top level man page directories.
     657   *
     658   * For directories which are in the user's path but not in the
     659   * man_db.config file, see if there is a subdirectory `man' or `MAN'.
     660   * If so, add that directory to the path.  Example:  user has
     661   * $HOME/bin in his path and the directory $HOME/bin/man exists -- the
     662   * directory $HOME/bin/man will be added to the manpath.
     663   */
     664  static char *guess_manpath (const char *systems)
     665  {
     666  	const char *path = getenv ("PATH");
     667  	char *manpathlist, *manpath;
     668  
     669  	if (path == NULL || getenv ("MAN_TEST_DISABLE_PATH")) {
     670  		/* Things aren't going to work well, but hey... */
     671  		if (path == NULL && !quiet)
     672  			error (0, 0, _("warning: $PATH not set"));
     673  
     674  		manpathlist = def_path (MANDATORY);
     675  	} else {
     676  		if (strlen (path) == 0) {
     677  			/* Things aren't going to work well here either... */
     678  			if (!quiet)
     679  				error (0, 0, _("warning: empty $PATH"));
     680  
     681  			return add_system_manpath (systems,
     682  						   def_path (MANDATORY));
     683  		}
     684  
     685  		manpathlist = get_manpath_from_path (path, true);
     686  	}
     687  	manpath = add_system_manpath (systems, manpathlist);
     688  	free (manpathlist);
     689  	return manpath;
     690  }
     691  
     692  char *get_manpath (const char *systems)
     693  {
     694  	char *manpathlist;
     695  
     696  	/* need to read config file even if MANPATH set, for mandb(8) */
     697  	read_config_file (false);
     698  
     699  	manpathlist = getenv ("MANPATH");
     700  	if (manpathlist && *manpathlist) {
     701  		char *system1, *system2, *guessed;
     702  		char *pos;
     703  		/* This must be it. */
     704  		if (manpathlist[0] == ':') {
     705  			if (!quiet)
     706  				error (0, 0,
     707  				       _("warning: $MANPATH set, "
     708  					 "prepending %s"),
     709  				       CONFIG_FILE);
     710  			system1 = add_system_manpath (systems, manpathlist);
     711  			guessed = guess_manpath (systems);
     712  			manpathlist = xasprintf ("%s%s", guessed, system1);
     713  			free (guessed);
     714  			free (system1);
     715  		} else if (manpathlist[strlen (manpathlist) - 1] == ':') {
     716  			if (!quiet)
     717  				error (0, 0,
     718  				       _("warning: $MANPATH set, "
     719  					 "appending %s"),
     720  				       CONFIG_FILE);
     721  			system1 = add_system_manpath (systems, manpathlist);
     722  			guessed = guess_manpath (systems);
     723  			manpathlist = xasprintf ("%s%s", system1, guessed);
     724  			free (guessed);
     725  			free (system1);
     726  		} else if ((pos = strstr (manpathlist,"::"))) {
     727  			*(pos++) = '\0';
     728  			if (!quiet)
     729  				error (0, 0,
     730  				       _("warning: $MANPATH set, "
     731  					 "inserting %s"),
     732  				       CONFIG_FILE);
     733  			system1 = add_system_manpath (systems, manpathlist);
     734  			guessed = guess_manpath (systems);
     735  			system2 = add_system_manpath (systems, pos);
     736  			manpathlist = xasprintf ("%s:%s%s", system1, guessed,
     737  						 system2);
     738  			free (system2);
     739  			free (guessed);
     740  			free (system1);
     741  		} else {
     742  			if (!quiet)
     743  				error (0, 0,
     744  				       _("warning: $MANPATH set, ignoring %s"),
     745  				       CONFIG_FILE);
     746  			manpathlist = add_system_manpath (systems,
     747  							  manpathlist);
     748  		}
     749  	} else
     750  		manpathlist = guess_manpath (systems);
     751  
     752  	return manpathlist;
     753  }
     754  
     755  /* Parse the manpath.config file, extracting appropriate information. */
     756  static void add_to_dirlist (FILE *config_file, bool user)
     757  {
     758  	char *bp;
     759  	char *buf = NULL;
     760  	size_t n = 0;
     761  	char key[512], cont[512];
     762  	int val;
     763  	int c;
     764  
     765  	while (getline (&buf, &n, config_file) >= 0) {
     766  		bp = buf;
     767  
     768  		while (CTYPE (isspace, *bp))
     769  			bp++;
     770  
     771  		/* TODO: would like a (limited) replacement for sscanf()
     772  		 * here that allocates its own memory. At that point check
     773  		 * everything that sprintf()s manpath et al!
     774  		 */
     775  		if (*bp == '#' || *bp == '\0')
     776  			goto next;
     777  		else if (strncmp (bp, "NOCACHE", 7) == 0)
     778  			disable_cache = true;
     779  		else if (strncmp (bp, "NO", 2) == 0)
     780  			goto next;	/* match any word starting with NO */
     781  		else if (sscanf (bp, "MANBIN %*s") == 1)
     782  			goto next;
     783  		else if (sscanf (bp, "MANDATORY_MANPATH %511s", key) == 1)
     784  			add_mandatory (key);
     785  		else if (sscanf (bp, "MANPATH_MAP %511s %511s",
     786  			 key, cont) == 2)
     787  			add_manpath_map (key, cont);
     788  		else if ((c = sscanf (bp, "MANDB_MAP %511s %511s",
     789  				      key, cont)) > 0)
     790  			add_mandb_map (key, c == 2 ? cont : key, user);
     791  		else if ((c = sscanf (bp, "DEFINE %511s %511[^\n]",
     792  				      key, cont)) > 0)
     793  			add_def (key, c == 2 ? cont : "", user);
     794  		else if (sscanf (bp, "SECTION %511[^\n]", cont) == 1)
     795  			add_sections (cont, user);
     796  		else if (sscanf (bp, "SECTIONS %511[^\n]", cont) == 1)
     797  			/* Since I keep getting it wrong ... */
     798  			add_sections (cont, user);
     799  		else if (sscanf (bp, "MINCATWIDTH %d", &val) == 1)
     800  			min_cat_width = val;
     801  		else if (sscanf (bp, "MAXCATWIDTH %d", &val) == 1)
     802  			max_cat_width = val;
     803  		else if (sscanf (bp, "CATWIDTH %d", &val) == 1)
     804  			cat_width = val;
     805  	 	else {
     806  			error (0, 0, _("can't parse directory list `%s'"), bp);
     807  			gripe_reading_mp_config (CONFIG_FILE);
     808  		}
     809  
     810  next:
     811  		free (buf);
     812  		buf = NULL;
     813  	}
     814  
     815  	free (buf);
     816  }
     817  
     818  static void free_config_file (void *unused MAYBE_UNUSED)
     819  {
     820  	gl_list_free (config);
     821  }
     822  
     823  void read_config_file (bool optional)
     824  {
     825  	static bool done = false;
     826  	char *dotmanpath = NULL;
     827  	FILE *config_file;
     828  
     829  	if (done)
     830  		return;
     831  
     832  	config = gl_list_create_empty (GL_ARRAY_LIST, NULL, NULL,
     833  				       config_item_free, true);
     834  	push_cleanup (free_config_file, NULL, 0);
     835  
     836  	if (user_config_file)
     837  		dotmanpath = xstrdup (user_config_file);
     838  	else {
     839  		char *home = getenv ("HOME");
     840  		if (home)
     841  			dotmanpath = xasprintf ("%s/.manpath", home);
     842  	}
     843  	if (dotmanpath) {
     844  		config_file = fopen (dotmanpath, "r");
     845  		if (config_file != NULL) {
     846  			debug ("From the config file %s:\n", dotmanpath);
     847  			add_to_dirlist (config_file, true);
     848  			fclose (config_file);
     849  		}
     850  		free (dotmanpath);
     851  	}
     852  
     853  	if (getenv ("MAN_TEST_DISABLE_SYSTEM_CONFIG") == NULL) {
     854  		config_file = fopen (CONFIG_FILE, "r");
     855  		if (config_file == NULL) {
     856  			if (optional)
     857  				debug ("can't open %s; continuing anyway\n",
     858  				       CONFIG_FILE);
     859  			else
     860  				error (FAIL, 0,
     861  				       _("can't open the manpath "
     862  					 "configuration file %s"),
     863  				       CONFIG_FILE);
     864  		} else {
     865  			debug ("From the config file %s:\n", CONFIG_FILE);
     866  
     867  			add_to_dirlist (config_file, false);
     868  			fclose (config_file);
     869  		}
     870  	}
     871  
     872  	done = true;
     873  }
     874  
     875  
     876  /*
     877   * Construct the default manpath.  This picks up mandatory manpaths
     878   * only.
     879   */
     880  static char *def_path (enum config_flag flag)
     881  {
     882  	char *manpath = NULL;
     883  	const struct config_item *item;
     884  
     885  	GL_LIST_FOREACH (config, item)
     886  		if (item->flag == flag) {
     887  			gl_list_t expanded_dirs;
     888  			const char *expanded_dir;
     889  
     890  			expanded_dirs = expand_path (item->key);
     891  			GL_LIST_FOREACH (expanded_dirs, expanded_dir) {
     892  				int status = is_directory (expanded_dir);
     893  
     894  				if (status < 0)
     895  					gripe_stat_file (expanded_dir);
     896  				else if (status == 0 && !quiet)
     897  					error (0, 0,
     898  					       _("warning: mandatory "
     899  						 "directory %s doesn't exist"),
     900  					       expanded_dir);
     901  				else if (status == 1)
     902  					manpath = pathappend (manpath,
     903  							      expanded_dir);
     904  			}
     905  			gl_list_free (expanded_dirs);
     906  		}
     907  
     908  	/* If we have complete config file failure... */
     909  	if (!manpath)
     910  		return xstrdup ("/usr/man");
     911  
     912  	return manpath;
     913  }
     914  
     915  /*
     916   * For each directory in the user's path, see if it is one of the
     917   * directories listed in the man_db.config file.  If so, and it is
     918   * not already in the manpath, add it.  If the directory is not listed
     919   * in the man_db.config file, see if there is a subdirectory `../man' or
     920   * `man', or, for FHS-compliance, `../share/man' or `share/man'.  If so,
     921   * and it is not already in the manpath, add it.
     922   * Example:  user has $HOME/bin in his path and the directory
     923   * $HOME/man exists -- the directory $HOME/man will be added
     924   * to the manpath.
     925   */
     926  char *get_manpath_from_path (const char *path, bool mandatory)
     927  {
     928  	gl_list_t tmplist;
     929  	const struct config_item *config_item;
     930  	int len;
     931  	char *tmppath;
     932  	char *p;
     933  	char *end;
     934  	char *manpathlist;
     935  	char *item;
     936  
     937  	tmplist = new_string_list (GL_LINKEDHASH_LIST, false);
     938  	tmppath = xstrdup (path);
     939  
     940  	for (end = p = tmppath; end; p = end + 1) {
     941  		bool manpath_map_found = false;
     942  
     943  		end = strchr (p, ':');
     944  		if (end)
     945  			*end = '\0';
     946  
     947  		/* don't do this for current dir ("." or empty entry in PATH) */
     948  		if (*p == '\0' || strcmp (p, ".") == 0)
     949  			continue;
     950  
     951  		debug ("path directory %s ", p);
     952  
     953  		/* If the directory we're working on has MANPATH_MAP entries
     954  		 * in the config file, add them to the list.
     955  		 */
     956  		GL_LIST_FOREACH (config, config_item) {
     957  			if (MANPATH_MAP != config_item->flag ||
     958  			    !STREQ (p, config_item->key))
     959  				continue;
     960  			if (!manpath_map_found)
     961  				debug ("is in the config file\n");
     962  			manpath_map_found = true;
     963  			add_dir_to_list (tmplist, config_item->cont);
     964  		}
     965  
     966  		 /* The directory we're working on isn't in the config file.
     967  		    See if it has ../man, man, ../share/man, or share/man
     968  		    subdirectories.  If so, and they haven't been added to
     969  		    the list, do. */
     970  
     971  		if (!manpath_map_found) {
     972  			debug ("is not in the config file\n");
     973  			add_man_subdirs (tmplist, p);
     974  		}
     975  	}
     976  
     977  	free (tmppath);
     978  
     979  	if (mandatory) {
     980  		debug ("adding mandatory man directories\n");
     981  
     982  		GL_LIST_FOREACH (config, config_item) {
     983  			if (config_item->flag == MANDATORY)
     984  				add_dir_to_list (tmplist, config_item->key);
     985  		}
     986  	}
     987  
     988  	len = 0;
     989  	GL_LIST_FOREACH (tmplist, item)
     990  		len += strlen (item) + 1;
     991  
     992  	if (!len)
     993  		/* No path elements in configuration file or with
     994  		 * appropriate subdirectories.
     995  		 */
     996  		return xstrdup ("");
     997  
     998  	manpathlist = xmalloc (len);
     999  	*manpathlist = '\0';
    1000  
    1001  	p = manpathlist;
    1002  	GL_LIST_FOREACH (tmplist, item) {
    1003  		len = strlen (item);
    1004  		memcpy (p, item, len);
    1005  		p += len;
    1006  		*p++ = ':';
    1007  	}
    1008  
    1009  	p[-1] = '\0';
    1010  
    1011  	gl_list_free (tmplist);
    1012  
    1013  	return manpathlist;
    1014  }
    1015  
    1016  /* Add a directory to the manpath list if it isn't already there. */
    1017  static void add_expanded_dir_to_list (gl_list_t list, const char *dir)
    1018  {
    1019  	int status;
    1020  
    1021  	if (gl_list_search (list, dir))
    1022  		return;
    1023  
    1024  	/* Not found -- add it. */
    1025  
    1026  	status = is_directory (dir);
    1027  
    1028  	if (status < 0)
    1029  		gripe_stat_file (dir);
    1030  	else if (status == 0)
    1031  		gripe_not_directory (dir);
    1032  	else if (status == 1) {
    1033  		debug ("  adding %s to manpath\n", dir);
    1034  		gl_list_add_last (list, xstrdup (dir));
    1035  	}
    1036  }
    1037  
    1038  /*
    1039   * Add a directory to the manpath list if it isn't already there, expanding
    1040   * wildcards.
    1041   */
    1042  static void add_dir_to_list (gl_list_t list, const char *dir)
    1043  {
    1044  	gl_list_t expanded_dirs;
    1045  	const char *expanded_dir;
    1046  
    1047  	expanded_dirs = expand_path (dir);
    1048  	GL_LIST_FOREACH (expanded_dirs, expanded_dir)
    1049  		add_expanded_dir_to_list (list, expanded_dir);
    1050  	gl_list_free (expanded_dirs);
    1051  }
    1052  
    1053  /* path does not exist in config file: check to see if path/../man,
    1054     path/man, path/../share/man, or path/share/man exist, and add them to the
    1055     list if they do. */
    1056  static void add_man_subdirs (gl_list_t list, const char *path)
    1057  {
    1058  	char *newpath;
    1059          char *trimmed_path = xstrdup (path);
    1060  
    1061  	/* don't assume anything about path, especially that it ends in
    1062  	   "bin" or even has a '/' in it! */
    1063  
    1064  	char *subdir = strrchr (trimmed_path, '/');
    1065  
    1066  	/* Trailing slash or root directory. Remove the trailing slash and
    1067  	   try again. If root directory, subdir will be null, so we don't
    1068  	   cause a segfault. If a path element is '/', we will correctly add
    1069  	   /man and /share/man manpaths. */
    1070  	if (subdir && strncmp (subdir, "/", 2) == 0) {
    1071  		subdir[0] = '\0';
    1072  		subdir = strrchr (trimmed_path, '/');
    1073  	}
    1074  	if (subdir) {
    1075  		newpath = xasprintf ("%.*s/man",
    1076  				     (int) (subdir - trimmed_path),
    1077  				     trimmed_path);
    1078  		if (is_directory (newpath) == 1)
    1079  			add_dir_to_list (list, newpath);
    1080  		free (newpath);
    1081  	}
    1082  
    1083  	newpath = xasprintf ("%s/man", trimmed_path);
    1084  	if (is_directory (newpath) == 1)
    1085  		add_dir_to_list (list, newpath);
    1086  	free (newpath);
    1087  
    1088  	if (subdir) {
    1089  		newpath = xasprintf ("%.*s/share/man",
    1090  				     (int) (subdir - trimmed_path),
    1091  				     trimmed_path);
    1092  		if (is_directory (newpath) == 1)
    1093  			add_dir_to_list (list, newpath);
    1094  		free (newpath);
    1095  	}
    1096  
    1097  	newpath = xasprintf ("%s/share/man", trimmed_path);
    1098  	if (is_directory (newpath) == 1)
    1099  		add_dir_to_list (list, newpath);
    1100  	free (newpath);
    1101  
    1102          free (trimmed_path);
    1103  }
    1104  
    1105  struct canonicalized_path {
    1106  	char *path;
    1107  	char *canon_path;
    1108  };
    1109  
    1110  static struct canonicalized_path *canonicalized_path_new (const char *path)
    1111  {
    1112  	char *canon_path;
    1113  	struct canonicalized_path *cp = NULL;
    1114  
    1115  	canon_path = canonicalize_file_name (path);
    1116  	if (canon_path) {
    1117  		cp = XMALLOC (struct canonicalized_path);
    1118  		cp->path = xstrdup (path);
    1119  		cp->canon_path = canon_path;	/* steal memory */
    1120  	}
    1121  	return cp;
    1122  }
    1123  
    1124  static bool ATTRIBUTE_PURE canonicalized_path_equals (const void *elt1,
    1125  						      const void *elt2)
    1126  {
    1127  	const struct canonicalized_path *cp1 = elt1, *cp2 = elt2;
    1128  	return string_equals (cp1->canon_path, cp2->canon_path);
    1129  }
    1130  
    1131  static size_t ATTRIBUTE_PURE canonicalized_path_hash (const void *elt)
    1132  {
    1133  	const struct canonicalized_path *cp = elt;
    1134  	return string_hash (cp->canon_path);
    1135  }
    1136  
    1137  static void canonicalized_path_free (const void *elt)
    1138  {
    1139  	/* gl_list declares the argument as const, but there doesn't seem to
    1140  	 * be a good reason for this.
    1141  	 */
    1142  	struct canonicalized_path *cp = (struct canonicalized_path *) elt;
    1143  	free (cp->path);
    1144  	free (cp->canon_path);
    1145  	free (cp);
    1146  }
    1147  
    1148  static void add_dir_to_path_list (gl_list_t list, const char *p)
    1149  {
    1150  	gl_list_t expanded_dirs;
    1151  	char *expanded_dir;
    1152  
    1153  	expanded_dirs = expand_path (p);
    1154  	GL_LIST_FOREACH (expanded_dirs, expanded_dir) {
    1155  		int status = is_directory (expanded_dir);
    1156  
    1157  		if (status < 0)
    1158  			gripe_stat_file (expanded_dir);
    1159  		else if (status == 0)
    1160  			gripe_not_directory (expanded_dir);
    1161  		else {
    1162  			char *path;
    1163  			struct canonicalized_path *cp;
    1164  
    1165  			/* deal with relative paths */
    1166  			if (*expanded_dir != '/') {
    1167  				char *cwd = xgetcwd ();
    1168  				if (!cwd)
    1169  					fatal (errno,
    1170  					       _("can't determine current directory"));
    1171  				path = appendstr (cwd, "/", expanded_dir,
    1172  						  (void *) 0);
    1173  			} else
    1174  				path = xstrdup (expanded_dir);
    1175  
    1176  			cp = canonicalized_path_new (path);
    1177  			if (cp && !gl_list_search (list, cp)) {
    1178  				debug ("adding %s to manpathlist\n", path);
    1179  				gl_list_add_last (list, cp);
    1180  			} else if (cp)
    1181  				canonicalized_path_free (cp);
    1182  			free (path);
    1183  		}
    1184  	}
    1185  	gl_list_free (expanded_dirs);
    1186  }
    1187  
    1188  gl_list_t create_pathlist (const char *manp)
    1189  {
    1190  	gl_list_t canonicalized_list, list;
    1191  	const char *p, *end;
    1192  	const struct canonicalized_path *cp;
    1193  
    1194  	/* Expand the manpath into a list of (path, canonicalized path)
    1195  	 * pairs for easier handling.  add_dir_to_path_list only adds items
    1196  	 * if they do not have the same canonicalized path as an existing
    1197  	 * item, thereby eliminating duplicates due to symlinks.
    1198  	 * For each entry, add corresponding OVERRIDE_DIR if configured.
    1199  	 */
    1200  
    1201  	canonicalized_list = gl_list_create_empty
    1202  		(GL_LINKEDHASH_LIST, canonicalized_path_equals,
    1203  		 canonicalized_path_hash, canonicalized_path_free, false);
    1204  	for (p = manp;; p = end + 1) {
    1205  		char *element;
    1206  
    1207  		end = strchr (p, ':');
    1208  		element = end ? xstrndup (p, end - p) : xstrdup (p);
    1209  
    1210  		if (*OVERRIDE_DIR) {
    1211  			char *element_override = xasprintf
    1212  				("%s/%s", element, OVERRIDE_DIR);
    1213  			add_dir_to_path_list
    1214  				(canonicalized_list, element_override);
    1215  			free (element_override);
    1216  		}
    1217  
    1218  		add_dir_to_path_list (canonicalized_list, element);
    1219  		free (element);
    1220  
    1221  		if (!end)
    1222  			break;
    1223  	}
    1224  
    1225  	list = new_string_list (GL_ARRAY_LIST, false);
    1226  	GL_LIST_FOREACH (canonicalized_list, cp)
    1227  		gl_list_add_last (list, xstrdup (cp->path));
    1228  
    1229  	if (debug_level) {
    1230  		debug ("final search path = ");
    1231  		GL_LIST_FOREACH (list, p) {
    1232  			if (!gl_list_previous_node (list, list_node))
    1233  				debug ("%s", p);
    1234  			else
    1235  				debug (":%s", p);
    1236  		}
    1237  		debug ("\n");
    1238  	}
    1239  
    1240  	gl_list_free (canonicalized_list);
    1241  	return list;
    1242  }
    1243  
    1244  void free_pathlist (gl_list_t list)
    1245  {
    1246  	gl_list_free (list);
    1247  }
    1248  
    1249  /* Routine to get list of named system and user manpaths (in reverse order). */
    1250  char *get_mandb_manpath (void)
    1251  {
    1252  	char *manpath = NULL;
    1253  	const struct config_item *item;
    1254  
    1255  	GL_LIST_FOREACH (config, item)
    1256  		if (item->flag == MANDB_MAP || item->flag == MANDB_MAP_USER)
    1257  			manpath = pathappend (manpath, item->key);
    1258  
    1259  	return manpath;
    1260  }
    1261  
    1262  /* Take manpath or manfile path as the first argument, and the type of
    1263   * catpaths we want as the other (system catpaths, user catpaths, or both).
    1264   * Return catdir mapping or NULL if it isn't a global/user mandir (as
    1265   * appropriate).
    1266   *
    1267   * This routine would seem to work correctly for nls subdirs and would
    1268   * specify the (correct) consistent catpath even if not defined in the
    1269   * config file.
    1270   *
    1271   * Do not return user catpaths when cattype == 0! This is used to decide
    1272   * whether to drop privileges. When cattype != 0 it's OK to return global
    1273   * catpaths.
    1274   */
    1275  char *get_catpath (const char *name, int cattype)
    1276  {
    1277  	const struct config_item *item;
    1278  	char *ret = NULL;
    1279  
    1280  	GL_LIST_FOREACH (config, item)
    1281  		if (((cattype & SYSTEM_CAT) && item->flag == MANDB_MAP) ||
    1282  		    ((cattype & USER_CAT)   && item->flag == MANDB_MAP_USER)) {
    1283  			size_t manlen = strlen (item->key);
    1284  			if (STRNEQ (name, item->key, manlen)) {
    1285  				const char *suffix;
    1286  				char *infix;
    1287  				char *catpath = xstrdup (item->cont);
    1288  
    1289  				/* For NLS subdirectories (e.g.
    1290  				 * /usr/share/man/de -> /var/cache/man/de),
    1291  				 * we need to find the second-last slash, as
    1292  				 * long as this strictly follows the key.
    1293  				 */
    1294  				suffix = strrchr (name, '/');
    1295  				if (!suffix) {
    1296  					ret = appendstr (catpath,
    1297  							 name + manlen,
    1298  							 (void *) 0);
    1299  					break;
    1300  				}
    1301  
    1302  				while (suffix > name + manlen)
    1303  					if (*--suffix == '/')
    1304  						break;
    1305  				if (suffix < name + manlen)
    1306  					suffix = name + manlen;
    1307  				if (*suffix == '/')
    1308  					++suffix;
    1309  				infix = xstrndup (name + manlen,
    1310  						  suffix - (name + manlen));
    1311  				catpath = appendstr (catpath, infix,
    1312  						     (void *) 0);
    1313  				free (infix);
    1314  				if (STRNEQ (suffix, "man", 3)) {
    1315  					suffix += 3;
    1316  					catpath = appendstr (catpath, "cat",
    1317  							     (void *) 0);
    1318  				}
    1319  				catpath = appendstr (catpath, suffix,
    1320  						     (void *) 0);
    1321  			  	ret = catpath;
    1322  				break;
    1323  			}
    1324  		}
    1325  
    1326  	return ret;
    1327  }
    1328  
    1329  /* Check to see if the supplied man directory is a system-wide mandir.
    1330   * Obviously, user directories must not be included here.
    1331   */
    1332  bool ATTRIBUTE_PURE is_global_mandir (const char *dir)
    1333  {
    1334  	const struct config_item *item;
    1335  	bool ret = false;
    1336  
    1337  	GL_LIST_FOREACH (config, item)
    1338  		if (item->flag == MANDB_MAP &&
    1339  		    STRNEQ (dir, item->key, strlen (item->key))) {
    1340  		    	ret = true;
    1341  			break;
    1342  		}
    1343  
    1344  	return ret;
    1345  }
    1346  
    1347  /* Accept a manpath (not a full pathname to a file) and return an FSSTND
    1348     equivalent catpath */
    1349  static char *fsstnd (const char *path)
    1350  {
    1351  	char *manpath;
    1352  	char *catpath;
    1353  	char *element;
    1354  
    1355  	if (strncmp (path, MAN_ROOT, sizeof MAN_ROOT - 1) != 0) {
    1356  		if (!quiet)
    1357  			error (0, 0, _("warning: %s does not begin with %s"),
    1358  			       path, MAN_ROOT);
    1359  		return xstrdup (path);
    1360  	}
    1361  	/* get rid of initial "/usr" */
    1362  	path += sizeof MAN_ROOT - 1;
    1363  	manpath = xstrdup (path);
    1364  	catpath = xmalloc (strlen (path) + sizeof CAT_ROOT - 3);
    1365  
    1366  	/* start with CAT_ROOT */
    1367  	(void) strcpy (catpath, CAT_ROOT);
    1368  
    1369  	/* split up path into elements and deal with accordingly */
    1370  	for (element = strtok (manpath, "/"); element;
    1371  	     element = strtok (NULL, "/")) {
    1372  		if (strncmp (element, "man", 3) == 0) {
    1373  			if (*(element + 3)) {
    1374  				*element = 'c';
    1375  				*(element + 2) = 't';
    1376  			} else
    1377  				continue;
    1378  		}
    1379  		(void) strcat (catpath, "/");
    1380  		(void) strcat (catpath, element);
    1381  	}
    1382  	free (manpath);
    1383  	return catpath;
    1384  }