diff options
Diffstat (limited to 'source4/locking/brlock.c')
-rw-r--r-- | source4/locking/brlock.c | 698 |
1 files changed, 698 insertions, 0 deletions
diff --git a/source4/locking/brlock.c b/source4/locking/brlock.c new file mode 100644 index 0000000000..4cd885f1a6 --- /dev/null +++ b/source4/locking/brlock.c @@ -0,0 +1,698 @@ +/* + 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_log(lock_path("brlock.tdb"), 0, TDB_DEFAULT|(read_only?0x0:TDB_CLEAR_IF_FIRST), + read_only?O_RDONLY:(O_RDWR|O_CREAT), 0644); + 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) +{ + 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; + + 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; + 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) +{ + 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) { + /* 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) { + /* 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); +} |