summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorColin Watson <cjwatson@debian.org>2022-01-30 00:01:51 +0000
committerColin Watson <cjwatson@debian.org>2022-01-30 00:01:51 +0000
commit106287fe531ee04ae3f6d0a793084b01659afa16 (patch)
treecabc4c3dc3d951e86887ef59eeab3e11d4b98541 /src
parentbf6d84c6d828db3d94d8218b2266b771ab6e5fa3 (diff)
mandb: Don't modify DB without changing its mtime
In d9ebedad15 (man-db 2.7.0), mandb started relying on the modification times of database files themselves rather than using a special row, in order to make databases reproducible between otherwise-identical installations (subject to predictable behaviour from the underlying database). There was a difficulty with this change. Because purging deleted pages from the database opened and closed the database in a separate phase before the main phase of looking for updated manual pages, and because the main phase relies on comparing against the mtime of the database to find changed files, I had to arrange to restore the original mtime at the end of the purge phase in order to avoid confusing the main phase into being a no-op. However, in some cases (perhaps due to other bugs, but nevertheless), it's possible for the purge phase to find work to do without the main phase ever finding any modified directories, which meant that the net effect of mandb would be to modify its database without changing its mtime. This is bad form, and confused some backup systems into believing that the filesystem had been corrupted, since files having their contents changed without updating their mtime is indeed a likely symptom of filesystem corruption. To avoid this, restructure our database-handling code so that a given mandb run opens any given database at most once. This means that we no longer need to modify mtimes manually, so the usual filesystem rules apply. Fixes Debian bug #1004355 and Ubuntu bug #1411633. * bootstrap.conf (gnulib_modules): Remove futimens. * libdb/db_btree.c (man_btree_close): Rename to ... (man_btree_free): ... this. Check whether wrap->file is set. Free wrap->mtime. (man_btree_new): New function. (man_btree_open): Convert to running on the result of man_btree_new. (man_btree_get_time): Cache return value in wrap->mtime. (man_btree_set_time): Remove. * libdb/db_gdbm.c (man_gdbm_new): New function. (man_gdbm_open_wrapper): Convert to running on the result of man_gdbm_new. (man_gdbm_get_time): Cache return value in wrap->mtime. (man_gdbm_set_time): Remove. (raw_close): Check whether wrap->file is set. (man_gdbm_close): Rename to ... (man_gdbm_free): ... this. * libdb/db_ndbm.c (raw_close): Check whether wrap->file is set. (man_ndbm_close): Rename to ... (man_ndbm_free): ... this. (man_ndbm_new): New function. (man_ndbm_open): Convert to running on the result of man_ndbm_new. (man_ndbm_get_time): Cache return value in wrap->mtime. (man_ndbm_set_time): Remove. * libdb/db_xdbm.c (man_xdbm_close): Rename to ... (man_xdbm_free): ... this. Update all callers. Free dbf->mtime. * libdb/db_xdbm.h (man_xdbm_close): Rename to ... (man_xdbm_free): ... this. * libdb/mydbm.h (man_gdbm_wrapper, man_ndbm_wrapper, man_btree_wrapper): Add mtime. (man_gdbm_new, man_ndbm_new, man_btree_new): Add prototypes. (man_gdbm_open_wrapper, man_ndbm_open, man_btree_open): Update prototypes. (man_gdbm_set_time, man_ndbm_set_time, man_btree_set_time): Remove prototypes. (man_gdbm_close, man_ndbm_close, man_btree_close): Rename to ... (man_gdbm_free, man_ndbm_free, man_btree_free): ... these, respectively. (MYDBM_NEW): New macro. (MYDBM_CTRWOPEN, MYDBM_RWOPEN, MYDBM_RDOPEN): Rename file argument to wrap. (MYDBM_CLOSE): Rename to ... (MYDBM_FREE): ... this. (MYDBM_SET_TIME): Remove macro. * src/catman.c (parse_for_sec): Take a MYDBM_FILE argument rather than a database path. * src/check_mandirs.c (gripe_rwopen_failed, testmandirs, create_db, update_db, purge_missing): Likewise. * src/mandb.c (update_one_file, update_db_wrapper): Likewise. * src/straycats.c (straycats): Likewise. * src/check_mandirs.h (create_db, update_db, purge_missing): Update prototypes. * src/straycats.h (straycats): Likewise. * src/check_mandirs.c (ensure_db_open): New function. (testmandirs): Only open the database if it wasn't already open, and don't close it. (update_db, purge_missing): Likewise. * src/mandb.c (update_one_file): Likewise. * src/straycats.c (straycats): Assert that the database is already open, and don't close it. * src/accessdb.c (main): Update database opening code. * src/catman.c (post_fork): Update database closing code. (parse_for_sec): Move database opening and closing code to ... (main): ... here, and update it. * src/man.c (dbdelete_wrapper): Update database opening and closing code. * src/whatis.c (search): Update database opening and closing code. * src/check_mandirs.c (purge_missing): Don't reorganize the database. * src/mandb.c (mandb): Create a MYDBM_FILE from the database path, and close and free it before returning. Rearrange purging so that it runs on the temporary database copy, which we close and remove if purge_missing finds a consistency problem requiring us to rescan from scratch. Unless purging requires a rescan, all of the purge, main, and stray cats phases now run on the same open database file. Reorganize the database before closing it if the purge phase did any work. (process_manpath): Rename the temporary database into place if either of the purge or stray cats phases did any work, even if the main phase didn't. * src/check_mandirs.c (update_db_time): Remove. (create_db): Don't manually update the database mtime. (update_db, purge_missing): Likewise. * src/tests/Makefile.am (ALL_TESTS): Add mandb-purge-updates-timestamp. (check_PROGRAMS): Add get-mtime. (get_mtime_SOURCES, get_mtime_LDADD): Add. * src/tests/get-mtime.c: New file: test helper to get a file's mtime portably. * src/tests/mandb-purge-updates-timestamp: New file. * .gitignore: Add src/tests/get-mtime. * NEWS: Document this.
Diffstat (limited to 'src')
-rw-r--r--src/accessdb.c8
-rw-r--r--src/catman.c32
-rw-r--r--src/check_mandirs.c130
-rw-r--r--src/check_mandirs.h8
-rw-r--r--src/man.c13
-rw-r--r--src/mandb.c80
-rw-r--r--src/straycats.c16
-rw-r--r--src/straycats.h4
-rw-r--r--src/tests/Makefile.am6
-rw-r--r--src/tests/get-mtime.c74
-rwxr-xr-xsrc/tests/mandb-purge-updates-timestamp67
-rw-r--r--src/whatis.c26
12 files changed, 284 insertions, 180 deletions
diff --git a/src/accessdb.c b/src/accessdb.c
index dcd81ea2..dadca86b 100644
--- a/src/accessdb.c
+++ b/src/accessdb.c
@@ -132,9 +132,9 @@ int main (int argc, char *argv[])
if (argp_parse (&argp, argc, argv, 0, 0, 0))
exit (FAIL);
- dbf = MYDBM_RDOPEN (database);
- if (dbf && dbver_rd (dbf)) {
- MYDBM_CLOSE (dbf);
+ dbf = MYDBM_NEW (database);
+ if (!MYDBM_RDOPEN (dbf) || dbver_rd (dbf)) {
+ MYDBM_FREE (dbf);
dbf = NULL;
}
if (!dbf)
@@ -172,6 +172,6 @@ next:
key = nextkey;
}
- MYDBM_CLOSE (dbf);
+ MYDBM_FREE (dbf);
exit (ret);
}
diff --git a/src/catman.c b/src/catman.c
index 9a396354..4be5f7bf 100644
--- a/src/catman.c
+++ b/src/catman.c
@@ -173,8 +173,7 @@ static gl_list_t manpathlist;
static void post_fork (void)
{
pop_all_cleanups ();
- if (dbf_close_post_fork)
- MYDBM_CLOSE (dbf_close_post_fork);
+ MYDBM_FREE (dbf_close_post_fork);
}
/* Execute man with the appropriate catman args. Always frees cmd. */
@@ -225,26 +224,14 @@ static size_t add_arg (pipecmd *cmd, datum key)
/* find all pages that are in the supplied manpath and section and that are
ultimate source files. */
-static int parse_for_sec (const char *database,
+static int parse_for_sec (MYDBM_FILE dbf,
const char *manpath, const char *section)
{
- MYDBM_FILE dbf;
pipecmd *basecmd, *cmd;
datum key;
size_t arg_size, initial_bit;
int message = 1, first_arg;
- dbf = MYDBM_RDOPEN (database);
- if (!dbf) {
- error (0, errno, _("cannot read database %s"), database);
- return 1;
- }
- if (dbver_rd (dbf)) {
- MYDBM_CLOSE (dbf);
- return 1;
- }
- dbf_close_post_fork = dbf;
-
basecmd = pipecmd_new (MAN);
pipecmd_clearenv (basecmd);
@@ -344,8 +331,6 @@ static int parse_for_sec (const char *database,
key = nextkey;
}
- dbf_close_post_fork = NULL;
- MYDBM_CLOSE (dbf);
if (pipecmd_get_nargs (cmd) > first_arg)
catman (cmd);
else
@@ -407,6 +392,7 @@ int main (int argc, char *argv[])
GL_LIST_FOREACH (manpathlist, mp) {
char *catpath, *database;
+ MYDBM_FILE dbf;
size_t len;
catpath = get_catpath (mp, SYSTEM_CAT | USER_CAT);
@@ -423,6 +409,13 @@ int main (int argc, char *argv[])
database = mkdbname (mp);
catpath = xstrdup (mp);
}
+ dbf = MYDBM_NEW (database);
+ if (!MYDBM_RDOPEN (dbf) || dbver_rd (dbf)) {
+ error (0, errno, _("cannot read database %s"),
+ database);
+ goto next;
+ }
+ dbf_close_post_fork = dbf;
len = strlen (catpath);
@@ -433,12 +426,15 @@ int main (int argc, char *argv[])
continue;
if (check_access (catpath))
continue;
- if (parse_for_sec (database, mp, *sp)) {
+ if (parse_for_sec (dbf, mp, *sp)) {
error (0, 0, _("unable to update %s"), mp);
break;
}
}
+next:
+ dbf_close_post_fork = NULL;
+ MYDBM_FREE (dbf);
free (database);
free (catpath);
}
diff --git a/src/check_mandirs.c b/src/check_mandirs.c
index d604d90b..3e3be20d 100644
--- a/src/check_mandirs.c
+++ b/src/check_mandirs.c
@@ -120,21 +120,32 @@ static inline bool is_eagain (int err)
}
#pragma GCC diagnostic pop
-static void gripe_rwopen_failed (const char *database)
+static void gripe_rwopen_failed (MYDBM_FILE dbf)
{
if (errno == EACCES || errno == EROFS)
- debug ("database %s is read-only\n", database);
+ debug ("database %s is read-only\n", dbf->name);
else if (is_eagain (errno))
- debug ("database %s is locked by another process\n", database);
+ debug ("database %s is locked by another process\n", dbf->name);
else {
#ifdef MAN_DB_UPDATES
if (!quiet)
#endif /* MAN_DB_UPDATES */
error (0, errno, _("can't update index cache %s"),
- database);
+ dbf->name);
}
}
+static bool ensure_db_open (MYDBM_FILE dbf)
+{
+ if (dbf->file)
+ return true;
+ if (!MYDBM_RWOPEN (dbf)) {
+ gripe_rwopen_failed (dbf);
+ return false;
+ }
+ return true;
+}
+
/* 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,
@@ -501,8 +512,7 @@ static void fix_permissions_tree (const char *catdir)
* 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 *database,
- const char *path, const char *catpath,
+static int testmandirs (MYDBM_FILE dbf, const char *path, const char *catpath,
struct timespec last, bool create)
{
DIR *dir;
@@ -530,7 +540,6 @@ static int testmandirs (const char *database,
while( (mandir = readdir (dir)) ) {
struct stat stbuf;
struct timespec mtime;
- MYDBM_FILE dbf;
if (strncmp (mandir->d_name, "man", 3) != 0)
continue;
@@ -563,17 +572,16 @@ static int testmandirs (const char *database,
/* Open the db in CTRW mode to store the $ver$ ID */
- dbf = MYDBM_CTRWOPEN (database);
- if (dbf == NULL) {
+ if (!MYDBM_CTRWOPEN (dbf)) {
if (errno == EACCES || errno == EROFS) {
debug ("database %s is read-only\n",
- database);
+ dbf->name);
closedir (dir);
return 0;
} else {
error (0, errno,
_("can't create index cache %s"),
- database);
+ dbf->name);
closedir (dir);
return -1;
}
@@ -582,11 +590,7 @@ static int testmandirs (const char *database,
dbver_wr (dbf);
created = true;
- } else
- dbf = MYDBM_RWOPEN(database);
-
- if (!dbf) {
- gripe_rwopen_failed (database);
+ } else if (!ensure_db_open (dbf)) {
closedir (dir);
return 0;
}
@@ -603,7 +607,6 @@ static int testmandirs (const char *database,
fprintf (stderr, "\n");
}
add_dir_entries (dbf, path, mandir->d_name);
- MYDBM_CLOSE (dbf);
amount++;
}
closedir (dir);
@@ -611,56 +614,20 @@ static int testmandirs (const char *database,
return amount;
}
-/* update the modification timestamp of `database' */
-static void update_db_time (const char *database)
-{
- 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 (is_eagain (errno))
- /* 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 *database, const char *manpath, const char *catpath)
+int create_db (MYDBM_FILE dbf, const char *manpath, const char *catpath)
{
struct timespec time_zero;
int amount;
- debug ("create_db(%s): %s\n", manpath, database);
+ debug ("create_db(%s): %s\n", manpath, dbf->name);
time_zero.tv_sec = 0;
time_zero.tv_nsec = 0;
- amount = testmandirs (database, manpath, catpath, time_zero, true);
+ amount = testmandirs (dbf, manpath, catpath, time_zero, true);
- if (amount > 0) {
- update_db_time (database);
- if (!quiet)
- fputs (_("done.\n"), stderr);
- }
+ if (amount > 0 && !quiet)
+ fputs (_("done.\n"), stderr);
return amount;
}
@@ -700,33 +667,23 @@ static bool sanity_check_db (MYDBM_FILE dbf)
/* routine to update the db, ensure that it is consistent with the
filesystem */
-int update_db (const char *database, const char *manpath, const char *catpath)
+int update_db (MYDBM_FILE dbf, 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);
+ if (!ensure_db_open (dbf) || !sanity_check_db (dbf)) {
+ debug ("failed to open %s O_RDONLY\n", dbf->name);
return -1;
}
mtime = MYDBM_GET_TIME (dbf);
- MYDBM_CLOSE (dbf);
debug ("update_db(): %ld.%09ld\n",
(long) mtime.tv_sec, (long) mtime.tv_nsec);
- new = testmandirs (database, manpath, catpath, mtime, false);
+ new = testmandirs (dbf, manpath, catpath, mtime, false);
- if (new > 0) {
- update_db_time (database);
- if (!quiet)
- fputs (_("done.\n"), stderr);
- }
+ if (new > 0 && !quiet)
+ fputs (_("done.\n"), stderr);
return new;
}
@@ -972,25 +929,23 @@ static int check_multi_key (const char *name, const char *content)
/* Go through the database and purge references to man pages that no longer
* exist.
*/
-int purge_missing (const char *database,
- const char *manpath, const char *catpath)
+int purge_missing (MYDBM_FILE dbf, const char *manpath, const char *catpath)
{
#ifdef NDBM
char *dirfile;
#endif
struct stat st;
bool db_exists;
- MYDBM_FILE dbf;
datum key;
int count = 0;
struct timespec db_mtime;
#ifdef NDBM
- dirfile = xasprintf ("%s.dir", database);
+ dirfile = xasprintf ("%s.dir", dbf->name);
db_exists = stat (dirfile, &st) == 0;
free (dirfile);
#else
- db_exists = stat (database, &st) == 0;
+ db_exists = stat (dbf->name, &st) == 0;
#endif
if (!db_exists)
/* nothing to purge */
@@ -999,14 +954,8 @@ int purge_missing (const char *database,
if (!quiet)
printf (_("Purging old database entries in %s...\n"), manpath);
- dbf = MYDBM_RWOPEN (database);
- if (!dbf) {
- gripe_rwopen_failed (database);
- return 0;
- }
- if (!sanity_check_db (dbf)) {
- MYDBM_CLOSE (dbf);
- dbf = NULL;
+ if (!ensure_db_open (dbf) || !sanity_check_db (dbf)) {
+ gripe_rwopen_failed (dbf);
return 0;
}
db_mtime = MYDBM_GET_TIME (dbf);
@@ -1102,12 +1051,5 @@ int purge_missing (const char *database,
key = nextkey;
}
- MYDBM_REORG (dbf);
- /* 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;
}
diff --git a/src/check_mandirs.h b/src/check_mandirs.h
index 475229e5..d89ca986 100644
--- a/src/check_mandirs.h
+++ b/src/check_mandirs.h
@@ -22,15 +22,15 @@
#include <stdbool.h>
-#include "db_storage.h"
+#include "mydbm.h"
/* check_mandirs.c */
extern void test_manfile (MYDBM_FILE dbf, const char *file, const char *path);
extern void chown_if_possible (const char *path);
-extern int create_db (const char *database,
+extern int create_db (MYDBM_FILE dbf,
const char *manpath, const char *catpath);
-extern int update_db (const char *database,
+extern int update_db (MYDBM_FILE dbf,
const char *manpath, const char *catpath);
extern void purge_pointers (MYDBM_FILE dbf, const char *name);
-extern int purge_missing (const char *database,
+extern int purge_missing (MYDBM_FILE dbf,
const char *manpath, const char *catpath);
diff --git a/src/man.c b/src/man.c
index 8a4ed1dd..03e0da16 100644
--- a/src/man.c
+++ b/src/man.c
@@ -3213,13 +3213,13 @@ static void dbdelete_wrapper (const char *page, struct mandata *info,
catpath = get_catpath (manpath,
global_manpath ? SYSTEM_CAT : USER_CAT);
database = mkdbname (catpath ? catpath : manpath);
- dbf = MYDBM_RWOPEN (database);
- if (dbf) {
+ dbf = MYDBM_NEW (database);
+ if (MYDBM_RWOPEN (dbf)) {
if (dbdelete (dbf, page, info) == 1)
debug ("%s(%s) not in db!\n", page, info->ext);
- MYDBM_CLOSE (dbf);
}
+ MYDBM_FREE (dbf);
free (database);
free (catpath);
}
@@ -3445,8 +3445,8 @@ static int try_db (const char *manpath, const char *sec, const char *name,
/* If we haven't looked here already, do so now. */
if (!gl_map_search (db_map, manpath, (const void **) &matches)) {
- dbf = MYDBM_RDOPEN (database);
- if (dbf && !dbver_rd (dbf)) {
+ dbf = MYDBM_NEW (database);
+ if (MYDBM_RDOPEN (dbf) && !dbver_rd (dbf)) {
debug ("Succeeded in opening %s O_RDONLY\n", database);
/* if section is set, only return those that match,
@@ -3514,8 +3514,7 @@ static int try_db (const char *manpath, const char *sec, const char *name,
0, name, manpath, NULL, loc);
out:
- if (dbf)
- MYDBM_CLOSE (dbf);
+ MYDBM_FREE (dbf);
free (database);
free (catpath);
return found;
diff --git a/src/mandb.c b/src/mandb.c
index 021d74a9..f29b3c84 100644
--- a/src/mandb.c
+++ b/src/mandb.c
@@ -349,13 +349,10 @@ static void do_chown (struct dbpaths *dbpaths)
#endif /* MAN_OWNER */
/* Update a single file in an existing database. */
-static int update_one_file (const char *database,
+static int update_one_file (MYDBM_FILE dbf,
const char *manpath, const char *filename)
{
- MYDBM_FILE dbf;
-
- dbf = MYDBM_RWOPEN (database);
- if (dbf) {
+ if (dbf->file || MYDBM_RWOPEN (dbf)) {
struct mandata info;
char *manpage;
@@ -370,25 +367,24 @@ static int update_one_file (const char *database,
test_manfile (dbf, filename, manpath);
}
- MYDBM_CLOSE (dbf);
return 1;
}
/* dont actually create any dbs, just do an update */
-static int update_db_wrapper (const char *database,
+static int update_db_wrapper (MYDBM_FILE dbf,
const char *manpath, const char *catpath)
{
int amount;
if (single_filename)
- return update_one_file (database, manpath, single_filename);
+ return update_one_file (dbf, manpath, single_filename);
- amount = update_db (database, manpath, catpath);
+ amount = update_db (dbf, manpath, catpath);
if (amount >= 0)
return amount;
- return create_db (database, manpath, catpath);
+ return create_db (dbf, manpath, catpath);
}
/* remove incomplete databases */
@@ -452,17 +448,13 @@ static int mandb (struct dbpaths *dbpaths,
char *database;
int amount;
char *dbname;
+ MYDBM_FILE dbf;
bool should_create;
+ int purged_here = 0;
dbname = mkdbname (catpath);
database = xasprintf ("%s/%d", catpath, getpid ());
-
- force_rescan = false;
- if (purge)
- purged += purge_missing (dbname, manpath, catpath);
-
- if (!quiet)
- printf (_("Processing manual pages under %s...\n"), manpath);
+ dbf = MYDBM_NEW (database);
if (!STREQ (catpath, manpath)) {
char *cachedir_tag;
@@ -495,7 +487,7 @@ static int mandb (struct dbpaths *dbpaths,
free (cachedir_tag);
}
- should_create = (create || force_rescan || opt_test);
+ should_create = (create || opt_test);
#ifdef NDBM
# ifdef BERKELEY_DB
@@ -533,14 +525,52 @@ static int mandb (struct dbpaths *dbpaths,
check_remove (dbpaths->xtmpfile);
#endif /* NDBM */
+ if (!should_create) {
+ force_rescan = false;
+ if (purge) {
+ purged_here = purge_missing (dbf, manpath, catpath);
+ purged += purged_here;
+ }
+
+ 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);
+#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 */
+ dbf = MYDBM_NEW (database);
+ should_create = true;
+ }
+ }
+
+ if (!quiet)
+ printf (_("Processing manual pages under %s...\n"), manpath);
+
if (should_create)
- amount = create_db (database, manpath, catpath);
+ amount = create_db (dbf, manpath, catpath);
else
- amount = update_db_wrapper (database, manpath, catpath);
+ amount = update_db_wrapper (dbf, manpath, catpath);
if (check_for_strays && amount > 0)
- strays += straycats (database, manpath);
+ strays += straycats (dbf, manpath);
+
+ if (purged_here)
+ MYDBM_REORG (dbf);
+ MYDBM_FREE (dbf);
free (database);
free (dbname);
return amount;
@@ -555,6 +585,8 @@ static int process_manpath (const char *manpath, bool global_manpath,
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);
@@ -589,15 +621,19 @@ static int process_manpath (const char *manpath, bool global_manpath,
push_cleanup (cleanup, dbpaths, 0);
push_cleanup (cleanup_sigsafe, 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)
+ if (!opt_test && (amount || new_purged || new_strays))
finish_up (dbpaths);
#ifdef MAN_OWNER
if (global_manpath)
diff --git a/src/straycats.c b/src/straycats.c
index dff2ec69..1315345b 100644
--- a/src/straycats.c
+++ b/src/straycats.c
@@ -28,6 +28,7 @@
# include "config.h"
#endif /* HAVE_CONFIG_H */
+#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
@@ -328,22 +329,12 @@ static int open_catdir (MYDBM_FILE dbf)
return strays;
}
-int straycats (const char *database, const char *manpath)
+int straycats (MYDBM_FILE dbf, const char *manpath)
{
- MYDBM_FILE dbf;
char *catpath;
int strays;
- dbf = MYDBM_RWOPEN (database);
- if (dbf && dbver_rd (dbf)) {
- MYDBM_CLOSE (dbf);
- dbf = NULL;
- }
- if (!dbf) {
- error (0, errno, _("warning: can't update index cache %s"),
- database);
- return 0;
- }
+ assert (dbf->file);
catpath = get_catpath (manpath, SYSTEM_CAT | USER_CAT);
@@ -370,6 +361,5 @@ int straycats (const char *database, const char *manpath)
free (catpath);
- MYDBM_CLOSE (dbf);
return strays;
}
diff --git a/src/straycats.h b/src/straycats.h
index 77e11b15..cb82f08e 100644
--- a/src/straycats.h
+++ b/src/straycats.h
@@ -21,4 +21,6 @@
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
-extern int straycats (const char *database, const char *manpath);
+#include "mydbm.h"
+
+extern int straycats (MYDBM_FILE dbf, const char *manpath);
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
index 3fdd74d6..096fbaac 100644
--- a/src/tests/Makefile.am
+++ b/src/tests/Makefile.am
@@ -50,6 +50,7 @@ ALL_TESTS = \
mandb-bogus-symlink \
mandb-cachedir-tag \
mandb-empty-page \
+ mandb-purge-updates-timestamp \
mandb-regular-file-symlink-changes \
mandb-symlink-beats-whatis-ref \
mandb-whatis-broken-link-changes \
@@ -64,10 +65,13 @@ AM_CPPFLAGS = \
-I$(top_builddir)/gl/lib \
-I$(top_srcdir)/gl/lib
AM_CFLAGS = $(WARN_CFLAGS)
-check_PROGRAMS = fspause
+check_PROGRAMS = fspause get-mtime
+
fspause_SOURCES = fspause.c
fspause_LDADD = \
$(top_builddir)/gl/lib/libgnu.la \
$(LIB_NANOSLEEP)
+get_mtime_SOURCES = get-mtime.c
+get_mtime_LDADD = $(top_builddir)/gl/lib/libgnu.la
dist_check_SCRIPTS = testlib.sh $(ALL_TESTS)
diff --git a/src/tests/get-mtime.c b/src/tests/get-mtime.c
new file mode 100644
index 00000000..9ab201e5
--- /dev/null
+++ b/src/tests/get-mtime.c
@@ -0,0 +1,74 @@
+/*
+ * get-mtime.c: get a file's modification time
+ *
+ * Copyright (C) 2022 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
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include "argp.h"
+#include "error.h"
+#include "progname.h"
+#include "stat-time.h"
+
+#include "manconfig.h"
+
+char *path;
+
+static const char args_doc[] = "PATH";
+
+static error_t parse_opt (int key, char *arg, struct argp_state *state)
+{
+ switch (key) {
+ case ARGP_KEY_ARG:
+ if (path)
+ argp_usage (state);
+ path = arg;
+ return 0;
+ case ARGP_KEY_NO_ARGS:
+ argp_usage (state);
+ break;
+ }
+ return ARGP_ERR_UNKNOWN;
+}
+
+static struct argp argp = { NULL, parse_opt, args_doc };
+
+int main (int argc, char **argv)
+{
+ struct stat st;
+ struct timespec ts;
+
+ set_program_name (argv[0]);
+
+ if (argp_parse (&argp, argc, argv, 0, 0, 0))
+ exit (FAIL);
+
+ if (stat (path, &st) < 0)
+ error (FATAL, errno, "can't stat %s", path);
+ ts = get_stat_mtime (&st);
+ printf ("%ld.%09ld\n", (long) ts.tv_sec, ts.tv_nsec);
+
+ exit (OK);
+}
diff --git a/src/tests/mandb-purge-updates-timestamp b/src/tests/mandb-purge-updates-timestamp
new file mode 100755
index 00000000..806673cf
--- /dev/null
+++ b/src/tests/mandb-purge-updates-timestamp
@@ -0,0 +1,67 @@
+#! /bin/sh
+
+# If mandb purges missing pages, it updates the database's timestamp,
+# without confusing itself into not scanning for newer pages.
+
+: ${srcdir=.}
+. "$srcdir/testlib.sh"
+
+: ${MANDB=mandb}
+: ${ACCESSDB=accessdb}
+
+init
+fake_config /usr/share/man
+MANPATH="$tmpdir/usr/share/man"
+export MANPATH
+db_ext="$(db_ext)"
+case $DBTYPE in
+ ndbm) full_db_ext=.pag ;;
+ *) full_db_ext="$db_ext" ;;
+esac
+
+write_page test1 1 "$tmpdir/usr/share/man/man1/test1.1.gz" \
+ UTF-8 gz t 'test1 \- test1(1)'
+write_page test2 1 "$tmpdir/usr/share/man/man1/test2.1.gz" \
+ UTF-8 gz t 'test2 \- test2(1)'
+write_page test3 1 "$tmpdir/usr/share/man/man1/test3.1.gz" \
+ UTF-8 gz t 'test3 \- test3(1)'
+run $MANDB -C "$tmpdir/manpath.config" -u -q "$tmpdir/usr/share/man"
+cat >"$tmpdir/1.exp" <<EOF
+test1 -> "- 1 1 MTIME A - - gz test1(1)"
+test2 -> "- 1 1 MTIME A - - gz test2(1)"
+test3 -> "- 1 1 MTIME A - - gz test3(1)"
+EOF
+accessdb_filter "$tmpdir/usr/share/man/index$db_ext" >"$tmpdir/1.out"
+expect_pass 'setup' 'diff -u "$tmpdir/1.exp" "$tmpdir/1.out"'
+mtime1="$(./get-mtime "$tmpdir/usr/share/man/index$full_db_ext")"
+
+./fspause
+rm -f "$tmpdir/usr/share/man/man1/test3.1.gz"
+# Fool mandb into believing that this directory was not modified. It will
+# still run its purge step.
+touch -r "$tmpdir/usr/share/man/index$db_ext" "$tmpdir/usr/share/man/man1"
+run $MANDB -C "$tmpdir/manpath.config" -u -q "$tmpdir/usr/share/man"
+cat >"$tmpdir/2.exp" <<EOF
+test1 -> "- 1 1 MTIME A - - gz test1(1)"
+test2 -> "- 1 1 MTIME A - - gz test2(1)"
+EOF
+accessdb_filter "$tmpdir/usr/share/man/index$db_ext" >"$tmpdir/2.out"
+expect_pass 'remove test3' 'diff -u "$tmpdir/2.exp" "$tmpdir/2.out"'
+mtime2="$(./get-mtime "$tmpdir/usr/share/man/index$full_db_ext")"
+expect_pass 'mtime changed (1)' 'test "$mtime1" != "$mtime2"'
+
+./fspause
+rm -f "$tmpdir/usr/share/man/man1/test2.1.gz"
+write_page test4 1 "$tmpdir/usr/share/man/man1/test4.1.gz" \
+ UTF-8 gz t 'test4 \- test4(1)'
+run $MANDB -C "$tmpdir/manpath.config" -u -q "$tmpdir/usr/share/man"
+cat >"$tmpdir/3.exp" <<EOF
+test1 -> "- 1 1 MTIME A - - gz test1(1)"
+test4 -> "- 1 1 MTIME A - - gz test4(1)"
+EOF
+accessdb_filter "$tmpdir/usr/share/man/index$db_ext" >"$tmpdir/3.out"
+expect_pass 'remove test2, add test4' 'diff -u "$tmpdir/3.exp" "$tmpdir/3.out"'
+mtime3="$(./get-mtime "$tmpdir/usr/share/man/index$full_db_ext")"
+expect_pass 'mtime changed (2)' 'test "$mtime2" != "$mtime3"'
+
+finish
diff --git a/src/whatis.c b/src/whatis.c
index 0dc1391b..81958aed 100644
--- a/src/whatis.c
+++ b/src/whatis.c
@@ -788,32 +788,23 @@ nextpage:
static bool search (const char * const *pages, int num_pages)
{
bool *found = XCALLOC (num_pages, bool);
- char *catpath, *mp;
+ char *mp;
bool any_found;
int i;
GL_LIST_FOREACH (manpathlist, mp) {
- char *database;
+ char *catpath, *database;
MYDBM_FILE dbf;
catpath = get_catpath (mp, SYSTEM_CAT | USER_CAT);
-
- if (catpath) {
- database = mkdbname (catpath);
- free (catpath);
- } else
- database = mkdbname (mp);
+ database = mkdbname (catpath ? catpath : mp);
debug ("path=%s\n", mp);
- dbf = MYDBM_RDOPEN (database);
- if (dbf && dbver_rd (dbf)) {
- MYDBM_CLOSE (dbf);
- dbf = NULL;
- }
- if (!dbf) {
+ dbf = MYDBM_NEW (database);
+ if (!MYDBM_RDOPEN (dbf) || dbver_rd (dbf)) {
use_grep (pages, num_pages, mp, found);
- continue;
+ goto next;
}
if (am_apropos)
@@ -824,8 +815,11 @@ static bool search (const char * const *pages, int num_pages)
else
do_whatis (dbf, pages, num_pages, mp, found);
}
- MYDBM_CLOSE (dbf);
+
+next:
+ MYDBM_FREE (dbf);
free (database);
+ free (catpath);
}
any_found = false;