summaryrefslogtreecommitdiffstats
path: root/src/manp.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:16:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:16:24 +0000
commit9221dca64f0c8b5de72727491e41cf63e902eaab (patch)
treed8cbbf520eb4b5c656a54b2e36947008dcb751ad /src/manp.c
parentInitial commit. (diff)
downloadman-db-9221dca64f0c8b5de72727491e41cf63e902eaab.tar.xz
man-db-9221dca64f0c8b5de72727491e41cf63e902eaab.zip
Adding upstream version 2.8.5.upstream/2.8.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/manp.c')
-rw-r--r--src/manp.c1410
1 files changed, 1410 insertions, 0 deletions
diff --git a/src/manp.c b/src/manp.c
new file mode 100644
index 0000000..5111be7
--- /dev/null
+++ b/src/manp.c
@@ -0,0 +1,1410 @@
+/*
+ * 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 <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <errno.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "canonicalize.h"
+#include "xgetcwd.h"
+#include "xvasprintf.h"
+
+#include "gettext.h"
+#define _(String) gettext (String)
+
+#include "manconfig.h"
+
+#include "error.h"
+#include "cleanup.h"
+#include "security.h"
+
+#include "manp.h"
+#include "globbing.h"
+
+struct list {
+ char *key;
+ char *cont;
+ int flag;
+ struct list *next;
+};
+
+static struct list *namestore, *tailstore;
+
+#define SECTION_USER -6
+#define SECTION -5
+#define DEFINE_USER -4
+#define DEFINE -3
+#define MANDB_MAP_USER -2
+#define MANDB_MAP -1
+#define MANPATH_MAP 0
+#define MANDATORY 1
+
+/* DIRLIST list[MAXDIRS]; */
+static char *tmplist[MAXDIRS];
+
+char *user_config_file = NULL;
+int disable_cache;
+int min_cat_width = 80, max_cat_width = 80, cat_width = 0;
+
+static char *has_mandir (const char *p);
+static char *fsstnd (const char *path);
+static char *def_path (int flag);
+static void add_dir_to_list (char **lp, const char *dir);
+static char **add_dir_to_path_list (char **mphead, char **mp, const char *p);
+
+
+static void add_to_list (const char *key, const char *cont, int flag)
+{
+ struct list *list = XMALLOC (struct list);
+ list->key = xstrdup (key);
+ list->cont = xstrdup (cont);
+ list->flag = flag;
+ list->next = NULL;
+ if (tailstore)
+ tailstore->next = list;
+ tailstore = list;
+ if (!namestore)
+ namestore = list;
+}
+
+static const char *get_from_list (const char *key, int flag)
+{
+ struct list *list;
+
+ for (list = namestore; list; list = list->next)
+ if (flag == list->flag && STREQ (key, list->key))
+ return list->cont;
+
+ return NULL;
+}
+
+static struct list *iterate_over_list (struct list *prev, char *key, int flag)
+{
+ struct list *list;
+
+ for (list = prev ? prev->next : namestore; list; list = list->next)
+ if (flag == list->flag && STREQ (key, list->key))
+ return list;
+
+ return NULL;
+}
+
+/* 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 *get_def (const char *thing, const char *def)
+{
+ const char *config_def;
+
+ if (!running_setuid ())
+ return get_def_user (thing, def);
+
+ config_def = get_from_list (thing, DEFINE);
+ return config_def ? config_def : def;
+}
+
+const char *get_def_user (const char *thing, const char *def)
+{
+ const char *config_def = get_from_list (thing, DEFINE_USER);
+ if (!config_def)
+ config_def = get_from_list (thing, DEFINE);
+ return config_def ? config_def : def;
+}
+
+static void print_list (void)
+{
+ struct list *list;
+
+ for (list = namestore; list; list = list->next)
+ debug ("`%s'\t`%s'\t`%d'\n",
+ list->key, list->cont, list->flag);
+}
+
+static void add_sections (char *sections, int user)
+{
+ char *section_list = xstrdup (sections);
+ char *sect;
+
+ for (sect = strtok (section_list, " "); sect;
+ sect = strtok (NULL, " ")) {
+ add_to_list (sect, "", user ? SECTION_USER : SECTION);
+ debug ("Added section `%s'.\n", sect);
+ }
+ free (section_list);
+}
+
+const char **get_sections (void)
+{
+ struct list *list;
+ int length_user = 0, length = 0;
+ const char **sections, **sectionp;
+ int flag;
+
+ for (list = namestore; list; list = list->next) {
+ if (list->flag == SECTION_USER)
+ length_user++;
+ else if (list->flag == SECTION)
+ length++;
+ }
+ if (length_user) {
+ sections = xnmalloc (length_user + 1, sizeof *sections);
+ flag = SECTION_USER;
+ } else {
+ sections = xnmalloc (length + 1, sizeof *sections);
+ flag = SECTION;
+ }
+ sectionp = sections;
+ for (list = namestore; list; list = list->next)
+ if (list->flag == flag)
+ *sectionp++ = list->key;
+ *sectionp = NULL;
+ return sections;
+}
+
+static void add_def (char *thing, char *config_def, int flag, int user)
+{
+ add_to_list (thing, flag == 2 ? 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_to_list (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,
+ int flag, int user)
+{
+ char *tmpcatdir;
+
+ assert (flag > 0);
+
+ if (!mandir)
+ return;
+
+ if (flag == 1)
+ tmpcatdir = xstrdup (mandir);
+ else if (STREQ (catdir, "FSSTND"))
+ tmpcatdir = fsstnd (mandir);
+ else
+ tmpcatdir = xstrdup (catdir);
+
+ if (!tmpcatdir)
+ return;
+
+ add_to_list (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_to_list (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 (!*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);
+ 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);
+}
+
+static void gripe_overlong_list (void)
+{
+ error (FAIL, 0, _("manpath list too long"));
+}
+
+/* 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_from_list (path, MANDB_MAP_USER);
+ if (!catdir)
+ catdir = get_from_list (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, 1);
+ }
+ 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 (0);
+
+ 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, int user)
+{
+ char *bp;
+ char *buf = NULL;
+ size_t n = 0;
+ char key[512], cont[512];
+ int val;
+ int c;
+
+ while (getline (&buf, &n, config) >= 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 = 1;
+ 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, cont, c, user);
+ else if ((c = sscanf (bp, "DEFINE %511s %511[^\n]",
+ key, cont)) > 0)
+ add_def (key, cont, c, 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 ATTRIBUTE_UNUSED)
+{
+ struct list *list = namestore, *prev;
+
+ while (list) {
+ free (list->key);
+ free (list->cont);
+ prev = list;
+ list = list->next;
+ free (prev);
+ }
+
+ namestore = tailstore = NULL;
+}
+
+void read_config_file (int optional)
+{
+ static int done = 0;
+ char *dotmanpath = NULL;
+ FILE *config;
+
+ if (done)
+ return;
+
+ 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 = fopen (dotmanpath, "r");
+ if (config != NULL) {
+ debug ("From the config file %s:\n\n", dotmanpath);
+ add_to_dirlist (config, 1);
+ fclose (config);
+ }
+ free (dotmanpath);
+ }
+
+ if (getenv ("MAN_TEST_DISABLE_SYSTEM_CONFIG") == NULL) {
+ config = fopen (CONFIG_FILE, "r");
+ if (config == 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\n", CONFIG_FILE);
+
+ add_to_dirlist (config, 0);
+ fclose (config);
+ }
+ }
+
+ print_list ();
+
+ done = 1;
+}
+
+
+/*
+ * Construct the default manpath. This picks up mandatory manpaths
+ * only.
+ */
+static char *def_path (int flag)
+{
+ char *manpath = NULL;
+ struct list *list;
+
+ for (list = namestore; list; list = list->next)
+ if (list->flag == flag) {
+ char **expanded_dirs;
+ int i;
+
+ expanded_dirs = expand_path (list->key);
+ for (i = 0; expanded_dirs[i]; i++) {
+ int status = is_directory (expanded_dirs[i]);
+
+ if (status < 0)
+ gripe_stat_file (expanded_dirs[i]);
+ else if (status == 0 && !quiet)
+ error (0, 0,
+ _("warning: mandatory "
+ "directory %s doesn't exist"),
+ expanded_dirs[i]);
+ else if (status == 1)
+ manpath = pathappend
+ (manpath, expanded_dirs[i]);
+ free (expanded_dirs[i]);
+ }
+ free (expanded_dirs);
+ }
+
+ /* If we have complete config file failure... */
+ if (!manpath)
+ return xstrdup ("/usr/man");
+
+ return manpath;
+}
+
+/*
+ * If specified with configure, append OVERRIDE_DIR to dir param and add it
+ * to the lp list.
+ */
+static void insert_override_dir (char **lp, const char *dir)
+{
+ char *override_dir = NULL;
+
+ if (!strlen (OVERRIDE_DIR))
+ return;
+
+ if ((override_dir = xasprintf ("%s/%s", dir, OVERRIDE_DIR))) {
+ add_dir_to_list (lp, override_dir);
+ free (override_dir);
+ }
+}
+
+/*
+ * 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, int mandatory)
+{
+ int len;
+ char *tmppath;
+ char *t;
+ char *p;
+ char **lp;
+ char *end;
+ char *manpathlist;
+ struct list *list;
+
+ tmppath = xstrdup (path);
+
+ for (end = p = tmppath; end; p = end + 1) {
+ struct list *mandir_list;
+
+ 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 ("\npath directory %s ", p);
+
+ mandir_list = iterate_over_list (NULL, p, MANPATH_MAP);
+
+ /*
+ * The directory we're working on is in the config file.
+ * If we haven't added it to the list yet, do.
+ */
+
+ if (mandir_list) {
+ debug ("is in the config file\n");
+ while (mandir_list) {
+ insert_override_dir (tmplist,
+ mandir_list->cont);
+ add_dir_to_list (tmplist, mandir_list->cont);
+ mandir_list = iterate_over_list
+ (mandir_list, p, MANPATH_MAP);
+ }
+
+ /* The directory we're working on isn't in the config file.
+ See if it has ../man or man subdirectories.
+ If so, and it hasn't been added to the list, do. */
+
+ } else {
+ debug ("is not in the config file\n");
+
+ t = has_mandir (p);
+ if (t) {
+ debug ("but does have a ../man, man, "
+ "../share/man, or share/man "
+ "subdirectory\n");
+
+ insert_override_dir (tmplist, t);
+ add_dir_to_list (tmplist, t);
+ free (t);
+ } else
+ debug ("and doesn't have ../man, man, "
+ "../share/man, or share/man "
+ "subdirectories\n");
+ }
+ }
+
+ free (tmppath);
+
+ if (mandatory) {
+ debug ("\nadding mandatory man directories\n\n");
+
+ for (list = namestore; list; list = list->next)
+ if (list->flag == MANDATORY) {
+ insert_override_dir (tmplist, list->key);
+ add_dir_to_list (tmplist, list->key);
+ }
+ }
+
+ len = 0;
+ lp = tmplist;
+ while (*lp != NULL) {
+ len += strlen (*lp) + 1;
+ lp++;
+ }
+
+ if (!len)
+ /* No path elements in configuration file or with
+ * appropriate subdirectories.
+ */
+ return xstrdup ("");
+
+ manpathlist = xmalloc (len);
+ *manpathlist = '\0';
+
+ lp = tmplist;
+ p = manpathlist;
+ while (*lp != NULL) {
+ len = strlen (*lp);
+ memcpy (p, *lp, len);
+ free (*lp);
+ *lp = NULL;
+ p += len;
+ *p++ = ':';
+ lp++;
+ }
+
+ p[-1] = '\0';
+
+ return manpathlist;
+}
+
+/* Add a directory to the manpath list if it isn't already there. */
+static void add_expanded_dir_to_list (char **lp, const char *dir)
+{
+ int status;
+ int pos = 0;
+
+ while (*lp != NULL) {
+ if (pos > MAXDIRS - 1)
+ gripe_overlong_list ();
+ if (!strcmp (*lp, dir)) {
+ debug ("%s is already in the manpath\n", dir);
+ return;
+ }
+ lp++;
+ pos++;
+ }
+
+ /* 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);
+
+ *lp = xstrdup (dir);
+ }
+}
+
+/*
+ * Add a directory to the manpath list if it isn't already there, expanding
+ * wildcards.
+ */
+static void add_dir_to_list (char **lp, const char *dir)
+{
+ char **expanded_dirs;
+ int i;
+
+ expanded_dirs = expand_path (dir);
+ for (i = 0; expanded_dirs[i]; i++) {
+ add_expanded_dir_to_list (lp, expanded_dirs[i]);
+ free (expanded_dirs[i]);
+ }
+ 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. If so return
+ it, if not return NULL. */
+static char *has_mandir (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)
+ return newpath;
+ free (newpath);
+ }
+
+ newpath = xasprintf ("%s/man", path);
+ if (is_directory (newpath) == 1)
+ return newpath;
+ free (newpath);
+
+ if (subdir) {
+ newpath = xasprintf ("%.*s/share/man",
+ (int) (subdir - path), path);
+ if (is_directory (newpath) == 1)
+ return newpath;
+ free (newpath);
+ }
+
+ newpath = xasprintf ("%s/share/man", path);
+ if (is_directory (newpath) == 1)
+ return newpath;
+ free (newpath);
+
+ return NULL;
+}
+
+static char **add_dir_to_path_list (char **mphead, char **mp, const char *p)
+{
+ int status, i;
+ char *cwd, *d, **expanded_dirs;
+
+ if (mp - mphead > MAXDIRS - 1)
+ gripe_overlong_list ();
+
+ expanded_dirs = expand_path (p);
+ for (i = 0; expanded_dirs[i]; i++) {
+ d = expanded_dirs[i];
+
+ status = is_directory (d);
+
+ if (status < 0)
+ gripe_stat_file (d);
+ else if (status == 0)
+ gripe_not_directory (d);
+ else {
+ /* deal with relative paths */
+ if (*d != '/') {
+ cwd = xgetcwd ();
+ if (!cwd)
+ error (FATAL, errno,
+ _("can't determine current directory"));
+ *mp = appendstr (cwd, "/", d, (void *) 0);
+ } else
+ *mp = xstrdup (d);
+
+ debug ("adding %s to manpathlist\n", *mp);
+ mp++;
+ }
+ free (d);
+ }
+ free (expanded_dirs);
+
+ return mp;
+}
+
+void create_pathlist (const char *manp, char **mp)
+{
+ const char *p, *end;
+ char **mphead = mp;
+
+ /* Expand the manpath into a list for easier handling. */
+
+ for (p = manp;; p = end + 1) {
+ end = strchr (p, ':');
+ if (end) {
+ char *element = xstrndup (p, end - p);
+ mp = add_dir_to_path_list (mphead, mp, element);
+ free (element);
+ } else {
+ mp = add_dir_to_path_list (mphead, mp, p);
+ break;
+ }
+ }
+ *mp = NULL;
+
+ /* Eliminate duplicates due to symlinks. */
+ mp = mphead;
+ while (*mp) {
+ char *target;
+ char **dupcheck;
+ int found_dup = 0;
+
+ /* After resolving all symlinks, is the target also in the
+ * manpath?
+ */
+ target = canonicalize_file_name (*mp);
+ if (!target) {
+ ++mp;
+ continue;
+ }
+ /* Only check up to the current list position, to keep item
+ * order stable across deduplication.
+ */
+ for (dupcheck = mphead; *dupcheck && dupcheck != mp;
+ ++dupcheck) {
+ char *dupcheck_target = canonicalize_file_name
+ (*dupcheck);
+ if (!dupcheck_target)
+ continue;
+ if (!STREQ (target, dupcheck_target)) {
+ free (dupcheck_target);
+ continue;
+ }
+ free (dupcheck_target);
+ debug ("Removing duplicate manpath entry %s (%td) -> "
+ "%s (%td)\n",
+ *mp, mp - mphead,
+ *dupcheck, dupcheck - mphead);
+ free (*mp);
+ for (dupcheck = mp; *(dupcheck + 1); ++dupcheck)
+ *dupcheck = *(dupcheck + 1);
+ *dupcheck = NULL;
+ found_dup = 1;
+ break;
+ }
+ free (target);
+ if (!found_dup)
+ ++mp;
+ }
+
+ if (debug_level) {
+ int first = 1;
+
+ debug ("final search path = ");
+ for (mp = mphead; *mp; ++mp) {
+ if (first) {
+ debug ("%s", *mp);
+ first = 0;
+ } else
+ debug (":%s", *mp);
+ }
+ debug ("\n");
+ }
+}
+
+void free_pathlist (char **mp)
+{
+ while (*mp) {
+ free (*mp);
+ *mp++ = NULL;
+ }
+}
+
+/* Routine to get list of named system and user manpaths (in reverse order). */
+char *get_mandb_manpath (void)
+{
+ char *manpath = NULL;
+ struct list *list;
+
+ for (list = namestore; list; list = list->next)
+ if (list->flag == MANDB_MAP || list->flag == MANDB_MAP_USER)
+ manpath = pathappend (manpath, list->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)
+{
+ struct list *list;
+
+ for (list = namestore; list; list = list->next)
+ if (((cattype & SYSTEM_CAT) && list->flag == MANDB_MAP) ||
+ ((cattype & USER_CAT) && list->flag == MANDB_MAP_USER)) {
+ size_t manlen = strlen (list->key);
+ if (STRNEQ (name, list->key, manlen)) {
+ const char *suffix;
+ char *infix;
+ char *catpath = xstrdup (list->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)
+ return appendstr (catpath,
+ name + manlen,
+ (void *) 0);
+
+ 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);
+ return catpath;
+ }
+ }
+
+ return NULL;
+}
+
+/* Check to see if the supplied man directory is a system-wide mandir.
+ * Obviously, user directories must not be included here.
+ */
+int is_global_mandir (const char *dir)
+{
+ struct list *list;
+
+ for (list = namestore; list; list = list->next)
+ if (list->flag == MANDB_MAP &&
+ STRNEQ (dir, list->key, strlen (list->key)))
+ return 1;
+ return 0;
+}
+
+/* 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;
+}