summaryrefslogtreecommitdiff
path: root/source3/lib
diff options
context:
space:
mode:
Diffstat (limited to 'source3/lib')
-rw-r--r--source3/lib/tdb_multikey.c530
1 files changed, 530 insertions, 0 deletions
diff --git a/source3/lib/tdb_multikey.c b/source3/lib/tdb_multikey.c
new file mode 100644
index 0000000000..77e63c5aaa
--- /dev/null
+++ b/source3/lib/tdb_multikey.c
@@ -0,0 +1,530 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * TDB multi-key wrapper
+ * Copyright (C) Volker Lendecke 2006
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "includes.h"
+
+static struct { enum TDB_ERROR t; NTSTATUS n; } tdb_to_ntstatus_map[] = {
+ { TDB_ERR_CORRUPT, NT_STATUS_INTERNAL_DB_CORRUPTION },
+ { TDB_ERR_IO, NT_STATUS_UNEXPECTED_IO_ERROR },
+ { TDB_ERR_LOCK, NT_STATUS_FILE_LOCK_CONFLICT },
+ { TDB_ERR_OOM, NT_STATUS_NO_MEMORY },
+ { TDB_ERR_EXISTS, NT_STATUS_OBJECTID_EXISTS },
+ { TDB_ERR_NOLOCK, NT_STATUS_NOT_LOCKED },
+ { TDB_ERR_LOCK_TIMEOUT, NT_STATUS_IO_TIMEOUT },
+ { TDB_ERR_NOEXIST, NT_STATUS_NOT_FOUND },
+ { TDB_ERR_EINVAL, NT_STATUS_INVALID_PARAMETER },
+ { TDB_ERR_RDONLY, NT_STATUS_ACCESS_DENIED },
+ { 0, NT_STATUS_OK },
+};
+
+static NTSTATUS map_ntstatus_from_tdb(struct tdb_context *t)
+{
+ enum TDB_ERROR err = tdb_error(t);
+ int i = 0;
+
+ while (tdb_to_ntstatus_map[i].t != 0) {
+ if (tdb_to_ntstatus_map[i].t == err) {
+ return tdb_to_ntstatus_map[i].n;
+ }
+ i += 1;
+ }
+
+ return NT_STATUS_INTERNAL_ERROR;
+}
+
+#define KEY_VERSION (1)
+#define PRIMARY_KEY_LENGTH (24)
+
+/*
+ * Check that the keying version is acceptable. Change operations are very
+ * expensive under transactions anyway, so we do this upon every change to
+ * avoid damage when someone changes the key format while we have the db open.
+ *
+ * To be called only within a transaction, we don't do locking here.
+ */
+
+static BOOL tdb_check_keyversion(struct tdb_context *tdb)
+{
+ const char *versionkey = "KEYVERSION";
+ TDB_DATA key, data;
+ NTSTATUS status;
+ unsigned long version;
+ char *endptr;
+
+ key.dptr = CONST_DISCARD(char *, versionkey);
+ key.dsize = strlen(versionkey)+1;
+
+ data = tdb_fetch(tdb, key);
+ if (data.dptr == NULL) {
+ char *vstr;
+ int res;
+
+ asprintf(&vstr, "%d", KEY_VERSION);
+ if (vstr == NULL) {
+ DEBUG(0, ("asprintf failed\n"));
+ return False;
+ }
+ data.dptr = vstr;
+ data.dsize = strlen(vstr)+1;
+
+ res = tdb_store(tdb, key, data, TDB_INSERT);
+ SAFE_FREE(vstr);
+
+ if (res < 0) {
+ status = map_ntstatus_from_tdb(tdb);
+ DEBUG(5, ("Could not store key: %s\n",
+ nt_errstr(status)));
+ return False;
+ }
+
+ return True;
+ }
+
+ /*
+ * We have a key, check it
+ */
+
+ SMB_ASSERT(data.dsize > 0);
+ if (data.dptr[data.dsize-1] != '\0') {
+ DEBUG(1, ("Key field not NUL terminated\n"));
+ SAFE_FREE(data.dptr);
+ return False;
+ }
+
+ version = strtoul(data.dptr, &endptr, 10);
+ if (endptr != data.dptr+data.dsize-1) {
+ DEBUG(1, ("Invalid version string\n"));
+ SAFE_FREE(data.dptr);
+ return False;
+ }
+ SAFE_FREE(data.dptr);
+
+ if (version != KEY_VERSION) {
+ DEBUG(1, ("Wrong key version: %ld, expected %d\n",
+ version, KEY_VERSION));
+ return False;
+ }
+
+ return True;
+}
+
+/*
+ * Find a record according to a key and value expected in that key. The
+ * primary_key is returned for later reference in tdb_idx_update or
+ * tdb_idx_delete.
+ */
+
+NTSTATUS tdb_find_keyed(TALLOC_CTX *ctx, struct tdb_context *tdb,
+ int keynumber, const char *value,
+ TDB_DATA *result, char **primary_key)
+{
+ TDB_DATA key, prim, data;
+ NTSTATUS status;
+
+ prim.dptr = data.dptr = NULL;
+
+ key.dptr = talloc_asprintf(ctx, "KEY/%d/%s", keynumber, value);
+ if (key.dptr == NULL) {
+ DEBUG(0, ("talloc_asprintf failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ key.dsize = strlen(key.dptr)+1;
+
+ prim = tdb_fetch(tdb, key);
+ if (prim.dptr == NULL) {
+ status = NT_STATUS_NOT_FOUND;
+ goto fail;
+ }
+
+ data = tdb_fetch(tdb, prim);
+ if (data.dptr == NULL) {
+ DEBUG(1, ("Did not find record %s for key %s\n",
+ prim.dptr, key.dptr));
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto fail;
+ }
+
+ if (primary_key != NULL) {
+ *primary_key = talloc_strndup(ctx, prim.dptr, prim.dsize);
+ if (*primary_key == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ }
+
+ /*
+ * The following copy will be removed when tdb_fetch takes a
+ * TALLOC_CTX as parameter.
+ */
+
+ result->dptr = (char *)talloc_memdup(ctx, data.dptr, data.dsize);
+ if (result->dptr == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ result->dsize = data.dsize;
+
+ status = NT_STATUS_OK;
+
+ fail:
+ TALLOC_FREE(key.dptr);
+ SAFE_FREE(prim.dptr);
+ SAFE_FREE(data.dptr);
+ return status;
+}
+
+/*
+ * Store all the key entries for a data entry. Best called within a tdb
+ * transaction.
+ */
+
+static NTSTATUS set_keys(struct tdb_context *tdb,
+ char **(*getkeys)(TALLOC_CTX *mem_ctx, TDB_DATA data,
+ void *private_data),
+ TDB_DATA primary_key, TDB_DATA user_data,
+ void *private_data)
+{
+ int i;
+ char **keys = getkeys(NULL, user_data, private_data);
+
+ if (keys == NULL) {
+ DEBUG(5, ("Could not get keys\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; keys[i] != NULL; i++) {
+ NTSTATUS status;
+ TDB_DATA key;
+
+ key.dptr = talloc_asprintf(keys, "KEY/%d/%s", i, keys[i]);
+ if (key.dptr == NULL) {
+ DEBUG(0, ("talloc_asprintf failed\n"));
+ TALLOC_FREE(keys);
+ return NT_STATUS_NO_MEMORY;
+ }
+ key.dsize = strlen(key.dptr)+1;
+
+ if (tdb_store(tdb, key, primary_key, TDB_INSERT) < 0) {
+ status = map_ntstatus_from_tdb(tdb);
+ DEBUG(5, ("Could not store key %d: %s\n", i,
+ nt_errstr(status)));
+ TALLOC_FREE(keys);
+ return status;
+ }
+ }
+
+ TALLOC_FREE(keys);
+ return NT_STATUS_OK;
+}
+
+/*
+ * Delete all the key entries for a data entry. Best called within a tdb
+ * transaction.
+ */
+
+static NTSTATUS del_keys(struct tdb_context *tdb,
+ char **(*getkeys)(TALLOC_CTX *mem_ctx, TDB_DATA data,
+ void *private_data),
+ TDB_DATA primary_key, void *private_data)
+{
+ TDB_DATA data;
+ int i;
+ char **keys;
+
+ /*
+ * We need the data record to be able to fetch all the keys, so pull
+ * the user data
+ */
+
+ data = tdb_fetch(tdb, primary_key);
+ if (data.dptr == NULL) {
+ DEBUG(5, ("Could not find record for key %s\n",
+ primary_key.dptr));
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ keys = getkeys(NULL, data, private_data);
+ if (keys == NULL) {
+ DEBUG(5, ("Could not get keys\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ SAFE_FREE(data.dptr);
+
+ for (i=0; keys[i] != NULL; i++) {
+ NTSTATUS status;
+ TDB_DATA key;
+
+ key.dptr = talloc_asprintf(keys, "KEY/%d/%s", i, keys[i]);
+ if (key.dptr == NULL) {
+ DEBUG(0, ("talloc_asprintf failed\n"));
+ TALLOC_FREE(keys);
+ return NT_STATUS_NO_MEMORY;
+ }
+ key.dsize = strlen(key.dptr)+1;
+
+ if (tdb_delete(tdb, key) < 0) {
+ status = map_ntstatus_from_tdb(tdb);
+ DEBUG(5, ("Could not delete key %d: %s\n", i,
+ nt_errstr(status)));
+ TALLOC_FREE(keys);
+ return status;
+ }
+ }
+
+ TALLOC_FREE(keys);
+ return NT_STATUS_OK;
+}
+
+/*
+ * Generate a unique primary key
+ */
+
+static TDB_DATA new_primary_key(struct tdb_context *tdb)
+{
+ TDB_DATA key;
+ int i;
+
+ /*
+ * Generate a new primary key, the for loop is for the very unlikely
+ * collisions.
+ */
+
+ for (i=0; i<20; i++) {
+ TDB_DATA data;
+ asprintf(&key.dptr, "KEYPRIM/%s", generate_random_str(16));
+ if (key.dptr == NULL) {
+ DEBUG(0, ("talloc_asprintf failed\n"));
+ return key;
+ }
+
+#ifdef DEVELOPER
+ SMB_ASSERT(strlen(key.dptr) == PRIMARY_KEY_LENGTH);
+#endif
+ key.dsize = PRIMARY_KEY_LENGTH+1;
+
+ data = tdb_fetch(tdb, key);
+ if (data.dptr == NULL) {
+ return key;
+ }
+ SAFE_FREE(key.dptr);
+ SAFE_FREE(data.dptr);
+ }
+
+ DEBUG(0, ("Did not find a unique key string!\n"));
+ key.dptr = NULL;
+ key.dsize = 0;
+ return key;
+}
+
+/*
+ * Add a new record to the database
+ */
+
+NTSTATUS tdb_add_keyed(struct tdb_context *tdb,
+ char **(*getkeys)(TALLOC_CTX *mem_ctx, TDB_DATA data,
+ void *private_data),
+ TDB_DATA data, void *private_data)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ TDB_DATA key;
+
+ key.dptr = NULL;
+
+ if (tdb_transaction_start(tdb) < 0) {
+ status = map_ntstatus_from_tdb(tdb);
+ DEBUG(5, ("Could not start transaction: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (!tdb_check_keyversion(tdb)) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto fail;
+ }
+
+ key = new_primary_key(tdb);
+ if (key.dptr == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (tdb_store(tdb, key, data, TDB_INSERT) < 0) {
+ status = map_ntstatus_from_tdb(tdb);
+ DEBUG(5, ("Could not store record: %s\n", nt_errstr(status)));
+ goto fail;
+ }
+
+ status = set_keys(tdb, getkeys, key, data, private_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("set_keys failed: %s\n", nt_errstr(status)));
+ goto fail;
+ }
+
+ if (tdb_transaction_commit(tdb) < 0) {
+ status = map_ntstatus_from_tdb(tdb);
+ DEBUG(5, ("tdb_transaction_commit failed: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ SAFE_FREE(key.dptr);
+ return NT_STATUS_OK;
+
+ fail:
+ if (tdb_transaction_cancel(tdb) < 0) {
+ smb_panic("tdb_cancel_transaction failed\n");
+ }
+
+ SAFE_FREE(key.dptr);
+ return status;
+}
+
+/*
+ * Delete a record from the database, given its primary key
+ */
+
+NTSTATUS tdb_del_keyed(struct tdb_context *tdb,
+ char **(*getkeys)(TALLOC_CTX *mem_ctx, TDB_DATA data,
+ void *private_data),
+ const char *primary_key, void *private_data)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ TDB_DATA key;
+
+ if ((primary_key == NULL) ||
+ (strlen(primary_key) != PRIMARY_KEY_LENGTH) ||
+ (strncmp(primary_key, "KEYPRIM/", 7) != 0)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (tdb_transaction_start(tdb) < 0) {
+ status = map_ntstatus_from_tdb(tdb);
+ DEBUG(5, ("Could not start transaction: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (!tdb_check_keyversion(tdb)) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto fail;
+ }
+
+ key.dptr = CONST_DISCARD(char *, primary_key);
+ key.dsize = PRIMARY_KEY_LENGTH+1;
+
+ status = del_keys(tdb, getkeys, key, private_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("del_keys failed: %s\n", nt_errstr(status)));
+ goto fail;
+ }
+
+ if (tdb_delete(tdb, key) < 0) {
+ DEBUG(5, ("Could not delete record %s\n", primary_key));
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto fail;
+ }
+
+ if (tdb_transaction_commit(tdb) < 0) {
+ status = map_ntstatus_from_tdb(tdb);
+ DEBUG(5, ("tdb_transaction_commit failed: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ return NT_STATUS_OK;
+
+ fail:
+ if (tdb_transaction_cancel(tdb) < 0) {
+ smb_panic("tdb_cancel_transaction failed\n");
+ }
+
+ return status;
+}
+
+/*
+ * Update a record that has previously been fetched and then changed.
+ */
+
+NTSTATUS tdb_update_keyed(struct tdb_context *tdb, const char *primary_key,
+ char **(*getkeys)(TALLOC_CTX *mem_ctx,
+ TDB_DATA data, void *private_data),
+ TDB_DATA data, void *private_data)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ TDB_DATA key;
+
+ if ((primary_key == NULL) ||
+ (strlen(primary_key) != PRIMARY_KEY_LENGTH) ||
+ (strncmp(primary_key, "KEYPRIM/", 7) != 0)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (tdb_transaction_start(tdb) < 0) {
+ status = map_ntstatus_from_tdb(tdb);
+ DEBUG(5, ("Could not start transaction: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (!tdb_check_keyversion(tdb)) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto fail;
+ }
+
+ key.dptr = CONST_DISCARD(char *, primary_key);
+ key.dsize = PRIMARY_KEY_LENGTH+1;
+
+ status = del_keys(tdb, getkeys, key, private_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("del_keys failed: %s\n", nt_errstr(status)));
+ goto fail;
+ }
+
+ if (tdb_store(tdb, key, data, TDB_REPLACE) < 0) {
+ status = map_ntstatus_from_tdb(tdb);
+ DEBUG(5, ("Could not store new record: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ status = set_keys(tdb, getkeys, key, data, private_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("set_keys failed: %s\n", nt_errstr(status)));
+ goto fail;
+ }
+
+ if (tdb_transaction_commit(tdb) < 0) {
+ status = map_ntstatus_from_tdb(tdb);
+ DEBUG(5, ("tdb_transaction_commit failed: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ return NT_STATUS_OK;
+
+ fail:
+ if (tdb_transaction_cancel(tdb) < 0) {
+ smb_panic("tdb_cancel_transaction failed\n");
+ }
+
+ return status;
+}