Description: Don't use ruby objects to accommodate C data
 Fix segfaults, due to incorrect memory access and the use of ruby level functions,
 in case the ruby objects were not cleanly (explicitily) closed.

Author: David Suárez <david.sephirot@gmail.com>
Last-Update: 2014-03-12

--- a/src/bdb.h
+++ b/src/bdb.h
@@ -159,10 +159,21 @@ struct ary_st {
     VALUE *ptr;
 };
 
+typedef enum BDB_OBJ_TYPE {BDB_OBJ_UNKNOWN, BDB_OBJ_DB, /*BDB_OBJ_ENV, BDB_OBJ_LOG, */ BDB_OBJ_LOCK, BDB_OBJ_TX};
+
+struct bdb_objassoc {
+   VALUE obj;
+   void* obj_ptr;
+   enum BDB_OBJ_TYPE obj_type;
+};
+
+struct bdb_objassoc* create_assoc(VALUE obj, void* obj_ptr, enum BDB_OBJ_TYPE obj_type);
+
 typedef struct  {
     int options;
     VALUE marshal;
     struct ary_st db_ary;
+    struct ary_st db_objassoc_ary;
     VALUE home;
     DB_ENV *envp;
 #if HAVE_DB_LOG_REGISTER_4 || HAVE_ST_DB_ENV_LG_INFO
@@ -214,7 +225,9 @@ typedef struct {
     int status, options;
     VALUE marshal, mutex;
     struct ary_st db_ary;
+    struct ary_st db_objassoc_ary;
     struct ary_st db_assoc;
+    struct ary_st db_assoc_objassoc_ary;
     VALUE env;
     DB_TXN *txnid;
     DB_TXN *parent;
@@ -238,6 +251,7 @@ typedef struct {
     VALUE marshal;
     DBTYPE type;
     VALUE env, orig, secondary, txn;
+    bdb_ENV* env_ptr;
     VALUE filename, database;
     VALUE bt_compare, bt_prefix, h_hash;
 #if HAVE_CONST_DB_DUPSORT
@@ -295,6 +309,7 @@ typedef struct {
 typedef struct {
     unsigned int lock;
     VALUE env, self;
+    bdb_ENV *env_ptr;
 } bdb_LOCKID;
 
 typedef struct {
@@ -573,6 +588,12 @@ extern VALUE bdb_ary_delete _((struct ar
 extern void bdb_ary_mark _((struct ary_st *));
 extern VALUE bdb_respond_to _((VALUE, ID));
 
+/* for internal close */
+extern VALUE bdb_internal_close(bdb_DB *dbst, bdb_ENV *envst);
+extern VALUE txn_free(bdb_TXN *txnst);
+extern VALUE bdb_ary_delete_assoc(struct ary_st *db_ary, VALUE val);
+extern void lockid_free( bdb_LOCKID *dblockid);
+
 #if ! HAVE_ST_DBC_C_CLOSE && HAVE_ST_DBC_CLOSE
 #define c_close close
 #undef HAVE_ST_DBC_C_CLOSE
--- a/src/common.c
+++ b/src/common.c
@@ -149,6 +149,31 @@ bdb_ary_delete(struct ary_st *db_ary, VA
     return Qfalse;
 }
 
+VALUE
+bdb_ary_delete_assoc(struct ary_st *db_ary, VALUE val)
+{
+    int i, pos;
+
+    if (!db_ary->ptr || db_ary->mark) return Qfalse;
+
+    for (pos = 0; pos < db_ary->len; pos++) {
+
+       struct bdb_objassoc* assoc = db_ary->ptr[pos];
+
+       if (assoc->obj == val) {
+          for (i = pos + 1; i < db_ary->len; i++, pos++) {
+              db_ary->ptr[pos] = db_ary->ptr[i];
+          }
+
+          free(assoc);
+          db_ary->len = pos;
+          return Qtrue;
+       }
+    }
+
+    return Qfalse;
+}
+
 void
 bdb_ary_mark(struct ary_st *db_ary)
 {
@@ -929,8 +954,10 @@ bdb_i_close(bdb_DB *dbst, int flags)
 
 	    Data_Get_Struct(dbst->txn, bdb_TXN, txnst);
 	    opened = bdb_ary_delete(&txnst->db_ary, dbst->ori_val);
+        bdb_ary_delete_assoc(&txnst->db_objassoc_ary, dbst->ori_val);
 	    if (!opened) {
 		opened = bdb_ary_delete(&txnst->db_assoc, dbst->ori_val);
+        bdb_ary_delete_assoc(&txnst->db_assoc_objassoc_ary, dbst->ori_val);
 	    }
 	    if (opened) {
 		if (txnst->options & BDB_TXN_COMMIT) {
@@ -948,6 +975,7 @@ bdb_i_close(bdb_DB *dbst, int flags)
 	    if (dbst->env && RBASIC(dbst->env)->flags) {
 		Data_Get_Struct(dbst->env, bdb_ENV, envst);
 		bdb_ary_delete(&envst->db_ary, dbst->ori_val);
+        bdb_ary_delete_assoc(&envst->db_objassoc_ary, dbst->ori_val);
 	    }
 	    if (!(dbst->options & BDB_NOT_OPEN)) {
 		dbst->dbp->close(dbst->dbp, flags);
@@ -967,10 +995,29 @@ bdb_local_aref()
     return obj;
 }
 
+static void
+bdb_i_internal_close(bdb_DB *dbst, int flags, bdb_ENV *envst)
+{
+    //bdb_ENV *envst;
+
+   if (dbst->dbp) {
+       if (dbst->env && RBASIC(dbst->env)->flags) {
+       //Data_Get_Struct(dbst->env, bdb_ENV, envst);
+       bdb_ary_delete(&envst->db_ary, dbst->ori_val);
+       bdb_ary_delete_assoc(&envst->db_objassoc_ary, dbst->ori_val);
+       }
+       if (!(dbst->options & BDB_NOT_OPEN)) {
+       dbst->dbp->close(dbst->dbp, flags);
+       }
+   }
+   dbst->dbp = NULL;
+}
+
 static VALUE
 i_close(bdb_DB *dbst)
 {
-    bdb_i_close(dbst, 0);
+    //bdb_i_close(dbst, 0);
+    bdb_i_internal_close(dbst, 0, dbst->env_ptr);
     return Qnil;
 }
 
@@ -1098,6 +1145,18 @@ bdb_close(int argc, VALUE *argv, VALUE o
     return Qnil;
 }
 
+VALUE
+bdb_internal_close(bdb_DB *dbst, bdb_ENV *envst)
+{
+    int flags = 0;
+
+    if (dbst->dbp != NULL) {
+        bdb_i_internal_close(dbst, flags, envst);
+    }
+
+    return Qnil;
+}
+
 #if ! HAVE_ST_DB_BTREE_STAT_BT_NKEYS
 
 static long
@@ -1211,6 +1270,17 @@ bdb_recno_length(obj)
     return hash;
 }
 
+struct bdb_objassoc* create_assoc(VALUE obj, void* obj_ptr, enum BDB_OBJ_TYPE obj_type)
+{
+   struct bdb_objassoc *assoc = ALLOC(struct bdb_objassoc);
+
+   assoc->obj = obj;
+   assoc->obj_ptr = obj_ptr;
+   assoc->obj_type = obj_type;
+
+   return assoc;
+}
+
 static VALUE
 bdb_s_new(int argc, VALUE *argv, VALUE obj)
 {
@@ -1236,6 +1306,7 @@ bdb_s_new(int argc, VALUE *argv, VALUE o
 	    Data_Get_Struct(v, bdb_TXN, txnst);
 	    dbst->txn = v;
 	    dbst->env = txnst->env;
+        dbst->env_ptr = envst;
 	    Data_Get_Struct(txnst->env, bdb_ENV, envst);
 	    envp = envst->envp;
 	    dbst->options |= envst->options & BDB_NO_THREAD;
@@ -1247,6 +1318,7 @@ bdb_s_new(int argc, VALUE *argv, VALUE o
 	    }
 	    Data_Get_Struct(v, bdb_ENV, envst);
 	    dbst->env = v;
+        dbst->env_ptr = envst;
 	    envp = envst->envp;
 	    dbst->options |= envst->options & BDB_NO_THREAD;
 	    dbst->marshal = envst->marshal;
@@ -1292,9 +1364,11 @@ bdb_s_new(int argc, VALUE *argv, VALUE o
     rb_obj_call_init(res, argc, argv);
     if (txnst) {
 	bdb_ary_push(&txnst->db_ary, res);
+    bdb_ary_push(&txnst->db_objassoc_ary, (VALUE)create_assoc(res, dbst, BDB_OBJ_DB));
     }
     else if (envst) {
 	bdb_ary_push(&envst->db_ary, res);
+    bdb_ary_push(&envst->db_objassoc_ary, (VALUE)create_assoc(res, dbst, BDB_OBJ_DB));
     }
     return res;
 }
--- a/src/env.c
+++ b/src/env.c
@@ -1080,6 +1080,11 @@ bdb_final(bdb_ENV *envst)
         envst->db_ary.total = envst->db_ary.len = 0;
         envst->db_ary.ptr = 0;
         free(ary);
+
+        envst->db_objassoc_ary.mark = Qfalse;
+        envst->db_objassoc_ary.total = envst->db_objassoc_ary.len = 0;
+        free(envst->db_objassoc_ary.ptr);
+        envst->db_objassoc_ary.ptr = 0;
     }
     if (envst->envp) {
 	if (!(envst->options & BDB_ENV_NOT_OPEN)) {
@@ -1112,7 +1117,75 @@ bdb_final(bdb_ENV *envst)
 static void
 bdb_env_free(bdb_ENV *envst)
 {
-    bdb_final(envst);
+    //bdb_final(envst);
+
+    VALUE *ary;
+    int i;
+
+    ary = envst->db_ary.ptr;
+    if (ary) {
+        envst->db_ary.mark = Qtrue;
+        envst->db_objassoc_ary.mark = Qtrue;
+        for (i = 0; i < envst->db_ary.len; i++) {
+            /*if (rb_respond_to(ary[i], rb_intern("close"))) {
+                bdb_protect_close(ary[i]);
+            }*/
+           //if(i < envst->db_ptr_ary.len)
+
+           struct bdb_objassoc* assoc = envst->db_objassoc_ary.ptr[i];
+
+           switch (assoc->obj_type) {
+              case BDB_OBJ_DB:
+                    bdb_internal_close(assoc->obj_ptr, envst);
+                    break;
+
+              case BDB_OBJ_TX:
+                  txn_free(assoc->obj_ptr);
+                  break;
+
+              case BDB_OBJ_LOCK:
+                  lockid_free(assoc->obj_ptr);
+                  break;
+           }
+
+        }
+        envst->db_ary.mark = Qfalse;
+        envst->db_ary.total = envst->db_ary.len = 0;
+        envst->db_ary.ptr = 0;
+        free(ary);
+
+        envst->db_objassoc_ary.mark = Qfalse;
+        envst->db_objassoc_ary.total = envst->db_objassoc_ary.len = 0;
+        free(envst->db_objassoc_ary.ptr);
+        envst->db_objassoc_ary.ptr = 0;
+    }
+    if (envst->envp) {
+    if (!(envst->options & BDB_ENV_NOT_OPEN)) {
+#if HAVE_ST_DB_ENV_CLOSE
+        envst->envp->close(envst->envp, 0);
+#else
+        db_appexit(envst->envp);
+        free(envst->envp);
+#endif
+    }
+    envst->envp = NULL;
+    }
+#if BDB_NEED_ENV_CURRENT
+    {
+    VALUE obj;
+    bdb_ENV *thst;
+    int status = 0;
+
+    obj = bdb_env_aref();
+    if (!status && !NIL_P(obj)) {
+        Data_Get_Struct(obj, bdb_ENV, thst);
+        if (thst == envst) {
+        rb_thread_local_aset(rb_thread_current(), bdb_id_current_env, Qnil);
+        }
+    }
+    }
+#endif
+
     free(envst);
 }
 
--- a/src/lock.c
+++ b/src/lock.c
@@ -12,21 +12,30 @@ bdb_clean_env(VALUE env, VALUE obj)
     bdb_ENV *envst;
     Data_Get_Struct(env, bdb_ENV, envst);
     bdb_ary_delete(&envst->db_ary, obj);
+    bdb_ary_delete_assoc(&envst->db_objassoc_ary, obj);
 }
-    
-static void 
+
+void
 lockid_free( bdb_LOCKID *dblockid)
 {
 #if HAVE_ST_DB_ENV_LOCK_ID_FREE
     bdb_ENV *envst;
 
-    bdb_clean_env(dblockid->env, dblockid->self);
-    Data_Get_Struct(dblockid->env, bdb_ENV, envst);
-    if (envst->envp) {
-	envst->envp->lock_id_free(envst->envp, dblockid->lock);
+    if (dblockid->env_ptr != NULL) {
+      envst = dblockid->env_ptr;
+
+      //bdb_clean_env(dblockid->env, dblockid->self);
+      bdb_ary_delete(&envst->db_ary, dblockid->self);
+      bdb_ary_delete_assoc(&envst->db_objassoc_ary, dblockid->self);
+
+      //Data_Get_Struct(dblockid->env, bdb_ENV, envst);
+      if (envst->envp) {
+      envst->envp->lock_id_free(envst->envp, dblockid->lock);
+      }
+  #endif
+      free(dblockid);
+      dblockid->env_ptr = NULL;
     }
-#endif
-    free(dblockid);
 }
 
 static VALUE
@@ -52,8 +61,10 @@ bdb_env_lockid(VALUE obj)
     dblockid->lock = idp;
     dblockid->env = obj;
     dblockid->self = a;
+    dblockid->env_ptr = envst;
 #if HAVE_ST_DB_ENV_LOCK_ID
     bdb_ary_push(&envst->db_ary, a);
+    bdb_ary_push(&envst->db_objassoc_ary, (VALUE)create_assoc(a, dblockid, BDB_OBJ_LOCK));
 #endif
     return a;
 }
--- a/src/log.c
+++ b/src/log.c
@@ -387,6 +387,7 @@ bdb_log_cursor(VALUE lsn)
 	GetEnvDB(lsnst->env, envst);
 	bdb_test_error(envst->envp->log_cursor(envst->envp, &lsnst->cursor, 0));
 	bdb_ary_push(&envst->db_ary, lsn);
+    //bdb_ary_push(&envst->db_objassoc_ary, (VALUE)create_assoc(lsn, lsnst, BDB_OBJ_LOG));
     }
     return lsn;
 }
--- a/src/transaction.c
+++ b/src/transaction.c
@@ -50,7 +50,68 @@ clean_ary(bdb_TXN *txnst, VALUE result)
     }
 }
 
-static VALUE 
+static VALUE
+bdb__txn__internal_close(VALUE obj, VALUE commit, VALUE real)
+{
+    bdb_DB *dbst, *dbst1;
+
+    if (!real) {
+   Data_Get_Struct(obj, bdb_DB, dbst);
+   dbst->dbp = NULL;
+    }
+    else {
+   if (commit) {
+       Data_Get_Struct(obj, bdb_DB, dbst);
+       if (dbst->orig) {
+       Data_Get_Struct(dbst->orig, bdb_DB, dbst1);
+       dbst1->len = dbst->len;
+       }
+   }
+// bdb_close(0, 0, obj);
+    }
+    return Qnil;
+}
+
+static void
+clean_ary_internal(bdb_TXN *txnst, VALUE result)
+{
+    VALUE *ary, tmp[3];
+    int i, len;
+
+    tmp[0] = Qnil;
+    tmp[1] = result;
+    tmp[2] = Qtrue;
+    if (txnst->db_ary.ptr) {
+        txnst->db_ary.mark = Qtrue;
+   ary = txnst->db_ary.ptr;
+   len = txnst->db_ary.len;
+
+   for (i = 0; i < len; i++) {
+            tmp[0] = ary[i];
+            bdb__txn__internal_close(txnst->db_objassoc_ary.ptr[i], result, Qtrue); //txn_close_i(tmp);
+   }
+   txnst->db_ary.mark = Qfalse;
+   txnst->db_ary.ptr = 0;
+   txnst->db_ary.total = txnst->db_ary.len = 0;
+   free(ary);
+    }
+    tmp[2] = Qfalse;
+    if (txnst->db_assoc.ptr) {
+        txnst->db_assoc.mark = Qtrue;
+   ary = txnst->db_assoc.ptr;
+   len = txnst->db_assoc.len;
+   for (i = 0; i < len; i++) {
+            tmp[0] = ary[i];
+            bdb__txn__internal_close(txnst->db_objassoc_ary.ptr[i], result, Qtrue); //txn_close_i(tmp);
+   }
+   txnst->db_assoc.mark = Qfalse;
+   txnst->db_assoc.ptr = 0;
+   txnst->db_assoc.total = txnst->db_assoc.len = 0;
+   free(ary);
+   }
+}
+
+VALUE
 txn_free(bdb_TXN *txnst)
 {
     if (txnst->txnid && txnst->parent == NULL) {
@@ -72,7 +133,7 @@ txn_free(bdb_TXN *txnst)
     if (txnst->txn_cxx) 
         free(txnst->txn_cxx);
 #endif
-    clean_ary(txnst, Qfalse);
+    clean_ary_internal(txnst, Qfalse);
     return Qnil;
 }
 
@@ -107,6 +168,7 @@ bdb_txn_assoc(int argc, VALUE *argv, VAL
     for (i = 0; i < argc; i++) {
 	a = rb_funcall(argv[i], rb_intern("__txn_dup__"), 1, obj);
 	bdb_ary_push(&txnst->db_assoc, a);
+    bdb_ary_push(&txnst->db_assoc_objassoc_ary, (VALUE)create_assoc(a, txnst, BDB_OBJ_TX));
         rb_ary_push(ary, a);
     }
     switch (RARRAY_LEN(ary)) {
@@ -386,6 +448,7 @@ bdb_env_rslbl_begin(VALUE origin, int ar
     txnst->options = envst->options & BDB_INIT_LOCK;
     txnst->mutex = opt.mutex;
     bdb_ary_unshift(&envst->db_ary, txnv);
+    bdb_ary_unshift(&envst->db_objassoc_ary, (VALUE)create_assoc(txnv, txnst, BDB_OBJ_TX));
     if (commit) {
 	txnst->options |= BDB_TXN_COMMIT;
     }
