Merge tag 'upstream/4.0.5+dfsg1' into samba_4.0_ivo
[abartlet/samba-debian.git] / lib / util / util_ntdb.c
diff --git a/lib/util/util_ntdb.c b/lib/util/util_ntdb.c
new file mode 100644 (file)
index 0000000..be720e5
--- /dev/null
@@ -0,0 +1,346 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   ntdb utility functions
+
+   Copyright (C) Rusty Russell 2012
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "includes.h"
+#include "util_ntdb.h"
+#include "lib/param/param.h"
+#include "replace.h"
+#include "system/filesys.h"
+
+/*
+ * This handles NTDB_CLEAR_IF_FIRST.
+ *
+ * It's a bad idea for new code, but S3 uses it quite a bit.
+ */
+static enum NTDB_ERROR clear_if_first(int fd, void *unused)
+{
+       /* We hold a lock offset 4 always, so we can tell if anyone else is. */
+       struct flock fl;
+
+       fl.l_type = F_WRLCK;
+       fl.l_whence = SEEK_SET;
+       fl.l_start = 4; /* ACTIVE_LOCK */
+       fl.l_len = 1;
+
+       if (fcntl(fd, F_SETLK, &fl) == 0) {
+               /* We must be first ones to open it w/ NTDB_CLEAR_IF_FIRST! */
+               if (ftruncate(fd, 0) != 0) {
+                       return NTDB_ERR_IO;
+               }
+       }
+       fl.l_type = F_RDLCK;
+       if (fcntl(fd, F_SETLKW, &fl) != 0) {
+               return NTDB_ERR_IO;
+       }
+       return NTDB_SUCCESS;
+}
+
+/* We only need these for the CLEAR_IF_FIRST lock. */
+static int reacquire_cif_lock(struct ntdb_context *ntdb, bool *fail)
+{
+       struct flock fl;
+       union ntdb_attribute cif;
+
+       cif.openhook.base.attr = NTDB_ATTRIBUTE_OPENHOOK;
+       cif.openhook.base.next = NULL;
+
+       if (ntdb_get_attribute(ntdb, &cif) != NTDB_SUCCESS
+           || cif.openhook.fn != clear_if_first) {
+               return 0;
+       }
+
+       /* We hold a lock offset 4 always, so we can tell if anyone else is. */
+       fl.l_type = F_RDLCK;
+       fl.l_whence = SEEK_SET;
+       fl.l_start = 4; /* ACTIVE_LOCK */
+       fl.l_len = 1;
+       if (fcntl(ntdb_fd(ntdb), F_SETLKW, &fl) != 0) {
+               *fail = true;
+               return -1;
+       }
+       return 0;
+}
+
+/* You only need this on databases with NTDB_CLEAR_IF_FIRST */
+int ntdb_reopen(struct ntdb_context *ntdb)
+{
+       bool unused;
+       return reacquire_cif_lock(ntdb, &unused);
+}
+
+/* You only need to do this if you have NTDB_CLEAR_IF_FIRST databases, and
+ * the parent will go away before this child. */
+int ntdb_reopen_all(void)
+{
+       bool fail = false;
+
+       ntdb_foreach(reacquire_cif_lock, &fail);
+       if (fail)
+               return -1;
+       return 0;
+}
+
+static void *ntdb_talloc(const void *owner, size_t len, void *priv_data)
+{
+       return talloc_size(owner, len);
+}
+
+static void *ntdb_expand(void *old, size_t newlen, void *priv_data)
+{
+       return talloc_realloc_size(NULL, old, newlen);
+}
+
+static void ntdb_free(void *old, void *priv_data)
+{
+       talloc_free(old);
+}
+
+static int ntdb_destroy(struct ntdb_context *ntdb)
+{
+       ntdb_close(ntdb);
+       return 0;
+}
+
+static void ntdb_log(struct ntdb_context *ntdb,
+                    enum ntdb_log_level level,
+                    enum NTDB_ERROR ecode,
+                    const char *message,
+                    void *unused)
+{
+       int dl;
+       const char *name = ntdb_name(ntdb);
+
+       switch (level) {
+       case NTDB_LOG_USE_ERROR:
+       case NTDB_LOG_ERROR:
+               dl = 0;
+               break;
+       case NTDB_LOG_WARNING:
+               dl = 2;
+               break;
+       default:
+               dl = 0;
+       }
+
+       DEBUG(dl, ("ntdb(%s):%s: %s\n", name ? name : "unnamed",
+                  ntdb_errorstr(ecode), message));
+}
+
+struct ntdb_context *ntdb_new(TALLOC_CTX *ctx,
+                             const char *name, int ntdb_flags,
+                             int open_flags, mode_t mode,
+                             union ntdb_attribute *attr,
+                             struct loadparm_context *lp_ctx)
+{
+       union ntdb_attribute log_attr, alloc_attr, open_attr;
+       struct ntdb_context *ntdb;
+
+       if (lp_ctx && !lpcfg_use_mmap(lp_ctx)) {
+               ntdb_flags |= NTDB_NOMMAP;
+       }
+
+       /* Great hack for speeding testing! */
+       if (getenv("TDB_NO_FSYNC")) {
+               ntdb_flags |= NTDB_NOSYNC;
+       }
+
+       log_attr.base.next = attr;
+       log_attr.base.attr = NTDB_ATTRIBUTE_LOG;
+       log_attr.log.fn = ntdb_log;
+
+       alloc_attr.base.next = &log_attr;
+       alloc_attr.base.attr = NTDB_ATTRIBUTE_ALLOCATOR;
+       alloc_attr.alloc.alloc = ntdb_talloc;
+       alloc_attr.alloc.expand = ntdb_expand;
+       alloc_attr.alloc.free = ntdb_free;
+
+       if (ntdb_flags & NTDB_CLEAR_IF_FIRST) {
+               log_attr.base.next = &open_attr;
+               open_attr.openhook.base.attr = NTDB_ATTRIBUTE_OPENHOOK;
+               open_attr.openhook.base.next = attr;
+               open_attr.openhook.fn = clear_if_first;
+               ntdb_flags &= ~NTDB_CLEAR_IF_FIRST;
+       }
+
+       ntdb = ntdb_open(name, ntdb_flags, open_flags, mode, &alloc_attr);
+       if (!ntdb) {
+               return NULL;
+       }
+
+       /* We can re-use the tdb's path name for the talloc name */
+       name = ntdb_name(ntdb);
+       if (name) {
+               talloc_set_name_const(ntdb, name);
+       } else {
+               talloc_set_name_const(ntdb, "unnamed ntdb");
+       }
+       talloc_set_destructor(ntdb, ntdb_destroy);
+
+       return talloc_steal(ctx, ntdb);
+}
+
+enum NTDB_ERROR ntdb_lock_bystring(struct ntdb_context *ntdb,
+                                  const char *keyval)
+{
+       NTDB_DATA key = string_term_ntdb_data(keyval);
+
+       return ntdb_chainlock(ntdb, key);
+}
+
+void ntdb_unlock_bystring(struct ntdb_context *ntdb, const char *keyval)
+{
+       NTDB_DATA key = string_term_ntdb_data(keyval);
+
+       ntdb_chainunlock(ntdb, key);
+}
+
+enum NTDB_ERROR ntdb_delete_bystring(struct ntdb_context *ntdb,
+                                    const char *keystr)
+{
+       NTDB_DATA key = string_term_ntdb_data(keystr);
+
+       return ntdb_delete(ntdb, key);
+}
+
+enum NTDB_ERROR ntdb_store_bystring(struct ntdb_context *ntdb,
+                                   const char *keystr,
+                                   NTDB_DATA data, int nflags)
+{
+       NTDB_DATA key = string_term_ntdb_data(keystr);
+
+       return ntdb_store(ntdb, key, data, nflags);
+}
+
+enum NTDB_ERROR ntdb_fetch_bystring(struct ntdb_context *ntdb,
+                                   const char *keystr,
+                                   NTDB_DATA *data)
+{
+       NTDB_DATA key = string_term_ntdb_data(keystr);
+
+       return ntdb_fetch(ntdb, key, data);
+}
+
+enum NTDB_ERROR ntdb_fetch_int32(struct ntdb_context *ntdb,
+                                const char *keystr, int32_t *val)
+{
+       NTDB_DATA data;
+       enum NTDB_ERROR err;
+
+       err = ntdb_fetch(ntdb, string_term_ntdb_data(keystr), &data);
+       if (err == NTDB_SUCCESS) {
+               if (data.dsize != sizeof(*val)) {
+                       err = NTDB_ERR_CORRUPT;
+               } else {
+                       *val = IVAL(data.dptr,0);
+               }
+               talloc_free(data.dptr);
+       }
+       return NTDB_SUCCESS;
+}
+
+enum NTDB_ERROR ntdb_store_int32(struct ntdb_context *ntdb,
+                                const char *keystr, int32_t val)
+{
+       NTDB_DATA data, key;
+       int32_t v_store;
+
+       SIVAL(&v_store, 0, val);
+       data = ntdb_mkdata(&v_store, sizeof(v_store));
+       key = string_term_ntdb_data(keystr);
+
+       return ntdb_store(ntdb, key, data, NTDB_REPLACE);
+}
+
+enum NTDB_ERROR ntdb_add_int32_atomic(struct ntdb_context *ntdb,
+                                     const char *keystr,
+                                     int32_t *oldval, int32_t addval)
+{
+       int32_t val;
+       enum NTDB_ERROR err;
+
+       err = ntdb_lock_bystring(ntdb, keystr);
+       if (err) {
+               return err;
+       }
+
+       err = ntdb_fetch_int32(ntdb, keystr, &val);
+       if (err) {
+               if (err == NTDB_ERR_NOEXIST) {
+                       /* Start with 'old' value */
+                       val = *oldval;
+               } else {
+                       goto err_out;
+               }
+       } else {
+               /* It worked, set return value (oldval) to tdb data */
+               *oldval = val;
+       }
+
+       /* Increase value and store for next time. */
+       val += addval;
+       err = ntdb_store_int32(ntdb, keystr, val);
+
+  err_out:
+       ntdb_unlock_bystring(ntdb, keystr);
+       return err;
+}
+
+NTSTATUS map_nt_error_from_ntdb(enum NTDB_ERROR err)
+{
+       NTSTATUS result = NT_STATUS_INTERNAL_ERROR;
+
+       switch (err) {
+       case NTDB_SUCCESS:
+               result = NT_STATUS_OK;
+               break;
+       case NTDB_ERR_CORRUPT:
+               result = NT_STATUS_INTERNAL_DB_CORRUPTION;
+               break;
+       case NTDB_ERR_IO:
+               result = NT_STATUS_UNEXPECTED_IO_ERROR;
+               break;
+       case NTDB_ERR_OOM:
+               result = NT_STATUS_NO_MEMORY;
+               break;
+       case NTDB_ERR_EXISTS:
+               result = NT_STATUS_OBJECT_NAME_COLLISION;
+               break;
+
+       case NTDB_ERR_LOCK:
+               /*
+                * NTDB_ERR_LOCK is very broad, we could for example
+                * distinguish between fcntl locks and invalid lock
+                * sequences. So NT_STATUS_FILE_LOCK_CONFLICT is a
+                * compromise.
+                */
+               result = NT_STATUS_FILE_LOCK_CONFLICT;
+               break;
+       case NTDB_ERR_NOEXIST:
+               result = NT_STATUS_NOT_FOUND;
+               break;
+       case NTDB_ERR_EINVAL:
+               result = NT_STATUS_INVALID_PARAMETER;
+               break;
+       case NTDB_ERR_RDONLY:
+               result = NT_STATUS_ACCESS_DENIED;
+               break;
+       };
+       return result;
+}