Blob Blame History Raw
commit e7d5980e2a7b091d973171144de04397204ebcd7
Author: Panu Matilainen <pmatilai@redhat.com>
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;
 }