diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 19:37:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 19:37:10 +0000 |
commit | c9addba5cc770d2d231b34f6739f32c6be8690f1 (patch) | |
tree | c643da154a95a1d163137135050bb47858a1654e /src/mandb.c | |
parent | Initial commit. (diff) | |
download | man-db-c9addba5cc770d2d231b34f6739f32c6be8690f1.tar.xz man-db-c9addba5cc770d2d231b34f6739f32c6be8690f1.zip |
Adding upstream version 2.12.0.upstream/2.12.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/mandb.c')
-rw-r--r-- | src/mandb.c | 1034 |
1 files changed, 1034 insertions, 0 deletions
diff --git a/src/mandb.c b/src/mandb.c new file mode 100644 index 0000000..07ea923 --- /dev/null +++ b/src/mandb.c @@ -0,0 +1,1034 @@ +/* + * 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; + if (!quiet) + fprintf (stderr, + _("Only the '%s' user can create or update " + "system-wide databases; acting as if the " + "--user-db option was used.\n"), + man_owner->pw_name); + } +#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); +} |