summaryrefslogtreecommitdiffstats
path: root/src/mandb.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mandb.c')
-rw-r--r--src/mandb.c1027
1 files changed, 1027 insertions, 0 deletions
diff --git a/src/mandb.c b/src/mandb.c
new file mode 100644
index 0000000..e62085e
--- /dev/null
+++ b/src/mandb.c
@@ -0,0 +1,1027 @@
+/*
+ * mandb.c: used to create and initialise global man database.
+ *
+ * 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
+ *
+ * Tue Apr 26 12:56:44 BST 1994 Wilf. (G.Wilford@ee.surrey.ac.uk)
+ *
+ * CJW: Security fixes. Make --test work. Purge old database entries.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h> /* for chmod() */
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+
+#ifdef MAN_OWNER
+# include <pwd.h>
+#endif /* MAN_OWNER */
+
+#include "argp.h"
+#include "dirname.h"
+#include "error.h"
+#include "gl_hash_map.h"
+#include "gl_list.h"
+#include "gl_xmap.h"
+#include "progname.h"
+#include "stat-time.h"
+#include "timespec.h"
+#include "utimens.h"
+#include "xalloc.h"
+#include "xgetcwd.h"
+#include "xvasprintf.h"
+
+#include "gettext.h"
+#define _(String) gettext (String)
+#define N_(String) gettext_noop (String)
+
+#include "manconfig.h"
+
+#include "cleanup.h"
+#include "debug.h"
+#include "filenames.h"
+#include "glcontainers.h"
+#include "pipeline.h"
+#include "sandbox.h"
+#include "security.h"
+#include "util.h"
+
+#include "db_storage.h"
+#include "mydbm.h"
+
+#include "check_mandirs.h"
+#include "manp.h"
+#include "straycats.h"
+
+int quiet = 1;
+extern bool opt_test; /* don't update db */
+char *manp;
+extern char *extension; /* for globbing.c */
+extern bool force_rescan; /* for check_mandirs.c */
+static char *single_filename = NULL;
+extern char *user_config_file; /* for manp.c */
+#ifdef MAN_OWNER
+struct passwd *man_owner;
+#endif
+man_sandbox *sandbox;
+
+static int purged = 0;
+static int strays = 0;
+
+static bool check_for_strays = true;
+static bool purge = true;
+static bool user;
+static bool create;
+static const char *arg_manp;
+
+struct tried_catdirs_entry {
+ char *manpath;
+ bool seen;
+};
+
+const char *argp_program_version = "mandb " PACKAGE_VERSION;
+const char *argp_program_bug_address = PACKAGE_BUGREPORT;
+error_t argp_err_exit_status = FAIL;
+
+static const char args_doc[] = N_("[MANPATH]");
+
+static struct argp_option options[] = {
+ OPT ("debug", 'd', 0, N_("emit debugging messages")),
+ OPT ("quiet", 'q', 0, N_("work quietly, except for 'bogus' warning")),
+ OPT ("no-straycats", 's', 0,
+ N_("don't look for or add stray cats to the dbs")),
+ OPT ("no-purge", 'p', 0,
+ N_("don't purge obsolete entries from the dbs")),
+ OPT ("user-db", 'u', 0, N_("produce user databases only")),
+ OPT ("create", 'c', 0,
+ N_("create dbs from scratch, rather than updating")),
+ OPT ("test", 't', 0, N_("check manual pages for correctness")),
+ OPT ("filename", 'f', N_("FILENAME"),
+ N_("update just the entry for this filename")),
+ OPT ("config-file", 'C', N_("FILE"),
+ N_("use this user configuration file")),
+ OPT_HELP_COMPAT,
+ { 0 }
+};
+
+static error_t parse_opt (int key, char *arg, struct argp_state *state)
+{
+ static int quiet_temp = 0;
+
+ switch (key) {
+ case 'd':
+ debug_level = true;
+ return 0;
+ case 'q':
+ ++quiet_temp;
+ return 0;
+ case 's':
+ check_for_strays = false;
+ return 0;
+ case 'p':
+ purge = false;
+ return 0;
+ case 'u':
+ user = true;
+ return 0;
+ case 'c':
+ create = true;
+ purge = false;
+ return 0;
+ case 't':
+ opt_test = true;
+ return 0;
+ case 'f':
+ single_filename = arg;
+ create = false;
+ purge = false;
+ check_for_strays = false;
+ return 0;
+ case 'C':
+ user_config_file = arg;
+ return 0;
+ case 'h':
+ argp_state_help (state, state->out_stream,
+ ARGP_HELP_STD_HELP);
+ break;
+ case ARGP_KEY_ARG:
+ if (arg_manp)
+ argp_usage (state);
+ arg_manp = arg;
+ return 0;
+ case ARGP_KEY_SUCCESS:
+ if (opt_test && !debug_level)
+ quiet = 1;
+ else if (quiet_temp == 1)
+ quiet = 2;
+ else
+ quiet = quiet_temp;
+ return 0;
+ }
+ return ARGP_ERR_UNKNOWN;
+}
+
+static struct argp argp = { options, parse_opt, args_doc };
+
+struct dbpaths {
+#ifdef NDBM
+# ifdef BERKELEY_DB
+ char *dbfile;
+ char *tmpdbfile;
+# else /* !BERKELEY_DB NDBM */
+ char *dirfile;
+ char *pagfile;
+ char *tmpdirfile;
+ char *tmppagfile;
+# endif /* BERKELEY_DB */
+#else /* !NDBM */
+ char *xfile;
+ char *xtmpfile;
+#endif /* NDBM */
+};
+
+#ifdef MAN_OWNER
+extern uid_t ruid;
+extern uid_t euid;
+#endif /* MAN_OWNER */
+
+static gl_list_t manpathlist;
+
+extern int pages;
+
+/* remove() with error checking */
+static void check_remove (const char *path)
+{
+ if (remove (path) == -1 && errno != ENOENT)
+ error (0, errno, _("can't remove %s"), path);
+}
+
+/* rename() with error checking */
+static void check_rename (const char *from, const char *to)
+{
+ if (rename (from, to) == -1 && errno != ENOENT) {
+ error (0, errno, _("can't rename %s to %s"), from, to);
+ check_remove (from);
+ }
+}
+
+/* chmod() with error checking */
+static void check_chmod (const char *path, mode_t mode)
+{
+ if (chmod (path, mode) == -1) {
+ error (0, errno, _("can't chmod %s"), path);
+ check_remove (path);
+ }
+}
+
+/* CPhipps 2000/02/24 - Copy a file. */
+static int xcopy (const char *from, const char *to)
+{
+ FILE *ifp, *ofp;
+ struct stat st;
+ struct timespec times[2];
+ static const size_t buf_size = 32 * 1024;
+ char *buf;
+ int ret = 0;
+
+ ifp = fopen (from, "r");
+ if (!ifp) {
+ ret = -errno;
+ if (errno == ENOENT)
+ return 0;
+ error (0, errno, "fopen %s", from);
+ return ret;
+ }
+
+ if (fstat (fileno (ifp), &st) >= 0) {
+ times[0] = get_stat_atime (&st);
+ times[1] = get_stat_mtime (&st);
+ } else {
+ times[0].tv_sec = 0;
+ times[0].tv_nsec = UTIME_OMIT;
+ times[1].tv_sec = 0;
+ times[1].tv_nsec = UTIME_OMIT;
+ }
+
+ ofp = fopen (to, "w");
+ if (!ofp) {
+ ret = -errno;
+ error (0, errno, "fopen %s", to);
+ fclose (ifp);
+ return ret;
+ }
+
+ buf = xmalloc (buf_size);
+ while (!feof (ifp) && !ferror (ifp)) {
+ size_t in = fread (buf, 1, buf_size, ifp);
+ if (in > 0) {
+ if (fwrite (buf, 1, in, ofp) == 0 && ferror (ofp)) {
+ ret = -errno;
+ error (0, errno, _("can't write to %s"), to);
+ break;
+ }
+ } else if (ferror (ifp)) {
+ ret = -errno;
+ error (0, errno, _("can't read from %s"), from);
+ break;
+ }
+ }
+ free (buf);
+
+ fclose (ifp);
+ fclose (ofp);
+
+ if (ret < 0)
+ check_remove (to);
+ else {
+ check_chmod (to, DBMODE);
+ utimens (to, times);
+ }
+
+ return ret;
+}
+
+static void dbpaths_init (struct dbpaths *dbpaths,
+ const char *base, const char *tmpbase)
+{
+#ifdef NDBM
+# ifdef BERKELEY_DB
+ dbpaths->dbfile = xasprintf ("%s.db", base);
+ dbpaths->tmpdbfile = xasprintf ("%s.db", tmpbase);
+# else /* !BERKELEY_DB NDBM */
+ dbpaths->dirfile = xasprintf ("%s.dir", base);
+ dbpaths->pagfile = xasprintf ("%s.pag", base);
+ dbpaths->tmpdirfile = xasprintf ("%s.dir", tmpbase);
+ dbpaths->tmppagfile = xasprintf ("%s.pag", tmpbase);
+# endif /* BERKELEY_DB NDBM */
+#else /* !NDBM */
+ dbpaths->xfile = xstrdup (base);
+ dbpaths->xtmpfile = xstrdup (tmpbase);
+#endif /* NDBM */
+}
+
+static int dbpaths_copy_to_tmp (struct dbpaths *dbpaths)
+{
+#ifdef NDBM
+# ifdef BERKELEY_DB
+ return xcopy (dbpaths->dbfile, dbpaths->tmpdbfile);
+# else /* !BERKELEY_DB NDBM */
+ int ret = xcopy (dbpaths->dirfile, dbpaths->tmpdirfile);
+ if (ret < 0)
+ return ret;
+ return xcopy (dbpaths->pagfile, dbpaths->tmppagfile);
+# endif /* BERKELEY_DB NDBM */
+#else /* !NDBM */
+ return xcopy (dbpaths->xfile, dbpaths->xtmpfile);
+#endif /* NDBM */
+}
+
+static void dbpaths_remove_tmp (struct dbpaths *dbpaths)
+{
+#ifdef NDBM
+# ifdef BERKELEY_DB
+ check_remove (dbpaths->tmpdbfile);
+# else /* !BERKELEY_DB NDBM */
+ check_remove (dbpaths->tmpdirfile);
+ check_remove (dbpaths->tmppagfile);
+# endif /* BERKELEY_DB NDBM */
+#else /* !NDBM */
+ check_remove (dbpaths->xtmpfile);
+#endif /* NDBM */
+}
+
+static void dbpaths_rename_from_tmp (struct dbpaths *dbpaths)
+{
+#ifdef NDBM
+# ifdef BERKELEY_DB
+ check_rename (dbpaths->tmpdbfile, dbpaths->dbfile);
+ check_chmod (dbpaths->dbfile, DBMODE);
+ free (dbpaths->tmpdbfile);
+ dbpaths->tmpdbfile = NULL;
+# else /* not BERKELEY_DB */
+ check_rename (dbpaths->tmpdirfile, dbpaths->dirfile);
+ check_chmod (dbpaths->dirfile, DBMODE);
+ check_rename (dbpaths->tmppagfile, dbpaths->pagfile);
+ check_chmod (dbpaths->pagfile, DBMODE);
+ free (dbpaths->tmpdirfile);
+ free (dbpaths->tmppagfile);
+ dbpaths->tmpdirfile = dbpaths->tmppagfile = NULL;
+# endif /* BERKELEY_DB */
+#else /* not NDBM */
+ check_rename (dbpaths->xtmpfile, dbpaths->xfile);
+ check_chmod (dbpaths->xfile, DBMODE);
+ free (dbpaths->xtmpfile);
+ dbpaths->xtmpfile = NULL;
+#endif /* NDBM */
+}
+
+#ifdef MAN_OWNER
+/* Change the owner of global man databases. */
+static void dbpaths_chown_if_possible (struct dbpaths *dbpaths)
+{
+# ifdef NDBM
+# ifdef BERKELEY_DB
+ chown_if_possible (dbpaths->dbfile);
+# else /* not BERKELEY_DB */
+ chown_if_possible (dbpaths->dirfile);
+ chown_if_possible (dbpaths->pagfile);
+# endif /* BERKELEY_DB */
+# else /* not NDBM */
+ chown_if_possible (dbpaths->xfile);
+# endif /* NDBM */
+}
+#endif /* MAN_OWNER */
+
+/* Remove incomplete databases. This is async-signal-safe. */
+static void dbpaths_unlink_tmp (struct dbpaths *dbpaths)
+{
+#ifdef NDBM
+# ifdef BERKELEY_DB
+ if (dbpaths->tmpdbfile)
+ unlink (dbpaths->tmpdbfile);
+# else /* !BERKELEY_DB NDBM */
+ if (dbpaths->tmpdirfile)
+ unlink (dbpaths->tmpdirfile);
+ if (dbpaths->tmppagfile)
+ unlink (dbpaths->tmppagfile);
+# endif /* BERKELEY_DB NDBM */
+#else /* !NDBM */
+ if (dbpaths->xtmpfile)
+ unlink (dbpaths->xtmpfile);
+#endif /* NDBM */
+}
+
+static void dbpaths_free_elements (struct dbpaths *dbpaths)
+{
+#ifdef NDBM
+# ifdef BERKELEY_DB
+ free (dbpaths->dbfile);
+ free (dbpaths->tmpdbfile);
+ dbpaths->dbfile = dbpaths->tmpdbfile = NULL;
+# else /* !BERKELEY_DB NDBM */
+ free (dbpaths->dirfile);
+ free (dbpaths->pagfile);
+ free (dbpaths->tmpdirfile);
+ free (dbpaths->tmppagfile);
+ dbpaths->dirfile = dbpaths->pagfile = NULL;
+ dbpaths->tmpdirfile = dbpaths->tmppagfile = NULL;
+# endif /* BERKELEY_DB NDBM */
+#else /* !NDBM */
+ free (dbpaths->xfile);
+ free (dbpaths->xtmpfile);
+ dbpaths->xfile = dbpaths->xtmpfile = NULL;
+#endif /* NDBM */
+}
+
+/* Reorganize a database by reading in all the items (assuming that the
+ * database layer provides them in sorted order) and writing them back out.
+ * This has the effect of giving the underlying database the best chance to
+ * produce deterministic output files based only on the set of items and not
+ * on their insertion order, although we may not be able to guarantee that
+ * for all database types.
+ */
+static void reorganize (const char *catpath, bool global_manpath MAYBE_UNUSED)
+{
+ char *dbname, *tmpdbname;
+ struct dbpaths *dbpaths;
+ MYDBM_FILE dbf, tmpdbf;
+ datum key;
+
+ dbname = mkdbname (catpath);
+ tmpdbname = xasprintf ("%s/%d", catpath, getpid ());
+ dbpaths = XZALLOC (struct dbpaths);
+ dbpaths_init (dbpaths, dbname, tmpdbname);
+ dbf = MYDBM_NEW (dbname);
+ tmpdbf = MYDBM_NEW (tmpdbname);
+ if (!MYDBM_RDOPEN (dbf) || dbver_rd (dbf)) {
+ debug ("Failed to open %s read-only\n", dbname);
+ goto out;
+ }
+ if (!MYDBM_CTRWOPEN (tmpdbf)) {
+ debug ("Failed to create %s\n", tmpdbname);
+ goto out;
+ }
+
+ key = MYDBM_FIRSTKEY (dbf);
+ while (MYDBM_DPTR (key)) {
+ datum content, nextkey;
+ int insert_status;
+
+ content = MYDBM_FETCH (dbf, key);
+ insert_status = MYDBM_INSERT (tmpdbf, key, content);
+ MYDBM_FREE_DPTR (content);
+ if (insert_status != 0) {
+ MYDBM_FREE_DPTR (key);
+ goto out;
+ }
+ nextkey = MYDBM_NEXTKEY (dbf, key);
+ MYDBM_FREE_DPTR (key);
+ key = nextkey;
+ }
+
+ dbpaths_rename_from_tmp (dbpaths);
+#ifdef MAN_OWNER
+ if (global_manpath)
+ dbpaths_chown_if_possible (dbpaths);
+#endif /* MAN_OWNER */
+
+out:
+ MYDBM_FREE (tmpdbf);
+ MYDBM_FREE (dbf);
+ dbpaths_unlink_tmp (dbpaths);
+ dbpaths_free_elements (dbpaths);
+ free (dbpaths);
+ free (tmpdbname);
+ free (dbname);
+}
+
+/* Update a single file in an existing database. */
+static int update_one_file (MYDBM_FILE dbf,
+ const char *manpath, const char *filename)
+{
+ if (dbf->file || MYDBM_RWOPEN (dbf)) {
+ struct mandata *info;
+
+ info = filename_info (filename, quiet < 2);
+ if (info) {
+ dbdelete (dbf, info->name, info);
+ purge_pointers (dbf, info->name);
+ }
+ free_mandata_struct (info);
+
+ test_manfile (dbf, filename, manpath);
+ }
+
+ return 1;
+}
+
+/* dont actually create any dbs, just do an update */
+static int update_db_wrapper (MYDBM_FILE dbf,
+ const char *manpath, const char *catpath)
+{
+ int amount;
+
+ if (single_filename)
+ return update_one_file (dbf, manpath, single_filename);
+
+ amount = update_db (dbf, manpath, catpath);
+ if (amount >= 0)
+ return amount;
+
+ return create_db (dbf, manpath, catpath);
+}
+
+#define CACHEDIR_TAG \
+ "Signature: 8a477f597d28d172789f06886806bc55\n" \
+ "# This file is a cache directory tag created by man-db.\n" \
+ "# For information about cache directory tags, see:\n" \
+ "#\thttp://www.brynosaurus.com/cachedir/\n"
+
+/* sort out the database names */
+static int mandb (struct dbpaths *dbpaths,
+ const char *catpath, const char *manpath,
+ bool global_manpath)
+{
+ char *database;
+ int amount;
+ char *dbname;
+ MYDBM_FILE dbf;
+ bool should_create;
+
+ dbname = mkdbname (catpath);
+ database = xasprintf ("%s/%d", catpath, getpid ());
+ dbf = MYDBM_NEW (database);
+
+ if (!STREQ (catpath, manpath)) {
+ char *cachedir_tag;
+ int fd;
+ bool cachedir_tag_exists = false;
+
+ cachedir_tag = xasprintf ("%s/CACHEDIR.TAG", catpath);
+ assert (cachedir_tag);
+ fd = open (cachedir_tag, O_RDONLY);
+ if (fd < 0) {
+ FILE *cachedir_tag_file;
+
+ if (errno != ENOENT)
+ check_remove (cachedir_tag);
+ cachedir_tag_file = fopen (cachedir_tag, "w");
+ if (cachedir_tag_file) {
+ cachedir_tag_exists = true;
+ fputs (CACHEDIR_TAG, cachedir_tag_file);
+ fclose (cachedir_tag_file);
+ }
+ } else {
+ cachedir_tag_exists = true;
+ close (fd);
+ }
+ if (cachedir_tag_exists) {
+ if (global_manpath)
+ chown_if_possible (cachedir_tag);
+ check_chmod (cachedir_tag, DBMODE);
+ }
+ free (cachedir_tag);
+ }
+
+ should_create = (create || opt_test);
+
+ dbpaths_init (dbpaths, dbname, database);
+ if (!should_create && dbpaths_copy_to_tmp (dbpaths) < 0)
+ should_create = true;
+ if (should_create)
+ dbpaths_remove_tmp (dbpaths);
+
+ if (!should_create) {
+ force_rescan = false;
+ if (purge)
+ purged += purge_missing (dbf, manpath, catpath);
+
+ if (force_rescan) {
+ /* We have an existing database and hadn't been
+ * going to recreate it, but purge_missing has
+ * discovered some kind of consistency problem and
+ * requested that we do so anyway. Close the
+ * database and remove temporary copies so that we
+ * start from scratch.
+ */
+ MYDBM_FREE (dbf);
+ dbpaths_remove_tmp (dbpaths);
+ dbf = MYDBM_NEW (database);
+ should_create = true;
+ }
+ }
+
+ if (!quiet)
+ printf (_("Processing manual pages under %s...\n"), manpath);
+
+ if (should_create)
+ amount = create_db (dbf, manpath, catpath);
+ else
+ amount = update_db_wrapper (dbf, manpath, catpath);
+
+ if (check_for_strays && dbf->file)
+ strays += straycats (dbf, manpath);
+
+ MYDBM_FREE (dbf);
+ free (database);
+ free (dbname);
+ return amount;
+}
+
+static int process_manpath (const char *manpath, bool global_manpath,
+ gl_map_t tried_catdirs)
+{
+ char *catpath;
+ struct tried_catdirs_entry *tried;
+ struct stat st;
+ bool run_mandb = false;
+ struct dbpaths *dbpaths = NULL;
+ int amount = 0;
+ bool new_purged = false;
+ bool new_strays = false;
+
+ if (global_manpath) { /* system db */
+ catpath = get_catpath (manpath, SYSTEM_CAT);
+ assert (catpath);
+ } else { /* user db */
+ catpath = get_catpath (manpath, USER_CAT);
+ if (!catpath)
+ catpath = xstrdup (manpath);
+ }
+ tried = XMALLOC (struct tried_catdirs_entry);
+ tried->manpath = xstrdup (manpath);
+ tried->seen = false;
+ gl_map_put (tried_catdirs, xstrdup (catpath), tried);
+
+ if (stat (manpath, &st) < 0 || !S_ISDIR (st.st_mode))
+ goto out;
+ tried->seen = true;
+
+ if (single_filename) {
+ /* The file might be in a per-locale subdirectory that we
+ * aren't processing right now.
+ */
+ char *manpath_prefix = xasprintf ("%s/man", manpath);
+ if (STRNEQ (manpath_prefix, single_filename,
+ strlen (manpath_prefix)))
+ run_mandb = true;
+ free (manpath_prefix);
+ } else
+ run_mandb = true;
+
+ dbpaths = XZALLOC (struct dbpaths);
+ push_cleanup ((cleanup_fun) dbpaths_free_elements, dbpaths, 0);
+ push_cleanup ((cleanup_fun) dbpaths_unlink_tmp, dbpaths, 1);
+ if (run_mandb) {
+ int purged_before = purged;
+ int strays_before = strays;
+ int ret = mandb (dbpaths, catpath, manpath, global_manpath);
+ if (ret < 0) {
+ amount = ret;
+ goto out;
+ }
+ amount += ret;
+ new_purged = purged != purged_before;
+ new_strays = strays != strays_before;
+
+ if (!opt_test && (amount || new_purged || new_strays)) {
+ dbpaths_rename_from_tmp (dbpaths);
+#ifdef MAN_OWNER
+ if (global_manpath)
+ dbpaths_chown_if_possible (dbpaths);
+#endif /* MAN_OWNER */
+ reorganize (catpath, global_manpath);
+ }
+ }
+
+out:
+ if (dbpaths) {
+ dbpaths_unlink_tmp (dbpaths);
+ pop_cleanup ((cleanup_fun) dbpaths_unlink_tmp, dbpaths);
+ dbpaths_free_elements (dbpaths);
+ pop_cleanup ((cleanup_fun) dbpaths_free_elements, dbpaths);
+ free (dbpaths);
+ }
+
+ free (catpath);
+
+ return amount;
+}
+
+static bool is_lang_dir (const char *base)
+{
+ return strlen (base) >= 2 &&
+ base[0] >= 'a' && base[0] <= 'z' &&
+ base[1] >= 'a' && base[1] <= 'z' &&
+ (!base[2] || base[2] < 'a' || base[2] > 'z');
+}
+
+static void tried_catdirs_free (const void *value)
+{
+ struct tried_catdirs_entry *tried =
+ (struct tried_catdirs_entry *) value;
+
+ free (tried->manpath);
+ free (tried);
+}
+
+static void purge_catdir (gl_map_t tried_catdirs, const char *path)
+{
+ struct stat st;
+
+ if (stat (path, &st) == 0 && S_ISDIR (st.st_mode) &&
+ !gl_map_get (tried_catdirs, path)) {
+ if (!quiet)
+ printf (_("Removing obsolete cat directory %s...\n"),
+ path);
+ remove_directory (path, true);
+ }
+}
+
+static void purge_catsubdirs (const char *manpath, const char *catpath)
+{
+ DIR *dir;
+ struct dirent *ent;
+ struct stat st;
+
+ dir = opendir (catpath);
+ if (!dir)
+ return;
+ while ((ent = readdir (dir)) != NULL) {
+ char *mandir, *catdir;
+
+ if (!STRNEQ (ent->d_name, "cat", 3))
+ continue;
+
+ mandir = xasprintf ("%s/man%s", manpath, ent->d_name + 3);
+ assert (mandir);
+ catdir = xasprintf ("%s/%s", catpath, ent->d_name);
+ assert (catdir);
+
+ if (stat (mandir, &st) != 0 && errno == ENOENT) {
+ if (!quiet)
+ printf (_("Removing obsolete cat directory "
+ "%s...\n"), catdir);
+ remove_directory (catdir, true);
+ }
+
+ free (catdir);
+ free (mandir);
+ }
+ closedir (dir);
+}
+
+/* Remove catdirs whose corresponding mandirs no longer exist. For safety,
+ * in case people set catdirs to silly locations, we only do this for the
+ * cat* and NLS subdirectories of catdirs, but not for the top-level catdir
+ * itself (which might contain other data, or which might be difficult for
+ * mandb to recreate with the proper permissions).
+ *
+ * We need to be careful here to avoid removing catdirs just because we
+ * happened not to inspect the corresponding mandir this time round. If a
+ * mandir was inspected and turned out not to exist, then its catdir is
+ * clearly fair game for removal of NLS subdirectories. These must match
+ * the usual NLS pattern (two lower-case letters followed by nothing or a
+ * non-letter).
+ */
+static void purge_catdirs (gl_map_t tried_catdirs)
+{
+ const char *path;
+ struct tried_catdirs_entry *tried;
+
+ GL_MAP_FOREACH (tried_catdirs, path, tried) {
+ char *base;
+ DIR *dir;
+ struct dirent *subdirent;
+
+ base = base_name (path);
+ if (is_lang_dir (base)) {
+ /* expect to check this as a subdirectory later */
+ free (base);
+ continue;
+ }
+ free (base);
+
+ purge_catsubdirs (tried->manpath, path);
+
+ dir = opendir (path);
+ if (!dir)
+ continue;
+ while ((subdirent = readdir (dir)) != NULL) {
+ char *subdirpath;
+ const struct tried_catdirs_entry *subtried;
+
+ if (STREQ (subdirent->d_name, ".") ||
+ STREQ (subdirent->d_name, ".."))
+ continue;
+ if (STRNEQ (subdirent->d_name, "cat", 3))
+ continue;
+ if (!is_lang_dir (subdirent->d_name))
+ continue;
+
+ subdirpath = xasprintf ("%s/%s", path,
+ subdirent->d_name);
+
+ subtried = gl_map_get (tried_catdirs, subdirpath);
+ if (subtried && subtried->seen) {
+ debug ("Seen mandir for %s; not deleting\n",
+ subdirpath);
+ /* However, we may still need to purge cat*
+ * subdirectories.
+ */
+ purge_catsubdirs (subtried->manpath,
+ subdirpath);
+ } else
+ purge_catdir (tried_catdirs, subdirpath);
+
+ free (subdirpath);
+ }
+ closedir (dir);
+ }
+}
+
+int main (int argc, char *argv[])
+{
+ char *sys_manp;
+ int amount = 0;
+ char *mp;
+ gl_map_t tried_catdirs;
+ struct sigaction sa;
+
+#ifdef __profile__
+ char *cwd;
+#endif /* __profile__ */
+
+ set_program_name (argv[0]);
+
+ init_debug ();
+ pipeline_install_post_fork (pop_all_cleanups);
+ sandbox = sandbox_init ();
+ init_locale ();
+
+ /* Reset SIGPIPE to its default disposition. Too many broken pieces
+ * of software (Python << 3.2, gnome-session, etc.) spawn child
+ * processes with SIGPIPE ignored, and this produces noise in cron
+ * mail.
+ */
+ memset (&sa, 0, sizeof sa);
+ sa.sa_handler = SIG_DFL;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction (SIGPIPE, &sa, NULL);
+
+ if (argp_parse (&argp, argc, argv, 0, 0, 0))
+ exit (FAIL);
+
+#ifdef __profile__
+ cwd = xgetcwd ();
+ if (!cwd) {
+ cwd = xmalloc (1);
+ cwd[0] = '\0';
+ }
+#endif /* __profile__ */
+
+ /* record who we are and drop effective privs for later use */
+ init_security ();
+
+#ifdef MAN_OWNER
+ man_owner = get_man_owner ();
+ if (!user && euid != 0 && euid != man_owner->pw_uid)
+ user = true;
+#endif /* MAN_OWNER */
+
+ read_config_file (user);
+
+ /* This is required for get_catpath(), regardless */
+ manp = get_manpath (NULL); /* also calls read_config_file() */
+
+ /* pick up the system manpath or use the supplied one */
+ if (arg_manp) {
+ free (manp);
+ manp = xstrdup (arg_manp);
+ } else if (!user) {
+ sys_manp = get_mandb_manpath ();
+ if (sys_manp) {
+ free (manp);
+ manp = sys_manp;
+ } else
+ error (0, 0,
+ _("warning: no MANDB_MAP directives in %s, "
+ "using your manpath"),
+ CONFIG_FILE);
+ }
+
+ /* get the manpath as a list of pointers */
+ manpathlist = create_pathlist (manp);
+
+ /* finished manpath processing, regain privs */
+ regain_effective_privs ();
+
+ tried_catdirs = new_string_map (GL_HASH_MAP, tried_catdirs_free);
+
+ GL_LIST_FOREACH (manpathlist, mp) {
+ bool global_manpath = is_global_mandir (mp);
+ int ret;
+ DIR *dir;
+ struct dirent *subdirent;
+
+ if (global_manpath) { /* system db */
+ if (user)
+ continue;
+ } else { /* user db */
+ drop_effective_privs ();
+ }
+
+ ret = process_manpath (mp, global_manpath, tried_catdirs);
+ if (ret < 0)
+ exit (FATAL);
+ amount += ret;
+
+ dir = opendir (mp);
+ if (!dir) {
+ error (0, errno, _("can't search directory %s"), mp);
+ goto next_manpath;
+ }
+
+ while ((subdirent = readdir (dir)) != NULL) {
+ char *subdirpath;
+
+ /* Look for per-locale subdirectories. */
+ if (STREQ (subdirent->d_name, ".") ||
+ STREQ (subdirent->d_name, ".."))
+ continue;
+ if (STRNEQ (subdirent->d_name, "man", 3))
+ continue;
+
+ subdirpath = xasprintf ("%s/%s", mp,
+ subdirent->d_name);
+ assert (subdirpath);
+ ret = process_manpath (subdirpath, global_manpath,
+ tried_catdirs);
+ if (ret < 0)
+ exit (FATAL);
+ amount += ret;
+ free (subdirpath);
+ }
+
+ closedir (dir);
+
+next_manpath:
+ if (!global_manpath)
+ regain_effective_privs ();
+ }
+
+ purge_catdirs (tried_catdirs);
+ gl_map_free (tried_catdirs);
+
+ if (!quiet) {
+ printf (ngettext ("%d man subdirectory contained newer "
+ "manual pages.\n",
+ "%d man subdirectories contained newer "
+ "manual pages.\n", amount),
+ amount);
+ printf (ngettext ("%d manual page was added.\n",
+ "%d manual pages were added.\n", pages),
+ pages);
+ if (check_for_strays)
+ printf (ngettext ("%d stray cat was added.\n",
+ "%d stray cats were added.\n",
+ strays),
+ strays);
+ if (purge)
+ printf (ngettext ("%d old database entry was "
+ "purged.\n",
+ "%d old database entries were "
+ "purged.\n", purged),
+ purged);
+ }
+
+#ifdef __profile__
+ /* For profiling */
+ if (cwd[0])
+ chdir (cwd);
+#endif /* __profile__ */
+
+ free_pathlist (manpathlist);
+ free (manp);
+ if (create && !amount) {
+ const char *must_create;
+ if (!quiet)
+ fprintf (stderr, _("No databases created."));
+ must_create = getenv ("MAN_MUST_CREATE");
+ if (must_create && STREQ (must_create, "1"))
+ exit (FAIL);
+ }
+ sandbox_free (sandbox);
+ exit (OK);
+}