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
+ 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,
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.
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "includes.h"
#undef calloc
#undef strdup
-/***************************************************************
- Allow a caller to set a "alarm" flag that tdb can check to abort
- a blocking lock on SIGALRM.
-***************************************************************/
-
-static sig_atomic_t *palarm_fired;
-
-static void tdb_set_lock_alarm(sig_atomic_t *palarm)
-{
- palarm_fired = palarm;
-}
-
/* these are little tdb utility functions that are meant to make
dealing with a tdb database a little less cumbersome in Samba */
Make a TDB_DATA and keep the const warning in one place
****************************************************************/
-TDB_DATA make_tdb_data(const char *dptr, size_t dsize)
+TDB_DATA make_tdb_data(const uint8 *dptr, size_t dsize)
{
TDB_DATA ret;
- ret.dptr = CONST_DISCARD(char *, dptr);
+ ret.dptr = CONST_DISCARD(uint8 *, dptr);
ret.dsize = dsize;
return ret;
}
TDB_DATA string_tdb_data(const char *string)
{
- return make_tdb_data(string, strlen(string));
+ return make_tdb_data((const uint8 *)string, string ? strlen(string) : 0 );
+}
+
+TDB_DATA string_term_tdb_data(const char *string)
+{
+ return make_tdb_data((const uint8 *)string, string ? strlen(string) + 1 : 0);
}
/****************************************************************************
/* Allow tdb_chainlock to be interrupted by an alarm. */
int ret;
gotalarm = 0;
- tdb_set_lock_alarm(CONST_DISCARD(sig_atomic_t *, &gotalarm));
if (timeout) {
CatchSignal(SIGALRM, SIGNAL_CAST gotalarm_sig);
int tdb_lock_bystring(TDB_CONTEXT *tdb, const char *keyval)
{
- TDB_DATA key = make_tdb_data(keyval, strlen(keyval)+1);
+ TDB_DATA key = string_term_tdb_data(keyval);
return tdb_chainlock(tdb, key);
}
int tdb_lock_bystring_with_timeout(TDB_CONTEXT *tdb, const char *keyval,
int timeout)
{
- TDB_DATA key = make_tdb_data(keyval, strlen(keyval)+1);
+ TDB_DATA key = string_term_tdb_data(keyval);
return tdb_chainlock_with_timeout(tdb, key, timeout);
}
void tdb_unlock_bystring(TDB_CONTEXT *tdb, const char *keyval)
{
- TDB_DATA key = make_tdb_data(keyval, strlen(keyval)+1);
+ TDB_DATA key = string_term_tdb_data(keyval);
tdb_chainunlock(tdb, key);
}
int tdb_read_lock_bystring_with_timeout(TDB_CONTEXT *tdb, const char *keyval, unsigned int timeout)
{
- TDB_DATA key = make_tdb_data(keyval, strlen(keyval)+1);
+ TDB_DATA key = string_term_tdb_data(keyval);
return tdb_chainlock_with_timeout_internal(tdb, key, timeout, F_RDLCK);
}
void tdb_read_unlock_bystring(TDB_CONTEXT *tdb, const char *keyval)
{
- TDB_DATA key = make_tdb_data(keyval, strlen(keyval)+1);
+ TDB_DATA key = string_term_tdb_data(keyval);
tdb_chainunlock_read(tdb, key);
}
Output is int32 in native byte order.
****************************************************************************/
-int32 tdb_fetch_int32_byblob(TDB_CONTEXT *tdb, const char *keyval, size_t len)
+int32 tdb_fetch_int32_byblob(TDB_CONTEXT *tdb, TDB_DATA key)
{
- TDB_DATA key = make_tdb_data(keyval, len);
TDB_DATA data;
int32 ret;
int32 tdb_fetch_int32(TDB_CONTEXT *tdb, const char *keystr)
{
- return tdb_fetch_int32_byblob(tdb, keystr, strlen(keystr) + 1);
+ TDB_DATA key = string_term_tdb_data(keystr);
+
+ return tdb_fetch_int32_byblob(tdb, key);
}
/****************************************************************************
Input is int32 in native byte order. Output in tdb is in little-endian.
****************************************************************************/
-int tdb_store_int32_byblob(TDB_CONTEXT *tdb, const char *keystr, size_t len, int32 v)
+int tdb_store_int32_byblob(TDB_CONTEXT *tdb, TDB_DATA key, int32 v)
{
- TDB_DATA key = make_tdb_data(keystr, len);
TDB_DATA data;
int32 v_store;
SIVAL(&v_store,0,v);
- data.dptr = (char *)&v_store;
+ data.dptr = (uint8 *)&v_store;
data.dsize = sizeof(int32);
return tdb_store(tdb, key, data, TDB_REPLACE);
int tdb_store_int32(TDB_CONTEXT *tdb, const char *keystr, int32 v)
{
- return tdb_store_int32_byblob(tdb, keystr, strlen(keystr) + 1, v);
+ TDB_DATA key = string_term_tdb_data(keystr);
+
+ return tdb_store_int32_byblob(tdb, key, v);
}
/****************************************************************************
Output is uint32 in native byte order.
****************************************************************************/
-BOOL tdb_fetch_uint32_byblob(TDB_CONTEXT *tdb, const char *keyval, size_t len, uint32 *value)
+BOOL tdb_fetch_uint32_byblob(TDB_CONTEXT *tdb, TDB_DATA key, uint32 *value)
{
- TDB_DATA key = make_tdb_data(keyval, len);
TDB_DATA data;
data = tdb_fetch(tdb, key);
BOOL tdb_fetch_uint32(TDB_CONTEXT *tdb, const char *keystr, uint32 *value)
{
- return tdb_fetch_uint32_byblob(tdb, keystr, strlen(keystr) + 1, value);
+ TDB_DATA key = string_term_tdb_data(keystr);
+
+ return tdb_fetch_uint32_byblob(tdb, key, value);
}
/****************************************************************************
Input is uint32 in native byte order. Output in tdb is in little-endian.
****************************************************************************/
-BOOL tdb_store_uint32_byblob(TDB_CONTEXT *tdb, const char *keystr, size_t len, uint32 value)
+BOOL tdb_store_uint32_byblob(TDB_CONTEXT *tdb, TDB_DATA key, uint32 value)
{
- TDB_DATA key = make_tdb_data(keystr, len);
TDB_DATA data;
uint32 v_store;
BOOL ret = True;
SIVAL(&v_store, 0, value);
- data.dptr = (char *)&v_store;
+ data.dptr = (uint8 *)&v_store;
data.dsize = sizeof(uint32);
if (tdb_store(tdb, key, data, TDB_REPLACE) == -1)
BOOL tdb_store_uint32(TDB_CONTEXT *tdb, const char *keystr, uint32 value)
{
- return tdb_store_uint32_byblob(tdb, keystr, strlen(keystr) + 1, value);
+ TDB_DATA key = string_term_tdb_data(keystr);
+
+ return tdb_store_uint32_byblob(tdb, key, value);
}
/****************************************************************************
Store a buffer by a null terminated string key. Return 0 on success, -1
int tdb_store_bystring(TDB_CONTEXT *tdb, const char *keystr, TDB_DATA data, int flags)
{
- TDB_DATA key = make_tdb_data(keystr, strlen(keystr)+1);
-
+ TDB_DATA key = string_term_tdb_data(keystr);
+
return tdb_store(tdb, key, data, flags);
}
+int tdb_trans_store_bystring(TDB_CONTEXT *tdb, const char *keystr,
+ TDB_DATA data, int flags)
+{
+ TDB_DATA key = string_term_tdb_data(keystr);
+
+ return tdb_trans_store(tdb, key, data, flags);
+}
+
/****************************************************************************
Fetch a buffer using a null terminated string key. Don't forget to call
free() on the result dptr.
TDB_DATA tdb_fetch_bystring(TDB_CONTEXT *tdb, const char *keystr)
{
- TDB_DATA key = make_tdb_data(keystr, strlen(keystr)+1);
+ TDB_DATA key = string_term_tdb_data(keystr);
return tdb_fetch(tdb, key);
}
int tdb_delete_bystring(TDB_CONTEXT *tdb, const char *keystr)
{
- TDB_DATA key = make_tdb_data(keystr, strlen(keystr)+1);
+ TDB_DATA key = string_term_tdb_data(keystr);
return tdb_delete(tdb, key);
}
integers and strings.
****************************************************************************/
-size_t tdb_pack_va(char *buf, int bufsize, const char *fmt, va_list ap)
+size_t tdb_pack_va(uint8 *buf, int bufsize, const char *fmt, va_list ap)
{
uint8 bt;
uint16 w;
int len;
char *s;
char c;
- char *buf0 = buf;
+ uint8 *buf0 = buf;
const char *fmt0 = fmt;
int bufsize0 = bufsize;
return PTR_DIFF(buf, buf0);
}
-size_t tdb_pack(char *buf, int bufsize, const char *fmt, ...)
+size_t tdb_pack(uint8 *buf, int bufsize, const char *fmt, ...)
{
va_list ap;
size_t result;
}
va_start(ap, fmt);
- len2 = tdb_pack_va((char *)(*buf)+(*len), len1, fmt, ap);
+ len2 = tdb_pack_va((*buf)+(*len), len1, fmt, ap);
va_end(ap);
if (len1 != len2) {
integers and strings.
****************************************************************************/
-int tdb_unpack(char *buf, int bufsize, const char *fmt, ...)
+int tdb_unpack(const uint8 *buf, int bufsize, const char *fmt, ...)
{
va_list ap;
uint8 *bt;
void **p;
char *s, **b;
char c;
- char *buf0 = buf;
+ const uint8 *buf0 = buf;
const char *fmt0 = fmt;
int bufsize0 = bufsize;
break;
case 'P':
s = va_arg(ap,char *);
- len = strlen(buf) + 1;
+ len = strlen((const char *)buf) + 1;
if (bufsize < len || len > sizeof(pstring))
goto no_space;
memcpy(s, buf, len);
break;
case 'f':
s = va_arg(ap,char *);
- len = strlen(buf) + 1;
+ len = strlen((const char *)buf) + 1;
if (bufsize < len || len > sizeof(fstring))
goto no_space;
memcpy(s, buf, len);
for (key = tdb_firstkey(tdb); key.dptr; key = next) {
/* duplicate key string to ensure null-termination */
- char *key_str = (char*) SMB_STRNDUP(key.dptr, key.dsize);
+ char *key_str = SMB_STRNDUP((const char *)key.dptr, key.dsize);
if (!key_str) {
DEBUG(0, ("tdb_search_keys: strndup() failed!\n"));
smb_panic("strndup failed!\n");
if ((res = tdb_store(tdb, key, dbuf, flag)) != 0) {
DEBUG(10, ("tdb_store failed\n"));
if (tdb_transaction_cancel(tdb) != 0) {
- smb_panic("Cancelling transaction failed\n");
+ smb_panic("Cancelling transaction failed");
}
return res;
}
if ((res = tdb_delete(tdb, key)) != 0) {
DEBUG(10, ("tdb_delete failed\n"));
if (tdb_transaction_cancel(tdb) != 0) {
- smb_panic("Cancelling transaction failed\n");
+ smb_panic("Cancelling transaction failed");
}
return res;
}
return res;
}
+
+/*
+ Log tdb messages via DEBUG().
+*/
+static void tdb_wrap_log(TDB_CONTEXT *tdb, enum tdb_debug_level level,
+ const char *format, ...) PRINTF_ATTRIBUTE(3,4);
+
+static void tdb_wrap_log(TDB_CONTEXT *tdb, enum tdb_debug_level level,
+ const char *format, ...)
+{
+ va_list ap;
+ char *ptr = NULL;
+ int debuglevel = 0;
+
+ va_start(ap, format);
+ vasprintf(&ptr, format, ap);
+ va_end(ap);
+
+ switch (level) {
+ case TDB_DEBUG_FATAL:
+ debug_level = 0;
+ break;
+ case TDB_DEBUG_ERROR:
+ debuglevel = 1;
+ break;
+ case TDB_DEBUG_WARNING:
+ debuglevel = 2;
+ break;
+ case TDB_DEBUG_TRACE:
+ debuglevel = 5;
+ break;
+ default:
+ debuglevel = 0;
+ }
+
+ if (ptr != NULL) {
+ const char *name = tdb_name(tdb);
+ DEBUG(debuglevel, ("tdb(%s): %s", name ? name : "unnamed", ptr));
+ free(ptr);
+ }
+}
+
+static struct tdb_wrap *tdb_list;
+
+/* destroy the last connection to a tdb */
+static int tdb_wrap_destructor(struct tdb_wrap *w)
+{
+ tdb_close(w->tdb);
+ DLIST_REMOVE(tdb_list, w);
+ return 0;
+}
+
+/*
+ wrapped connection to a tdb database
+ to close just talloc_free() the tdb_wrap pointer
+ */
+struct tdb_wrap *tdb_wrap_open(TALLOC_CTX *mem_ctx,
+ const char *name, int hash_size, int tdb_flags,
+ int open_flags, mode_t mode)
+{
+ struct tdb_wrap *w;
+ struct tdb_logging_context log_ctx;
+ log_ctx.log_fn = tdb_wrap_log;
+
+ if (!lp_use_mmap())
+ tdb_flags |= TDB_NOMMAP;
+
+ for (w=tdb_list;w;w=w->next) {
+ if (strcmp(name, w->name) == 0) {
+ /*
+ * Yes, talloc_reference is exactly what we want
+ * here. Otherwise we would have to implement our own
+ * reference counting.
+ */
+ return talloc_reference(mem_ctx, w);
+ }
+ }
+
+ w = talloc(mem_ctx, struct tdb_wrap);
+ if (w == NULL) {
+ return NULL;
+ }
+
+ if (!(w->name = talloc_strdup(w, name))) {
+ talloc_free(w);
+ return NULL;
+ }
+
+ w->tdb = tdb_open_ex(name, hash_size, tdb_flags,
+ open_flags, mode, &log_ctx, NULL);
+ if (w->tdb == NULL) {
+ talloc_free(w);
+ return NULL;
+ }
+
+ talloc_set_destructor(w, tdb_wrap_destructor);
+
+ DLIST_ADD(tdb_list, w);
+
+ return w;
+}
+
+NTSTATUS map_nt_error_from_tdb(enum TDB_ERROR err)
+{
+ struct { enum TDB_ERROR err; NTSTATUS status; } map[] =
+ { { TDB_SUCCESS, NT_STATUS_OK },
+ { TDB_ERR_CORRUPT, NT_STATUS_INTERNAL_DB_CORRUPTION },
+ { TDB_ERR_IO, NT_STATUS_UNEXPECTED_IO_ERROR },
+ { TDB_ERR_OOM, NT_STATUS_NO_MEMORY },
+ { TDB_ERR_EXISTS, NT_STATUS_OBJECT_NAME_COLLISION },
+
+ /*
+ * TDB_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.
+ */
+ { TDB_ERR_LOCK, NT_STATUS_FILE_LOCK_CONFLICT },
+ /*
+ * The next two ones in the enum are not actually used
+ */
+ { TDB_ERR_NOLOCK, NT_STATUS_FILE_LOCK_CONFLICT },
+ { TDB_ERR_LOCK_TIMEOUT, NT_STATUS_FILE_LOCK_CONFLICT },
+ { TDB_ERR_NOEXIST, NT_STATUS_NOT_FOUND },
+ { TDB_ERR_EINVAL, NT_STATUS_INVALID_PARAMETER },
+ { TDB_ERR_RDONLY, NT_STATUS_ACCESS_DENIED }
+ };
+
+ int i;
+
+ for (i=0; i < sizeof(map) / sizeof(map[0]); i++) {
+ if (err == map[i].err) {
+ return map[i].status;
+ }
+ }
+
+ return NT_STATUS_INTERNAL_ERROR;
+}
+
+
+/*********************************************************************
+ * the following is a generic validation mechanism for tdbs.
+ *********************************************************************/
+
+/*
+ * internal validation function, executed by the child.
+ */
+static int tdb_validate_child(const char *tdb_path,
+ tdb_validate_data_func validate_fn,
+ int pfd)
+{
+ int ret = -1;
+ int num_entries = 0;
+ TDB_CONTEXT *tdb = NULL;
+ struct tdb_validation_status v_status;
+
+ v_status.tdb_error = False;
+ v_status.bad_freelist = False;
+ v_status.bad_entry = False;
+ v_status.unknown_key = False;
+ v_status.success = True;
+
+ tdb = tdb_open_log(tdb_path, 0, TDB_DEFAULT, O_RDONLY, 0);
+ if (!tdb) {
+ v_status.tdb_error = True;
+ v_status.success = False;
+ goto out;
+ }
+
+ /* Check the cache freelist is good. */
+ if (tdb_validate_freelist(tdb, &num_entries) == -1) {
+ DEBUG(0,("tdb_validate_child: bad freelist in cache %s\n",
+ tdb_path));
+ v_status.bad_freelist = True;
+ v_status.success = False;
+ goto out;
+ }
+
+ DEBUG(10,("tdb_validate_child: cache %s freelist has %d entries\n",
+ tdb_path, num_entries));
+
+ /* Now traverse the cache to validate it. */
+ num_entries = tdb_traverse(tdb, validate_fn, (void *)&v_status);
+ if (num_entries == -1 || !(v_status.success)) {
+ DEBUG(0,("tdb_validate_child: cache %s traverse failed\n",
+ tdb_path));
+ if (!(v_status.success)) {
+ if (v_status.bad_entry) {
+ DEBUGADD(0, (" -> bad entry found\n"));
+ }
+ if (v_status.unknown_key) {
+ DEBUGADD(0, (" -> unknown key encountered\n"));
+ }
+ }
+ goto out;
+ }
+
+ DEBUG(10,("tdb_validate_child: cache %s is good "
+ "with %d entries\n", tdb_path, num_entries));
+ ret = 0; /* Cache is good. */
+
+out:
+ if (tdb) {
+ tdb_close(tdb);
+ }
+
+ DEBUG(10, ("tdb_validate_child: writing status to pipe\n"));
+ write (pfd, (const char *)&v_status, sizeof(v_status));
+ close(pfd);
+
+ return ret;
+}
+
+int tdb_validate(const char *tdb_path, tdb_validate_data_func validate_fn)
+{
+ pid_t child_pid = -1;
+ int child_status = 0;
+ int wait_pid = 0;
+ int ret = -1;
+ int pipe_fds[2];
+ struct tdb_validation_status v_status;
+ int bytes_read = 0;
+
+ /* fork and let the child do the validation.
+ * benefit: no need to twist signal handlers and panic functions.
+ * just let the child panic. we catch the signal.
+ * communicate the extended status struct over a pipe. */
+
+ if (pipe(pipe_fds) != 0) {
+ DEBUG(0, ("tdb_validate: unable to create pipe, "
+ "error %s", strerror(errno)));
+ smb_panic("winbind_validate_cache: unable to create pipe.");
+ }
+
+ DEBUG(10, ("tdb_validate: forking to let child do validation.\n"));
+ child_pid = sys_fork();
+ if (child_pid == 0) {
+ DEBUG(10, ("tdb_validate (validation child): created\n"));
+ close(pipe_fds[0]); /* close reading fd */
+ DEBUG(10, ("tdb_validate (validation child): "
+ "calling tdb_validate_child\n"));
+ exit(tdb_validate_child(tdb_path, validate_fn, pipe_fds[1]));
+ }
+ else if (child_pid < 0) {
+ smb_panic("tdb_validate: fork for validation failed.");
+ }
+
+ /* parent */
+
+ DEBUG(10, ("tdb_validate: fork succeeded, child PID = %d\n",
+ child_pid));
+ close(pipe_fds[1]); /* close writing fd */
+
+ v_status.success = True;
+ v_status.bad_entry = False;
+ v_status.unknown_key = False;
+
+ DEBUG(10, ("tdb_validate: reading from pipe.\n"));
+ bytes_read = read(pipe_fds[0], (void *)&v_status, sizeof(v_status));
+ close(pipe_fds[0]);
+
+ if (bytes_read != sizeof(v_status)) {
+ DEBUG(10, ("tdb_validate: read %d bytes from pipe "
+ "but expected %d", bytes_read, (int)sizeof(v_status)));
+ DEBUGADD(10, (" -> assuming child crashed\n"));
+ v_status.success = False;
+ }
+ else {
+ DEBUG(10, ("tdb_validate: read status from child\n"));
+ DEBUGADD(10, (" * tdb error: %s\n", v_status.tdb_error ? "yes" : "no"));
+ DEBUGADD(10, (" * bad freelist: %s\n", v_status.bad_freelist ? "yes" : "no"));
+ DEBUGADD(10, (" * bad entry: %s\n", v_status.bad_entry ? "yes" : "no"));
+ DEBUGADD(10, (" * unknown key: %s\n", v_status.unknown_key ? "yes" : "no"));
+ DEBUGADD(10, (" => overall success: %s\n", v_status.success ? "yes" : "no"));
+ }
+
+ DEBUG(10, ("tdb_validate: waiting for child to finish...\n"));
+ while ((wait_pid = sys_waitpid(child_pid, &child_status, 0)) < 0) {
+ if (errno == EINTR) {
+ DEBUG(10, ("tdb_validate: got signal during "
+ "waitpid, retrying\n"));
+ errno = 0;
+ continue;
+ }
+ DEBUG(0, ("tdb_validate: waitpid failed with "
+ "errno %s\n", strerror(errno)));
+ smb_panic("tdb_validate: waitpid failed.");
+ }
+ if (wait_pid != child_pid) {
+ DEBUG(0, ("tdb_validate: waitpid returned pid %d, "
+ "but %d was expexted\n", wait_pid, child_pid));
+ smb_panic("tdb_validate: waitpid returned "
+ "unexpected PID.");
+ }
+
+ DEBUG(10, ("tdb_validate: validating child returned.\n"));
+ if (WIFEXITED(child_status)) {
+ DEBUG(10, ("tdb_validate: child exited, code %d.\n",
+ WEXITSTATUS(child_status)));
+ ret = WEXITSTATUS(child_status);
+ }
+ if (WIFSIGNALED(child_status)) {
+ DEBUG(10, ("tdb_validate: child terminated "
+ "by signal %d\n", WTERMSIG(child_status)));
+#ifdef WCOREDUMP
+ if (WCOREDUMP(child_status)) {
+ DEBUGADD(10, ("core dumped\n"));
+ }
+#endif
+ ret = WTERMSIG(child_status);
+ }
+ if (WIFSTOPPED(child_status)) {
+ DEBUG(10, ("tdb_validate: child was stopped "
+ "by signal %d\n",
+ WSTOPSIG(child_status)));
+ ret = WSTOPSIG(child_status);
+ }
+
+ return ret;
+}