r12534: Make the transaction code fill the error string on failure.
[samba.git] / source4 / lib / ldb / common / ldb.c
index 25e7bee66b749a4135037d571a012b1e87d5e6e7..6095f4fc04ce2a7efea971a8f31857083b5012ad 100644 (file)
@@ -34,6 +34,7 @@
 
 #include "includes.h"
 #include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
 #include "ldb/include/ldb_private.h"
 
 /* 
@@ -89,37 +90,189 @@ int ldb_connect(struct ldb_context *ldb, const char *url, unsigned int flags, co
 #endif
        else {
                ldb_debug(ldb, LDB_DEBUG_FATAL, "Unable to find backend for '%s'\n", url);
-               return -1;
+               return LDB_ERR_OTHER;
        }
 
-       if (ret != 0) {
+       if (ret != LDB_SUCCESS) {
                ldb_debug(ldb, LDB_DEBUG_ERROR, "Failed to connect to '%s'\n", url);
                return ret;
        }
 
-       if (ldb_load_modules(ldb, options) != 0) {
+       if (ldb_load_modules(ldb, options) != LDB_SUCCESS) {
                ldb_debug(ldb, LDB_DEBUG_FATAL, "Unable to load modules for '%s'\n", url);
-               return -1;
+               return LDB_ERR_OTHER;
        }
 
-       return 0;
+       return LDB_SUCCESS;
+}
+
+static void ldb_reset_err_string(struct ldb_context *ldb)
+{
+       if (ldb->err_string) {
+               talloc_free(ldb->err_string);
+               ldb->err_string = NULL;
+       }
 }
 
+#define FIRST_OP(ldb, op) do { \
+       module = ldb->modules; \
+       while (module && module->ops->op == NULL) module = module->next; \
+       if (module == NULL) return -1; \
+} while (0)
+
 /*
-  search the database given a LDAP-like search expression
+  start a transaction
+*/
+int ldb_transaction_start(struct ldb_context *ldb)
+{
+       struct ldb_module *module;
+       int status;
+       FIRST_OP(ldb, start_transaction);
+       
+       ldb->transaction_active++;
+
+       ldb_reset_err_string(ldb);
+
+       status = module->ops->start_transaction(module);
+       if (status != LDB_SUCCESS) {
+               if (ldb->err_string == NULL) {
+                       /* no error string was setup by the backend */
+                       ldb_set_errstring(ldb->modules, 
+                                         talloc_asprintf(ldb, "ldb transaction start error %d", status));
+               }
+       }
+       return status;
+}
 
