Commit 7607ced7 authored by Rusty Russell's avatar Rusty Russell

tdb2: tdb_append implemented, beef up tests.

parent d01dfe4e
...@@ -699,13 +699,50 @@ static tdb_off_t find_and_lock(struct tdb_context *tdb, ...@@ -699,13 +699,50 @@ static tdb_off_t find_and_lock(struct tdb_context *tdb,
return TDB_OFF_ERR; return TDB_OFF_ERR;
} }
/* Returns -1 on error, 0 on OK, 1 on "expand and retry." */
static int replace_data(struct tdb_context *tdb,
uint64_t h, struct tdb_data key, struct tdb_data dbuf,
tdb_off_t bucket,
tdb_off_t old_off, tdb_len_t old_room,
bool growing)
{
tdb_off_t new_off;
/* Allocate a new record. */
new_off = alloc(tdb, key.dsize, dbuf.dsize, h, growing);
if (new_off == 0)
return 1;
/* We didn't like the existing one: remove it. */
if (old_off)
add_free_record(tdb, old_off,
sizeof(struct tdb_used_record)
+ key.dsize + old_room);
/* FIXME: Encode extra hash bits! */
if (tdb_write_off(tdb, hash_off(tdb, bucket), new_off) == -1)
return -1;
new_off += sizeof(struct tdb_used_record);
if (tdb->methods->write(tdb, new_off, key.dptr, key.dsize) == -1)
return -1;
new_off += key.dsize;
if (tdb->methods->write(tdb, new_off, dbuf.dptr, dbuf.dsize) == -1)
return -1;
/* FIXME: tdb_increment_seqnum(tdb); */
return 0;
}
int tdb_store(struct tdb_context *tdb, int tdb_store(struct tdb_context *tdb,
struct tdb_data key, struct tdb_data dbuf, int flag) struct tdb_data key, struct tdb_data dbuf, int flag)
{ {
tdb_off_t new_off, off, bucket, start, num; tdb_off_t off, bucket, start, num;
tdb_len_t old_room = 0;
struct tdb_used_record rec; struct tdb_used_record rec;
uint64_t h; uint64_t h;
bool growing = false; int ret;
h = tdb_hash(tdb, key.dptr, key.dsize); h = tdb_hash(tdb, key.dptr, key.dsize);
off = find_and_lock(tdb, key, h, F_WRLCK, &start, &num, &bucket, &rec); off = find_and_lock(tdb, key, h, F_WRLCK, &start, &num, &bucket, &rec);
...@@ -720,18 +757,22 @@ int tdb_store(struct tdb_context *tdb, ...@@ -720,18 +757,22 @@ int tdb_store(struct tdb_context *tdb,
} }
} else { } else {
if (off) { if (off) {
if (rec_data_length(&rec) + rec_extra_padding(&rec) old_room = rec_data_length(&rec)
>= dbuf.dsize) { + rec_extra_padding(&rec);
new_off = off; if (old_room >= dbuf.dsize) {
/* Can modify in-place. Easy! */
if (update_rec_hdr(tdb, off, if (update_rec_hdr(tdb, off,
key.dsize, dbuf.dsize, key.dsize, dbuf.dsize,
&rec, h)) &rec, h))
goto fail; goto fail;
goto write; if (tdb->methods->write(tdb, off + sizeof(rec)
+ key.dsize,
dbuf.dptr, dbuf.dsize))
goto fail;
unlock_lists(tdb, start, num, F_WRLCK);
return 0;
} }
/* FIXME: See if right record is free? */ /* FIXME: See if right record is free? */
/* Hint to allocator that we've realloced. */
growing = true;
} else { } else {
if (flag == TDB_MODIFY) { if (flag == TDB_MODIFY) {
/* if the record doesn't exist and we /* if the record doesn't exist and we
...@@ -743,38 +784,99 @@ int tdb_store(struct tdb_context *tdb, ...@@ -743,38 +784,99 @@ int tdb_store(struct tdb_context *tdb,
} }
} }
/* Allocate a new record. */ /* If we didn't use the old record, this implies we're growing. */
new_off = alloc(tdb, key.dsize, dbuf.dsize, h, growing); ret = replace_data(tdb, h, key, dbuf, bucket, off, old_room, off != 0);
if (new_off == 0) { unlock_lists(tdb, start, num, F_WRLCK);
unlock_lists(tdb, start, num, F_WRLCK);
if (unlikely(ret == 1)) {
/* Expand, then try again... */ /* Expand, then try again... */
if (tdb_expand(tdb, key.dsize, dbuf.dsize, growing) == -1) if (tdb_expand(tdb, key.dsize, dbuf.dsize, off != 0) == -1)
return -1; return -1;
return tdb_store(tdb, key, dbuf, flag); return tdb_store(tdb, key, dbuf, flag);
} }
/* We didn't like the existing one: remove it. */ /* FIXME: by simple simulation, this approximated 60% full.
* Check in real case! */
if (unlikely(num > 4 * tdb->header.v.hash_bits - 30))
enlarge_hash(tdb);
return ret;
fail:
unlock_lists(tdb, start, num, F_WRLCK);
return -1;
}
int tdb_append(struct tdb_context *tdb,
struct tdb_data key, struct tdb_data dbuf)
{
tdb_off_t off, bucket, start, num;
struct tdb_used_record rec;
tdb_len_t old_room = 0, old_dlen;
uint64_t h;
unsigned char *newdata;
struct tdb_data new_dbuf;
int ret;
h = tdb_hash(tdb, key.dptr, key.dsize);
off = find_and_lock(tdb, key, h, F_WRLCK, &start, &num, &bucket, &rec);
if (unlikely(off == TDB_OFF_ERR))
return -1;
if (off) { if (off) {
add_free_record(tdb, off, sizeof(struct tdb_used_record) old_dlen = rec_data_length(&rec);
+ rec_key_length(&rec) old_room = old_dlen + rec_extra_padding(&rec);
+ rec_data_length(&rec)
+ rec_extra_padding(&rec));
}
/* FIXME: Encode extra hash bits! */ /* Fast path: can append in place. */
if (tdb_write_off(tdb, hash_off(tdb, bucket), new_off) == -1) if (rec_extra_padding(&rec) >= dbuf.dsize) {
goto fail; if (update_rec_hdr(tdb, off, key.dsize,
old_dlen + dbuf.dsize, &rec, h))
goto fail;
write: off += sizeof(rec) + key.dsize + old_dlen;
off = new_off + sizeof(struct tdb_used_record); if (tdb->methods->write(tdb, off, dbuf.dptr,
if (tdb->methods->write(tdb, off, key.dptr, key.dsize) == -1) dbuf.dsize) == -1)
goto fail; goto fail;
off += key.dsize;
if (tdb->methods->write(tdb, off, dbuf.dptr, dbuf.dsize) == -1)
goto fail;
/* FIXME: tdb_increment_seqnum(tdb); */ /* FIXME: tdb_increment_seqnum(tdb); */
unlock_lists(tdb, start, num, F_WRLCK);
return 0;
}
/* FIXME: Check right record free? */
/* Slow path. */
newdata = malloc(key.dsize + old_dlen + dbuf.dsize);
if (!newdata) {
tdb->ecode = TDB_ERR_OOM;
tdb->log(tdb, TDB_DEBUG_FATAL, tdb->log_priv,
"tdb_append: cannot allocate %llu bytes!\n",
(long long)key.dsize + old_dlen + dbuf.dsize);
goto fail;
}
if (tdb->methods->read(tdb, off + sizeof(rec) + key.dsize,
newdata, old_dlen) != 0) {
free(newdata);
goto fail;
}
memcpy(newdata + old_dlen, dbuf.dptr, dbuf.dsize);
new_dbuf.dptr = newdata;
new_dbuf.dsize = old_dlen + dbuf.dsize;
} else {
newdata = NULL;
new_dbuf = dbuf;
}
/* If they're using tdb_append(), it implies they're growing record. */
ret = replace_data(tdb, h, key, new_dbuf, bucket, off, old_room, true);
unlock_lists(tdb, start, num, F_WRLCK); unlock_lists(tdb, start, num, F_WRLCK);
free(newdata);
if (unlikely(ret == 1)) {
/* Expand, then try again. */
if (tdb_expand(tdb, key.dsize, dbuf.dsize, true) == -1)
return -1;
return tdb_append(tdb, key, dbuf);
}
/* FIXME: by simple simulation, this approximated 60% full. /* FIXME: by simple simulation, this approximated 60% full.
* Check in real case! */ * Check in real case! */
......
...@@ -128,6 +128,7 @@ struct tdb_context *tdb_open(const char *name, int tdb_flags, ...@@ -128,6 +128,7 @@ struct tdb_context *tdb_open(const char *name, int tdb_flags,
struct tdb_data tdb_fetch(struct tdb_context *tdb, struct tdb_data key); struct tdb_data tdb_fetch(struct tdb_context *tdb, struct tdb_data key);
int tdb_delete(struct tdb_context *tdb, struct tdb_data key); int tdb_delete(struct tdb_context *tdb, struct tdb_data key);
int tdb_store(struct tdb_context *tdb, struct tdb_data key, struct tdb_data dbuf, int flag); int tdb_store(struct tdb_context *tdb, struct tdb_data key, struct tdb_data dbuf, int flag);
int tdb_append(struct tdb_context *tdb, struct tdb_data key, struct tdb_data dbuf);
int tdb_close(struct tdb_context *tdb); int tdb_close(struct tdb_context *tdb);
int tdb_check(struct tdb_context *tdb, int tdb_check(struct tdb_context *tdb,
int (*check)(TDB_DATA key, TDB_DATA data, void *private_data), int (*check)(TDB_DATA key, TDB_DATA data, void *private_data),
......
#include <ccan/tdb2/tdb.c>
#include <ccan/tdb2/free.c>
#include <ccan/tdb2/lock.c>
#include <ccan/tdb2/io.c>
#include <ccan/tdb2/check.c>
#include <ccan/tap/tap.h>
#include "logging.h"
#define MAX_SIZE 13100
#define SIZE_STEP 131
int main(int argc, char *argv[])
{
unsigned int i, j;
struct tdb_context *tdb;
unsigned char *buffer;
int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP,
TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT,
TDB_NOMMAP|TDB_CONVERT };
struct tdb_data key = { (unsigned char *)"key", 3 };
struct tdb_data data;
buffer = malloc(MAX_SIZE);
for (i = 0; i < MAX_SIZE; i++)
buffer[i] = i;
plan_tests(sizeof(flags) / sizeof(flags[0])
* ((2 + MAX_SIZE/SIZE_STEP * 4) * 2 + 6)
+ 1);
/* Using tdb_store. */
for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
tdb = tdb_open("run-append.tdb", flags[i],
O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
ok1(tdb);
if (!tdb)
continue;
for (j = 0; j < MAX_SIZE; j += SIZE_STEP) {
data.dptr = buffer;
data.dsize = j;
ok1(tdb_store(tdb, key, data, TDB_REPLACE) == 0);
ok1(tdb_check(tdb, NULL, NULL) == 0);
data = tdb_fetch(tdb, key);
ok1(data.dsize == j);
ok1(memcmp(data.dptr, buffer, data.dsize) == 0);
free(data.dptr);
}
ok1(!tdb_has_locks(tdb));
tdb_close(tdb);
}
/* Using tdb_append. */
for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
size_t prev_len = 0;
tdb = tdb_open("run-append.tdb", flags[i],
O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
ok1(tdb);
if (!tdb)
continue;
for (j = 0; j < MAX_SIZE; j += SIZE_STEP) {
data.dptr = buffer + prev_len;
data.dsize = j - prev_len;
ok1(tdb_append(tdb, key, data) == 0);
ok1(tdb_check(tdb, NULL, NULL) == 0);
data = tdb_fetch(tdb, key);
ok1(data.dsize == j);
ok1(memcmp(data.dptr, buffer, data.dsize) == 0);
free(data.dptr);
prev_len = data.dsize;
}
ok1(!tdb_has_locks(tdb));
tdb_close(tdb);
}
for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
tdb = tdb_open("run-append.tdb", flags[i],
O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
ok1(tdb);
if (!tdb)
continue;
/* Huge initial store. */
data.dptr = buffer;
data.dsize = MAX_SIZE;
ok1(tdb_append(tdb, key, data) == 0);
ok1(tdb_check(tdb, NULL, NULL) == 0);
data = tdb_fetch(tdb, key);
ok1(data.dsize == MAX_SIZE);
ok1(memcmp(data.dptr, buffer, data.dsize) == 0);
free(data.dptr);
ok1(!tdb_has_locks(tdb));
tdb_close(tdb);
}
ok1(tap_log_messages == 0);
return exit_status();
}
...@@ -62,9 +62,38 @@ static void test_val(struct tdb_context *tdb, unsigned int val) ...@@ -62,9 +62,38 @@ static void test_val(struct tdb_context *tdb, unsigned int val)
v = val + 1; v = val + 1;
ok1(tdb_fetch(tdb, key).dsize == data.dsize); ok1(tdb_fetch(tdb, key).dsize == data.dsize);
/* Delete that, so we are empty. */ /* Now, this will be ideally placed. */
v = val + 2;
ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
ok1(tdb_check(tdb, NULL, NULL) == 0);
/* This will collide with both. */
v = val;
ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
/* We can still find them all, right? */
ok1(tdb_fetch(tdb, key).dsize == data.dsize);
v = val + 1;
ok1(tdb_fetch(tdb, key).dsize == data.dsize);
v = val + 2;
ok1(tdb_fetch(tdb, key).dsize == data.dsize);
/* And if we delete val + 1, that val + 2 should not move! */
v = val + 1;
ok1(tdb_delete(tdb, key) == 0); ok1(tdb_delete(tdb, key) == 0);
ok1(tdb_check(tdb, NULL, NULL) == 0); ok1(tdb_check(tdb, NULL, NULL) == 0);
v = val;
ok1(tdb_fetch(tdb, key).dsize == data.dsize);
v = val + 2;
ok1(tdb_fetch(tdb, key).dsize == data.dsize);
/* Delete those two, so we are empty. */
ok1(tdb_delete(tdb, key) == 0);
v = val;
ok1(tdb_delete(tdb, key) == 0);
ok1(tdb_check(tdb, NULL, NULL) == 0);
} }
int main(int argc, char *argv[]) int main(int argc, char *argv[])
...@@ -73,12 +102,13 @@ int main(int argc, char *argv[]) ...@@ -73,12 +102,13 @@ int main(int argc, char *argv[])
struct tdb_context *tdb; struct tdb_context *tdb;
union tdb_attribute hattr = { .hash = { .base = { TDB_ATTRIBUTE_HASH }, union tdb_attribute hattr = { .hash = { .base = { TDB_ATTRIBUTE_HASH },
.hash_fn = clash } }; .hash_fn = clash } };
int flags[] = { TDB_INTERNAL, TDB_DEFAULT, int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP,
TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT }; TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT,
TDB_NOMMAP|TDB_CONVERT };
hattr.base.next = &tap_log_attr; hattr.base.next = &tap_log_attr;
plan_tests(sizeof(flags) / sizeof(flags[0]) * 44 + 1); plan_tests(sizeof(flags) / sizeof(flags[0]) * 66 + 1);
for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
tdb = tdb_open("run-delete.tdb", flags[i], tdb = tdb_open("run-delete.tdb", flags[i],
O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr); O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr);
......
...@@ -21,8 +21,9 @@ int main(int argc, char *argv[]) ...@@ -21,8 +21,9 @@ int main(int argc, char *argv[])
struct tdb_data data = { (unsigned char *)&v, sizeof(v) }; struct tdb_data data = { (unsigned char *)&v, sizeof(v) };
union tdb_attribute hattr = { .hash = { .base = { TDB_ATTRIBUTE_HASH }, union tdb_attribute hattr = { .hash = { .base = { TDB_ATTRIBUTE_HASH },
.hash_fn = clash } }; .hash_fn = clash } };
int flags[] = { TDB_INTERNAL, TDB_DEFAULT, int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP,
TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT }; TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT,
TDB_NOMMAP|TDB_CONVERT };
hattr.base.next = &tap_log_attr; hattr.base.next = &tap_log_attr;
......
...@@ -20,8 +20,9 @@ int main(int argc, char *argv[]) ...@@ -20,8 +20,9 @@ int main(int argc, char *argv[])
tdb_off_t off; tdb_off_t off;
uint64_t val, buckets; uint64_t val, buckets;
struct tdb_context *tdb; struct tdb_context *tdb;
int flags[] = { TDB_INTERNAL, TDB_DEFAULT, int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP,
TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT }; TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT,
TDB_NOMMAP|TDB_CONVERT };
plan_tests(sizeof(flags) / sizeof(flags[0]) * 40 + 1); plan_tests(sizeof(flags) / sizeof(flags[0]) * 40 + 1);
......
#include <ccan/tdb2/tdb.c>
#include <ccan/tdb2/free.c>
#include <ccan/tdb2/lock.c>
#include <ccan/tdb2/io.c>
#include <ccan/tdb2/check.c>
#include <ccan/tap/tap.h>
#include "logging.h"
/* We rig the hash so adjacent-numbered records always clash. */
static uint64_t clash(const void *key, size_t len, uint64_t seed, void *priv)
{
return *(unsigned int *)key / 2;
}
static void test_val(struct tdb_context *tdb, unsigned int val)
{
unsigned int v;
struct tdb_data key = { (unsigned char *)&v, sizeof(v) };
struct tdb_data data = { (unsigned char *)&v, sizeof(v) };
/* Insert two entries, with the same hash. */
v = val;
ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
v = val + 1;
ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
ok1(tdb_check(tdb, NULL, NULL) == 0);
/* Can find both? */
v = val;
ok1(tdb_fetch(tdb, key).dsize == data.dsize);
v = val + 1;
ok1(tdb_fetch(tdb, key).dsize == data.dsize);
}
int main(int argc, char *argv[])
{
unsigned int i;
struct tdb_context *tdb;
union tdb_attribute hattr = { .hash = { .base = { TDB_ATTRIBUTE_HASH },
.hash_fn = clash } };
int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP,
TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT,
TDB_NOMMAP|TDB_CONVERT,
};
hattr.base.next = &tap_log_attr;
plan_tests(sizeof(flags) / sizeof(flags[0]) * 14 + 1);
for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
tdb = tdb_open("run-hashclash.tdb", flags[i],
O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr);
ok1(tdb);
if (!tdb)
continue;
/* Check start of hash table. */
test_val(tdb, 0);
ok1(!tdb_has_locks(tdb));
tdb_close(tdb);
tdb = tdb_open("run-hashclash.tdb", flags[i],
O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr);
ok1(tdb);
if (!tdb)
continue;
/* Check end of hash table (will wrap around!). */
test_val(tdb, ((1 << tdb->header.v.hash_bits) - 1) * 2);
ok1(!tdb_has_locks(tdb));
tdb_close(tdb);
}
ok1(tap_log_messages == 0);
return exit_status();
}
...@@ -10,8 +10,9 @@ int main(int argc, char *argv[]) ...@@ -10,8 +10,9 @@ int main(int argc, char *argv[])
{ {
unsigned int i; unsigned int i;
struct tdb_context *tdb; struct tdb_context *tdb;
int flags[] = { TDB_INTERNAL, TDB_DEFAULT, int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP,
TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT }; TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT,
TDB_NOMMAP|TDB_CONVERT };
plan_tests(sizeof(flags) / sizeof(flags[0]) * 2 + 1); plan_tests(sizeof(flags) / sizeof(flags[0]) * 2 + 1);
for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
......
...@@ -13,8 +13,9 @@ int main(int argc, char *argv[]) ...@@ -13,8 +13,9 @@ int main(int argc, char *argv[])
{ {
unsigned int i; unsigned int i;
struct tdb_context *tdb; struct tdb_context *tdb;
int flags[] = { TDB_INTERNAL, TDB_DEFAULT, int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP,
TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT }; TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT,
TDB_NOMMAP|TDB_CONVERT };
struct tdb_data key = { (unsigned char *)"key", 3 }; struct tdb_data key = { (unsigned char *)"key", 3 };
struct tdb_data data; struct tdb_data data;
......
...@@ -10,8 +10,9 @@ int main(int argc, char *argv[]) ...@@ -10,8 +10,9 @@ int main(int argc, char *argv[])
{ {
unsigned int i; unsigned int i;
struct tdb_context *tdb; struct tdb_context *tdb;
int flags[] = { TDB_INTERNAL, TDB_DEFAULT, int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP,
TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT }; TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT,
TDB_NOMMAP|TDB_CONVERT };
struct tdb_data key = { (unsigned char *)"key", 3 }; struct tdb_data key = { (unsigned char *)"key", 3 };
struct tdb_data data = { (unsigned char *)"data", 4 }; struct tdb_data data = { (unsigned char *)"data", 4 };
......
...@@ -10,8 +10,9 @@ int main(int argc, char *argv[]) ...@@ -10,8 +10,9 @@ int main(int argc, char *argv[])
{ {
unsigned int i; unsigned int i;
struct tdb_context *tdb; struct tdb_context *tdb;
int flags[] = { TDB_INTERNAL, TDB_DEFAULT, int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP,
TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT }; TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT,
TDB_NOMMAP|TDB_CONVERT };
struct tdb_data key = { (unsigned char *)"key", 3 }; struct tdb_data key = { (unsigned char *)"key", 3 };
struct tdb_data data = { (unsigned char *)"data", 4 }; struct tdb_data data = { (unsigned char *)"data", 4 };
......
...@@ -10,8 +10,9 @@ int main(int argc, char *argv[]) ...@@ -10,8 +10,9 @@ int main(int argc, char *argv[])
{ {
unsigned int i; unsigned int i;
struct tdb_context *tdb; struct tdb_context *tdb;
int flags[] = { TDB_INTERNAL, TDB_DEFAULT, int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP,
TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT }; TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT,
TDB_NOMMAP|TDB_CONVERT };
struct tdb_data key = { (unsigned char *)"key", 3 }; struct tdb_data key = { (unsigned char *)"key", 3 };
struct tdb_data data = { (unsigned char *)"data", 4 }; struct tdb_data data = { (unsigned char *)"data", 4 };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment