/*
 * manp.c: Manpath calculations
 *
 * Copyright (C) 1990, 1991 John W. Eaton.
 * Copyright (C) 1994, 1995 Graeme W. Wilford. (Wilf.)
 * Copyright (C) 2001, 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011,
 *               2012 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
 *
 * John W. Eaton
 * jwe@che.utexas.edu
 * Department of Chemical Engineering
 * The University of Texas at Austin
 * Austin, Texas  78712
 *
 * unpack_locale_bits is derived from _nl_explode_name in libintl:
 * Copyright (C) 1995-1998, 2000-2001, 2003, 2005 Free Software Foundation,
 * Inc.
 * Contributed by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.
 * This was originally LGPL v2 or later, but I (Colin Watson) hereby
 * exercise my option under section 3 of LGPL v2 to distribute it under the
 * GPL v2 or later as above.
 *
 * Wed May  4 15:44:47 BST 1994 Wilf. (G.Wilford@ee.surrey.ac.uk): changes
 * to get_dirlist() and manpath().
 *
 * This whole code segment is unfriendly and could do with a complete
 * overhaul.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdbool.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <errno.h>
#include <dirent.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "attribute.h"
#include "canonicalize.h"
#include "error.h"
#include "gl_array_list.h"
#include "gl_linkedhash_list.h"
#include "gl_xlist.h"
#include "xalloc.h"
#include "xgetcwd.h"
#include "xstrndup.h"
#include "xvasprintf.h"

#include "gettext.h"
#define _(String) gettext (String)

#include "manconfig.h"

#include "appendstr.h"
#include "cleanup.h"
#include "debug.h"
#include "fatal.h"
#include "glcontainers.h"
#include "security.h"
#include "util.h"

#include "manp.h"
#include "globbing.h"

enum config_flag {
	MANDATORY,
	MANPATH_MAP,
	MANDB_MAP,
	MANDB_MAP_USER,
	DEFINE,
	DEFINE_USER,
	SECTION,
	SECTION_USER
};

struct config_item {
	char *key;
	char *cont;
	enum config_flag flag;
};

static gl_list_t config;

char *user_config_file = NULL;
bool disable_cache;
int min_cat_width = 80, max_cat_width = 80, cat_width = 0;

static void add_man_subdirs (gl_list_t list, const char *p);
static char *fsstnd (const char *path);
static char *def_path (enum config_flag flag);
static void add_dir_to_list (gl_list_t list, const char *dir);
static void add_dir_to_path_list (gl_list_t list, const char *p);


static void config_item_free (const void *elt)
{
	/* gl_list declares the argument as const, but there doesn't seem to
	 * be a good reason for this.
	 */
	struct config_item *item = (struct config_item *) elt;
	free (item->key);
	free (item->cont);
	free (item);
}

static void add_config (const char *key, const char *cont,
			enum config_flag flag)
{
	struct config_item *item = XMALLOC (struct config_item);
	item->key = xstrdup (key);
	item->cont = xstrdup (cont);
	item->flag = flag;
	gl_list_add_last (config, item);
}

static const char * ATTRIBUTE_PURE get_config (const char *key,
					       enum config_flag flag)
{
	const struct config_item *item;
	char *cont = NULL;

	GL_LIST_FOREACH (config, item)
		if (flag == item->flag && STREQ (key, item->key)) {
			cont = item->cont;
			break;
		}

	return cont;
}

/* Must not return DEFINEs set in ~/.manpath. This is used to fetch
 * definitions used in raised-privilege code; if in doubt, be conservative!
 *
 * If not setuid, this is identical to get_def_user.
 */
const char * ATTRIBUTE_PURE get_def (const char *thing, const char *def)
{
	const char *config_def;

	if (!running_setuid ())
		return get_def_user (thing, def);

	config_def = get_config (thing, DEFINE);
	return config_def ? config_def : def;
}

const char * ATTRIBUTE_PURE get_def_user (const char *thing, const char *def)
{
	const char *config_def = get_config (thing, DEFINE_USER);
	if (!config_def)
		config_def = get_config (thing, DEFINE);
	return config_def ? config_def : def;
}

static void add_sections (char *sections, bool user)
{
	char *section_list = xstrdup (sections);
	char *sect;
	bool first = true;

	debug ("  Added sections: ");
	for (sect = strtok (section_list, " "); sect;
	     sect = strtok (NULL, " ")) {
		add_config (sect, "", user ? SECTION_USER : SECTION);
		if (!first)
			debug (", ");
		debug ("`%s'", sect);
		first = false;
	}
	debug (".\n");
	free (section_list);
}

gl_list_t get_sections (void)
{
	const struct config_item *item;
	int length_user = 0, length = 0;
	gl_list_t sections;
	enum config_flag flag;

	GL_LIST_FOREACH (config, item) {
		if (item->flag == SECTION_USER)
			length_user++;
		else if (item->flag == SECTION)
			length++;
	}
	sections = new_string_list (GL_ARRAY_LIST, true);
	if (length_user)
		flag = SECTION_USER;
	else
		flag = SECTION;
	GL_LIST_FOREACH (config, item)
		if (item->flag == flag)
			gl_list_add_last (sections, xstrdup (item->key));
	return sections;
}

