diff options
Diffstat (limited to '')
-rw-r--r-- | src/manp.c | 1384 |
1 files changed, 1384 insertions, 0 deletions
diff --git a/src/manp.c b/src/manp.c new file mode 100644 index 0000000..bcb919c --- /dev/null +++ b/src/manp.c @@ -0,0 +1,1384 @@ +/* + * 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; + char *trimmed_path = xstrdup (path); + + /* don't assume anything about path, especially that it ends in + "bin" or even has a '/' in it! */ + + char *subdir = strrchr (trimmed_path, '/'); + + /* Trailing slash or root directory. Remove the trailing slash and + try again. If root directory, subdir will be null, so we don't + cause a segfault. If a path element is '/', we will correctly add + /man and /share/man manpaths. */ + if (subdir && strncmp (subdir, "/", 2) == 0) { + subdir[0] = '\0'; + subdir = strrchr (trimmed_path, '/'); + } + if (subdir) { + newpath = xasprintf ("%.*s/man", + (int) (subdir - trimmed_path), + trimmed_path); + if (is_directory (newpath) == 1) + add_dir_to_list (list, newpath); + free (newpath); + } + + newpath = xasprintf ("%s/man", trimmed_path); + if (is_directory (newpath) == 1) + add_dir_to_list (list, newpath); + free (newpath); + + if (subdir) { + newpath = xasprintf ("%.*s/share/man", + (int) (subdir - trimmed_path), + trimmed_path); + if (is_directory (newpath) == 1) + add_dir_to_list (list, newpath); + free (newpath); + } + + newpath = xasprintf ("%s/share/man", trimmed_path); + if (is_directory (newpath) == 1) + add_dir_to_list (list, newpath); + free (newpath); + + free (trimmed_path); +} + +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; +} |