commit e7d5980e2a7b091d973171144de04397204ebcd7 Author: Panu Matilainen Date: Tue May 28 08:56:22 2013 +0300 Serialize BDB environment open/close (RhBug:924417 etc) - Introduce Yet Another Broken Lock[*] to serialize BDB environment open: otherwise we can end up calling dbenv->failchk() while another process is just joining the environment, leading to transient "Thread died in..." DB_RUNRECOVER errors. Also prevents races on chrooted operations where we remove the entire environment on close. - This should also make it possible to handle at least some cases of real DB_RUNRECOVER errors by just nuking the environment but that's another topic... [*] YABL as this is nowhere near foolproof or sufficient for all the possible variants, but better than not having it... (cherry picked from commit ad874d60e3804f1bcd64f3510e1e2dfbf81456cd) diff --git a/lib/backend/db3.c b/lib/backend/db3.c index de8071b..9d385c6 100644 --- a/lib/backend/db3.c +++ b/lib/backend/db3.c @@ -57,10 +57,42 @@ static uint32_t db_envflags(DB * db) return eflags; } +/* + * Try to acquire db environment open/close serialization lock. + * Return the open, locked fd on success, -1 on failure. + */ +static int serialize_env(const char *dbhome) +{ + char *lock_path = rstrscat(NULL, dbhome, "/.dbenv.lock", NULL); + mode_t oldmask = umask(022); + int fd = open(lock_path, (O_RDWR|O_CREAT), 0644); + umask(oldmask); + + if (fd >= 0) { + int rc; + struct flock info; + memset(&info, 0, sizeof(info)); + info.l_type = F_WRLCK; + info.l_whence = SEEK_SET; + do { + rc = fcntl(fd, F_SETLKW, &info); + } while (rc == -1 && errno == EINTR); + + if (rc == -1) { + close(fd); + fd = -1; + } + } + + free(lock_path); + return fd; +} + static int db_fini(rpmdb rdb, const char * dbhome) { DB_ENV * dbenv = rdb->db_dbenv; int rc; + int lockfd = -1; uint32_t eflags = 0; if (dbenv == NULL) @@ -72,6 +104,9 @@ static int db_fini(rpmdb rdb, const char * dbhome) } (void) dbenv->get_open_flags(dbenv, &eflags); + if (!(eflags & DB_PRIVATE)) + lockfd = serialize_env(dbhome); + rc = dbenv->close(dbenv, 0); rc = dbapi_err(rdb, "dbenv->close", rc, _debug); @@ -89,6 +124,10 @@ static int db_fini(rpmdb rdb, const char * dbhome) rpmlog(RPMLOG_DEBUG, "removed db environment %s\n", dbhome); } + + if (lockfd >= 0) + close(lockfd); + return rc; } @@ -122,6 +161,7 @@ static int db_init(rpmdb rdb, const char * dbhome) DB_ENV *dbenv = NULL; int rc, xx; int retry_open = 2; + int lockfd = -1; struct dbConfig_s * cfg = &rdb->cfg; /* This is our setup, thou shall not have other setups before us */ uint32_t eflags = (DB_CREATE|DB_INIT_MPOOL|DB_INIT_CDB); @@ -176,6 +216,24 @@ static int db_init(rpmdb rdb, const char * dbhome) } /* + * Serialize shared environment open (and clock) via fcntl() lock. + * Otherwise we can end up calling dbenv->failchk() while another + * process is joining the environment, leading to transient + * DB_RUNRECOVER errors. Also prevents races wrt removing the + * environment (eg chrooted operation). Silently fall back to + * private environment on failure to allow non-privileged queries + * to "work", broken as it might be. + */ + if (!(eflags & DB_PRIVATE)) { + lockfd = serialize_env(dbhome); + if (lockfd < 0) { + eflags |= DB_PRIVATE; + retry_open--; + rpmlog(RPMLOG_DEBUG, "serialize failed, using private dbenv\n"); + } + } + + /* * Actually open the environment. Fall back to private environment * if we dont have permission to join/create shared environment or * system doesn't support it.. @@ -208,6 +266,8 @@ static int db_init(rpmdb rdb, const char * dbhome) rdb->db_dbenv = dbenv; rdb->db_opens = 1; + if (lockfd >= 0) + close(lockfd); return 0; errxit: @@ -216,6 +276,8 @@ errxit: xx = dbenv->close(dbenv, 0); xx = dbapi_err(rdb, "dbenv->close", xx, _debug); } + if (lockfd >= 0) + close(lockfd); return rc; }