static void add_def (const char *thing, const char *config_def, bool user)
{
	add_config (thing, config_def, user ? DEFINE_USER : DEFINE);

	debug ("  Defined `%s' as `%s'.\n", thing, config_def);
}

static void add_manpath_map (const char *path, const char *mandir)
{
	if (!path || !mandir)
		return;

	add_config (path, mandir, MANPATH_MAP);

	debug ("  Path `%s' mapped to mandir `%s'.\n", path, mandir);
}

static void add_mandb_map (const char *mandir, const char *catdir, bool user)
{
	char *tmpcatdir;

	if (!mandir)
		return;

	if (STREQ (catdir, "FSSTND"))
		tmpcatdir = fsstnd (mandir);
	else
		tmpcatdir = xstrdup (catdir);

	if (!tmpcatdir)
		return;

	add_config (mandir, tmpcatdir, user ? MANDB_MAP_USER : MANDB_MAP);

	debug ("  %s mandir `%s', catdir `%s'.\n",
	       user ? "User" : "Global", mandir, tmpcatdir);

	free (tmpcatdir);
}

static void add_mandatory (const char *mandir)
{
	if (!mandir)
		return;

	add_config (mandir, "", MANDATORY);

	debug ("  Mandatory mandir `%s'.\n", mandir);
}

/* accept (NULL or oldpath) and new path component. return new path */
static char *pathappend (char *oldpath, const char *appendage)
{
	assert ((!oldpath || *oldpath) && appendage);
	/* Remove duplicates */
	if (oldpath) {
		char *oldpathtok = xstrdup (oldpath), *tok;
		char *app_dedup = xstrdup (appendage);
		char *oldpathtok_ptr = oldpathtok;
		for (tok = strsep (&oldpathtok_ptr, ":"); tok;
		     tok = strsep (&oldpathtok_ptr, ":")) {
			char *search;
			if (!*tok)	    /* ignore empty fields */
				continue;
			search = strstr (app_dedup, tok);
			while (search) {
				char *terminator = search + strlen (tok);
				if (search > app_dedup && search[-1] != ':')
					/* Ignore suffix matches. */
					;
				else if (!*terminator) {
					/* End of the string, so chop here. */
					*search = 0;
					while (search > app_dedup &&
					       *--search == ':')
						*search = 0;
					break;
				} else if (*terminator == ':') {
					char *newapp;
					*search = 0;
					newapp = xasprintf ("%s%s", app_dedup,
							    terminator + 1);
					assert (newapp);
					free (app_dedup);
					app_dedup = newapp;
				}
				search = strstr (terminator, tok);
			}
		}
		free (oldpathtok);
		if (!STREQ (appendage, app_dedup))
			debug ("%s:%s reduced to %s%s%s\n",
			       oldpath, appendage,
			       oldpath, *app_dedup ? ":" : "", app_dedup);
		if (*app_dedup)
			oldpath = appendstr (oldpath, ":", app_dedup,
					     (void *) 0);
		free (app_dedup);
		return oldpath;
	} else
		return xstrdup (appendage);
}

static void gripe_reading_mp_config (const char *file)
{
	error (FAIL, 0,
	       _("can't make sense of the manpath configuration file %s"),
	       file);
}

static void gripe_stat_file (const char *file)
{
	debug_error (_("warning: %s"), file);
}

static void gripe_not_directory (const char *dir)
{
	if (!quiet)
		error (0, 0, _("warning: %s isn't a directory"), dir);
}

/* accept a manpath list, separated with ':', return the associated
   catpath list */
char *cat_manpath (char *manp)
{
	char *catp = NULL;
	const char *path, *catdir;

	for (path = strsep (&manp, ":"); path; path = strsep (&manp, ":")) {
		catdir = get_config (path, MANDB_MAP_USER);
		if (!catdir)
			catdir = get_config (path, MANDB_MAP);
		catp = catdir ? pathappend (catp, catdir)
			      : pathappend (catp, path);
	}

	return catp;
}

/* Unpack a glibc-style locale into its component parts.
 *
 * This function was inspired by _nl_explode_name in libintl; I've rewritten
 * it here with extensive modifications in order not to require libintl or
 * glibc internals, because this API is more convenient for man-db, and to
 * be consistent with surrounding style. I also dropped the normalised
 * codeset handling, which we don't need here.
 */
