*/
#include <linux/config.h>
+#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/fs.h>
#define NLMDBG_FACILITY NLMDBG_CLIENT
#define NLMCLNT_GRACE_WAIT (5*HZ)
+#define NLMCLNT_POLL_TIMEOUT (30*HZ)
+#define NLMCLNT_MAX_RETRIES 3
static int nlmclnt_test(struct nlm_rqst *, struct file_lock *);
static int nlmclnt_lock(struct nlm_rqst *, struct file_lock *);
static int nlmclnt_unlock(struct nlm_rqst *, struct file_lock *);
-static void nlmclnt_unlock_callback(struct rpc_task *);
-static void nlmclnt_cancel_callback(struct rpc_task *);
static int nlm_stat_to_errno(u32 stat);
+static void nlmclnt_locks_init_private(struct file_lock *fl, struct nlm_host *host);
+static int nlmclnt_cancel(struct nlm_host *, int , struct file_lock *);
+
+static const struct rpc_call_ops nlmclnt_unlock_ops;
+static const struct rpc_call_ops nlmclnt_cancel_ops;
/*
* Cookie counter for NLM requests
nlm_cookie++;
}
+static struct nlm_lockowner *nlm_get_lockowner(struct nlm_lockowner *lockowner)
+{
+ atomic_inc(&lockowner->count);
+ return lockowner;
+}
+
+static void nlm_put_lockowner(struct nlm_lockowner *lockowner)
+{
+ if (!atomic_dec_and_lock(&lockowner->count, &lockowner->host->h_lock))
+ return;
+ list_del(&lockowner->list);
+ spin_unlock(&lockowner->host->h_lock);
+ nlm_release_host(lockowner->host);
+ kfree(lockowner);
+}
+
+static inline int nlm_pidbusy(struct nlm_host *host, uint32_t pid)
+{
+ struct nlm_lockowner *lockowner;
+ list_for_each_entry(lockowner, &host->h_lockowners, list) {
+ if (lockowner->pid == pid)
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static inline uint32_t __nlm_alloc_pid(struct nlm_host *host)
+{
+ uint32_t res;
+ do {
+ res = host->h_pidcount++;
+ } while (nlm_pidbusy(host, res) < 0);
+ return res;
+}
+
+static struct nlm_lockowner *__nlm_find_lockowner(struct nlm_host *host, fl_owner_t owner)
+{
+ struct nlm_lockowner *lockowner;
+ list_for_each_entry(lockowner, &host->h_lockowners, list) {
+ if (lockowner->owner != owner)
+ continue;
+ return nlm_get_lockowner(lockowner);
+ }
+ return NULL;
+}
+
+static struct nlm_lockowner *nlm_find_lockowner(struct nlm_host *host, fl_owner_t owner)
+{
+ struct nlm_lockowner *res, *new = NULL;
+
+ spin_lock(&host->h_lock);
+ res = __nlm_find_lockowner(host, owner);
+ if (res == NULL) {
+ spin_unlock(&host->h_lock);
+ new = (struct nlm_lockowner *)kmalloc(sizeof(*new), GFP_KERNEL);
+ spin_lock(&host->h_lock);
+ res = __nlm_find_lockowner(host, owner);
+ if (res == NULL && new != NULL) {
+ res = new;
+ atomic_set(&new->count, 1);
+ new->owner = owner;
+ new->pid = __nlm_alloc_pid(host);
+ new->host = nlm_get_host(host);
+ list_add(&new->list, &host->h_lockowners);
+ new = NULL;
+ }
+ }
+ spin_unlock(&host->h_lock);
+ kfree(new);
+ return res;
+}
+
/*
* Initialize arguments for TEST/LOCK/UNLOCK/CANCEL calls
*/
-static inline void
-nlmclnt_setlockargs(struct nlm_rqst *req, struct file_lock *fl)
+static void nlmclnt_setlockargs(struct nlm_rqst *req, struct file_lock *fl)
{
struct nlm_args *argp = &req->a_args;
struct nlm_lock *lock = &argp->lock;
locks_copy_lock(&lock->fl, fl);
}
+static void nlmclnt_release_lockargs(struct nlm_rqst *req)
+{
+ struct file_lock *fl = &req->a_args.lock.fl;
+
+ if (fl->fl_ops && fl->fl_ops->fl_release_private)
+ fl->fl_ops->fl_release_private(fl);
+}
+
/*
* Initialize arguments for GRANTED call. The nlm_rqst structure
* has been cleared already.
if (lock->oh.len > NLMCLNT_OHSIZE) {
void *data = kmalloc(lock->oh.len, GFP_KERNEL);
- if (!data)
+ if (!data) {
+ nlmclnt_freegrantargs(call);
return 0;
+ }
call->a_args.lock.oh.data = (u8 *) data;
}
void
nlmclnt_freegrantargs(struct nlm_rqst *call)
{
+ struct file_lock *fl = &call->a_args.lock.fl;
/*
* Check whether we allocated memory for the owner.
*/
if (call->a_args.lock.oh.data != (u8 *) call->a_owner) {
kfree(call->a_args.lock.oh.data);
}
+ if (fl->fl_ops && fl->fl_ops->fl_release_private)
+ fl->fl_ops->fl_release_private(fl);
}
/*
goto done;
}
clnt->cl_softrtry = nfssrv->client->cl_softrtry;
- clnt->cl_intr = nfssrv->client->cl_intr;
- clnt->cl_chatty = nfssrv->client->cl_chatty;
+ clnt->cl_intr = nfssrv->client->cl_intr;
}
/* Keep the old signal mask */
}
call->a_host = host;
+ nlmclnt_locks_init_private(fl, host);
+
/* Set up the argument struct */
nlmclnt_setlockargs(call, fl);
else
status = -EINVAL;
- if (status < 0 && (call->a_flags & RPC_TASK_ASYNC))
- kfree(call);
-
out_restore:
spin_lock_irqsave(¤t->sighand->siglock, flags);
current->blocked = oldset;
nlm_release_host(host);
return status;
}
+EXPORT_SYMBOL(nlmclnt_proc);
/*
* Allocate an NLM RPC call struct
return call;
}
printk("nlmclnt_alloc_call: failed, waiting for memory\n");
- current->state = TASK_INTERRUPTIBLE;
- schedule_timeout(5*HZ);
+ schedule_timeout_interruptible(5*HZ);
}
return NULL;
}
prepare_to_wait(queue, &wait, TASK_INTERRUPTIBLE);
if (!signalled ()) {
schedule_timeout(NLMCLNT_GRACE_WAIT);
+ try_to_freeze();
if (!signalled ())
status = 0;
}
/*
* Generic NLM call
*/
-int
+static int
nlmclnt_call(struct nlm_rqst *req, u32 proc)
{
struct nlm_host *host = req->a_host;
struct rpc_clnt *clnt;
struct nlm_args *argp = &req->a_args;
struct nlm_res *resp = &req->a_res;
- struct file *filp = argp->lock.fl.fl_file;
struct rpc_message msg = {
.rpc_argp = argp,
.rpc_resp = resp,
dprintk("lockd: call procedure %d on %s\n",
(int)proc, host->h_name);
- if (filp)
- msg.rpc_cred = nfs_file_cred(filp);
-
do {
if (host->h_reclaiming && !argp->reclaim)
goto in_grace_period;
/*
* Generic NLM call, async version.
*/
-int
-nlmsvc_async_call(struct nlm_rqst *req, u32 proc, rpc_action callback)
+int nlmsvc_async_call(struct nlm_rqst *req, u32 proc, const struct rpc_call_ops *tk_ops)
{
struct nlm_host *host = req->a_host;
struct rpc_clnt *clnt;
msg.rpc_proc = &clnt->cl_procinfo[proc];
/* bootstrap and kick off the async RPC call */
- status = rpc_call_async(clnt, &msg, RPC_TASK_ASYNC, callback, req);
+ status = rpc_call_async(clnt, &msg, RPC_TASK_ASYNC, tk_ops, req);
return status;
}
-int
-nlmclnt_async_call(struct nlm_rqst *req, u32 proc, rpc_action callback)
+static int nlmclnt_async_call(struct nlm_rqst *req, u32 proc, const struct rpc_call_ops *tk_ops)
{
struct nlm_host *host = req->a_host;
struct rpc_clnt *clnt;
struct nlm_args *argp = &req->a_args;
struct nlm_res *resp = &req->a_res;
- struct file *file = argp->lock.fl.fl_file;
struct rpc_message msg = {
.rpc_argp = argp,
.rpc_resp = resp,
return -ENOLCK;
msg.rpc_proc = &clnt->cl_procinfo[proc];
- /* bootstrap and kick off the async RPC call */
- if (file)
- msg.rpc_cred = nfs_file_cred(file);
/* Increment host refcount */
nlm_get_host(host);
- status = rpc_call_async(clnt, &msg, RPC_TASK_ASYNC, callback, req);
+ /* bootstrap and kick off the async RPC call */
+ status = rpc_call_async(clnt, &msg, RPC_TASK_ASYNC, tk_ops, req);
if (status < 0)
nlm_release_host(host);
return status;
{
int status;
- if ((status = nlmclnt_call(req, NLMPROC_TEST)) < 0)
+ status = nlmclnt_call(req, NLMPROC_TEST);
+ nlmclnt_release_lockargs(req);
+ if (status < 0)
return status;
status = req->a_res.status;
} if (status == NLM_LCK_DENIED) {
/*
* Report the conflicting lock back to the application.
- * FIXME: Is it OK to report the pid back as well?
*/
locks_copy_lock(fl, &req->a_res.lock.fl);
- /* fl->fl_pid = 0; */
+ fl->fl_pid = 0;
} else {
return nlm_stat_to_errno(req->a_res.status);
}
return 0;
}
-static
-void nlmclnt_insert_lock_callback(struct file_lock *fl)
+static void nlmclnt_locks_copy_lock(struct file_lock *new, struct file_lock *fl)
+{
+ memcpy(&new->fl_u.nfs_fl, &fl->fl_u.nfs_fl, sizeof(new->fl_u.nfs_fl));
+ nlm_get_lockowner(new->fl_u.nfs_fl.owner);
+}
+
+static void nlmclnt_locks_release_private(struct file_lock *fl)
{
- nlm_get_host(fl->fl_u.nfs_fl.host);
+ nlm_put_lockowner(fl->fl_u.nfs_fl.owner);
+ fl->fl_ops = NULL;
}
-static
-void nlmclnt_remove_lock_callback(struct file_lock *fl)
+
+static struct file_lock_operations nlmclnt_lock_ops = {
+ .fl_copy_lock = nlmclnt_locks_copy_lock,
+ .fl_release_private = nlmclnt_locks_release_private,
+};
+
+static void nlmclnt_locks_init_private(struct file_lock *fl, struct nlm_host *host)
+{
+ BUG_ON(fl->fl_ops != NULL);
+ fl->fl_u.nfs_fl.state = 0;
+ fl->fl_u.nfs_fl.flags = 0;
+ fl->fl_u.nfs_fl.owner = nlm_find_lockowner(host, fl->fl_owner);
+ fl->fl_ops = &nlmclnt_lock_ops;
+}
+
+static void do_vfs_lock(struct file_lock *fl)
{
- if (fl->fl_u.nfs_fl.host) {
- nlm_release_host(fl->fl_u.nfs_fl.host);
- fl->fl_u.nfs_fl.host = NULL;
+ int res = 0;
+ switch (fl->fl_flags & (FL_POSIX|FL_FLOCK)) {
+ case FL_POSIX:
+ res = posix_lock_file_wait(fl->fl_file, fl);
+ break;
+ case FL_FLOCK:
+ res = flock_lock_file_wait(fl->fl_file, fl);
+ break;
+ default:
+ BUG();
}
+ if (res < 0)
+ printk(KERN_WARNING "%s: VFS is out of sync with lock manager!\n",
+ __FUNCTION__);
}
/*
{
struct nlm_host *host = req->a_host;
struct nlm_res *resp = &req->a_res;
- int status;
+ long timeout;
+ int status;
if (!host->h_monitored && nsm_monitor(host) < 0) {
printk(KERN_NOTICE "lockd: failed to monitor %s\n",
host->h_name);
- return -ENOLCK;
+ status = -ENOLCK;
+ goto out;
}
- do {
- if ((status = nlmclnt_call(req, NLMPROC_LOCK)) >= 0) {
- if (resp->status != NLM_LCK_BLOCKED)
- break;
- status = nlmclnt_block(host, fl, &resp->status);
- }
+ if (req->a_args.block) {
+ status = nlmclnt_prepare_block(req, host, fl);
+ if (status < 0)
+ goto out;
+ }
+ for(;;) {
+ status = nlmclnt_call(req, NLMPROC_LOCK);
if (status < 0)
- return status;
- } while (resp->status == NLM_LCK_BLOCKED && req->a_args.block);
+ goto out_unblock;
+ if (resp->status != NLM_LCK_BLOCKED)
+ break;
+ /* Wait on an NLM blocking lock */
+ timeout = nlmclnt_block(req, NLMCLNT_POLL_TIMEOUT);
+ /* Did a reclaimer thread notify us of a server reboot? */
+ if (resp->status == NLM_LCK_DENIED_GRACE_PERIOD)
+ continue;
+ if (resp->status != NLM_LCK_BLOCKED)
+ break;
+ if (timeout >= 0)
+ continue;
+ /* We were interrupted. Send a CANCEL request to the server
+ * and exit
+ */
+ status = (int)timeout;
+ goto out_unblock;
+ }
if (resp->status == NLM_LCK_GRANTED) {
fl->fl_u.nfs_fl.state = host->h_state;
fl->fl_u.nfs_fl.flags |= NFS_LCK_GRANTED;
- fl->fl_u.nfs_fl.host = host;
- fl->fl_insert = nlmclnt_insert_lock_callback;
- fl->fl_remove = nlmclnt_remove_lock_callback;
+ fl->fl_flags |= FL_SLEEP;
+ do_vfs_lock(fl);
}
-
- return nlm_stat_to_errno(resp->status);
+ status = nlm_stat_to_errno(resp->status);
+out_unblock:
+ nlmclnt_finish_block(req);
+ /* Cancel the blocked request if it is still pending */
+ if (resp->status == NLM_LCK_BLOCKED)
+ nlmclnt_cancel(host, req->a_args.block, fl);
+out:
+ nlmclnt_release_lockargs(req);
+ return status;
}
/*
* reclaimed while we're stuck in the unlock call. */
fl->fl_u.nfs_fl.flags &= ~NFS_LCK_GRANTED;
+ /*
+ * Note: the server is supposed to either grant us the unlock
+ * request, or to deny it with NLM_LCK_DENIED_GRACE_PERIOD. In either
+ * case, we want to unlock.
+ */
+ do_vfs_lock(fl);
+
if (req->a_flags & RPC_TASK_ASYNC) {
- return nlmclnt_async_call(req, NLMPROC_UNLOCK,
- nlmclnt_unlock_callback);
+ status = nlmclnt_async_call(req, NLMPROC_UNLOCK,
+ &nlmclnt_unlock_ops);
+ /* Hrmf... Do the unlock early since locks_remove_posix()
+ * really expects us to free the lock synchronously */
+ if (status < 0) {
+ nlmclnt_release_lockargs(req);
+ kfree(req);
+ }
+ return status;
}
- if ((status = nlmclnt_call(req, NLMPROC_UNLOCK)) < 0)
+ status = nlmclnt_call(req, NLMPROC_UNLOCK);
+ nlmclnt_release_lockargs(req);
+ if (status < 0)
return status;
if (resp->status == NLM_LCK_GRANTED)
return -ENOLCK;
}
-static void
-nlmclnt_unlock_callback(struct rpc_task *task)
+static void nlmclnt_unlock_callback(struct rpc_task *task, void *data)
{
- struct nlm_rqst *req = (struct nlm_rqst *) task->tk_calldata;
+ struct nlm_rqst *req = data;
int status = req->a_res.status;
if (RPC_ASSASSINATED(task))
}
if (status != NLM_LCK_GRANTED)
printk(KERN_WARNING "lockd: unexpected unlock status: %d\n", status);
-
die:
nlm_release_host(req->a_host);
+ nlmclnt_release_lockargs(req);
kfree(req);
return;
retry_rebind:
rpc_restart_call(task);
}
+static const struct rpc_call_ops nlmclnt_unlock_ops = {
+ .rpc_call_done = nlmclnt_unlock_callback,
+};
+
/*
* Cancel a blocked lock request.
* We always use an async RPC call for this in order not to hang a
* process that has been Ctrl-C'ed.
*/
-int
-nlmclnt_cancel(struct nlm_host *host, struct file_lock *fl)
+static int nlmclnt_cancel(struct nlm_host *host, int block, struct file_lock *fl)
{
struct nlm_rqst *req;
unsigned long flags;
req->a_flags = RPC_TASK_ASYNC;
nlmclnt_setlockargs(req, fl);
+ req->a_args.block = block;
- status = nlmclnt_async_call(req, NLMPROC_CANCEL,
- nlmclnt_cancel_callback);
- if (status < 0)
+ status = nlmclnt_async_call(req, NLMPROC_CANCEL, &nlmclnt_cancel_ops);
+ if (status < 0) {
+ nlmclnt_release_lockargs(req);
kfree(req);
+ }
spin_lock_irqsave(¤t->sighand->siglock, flags);
current->blocked = oldset;
return status;
}
-static void
-nlmclnt_cancel_callback(struct rpc_task *task)
+static void nlmclnt_cancel_callback(struct rpc_task *task, void *data)
{
- struct nlm_rqst *req = (struct nlm_rqst *) task->tk_calldata;
+ struct nlm_rqst *req = data;
if (RPC_ASSASSINATED(task))
goto die;
die:
nlm_release_host(req->a_host);
+ nlmclnt_release_lockargs(req);
kfree(req);
return;
retry_cancel:
+ /* Don't ever retry more than 3 times */
+ if (req->a_retries++ >= NLMCLNT_MAX_RETRIES)
+ goto die;
nlm_rebind_host(req->a_host);
rpc_restart_call(task);
rpc_delay(task, 30 * HZ);
}
+static const struct rpc_call_ops nlmclnt_cancel_ops = {
+ .rpc_call_done = nlmclnt_cancel_callback,
+};
+
/*
* Convert an NLM status code to a generic kernel errno
*/