always use prepare_commit in ldb transaction commits if possible
authorAndrew Tridgell <tridge@samba.org>
Thu, 3 Sep 2009 08:29:58 +0000 (18:29 +1000)
committerAndrew Tridgell <tridge@samba.org>
Thu, 3 Sep 2009 08:36:09 +0000 (18:36 +1000)
The reason we need this is to make multi-tdb transactions safe, with
the partition module. The linked_attributes and repl_meta_data modules
now do extra processing when the transaction ends, and that processing
can fail. When it fails we need to cancel the transaction, which we
can only do if the hook is on the prepare commit instead of the end
transaction call. Otherwise the partition module cannot ensure that no
commit has been done on another partition.

source4/lib/ldb/common/ldb.c
source4/lib/ldb/common/ldb_modules.c
source4/lib/ldb/include/ldb_module.h

index 38cc0c880b224f0fd38229ee8784935e6ba1b7eb..dc3032643cc062224d1121f4f945589c72222da6 100644 (file)
@@ -282,15 +282,20 @@ void ldb_reset_err_string(struct ldb_context *ldb)
        }
 }
 
-#define FIRST_OP(ldb, op) do { \
+#define FIRST_OP_NOERR(ldb, op) do { \
        module = ldb->modules;                                  \
        while (module && module->ops->op == NULL) module = module->next; \
-       if (module == NULL) {                                           \
+} while (0)
+
+#define FIRST_OP(ldb, op) do { \
+       FIRST_OP_NOERR(ldb, op); \
+       if (module == NULL) {                                   \
                ldb_asprintf_errstring(ldb, "unable to find module or backend to handle operation: " #op); \
                return LDB_ERR_OPERATIONS_ERROR;                        \
        } \
 } while (0)
 
+
 /*
   start a transaction
 */
@@ -337,6 +342,7 @@ int ldb_transaction_commit(struct ldb_context *ldb)
        struct ldb_module *module;
        int status;
 
+
        ldb->transaction_active--;
 
        ldb_debug(ldb, LDB_DEBUG_TRACE,
@@ -355,10 +361,30 @@ int ldb_transaction_commit(struct ldb_context *ldb)
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       FIRST_OP(ldb, end_transaction);
+       /* call prepare transaction if available */
+       FIRST_OP_NOERR(ldb, prepare_commit);
+       if (module != NULL) {
+               status = module->ops->prepare_commit(module);
+               if (status != LDB_SUCCESS) {
+                       /* if a module fails the prepare then we need
+                          to call the end transaction for everyone */
+                       /* preserve err string */
+                       FIRST_OP(ldb, end_transaction);
+                       module->ops->end_transaction(module);
+                       if (ldb->err_string == NULL) {
+                               /* no error string was setup by the backend */
+                               ldb_asprintf_errstring(ldb,
+                                                      "ldb transaction prepare commit: %s (%d)",
+                                                      ldb_strerror(status),
+                                                      status);
+                       }
+                       return status;
+               }
+       }
 
        ldb_reset_err_string(ldb);
 
+       FIRST_OP(ldb, end_transaction);
        status = module->ops->end_transaction(module);
        if (status != LDB_SUCCESS) {
                if (ldb->err_string == NULL) {
@@ -368,6 +394,9 @@ int ldb_transaction_commit(struct ldb_context *ldb)
                                ldb_strerror(status),
                                status);
                }
+               /* cancel the transaction */
+               FIRST_OP(ldb, del_transaction);
+               module->ops->del_transaction(module);
        }
        return status;
 }
@@ -393,7 +422,7 @@ int ldb_transaction_cancel(struct ldb_context *ldb)
 
        if (ldb->transaction_active < 0) {
                ldb_debug(ldb, LDB_DEBUG_FATAL,
-                         "commit called but no ldb transactions are active!");
+                         "cancel called but no ldb transactions are active!");
                ldb->transaction_active = 0;
                return LDB_ERR_OPERATIONS_ERROR;
        }
index 05dffd005a09b8b10d29cc71a80ed996d2976ddb..b792daa913ae631c2dea8414e9e7aba037a41fb7 100644 (file)
@@ -472,10 +472,14 @@ int ldb_load_modules(struct ldb_context *ldb, const char *options[])
   which makes writing a module simpler, and makes it more likely to keep working
   when ldb is extended
 */
-#define FIND_OP(module, op) do { \
-       struct ldb_context *ldb = module->ldb; \
+#define FIND_OP_NOERR(module, op) do { \
        module = module->next; \
        while (module && module->ops->op == NULL) module = module->next; \
+} while (0)
+
+#define FIND_OP(module, op) do { \
+       struct ldb_context *ldb = module->ldb; \
+       FIND_OP_NOERR(module, op); \
        if (module == NULL) { \
                ldb_asprintf_errstring(ldb, "Unable to find backend operation for " #op ); \
                return LDB_ERR_OPERATIONS_ERROR;        \
@@ -595,6 +599,17 @@ int ldb_next_end_trans(struct ldb_module *module)
        return module->ops->end_transaction(module);
 }
 
+int ldb_next_prepare_commit(struct ldb_module *module)
+{
+       FIND_OP_NOERR(module, prepare_commit);
+       if (module == NULL) {
+               /* we are allowed to have no prepare commit in
+                  backends */
+               return LDB_SUCCESS;
+       }
+       return module->ops->prepare_commit(module);
+}
+
 int ldb_next_del_trans(struct ldb_module *module)
 {
        FIND_OP(module, del_transaction);
index cc24d8a6c03a0cc8a9feac1103cf3f98293ccd91..1d6329ca2fd660085f13e96111ac6cbe9039e2ee 100644 (file)
@@ -127,6 +127,7 @@ int ldb_next_request(struct ldb_module *module, struct ldb_request *request);
 int ldb_next_start_trans(struct ldb_module *module);
 int ldb_next_end_trans(struct ldb_module *module);
 int ldb_next_del_trans(struct ldb_module *module);
+int ldb_next_prepare_commit(struct ldb_module *module);
 int ldb_next_init(struct ldb_module *module);
 
 void ldb_set_errstring(struct ldb_context *ldb, const char *err_string);