void unpack_locale_bits (const char *locale, struct locale_bits *bits)
{
	const char *p, *start;

	bits->language = NULL;
	bits->territory = NULL;
	bits->codeset = NULL;
	bits->modifier = NULL;

	/* Now we determine the single parts of the locale name. First look
	 * for the language. Termination symbols are '_', '.', and '@'.
	 */
	p = locale;
	while (*p && *p != '_' && *p != '.' && *p != '@')
		++p;
	if (p == locale) {
		/* This does not make sense: language has to be specified.
		 * Use this entry as it is without exploding. Perhaps it is
		 * an alias.
		 */
		bits->language = xstrdup (locale);
		goto out;
	}
	bits->language = xstrndup (locale, p - locale);

	if (*p == '_') {
		/* Next is the territory. */
		start = ++p;
		while (*p && *p != '.' && *p != '@')
			++p;
		bits->territory = xstrndup (start, p - start);
	}

	if (*p == '.') {
		/* Next is the codeset. */
		start = ++p;
		while (*p && *p != '@')
			++p;
		bits->codeset = xstrndup (start, p - start);
	}

	if (*p == '@')
		/* Next is the modifier. */
		bits->modifier = xstrdup (++p);

out:
	if (!bits->territory)
		bits->territory = xstrdup ("");
	if (!bits->codeset)
		bits->codeset = xstrdup ("");
	if (!bits->modifier)
		bits->modifier = xstrdup ("");
}

/* Free the contents of a locale_bits structure populated by
 * unpack_locale_bits. Does not free the pointer argument.
 */
void free_locale_bits (struct locale_bits *bits)
{
	free (bits->language);
	free (bits->territory);
	free (bits->codeset);
	free (bits->modifier);
}


static char *get_nls_manpath (const char *manpathlist, const char *locale)
{
	struct locale_bits lbits;
	char *manpath = NULL;
	char *manpathlist_copy, *path, *manpathlist_ptr;

	unpack_locale_bits (locale, &lbits);
	if (STREQ (lbits.language, "C") || STREQ (lbits.language, "POSIX")) {
		free_locale_bits (&lbits);
		return xstrdup (manpathlist);
	}

	manpathlist_copy = xstrdup (manpathlist);
	manpathlist_ptr = manpathlist_copy;
	for (path = strsep (&manpathlist_ptr, ":"); path;
	     path = strsep (&manpathlist_ptr, ":")) {
		DIR *mandir = opendir (path);
		struct dirent *mandirent;

		if (!mandir)
			continue;

		while ((mandirent = readdir (mandir)) != NULL) {
			const char *name;
			struct locale_bits mbits;
			char *fullpath;

			name = mandirent->d_name;
			if (STREQ (name, ".") || STREQ (name, ".."))
				continue;
			if (STRNEQ (name, "man", 3))
				continue;
			fullpath = xasprintf ("%s/%s", path, name);
			if (is_directory (fullpath) != 1) {
				free (fullpath);
				continue;
			}

			unpack_locale_bits (name, &mbits);
			if (STREQ (lbits.language, mbits.language) &&
			    (!*mbits.territory ||
			     STREQ (lbits.territory, mbits.territory)) &&
			    (!*mbits.modifier ||
			     STREQ (lbits.modifier, mbits.modifier)))
				manpath = pathappend (manpath, fullpath);
			free_locale_bits (&mbits);
			free (fullpath);
		}

		if (STREQ (lbits.language, "en"))
			/* For English, we look in the subdirectories as
			 * above just in case there's something like
			 * en_GB.UTF-8, but it's more probable that English
			 * manual pages reside at the top level.
			 */
			manpath = pathappend (manpath, path);

		closedir (mandir);
	}
	free (manpathlist_copy);

	free_locale_bits (&lbits);
	return manpath;
}

char *add_nls_manpaths (const char *manpathlist, const char *locales)
{
	char *manpath = NULL;
	char *locales_copy, *tok, *locales_ptr;
	char *locale_manpath;

	debug ("add_nls_manpaths(): processing %s\n", manpathlist);

	if (locales == NULL || *locales == '\0')
		return xstrdup (manpathlist);

	/* For each locale, we iterate over the manpath and find appropriate
	 * locale directories for each item. We then concatenate the results
	 * for all locales. In other words, LANGUAGE=fr:de and
	 * manpath=/usr/share/man:/usr/local/share/man could result in
	 * something like this list:
	 *
	 *   /usr/share/man/fr
	 *   /usr/local/share/man/fr
	 *   /usr/share/man/de
	 *   /usr/local/share/man/de
	 *   /usr/share/man
	 *   /usr/local/share/man
	 *
	 * This assumes that it's more important to have documentation in
	 * the preferred language than to have documentation for the correct
	 * object (in the case where there are different versions of a
	 * program in different hierarchies, for example). It is not
	 * entirely obvious that this is the right assumption, but on the
	 * other hand the other choice is not entirely obvious either. We
	 * tie-break on "we've always done it this way", and people can use
	 * 'man -a' or whatever in the occasional case where we get it
	 * wrong.
	 *
	 * We go to no special effort to de-duplicate directories here.
	 * create_pathlist will sort it out later; note that it preserves
	 * order in that it keeps the first of any duplicate set in its
	 * original position.
	 */

	locales_copy = xstrdup (locales);
	locales_ptr = locales_copy;
	for (tok = strsep (&locales_ptr, ":"); tok;
	     tok = strsep (&locales_ptr, ":")) {
		if (!*tok)	/* ignore empty fields */
			continue;
		debug ("checking for locale %s\n", tok);

		locale_manpath = get_nls_manpath (manpathlist, tok);
		if (locale_manpath) {
			if (manpath)
				manpath = appendstr (manpath, ":",
						     locale_manpath,
						     (void *) 0);
			else
				manpath = xstrdup (locale_manpath);
			free (locale_manpath);
		}
	}
	free (locales_copy);

	/* Always try untranslated pages as a last resort. */
	locale_manpath = get_nls_manpath (manpathlist, "C");
	if (locale_manpath) {
		if (manpath)
			manpath = appendstr (manpath, ":",
					     locale_manpath, (void *) 0);
		else
			manpath = xstrdup (locale_manpath);
		free (locale_manpath);
	}

	return manpath;
}

