mirror of
https://github.com/samba-team/samba.git
synced 2024-12-25 23:21:54 +03:00
cd96271036
TDB2 doesn't have (the racy) signal pointer; the new method is to override the locking callbacks and do the timeout internally. The technique here is to invalidate the struct flock when the timeout occurs, so it works even if it happens before we enter the fcntl() call. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
660 lines
16 KiB
C
660 lines
16 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
tdb utility functions
|
|
Copyright (C) Andrew Tridgell 1992-1998
|
|
Copyright (C) Rafal Szczesniak 2002
|
|
Copyright (C) Michael Adam 2007
|
|
|
|
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 "system/filesys.h"
|
|
#include "util_tdb.h"
|
|
|
|
#undef malloc
|
|
#undef realloc
|
|
#undef calloc
|
|
#undef strdup
|
|
|
|
#ifdef BUILD_TDB2
|
|
static struct flock flock_struct;
|
|
|
|
/* Return a value which is none of v1, v2 or v3. */
|
|
static inline short int invalid_value(short int v1, short int v2, short int v3)
|
|
{
|
|
short int try = (v1+v2+v3)^((v1+v2+v3) << 16);
|
|
while (try == v1 || try == v2 || try == v3)
|
|
try++;
|
|
return try;
|
|
}
|
|
|
|
/* We invalidate in as many ways as we can, so the OS rejects it */
|
|
static void invalidate_flock_struct(int signum)
|
|
{
|
|
flock_struct.l_type = invalid_value(F_RDLCK, F_WRLCK, F_UNLCK);
|
|
flock_struct.l_whence = invalid_value(SEEK_SET, SEEK_CUR, SEEK_END);
|
|
flock_struct.l_start = -1;
|
|
/* A large negative. */
|
|
flock_struct.l_len = (((off_t)1 << (sizeof(off_t)*CHAR_BIT - 1)) + 1);
|
|
}
|
|
|
|
static int timeout_lock(int fd, int rw, off_t off, off_t len, bool waitflag,
|
|
void *_timeout)
|
|
{
|
|
int ret, saved_errno;
|
|
unsigned int timeout = *(unsigned int *)_timeout;
|
|
|
|
flock_struct.l_type = rw;
|
|
flock_struct.l_whence = SEEK_SET;
|
|
flock_struct.l_start = off;
|
|
flock_struct.l_len = len;
|
|
|
|
CatchSignal(SIGALRM, invalidate_flock_struct);
|
|
alarm(timeout);
|
|
|
|
for (;;) {
|
|
if (waitflag)
|
|
ret = fcntl(fd, F_SETLKW, &flock_struct);
|
|
else
|
|
ret = fcntl(fd, F_SETLK, &flock_struct);
|
|
|
|
if (ret == 0)
|
|
break;
|
|
|
|
/* Not signalled? Something else went wrong. */
|
|
if (flock_struct.l_len == len) {
|
|
if (errno == EAGAIN || errno == EINTR)
|
|
continue;
|
|
saved_errno = errno;
|
|
break;
|
|
} else {
|
|
saved_errno = EINTR;
|
|
break;
|
|
}
|
|
}
|
|
|
|
alarm(0);
|
|
errno = saved_errno;
|
|
return ret;
|
|
}
|
|
|
|
static int tdb_chainlock_with_timeout_internal(struct tdb_context *tdb,
|
|
TDB_DATA key,
|
|
unsigned int timeout,
|
|
int rw_type)
|
|
{
|
|
union tdb_attribute locking;
|
|
enum TDB_ERROR ecode;
|
|
|
|
if (timeout) {
|
|
locking.base.attr = TDB_ATTRIBUTE_FLOCK;
|
|
ecode = tdb_get_attribute(tdb, &locking);
|
|
if (ecode != TDB_SUCCESS)
|
|
return ecode;
|
|
|
|
/* Replace locking function with our own. */
|
|
locking.flock.data = &timeout;
|
|
locking.flock.lock = timeout_lock;
|
|
|
|
ecode = tdb_set_attribute(tdb, &locking);
|
|
if (ecode != TDB_SUCCESS)
|
|
return ecode;
|
|
}
|
|
if (rw_type == F_RDLCK)
|
|
ecode = tdb_chainlock_read(tdb, key);
|
|
else
|
|
ecode = tdb_chainlock(tdb, key);
|
|
|
|
if (timeout) {
|
|
tdb_unset_attribute(tdb, TDB_ATTRIBUTE_FLOCK);
|
|
}
|
|
return ecode == TDB_SUCCESS ? 0 : -1;
|
|
}
|
|
#else
|
|
/* these are little tdb utility functions that are meant to make
|
|
dealing with a tdb database a little less cumbersome in Samba */
|
|
|
|
static SIG_ATOMIC_T gotalarm;
|
|
|
|
/***************************************************************
|
|
Signal function to tell us we timed out.
|
|
****************************************************************/
|
|
|
|
static void gotalarm_sig(int signum)
|
|
{
|
|
gotalarm = 1;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Lock a chain with timeout (in seconds).
|
|
****************************************************************************/
|
|
|
|
static int tdb_chainlock_with_timeout_internal( TDB_CONTEXT *tdb, TDB_DATA key, unsigned int timeout, int rw_type)
|
|
{
|
|
/* Allow tdb_chainlock to be interrupted by an alarm. */
|
|
int ret;
|
|
gotalarm = 0;
|
|
|
|
if (timeout) {
|
|
CatchSignal(SIGALRM, gotalarm_sig);
|
|
tdb_setalarm_sigptr(tdb, &gotalarm);
|
|
alarm(timeout);
|
|
}
|
|
|
|
if (rw_type == F_RDLCK)
|
|
ret = tdb_chainlock_read(tdb, key);
|
|
else
|
|
ret = tdb_chainlock(tdb, key);
|
|
|
|
if (timeout) {
|
|
alarm(0);
|
|
tdb_setalarm_sigptr(tdb, NULL);
|
|
CatchSignal(SIGALRM, SIG_IGN);
|
|
if (gotalarm && (ret != 0)) {
|
|
DEBUG(0,("tdb_chainlock_with_timeout_internal: alarm (%u) timed out for key %s in tdb %s\n",
|
|
timeout, key.dptr, tdb_name(tdb)));
|
|
/* TODO: If we time out waiting for a lock, it might
|
|
* be nice to use F_GETLK to get the pid of the
|
|
* process currently holding the lock and print that
|
|
* as part of the debugging message. -- mbp */
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return ret == 0 ? 0 : -1;
|
|
}
|
|
#endif /* TDB1 */
|
|
|
|
/****************************************************************************
|
|
Write lock a chain. Return non-zero if timeout or lock failed.
|
|
****************************************************************************/
|
|
|
|
int tdb_chainlock_with_timeout( TDB_CONTEXT *tdb, TDB_DATA key, unsigned int timeout)
|
|
{
|
|
return tdb_chainlock_with_timeout_internal(tdb, key, timeout, F_WRLCK);
|
|
}
|
|
|
|
int tdb_lock_bystring_with_timeout(TDB_CONTEXT *tdb, const char *keyval,
|
|
int timeout)
|
|
{
|
|
TDB_DATA key = string_term_tdb_data(keyval);
|
|
|
|
return tdb_chainlock_with_timeout(tdb, key, timeout);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Read lock a chain by string. Return non-zero if timeout or lock failed.
|
|
****************************************************************************/
|
|
|
|
int tdb_read_lock_bystring_with_timeout(TDB_CONTEXT *tdb, const char *keyval, unsigned int timeout)
|
|
{
|
|
TDB_DATA key = string_term_tdb_data(keyval);
|
|
|
|
return tdb_chainlock_with_timeout_internal(tdb, key, timeout, F_RDLCK);
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Useful pair of routines for packing/unpacking data consisting of
|
|
integers and strings.
|
|
****************************************************************************/
|
|
|
|
static size_t tdb_pack_va(uint8 *buf, int bufsize, const char *fmt, va_list ap)
|
|
{
|
|
uint8 bt;
|
|
uint16 w;
|
|
uint32 d;
|
|
int i;
|
|
void *p;
|
|
int len;
|
|
char *s;
|
|
char c;
|
|
uint8 *buf0 = buf;
|
|
const char *fmt0 = fmt;
|
|
int bufsize0 = bufsize;
|
|
|
|
while (*fmt) {
|
|
switch ((c = *fmt++)) {
|
|
case 'b': /* unsigned 8-bit integer */
|
|
len = 1;
|
|
bt = (uint8)va_arg(ap, int);
|
|
if (bufsize && bufsize >= len)
|
|
SSVAL(buf, 0, bt);
|
|
break;
|
|
case 'w': /* unsigned 16-bit integer */
|
|
len = 2;
|
|
w = (uint16)va_arg(ap, int);
|
|
if (bufsize && bufsize >= len)
|
|
SSVAL(buf, 0, w);
|
|
break;
|
|
case 'd': /* signed 32-bit integer (standard int in most systems) */
|
|
len = 4;
|
|
d = va_arg(ap, uint32);
|
|
if (bufsize && bufsize >= len)
|
|
SIVAL(buf, 0, d);
|
|
break;
|
|
case 'p': /* pointer */
|
|
len = 4;
|
|
p = va_arg(ap, void *);
|
|
d = p?1:0;
|
|
if (bufsize && bufsize >= len)
|
|
SIVAL(buf, 0, d);
|
|
break;
|
|
case 'P': /* null-terminated string */
|
|
s = va_arg(ap,char *);
|
|
w = strlen(s);
|
|
len = w + 1;
|
|
if (bufsize && bufsize >= len)
|
|
memcpy(buf, s, len);
|
|
break;
|
|
case 'f': /* null-terminated string */
|
|
s = va_arg(ap,char *);
|
|
w = strlen(s);
|
|
len = w + 1;
|
|
if (bufsize && bufsize >= len)
|
|
memcpy(buf, s, len);
|
|
break;
|
|
case 'B': /* fixed-length string */
|
|
i = va_arg(ap, int);
|
|
s = va_arg(ap, char *);
|
|
len = 4+i;
|
|
if (bufsize && bufsize >= len) {
|
|
SIVAL(buf, 0, i);
|
|
memcpy(buf+4, s, i);
|
|
}
|
|
break;
|
|
default:
|
|
DEBUG(0,("Unknown tdb_pack format %c in %s\n",
|
|
c, fmt));
|
|
len = 0;
|
|
break;
|
|
}
|
|
|
|
buf += len;
|
|
if (bufsize)
|
|
bufsize -= len;
|
|
if (bufsize < 0)
|
|
bufsize = 0;
|
|
}
|
|
|
|
DEBUG(18,("tdb_pack_va(%s, %d) -> %d\n",
|
|
fmt0, bufsize0, (int)PTR_DIFF(buf, buf0)));
|
|
|
|
return PTR_DIFF(buf, buf0);
|
|
}
|
|
|
|
size_t tdb_pack(uint8 *buf, int bufsize, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
size_t result;
|
|
|
|
va_start(ap, fmt);
|
|
result = tdb_pack_va(buf, bufsize, fmt, ap);
|
|
va_end(ap);
|
|
return result;
|
|
}
|
|
|
|
bool tdb_pack_append(TALLOC_CTX *mem_ctx, uint8 **buf, size_t *len,
|
|
const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
size_t len1, len2;
|
|
|
|
va_start(ap, fmt);
|
|
len1 = tdb_pack_va(NULL, 0, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (mem_ctx != NULL) {
|
|
*buf = talloc_realloc(mem_ctx, *buf, uint8,
|
|
(*len) + len1);
|
|
} else {
|
|
*buf = SMB_REALLOC_ARRAY(*buf, uint8, (*len) + len1);
|
|
}
|
|
|
|
if (*buf == NULL) {
|
|
return False;
|
|
}
|
|
|
|
va_start(ap, fmt);
|
|
len2 = tdb_pack_va((*buf)+(*len), len1, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (len1 != len2) {
|
|
return False;
|
|
}
|
|
|
|
*len += len2;
|
|
|
|
return True;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Useful pair of routines for packing/unpacking data consisting of
|
|
integers and strings.
|
|
****************************************************************************/
|
|
|
|
int tdb_unpack(const uint8 *buf, int bufsize, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
uint8 *bt;
|
|
uint16 *w;
|
|
uint32 *d;
|
|
int len;
|
|
int *i;
|
|
void **p;
|
|
char *s, **b, **ps;
|
|
char c;
|
|
const uint8 *buf0 = buf;
|
|
const char *fmt0 = fmt;
|
|
int bufsize0 = bufsize;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
while (*fmt) {
|
|
switch ((c=*fmt++)) {
|
|
case 'b': /* unsigned 8-bit integer */
|
|
len = 1;
|
|
bt = va_arg(ap, uint8 *);
|
|
if (bufsize < len)
|
|
goto no_space;
|
|
*bt = SVAL(buf, 0);
|
|
break;
|
|
case 'w': /* unsigned 16-bit integer */
|
|
len = 2;
|
|
w = va_arg(ap, uint16 *);
|
|
if (bufsize < len)
|
|
goto no_space;
|
|
*w = SVAL(buf, 0);
|
|
break;
|
|
case 'd': /* signed 32-bit integer (standard int in most systems) */
|
|
len = 4;
|
|
d = va_arg(ap, uint32 *);
|
|
if (bufsize < len)
|
|
goto no_space;
|
|
*d = IVAL(buf, 0);
|
|
break;
|
|
case 'p': /* pointer */
|
|
len = 4;
|
|
p = va_arg(ap, void **);
|
|
if (bufsize < len)
|
|
goto no_space;
|
|
/*
|
|
* This isn't a real pointer - only a token (1 or 0)
|
|
* to mark the fact a pointer is present.
|
|
*/
|
|
|
|
*p = (void *)(IVAL(buf, 0) ? (void *)1 : NULL);
|
|
break;
|
|
case 'P': /* null-terminated string */
|
|
/* Return malloc'ed string. */
|
|
ps = va_arg(ap,char **);
|
|
len = strlen((const char *)buf) + 1;
|
|
*ps = SMB_STRDUP((const char *)buf);
|
|
break;
|
|
case 'f': /* null-terminated string */
|
|
s = va_arg(ap,char *);
|
|
len = strlen((const char *)buf) + 1;
|
|
if (bufsize < len || len > sizeof(fstring))
|
|
goto no_space;
|
|
memcpy(s, buf, len);
|
|
break;
|
|
case 'B': /* fixed-length string */
|
|
i = va_arg(ap, int *);
|
|
b = va_arg(ap, char **);
|
|
len = 4;
|
|
if (bufsize < len)
|
|
goto no_space;
|
|
*i = IVAL(buf, 0);
|
|
if (! *i) {
|
|
*b = NULL;
|
|
break;
|
|
}
|
|
len += *i;
|
|
if (bufsize < len)
|
|
goto no_space;
|
|
*b = (char *)SMB_MALLOC(*i);
|
|
if (! *b)
|
|
goto no_space;
|
|
memcpy(*b, buf+4, *i);
|
|
break;
|
|
default:
|
|
DEBUG(0,("Unknown tdb_unpack format %c in %s\n",
|
|
c, fmt));
|
|
|
|
len = 0;
|
|
break;
|
|
}
|
|
|
|
buf += len;
|
|
bufsize -= len;
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
DEBUG(18,("tdb_unpack(%s, %d) -> %d\n",
|
|
fmt0, bufsize0, (int)PTR_DIFF(buf, buf0)));
|
|
|
|
return PTR_DIFF(buf, buf0);
|
|
|
|
no_space:
|
|
va_end(ap);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
Log tdb messages via DEBUG().
|
|
****************************************************************************/
|
|
|
|
#ifdef BUILD_TDB2
|
|
static void tdb_log(TDB_CONTEXT *tdb, enum tdb_log_level level,
|
|
const char *message, void *unused)
|
|
{
|
|
DEBUG((int)level, ("tdb(%s): %s",
|
|
tdb_name(tdb) ? tdb_name(tdb) : "unnamed", message));
|
|
}
|
|
#else
|
|
static void tdb_log(TDB_CONTEXT *tdb, enum tdb_debug_level level, const char *format, ...)
|
|
{
|
|
va_list ap;
|
|
char *ptr = NULL;
|
|
int ret;
|
|
|
|
va_start(ap, format);
|
|
ret = vasprintf(&ptr, format, ap);
|
|
va_end(ap);
|
|
|
|
if ((ret == -1) || !*ptr)
|
|
return;
|
|
|
|
DEBUG((int)level, ("tdb(%s): %s", tdb_name(tdb) ? tdb_name(tdb) : "unnamed", ptr));
|
|
SAFE_FREE(ptr);
|
|
}
|
|
#endif /* TDB1 */
|
|
|
|
/****************************************************************************
|
|
Like tdb_open() but also setup a logging function that redirects to
|
|
the samba DEBUG() system.
|
|
****************************************************************************/
|
|
|
|
TDB_CONTEXT *tdb_open_log(const char *name, int hash_size, int tdb_flags,
|
|
int open_flags, mode_t mode)
|
|
{
|
|
TDB_CONTEXT *tdb;
|
|
|
|
if (!lp_use_mmap())
|
|
tdb_flags |= TDB_NOMMAP;
|
|
|
|
if ((hash_size == 0) && (name != NULL)) {
|
|
const char *base = strrchr_m(name, '/');
|
|
if (base != NULL) {
|
|
base += 1;
|
|
}
|
|
else {
|
|
base = name;
|
|
}
|
|
hash_size = lp_parm_int(-1, "tdb_hashsize", base, 0);
|
|
}
|
|
|
|
tdb = tdb_open_compat(name, hash_size, tdb_flags,
|
|
open_flags, mode, tdb_log, NULL);
|
|
if (!tdb)
|
|
return NULL;
|
|
|
|
return tdb;
|
|
}
|
|
|
|
/****************************************************************************
|
|
tdb_store, wrapped in a transaction. This way we make sure that a process
|
|
that dies within writing does not leave a corrupt tdb behind.
|
|
****************************************************************************/
|
|
|
|
int tdb_trans_store(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
|
|
int flag)
|
|
{
|
|
int res;
|
|
|
|
if ((res = tdb_transaction_start(tdb)) != 0) {
|
|
DEBUG(5, ("tdb_transaction_start failed\n"));
|
|
return res;
|
|
}
|
|
|
|
if ((res = tdb_store(tdb, key, dbuf, flag)) != 0) {
|
|
DEBUG(10, ("tdb_store failed\n"));
|
|
tdb_transaction_cancel(tdb);
|
|
return res;
|
|
}
|
|
|
|
if ((res = tdb_transaction_commit(tdb)) != 0) {
|
|
DEBUG(5, ("tdb_transaction_commit failed\n"));
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/****************************************************************************
|
|
tdb_delete, wrapped in a transaction. This way we make sure that a process
|
|
that dies within deleting does not leave a corrupt tdb behind.
|
|
****************************************************************************/
|
|
|
|
int tdb_trans_delete(struct tdb_context *tdb, TDB_DATA key)
|
|
{
|
|
int res;
|
|
|
|
if ((res = tdb_transaction_start(tdb)) != 0) {
|
|
DEBUG(5, ("tdb_transaction_start failed\n"));
|
|
return res;
|
|
}
|
|
|
|
if ((res = tdb_delete(tdb, key)) != 0) {
|
|
DEBUG(10, ("tdb_delete failed\n"));
|
|
tdb_transaction_cancel(tdb);
|
|
return res;
|
|
}
|
|
|
|
if ((res = tdb_transaction_commit(tdb)) != 0) {
|
|
DEBUG(5, ("tdb_transaction_commit failed\n"));
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
NTSTATUS map_nt_error_from_tdb(enum TDB_ERROR err)
|
|
{
|
|
NTSTATUS result = NT_STATUS_INTERNAL_ERROR;
|
|
|
|
switch (err) {
|
|
case TDB_SUCCESS:
|
|
result = NT_STATUS_OK;
|
|
break;
|
|
case TDB_ERR_CORRUPT:
|
|
result = NT_STATUS_INTERNAL_DB_CORRUPTION;
|
|
break;
|
|
case TDB_ERR_IO:
|
|
result = NT_STATUS_UNEXPECTED_IO_ERROR;
|
|
break;
|
|
case TDB_ERR_OOM:
|
|
result = NT_STATUS_NO_MEMORY;
|
|
break;
|
|
case TDB_ERR_EXISTS:
|
|
result = NT_STATUS_OBJECT_NAME_COLLISION;
|
|
break;
|
|
|
|
case TDB_ERR_LOCK:
|
|
/*
|
|
* 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.
|
|
*/
|
|
result = NT_STATUS_FILE_LOCK_CONFLICT;
|
|
break;
|
|
|
|
#ifndef BUILD_TDB2
|
|
case TDB_ERR_NOLOCK:
|
|
case TDB_ERR_LOCK_TIMEOUT:
|
|
/*
|
|
* These two ones in the enum are not actually used
|
|
*/
|
|
result = NT_STATUS_FILE_LOCK_CONFLICT;
|
|
break;
|
|
#endif
|
|
case TDB_ERR_NOEXIST:
|
|
result = NT_STATUS_NOT_FOUND;
|
|
break;
|
|
case TDB_ERR_EINVAL:
|
|
result = NT_STATUS_INVALID_PARAMETER;
|
|
break;
|
|
case TDB_ERR_RDONLY:
|
|
result = NT_STATUS_ACCESS_DENIED;
|
|
break;
|
|
#ifndef BUILD_TDB2
|
|
case TDB_ERR_NESTING:
|
|
result = NT_STATUS_INTERNAL_ERROR;
|
|
break;
|
|
#endif
|
|
};
|
|
return result;
|
|
}
|
|
|
|
int tdb_data_cmp(TDB_DATA t1, TDB_DATA t2)
|
|
{
|
|
int ret;
|
|
if (t1.dptr == NULL && t2.dptr != NULL) {
|
|
return -1;
|
|
}
|
|
if (t1.dptr != NULL && t2.dptr == NULL) {
|
|
return 1;
|
|
}
|
|
if (t1.dptr == t2.dptr) {
|
|
return t1.dsize - t2.dsize;
|
|
}
|
|
ret = memcmp(t1.dptr, t2.dptr, MIN(t1.dsize, t2.dsize));
|
|
if (ret == 0) {
|
|
return t1.dsize - t2.dsize;
|
|
}
|
|
return ret;
|
|
}
|