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