static char *add_system_manpath (const char *systems, const char *manpathlist)
{
	char *one_system;
	char *manpath = NULL;
	char *tmpsystems;

	if (!systems)
		systems = getenv ("SYSTEM");

	if (!systems || !*systems)
		return xstrdup (manpathlist);

	/* Avoid breaking the environment. */
	tmpsystems = xstrdup (systems);

	/* For each systems component */

	for (one_system = strtok (tmpsystems, ",:"); one_system;
	     one_system = strtok (NULL, ",:")) {

		/* For each manpathlist component */

		if (!STREQ (one_system, "man")) {
			const char *next, *path;
			char *newdir = NULL;
			for (path = manpathlist; path; path = next) {
				int status;
				char *element;

				next = strchr (path, ':');
				if (next) {
					element = xstrndup (path, next - path);
					++next;
				} else
					element = xstrdup (path);
				newdir = appendstr (newdir, element, "/",
						    one_system, (void *) 0);
				free (element);

				status = is_directory (newdir);

				if (status == 0)
					gripe_not_directory (newdir);
				else if (status == 1) {
					debug ("adding %s to manpathlist\n",
					       newdir);
					manpath = pathappend (manpath, newdir);
				} else
					debug_error ("can't stat %s", newdir);
				/* reset newdir */
				*newdir = '\0';
			}
			free (newdir);
		} else
			manpath = pathappend (manpath, manpathlist);
	}
	free (tmpsystems);

	/*
	 * Thu, 21 Nov 1996 22:24:19 +0200 fpolacco@debian.org
	 * bug#5534 (man fails if env var SYSTEM is defined)
	 * with error [man: internal manpath equates to NULL]
	 * the reason: is_directory (newdir); returns -1
	 */
	if (!manpath) {
		debug ("add_system_manpath(): "
		       "internal manpath equates to NULL\n");
		return xstrdup (manpathlist);
	}
	return manpath;
}

/*
 * Always add system and locale directories to pathlist.
 * If the environment variable MANPATH is set, return it.
 * If the environment variable PATH is set and has a nonzero length,
 * try to determine the corresponding manpath, otherwise, return the
 * default manpath.
 *
 * The man_db.config file is used to map system wide /bin directories
 * to top level man page directories.
 *
 * For directories which are in the user's path but not in the
 * man_db.config file, see if there is a subdirectory `man' or `MAN'.
 * If so, add that directory to the path.  Example:  user has
 * $HOME/bin in his path and the directory $HOME/bin/man exists -- the
 * directory $HOME/bin/man will be added to the manpath.
 */
static char *guess_manpath (const char *systems)
{
	const char *path = getenv ("PATH");
	char *manpathlist, *manpath;

	if (path == NULL || getenv ("MAN_TEST_DISABLE_PATH")) {
		/* Things aren't going to work well, but hey... */
		if (path == NULL && !quiet)
			error (0, 0, _("warning: $PATH not set"));

		manpathlist = def_path (MANDATORY);
	} else {
		if (strlen (path) == 0) {
			/* Things aren't going to work well here either... */
			if (!quiet)
				error (0, 0, _("warning: empty $PATH"));

			return add_system_manpath (systems,
						   def_path (MANDATORY));
		}

		manpathlist = get_manpath_from_path (path, true);
	}
	manpath = add_system_manpath (systems, manpathlist);
	free (manpathlist);
	return manpath;
}

char *get_manpath (const char *systems)
{
	char *manpathlist;

	/* need to read config file even if MANPATH set, for mandb(8) */
	read_config_file (false);

	manpathlist = getenv ("MANPATH");
	if (manpathlist && *manpathlist) {
		char *system1, *system2, *guessed;
		char *pos;
		/* This must be it. */
		if (manpathlist[0] == ':') {
			if (!quiet)
				error (0, 0,
				       _("warning: $MANPATH set, "
					 "prepending %s"),
				       CONFIG_FILE);
			system1 = add_system_manpath (systems, manpathlist);
			guessed = guess_manpath (systems);
			manpathlist = xasprintf ("%s%s", guessed, system1);
			free (guessed);
			free (system1);
		} else if (manpathlist[strlen (manpathlist) - 1] == ':') {
			if (!quiet)
				error (0, 0,
				       _("warning: $MANPATH set, "
					 "appending %s"),
				       CONFIG_FILE);
			system1 = add_system_manpath (systems, manpathlist);
			guessed = guess_manpath (systems);
			manpathlist = xasprintf ("%s%s", system1, guessed);
			free (guessed);
			free (system1);
		} else if ((pos = strstr (manpathlist,"::"))) {
			*(pos++) = '\0';
			if (!quiet)
				error (0, 0,
				       _("warning: $MANPATH set, "
					 "inserting %s"),
				       CONFIG_FILE);
			system1 = add_system_manpath (systems, manpathlist);
			guessed = guess_manpath (systems);
			system2 = add_system_manpath (systems, pos);
			manpathlist = xasprintf ("%s:%s%s", system1, guessed,
						 system2);
			free (system2);
			free (guessed);
			free (system1);
		} else {
			if (!quiet)
				error (0, 0,
				       _("warning: $MANPATH set, ignoring %s"),
				       CONFIG_FILE);
			manpathlist = add_system_manpath (systems,
							  manpathlist);
		}
	} else
		manpathlist = guess_manpath (systems);

	return manpathlist;
}

/* Parse the manpath.config file, extracting appropriate information. */
static void add_to_dirlist (FILE *config_file, bool user)
{
	char *bp;
	char *buf = NULL;
	size_t n = 0;
	char key[512], cont[512];
	int val;
	int c;

	while (getline (&buf, &n, config_file) >= 0) {
		bp = buf;

		while (CTYPE (isspace, *bp))
			bp++;

		/* TODO: would like a (limited) replacement for sscanf()
		 * here that allocates its own memory. At that point check
		 * everything that sprintf()s manpath et al!
		 */
		if (*bp == '#' || *bp == '\0')
			goto next;
		else if (strncmp (bp, "NOCACHE", 7) == 0)
			disable_cache = true;
		else if (strncmp (bp, "NO", 2) == 0)
			goto next;	/* match any word starting with NO */
		else if (sscanf (bp, "MANBIN %*s") == 1)
			goto next;
		else if (sscanf (bp, "MANDATORY_MANPATH %511s", key) == 1)
			add_mandatory (key);
		else if (sscanf (bp, "MANPATH_MAP %511s %511s",
			 key, cont) == 2)
			add_manpath_map (key, cont);
		else if ((c = sscanf (bp, "MANDB_MAP %511s %511s",
				      key, cont)) > 0)
			add_mandb_map (key, c == 2 ? cont : key, user);
		else if ((c = sscanf (bp, "DEFINE %511s %511[^\n]",
				      key, cont)) > 0)
			add_def (key, c == 2 ? cont : "", user);
		else if (sscanf (bp, "SECTION %511[^\n]", cont) == 1)
			add_sections (cont, user);
		else if (sscanf (bp, "SECTIONS %511[^\n]", cont) == 1)
			/* Since I keep getting it wrong ... */
			add_sections (cont, user);
		else if (sscanf (bp, "MINCATWIDTH %d", &val) == 1)
			min_cat_width = val;
		else if (sscanf (bp, "MAXCATWIDTH %d", &val) == 1)
			max_cat_width = val;
		else if (sscanf (bp, "CATWIDTH %d", &val) == 1)
			cat_width = val;
	 	else {
			error (0, 0, _("can't parse directory list `%s'"), bp);
			gripe_reading_mp_config (CONFIG_FILE);
		}

next:
		free (buf);
		buf = NULL;
	}

	free (buf);
}

static void free_config_file (void *unused MAYBE_UNUSED)
{
	gl_list_free (config);
}

void read_config_file (bool optional)
{
	static bool done = false;
	char *dotmanpath = NULL;
	FILE *config_file;

	if (done)
		return;

	config = gl_list_create_empty (GL_ARRAY_LIST, NULL, NULL,
				       config_item_free, true);
	push_cleanup (free_config_file, NULL, 0);

	if (user_config_file)
		dotmanpath = xstrdup (user_config_file);
	else {
		char *home = getenv ("HOME");
		if (home)
			dotmanpath = xasprintf ("%s/.manpath", home);
	}
	if (dotmanpath) {
		config_file = fopen (dotmanpath, "r");
		if (config_file != NULL) {
			debug ("From the config file %s:\n", dotmanpath);
			add_to_dirlist (config_file, true);
			fclose (config_file);
		}
		free (dotmanpath);
	}

	if (getenv ("MAN_TEST_DISABLE_SYSTEM_CONFIG") == NULL) {
		config_file = fopen (CONFIG_FILE, "r");
		if (config_file == NULL) {
			if (optional)
				debug ("can't open %s; continuing anyway\n",
				       CONFIG_FILE);
			else
				error (FAIL, 0,
				       _("can't open the manpath "
					 "configuration file %s"),
				       CONFIG_FILE);
		} else {
			debug ("From the config file %s:\n", CONFIG_FILE);

			add_to_dirlist (config_file, false);
			fclose (config_file);
		}
	}

	done = true;
}


