summaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/fuse/file.c3
-rw-r--r--fs/gfs2/locking/dlm/plock.c109
-rw-r--r--fs/gfs2/locking/nolock/main.c8
-rw-r--r--fs/gfs2/ops_file.c12
-rw-r--r--fs/lockd/svc4proc.c6
-rw-r--r--fs/lockd/svclock.c275
-rw-r--r--fs/lockd/svcproc.c7
-rw-r--r--fs/lockd/svcsubs.c2
-rw-r--r--fs/locks.c264
-rw-r--r--fs/nfs/file.c7
-rw-r--r--fs/nfs/nfs4proc.c1
-rw-r--r--fs/nfsd/nfs4state.c30
12 files changed, 524 insertions, 200 deletions
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 2fd0692..acfad65 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -738,8 +738,7 @@ static int fuse_file_lock(struct file *file, int cmd, struct file_lock *fl)
if (cmd == F_GETLK) {
if (fc->no_lock) {
- if (!posix_test_lock(file, fl, fl))
- fl->fl_type = F_UNLCK;
+ posix_test_lock(file, fl);
err = 0;
} else
err = fuse_getlk(file, fl);
diff --git a/fs/gfs2/locking/dlm/plock.c b/fs/gfs2/locking/dlm/plock.c
index 1dd4215..f82495e 100644
--- a/fs/gfs2/locking/dlm/plock.c
+++ b/fs/gfs2/locking/dlm/plock.c
@@ -25,6 +25,15 @@ struct plock_op {
struct gdlm_plock_info info;
};
+struct plock_xop {
+ struct plock_op xop;
+ void *callback;
+ void *fl;
+ void *file;
+ struct file_lock flc;
+};
+
+
static inline void set_version(struct gdlm_plock_info *info)
{
info->version[0] = GDLM_PLOCK_VERSION_MAJOR;
@@ -64,12 +73,14 @@ int gdlm_plock(void *lockspace, struct lm_lockname *name,
{
struct gdlm_ls *ls = lockspace;
struct plock_op *op;
+ struct plock_xop *xop;
int rv;
- op = kzalloc(sizeof(*op), GFP_KERNEL);
- if (!op)
+ xop = kzalloc(sizeof(*xop), GFP_KERNEL);
+ if (!xop)
return -ENOMEM;
+ op = &xop->xop;
op->info.optype = GDLM_PLOCK_OP_LOCK;
op->info.pid = fl->fl_pid;
op->info.ex = (fl->fl_type == F_WRLCK);
@@ -79,9 +90,21 @@ int gdlm_plock(void *lockspace, struct lm_lockname *name,
op->info.start = fl->fl_start;
op->info.end = fl->fl_end;
op->info.owner = (__u64)(long) fl->fl_owner;
+ if (fl->fl_lmops && fl->fl_lmops->fl_grant) {
+ xop->callback = fl->fl_lmops->fl_grant;
+ locks_init_lock(&xop->flc);
+ locks_copy_lock(&xop->flc, fl);
+ xop->fl = fl;
+ xop->file = file;
+ } else
+ xop->callback = NULL;
send_op(op);
- wait_event(recv_wq, (op->done != 0));
+
+ if (xop->callback == NULL)
+ wait_event(recv_wq, (op->done != 0));
+ else
+ return -EINPROGRESS;
spin_lock(&ops_lock);
if (!list_empty(&op->list)) {
@@ -99,7 +122,63 @@ int gdlm_plock(void *lockspace, struct lm_lockname *name,
(unsigned long long)name->ln_number);
}
- kfree(op);
+ kfree(xop);
+ return rv;
+}
+
+/* Returns failure iff a succesful lock operation should be canceled */
+static int gdlm_plock_callback(struct plock_op *op)
+{
+ struct file *file;
+ struct file_lock *fl;
+ struct file_lock *flc;
+ int (*notify)(void *, void *, int) = NULL;
+ struct plock_xop *xop = (struct plock_xop *)op;
+ int rv = 0;
+
+ spin_lock(&ops_lock);
+ if (!list_empty(&op->list)) {
+ printk(KERN_INFO "plock op on list\n");
+ list_del(&op->list);
+ }
+ spin_unlock(&ops_lock);
+
+ /* check if the following 2 are still valid or make a copy */
+ file = xop->file;
+ flc = &xop->flc;
+ fl = xop->fl;
+ notify = xop->callback;
+
+ if (op->info.rv) {
+ notify(flc, NULL, op->info.rv);
+ goto out;
+ }
+
+ /* got fs lock; bookkeep locally as well: */
+ flc->fl_flags &= ~FL_SLEEP;
+ if (posix_lock_file(file, flc, NULL)) {
+ /*
+ * This can only happen in the case of kmalloc() failure.
+ * The filesystem's own lock is the authoritative lock,
+ * so a failure to get the lock locally is not a disaster.
+ * As long as GFS cannot reliably cancel locks (especially
+ * in a low-memory situation), we're better off ignoring
+ * this failure than trying to recover.
+ */
+ log_error("gdlm_plock: vfs lock error file %p fl %p",
+ file, fl);
+ }
+
+ rv = notify(flc, NULL, 0);
+ if (rv) {
+ /* XXX: We need to cancel the fs lock here: */
+ printk("gfs2 lock granted after lock request failed;"
+ " dangling lock!\n");
+ goto out;
+ }
+
+out:
+ kfree(xop);
return rv;
}
@@ -138,6 +217,9 @@ int gdlm_punlock(void *lockspace, struct lm_lockname *name,
rv = op->info.rv;
+ if (rv == -ENOENT)
+ rv = 0;
+
kfree(op);
return rv;
}
@@ -161,6 +243,7 @@ int gdlm_plock_get(void *lockspace, struct lm_lockname *name,
op->info.start = fl->fl_start;
op->info.end = fl->fl_end;
+
send_op(op);
wait_event(recv_wq, (op->done != 0));
@@ -173,9 +256,10 @@ int gdlm_plock_get(void *lockspace, struct lm_lockname *name,
rv = op->info.rv;
- if (rv == 0)
- fl->fl_type = F_UNLCK;
- else if (rv > 0) {
+ fl->fl_type = F_UNLCK;
+ if (rv == -ENOENT)
+ rv = 0;
+ else if (rv == 0 && op->info.pid != fl->fl_pid) {
fl->fl_type = (op->info.ex) ? F_WRLCK : F_RDLCK;
fl->fl_pid = op->info.pid;
fl->fl_start = op->info.start;
@@ -243,9 +327,14 @@ static ssize_t dev_write(struct file *file, const char __user *u, size_t count,
}
spin_unlock(&ops_lock);
- if (found)
- wake_up(&recv_wq);
- else
+ if (found) {
+ struct plock_xop *xop;
+ xop = (struct plock_xop *)op;
+ if (xop->callback)
+ count = gdlm_plock_callback(op);
+ else
+ wake_up(&recv_wq);
+ } else
printk(KERN_INFO "gdlm dev_write no op %x %llx\n", info.fsid,
(unsigned long long)info.number);
return count;
diff --git a/fs/gfs2/locking/nolock/main.c b/fs/gfs2/locking/nolock/main.c
index acfbc94..5cc1dfa 100644
--- a/fs/gfs2/locking/nolock/main.c
+++ b/fs/gfs2/locking/nolock/main.c
@@ -164,13 +164,7 @@ static void nolock_unhold_lvb(void *lock, char *lvb)
static int nolock_plock_get(void *lockspace, struct lm_lockname *name,
struct file *file, struct file_lock *fl)
{
- struct file_lock tmp;
- int ret;
-
- ret = posix_test_lock(file, fl, &tmp);
- fl->fl_type = F_UNLCK;
- if (ret)
- memcpy(fl, &tmp, sizeof(struct file_lock));
+ posix_test_lock(file, fl);
return 0;
}
diff --git a/fs/gfs2/ops_file.c b/fs/gfs2/ops_file.c
index b50180e..329c4dc 100644
--- a/fs/gfs2/ops_file.c
+++ b/fs/gfs2/ops_file.c
@@ -513,18 +513,18 @@ static int gfs2_lock(struct file *file, int cmd, struct file_lock *fl)
if (sdp->sd_args.ar_localflocks) {
if (IS_GETLK(cmd)) {
- struct file_lock tmp;
- int ret;
- ret = posix_test_lock(file, fl, &tmp);
- fl->fl_type = F_UNLCK;
- if (ret)
- memcpy(fl, &tmp, sizeof(struct file_lock));
+ posix_test_lock(file, fl);
return 0;
} else {
return posix_lock_file_wait(file, fl);
}
}
+ if (cmd == F_CANCELLK) {
+ /* Hack: */
+ cmd = F_SETLK;
+ fl->fl_type = F_UNLCK;
+ }
if (IS_GETLK(cmd))
return gfs2_lm_plock_get(sdp, &name, file, fl);
else if (fl->fl_type == F_UNLCK)
diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c
index 47a66aa..bf27b6c 100644
--- a/fs/lockd/svc4proc.c
+++ b/fs/lockd/svc4proc.c
@@ -99,7 +99,9 @@ nlm4svc_proc_test(struct svc_rqst *rqstp, struct nlm_args *argp,
return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success;
/* Now check for conflicting locks */
- resp->status = nlmsvc_testlock(file, &argp->lock, &resp->lock);
+ resp->status = nlmsvc_testlock(rqstp, file, &argp->lock, &resp->lock, &resp->cookie);
+ if (resp->status == nlm_drop_reply)
+ return rpc_drop_reply;
dprintk("lockd: TEST4 status %d\n", ntohl(resp->status));
nlm_release_host(host);
@@ -143,6 +145,8 @@ nlm4svc_proc_lock(struct svc_rqst *rqstp, struct nlm_args *argp,
/* Now try to lock the file */
resp->status = nlmsvc_lock(rqstp, file, &argp->lock,
argp->block, &argp->cookie);
+ if (resp->status == nlm_drop_reply)
+ return rpc_drop_reply;
dprintk("lockd: LOCK status %d\n", ntohl(resp->status));
nlm_release_host(host);
diff --git a/fs/lockd/svclock.c b/fs/lockd/svclock.c
index cf51f84..b3efa45 100644
--- a/fs/lockd/svclock.c
+++ b/fs/lockd/svclock.c
@@ -173,7 +173,7 @@ found:
*/
static inline struct nlm_block *
nlmsvc_create_block(struct svc_rqst *rqstp, struct nlm_file *file,
- struct nlm_lock *lock, struct nlm_cookie *cookie)
+ struct nlm_lock *lock, struct nlm_cookie *cookie)
{
struct nlm_block *block;
struct nlm_host *host;
@@ -210,6 +210,7 @@ nlmsvc_create_block(struct svc_rqst *rqstp, struct nlm_file *file,
block->b_daemon = rqstp->rq_server;
block->b_host = host;
block->b_file = file;
+ block->b_fl = NULL;
file->f_count++;
/* Add to file's list of blocks */
@@ -261,6 +262,7 @@ static void nlmsvc_free_block(struct kref *kref)
nlmsvc_freegrantargs(block->b_call);
nlm_release_call(block->b_call);
nlm_release_file(block->b_file);
+ kfree(block->b_fl);
kfree(block);
}
@@ -331,6 +333,31 @@ static void nlmsvc_freegrantargs(struct nlm_rqst *call)
}
/*
+ * Deferred lock request handling for non-blocking lock
+ */
+static u32
+nlmsvc_defer_lock_rqst(struct svc_rqst *rqstp, struct nlm_block *block)
+{
+ u32 status = nlm_lck_denied_nolocks;
+
+ block->b_flags |= B_QUEUED;
+
+ nlmsvc_insert_block(block, NLM_TIMEOUT);
+
+ block->b_cache_req = &rqstp->rq_chandle;
+ if (rqstp->rq_chandle.defer) {
+ block->b_deferred_req =
+ rqstp->rq_chandle.defer(block->b_cache_req);
+ if (block->b_deferred_req != NULL)
+ status = nlm_drop_reply;
+ }
+ dprintk("lockd: nlmsvc_defer_lock_rqst block %p flags %d status %d\n",
+ block, block->b_flags, status);
+
+ return status;
+}
+
+/*
* Attempt to establish a lock, and if it can't be granted, block it
* if required.
*/
@@ -338,7 +365,7 @@ __be32
nlmsvc_lock(struct svc_rqst *rqstp, struct nlm_file *file,
struct nlm_lock *lock, int wait, struct nlm_cookie *cookie)
{
- struct nlm_block *block, *newblock = NULL;
+ struct nlm_block *block = NULL;
int error;
__be32 ret;
@@ -351,29 +378,58 @@ nlmsvc_lock(struct svc_rqst *rqstp, struct nlm_file *file,
wait);
- lock->fl.fl_flags &= ~FL_SLEEP;
-again:
/* Lock file against concurrent access */
mutex_lock(&file->f_mutex);
- /* Get existing block (in case client is busy-waiting) */
+ /* Get existing block (in case client is busy-waiting)
+ * or create new block
+ */
block = nlmsvc_lookup_block(file, lock);
if (block == NULL) {
- if (newblock != NULL)
- lock = &newblock->b_call->a_args.lock;
- } else
+ block = nlmsvc_create_block(rqstp, file, lock, cookie);
+ ret = nlm_lck_denied_nolocks;
+ if (block == NULL)
+ goto out;
lock = &block->b_call->a_args.lock;
+ } else
+ lock->fl.fl_flags &= ~FL_SLEEP;
- error = posix_lock_file(file->f_file, &lock->fl);
- lock->fl.fl_flags &= ~FL_SLEEP;
+ if (block->b_flags & B_QUEUED) {
+ dprintk("lockd: nlmsvc_lock deferred block %p flags %d\n",
+ block, block->b_flags);
+ if (block->b_granted) {
+ nlmsvc_unlink_block(block);
+ ret = nlm_granted;
+ goto out;
+ }
+ if (block->b_flags & B_TIMED_OUT) {
+ nlmsvc_unlink_block(block);
+ ret = nlm_lck_denied;
+ goto out;
+ }
+ ret = nlm_drop_reply;
+ goto out;
+ }
- dprintk("lockd: posix_lock_file returned %d\n", error);
+ if (!wait)
+ lock->fl.fl_flags &= ~FL_SLEEP;
+ error = vfs_lock_file(file->f_file, F_SETLK, &lock->fl, NULL);
+ lock->fl.fl_flags &= ~FL_SLEEP;
+ dprintk("lockd: vfs_lock_file returned %d\n", error);
switch(error) {
case 0:
ret = nlm_granted;
goto out;
case -EAGAIN:
+ ret = nlm_lck_denied;
break;
+ case -EINPROGRESS:
+ if (wait)
+ break;
+ /* Filesystem lock operation is in progress
+ Add it to the queue waiting for callback */
+ ret = nlmsvc_defer_lock_rqst(rqstp, block);
+ goto out;
case -EDEADLK:
ret = nlm_deadlock;
goto out;
@@ -387,26 +443,11 @@ again:
goto out;
ret = nlm_lck_blocked;
- if (block != NULL)
- goto out;
-
- /* If we don't have a block, create and initialize it. Then
- * retry because we may have slept in kmalloc. */
- /* We have to release f_mutex as nlmsvc_create_block may try to
- * to claim it while doing host garbage collection */
- if (newblock == NULL) {
- mutex_unlock(&file->f_mutex);
- dprintk("lockd: blocking on this lock (allocating).\n");
- if (!(newblock = nlmsvc_create_block(rqstp, file, lock, cookie)))
- return nlm_lck_denied_nolocks;
- goto again;
- }
/* Append to list of blocked */
- nlmsvc_insert_block(newblock, NLM_NEVER);
+ nlmsvc_insert_block(block, NLM_NEVER);
out:
mutex_unlock(&file->f_mutex);
- nlmsvc_release_block(newblock);
nlmsvc_release_block(block);
dprintk("lockd: nlmsvc_lock returned %u\n", ret);
return ret;
@@ -416,9 +457,14 @@ out:
* Test for presence of a conflicting lock.
*/
__be32
-nlmsvc_testlock(struct nlm_file *file, struct nlm_lock *lock,
- struct nlm_lock *conflock)
+nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file,
+ struct nlm_lock *lock, struct nlm_lock *conflock,
+ struct nlm_cookie *cookie)
{
+ struct nlm_block *block = NULL;
+ int error;
+ __be32 ret;
+
dprintk("lockd: nlmsvc_testlock(%s/%ld, ty=%d, %Ld-%Ld)\n",
file->f_file->f_path.dentry->d_inode->i_sb->s_id,
file->f_file->f_path.dentry->d_inode->i_ino,
@@ -426,19 +472,70 @@ nlmsvc_testlock(struct nlm_file *file, struct nlm_lock *lock,
(long long)lock->fl.fl_start,
(long long)lock->fl.fl_end);
- if (posix_test_lock(file->f_file, &lock->fl, &conflock->fl)) {
- dprintk("lockd: conflicting lock(ty=%d, %Ld-%Ld)\n",
- conflock->fl.fl_type,
- (long long)conflock->fl.fl_start,
- (long long)conflock->fl.fl_end);
- conflock->caller = "somehost"; /* FIXME */
- conflock->len = strlen(conflock->caller);
- conflock->oh.len = 0; /* don't return OH info */
- conflock->svid = conflock->fl.fl_pid;
- return nlm_lck_denied;
+ /* Get existing block (in case client is busy-waiting) */
+ block = nlmsvc_lookup_block(file, lock);
+
+ if (block == NULL) {
+ struct file_lock *conf = kzalloc(sizeof(*conf), GFP_KERNEL);
+
+ if (conf == NULL)
+ return nlm_granted;
+ block = nlmsvc_create_block(rqstp, file, lock, cookie);
+ if (block == NULL) {
+ kfree(conf);
+ return nlm_granted;
+ }
+ block->b_fl = conf;
+ }
+ if (block->b_flags & B_QUEUED) {
+ dprintk("lockd: nlmsvc_testlock deferred block %p flags %d fl %p\n",
+ block, block->b_flags, block->b_fl);
+ if (block->b_flags & B_TIMED_OUT) {
+ nlmsvc_unlink_block(block);
+ return nlm_lck_denied;
+ }
+ if (block->b_flags & B_GOT_CALLBACK) {
+ if (block->b_fl != NULL
+ && block->b_fl->fl_type != F_UNLCK) {
+ lock->fl = *block->b_fl;
+ goto conf_lock;
+ }
+ else {
+ nlmsvc_unlink_block(block);
+ return nlm_granted;
+ }
+ }
+ return nlm_drop_reply;
}
- return nlm_granted;
+ error = vfs_test_lock(file->f_file, &lock->fl);
+ if (error == -EINPROGRESS)
+ return nlmsvc_defer_lock_rqst(rqstp, block);
+ if (error) {
+ ret = nlm_lck_denied_nolocks;
+ goto out;
+ }
+ if (lock->fl.fl_type == F_UNLCK) {
+ ret = nlm_granted;
+ goto out;
+ }
+
+conf_lock:
+ dprintk("lockd: conflicting lock(ty=%d, %Ld-%Ld)\n",
+ lock->fl.fl_type, (long long)lock->fl.fl_start,
+ (long long)lock->fl.fl_end);
+ conflock->caller = "somehost"; /* FIXME */
+ conflock->len = strlen(conflock->caller);
+ conflock->oh.len = 0; /* don't return OH info */
+ conflock->svid = lock->fl.fl_pid;
+ conflock->fl.fl_type = lock->fl.fl_type;
+ conflock->fl.fl_start = lock->fl.fl_start;
+ conflock->fl.fl_end = lock->fl.fl_end;
+ ret = nlm_lck_denied;
+out:
+ if (block)
+ nlmsvc_release_block(block);
+ return ret;
}
/*
@@ -464,7 +561,7 @@ nlmsvc_unlock(struct nlm_file *file, struct nlm_lock *lock)
nlmsvc_cancel_blocked(file, lock);
lock->fl.fl_type = F_UNLCK;
- error = posix_lock_file(file->f_file, &lock->fl);
+ error = vfs_lock_file(file->f_file, F_SETLK, &lock->fl, NULL);
return (error < 0)? nlm_lck_denied_nolocks : nlm_granted;
}
@@ -493,6 +590,8 @@ nlmsvc_cancel_blocked(struct nlm_file *file, struct nlm_lock *lock)
block = nlmsvc_lookup_block(file, lock);
mutex_unlock(&file->f_mutex);
if (block != NULL) {
+ vfs_cancel_lock(block->b_file->f_file,
+ &block->b_call->a_args.lock.fl);
status = nlmsvc_unlink_block(block);
nlmsvc_release_block(block);
}
@@ -500,6 +599,63 @@ nlmsvc_cancel_blocked(struct nlm_file *file, struct nlm_lock *lock)
}
/*
+ * This is a callback from the filesystem for VFS file lock requests.
+ * It will be used if fl_grant is defined and the filesystem can not
+ * respond to the request immediately.
+ * For GETLK request it will copy the reply to the nlm_block.
+ * For SETLK or SETLKW request it will get the local posix lock.
+ * In all cases it will move the block to the head of nlm_blocked q where
+ * nlmsvc_retry_blocked() can send back a reply for SETLKW or revisit the
+ * deferred rpc for GETLK and SETLK.
+ */
+static void
+nlmsvc_update_deferred_block(struct nlm_block *block, struct file_lock *conf,
+ int result)
+{
+ block->b_flags |= B_GOT_CALLBACK;
+ if (result == 0)
+ block->b_granted = 1;
+ else
+ block->b_flags |= B_TIMED_OUT;
+ if (conf) {
+ if (block->b_fl)
+ locks_copy_lock(block->b_fl, conf);
+ }
+}
+
+static int nlmsvc_grant_deferred(struct file_lock *fl, struct file_lock *conf,
+ int result)
+{
+ struct nlm_block *block;
+ int rc = -ENOENT;
+
+ lock_kernel();
+ list_for_each_entry(block, &nlm_blocked, b_list) {
+ if (nlm_compare_locks(&block->b_call->a_args.lock.fl, fl)) {
+ dprintk("lockd: nlmsvc_notify_blocked block %p flags %d\n",
+ block, block->b_flags);
+ if (block->b_flags & B_QUEUED) {
+ if (block->b_flags & B_TIMED_OUT) {
+ rc = -ENOLCK;
+ break;
+ }
+ nlmsvc_update_deferred_block(block, conf, result);
+ } else if (result == 0)
+ block->b_granted = 1;
+
+ nlmsvc_insert_block(block, 0);
+ svc_wake_up(block->b_daemon);
+ rc = 0;
+ break;
+ }
+ }
+ unlock_kernel();
+ if (rc == -ENOENT)
+ printk(KERN_WARNING "lockd: grant for unknown block\n");
+ return rc;
+}
+
+/*
* Unblock a blocked lock request. This is a callback invoked from the
* VFS layer when a lock on which we blocked is removed.
*
@@ -531,6 +687,7 @@ static int nlmsvc_same_owner(struct file_lock *fl1, struct file_lock *fl2)
struct lock_manager_operations nlmsvc_lock_operations = {
.fl_compare_owner = nlmsvc_same_owner,
.fl_notify = nlmsvc_notify_blocked,
+ .fl_grant = nlmsvc_grant_deferred,
};
/*
@@ -553,6 +710,8 @@ nlmsvc_grant_blocked(struct nlm_block *block)
dprintk("lockd: grant blocked lock %p\n", block);
+ kref_get(&block->b_count);
+
/* Unlink block request from list */
nlmsvc_unlink_block(block);
@@ -566,20 +725,23 @@ nlmsvc_grant_blocked(struct nlm_block *block)
/* Try the lock operation again */
lock->fl.fl_flags |= FL_SLEEP;
- error = posix_lock_file(file->f_file, &lock->fl);
+ error = vfs_lock_file(file->f_file, F_SETLK, &lock->fl, NULL);
lock->fl.fl_flags &= ~FL_SLEEP;
switch (error) {
case 0:
break;
case -EAGAIN:
- dprintk("lockd: lock still blocked\n");
+ case -EINPROGRESS:
+ dprintk("lockd: lock still blocked error %d\n", error);
nlmsvc_insert_block(block, NLM_NEVER);
+ nlmsvc_release_block(block);
return;
default:
printk(KERN_WARNING "lockd: unexpected error %d in %s!\n",
-error, __FUNCTION__);
nlmsvc_insert_block(block, 10 * HZ);
+ nlmsvc_release_block(block);
return;
}
@@ -592,7 +754,6 @@ callback:
nlmsvc_insert_block(block, 30 * HZ);
/* Call the client */
- kref_get(&block->b_count);
nlm_async_call(block->b_call, NLMPROC_GRANTED_MSG, &nlmsvc_grant_ops);
}
@@ -665,6 +826,23 @@ nlmsvc_grant_reply(struct nlm_cookie *cookie, __be32 status)
nlmsvc_release_block(block);
}
+/* Helper function to handle retry of a deferred block.
+ * If it is a blocking lock, call grant_blocked.
+ * For a non-blocking lock or test lock, revisit the request.
+ */
+static void
+retry_deferred_block(struct nlm_block *block)
+{
+ if (!(block->b_flags & B_GOT_CALLBACK))
+ block->b_flags |= B_TIMED_OUT;
+ nlmsvc_insert_block(block, NLM_TIMEOUT);
+ dprintk("revisit block %p flags %d\n", block, block->b_flags);
+ if (block->b_deferred_req) {
+ block->b_deferred_req->revisit(block->b_deferred_req, 0);
+ block->b_deferred_req = NULL;
+ }
+}
+
/*
* Retry all blocked locks that have been notified. This is where lockd
* picks up locks that can be granted, or grant notifications that must
@@ -688,9 +866,12 @@ nlmsvc_retry_blocked(void)
dprintk("nlmsvc_retry_blocked(%p, when=%ld)\n",
block, block->b_when);
- kref_get(&block->b_count);
- nlmsvc_grant_blocked(block);
- nlmsvc_release_block(block);
+ if (block->b_flags & B_QUEUED) {
+ dprintk("nlmsvc_retry_blocked delete block (%p, granted=%d, flags=%d)\n",
+ block, block->b_granted, block->b_flags);
+ retry_deferred_block(block);
+ } else
+ nlmsvc_grant_blocked(block);
}
return timeout;
diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c
index 31cb484..9cd5c8b 100644
--- a/fs/lockd/svcproc.c
+++ b/fs/lockd/svcproc.c
@@ -33,6 +33,7 @@ cast_to_nlm(__be32 status, u32 vers)
case nlm_lck_denied_nolocks:
case nlm_lck_blocked:
case nlm_lck_denied_grace_period:
+ case nlm_drop_reply:
break;
case nlm4_deadlock:
status = nlm_lck_denied;
@@ -127,7 +128,9 @@ nlmsvc_proc_test(struct svc_rqst *rqstp, struct nlm_args *argp,
return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success;
/* Now check for conflicting locks */
- resp->status = cast_status(nlmsvc_testlock(file, &argp->lock, &resp->lock));
+ resp->status = cast_status(nlmsvc_testlock(rqstp, file, &argp->lock, &resp->lock, &resp->cookie));
+ if (resp->status == nlm_drop_reply)
+ return rpc_drop_reply;
dprintk("lockd: TEST status %d vers %d\n",
ntohl(resp->status), rqstp->rq_vers);
@@ -172,6 +175,8 @@ nlmsvc_proc_lock(struct svc_rqst *rqstp, struct nlm_args *argp,
/* Now try to lock the file */
resp->status = cast_status(nlmsvc_lock(rqstp, file, &argp->lock,
argp->block, &argp->cookie));
+ if (resp->status == nlm_drop_reply)
+ return rpc_drop_reply;
dprintk("lockd: LOCK status %d\n", ntohl(resp->status));
nlm_release_host(host);
diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c
index c0df00c..84ebba3 100644
--- a/fs/lockd/svcsubs.c
+++ b/fs/lockd/svcsubs.c
@@ -182,7 +182,7 @@ again:
lock.fl_type = F_UNLCK;
lock.fl_start = 0;
lock.fl_end = OFFSET_MAX;
- if (posix_lock_file(file->f_file, &lock) < 0) {
+ if (vfs_lock_file(file->f_file, F_SETLK, &lock, NULL) < 0) {
printk("lockd: unlock failure in %s:%d\n",
__FILE__, __LINE__);
return 1;
diff --git a/fs/locks.c b/fs/locks.c
index 3255780..671a034 100644
--- a/fs/locks.c
+++ b/fs/locks.c
@@ -665,11 +665,11 @@ static int locks_block_on_timeout(struct file_lock *blocker, struct file_lock *w
}
int
-posix_test_lock(struct file *filp, struct file_lock *fl,
- struct file_lock *conflock)
+posix_test_lock(struct file *filp, struct file_lock *fl)
{
struct file_lock *cfl;
+ fl->fl_type = F_UNLCK;
lock_kernel();
for (cfl = filp->f_path.dentry->d_inode->i_flock; cfl; cfl = cfl->fl_next) {
if (!IS_POSIX(cfl))
@@ -678,7 +678,7 @@ posix_test_lock(struct file *filp, struct file_lock *fl,
break;
}
if (cfl) {
- __locks_copy_lock(conflock, cfl);
+ __locks_copy_lock(fl, cfl);
unlock_kernel();
return 1;
}
@@ -800,7 +800,7 @@ out:
return error;
}
-static int __posix_lock_file_conf(struct inode *inode, struct file_lock *request, struct file_lock *conflock)
+static int __posix_lock_file(struct inode *inode, struct file_lock *request, struct file_lock *conflock)
{
struct file_lock *fl;
struct file_lock *new_fl = NULL;
@@ -1006,6 +1006,7 @@ static int __posix_lock_file_conf(struct inode *inode, struct file_lock *request
* posix_lock_file - Apply a POSIX-style lock to a file
* @filp: The file to apply the lock to
* @fl: The lock to be applied
+ * @conflock: Place to return a copy of the conflicting lock, if found.
*
* Add a POSIX style lock to a file.
* We merge adjacent & overlapping locks whenever possible.
@@ -1015,26 +1016,12 @@ static int __posix_lock_file_conf(struct inode *inode, struct file_lock *request
* whether or not a lock was successfully freed by testing the return
* value for -ENOENT.
*/
-int posix_lock_file(struct file *filp, struct file_lock *fl)
-{
- return __posix_lock_file_conf(filp->f_path.dentry->d_inode, fl, NULL);
-}
-EXPORT_SYMBOL(posix_lock_file);
-
-/**
- * posix_lock_file_conf - Apply a POSIX-style lock to a file
- * @filp: The file to apply the lock to
- * @fl: The lock to be applied
- * @conflock: Place to return a copy of the conflicting lock, if found.
- *
- * Except for the conflock parameter, acts just like posix_lock_file.
- */
-int posix_lock_file_conf(struct file *filp, struct file_lock *fl,
+int posix_lock_file(struct file *filp, struct file_lock *fl,
struct file_lock *conflock)
{
- return __posix_lock_file_conf(filp->f_path.dentry->d_inode, fl, conflock);
+ return __posix_lock_file(filp->f_path.dentry->d_inode, fl, conflock);
}
-EXPORT_SYMBOL(posix_lock_file_conf);
+EXPORT_SYMBOL(posix_lock_file);
/**
* posix_lock_file_wait - Apply a POSIX-style lock to a file
@@ -1050,7 +1037,7 @@ int posix_lock_file_wait(struct file *filp, struct file_lock *fl)
int error;
might_sleep ();
for (;;) {
- error = posix_lock_file(filp, fl);
+ error = posix_lock_file(filp, fl, NULL);
if ((error != -EAGAIN) || !(fl->fl_flags & FL_SLEEP))
break;
error = wait_event_interruptible(fl->fl_wait, !fl->fl_next);
@@ -1122,7 +1109,7 @@ int locks_mandatory_area(int read_write, struct inode *inode,
fl.fl_end = offset + count - 1;
for (;;) {
- error = __posix_lock_file_conf(inode, &fl, NULL);
+ error = __posix_lock_file(inode, &fl, NULL);
if (error != -EAGAIN)
break;
if (!(fl.fl_flags & FL_SLEEP))
@@ -1610,12 +1597,62 @@ asmlinkage long sys_flock(unsigned int fd, unsigned int cmd)
return error;
}
+/**
+ * vfs_test_lock - test file byte range lock
+ * @filp: The file to test lock for
+ * @fl: The lock to test
+ * @conf: Place to return a copy of the conflicting lock, if found
+ *
+ * Returns -ERRNO on failure. Indicates presence of conflicting lock by
+ * setting conf->fl_type to something other than F_UNLCK.
+ */
+int vfs_test_lock(struct file *filp, struct file_lock *fl)
+{
+ if (filp->f_op && filp->f_op->lock)
+ return filp->f_op->lock(filp, F_GETLK, fl);
+ posix_test_lock(filp, fl);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vfs_test_lock);
+
+static int posix_lock_to_flock(struct flock *flock, struct file_lock *fl)
+{
+ flock->l_pid = fl->fl_pid;
+#if BITS_PER_LONG == 32
+ /*
+ * Make sure we can represent the posix lock via
+ * legacy 32bit flock.
+ */
+ if (fl->fl_start > OFFT_OFFSET_MAX)
+ return -EOVERFLOW;
+ if (fl->fl_end != OFFSET_MAX && fl->fl_end > OFFT_OFFSET_MAX)
+ return -EOVERFLOW;
+#endif
+ flock->l_start = fl->fl_start;
+ flock->l_len = fl->fl_end == OFFSET_MAX ? 0 :
+ fl->fl_end - fl->fl_start + 1;
+ flock->l_whence = 0;
+ return 0;
+}
+
+#if BITS_PER_LONG == 32
+static void posix_lock_to_flock64(struct flock64 *flock, struct file_lock *fl)
+{
+ flock->l_pid = fl->fl_pid;
+ flock->l_start = fl->fl_start;
+ flock->l_len = fl->fl_end == OFFSET_MAX ? 0 :
+ fl->fl_end - fl->fl_start + 1;
+ flock->l_whence = 0;
+ flock->l_type = fl->fl_type;
+}
+#endif
+
/* Report the first existing lock that would conflict with l.
* This implements the F_GETLK command of fcntl().
*/
int fcntl_getlk(struct file *filp, struct flock __user *l)
{
- struct file_lock *fl, cfl, file_lock;
+ struct file_lock file_lock;
struct flock flock;
int error;
@@ -1630,38 +1667,15 @@ int fcntl_getlk(struct file *filp, struct flock __user *l)
if (error)
goto out;
- if (filp->f_op && filp->f_op->lock) {
- error = filp->f_op->lock(filp, F_GETLK, &file_lock);
- if (file_lock.fl_ops && file_lock.fl_ops->fl_release_private)
- file_lock.fl_ops->fl_release_private(&file_lock);
- if (error < 0)
- goto out;
- else
- fl = (file_lock.fl_type == F_UNLCK ? NULL : &file_lock);
- } else {
- fl = (posix_test_lock(filp, &file_lock, &cfl) ? &cfl : NULL);
- }
+ error = vfs_test_lock(filp, &file_lock);
+ if (error)
+ goto out;
- flock.l_type = F_UNLCK;
- if (fl != NULL) {
- flock.l_pid = fl->fl_pid;
-#if BITS_PER_LONG == 32
- /*
- * Make sure we can represent the posix lock via
- * legacy 32bit flock.
- */
- error = -EOVERFLOW;
- if (fl->fl_start > OFFT_OFFSET_MAX)
- goto out;
- if ((fl->fl_end != OFFSET_MAX)
- && (fl->fl_end > OFFT_OFFSET_MAX))
+ flock.l_type = file_lock.fl_type;
+ if (file_lock.fl_type != F_UNLCK) {
+ error = posix_lock_to_flock(&flock, &file_lock);
+ if (error)
goto out;
-#endif
- flock.l_start = fl->fl_start;
- flock.l_len = fl->fl_end == OFFSET_MAX ? 0 :
- fl->fl_end - fl->fl_start + 1;
- flock.l_whence = 0;
- flock.l_type = fl->fl_type;
}
error = -EFAULT;
if (!copy_to_user(l, &flock, sizeof(flock)))
@@ -1670,6 +1684,48 @@ out:
return error;
}
+/**
+ * vfs_lock_file - file byte range lock
+ * @filp: The file to apply the lock to
+ * @cmd: type of locking operation (F_SETLK, F_GETLK, etc.)
+ * @fl: The lock to be applied
+ * @conf: Place to return a copy of the conflicting lock, if found.
+ *
+ * A caller that doesn't care about the conflicting lock may pass NULL
+ * as the final argument.
+ *
+ * If the filesystem defines a private ->lock() method, then @conf will
+ * be left unchanged; so a caller that cares should initialize it to
+ * some acceptable default.
+ *
+ * To avoid blocking kernel daemons, such as lockd, that need to acquire POSIX
+ * locks, the ->lock() interface may return asynchronously, before the lock has
+ * been granted or denied by the underlying filesystem, if (and only if)
+ * fl_grant is set. Callers expecting ->lock() to return asynchronously
+ * will only use F_SETLK, not F_SETLKW; they will set FL_SLEEP if (and only if)
+ * the request is for a blocking lock. When ->lock() does return asynchronously,
+ * it must return -EINPROGRESS, and call ->fl_grant() when the lock
+ * request completes.
+ * If the request is for non-blocking lock the file system should return
+ * -EINPROGRESS then try to get the lock and call the callback routine with
+ * the result. If the request timed out the callback routine will return a
+ * nonzero return code and the file system should release the lock. The file
+ * system is also responsible to keep a corresponding posix lock when it
+ * grants a lock so the VFS can find out which locks are locally held and do
+ * the correct lock cleanup when required.
+ * The underlying filesystem must not drop the kernel lock or call
+ * ->fl_grant() before returning to the caller with a -EINPROGRESS
+ * return code.
+ */
+int vfs_lock_file(struct file *filp, unsigned int cmd, struct file_lock *fl, struct file_lock *conf)
+{
+ if (filp->f_op && filp->f_op->lock)
+ return filp->f_op->lock(filp, cmd, fl);
+ else
+ return posix_lock_file(filp, fl, conf);
+}
+EXPORT_SYMBOL_GPL(vfs_lock_file);
+
/* Apply the lock described by l to an open file descriptor.
* This implements both the F_SETLK and F_SETLKW commands of fcntl().
*/
@@ -1732,21 +1788,17 @@ again:
if (error)
goto out;
- if (filp->f_op && filp->f_op->lock != NULL)
- error = filp->f_op->lock(filp, cmd, file_lock);
- else {
- for (;;) {
- error = posix_lock_file(filp, file_lock);
- if ((error != -EAGAIN) || (cmd == F_SETLK))
- break;
- error = wait_event_interruptible(file_lock->fl_wait,
- !file_lock->fl_next);
- if (!error)
- continue;
-
- locks_delete_block(file_lock);
+ for (;;) {
+ error = vfs_lock_file(filp, cmd, file_lock, NULL);
+ if (error != -EAGAIN || cmd == F_SETLK)
break;
- }
+ error = wait_event_interruptible(file_lock->fl_wait,
+ !file_lock->fl_next);
+ if (!error)
+ continue;
+
+ locks_delete_block(file_lock);
+ break;
}
/*
@@ -1769,7 +1821,7 @@ out:
*/
int fcntl_getlk64(struct file *filp, struct flock64 __user *l)
{
- struct file_lock *fl, cfl, file_lock;
+ struct file_lock file_lock;
struct flock64 flock;
int error;
@@ -1784,27 +1836,14 @@ int fcntl_getlk64(struct file *filp, struct flock64 __user *l)
if (error)
goto out;
- if (filp->f_op && filp->f_op->lock) {
- error = filp->f_op->lock(filp, F_GETLK, &file_lock);
- if (file_lock.fl_ops && file_lock.fl_ops->fl_release_private)
- file_lock.fl_ops->fl_release_private(&file_lock);
- if (error < 0)
- goto out;
- else
- fl = (file_lock.fl_type == F_UNLCK ? NULL : &file_lock);
- } else {
- fl = (posix_test_lock(filp, &file_lock, &cfl) ? &cfl : NULL);
- }
-
- flock.l_type = F_UNLCK;
- if (fl != NULL) {
- flock.l_pid = fl->fl_pid;
- flock.l_start = fl->fl_start;
- flock.l_len = fl->fl_end == OFFSET_MAX ? 0 :
- fl->fl_end - fl->fl_start + 1;
- flock.l_whence = 0;
- flock.l_type = fl->fl_type;
- }
+ error = vfs_test_lock(filp, &file_lock);
+ if (error)
+ goto out;
+
+ flock.l_type = file_lock.fl_type;
+ if (file_lock.fl_type != F_UNLCK)
+ posix_lock_to_flock64(&flock, &file_lock);
+
error = -EFAULT;
if (!copy_to_user(l, &flock, sizeof(flock)))
error = 0;
@@ -1875,21 +1914,17 @@ again:
if (error)
goto out;
- if (filp->f_op && filp->f_op->lock != NULL)
- error = filp->f_op->lock(filp, cmd, file_lock);
- else {
- for (;;) {
- error = posix_lock_file(filp, file_lock);
- if ((error != -EAGAIN) || (cmd == F_SETLK64))
- break;
- error = wait_event_interruptible(file_lock->fl_wait,
- !file_lock->fl_next);
- if (!error)
- continue;
-
- locks_delete_block(file_lock);
+ for (;;) {
+ error = vfs_lock_file(filp, cmd, file_lock, NULL);
+ if (error != -EAGAIN || cmd == F_SETLK64)
break;
- }
+ error = wait_event_interruptible(file_lock->fl_wait,
+ !file_lock->fl_next);
+ if (!error)
+ continue;
+
+ locks_delete_block(file_lock);
+ break;
}
/*
@@ -1934,10 +1969,7 @@ void locks_remove_posix(struct file *filp, fl_owner_t owner)
lock.fl_ops = NULL;
lock.fl_lmops = NULL;
- if (filp->f_op && filp->f_op->lock != NULL)
- filp->f_op->lock(filp, F_SETLK, &lock);
- else
- posix_lock_file(filp, &lock);
+ vfs_lock_file(filp, F_SETLK, &lock, NULL);
if (lock.fl_ops && lock.fl_ops->fl_release_private)
lock.fl_ops->fl_release_private(&lock);
@@ -2014,6 +2046,22 @@ posix_unblock_lock(struct file *filp, struct file_lock *waiter)
EXPORT_SYMBOL(posix_unblock_lock);
+/**
+ * vfs_cancel_lock - file byte range unblock lock
+ * @filp: The file to apply the unblock to
+ * @fl: The lock to be unblocked
+ *
+ * Used by lock managers to cancel blocked requests
+ */
+int vfs_cancel_lock(struct file *filp, struct file_lock *fl)
+{
+ if (filp->f_op && filp->f_op->lock)
+ return filp->f_op->lock(filp, F_CANCELLK, fl);
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(vfs_cancel_lock);
+
static void lock_get_status(char* out, struct file_lock *fl, int id, char *pfx)
{
struct inode *inode = NULL;
diff --git a/fs/nfs/file.c b/fs/nfs/file.c
index 8e66b5a..5eaee6d 100644
--- a/fs/nfs/file.c
+++ b/fs/nfs/file.c
@@ -391,17 +391,12 @@ out_swapfile:
static int do_getlk(struct file *filp, int cmd, struct file_lock *fl)
{
- struct file_lock cfl;
struct inode *inode = filp->f_mapping->host;
int status = 0;
lock_kernel();
/* Try local locking first */
- if (posix_test_lock(filp, fl, &cfl)) {
- fl->fl_start = cfl.fl_start;
- fl->fl_end = cfl.fl_end;
- fl->fl_type = cfl.fl_type;
- fl->fl_pid = cfl.fl_pid;
+ if (posix_test_lock(filp, fl)) {
goto out;
}
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 3b5ca1b..d6a30e9 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -3017,6 +3017,7 @@ static int _nfs4_proc_getlk(struct nfs4_state *state, int cmd, struct file_lock
case -NFS4ERR_DENIED:
status = 0;
}
+ request->fl_ops->fl_release_private(request);
out:
up_read(&clp->cl_sem);
return status;
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index af36070..678f3be 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -50,6 +50,7 @@
#include <linux/nfsd/xdr4.h>
#include <linux/namei.h>
#include <linux/mutex.h>
+#include <linux/lockd/bind.h>
#define NFSDDBG_FACILITY NFSDDBG_PROC
@@ -2657,6 +2658,7 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct file_lock conflock;
__be32 status = 0;
unsigned int strhashval;
+ unsigned int cmd;
int err;
dprintk("NFSD: nfsd4_lock: start=%Ld length=%Ld\n",
@@ -2739,10 +2741,12 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
case NFS4_READ_LT:
case NFS4_READW_LT:
file_lock.fl_type = F_RDLCK;
+ cmd = F_SETLK;
break;
case NFS4_WRITE_LT:
case NFS4_WRITEW_LT:
file_lock.fl_type = F_WRLCK;
+ cmd = F_SETLK;
break;
default:
status = nfserr_inval;
@@ -2769,9 +2773,8 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
/* XXX?: Just to divert the locks_release_private at the start of
* locks_copy_lock: */
- conflock.fl_ops = NULL;
- conflock.fl_lmops = NULL;
- err = posix_lock_file_conf(filp, &file_lock, &conflock);
+ locks_init_lock(&conflock);
+ err = vfs_lock_file(filp, cmd, &file_lock, &conflock);
switch (-err) {
case 0: /* success! */
update_stateid(&lock_stp->st_stateid);
@@ -2788,7 +2791,7 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
status = nfserr_deadlock;
break;
default:
- dprintk("NFSD: nfsd4_lock: posix_lock_file_conf() failed! status %d\n",err);
+ dprintk("NFSD: nfsd4_lock: vfs_lock_file() failed! status %d\n",err);
status = nfserr_resource;
break;
}
@@ -2813,7 +2816,7 @@ nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct inode *inode;
struct file file;
struct file_lock file_lock;
- struct file_lock conflock;
+ int error;
__be32 status;
if (nfs4_in_grace())
@@ -2869,18 +2872,23 @@ nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
nfs4_transform_lock_offset(&file_lock);
- /* posix_test_lock uses the struct file _only_ to resolve the inode.
+ /* vfs_test_lock uses the struct file _only_ to resolve the inode.
* since LOCKT doesn't require an OPEN, and therefore a struct
- * file may not exist, pass posix_test_lock a struct file with
+ * file may not exist, pass vfs_test_lock a struct file with
* only the dentry:inode set.
*/
memset(&file, 0, sizeof (struct file));
file.f_path.dentry = cstate->current_fh.fh_dentry;
status = nfs_ok;
- if (posix_test_lock(&file, &file_lock, &conflock)) {
+ error = vfs_test_lock(&file, &file_lock);
+ if (error) {
+ status = nfserrno(error);
+ goto out;
+ }
+ if (file_lock.fl_type != F_UNLCK) {
status = nfserr_denied;
- nfs4_set_lock_denied(&conflock, &lockt->lt_denied);
+ nfs4_set_lock_denied(&file_lock, &lockt->lt_denied);
}
out:
nfs4_unlock_state();
@@ -2933,9 +2941,9 @@ nfsd4_locku(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
/*
* Try to unlock the file in the VFS.
*/
- err = posix_lock_file(filp, &file_lock);
+ err = vfs_lock_file(filp, F_SETLK, &file_lock, NULL);
if (err) {
- dprintk("NFSD: nfs4_locku: posix_lock_file failed!\n");
+ dprintk("NFSD: nfs4_locku: vfs_lock_file failed!\n");
goto out_nfserr;
}
/*
OpenPOWER on IntegriCloud