/* * 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 , 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 #include #include #include #include #include #include #include #include #include #include #include #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; }