#include "system/filesys.h"
#include "system/glob.h"
#include "util_tdb.h"
+#include "tdb_wrap/tdb_wrap.h"
+#include "../lib/util/memcache.h"
#undef DBGC_CLASS
#define DBGC_CLASS DBGC_TDB
#define BLOB_TYPE "DATA_BLOB"
#define BLOB_TYPE_LEN 9
-static struct tdb_context *cache;
-static struct tdb_context *cache_notrans;
+static struct tdb_wrap *cache;
+static struct tdb_wrap *cache_notrans;
+static int cache_notrans_seqnum;
/**
* @file gencache.c
{
char* cache_fname = NULL;
int open_flags = O_RDWR|O_CREAT;
- bool first_try = true;
/* skip file open if it's already opened */
if (cache) return True;
- cache_fname = lock_path("gencache.tdb");
+ cache_fname = cache_path("gencache.tdb");
+ if (cache_fname == NULL) {
+ return false;
+ }
DEBUG(5, ("Opening cache file at %s\n", cache_fname));
-again:
- cache = tdb_open_log(cache_fname, 0, TDB_DEFAULT|TDB_INCOMPATIBLE_HASH, open_flags, 0644);
+ cache = tdb_wrap_open(NULL, cache_fname, 0,
+ TDB_DEFAULT|TDB_INCOMPATIBLE_HASH,
+ open_flags, 0644);
if (cache) {
int ret;
- ret = tdb_check(cache, NULL, NULL);
+ ret = tdb_check(cache->tdb, NULL, NULL);
if (ret != 0) {
- tdb_close(cache);
- cache = NULL;
- if (!first_try) {
- DEBUG(0, ("gencache_init: tdb_check(%s) failed\n",
- cache_fname));
- return false;
- }
- first_try = false;
- DEBUG(0, ("gencache_init: tdb_check(%s) failed - retry after truncate\n",
- cache_fname));
- truncate(cache_fname, 0);
- goto again;
+ TALLOC_FREE(cache);
+
+ /*
+ * Retry with CLEAR_IF_FIRST.
+ *
+ * Warning: Converting this to dbwrap won't work
+ * directly. gencache.c does transactions on this tdb,
+ * and dbwrap forbids this for CLEAR_IF_FIRST
+ * databases. tdb does allow transactions on
+ * CLEAR_IF_FIRST databases, so lets use it here to
+ * clean up a broken database.
+ */
+ cache = tdb_wrap_open(NULL, cache_fname, 0,
+ TDB_DEFAULT|
+ TDB_INCOMPATIBLE_HASH|
+ TDB_CLEAR_IF_FIRST,
+ open_flags, 0644);
}
}
if (!cache && (errno == EACCES)) {
open_flags = O_RDONLY;
- cache = tdb_open_log(cache_fname, 0, TDB_DEFAULT|TDB_INCOMPATIBLE_HASH, open_flags,
- 0644);
+ cache = tdb_wrap_open(NULL, cache_fname, 0,
+ TDB_DEFAULT|TDB_INCOMPATIBLE_HASH,
+ open_flags, 0644);
if (cache) {
DEBUG(5, ("gencache_init: Opening cache file %s read-only.\n", cache_fname));
}
}
+ TALLOC_FREE(cache_fname);
if (!cache) {
DEBUG(5, ("Attempt to open gencache.tdb has failed.\n"));
}
cache_fname = lock_path("gencache_notrans.tdb");
+ if (cache_fname == NULL) {
+ TALLOC_FREE(cache);
+ return false;
+ }
DEBUG(5, ("Opening cache file at %s\n", cache_fname));
- cache_notrans = tdb_open_log(cache_fname, 0, TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH,
- open_flags, 0644);
+ cache_notrans = tdb_wrap_open(NULL, cache_fname, 0,
+ TDB_CLEAR_IF_FIRST|
+ TDB_INCOMPATIBLE_HASH|
+ TDB_SEQNUM|
+ TDB_NOSYNC|
+ TDB_MUTEX_LOCKING,
+ open_flags, 0644);
if (cache_notrans == NULL) {
DEBUG(5, ("Opening %s failed: %s\n", cache_fname,
strerror(errno)));
- tdb_close(cache);
- cache = NULL;
+ TALLOC_FREE(cache_fname);
+ TALLOC_FREE(cache);
return false;
}
+ TALLOC_FREE(cache_fname);
return True;
}
return result;
}
+struct gencache_have_val_state {
+ time_t new_timeout;
+ const DATA_BLOB *data;
+ bool gotit;
+};
+
+static void gencache_have_val_parser(time_t old_timeout, DATA_BLOB data,
+ void *private_data)
+{
+ struct gencache_have_val_state *state =
+ (struct gencache_have_val_state *)private_data;
+ time_t now = time(NULL);
+ int cache_time_left, new_time_left, additional_time;
+
+ /*
+ * Excuse the many variables, but these time calculations are
+ * confusing to me. We do not want to write to gencache with a
+ * possibly expensive transaction if we are about to write the same
+ * value, just extending the remaining timeout by less than 10%.
+ */
+
+ cache_time_left = old_timeout - now;
+ if (cache_time_left <= 0) {
+ /*
+ * timed out, write new value
+ */
+ return;
+ }
+
+ new_time_left = state->new_timeout - now;
+ if (new_time_left <= 0) {
+ /*
+ * Huh -- no new timeout?? Write it.
+ */
+ return;
+ }
+
+ if (new_time_left < cache_time_left) {
+ /*
+ * Someone wants to shorten the timeout. Let it happen.
+ */
+ return;
+ }
+
+ /*
+ * By how much does the new timeout extend the remaining cache time?
+ */
+ additional_time = new_time_left - cache_time_left;
+
+ if (additional_time * 10 < 0) {
+ /*
+ * Integer overflow. We extend by so much that we have to write it.
+ */
+ return;
+ }
+
+ /*
+ * The comparison below is essentially equivalent to
+ *
+ * new_time_left > cache_time_left * 1.10
+ *
+ * but without floating point calculations.
+ */
+
+ if (additional_time * 10 > cache_time_left) {
+ /*
+ * We extend the cache timeout by more than 10%. Do it.
+ */
+ return;
+ }
+
+ /*
+ * Now the more expensive data compare.
+ */
+ if (data_blob_cmp(state->data, &data) != 0) {
+ /*
+ * Write a new value. Certainly do it.
+ */
+ return;
+ }
+
+ /*
+ * Extending the timeout by less than 10% for the same cache value is
+ * not worth the trouble writing a value into gencache under a
+ * possibly expensive transaction.
+ */
+ state->gotit = true;
+}
+
+static bool gencache_have_val(const char *keystr, const DATA_BLOB *data,
+ time_t timeout)
+{
+ struct gencache_have_val_state state;
+
+ state.new_timeout = timeout;
+ state.data = data;
+ state.gotit = false;
+
+ if (!gencache_parse(keystr, gencache_have_val_parser, &state)) {
+ return false;
+ }
+ return state.gotit;
+}
+
+static int last_stabilize_parser(TDB_DATA key, TDB_DATA data,
+ void *private_data)
+{
+ time_t *last_stabilize = private_data;
+
+ if ((data.dsize != 0) && (data.dptr[data.dsize-1] == '\0')) {
+ *last_stabilize = atoi((char *)data.dptr);
+ }
+ return 0;
+}
+
/**
* Set an entry in the cache file. If there's no such
* one, then add it.
time_t timeout)
{
int ret;
- TDB_DATA databuf;
char* val;
time_t last_stabilize;
static int writecount;
if (!gencache_init()) return False;
+ if (gencache_have_val(keystr, blob, timeout)) {
+ DEBUG(10, ("Did not store value for %s, we already got it\n",
+ keystr));
+ return true;
+ }
+
val = talloc_asprintf(talloc_tos(), CACHE_DATA_FMT, (int)timeout);
if (val == NULL) {
return False;
return false;
}
- DEBUG(10, ("Adding cache entry with key = %s and timeout ="
- " %s (%d seconds %s)\n", keystr, ctime(&timeout),
+ DEBUG(10, ("Adding cache entry with key=[%s] and timeout="
+ "[%s] (%d seconds %s)\n", keystr,
+ timestring(talloc_tos(), timeout),
(int)(timeout - time(NULL)),
timeout > time(NULL) ? "ahead" : "in the past"));
ret = tdb_store_bystring(
- cache_notrans, keystr,
+ cache_notrans->tdb, keystr,
make_tdb_data((uint8_t *)val, talloc_array_length(val)),
0);
TALLOC_FREE(val);
*/
last_stabilize = 0;
- databuf = tdb_fetch_compat(cache_notrans, last_stabilize_key());
- if ((databuf.dptr != NULL)
- && (databuf.dptr[databuf.dsize-1] == '\0')) {
- last_stabilize = atoi((char *)databuf.dptr);
- SAFE_FREE(databuf.dptr);
- }
+
+ tdb_parse_record(cache_notrans->tdb, last_stabilize_key(),
+ last_stabilize_parser, &last_stabilize);
+
if ((last_stabilize
+ lp_parm_int(-1, "gencache", "stabilize_interval", 300))
< time(NULL)) {
if (!gencache_init()) return False;
- DEBUG(10, ("Deleting cache entry (key = %s)\n", keystr));
+ DEBUG(10, ("Deleting cache entry (key=[%s])\n", keystr));
/*
* We delete an element by setting its timeout to 0. This way we don't
* element.
*/
- exists = gencache_get_data_blob(keystr, &value, NULL, &was_expired);
+ exists = gencache_get_data_blob(keystr, NULL, &value, NULL,
+ &was_expired);
if (!exists && was_expired) {
/*
struct gencache_parse_state {
void (*parser)(time_t timeout, DATA_BLOB blob, void *private_data);
void *private_data;
+ bool is_memcache;
};
static int gencache_parse_fn(TDB_DATA key, TDB_DATA data, void *private_data)
blob = data_blob_const(
endptr+1, data.dsize - PTR_DIFF(endptr+1, data.dptr));
state->parser(t, blob, state->private_data);
+
+ if (!state->is_memcache) {
+ memcache_add(NULL, GENCACHE_RAM,
+ data_blob_const(key.dptr, key.dsize),
+ data_blob_const(data.dptr, data.dsize));
+ }
+
return 0;
}
void *private_data)
{
struct gencache_parse_state state;
- TDB_DATA key;
+ TDB_DATA key = string_term_tdb_data(keystr);
+ DATA_BLOB memcache_val;
int ret;
if (keystr == NULL) {
return false;
}
- if (tdb_data_cmp(string_term_tdb_data(keystr),
- last_stabilize_key()) == 0) {
+ if (tdb_data_cmp(key, last_stabilize_key()) == 0) {
return false;
}
if (!gencache_init()) {
return false;
}
- key = string_term_tdb_data(keystr);
state.parser = parser;
state.private_data = private_data;
- ret = tdb_parse_record(cache_notrans, key, gencache_parse_fn, &state);
- if (ret != -1) {
+ if (memcache_lookup(NULL, GENCACHE_RAM,
+ data_blob_const(key.dptr, key.dsize),
+ &memcache_val)) {
+ /*
+ * Make sure that nobody has changed the gencache behind our
+ * back.
+ */
+ int current_seqnum = tdb_get_seqnum(cache_notrans->tdb);
+ if (current_seqnum == cache_notrans_seqnum) {
+ /*
+ * Ok, our memcache is still current, use it without
+ * going to the tdb files.
+ */
+ state.is_memcache = true;
+ gencache_parse_fn(key, make_tdb_data(memcache_val.data,
+ memcache_val.length),
+ &state);
+ return true;
+ }
+ memcache_flush(NULL, GENCACHE_RAM);
+ cache_notrans_seqnum = current_seqnum;
+ }
+
+ state.is_memcache = false;
+
+ ret = tdb_parse_record(cache_notrans->tdb, key,
+ gencache_parse_fn, &state);
+ if (ret == 0) {
return true;
}
- ret = tdb_parse_record(cache, key, gencache_parse_fn, &state);
- return (ret != -1);
+ ret = tdb_parse_record(cache->tdb, key, gencache_parse_fn, &state);
+ return (ret == 0);
}
struct gencache_get_data_blob_state {
+ TALLOC_CTX *mem_ctx;
DATA_BLOB *blob;
time_t timeout;
bool result;
return;
}
- *state->blob = data_blob(blob.data, blob.length);
+ *state->blob = data_blob_talloc(state->mem_ctx, blob.data,
+ blob.length);
if (state->blob->data == NULL) {
state->result = false;
return;
* @retval False for failure
**/
-bool gencache_get_data_blob(const char *keystr, DATA_BLOB *blob,
+bool gencache_get_data_blob(const char *keystr, TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
time_t *timeout, bool *was_expired)
{
struct gencache_get_data_blob_state state;
bool expired = false;
state.result = false;
+ state.mem_ctx = mem_ctx;
state.blob = blob;
if (!gencache_parse(keystr, gencache_get_data_blob_parser, &state)) {
struct stabilize_state {
bool written;
- bool error;
};
static int stabilize_fn(struct tdb_context *tdb, TDB_DATA key, TDB_DATA val,
void *priv);
+static int wipe_fn(struct tdb_context *tdb, TDB_DATA key, TDB_DATA val,
+ void *priv);
+
/**
* Stabilize gencache
*
return false;
}
- res = tdb_transaction_start_nonblock(cache);
+ res = tdb_transaction_start_nonblock(cache->tdb);
if (res != 0) {
-
- if (tdb_error(cache) == TDB_ERR_NOLOCK) {
+ if (tdb_error(cache->tdb) == TDB_ERR_NOLOCK)
+ {
/*
* Someone else already does the stabilize,
* this does not have to be done twice
}
DEBUG(10, ("Could not start transaction on gencache.tdb: "
- "%s\n", tdb_errorstr(cache)));
+ "%s\n", tdb_errorstr_compat(cache->tdb)));
return false;
}
- res = tdb_transaction_start(cache_notrans);
+
+ res = tdb_lockall(cache_notrans->tdb);
if (res != 0) {
- tdb_transaction_cancel(cache);
- DEBUG(10, ("Could not start transaction on "
+ tdb_transaction_cancel(cache->tdb);
+ DEBUG(10, ("Could not get allrecord lock on "
"gencache_notrans.tdb: %s\n",
- tdb_errorstr(cache_notrans)));
+ tdb_errorstr_compat(cache_notrans->tdb)));
return false;
}
- state.error = false;
state.written = false;
- res = tdb_traverse(cache_notrans, stabilize_fn, &state);
- if ((res == -1) || state.error) {
- tdb_transaction_cancel(cache_notrans);
- tdb_transaction_cancel(cache);
+ res = tdb_traverse(cache_notrans->tdb, stabilize_fn, &state);
+ if (res < 0) {
+ tdb_unlockall(cache_notrans->tdb);
+ tdb_transaction_cancel(cache->tdb);
return false;
}
if (!state.written) {
- tdb_transaction_cancel(cache_notrans);
- tdb_transaction_cancel(cache);
+ tdb_unlockall(cache_notrans->tdb);
+ tdb_transaction_cancel(cache->tdb);
return true;
}
- res = tdb_transaction_commit(cache);
+ res = tdb_transaction_commit(cache->tdb);
if (res != 0) {
DEBUG(10, ("tdb_transaction_commit on gencache.tdb failed: "
- "%s\n", tdb_errorstr(cache)));
- tdb_transaction_cancel(cache_notrans);
+ "%s\n", tdb_errorstr_compat(cache->tdb)));
+ tdb_unlockall(cache_notrans->tdb);
+ return false;
+ }
+
+ res = tdb_traverse(cache_notrans->tdb, wipe_fn, NULL);
+ if (res < 0) {
+ DEBUG(10, ("tdb_traverse with wipe_fn on gencache_notrans.tdb "
+ "failed: %s\n",
+ tdb_errorstr_compat(cache_notrans->tdb)));
+ tdb_unlockall(cache_notrans->tdb);
return false;
}
- res = tdb_transaction_commit(cache_notrans);
+ res = tdb_unlockall(cache_notrans->tdb);
if (res != 0) {
- DEBUG(10, ("tdb_transaction_commit on gencache.tdb failed: "
- "%s\n", tdb_errorstr(cache)));
+ DEBUG(10, ("tdb_unlockall on gencache.tdb failed: "
+ "%s\n", tdb_errorstr_compat(cache->tdb)));
return false;
}
now = talloc_asprintf(talloc_tos(), "%d", (int)time(NULL));
if (now != NULL) {
- tdb_store(cache_notrans, last_stabilize_key(),
+ tdb_store(cache_notrans->tdb, last_stabilize_key(),
string_term_tdb_data(now), 0);
TALLOC_FREE(now);
}
return 0;
}
if ((timeout < time(NULL)) || (val.dsize == 0)) {
- res = tdb_delete(cache, key);
- if ((res != 0) && (tdb_error(cache) == TDB_ERR_NOEXIST)) {
- res = 0;
- } else {
+ res = tdb_delete(cache->tdb, key);
+ if (res == 0) {
state->written = true;
+ } else if (tdb_error(cache->tdb) == TDB_ERR_NOEXIST) {
+ res = 0;
}
} else {
- res = tdb_store(cache, key, val, 0);
+ res = tdb_store(cache->tdb, key, val, 0);
if (res == 0) {
state->written = true;
}
if (res != 0) {
DEBUG(10, ("Transfer to gencache.tdb failed: %s\n",
- tdb_errorstr(cache)));
- state->error = true;
+ tdb_errorstr_compat(cache->tdb)));
return -1;
}
- if (tdb_delete(cache_notrans, key) != 0) {
+ return 0;
+}
+
+static int wipe_fn(struct tdb_context *tdb, TDB_DATA key, TDB_DATA val,
+ void *priv)
+{
+ int res;
+ bool ok;
+ time_t timeout;
+
+ res = tdb_data_cmp(key, last_stabilize_key());
+ if (res == 0) {
+ return 0;
+ }
+
+ ok = gencache_pull_timeout((char *)val.dptr, &timeout, NULL);
+ if (!ok) {
+ DEBUG(10, ("Ignoring invalid entry\n"));
+ return 0;
+ }
+
+ res = tdb_delete(tdb, key);
+ if (res != 0) {
DEBUG(10, ("tdb_delete from gencache_notrans.tdb failed: "
- "%s\n", tdb_errorstr(cache_notrans)));
- state->error = true;
+ "%s\n", tdb_errorstr_compat(cache_notrans->tdb)));
return -1;
}
+
return 0;
}
+
/**
* Get existing entry from the cache file.
*
* @retval False for failure
**/
-bool gencache_get(const char *keystr, char **value, time_t *ptimeout)
+bool gencache_get(const char *keystr, TALLOC_CTX *mem_ctx, char **value,
+ time_t *ptimeout)
{
DATA_BLOB blob;
bool ret = False;
- ret = gencache_get_data_blob(keystr, &blob, ptimeout, NULL);
+ ret = gencache_get_data_blob(keystr, mem_ctx, &blob, ptimeout, NULL);
if (!ret) {
return false;
}
if ((blob.data == NULL) || (blob.length == 0)) {
- SAFE_FREE(blob.data);
+ data_blob_free(&blob);
return false;
}
if (blob.data[blob.length-1] != '\0') {
/* Not NULL terminated, can't be a string */
- SAFE_FREE(blob.data);
+ data_blob_free(&blob);
return false;
}
if (value) {
- *value = SMB_STRDUP((char *)blob.data);
- data_blob_free(&blob);
- if (*value == NULL) {
- return false;
- }
+ /*
+ * talloc_move generates a type-punned warning here. As we
+ * leave the function immediately, do a simple talloc_steal.
+ */
+ *value = (char *)talloc_steal(mem_ctx, blob.data);
return true;
}
data_blob_free(&blob);
if (tdb_data_cmp(key, last_stabilize_key()) == 0) {
return 0;
}
- if (state->in_persistent && tdb_exists(cache_notrans, key)) {
+ if (state->in_persistent && tdb_exists(cache_notrans->tdb, key)) {
return 0;
}
keystr = (char *)key.dptr;
} else {
/* ensure 0-termination */
- keystr = SMB_STRNDUP((char *)key.dptr, key.dsize);
+ keystr = talloc_strndup(talloc_tos(), (char *)key.dptr, key.dsize);
free_key = keystr;
+ if (keystr == NULL) {
+ goto done;
+ }
}
if (!gencache_pull_timeout((char *)data.dptr, &timeout, &endptr)) {
goto done;
}
- DEBUG(10, ("Calling function with arguments (key=%s, timeout=%s)\n",
- keystr, ctime(&timeout)));
+ DEBUG(10, ("Calling function with arguments "
+ "(key=[%s], timeout=[%s])\n",
+ keystr, timestring(talloc_tos(), timeout)));
state->fn(keystr,
data_blob_const(endptr,
timeout, state->private_data);
done:
- SAFE_FREE(free_key);
+ TALLOC_FREE(free_key);
return 0;
}
state.private_data = private_data;
state.in_persistent = false;
- tdb_traverse(cache_notrans, gencache_iterate_blobs_fn, &state);
+ tdb_traverse(cache_notrans->tdb, gencache_iterate_blobs_fn, &state);
state.in_persistent = true;
- tdb_traverse(cache, gencache_iterate_blobs_fn, &state);
+ tdb_traverse(cache->tdb, gencache_iterate_blobs_fn, &state);
}
/**
valstr = (char *)value.data;
} else {
/* ensure 0-termination */
- valstr = SMB_STRNDUP((char *)value.data, value.length);
+ valstr = talloc_strndup(talloc_tos(), (char *)value.data, value.length);
free_val = valstr;
+ if (valstr == NULL) {
+ goto done;
+ }
}
DEBUG(10, ("Calling function with arguments "
- "(key = %s, value = %s, timeout = %s)\n",
- key, valstr, ctime(&timeout)));
+ "(key=[%s], value=[%s], timeout=[%s])\n",
+ key, valstr, timestring(talloc_tos(), timeout)));
state->fn(key, valstr, timeout, state->private_data);
- SAFE_FREE(free_val);
+ done:
+
+ TALLOC_FREE(free_val);
}
void gencache_iterate(void (*fn)(const char *key, const char *value,