/*
 * Construct the default manpath.  This picks up mandatory manpaths
 * only.
 */
static char *def_path (enum config_flag flag)
{
	char *manpath = NULL;
	const struct config_item *item;

	GL_LIST_FOREACH (config, item)
		if (item->flag == flag) {
			gl_list_t expanded_dirs;
			const char *expanded_dir;

			expanded_dirs = expand_path (item->key);
			GL_LIST_FOREACH (expanded_dirs, expanded_dir) {
				int status = is_directory (expanded_dir);

				if (status < 0)
					gripe_stat_file (expanded_dir);
				else if (status == 0 && !quiet)
					error (0, 0,
					       _("warning: mandatory "
						 "directory %s doesn't exist"),
					       expanded_dir);
				else if (status == 1)
					manpath = pathappend (manpath,
							      expanded_dir);
			}
			gl_list_free (expanded_dirs);
		}

	/* If we have complete config file failure... */
	if (!manpath)
		return xstrdup ("/usr/man");

	return manpath;
}

/*
 * For each directory in the user's path, see if it is one of the
 * directories listed in the man_db.config file.  If so, and it is
 * not already in the manpath, add it.  If the directory is not listed
 * in the man_db.config file, see if there is a subdirectory `../man' or
 * `man', or, for FHS-compliance, `../share/man' or `share/man'.  If so,
 * and it is not already in the manpath, add it.
 * Example:  user has $HOME/bin in his path and the directory
 * $HOME/man exists -- the directory $HOME/man will be added
 * to the manpath.
 */
char *get_manpath_from_path (const char *path, bool mandatory)
{
	gl_list_t tmplist;
	const struct config_item *config_item;
	int len;
	char *tmppath;
	char *p;
	char *end;
	char *manpathlist;
	char *item;

	tmplist = new_string_list (GL_LINKEDHASH_LIST, false);
	tmppath = xstrdup (path);

	for (end = p = tmppath; end; p = end + 1) {
		bool manpath_map_found = false;

		end = strchr (p, ':');
		if (end)
			*end = '\0';

		/* don't do this for current dir ("." or empty entry in PATH) */
		if (*p == '\0' || strcmp (p, ".") == 0)
			continue;

		debug ("path directory %s ", p);

		/* If the directory we're working on has MANPATH_MAP entries
		 * in the config file, add them to the list.
		 */
		GL_LIST_FOREACH (config, config_item) {
			if (MANPATH_MAP != config_item->flag ||
			    !STREQ (p, config_item->key))
				continue;
			if (!manpath_map_found)
				debug ("is in the config file\n");
			manpath_map_found = true;
			add_dir_to_list (tmplist, config_item->cont);
		}

		 /* The directory we're working on isn't in the config file.
		    See if it has ../man, man, ../share/man, or share/man
		    subdirectories.  If so, and they haven't been added to
		    the list, do. */

		if (!manpath_map_found) {
			debug ("is not in the config file\n");
			add_man_subdirs (tmplist, p);
		}
	}

	free (tmppath);

	if (mandatory) {
		debug ("adding mandatory man directories\n");

		GL_LIST_FOREACH (config, config_item) {
			if (config_item->flag == MANDATORY)
				add_dir_to_list (tmplist, config_item->key);
		}
	}

	len = 0;
	GL_LIST_FOREACH (tmplist, item)
		len += strlen (item) + 1;

	if (!len)
		/* No path elements in configuration file or with
		 * appropriate subdirectories.
		 */
		return xstrdup ("");

	manpathlist = xmalloc (len);
	*manpathlist = '\0';

	p = manpathlist;
	GL_LIST_FOREACH (tmplist, item) {
		len = strlen (item);
		memcpy (p, item, len);
		p += len;
		*p++ = ':';
	}

	p[-1] = '\0';

	gl_list_free (tmplist);

	return manpathlist;
}

/* Add a directory to the manpath list if it isn't already there. */
static void add_expanded_dir_to_list (gl_list_t list, const char *dir)
{
	int status;

	if (gl_list_search (list, dir))
		return;

	/* Not found -- add it. */

	status = is_directory (dir);

	if (status < 0)
		gripe_stat_file (dir);
	else if (status == 0)
		gripe_not_directory (dir);
	else if (status == 1) {
		debug ("  adding %s to manpath\n", dir);
		gl_list_add_last (list, xstrdup (dir));
	}
}

/*
 * Add a directory to the manpath list if it isn't already there, expanding
 * wildcards.
 */