-  return the number of records found, or -1 on error
+/*
+  commit a transaction
+*/
+int ldb_transaction_commit(struct ldb_context *ldb)
+{
+       struct ldb_module *module;
+       int status;
+       FIRST_OP(ldb, end_transaction);
+
+       if (ldb->transaction_active > 0) {
+               ldb->transaction_active--;
+       } else {
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
 
-  Use talloc_free to free the ldb_message returned in 'res'
+       ldb_reset_err_string(ldb);
 
+       status = module->ops->end_transaction(module);
+       if (status != LDB_SUCCESS) {
+               if (ldb->err_string == NULL) {
+                       /* no error string was setup by the backend */
+                       ldb_set_errstring(ldb->modules, 
+                                         talloc_asprintf(ldb, "ldb transaction commit error %d", status));
+               }
+       }
+       return status;
+}
+
+/*
+  cancel a transaction
 */
-int ldb_search(struct ldb_context *ldb, 
-              const struct ldb_dn *base,
-              enum ldb_scope scope,
-              const char *expression,
-              const char * const *attrs, struct ldb_message ***res)
+int ldb_transaction_cancel(struct ldb_context *ldb)
 {
-       return ldb->modules->ops->search(ldb->modules, base, scope, expression, attrs, res);
+       struct ldb_module *module;
+       int status;
+       FIRST_OP(ldb, del_transaction);
+
+       if (ldb->transaction_active > 0) {
+               ldb->transaction_active--;
+       } else {
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       status = module->ops->del_transaction(module);
+       if (status != LDB_SUCCESS) {
+               if (ldb->err_string == NULL) {
+                       /* no error string was setup by the backend */
+                       ldb_set_errstring(ldb->modules, 
+                                         talloc_asprintf(ldb, "ldb transaction cancel error %d", status));
+               }
+       }
+       return status;
+}
+
+/*
+  check for an error return from an op 
+  if an op fails, but has not setup an error string, then setup one now
+*/
+static int ldb_op_finish(struct ldb_context *ldb, int status)
+{
+       if (status == LDB_SUCCESS) {
+               return ldb_transaction_commit(ldb);
+       }
+       if (ldb->err_string == NULL) {
+               /* no error string was setup by the backend */
+               ldb_set_errstring(ldb->modules, 
+                                 talloc_asprintf(ldb, "ldb error %d", status));
+       }
+       ldb_transaction_cancel(ldb);
+       return status;
+}
+
+/*
+  start an ldb request
+  autostarts a transacion if none active and the operation is not a search
+  returns LDB_ERR_* on errors.
+*/
+int ldb_request(struct ldb_context *ldb, struct ldb_request *request)
+{
+       int status, started_transaction=0;
+       struct ldb_request *r;
+
+       ldb_reset_err_string(ldb);
+
+       /* to allow ldb modules to assume they can use the request ptr
+          as a talloc context for the request, we have to copy the 
+          structure here */
+       r = talloc(ldb, struct ldb_request);
+       if (r == NULL) {
+               ldb_oom(ldb);
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       *r = *request;
+
+       if (r->operation == LDB_REQ_SEARCH) {
+               r->op.search.res = NULL;
+       }
+
+       /* start a transaction if needed */
+       if ((!ldb->transaction_active) &&
+           (request->operation == LDB_REQ_ADD ||
+            request->operation == LDB_REQ_MODIFY ||
+            request->operation == LDB_REQ_DELETE ||
+            request->operation == LDB_REQ_RENAME)) {
+               status = ldb_transaction_start(ldb);
+               if (status != LDB_SUCCESS) {
+                       talloc_free(r);
+                       return status;
+               }
+               started_transaction = 1;
+       }
+
+       /* call the first module in the chain */
+       status = ldb->modules->ops->request(ldb->modules, r);
+
+       /* the search call is the only one that returns something
+          other than a status code. We steal the results into
+          the context of the ldb before freeing the request */
+       if (request->operation == LDB_REQ_SEARCH) {
+               request->op.search.res = talloc_steal(ldb, r->op.search.res);
+       }
+       talloc_free(r);
+
+       if (started_transaction) {
+               return ldb_op_finish(ldb, status);
+       }
+
+       return status;
 }
 
 /*
@@ -130,13 +283,38 @@ int ldb_search(struct ldb_context *ldb,
   Use talloc_free to free the ldb_message returned in 'res'
 
 */
-int ldb_search_bytree(struct ldb_context *ldb, 
-                     const struct ldb_dn *base,
-                     enum ldb_scope scope,
-                     struct ldb_parse_tree *tree,
-                     const char * const *attrs, struct ldb_message ***res)
+int ldb_search(struct ldb_context *ldb, 
+              const struct ldb_dn *base,
+              enum ldb_scope scope,
+              const char *expression,
+              const char * const *attrs, 
+              struct ldb_result **res)
 {
-       return ldb->modules->ops->search_bytree(ldb->modules, base, scope, tree, attrs, res);
+       struct ldb_request request;
+       struct ldb_parse_tree *tree;
+       int ret;
+
+       (*res) = NULL;
+
+       tree = ldb_parse_tree(ldb, expression);
+       if (tree == NULL) {
+               ldb_set_errstring(ldb->modules, talloc_strdup(ldb, "Unable to parse search expression"));
+               return -1;
+       }
+
+       request.operation = LDB_REQ_SEARCH;
+       request.op.search.base = base;
+       request.op.search.scope = scope;
+       request.op.search.tree = tree;
+       request.op.search.attrs = attrs;
+
+       ret = ldb_request(ldb, &request);
+
+       (*res) = request.op.search.res;
+
+       talloc_free(tree);
+
+       return ret;
 }
 
 /*
@@ -146,7 +324,16 @@ int ldb_search_bytree(struct ldb_context *ldb,
 int ldb_add(struct ldb_context *ldb, 
            const struct ldb_message *message)
 {
-       return ldb->modules->ops->add_record(ldb->modules, message);
+       struct ldb_request request;
+       int status;
+
+       status = ldb_msg_sanity_check(message);
+       if (status != LDB_SUCCESS) return status;
+
+       request.operation = LDB_REQ_ADD;
+       request.op.add.message = message;
+
+       return ldb_request(ldb, &request);
 }
 
 /*
@@ -155,7 +342,16 @@ int ldb_add(struct ldb_context *ldb,
 int ldb_modify(struct ldb_context *ldb, 
               const struct ldb_message *message)
 {
-       return ldb->modules->ops->modify_record(ldb->modules, message);
+       struct ldb_request request;
+       int status;
+
+       status = ldb_msg_sanity_check(message);
+       if (status != LDB_SUCCESS) return status;
+
+       request.operation = LDB_REQ_MODIFY;
+       request.op.mod.message = message;
+
+       return ldb_request(ldb, &request);
 }
 
 
@@ -164,7 +360,12 @@ int ldb_modify(struct ldb_context *ldb,
 */
 int ldb_delete(struct ldb_context *ldb, const struct ldb_dn *dn)
 {
-       return ldb->modules->ops->delete_record(ldb->modules, dn);
+       struct ldb_request request;
+
+       request.operation = LDB_REQ_DELETE;
+       request.op.del.dn = dn;
+
+       return ldb_request(ldb, &request);
 }
 
 /*
@@ -172,34 +373,27 @@ int ldb_delete(struct ldb_context *ldb, const struct ldb_dn *dn)
 */
 int ldb_rename(struct ldb_context *ldb, const struct ldb_dn *olddn, const struct ldb_dn *newdn)
 {
-       return ldb->modules->ops->rename_record(ldb->modules, olddn, newdn);
-}
+       struct ldb_request request;
 
-/*
-  create a named lock
-*/
-int ldb_lock(struct ldb_context *ldb, const char *lockname)
-{
-        return ldb->modules->ops->named_lock(ldb->modules, lockname);
-}
+       request.operation = LDB_REQ_RENAME;
+       request.op.rename.olddn = olddn;
+       request.op.rename.newdn = newdn;
 
-/*
-  release a named lock
-*/
-int ldb_unlock(struct ldb_context *ldb, const char *lockname)
-{
-        return ldb->modules->ops->named_unlock(ldb->modules, lockname);
+       return ldb_request(ldb, &request);
 }
 
+
+
 /*
   return extended error information 
 */
 const char *ldb_errstring(struct ldb_context *ldb)
 {
-       if (ldb->modules == NULL) {
-               return "ldb not connected";
+       if (ldb->err_string) {
+               return ldb->err_string;
        }
-       return ldb->modules->ops->errstring(ldb->modules);
+
+       return NULL;
 }
 
 
@@ -208,16 +402,26 @@ const char *ldb_errstring(struct ldb_context *ldb)
 */
 int ldb_set_opaque(struct ldb_context *ldb, const char *name, void *value)
 {
-       struct ldb_opaque *o = talloc(ldb, struct ldb_opaque);
+       struct ldb_opaque *o;
+
+       /* allow updating an existing value */
+       for (o=ldb->opaque;o;o=o->next) {
+               if (strcmp(o->name, name) == 0) {
+                       o->value = value;
+                       return LDB_SUCCESS;
+               }
+       }
+
+       o = talloc(ldb, struct ldb_opaque);
        if (o == NULL) {
                ldb_oom(ldb);
-               return -1;
+               return LDB_ERR_OTHER;
        }
        o->next = ldb->opaque;
        o->name = name;
        o->value = value;
        ldb->opaque = o;
-       return 0;
+       return LDB_SUCCESS;
 }
 
 /*