/* * 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 #include #include #include #include #include #include #include /* for chmod() */ #include #include #include #include #ifdef MAN_OWNER # include #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); }