/* * check_mandirs.c: used to auto-update the database caches * * Copyright (C) 1994, 1995 Graeme W. Wilford. (Wilf.) * Copyright (C) 2001, 2002, 2003, 2004, 2007, 2008, 2009, 2010, 2011 * 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 * * Mon May 2 17:36:33 BST 1994 Wilf. (G.Wilford@ee.surrey.ac.uk) * * CJW: Many changes to whatis parsing. Added database purging. * See ChangeLog for details. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif /* HAVE_CONFIG_H */ #include #include #include #include #include #include #include #include #include #include #include "dirname.h" #include "stat-time.h" #include "timespec.h" #include "xvasprintf.h" #include "gettext.h" #define _(String) gettext (String) #include "manconfig.h" #include "error.h" #include "hashtable.h" #include "orderfiles.h" #include "security.h" #include "xchown.h" #include "mydbm.h" #include "db_storage.h" #include "descriptions.h" #include "filenames.h" #include "globbing.h" #include "manp.h" #include "ult_src.h" #include "check_mandirs.h" int opt_test; /* don't update db */ int pages; int force_rescan = 0; static struct hashtable *whatis_hash = NULL; struct whatis_hashent { char *whatis; struct ult_trace trace; }; static void whatis_hashtable_free (void *defn) { struct whatis_hashent *hashent = defn; free (hashent->whatis); free_ult_trace (&hashent->trace); free (hashent); } static void gripe_multi_extensions (const char *path, const char *sec, const char *name, const char *ext) { if (quiet < 2) error (0, 0, _("warning: %s/man%s/%s.%s*: competing extensions"), path, sec, name, ext); } static void gripe_rwopen_failed (void) { if (errno == EACCES || errno == EROFS) debug ("database %s is read-only\n", database); else if (errno == EAGAIN || errno == EWOULDBLOCK) debug ("database %s is locked by another process\n", database); else { #ifdef MAN_DB_UPDATES if (!quiet) #endif /* MAN_DB_UPDATES */ error (0, errno, _("can't update index cache %s"), database); } } /* Take absolute filename and path (for ult_src) and do sanity checks on * file. Also check that file is non-zero in length and is not already in * the db. If not, find its ult_src() and see if we have the whatis cached, * otherwise cache it in case we trace another manpage back to it. Next, * store it in the db along with any references found in the whatis. */ void test_manfile (MYDBM_FILE dbf, const char *file, const char *path) { char *manpage_base; const char *ult; struct lexgrog lg; char *manpage; struct mandata info, *exists; struct stat buf; size_t len; struct ult_trace ult_trace; struct whatis_hashent *whatis; memset (&lg, 0, sizeof (struct lexgrog)); memset (&info, 0, sizeof (struct mandata)); memset (&ult_trace, 0, sizeof (struct ult_trace)); manpage = filename_info (file, &info, NULL); if (!manpage) return; manpage_base = manpage + strlen (manpage) + 1; len = strlen (manpage) + 1; /* skip over directory name */ len += strlen (manpage + len) + 1; /* skip over base name */ len += strlen (manpage + len); /* skip over section ext */ /* to get mtime info */ (void) lstat (file, &buf); info.mtime = get_stat_mtime (&buf); /* check that our file actually contains some data */ if (buf.st_size == 0) { /* man-db pre 2.3 place holder ? */ free (manpage); return; } /* See if we already have it, before going any further. This will * save both an ult_src() and a find_name(), amongst other wastes of * time. */ exists = dblookup_exact (dbf, manpage_base, info.ext, 1); /* Ensure we really have the actual page. Gzip keeps the mtime the * same when it compresses, so we have to compare compression * extensions as well. */ if (exists) { if (strcmp (exists->comp, info.comp ? info.comp : "-") == 0) { if (timespec_cmp (exists->mtime, info.mtime) == 0 && exists->id < WHATIS_MAN) { free_mandata_struct (exists); free (manpage); return; } } else { char *abs_filename; /* see if the cached file actually exists. It's evident at this point that we have multiple comp extensions */ abs_filename = make_filename (path, NULL, exists, "man"); if (!abs_filename) { if (!opt_test) dbdelete (dbf, manpage_base, exists); } else { gripe_multi_extensions (path, exists->sec, manpage_base, exists->ext); free (abs_filename); free_mandata_struct (exists); free (manpage); return; } } free_mandata_struct (exists); } /* Check if it happens to be a symlink/hardlink to something already * in our cache. This just does some extra checks to avoid scanning * links quite so many times. */ { /* Avoid too much noise in debug output */ int save_debug = debug_level; debug_level = 0; ult = ult_src (file, path, &buf, SOFT_LINK | HARD_LINK, NULL); debug_level = save_debug; } if (!ult) { /* already warned about this, don't do so again */ debug ("test_manfile(): bad link %s\n", file); free (manpage); return; } if (!whatis_hash) whatis_hash = hashtable_create (&whatis_hashtable_free); whatis = hashtable_lookup (whatis_hash, ult, strlen (ult)); if (!whatis) { if (!STRNEQ (ult, file, len)) debug ("\ntest_manfile(): link not in cache:\n" " source = %s\n" " target = %s\n", file, ult); /* Trace the file to its ultimate source, otherwise we'll be * looking for whatis info in files containing only '.so * manx/foo.x', which will give us an unobtainable whatis * for the entry. */ ult = ult_src (file, path, &buf, SO_LINK | SOFT_LINK | HARD_LINK, &ult_trace); } if (!ult) { if (quiet < 2) error (0, 0, _("warning: %s: bad symlink or ROFF `.so' request"), file); free (manpage); return; } pages++; /* pages seen so far */ if (strncmp (ult, file, len) == 0) info.id = ULT_MAN; /* ultimate source file */ else info.id = SO_MAN; /* .so, sym or hard linked file */ /* Ok, here goes: Use a hash tree to store the ult_srcs with * their whatis. Anytime after, check the hash tree, if it's there, * use it. This saves us a find_name() which is a real hog. * * Use the full path in ult as the hash key so we don't have to * clear the hash between calls. */ if (whatis) lg.whatis = whatis->whatis ? xstrdup (whatis->whatis) : NULL; else { /* Cache miss; go and get the whatis info in its raw state. */ char *file_base = base_name (file); lg.type = MANPAGE; drop_effective_privs (); find_name (ult, file_base, &lg, NULL); free (file_base); regain_effective_privs (); whatis = XMALLOC (struct whatis_hashent); whatis->whatis = lg.whatis ? xstrdup (lg.whatis) : NULL; /* We filled out ult_trace above. */ memcpy (&whatis->trace, &ult_trace, sizeof (ult_trace)); hashtable_install (whatis_hash, ult, strlen (ult), whatis); } debug ("\"%s\"\n", lg.whatis); /* split up the raw whatis data and store references */ info.pointer = NULL; /* direct page, so far */ info.filter = lg.filters; if (lg.whatis) { struct page_description *descs = parse_descriptions (manpage_base, lg.whatis); if (descs) { if (!opt_test) store_descriptions (dbf, descs, &info, path, manpage_base, &whatis->trace); free_descriptions (descs); } } else if (quiet < 2) { (void) stat (ult, &buf); if (buf.st_size == 0) error (0, 0, _("warning: %s: ignoring empty file"), ult); else error (0, 0, _("warning: %s: whatis parse for %s(%s) failed"), ult, manpage_base, info.ext); } free (manpage); free (lg.whatis); } static void add_dir_entries (MYDBM_FILE dbf, const char *path, char *infile) { char *manpage; int len; struct dirent *newdir; DIR *dir; char **names; size_t names_len, names_max, i; manpage = xasprintf ("%s/%s/", path, infile); len = strlen (manpage); /* * All filename entries in this dir should either be valid manpages * or . files (such as current, parent dir). */ dir = opendir (infile); if (!dir) { error (0, errno, _("can't search directory %s"), manpage); free (manpage); return; } names_len = 0; names_max = 1024; names = XNMALLOC (names_max, char *); /* strlen(newdir->d_name) could be replaced by newdir->d_reclen */ while ((newdir = readdir (dir)) != NULL) { if (*newdir->d_name == '.' && strlen (newdir->d_name) < (size_t) 3) continue; if (names_len >= names_max) { names_max *= 2; names = xnrealloc (names, names_max, sizeof (char *)); } names[names_len++] = xstrdup (newdir->d_name); } closedir (dir); order_files (infile, names, names_len); for (i = 0; i < names_len; ++i) { manpage = appendstr (manpage, names[i], (void *) 0); test_manfile (dbf, manpage, path); *(manpage + len) = '\0'; free (names[i]); } free (names); free (manpage); } #ifdef MAN_OWNER extern uid_t uid; /* current effective user id */ extern gid_t gid; /* current effective group id */ /* Fix a path's ownership if possible and necessary. */ void chown_if_possible (const char *path) { struct stat st; struct passwd *man_owner = get_man_owner (); if (lstat (path, &st) != 0) return; if ((uid == 0 || (uid == man_owner->pw_uid && st.st_uid == man_owner->pw_uid && gid == man_owner->pw_gid)) && (st.st_uid != man_owner->pw_uid || st.st_gid != man_owner->pw_gid)) { debug ("fixing ownership of %s\n", path); #ifdef HAVE_LCHOWN xlchown (path, man_owner->pw_uid, man_owner->pw_gid); #else xchown (path, man_owner->pw_uid, man_owner->pw_gid); #endif } } #else /* !MAN_OWNER */ void chown_if_possible (const char *path ATTRIBUTE_UNUSED) { } #endif /* MAN_OWNER */ /* create the catman hierarchy if it doesn't exist */ static void mkcatdirs (const char *mandir, const char *catdir) { char *manname, *catname; if (catdir) { int oldmask = umask (022); /* first the base catdir */ if (is_directory (catdir) != 1) { regain_effective_privs (); if (mkdir (catdir, 0755) < 0) { if (!quiet) error (0, 0, _("warning: cannot create catdir %s"), catdir); debug ("warning: cannot create catdir %s\n", catdir); } else debug ("created base catdir %s\n", catdir); chown_if_possible (catdir); drop_effective_privs (); } /* then the hierarchy */ catname = xasprintf ("%s/cat1", catdir); manname = xasprintf ("%s/man1", mandir); if (is_directory (catdir) == 1) { int j; regain_effective_privs (); debug ("creating catdir hierarchy %s ", catdir); for (j = 1; j <= 9; j++) { catname[strlen (catname) - 1] = '0' + j; manname[strlen (manname) - 1] = '0' + j; if ((is_directory (manname) == 1) && (is_directory (catname) != 1)) { if (mkdir (catname, 0755) < 0) { if (!quiet) error (0, 0, _("warning: cannot create catdir %s"), catname); debug ("warning: cannot create catdir %s\n", catname); } else debug (" cat%d", j); chown_if_possible (catname); } } debug ("\n"); drop_effective_privs (); } free (catname); free (manname); umask (oldmask); } } /* We used to install cat directories with the setgid bit set, but this * wasn't very useful and introduces the ability to escalate privileges to * that group: * https://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/ */ static void fix_permissions (const char *dir) { struct stat st; if (stat (dir, &st) == 0) { if ((st.st_mode & S_ISGID) != 0) { int status; debug ("removing setgid bit from %s\n", dir); status = chmod (dir, st.st_mode & ~S_ISGID); if (status) error (0, errno, _("can't chmod %s"), dir); } chown_if_possible (dir); } } static void fix_permissions_tree (const char *catdir) { if (is_directory (catdir) == 1) { char *catname; int i; fix_permissions (catdir); catname = xasprintf ("%s/cat1", catdir); for (i = 1; i <= 9; ++i) { catname[strlen (catname) - 1] = '0' + i; fix_permissions (catname); } free (catname); } } /* * accepts the raw man dir tree eg. "/usr/man" and the time stored in the db * any dirs of the tree that have been modified (ie added to) will then be * scanned for new files, which are then added to the db. */ static int testmandirs (const char *path, const char *catpath, struct timespec last, int create) { DIR *dir; struct dirent *mandir; int amount = 0; int created = 0; debug ("Testing %s for new files\n", path); if (catpath) fix_permissions_tree (catpath); dir = opendir (path); if (!dir) { error (0, errno, _("can't search directory %s"), path); return 0; } if (chdir (path) != 0) { error (0, errno, _("can't change to directory %s"), path); closedir (dir); return 0; } while( (mandir = readdir (dir)) ) { struct stat stbuf; struct timespec mtime; MYDBM_FILE dbf; if (strncmp (mandir->d_name, "man", 3) != 0) continue; debug ("Examining %s\n", mandir->d_name); if (stat (mandir->d_name, &stbuf) != 0) /* stat failed */ continue; if (!S_ISDIR(stbuf.st_mode)) /* not a directory */ continue; mtime = get_stat_mtime (&stbuf); if (last.tv_sec && timespec_cmp (mtime, last) <= 0) { /* scanned already */ debug ("%s modified %ld.%09ld, " "db modified %ld.%09ld\n", mandir->d_name, (long) mtime.tv_sec, (long) mtime.tv_nsec, (long) last.tv_sec, (long) last.tv_nsec); continue; } debug ("\tsubdirectory %s has been 'modified'\n", mandir->d_name); if (create && !created) { /* We seem to have something to do, so create the * database now. */ mkcatdirs (path, catpath); /* Open the db in CTRW mode to store the $ver$ ID */ dbf = MYDBM_CTRWOPEN (database); if (dbf == NULL) { if (errno == EACCES || errno == EROFS) { debug ("database %s is read-only\n", database); closedir (dir); return 0; } else { error (0, errno, _("can't create index cache %s"), database); closedir (dir); return -errno; } } dbver_wr (dbf); created = 1; } else dbf = MYDBM_RWOPEN(database); if (!dbf) { gripe_rwopen_failed (); closedir (dir); return 0; } if (!quiet) { int tty = isatty (STDERR_FILENO); if (tty) fprintf (stderr, "\r"); fprintf (stderr, _("Updating index cache for path " "`%s/%s'. Wait..."), path, mandir->d_name); if (!tty) fprintf (stderr, "\n"); } add_dir_entries (dbf, path, mandir->d_name); MYDBM_CLOSE (dbf); amount++; } closedir (dir); return amount; } /* update the modification timestamp of `database' */ static void update_db_time (void) { MYDBM_FILE dbf; struct timespec now; /* Open the db in RW to update its mtime */ /* we know that this should succeed because we just updated the db! */ dbf = MYDBM_RWOPEN (database); if (dbf == NULL) { if (errno == EAGAIN || errno == EWOULDBLOCK) /* Another mandb process is probably running. With * any luck it will update the mtime ... */ debug ("database %s is locked by another process\n", database); else { #ifdef MAN_DB_UPDATES if (!quiet) #endif /* MAN_DB_UPDATES */ error (0, errno, _("can't update index cache %s"), database); } return; } now.tv_sec = 0; now.tv_nsec = UTIME_NOW; MYDBM_SET_TIME (dbf, now); MYDBM_CLOSE (dbf); } /* routine to prepare/create the db prior to calling testmandirs() */ int create_db (const char *manpath, const char *catpath) { struct timespec time_zero; int amount; debug ("create_db(%s): %s\n", manpath, database); time_zero.tv_sec = 0; time_zero.tv_nsec = 0; amount = testmandirs (manpath, catpath, time_zero, 1); if (amount) { update_db_time (); if (!quiet) fputs (_("done.\n"), stderr); } return amount; } /* Make sure an existing database is essentially sane. */ static int sanity_check_db (MYDBM_FILE dbf) { datum key; if (dbver_rd (dbf)) return 0; key = MYDBM_FIRSTKEY (dbf); while (MYDBM_DPTR (key) != NULL) { datum content, nextkey; content = MYDBM_FETCH (dbf, key); if (!MYDBM_DPTR (content)) { debug ("warning: %s has a key with no content (%s); " "rebuilding\n", database, MYDBM_DPTR (key)); MYDBM_FREE_DPTR (key); return 0; } MYDBM_FREE_DPTR (content); nextkey = MYDBM_NEXTKEY (dbf, key); MYDBM_FREE_DPTR (key); key = nextkey; } return 1; } /* routine to update the db, ensure that it is consistent with the filesystem */ int update_db (const char *manpath, const char *catpath) { MYDBM_FILE dbf; struct timespec mtime; int new; dbf = MYDBM_RDOPEN (database); if (dbf && !sanity_check_db (dbf)) { MYDBM_CLOSE (dbf); dbf = NULL; } if (!dbf) { debug ("failed to open %s O_RDONLY\n", database); return EOF; } mtime = MYDBM_GET_TIME (dbf); MYDBM_CLOSE (dbf); debug ("update_db(): %ld.%09ld\n", (long) mtime.tv_sec, (long) mtime.tv_nsec); new = testmandirs (manpath, catpath, mtime, 0); if (new) { update_db_time (); if (!quiet) fputs (_("done.\n"), stderr); } return new; } /* Purge any entries pointing to name. This currently assumes that pointers * are always shallow, which may not be a good assumption yet; it should be * close, though. */ void purge_pointers (MYDBM_FILE dbf, const char *name) { datum key = MYDBM_FIRSTKEY (dbf); debug ("Purging pointers to vanished page \"%s\"\n", name); while (MYDBM_DPTR (key) != NULL) { datum content, nextkey; struct mandata entry; char *nicekey, *tab; /* Ignore db identifier keys. */ if (*MYDBM_DPTR (key) == '$') goto pointers_next; content = MYDBM_FETCH (dbf, key); if (!MYDBM_DPTR (content)) return; /* Get just the name. */ nicekey = xstrdup (MYDBM_DPTR (key)); tab = strchr (nicekey, '\t'); if (tab) *tab = '\0'; if (*MYDBM_DPTR (content) == '\t') goto pointers_contentnext; split_content (MYDBM_DPTR (content), &entry); if (entry.id != SO_MAN && entry.id != WHATIS_MAN) goto pointers_contentnext; if (STREQ (entry.pointer, name)) { if (!opt_test) dbdelete (dbf, nicekey, &entry); else debug ("%s(%s): pointer vanished, " "would delete\n", nicekey, entry.ext); } pointers_contentnext: free (nicekey); MYDBM_FREE_DPTR (content); pointers_next: nextkey = MYDBM_NEXTKEY (dbf, key); MYDBM_FREE_DPTR (key); key = nextkey; } } /* Count the number of exact extension matches returned from look_for_file() * (which may return inexact extension matches in some cases). It may turn * out that this is better handled in look_for_file() itself. */ static int count_glob_matches (const char *name, const char *ext, char **source, struct timespec db_mtime) { char **walk; int count = 0; for (walk = source; walk && *walk; ++walk) { struct mandata info; struct stat statbuf; char *buf; memset (&info, 0, sizeof (struct mandata)); if (stat (*walk, &statbuf) == -1) { debug ("count_glob_matches: excluding %s " "because stat failed\n", *walk); continue; } if (db_mtime.tv_sec != (time_t) -1 && timespec_cmp (get_stat_mtime (&statbuf), db_mtime) <= 0) { debug ("count_glob_matches: excluding %s, " "no newer than database\n", *walk); continue; } buf = filename_info (*walk, &info, name); if (buf) { if (STREQ (ext, info.ext)) ++count; free (info.name); free (buf); } } return count; } /* Decide whether to purge a reference to a "normal" (ULT_MAN or SO_MAN) * page. */ static int purge_normal (MYDBM_FILE dbf, const char *name, struct mandata *info, char **found) { struct timespec t; /* TODO: On some systems, the cat page extension differs from the * man page extension, so this may be too strict. */ t.tv_sec = -1; t.tv_nsec = -1; if (count_glob_matches (name, info->ext, found, t)) return 0; if (!opt_test) dbdelete (dbf, name, info); else debug ("%s(%s): missing page, would delete\n", name, info->ext); return 1; } /* Decide whether to purge a reference to a WHATIS_MAN or WHATIS_CAT page. */ static int purge_whatis (MYDBM_FILE dbf, const char *path, int cat, const char *name, struct mandata *info, char **found, struct timespec db_mtime) { /* TODO: On some systems, the cat page extension differs from the * man page extension, so this may be too strict. */ if (count_glob_matches (name, info->ext, found, db_mtime)) { /* If the page exists and didn't beforehand, then presumably * we're about to rescan, which will replace the WHATIS_MAN * entry with something better. However, there have been * bugs that created false WHATIS_MAN entries, so force the * rescan just to be sure; since in the absence of a bug we * would rescan anyway, this isn't a problem. */ if (!force_rescan) debug ("%s(%s): whatis replaced by real page; " "forcing a rescan just in case\n", name, info->ext); force_rescan = 1; return 0; } else if (STREQ (info->pointer, "-")) { /* This is broken; a WHATIS_MAN should never have an empty * pointer field. This might have happened due to the first * name in a page being different from what the file name * says; that's fixed now, so delete and force a rescan. */ if (!opt_test) dbdelete (dbf, name, info); else debug ("%s(%s): whatis with empty pointer, " "would delete\n", name, info->ext); if (!force_rescan) debug ("%s(%s): whatis had empty pointer; " "forcing a rescan just in case\n", name, info->ext); force_rescan = 1; return 1; } else { /* Does the real page still exist? */ char **real_found; int save_debug = debug_level; struct timespec t; debug_level = 0; real_found = look_for_file (path, info->ext, info->pointer, cat, LFF_MATCHCASE); debug_level = save_debug; t.tv_sec = -1; t.tv_nsec = -1; if (count_glob_matches (info->pointer, info->ext, real_found, t)) return 0; if (!opt_test) dbdelete (dbf, name, info); else debug ("%s(%s): whatis target was deleted, " "would delete\n", name, info->ext); return 1; } } /* Check that multi keys are correctly constructed. */ static int check_multi_key (const char *name, const char *content) { const char *walk, *next; if (!*content) return 0; for (walk = content; walk && *walk; walk = next) { /* The name in the multi key should only differ from the * name of the key itself in its case, if at all. */ int valid = 1; ++walk; /* skip over initial tab */ next = strchr (walk, '\t'); if (next) { if (strncasecmp (name, walk, next - walk)) valid = 0; } else { if (strcasecmp (name, walk)) valid = 0; } if (!valid) { debug ("%s: broken multi key \"%s\", " "forcing a rescan\n", name, content); force_rescan = 1; return 1; } /* If the name was valid, skip over the extension and * continue the scan. */ walk = next; next = walk ? strchr (walk + 1, '\t') : NULL; } return 0; } /* Go through the database and purge references to man pages that no longer * exist. */ int purge_missing (const char *manpath, const char *catpath, int will_run_mandb) { #ifdef NDBM char *dirfile; #endif struct stat st; int db_exists; MYDBM_FILE dbf; datum key; int count = 0; struct timespec db_mtime; #ifdef NDBM dirfile = xasprintf ("%s.dir", database); db_exists = stat (dirfile, &st) == 0; free (dirfile); #else db_exists = stat (database, &st) == 0; #endif if (!db_exists) /* nothing to purge */ return 0; if (!quiet) printf (_("Purging old database entries in %s...\n"), manpath); dbf = MYDBM_RWOPEN (database); if (!dbf) { gripe_rwopen_failed (); return 0; } if (!sanity_check_db (dbf)) { MYDBM_CLOSE (dbf); dbf = NULL; return 0; } db_mtime = MYDBM_GET_TIME (dbf); key = MYDBM_FIRSTKEY (dbf); while (MYDBM_DPTR (key) != NULL) { datum content, nextkey; struct mandata entry; char *nicekey, *tab; int save_debug; char **found; /* Ignore db identifier keys. */ if (*MYDBM_DPTR (key) == '$') { nextkey = MYDBM_NEXTKEY (dbf, key); MYDBM_FREE_DPTR (key); key = nextkey; continue; } content = MYDBM_FETCH (dbf, key); if (!MYDBM_DPTR (content)) { nextkey = MYDBM_NEXTKEY (dbf, key); MYDBM_FREE_DPTR (key); key = nextkey; continue; } /* Get just the name. */ nicekey = xstrdup (MYDBM_DPTR (key)); tab = strchr (nicekey, '\t'); if (tab) *tab = '\0'; /* Deal with multi keys. */ if (*MYDBM_DPTR (content) == '\t') { if (check_multi_key (nicekey, MYDBM_DPTR (content))) MYDBM_DELETE (dbf, key); free (nicekey); MYDBM_FREE_DPTR (content); nextkey = MYDBM_NEXTKEY (dbf, key); MYDBM_FREE_DPTR (key); key = nextkey; continue; } split_content (MYDBM_DPTR (content), &entry); save_debug = debug_level; debug_level = 0; /* look_for_file() is quite noisy */ if (entry.id <= WHATIS_MAN) found = look_for_file (manpath, entry.ext, entry.name ? entry.name : nicekey, 0, LFF_MATCHCASE); else found = look_for_file (catpath, entry.ext, entry.name ? entry.name : nicekey, 1, LFF_MATCHCASE); debug_level = save_debug; /* Now actually decide whether to purge, depending on the * type of entry. */ if (entry.id == ULT_MAN || entry.id == SO_MAN || entry.id == STRAY_CAT) count += purge_normal (dbf, nicekey, &entry, found); else if (entry.id == WHATIS_MAN) count += purge_whatis (dbf, manpath, 0, nicekey, &entry, found, db_mtime); else /* entry.id == WHATIS_CAT */ count += purge_whatis (dbf, catpath, 1, nicekey, &entry, found, db_mtime); free (nicekey); free_mandata_elements (&entry); nextkey = MYDBM_NEXTKEY (dbf, key); MYDBM_FREE_DPTR (key); key = nextkey; } MYDBM_REORG (dbf); if (will_run_mandb) /* Reset mtime to avoid confusing mandb into not running. * TODO: It would be better to avoid this by only opening * the database once between here and mandb. */ MYDBM_SET_TIME (dbf, db_mtime); MYDBM_CLOSE (dbf); return count; }