linux 2.6.16.38 w/ vs2.0.3-rc1
[linux-2.6.git] / fs / lockd / clntproc.c
index 319e28f..970b6a6 100644 (file)
@@ -7,6 +7,7 @@
  */
 
 #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
@@ -41,11 +47,82 @@ static inline void nlmclnt_next_cookie(struct nlm_cookie *c)
        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;
@@ -60,6 +137,14 @@ nlmclnt_setlockargs(struct nlm_rqst *req, struct file_lock *fl)
        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.
@@ -77,8 +162,10 @@ nlmclnt_setgrantargs(struct nlm_rqst *call, struct nlm_lock *lock)
 
        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;
        }
 
@@ -89,12 +176,15 @@ nlmclnt_setgrantargs(struct nlm_rqst *call, struct nlm_lock *lock)
 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);
 }
 
 /*
@@ -134,8 +224,7 @@ nlmclnt_proc(struct inode *inode, int cmd, struct file_lock *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 */
@@ -165,6 +254,8 @@ nlmclnt_proc(struct inode *inode, int cmd, struct file_lock *fl)
        }
        call->a_host = host;
 
+       nlmclnt_locks_init_private(fl, host);
+
        /* Set up the argument struct */
        nlmclnt_setlockargs(call, fl);
 
@@ -179,9 +270,6 @@ nlmclnt_proc(struct inode *inode, int cmd, struct file_lock *fl)
        else
                status = -EINVAL;
 
-       if (status < 0 && (call->a_flags & RPC_TASK_ASYNC))
-               kfree(call);
-
  out_restore:
        spin_lock_irqsave(&current->sighand->siglock, flags);
        current->blocked = oldset;
@@ -193,6 +281,7 @@ done:
        nlm_release_host(host);
        return status;
 }
+EXPORT_SYMBOL(nlmclnt_proc);
 
 /*
  * Allocate an NLM RPC call struct
@@ -211,8 +300,7 @@ nlmclnt_alloc_call(void)
                        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;
 }
@@ -225,6 +313,7 @@ static int nlm_wait_on_grace(wait_queue_head_t *queue)
        prepare_to_wait(queue, &wait, TASK_INTERRUPTIBLE);
        if (!signalled ()) {
                schedule_timeout(NLMCLNT_GRACE_WAIT);
+               try_to_freeze();
                if (!signalled ())
                        status = 0;
        }
@@ -235,14 +324,13 @@ static int nlm_wait_on_grace(wait_queue_head_t *queue)
 /*
  * 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,
@@ -252,9 +340,6 @@ nlmclnt_call(struct nlm_rqst *req, u32 proc)
        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;
@@ -316,8 +401,7 @@ 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;
@@ -336,19 +420,17 @@ nlmsvc_async_call(struct nlm_rqst *req, u32 proc, rpc_action callback)
        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,
@@ -363,12 +445,10 @@ nlmclnt_async_call(struct nlm_rqst *req, u32 proc, rpc_action callback)
                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;
@@ -382,7 +462,9 @@ nlmclnt_test(struct nlm_rqst *req, struct file_lock *fl)
 {
        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;
@@ -391,10 +473,9 @@ nlmclnt_test(struct nlm_rqst *req, struct file_lock *fl)
        } 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);
        }
@@ -402,18 +483,48 @@ nlmclnt_test(struct nlm_rqst *req, struct file_lock *fl)
        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__);
 }
 
 /*
@@ -441,33 +552,58 @@ nlmclnt_lock(struct nlm_rqst *req, struct file_lock *fl)
 {
        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;
 }
 
 /*
@@ -526,12 +662,28 @@ nlmclnt_unlock(struct nlm_rqst *req, struct file_lock *fl)
         * 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)
@@ -545,10 +697,9 @@ nlmclnt_unlock(struct nlm_rqst *req, struct file_lock *fl)
        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))
@@ -564,9 +715,9 @@ nlmclnt_unlock_callback(struct rpc_task *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:
@@ -575,13 +726,16 @@ die:
        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;
@@ -602,11 +756,13 @@ nlmclnt_cancel(struct nlm_host *host, struct file_lock *fl)
        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(&current->sighand->siglock, flags);
        current->blocked = oldset;
@@ -616,10 +772,9 @@ nlmclnt_cancel(struct nlm_host *host, struct file_lock *fl)
        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;
@@ -648,15 +803,23 @@ nlmclnt_cancel_callback(struct rpc_task *task)
 
 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
  */