1
0
mirror of https://github.com/samba-team/samba.git synced 2025-01-12 09:18:10 +03:00
samba-mirror/source3/locking/brlock.c
Gerald Carter df6d2db4ce merge from old APP_HEAD
* remove corrupt tdb and shutdown (only for printing tdbs, connections,
  sessionid & locking)
* decrement smbd counter in connections.tdb in smb_panic()
* various Makefile hack to get things to link

'max smbd processes' looks like it might be broken.  The counter KEY is not
being set.  Will look into that tomorrow.
(This used to be commit 6e22c5da92)
2004-02-12 05:24:02 +00:00

715 lines
18 KiB
C

/*
Unix SMB/CIFS implementation.
byte range locking code
Updated to handle range splits/merges.
Copyright (C) Andrew Tridgell 1992-2000
Copyright (C) Jeremy Allison 1992-2000
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.
*/
/* This module implements a tdb based byte range locking service,
replacing the fcntl() based byte range locking previously
used. This allows us to provide the same semantics as NT */
#include "includes.h"
#define ZERO_ZERO 0
/* This contains elements that differentiate locks. The smbpid is a
client supplied pid, and is essentially the locking context for
this client */
struct lock_context {
uint16 smbpid;
uint16 tid;
pid_t pid;
};
/* The data in brlock records is an unsorted linear array of these
records. It is unnecessary to store the count as tdb provides the
size of the record */
struct lock_struct {
struct lock_context context;
br_off start;
br_off size;
int fnum;
enum brl_type lock_type;
};
/* The key used in the brlock database. */
struct lock_key {
SMB_DEV_T device;
SMB_INO_T inode;
};
/* The open brlock.tdb database. */
static TDB_CONTEXT *tdb;
/****************************************************************************
Create a locking key - ensuring zero filled for pad purposes.
****************************************************************************/
static TDB_DATA locking_key(SMB_DEV_T dev, SMB_INO_T inode)
{
static struct lock_key key;
TDB_DATA kbuf;
memset(&key, '\0', sizeof(key));
key.device = dev;
key.inode = inode;
kbuf.dptr = (char *)&key;
kbuf.dsize = sizeof(key);
return kbuf;
}
/****************************************************************************
See if two locking contexts are equal.
****************************************************************************/
static BOOL brl_same_context(struct lock_context *ctx1,
struct lock_context *ctx2)
{
return (ctx1->pid == ctx2->pid) &&
(ctx1->smbpid == ctx2->smbpid) &&
(ctx1->tid == ctx2->tid);
}
/****************************************************************************
See if lock2 can be added when lock1 is in place.
****************************************************************************/
static BOOL brl_conflict(struct lock_struct *lck1,
struct lock_struct *lck2)
{
if (lck1->lock_type == PENDING_LOCK || lck2->lock_type == PENDING_LOCK )
return False;
if (lck1->lock_type == READ_LOCK && lck2->lock_type == READ_LOCK) {
return False;
}
if (brl_same_context(&lck1->context, &lck2->context) &&
lck2->lock_type == READ_LOCK && lck1->fnum == lck2->fnum) {
return False;
}
if (lck1->start >= (lck2->start + lck2->size) ||
lck2->start >= (lck1->start + lck1->size)) {
return False;
}
return True;
}
#if ZERO_ZERO
static BOOL brl_conflict1(struct lock_struct *lck1,
struct lock_struct *lck2)
{
if (lck1->lock_type == PENDING_LOCK || lck2->lock_type == PENDING_LOCK )
return False;
if (lck1->lock_type == READ_LOCK && lck2->lock_type == READ_LOCK) {
return False;
}
if (brl_same_context(&lck1->context, &lck2->context) &&
lck2->lock_type == READ_LOCK && lck1->fnum == lck2->fnum) {
return False;
}
if (lck2->start == 0 && lck2->size == 0 && lck1->size != 0) {
return True;
}
if (lck1->start >= (lck2->start + lck2->size) ||
lck2->start >= (lck1->start + lck1->size)) {
return False;
}
return True;
}
#endif
/****************************************************************************
Check to see if this lock conflicts, but ignore our own locks on the
same fnum only.
****************************************************************************/
static BOOL brl_conflict_other(struct lock_struct *lck1, struct lock_struct *lck2)
{
if (lck1->lock_type == PENDING_LOCK || lck2->lock_type == PENDING_LOCK )
return False;
if (lck1->lock_type == READ_LOCK && lck2->lock_type == READ_LOCK)
return False;
/*
* Incoming WRITE locks conflict with existing READ locks even
* if the context is the same. JRA. See LOCKTEST7 in smbtorture.
*/
if (!(lck2->lock_type == WRITE_LOCK && lck1->lock_type == READ_LOCK)) {
if (brl_same_context(&lck1->context, &lck2->context) &&
lck1->fnum == lck2->fnum)
return False;
}
if (lck1->start >= (lck2->start + lck2->size) ||
lck2->start >= (lck1->start + lck1->size))
return False;
return True;
}
#if DONT_DO_THIS
/* doing this traversal could kill solaris machines under high load (tridge) */
/* delete any dead locks */
/****************************************************************************
Delete a record if it is for a dead process, if check_self is true, then
delete any records belonging to this pid also (there shouldn't be any).
****************************************************************************/
static int delete_fn(TDB_CONTEXT *ttdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state)
{
struct lock_struct *locks;
int count, i;
BOOL check_self = *(BOOL *)state;
pid_t mypid = sys_getpid();
tdb_chainlock(tdb, kbuf);
locks = (struct lock_struct *)dbuf.dptr;
count = dbuf.dsize / sizeof(*locks);
for (i=0; i<count; i++) {
struct lock_struct *lock = &locks[i];
/* If check_self is true we want to remove our own records. */
if (check_self && (mypid == lock->context.pid)) {
DEBUG(0,("brlock : delete_fn. LOGIC ERROR ! Shutting down and a record for my pid (%u) exists !\n",
(unsigned int)lock->context.pid ));
} else if (process_exists(lock->context.pid)) {
DEBUG(10,("brlock : delete_fn. pid %u exists.\n", (unsigned int)lock->context.pid ));
continue;
}
DEBUG(10,("brlock : delete_fn. Deleting record for process %u\n",
(unsigned int)lock->context.pid ));
if (count > 1 && i < count-1) {
memmove(&locks[i], &locks[i+1],
sizeof(*locks)*((count-1) - i));
}
count--;
i--;
}
if (count == 0) {
tdb_delete(tdb, kbuf);
} else if (count < (dbuf.dsize / sizeof(*locks))) {
dbuf.dsize = count * sizeof(*locks);
tdb_store(tdb, kbuf, dbuf, TDB_REPLACE);
}
tdb_chainunlock(tdb, kbuf);
return 0;
}
#endif
/****************************************************************************
Open up the brlock.tdb database.
****************************************************************************/
void brl_init(int read_only)
{
if (tdb)
return;
tdb = tdb_open_ex(lock_path("brlock.tdb"), 0, TDB_DEFAULT|(read_only?0x0:TDB_CLEAR_IF_FIRST),
read_only?O_RDONLY:(O_RDWR|O_CREAT), 0644, smbd_tdb_log);
if (!tdb) {
DEBUG(0,("Failed to open byte range locking database\n"));
return;
}
#if DONT_DO_THIS
/* doing this traversal could kill solaris machines under high load (tridge) */
/* delete any dead locks */
if (!read_only) {
BOOL check_self = False;
tdb_traverse(tdb, delete_fn, &check_self);
}
#endif
}
/****************************************************************************
Close down the brlock.tdb database.
****************************************************************************/
void brl_shutdown(int read_only)
{
if (!tdb)
return;
#if DONT_DO_THIS
/* doing this traversal could kill solaris machines under high load (tridge) */
/* delete any dead locks */
if (!read_only) {
BOOL check_self = True;
tdb_traverse(tdb, delete_fn, &check_self);
}
#endif
tdb_close(tdb);
}
#if ZERO_ZERO
/****************************************************************************
compare two locks for sorting
****************************************************************************/
static int lock_compare(struct lock_struct *lck1,
struct lock_struct *lck2)
{
if (lck1->start != lck2->start) return (lck1->start - lck2->start);
if (lck2->size != lck1->size) {
return ((int)lck1->size - (int)lck2->size);
}
return 0;
}
#endif
/****************************************************************************
Lock a range of bytes.
****************************************************************************/
NTSTATUS brl_lock(SMB_DEV_T dev, SMB_INO_T ino, int fnum,
uint16 smbpid, pid_t pid, uint16 tid,
br_off start, br_off size,
enum brl_type lock_type, BOOL *my_lock_ctx)
{
TDB_DATA kbuf, dbuf;
int count, i;
struct lock_struct lock, *locks;
char *tp;
NTSTATUS status = NT_STATUS_OK;
static int last_failed = -1;
static br_off last_failed_start;
*my_lock_ctx = False;
kbuf = locking_key(dev,ino);
dbuf.dptr = NULL;
#if !ZERO_ZERO
if (start == 0 && size == 0) {
DEBUG(0,("client sent 0/0 lock - please report this\n"));
}
#endif
tdb_chainlock(tdb, kbuf);
dbuf = tdb_fetch(tdb, kbuf);
lock.context.smbpid = smbpid;
lock.context.pid = pid;
lock.context.tid = tid;
lock.start = start;
lock.size = size;
lock.fnum = fnum;
lock.lock_type = lock_type;
if (dbuf.dptr) {
/* there are existing locks - make sure they don't conflict */
locks = (struct lock_struct *)dbuf.dptr;
count = dbuf.dsize / sizeof(*locks);
for (i=0; i<count; i++) {
if (brl_conflict(&locks[i], &lock)) {
status = NT_STATUS_LOCK_NOT_GRANTED;
/* Did we block ourselves ? */
if (brl_same_context(&locks[i].context, &lock.context))
*my_lock_ctx = True;
goto fail;
}
#if ZERO_ZERO
if (lock.start == 0 && lock.size == 0 &&
locks[i].size == 0) {
break;
}
#endif
}
}
/* no conflicts - add it to the list of locks */
tp = Realloc(dbuf.dptr, dbuf.dsize + sizeof(*locks));
if (!tp) {
status = NT_STATUS_NO_MEMORY;
goto fail;
} else {
dbuf.dptr = tp;
}
memcpy(dbuf.dptr + dbuf.dsize, &lock, sizeof(lock));
dbuf.dsize += sizeof(lock);
#if ZERO_ZERO
/* sort the lock list */
qsort(dbuf.dptr, dbuf.dsize/sizeof(lock), sizeof(lock), lock_compare);
#endif
tdb_store(tdb, kbuf, dbuf, TDB_REPLACE);
SAFE_FREE(dbuf.dptr);
tdb_chainunlock(tdb, kbuf);
return NT_STATUS_OK;
fail:
/* this is a nasty hack to try to simulate the lock result cache code in w2k.
It isn't completely accurate as I haven't yet worked out the correct
semantics (tridge)
*/
if (last_failed == fnum &&
last_failed_start == start &&
NT_STATUS_EQUAL(status, NT_STATUS_LOCK_NOT_GRANTED)) {
status = NT_STATUS_FILE_LOCK_CONFLICT;
}
last_failed = fnum;
last_failed_start = start;
SAFE_FREE(dbuf.dptr);
tdb_chainunlock(tdb, kbuf);
return status;
}
/****************************************************************************
Check if an unlock overlaps a pending lock.
****************************************************************************/
static BOOL brl_pending_overlap(struct lock_struct *lock, struct lock_struct *pend_lock)
{
if ((lock->start <= pend_lock->start) && (lock->start + lock->size > pend_lock->start))
return True;
if ((lock->start >= pend_lock->start) && (lock->start <= pend_lock->start + pend_lock->size))
return True;
return False;
}
/****************************************************************************
Unlock a range of bytes.
****************************************************************************/
BOOL brl_unlock(SMB_DEV_T dev, SMB_INO_T ino, int fnum,
uint16 smbpid, pid_t pid, uint16 tid,
br_off start, br_off size,
BOOL remove_pending_locks_only,
void (*pre_unlock_fn)(void *),
void *pre_unlock_data)
{
TDB_DATA kbuf, dbuf;
int count, i, j;
struct lock_struct *locks;
struct lock_context context;
kbuf = locking_key(dev,ino);
dbuf.dptr = NULL;
tdb_chainlock(tdb, kbuf);
dbuf = tdb_fetch(tdb, kbuf);
if (!dbuf.dptr) {
DEBUG(10,("brl_unlock: tdb_fetch failed !\n"));
goto fail;
}
context.smbpid = smbpid;
context.pid = pid;
context.tid = tid;
/* there are existing locks - find a match */
locks = (struct lock_struct *)dbuf.dptr;
count = dbuf.dsize / sizeof(*locks);
#if ZERO_ZERO
for (i=0; i<count; i++) {
struct lock_struct *lock = &locks[i];
if (lock->lock_type == WRITE_LOCK &&
brl_same_context(&lock->context, &context) &&
lock->fnum == fnum &&
lock->start == start &&
lock->size == size) {
if (pre_unlock_fn)
(*pre_unlock_fn)(pre_unlock_data);
/* found it - delete it */
if (count == 1) {
tdb_delete(tdb, kbuf);
} else {
if (i < count-1) {
memmove(&locks[i], &locks[i+1],
sizeof(*locks)*((count-1) - i));
}
dbuf.dsize -= sizeof(*locks);
tdb_store(tdb, kbuf, dbuf, TDB_REPLACE);
}
SAFE_FREE(dbuf.dptr);
tdb_chainunlock(tdb, kbuf);
return True;
}
}
#endif
locks = (struct lock_struct *)dbuf.dptr;
count = dbuf.dsize / sizeof(*locks);
for (i=0; i<count; i++) {
struct lock_struct *lock = &locks[i];
if (brl_same_context(&lock->context, &context) &&
lock->fnum == fnum &&
lock->start == start &&
lock->size == size) {
if (remove_pending_locks_only && lock->lock_type != PENDING_LOCK)
continue;
if (lock->lock_type != PENDING_LOCK) {
/* Do any POSIX unlocks needed. */
if (pre_unlock_fn)
(*pre_unlock_fn)(pre_unlock_data);
/* Send unlock messages to any pending waiters that overlap. */
for (j=0; j<count; j++) {
struct lock_struct *pend_lock = &locks[j];
/* Ignore non-pending locks. */
if (pend_lock->lock_type != PENDING_LOCK)
continue;
/* We could send specific lock info here... */
if (brl_pending_overlap(lock, pend_lock)) {
DEBUG(10,("brl_unlock: sending unlock message to pid %u\n",
(unsigned int)pend_lock->context.pid ));
message_send_pid(pend_lock->context.pid,
MSG_SMB_UNLOCK,
NULL, 0, True);
}
}
}
/* found it - delete it */
if (count == 1) {
tdb_delete(tdb, kbuf);
} else {
if (i < count-1) {
memmove(&locks[i], &locks[i+1],
sizeof(*locks)*((count-1) - i));
}
dbuf.dsize -= sizeof(*locks);
tdb_store(tdb, kbuf, dbuf, TDB_REPLACE);
}
SAFE_FREE(dbuf.dptr);
tdb_chainunlock(tdb, kbuf);
return True;
}
}
/* we didn't find it */
fail:
SAFE_FREE(dbuf.dptr);
tdb_chainunlock(tdb, kbuf);
return False;
}
/****************************************************************************
Test if we could add a lock if we wanted to.
****************************************************************************/
BOOL brl_locktest(SMB_DEV_T dev, SMB_INO_T ino, int fnum,
uint16 smbpid, pid_t pid, uint16 tid,
br_off start, br_off size,
enum brl_type lock_type, int check_self)
{
TDB_DATA kbuf, dbuf;
int count, i;
struct lock_struct lock, *locks;
kbuf = locking_key(dev,ino);
dbuf.dptr = NULL;
tdb_chainlock(tdb, kbuf);
dbuf = tdb_fetch(tdb, kbuf);
lock.context.smbpid = smbpid;
lock.context.pid = pid;
lock.context.tid = tid;
lock.start = start;
lock.size = size;
lock.fnum = fnum;
lock.lock_type = lock_type;
if (dbuf.dptr) {
/* there are existing locks - make sure they don't conflict */
locks = (struct lock_struct *)dbuf.dptr;
count = dbuf.dsize / sizeof(*locks);
for (i=0; i<count; i++) {
if (check_self) {
if (brl_conflict(&locks[i], &lock))
goto fail;
} else {
/*
* Our own locks don't conflict.
*/
if (brl_conflict_other(&locks[i], &lock))
goto fail;
}
}
}
/* no conflicts - we could have added it */
SAFE_FREE(dbuf.dptr);
tdb_chainunlock(tdb, kbuf);
return True;
fail:
SAFE_FREE(dbuf.dptr);
tdb_chainunlock(tdb, kbuf);
return False;
}
/****************************************************************************
Remove any locks associated with a open file.
****************************************************************************/
void brl_close(SMB_DEV_T dev, SMB_INO_T ino, pid_t pid, int tid, int fnum)
{
TDB_DATA kbuf, dbuf;
int count, i, j, dcount=0;
struct lock_struct *locks;
kbuf = locking_key(dev,ino);
dbuf.dptr = NULL;
tdb_chainlock(tdb, kbuf);
dbuf = tdb_fetch(tdb, kbuf);
if (!dbuf.dptr) goto fail;
/* there are existing locks - remove any for this fnum */
locks = (struct lock_struct *)dbuf.dptr;
count = dbuf.dsize / sizeof(*locks);
for (i=0; i<count; i++) {
struct lock_struct *lock = &locks[i];
if (lock->context.tid == tid &&
lock->context.pid == pid &&
lock->fnum == fnum) {
/* Send unlock messages to any pending waiters that overlap. */
for (j=0; j<count; j++) {
struct lock_struct *pend_lock = &locks[j];
/* Ignore our own or non-pending locks. */
if (pend_lock->lock_type != PENDING_LOCK)
continue;
if (pend_lock->context.tid == tid &&
pend_lock->context.pid == pid &&
pend_lock->fnum == fnum)
continue;
/* We could send specific lock info here... */
if (brl_pending_overlap(lock, pend_lock))
message_send_pid(pend_lock->context.pid,
MSG_SMB_UNLOCK,
NULL, 0, True);
}
/* found it - delete it */
if (count > 1 && i < count-1) {
memmove(&locks[i], &locks[i+1],
sizeof(*locks)*((count-1) - i));
}
count--;
i--;
dcount++;
}
}
if (count == 0) {
tdb_delete(tdb, kbuf);
} else if (count < (dbuf.dsize / sizeof(*locks))) {
dbuf.dsize -= dcount * sizeof(*locks);
tdb_store(tdb, kbuf, dbuf, TDB_REPLACE);
}
/* we didn't find it */
fail:
SAFE_FREE(dbuf.dptr);
tdb_chainunlock(tdb, kbuf);
}
/****************************************************************************
Traverse the whole database with this function, calling traverse_callback
on each lock.
****************************************************************************/
static int traverse_fn(TDB_CONTEXT *ttdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state)
{
struct lock_struct *locks;
struct lock_key *key;
int i;
BRLOCK_FN(traverse_callback) = (BRLOCK_FN_CAST())state;
locks = (struct lock_struct *)dbuf.dptr;
key = (struct lock_key *)kbuf.dptr;
for (i=0;i<dbuf.dsize/sizeof(*locks);i++) {
traverse_callback(key->device, key->inode,
locks[i].context.pid,
locks[i].lock_type,
locks[i].start,
locks[i].size);
}
return 0;
}
/*******************************************************************
Call the specified function on each lock in the database.
********************************************************************/
int brl_forall(BRLOCK_FN(fn))
{
if (!tdb) return 0;
return tdb_traverse(tdb, traverse_fn, (void *)fn);
}