summaryrefslogtreecommitdiff
path: root/source4/ntvfs/posix/pvfs_lock.c
diff options
context:
space:
mode:
authorAndrew Tridgell <tridge@samba.org>2004-10-18 07:40:17 +0000
committerGerald (Jerry) Carter <jerry@samba.org>2007-10-10 13:00:00 -0500
commitd0cc571e30bf49443ac7d1b1a0b896ee72d7d9a6 (patch)
treea0c43b9894da40ab6b78cdad8a6a6964d8067fe7 /source4/ntvfs/posix/pvfs_lock.c
parentd37acd0fe71923987a68377bf01040eaed245d9f (diff)
downloadsamba-d0cc571e30bf49443ac7d1b1a0b896ee72d7d9a6.tar.gz
samba-d0cc571e30bf49443ac7d1b1a0b896ee72d7d9a6.tar.bz2
samba-d0cc571e30bf49443ac7d1b1a0b896ee72d7d9a6.zip
r3029: implemented byte range lock timeouts.
This adds a pvfs_wait_message() routine which uses the new messaging system, event timers and talloc destructors to give a nice generic async event handling system with a easy to use interface. The extensions to pvfs_lock.c are based on calls to pvfs_wait_message() routines. We now pass all of our smbtorture locking tests, although while writing this code I have thought of some additonal tests that should be added, particularly for lock cancel operations. I'll work on that soon. This commit also extends the smbtorture lock tests to test the rather weird 0xEEFFFFFF locking semantics that I have discovered in win2003. Win2003 treats the 0xEEFFFFFF boundary as special, and will give different error codes on either side of it. Locks on both sides are allowed, the only difference is which error code is given when a lock is denied. Anyone like to hazard a guess as to why? It has me stumped. (This used to be commit 4395c0557ab175d6a8dd99df03c266325949ffa5)
Diffstat (limited to 'source4/ntvfs/posix/pvfs_lock.c')
-rw-r--r--source4/ntvfs/posix/pvfs_lock.c186
1 files changed, 182 insertions, 4 deletions
diff --git a/source4/ntvfs/posix/pvfs_lock.c b/source4/ntvfs/posix/pvfs_lock.c
index d7aca9df8b..548c5bd82c 100644
--- a/source4/ntvfs/posix/pvfs_lock.c
+++ b/source4/ntvfs/posix/pvfs_lock.c
@@ -44,6 +44,153 @@ NTSTATUS pvfs_check_lock(struct pvfs_state *pvfs,
offset, count, rw);
}
+/* this state structure holds information about a lock we are waiting on */
+struct pending_state {
+ struct pvfs_state *pvfs;
+ union smb_lock *lck;
+ struct pvfs_file *f;
+ struct smbsrv_request *req;
+ int pending_lock;
+ void *wait_handle;
+ time_t end_time;
+};
+
+
+/*
+ a secondary attempt to setup a lock has failed - back out
+ the locks we did get and send an error
+*/
+static void pvfs_lock_async_failed(struct pvfs_state *pvfs,
+ struct smbsrv_request *req,
+ struct pvfs_file *f,
+ struct smb_lock_entry *locks,
+ int i,
+ NTSTATUS status)
+{
+ /* undo the locks we just did */
+ for (i=i-1;i>=0;i--) {
+ brl_unlock(pvfs->brl_context,
+ &f->locking_key,
+ locks[i].pid,
+ f->fnum,
+ locks[i].offset,
+ locks[i].count);
+ }
+ req->async.status = status;
+ req->async.send_fn(req);
+}
+
+
+/*
+ called when we receive a pending lock notification. It means that
+ either our lock timed out or somoene else has unlocked a overlapping
+ range, so we should try the lock again. Note that on timeout we
+ do retry the lock, giving it a last chance.
+*/
+static void pvfs_pending_lock_continue(void *private, BOOL timed_out)
+{
+ struct pending_state *pending = private;
+ struct pvfs_state *pvfs = pending->pvfs;
+ struct pvfs_file *f = pending->f;
+ struct smbsrv_request *req = pending->req;
+ union smb_lock *lck = pending->lck;
+ struct smb_lock_entry *locks;
+ enum brl_type rw;
+ NTSTATUS status;
+ int i;
+
+ locks = lck->lockx.in.locks + lck->lockx.in.ulock_cnt;
+
+ if (lck->lockx.in.mode & LOCKING_ANDX_SHARED_LOCK) {
+ rw = READ_LOCK;
+ } else {
+ rw = WRITE_LOCK;
+ }
+
+ status = brl_lock(pvfs->brl_context,
+ &f->locking_key,
+ req->smbpid,
+ f->fnum,
+ locks[pending->pending_lock].offset,
+ locks[pending->pending_lock].count,
+ rw, NULL);
+
+ /* if we have failed and timed out, or succeeded, then we
+ don't need the pending lock any more */
+ if (NT_STATUS_IS_OK(status) || timed_out) {
+ NTSTATUS status2;
+ status2 = brl_remove_pending(pvfs->brl_context, &f->locking_key, pending);
+ if (!NT_STATUS_IS_OK(status2)) {
+ DEBUG(0,("pvfs_lock: failed to remove pending lock - %s\n", nt_errstr(status2)));
+ }
+ talloc_free(pending->wait_handle);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (timed_out) {
+ /* no more chances */
+ pvfs_lock_async_failed(pvfs, req, f, locks, pending->pending_lock, status);
+ }
+ /* we can try again */
+ return;
+ }
+
+ /* if we haven't timed out yet, then we can do more pending locks */
+ if (timed_out) {
+ pending = NULL;
+ } else {
+ if (rw == READ_LOCK) {
+ rw = PENDING_READ_LOCK;
+ } else {
+ rw = PENDING_WRITE_LOCK;
+ }
+ }
+
+ /* we've now got the pending lock. try and get the rest, which might
+ lead to more pending locks */
+ for (i=pending->pending_lock;i<lck->lockx.in.lock_cnt;i++) {
+ if (pending) {
+ pending->pending_lock = i;
+ }
+
+ status = brl_lock(pvfs->brl_context,
+ &f->locking_key,
+ req->smbpid,
+ f->fnum,
+ locks[i].offset,
+ locks[i].count,
+ rw, pending);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (pending) {
+ /* a timed lock failed - setup a wait message to handle
+ the pending lock notification or a timeout */
+ pending->wait_handle = pvfs_wait_message(pvfs, req, MSG_BRL_RETRY,
+ pending->end_time,
+ pvfs_pending_lock_continue,
+ pending);
+ if (pending->wait_handle == NULL) {
+ pvfs_lock_async_failed(pvfs, req, f, locks, i, NT_STATUS_NO_MEMORY);
+ }
+ return;
+ }
+ pvfs_lock_async_failed(pvfs, req, f, locks, i, status);
+ return;
+ }
+ }
+
+ brl_unlock(pvfs->brl_context,
+ &f->locking_key,
+ req->smbpid,
+ f->fnum,
+ lck->lock.in.offset,
+ lck->lock.in.count);
+
+ /* we've managed to get all the locks. Tell the client */
+ req->async.status = NT_STATUS_OK;
+ req->async.send_fn(req);
+}
+
+
/*
lock or unlock a byte range
*/
@@ -55,6 +202,7 @@ NTSTATUS pvfs_lock(struct ntvfs_module_context *ntvfs,
struct smb_lock_entry *locks;
int i;
enum brl_type rw;
+ struct pending_state *pending = NULL;
f = pvfs_find_fd(pvfs, req, lck->generic.in.fnum);
if (!f) {
@@ -69,7 +217,7 @@ NTSTATUS pvfs_lock(struct ntvfs_module_context *ntvfs,
f->fnum,
lck->lock.in.offset,
lck->lock.in.count,
- WRITE_LOCK);
+ WRITE_LOCK, NULL);
case RAW_LOCK_UNLOCK:
return brl_unlock(pvfs->brl_context,
@@ -88,11 +236,25 @@ NTSTATUS pvfs_lock(struct ntvfs_module_context *ntvfs,
}
/* now the lockingX case, most common and also most complex */
+ if (lck->lockx.in.timeout != 0) {
+ pending = talloc_p(req, struct pending_state);
+ if (pending == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pending->pvfs = pvfs;
+ pending->lck = lck;
+ pending->f = f;
+ pending->req = req;
+
+ /* round up to the nearest second */
+ pending->end_time = time(NULL) + ((lck->lockx.in.timeout+999)/1000);
+ }
if (lck->lockx.in.mode & LOCKING_ANDX_SHARED_LOCK) {
- rw = READ_LOCK;
+ rw = pending? PENDING_READ_LOCK : READ_LOCK;
} else {
- rw = WRITE_LOCK;
+ rw = pending? PENDING_WRITE_LOCK : WRITE_LOCK;
}
if (lck->lockx.in.mode &
@@ -125,14 +287,30 @@ NTSTATUS pvfs_lock(struct ntvfs_module_context *ntvfs,
for (i=0;i<lck->lockx.in.lock_cnt;i++) {
NTSTATUS status;
+ if (pending) {
+ pending->pending_lock = i;
+ }
+
status = brl_lock(pvfs->brl_context,
&f->locking_key,
locks[i].pid,
f->fnum,
locks[i].offset,
locks[i].count,
- rw);
+ rw, pending);
if (!NT_STATUS_IS_OK(status)) {
+ if (pending) {
+ /* a timed lock failed - setup a wait message to handle
+ the pending lock notification or a timeout */
+ pending->wait_handle = pvfs_wait_message(pvfs, req, MSG_BRL_RETRY,
+ pending->end_time,
+ pvfs_pending_lock_continue,
+ pending);
+ if (pending->wait_handle == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+ }
/* undo the locks we just did */
for (i=i-1;i>=0;i--) {
brl_unlock(pvfs->brl_context,