static void add_dir_to_list (gl_list_t list, const char *dir)
{
	gl_list_t expanded_dirs;
	const char *expanded_dir;

	expanded_dirs = expand_path (dir);
	GL_LIST_FOREACH (expanded_dirs, expanded_dir)
		add_expanded_dir_to_list (list, expanded_dir);
	gl_list_free (expanded_dirs);
}

/* path does not exist in config file: check to see if path/../man,
   path/man, path/../share/man, or path/share/man exist, and add them to the
   list if they do. */
static void add_man_subdirs (gl_list_t list, const char *path)
{
	char *newpath;

	/* don't assume anything about path, especially that it ends in
	   "bin" or even has a '/' in it! */

	char *subdir = strrchr (path, '/');
	if (subdir) {
		newpath = xasprintf ("%.*s/man", (int) (subdir - path), path);
		if (is_directory (newpath) == 1)
			add_dir_to_list (list, newpath);
		free (newpath);
	}

	newpath = xasprintf ("%s/man", path);
	if (is_directory (newpath) == 1)
		add_dir_to_list (list, newpath);
	free (newpath);

	if (subdir) {
		newpath = xasprintf ("%.*s/share/man",
				     (int) (subdir - path), path);
		if (is_directory (newpath) == 1)
			add_dir_to_list (list, newpath);
		free (newpath);
	}

	newpath = xasprintf ("%s/share/man", path);
	if (is_directory (newpath) == 1)
		add_dir_to_list (list, newpath);
	free (newpath);
}

struct canonicalized_path {
	char *path;
	char *canon_path;
};

static struct canonicalized_path *canonicalized_path_new (const char *path)
{
	char *canon_path;
	struct canonicalized_path *cp = NULL;

	canon_path = canonicalize_file_name (path);
	if (canon_path) {
		cp = XMALLOC (struct canonicalized_path);
		cp->path = xstrdup (path);
		cp->canon_path = canon_path;	/* steal memory */
	}
	return cp;
}

static bool ATTRIBUTE_PURE canonicalized_path_equals (const void *elt1,
						      const void *elt2)
{
	const struct canonicalized_path *cp1 = elt1, *cp2 = elt2;
	return string_equals (cp1->canon_path, cp2->canon_path);
}

static size_t ATTRIBUTE_PURE canonicalized_path_hash (const void *elt)
{
	const struct canonicalized_path *cp = elt;
	return string_hash (cp->canon_path);
}

static void canonicalized_path_free (const void *elt)
{
	/* gl_list declares the argument as const, but there doesn't seem to
	 * be a good reason for this.
	 */
	struct canonicalized_path *cp = (struct canonicalized_path *) elt;
	free (cp->path);
	free (cp->canon_path);
	free (cp);
}

static void add_dir_to_path_list (gl_list_t list, const char *p)
{
	gl_list_t expanded_dirs;
	char *expanded_dir;

	expanded_dirs = expand_path (p);
	GL_LIST_FOREACH (expanded_dirs, expanded_dir) {
		int status = is_directory (expanded_dir);

		if (status < 0)
			gripe_stat_file (expanded_dir);
		else if (status == 0)
			gripe_not_directory (expanded_dir);
		else {
			char *path;
			struct canonicalized_path *cp;

			/* deal with relative paths */
			if (*expanded_dir != '/') {
				char *cwd = xgetcwd ();
				if (!cwd)
					fatal (errno,
					       _("can't determine current directory"));
				path = appendstr (cwd, "/", expanded_dir,
						  (void *) 0);
			} else
				path = xstrdup (expanded_dir);

			cp = canonicalized_path_new (path);
			if (cp && !gl_list_search (list, cp)) {
				debug ("adding %s to manpathlist\n", path);
				gl_list_add_last (list, cp);
			} else if (cp)
				canonicalized_path_free (cp);
			free (path);
		}
	}
	gl_list_free (expanded_dirs);
}

gl_list_t create_pathlist (const char *manp)
{
	gl_list_t canonicalized_list, list;
	const char *p, *end;
	const struct canonicalized_path *cp;

	/* Expand the manpath into a list of (path, canonicalized path)
	 * pairs for easier handling.  add_dir_to_path_list only adds items
	 * if they do not have the same canonicalized path as an existing
	 * item, thereby eliminating duplicates due to symlinks.
	 * For each entry, add corresponding OVERRIDE_DIR if configured.
	 */

	canonicalized_list = gl_list_create_empty
		(GL_LINKEDHASH_LIST, canonicalized_path_equals,
		 canonicalized_path_hash, canonicalized_path_free, false);
	for (p = manp;; p = end + 1) {
		char *element;

		end = strchr (p, ':');
		element = end ? xstrndup (p, end - p) : xstrdup (p);

		if (*OVERRIDE_DIR) {
			char *element_override = xasprintf
				("%s/%s", element, OVERRIDE_DIR);
			add_dir_to_path_list
				(canonicalized_list, element_override);
			free (element_override);
		}

		add_dir_to_path_list (canonicalized_list, element);
		free (element);

		if (!end)
			break;
	}

	list = new_string_list (GL_ARRAY_LIST, false);
	GL_LIST_FOREACH (canonicalized_list, cp)
		gl_list_add_last (list, xstrdup (cp->path));

	if (debug_level) {
		debug ("final search path = ");
		GL_LIST_FOREACH (list, p) {
			if (!gl_list_previous_node (list, list_node))
				debug ("%s", p);
			else
				debug (":%s", p);
		}
		debug ("\n");
	}

	gl_list_free (canonicalized_list);
	return list;
}

