]> arthur.barton.de Git - netatalk.git/commitdiff
Fix a problem with DB corruption when multiple clients open the same volume
authorjmarcus <jmarcus>
Mon, 10 Dec 2001 03:51:56 +0000 (03:51 +0000)
committerjmarcus <jmarcus>
Mon, 10 Dec 2001 03:51:56 +0000 (03:51 +0000)
at the same time.  Also correct a possible deadlock when deleting files
at the same time another client is copying files to the same volume.

libatalk/cnid/cnid_close.c
libatalk/cnid/cnid_delete.c
libatalk/cnid/cnid_open.c

index 0f899a2a6bf11526cd764eda20a0c0e8a4d66370..4858ff83c8d8e8baa4afd8843919d88703255725 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * $Id: cnid_close.c,v 1.13 2001-11-27 23:38:18 jmarcus Exp $
+ * $Id: cnid_close.c,v 1.14 2001-12-10 03:51:56 jmarcus Exp $
  */
 
 #ifdef HAVE_CONFIG_H
@@ -59,8 +59,10 @@ void cnid_close(void *CNID) {
             if (list != NULL) {
                 for (first = list; *list != NULL; ++list) {
                     if ((rc = remove(*list)) != 0) {
+#ifdef DEBUG
                         syslog(LOG_INFO, "cnid_close: failed to remove %s: %s",
                                *list, strerror(rc));
+#endif
                     }
                 }
                 free(first);
index 583078af4f48df0bb80d1307e17f1166ee98bc57..03ee94d1a239157e6ac7c28801024915d98e638e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * $Id: cnid_delete.c,v 1.10 2001-11-27 23:38:18 jmarcus Exp $
+ * $Id: cnid_delete.c,v 1.11 2001-12-10 03:51:56 jmarcus Exp $
  *
  * Copyright (c) 1999. Adrian Sun (asun@zoology.washington.edu)
  * All Rights Reserved. See COPYRIGHT.
@@ -28,7 +28,7 @@ int cnid_delete(void *CNID, const cnid_t id) {
     CNID_private *db;
     DBT key, data;
     DB_TXN *tid;
-    int rc;
+    int rc, found = 0;
 
     if (!(db = CNID) || !id || (db->flags & CNIDFLAG_DB_RO)) {
         return -1;
@@ -37,6 +37,30 @@ int cnid_delete(void *CNID, const cnid_t id) {
     memset(&key, 0, sizeof(key));
     memset(&data, 0, sizeof(data));
 
+    /* Get from ain CNID database. */
+    key.data = (cnid_t *)&id;
+    key.size = sizeof(id);
+    while (!found) {
+        rc = db->db_cnid->get(db->db_cnid, NULL, &key, &data, 0);
+        switch (rc) {
+        case 0:
+            found = 1;
+            break;
+        case DB_LOCK_DEADLOCK:
+            break;
+        case DB_NOTFOUND:
+#ifdef DEBUG
+            syslog(LOG_INFO, "cnid_delete: CNID %u not in database",
+                   ntohl(id));
+#endif
+            return 0;
+        default:
+            syslog(LOG_ERR, "cnid_delete: Unable to delete entry: %s",
+                   db_strerror(rc));
+            return rc;
+        }
+    }
+
 retry:
     if ((rc = txn_begin(db->dbenv, NULL, &tid, 0)) != 0) {
         syslog(LOG_ERR, "cnid_delete: Failed to begin transaction: %s",
@@ -44,10 +68,10 @@ retry:
         return rc;
     }
 
-    /* Get from ain CNID database. */
+    /* Now delete from the main CNID database. */
     key.data = (cnid_t *)&id;
     key.size = sizeof(id);
-    if ((rc = db->db_cnid->get(db->db_cnid, tid, &key, &data, 0))) {
+    if ((rc = db->db_cnid->del(db->db_cnid, tid, &key, 0))) {
         int ret;
         if ((ret = txn_abort(tid)) != 0) {
             syslog(LOG_ERR, "cnid_delete: txn_abort: %s", db_strerror(ret));
@@ -56,14 +80,8 @@ retry:
         switch (rc) {
         case DB_LOCK_DEADLOCK:
             goto retry;
-        case DB_NOTFOUND:
-            syslog(LOG_INFO, "cnid_delete: CNID %u not in database",
-                   ntohl(id));
-            return 0;
         default:
-            syslog(LOG_ERR, "cnid_delete: Unable to delete entry: %s",
-                   db_strerror(rc));
-            return rc;
+            goto abort_err;
         }
     }
 
@@ -94,7 +112,6 @@ retry:
 
     /* Get data from the did/name database.
      * TODO Also handle did/macname, did/shortname, and did/longname. */
-
     key.data = (char *)data.data + CNID_DEVINO_LEN;
     key.size = data.size - CNID_DEVINO_LEN;
     if ((rc = db->db_didname->del(db->db_didname, tid, &key, 0))) {
@@ -118,23 +135,6 @@ retry:
         }
     }
 
-    /* Now delete from the main CNID database. */
-    key.data = (cnid_t *)&id;
-    key.size = sizeof(id);
-    if ((rc = db->db_cnid->del(db->db_cnid, tid, &key, 0))) {
-        int ret;
-        if ((ret = txn_abort(tid)) != 0) {
-            syslog(LOG_ERR, "cnid_delete: txn_abort: %s", db_strerror(ret));
-            return ret;
-        }
-        switch (rc) {
-        case DB_LOCK_DEADLOCK:
-            goto retry;
-        default:
-            goto abort_err;
-        }
-    }
-
 #ifdef DEBUG
     syslog(LOG_INFO, "cnid_delete: Deleting CNID %u", ntohl(id));
 #endif
index da17aae10942adf649e0f473b23385db3ddb7991..910e6aa19af54c91a2601cb474eabbdb3e1dc96c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * $Id: cnid_open.c,v 1.22 2001-12-07 17:29:06 jmarcus Exp $
+ * $Id: cnid_open.c,v 1.23 2001-12-10 03:51:56 jmarcus Exp $
  *
  * Copyright (c) 1999. Adrian Sun (asun@zoology.washington.edu)
  * All Rights Reserved. See COPYRIGHT.
@@ -71,6 +71,7 @@
 #define DBMACNAME    "macname.db"   /* did/31 mapping */
 #define DBLONGNAME   "longname.db"  /* did/unicode mapping */
 #define DBLOCKFILE   "cnid.lock"
+#define DBRECOVERFILE "cnid.dbrecover"
 
 #define DBHOMELEN    8
 #define DBLEN        10
@@ -93,6 +94,18 @@ DB_INIT_LOG | DB_INIT_TXN | DB_TXN_NOSYNC)
 DB_INIT_LOG | DB_INIT_TXN)*/
 #endif /* DB_VERSION_MINOR */
 
+/* Let's try and use the random deadlock decider if available.  This adds
+ * a bit of entropy to the mix that might be beneficial.  If random isn't
+ * available, we'll decide deadlocks by kicking off the youngest process.
+ * If we can't do that, then let DB3 use its default deadlock detector. */
+#ifdef DB_LOCK_RANDOM
+#define DEAD_LOCK_DETECT DB_LOCK_RANDOM
+#elif defined DB_LOCK_YOUNGEST
+#define DEAD_LOCK_DETECT DB_LOCK_YOUNGEST
+#else /* DB_LOCK_RANDOM */
+#define DEAD_LOCK_DETECT DB_LOCK_DEFAULT
+#endif /* DB_LOCK_RANDOM */
+
 #define MAXITER     0xFFFF /* maximum number of simultaneously open CNID
 * databases. */
 
@@ -174,15 +187,16 @@ static int compare_unicode(const DBT *a, const DBT *b)
 static int have_lock = 0;
 
 void *cnid_open(const char *dir) {
-    struct stat st;
+    struct stat st, rsb;
     struct flock lock;
     char path[MAXPATHLEN + 1];
+       char recover_file[MAXPATHLEN + 1];
     CNID_private *db;
     DBT key, data;
     DB_TXN *tid;
     u_int32_t DBEXTRAS = 0;
     int open_flag, len;
-    int rc;
+    int rc, rfd = -1;
 
     if (!dir) {
         return NULL;
@@ -220,6 +234,25 @@ void *cnid_open(const char *dir) {
        strcat(path, "/");
        len++;
 
+       /* Create a file to represent database recovery.  While this file
+        * exists, the database is being recovered, and all other clients will
+        * sleep until recovery is complete, and this file goes away. */
+       strcpy(recover_file, path);
+       strcat(recover_file, DBRECOVERFILE);
+       if (!have_lock) {
+               if (stat(recover_file, &rsb) < 0) {
+                       if ((rfd = open(recover_file, O_RDWR | O_CREAT, 0666)) > -1) {
+                       DBEXTRAS |= DB_RECOVER;
+                               have_lock = 1;
+                       }
+               }
+               else {
+                       while(stat(recover_file, &rsb) == 0) {
+                               sleep(1);
+                       }
+               }
+       }
+
     /* Search for a byte lock.  This allows us to cleanup the log files
      * at cnid_close() in a clean fashion.
      *
@@ -230,30 +263,18 @@ void *cnid_open(const char *dir) {
         lock.l_start = 0;
         lock.l_len = 1;
         while (fcntl(db->lockfd, F_SETLK, &lock) < 0) {
-            if (++lock.l_start > MAXITER) {
-                syslog(LOG_INFO, "cnid_open: Cannot establish logfile cleanup for database environment %s lock (lock failed)", path);
-                close(db->lockfd);
-                db->lockfd = -1;
-                break;
-            }
-        }
-    }
+               if (++lock.l_start > MAXITER) {
+                       syslog(LOG_ERR, "cnid_open: Cannot establish logfile cleanup for database environment %s lock (lock failed)", path);
+                       close(db->lockfd);
+                       db->lockfd = -1;
+                       break;
+               }
+       }
+       }
     else {
-        syslog(LOG_INFO, "cnid_open: Cannot establish logfile cleanup lock for database environment %s (open() failed)", path);
+        syslog(LOG_ERR, "cnid_open: Cannot establish logfile cleanup lock for database environment %s (open() failed)", path);
     }
 
-    if (!have_lock && db->lockfd > -1 && lock.l_start == 0) {
-        /* We test to see if we have exclusive database access.  If we do, we
-         * will open the database with the DB_RECOVER flag.
-         */
-#ifdef DEBUG
-        syslog(LOG_INFO, "cnid_open: Opening database environment %s with DB_RECOVER flag", path);
-#endif
-        DBEXTRAS |= DB_RECOVER;
-        have_lock = 1;
-    }
-
-
     path[len + DBHOMELEN] = '\0';
     open_flag = DB_CREATE;
 
@@ -266,7 +287,7 @@ void *cnid_open(const char *dir) {
     }
 
     /* Setup internal deadlock detection. */
-    if ((rc = db->dbenv->set_lk_detect(db->dbenv, DB_LOCK_DEFAULT)) != 0) {
+    if ((rc = db->dbenv->set_lk_detect(db->dbenv, DEAD_LOCK_DETECT)) != 0) {
         syslog(LOG_ERR, "cnid_open: set_lk_detect: %s", db_strerror(rc));
         goto fail_lock;
     }
@@ -304,6 +325,14 @@ void *cnid_open(const char *dir) {
         syslog(LOG_INFO, "cnid_open: Obtained read-only database environment %s", path);
     }
 
+       /* If we have the recovery lock, close the file, remove it, so other
+        * clients can proceed opening the DB environment. */
+       if (rfd > -1) {
+               close(rfd);
+               (void)remove(recover_file);
+               rfd = -1;
+       }
+
     /* did/name reverse mapping.  We use a BTree for this one. */
     if ((rc = db_create(&db->db_didname, db->dbenv, 0)) != 0) {
         syslog(LOG_ERR, "cnid_open: Failed to create did/name database: %s",