void free_pathlist (gl_list_t list)
{
	gl_list_free (list);
}

/* Routine to get list of named system and user manpaths (in reverse order). */
char *get_mandb_manpath (void)
{
	char *manpath = NULL;
	const struct config_item *item;

	GL_LIST_FOREACH (config, item)
		if (item->flag == MANDB_MAP || item->flag == MANDB_MAP_USER)
			manpath = pathappend (manpath, item->key);

	return manpath;
}

/* Take manpath or manfile path as the first argument, and the type of
 * catpaths we want as the other (system catpaths, user catpaths, or both).
 * Return catdir mapping or NULL if it isn't a global/user mandir (as
 * appropriate).
 *
 * This routine would seem to work correctly for nls subdirs and would
 * specify the (correct) consistent catpath even if not defined in the
 * config file.
 *
 * Do not return user catpaths when cattype == 0! This is used to decide
 * whether to drop privileges. When cattype != 0 it's OK to return global
 * catpaths.
 */
char *get_catpath (const char *name, int cattype)
{
	const struct config_item *item;
	char *ret = NULL;

	GL_LIST_FOREACH (config, item)
		if (((cattype & SYSTEM_CAT) && item->flag == MANDB_MAP) ||
		    ((cattype & USER_CAT)   && item->flag == MANDB_MAP_USER)) {
			size_t manlen = strlen (item->key);
			if (STRNEQ (name, item->key, manlen)) {
				const char *suffix;
				char *infix;
				char *catpath = xstrdup (item->cont);

				/* For NLS subdirectories (e.g.
				 * /usr/share/man/de -> /var/cache/man/de),
				 * we need to find the second-last slash, as
				 * long as this strictly follows the key.
				 */
				suffix = strrchr (name, '/');
				if (!suffix) {
					ret = appendstr (catpath,
							 name + manlen,
							 (void *) 0);
					break;
				}

				while (suffix > name + manlen)
					if (*--suffix == '/')
						break;
				if (suffix < name + manlen)
					suffix = name + manlen;
				if (*suffix == '/')
					++suffix;
				infix = xstrndup (name + manlen,
						  suffix - (name + manlen));
				catpath = appendstr (catpath, infix,
						     (void *) 0);
				free (infix);
				if (STRNEQ (suffix, "man", 3)) {
					suffix += 3;
					catpath = appendstr (catpath, "cat",
							     (void *) 0);
				}
				catpath = appendstr (catpath, suffix,
						     (void *) 0);
			  	ret = catpath;
				break;
			}
		}

	return ret;
}

/* Check to see if the supplied man directory is a system-wide mandir.
 * Obviously, user directories must not be included here.
 */
bool ATTRIBUTE_PURE is_global_mandir (const char *dir)
{
	const struct config_item *item;
	bool ret = false;

	GL_LIST_FOREACH (config, item)
		if (item->flag == MANDB_MAP &&
		    STRNEQ (dir, item->key, strlen (item->key))) {
		    	ret = true;
			break;
		}

	return ret;
}

/* Accept a manpath (not a full pathname to a file) and return an FSSTND
   equivalent catpath */
static char *fsstnd (const char *path)
{
	char *manpath;
	char *catpath;
	char *element;

	if (strncmp (path, MAN_ROOT, sizeof MAN_ROOT - 1) != 0) {
		if (!quiet)
			error (0, 0, _("warning: %s does not begin with %s"),
			       path, MAN_ROOT);
		return xstrdup (path);
	}
	/* get rid of initial "/usr" */
	path += sizeof MAN_ROOT - 1;
	manpath = xstrdup (path);
	catpath = xmalloc (strlen (path) + sizeof CAT_ROOT - 3);

	/* start with CAT_ROOT */
	(void) strcpy (catpath, CAT_ROOT);

	/* split up path into elements and deal with accordingly */
	for (element = strtok (manpath, "/"); element;
	     element = strtok (NULL, "/")) {
		if (strncmp (element, "man", 3) == 0) {
			if (*(element + 3)) {
				*element = 'c';
				*(element + 2) = 't';
			} else
				continue;
		}
		(void) strcat (catpath, "/");
		(void) strcat (catpath, element);
	}
	free (manpath);
	return catpath;
}
