Merge branch 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux...
[safe/jmp/linux-2.6] / fs / lockd / svclock.c
index 1f91567..84055d3 100644 (file)
@@ -21,6 +21,7 @@
  */
 
 #include <linux/types.h>
+#include <linux/slab.h>
 #include <linux/errno.h>
 #include <linux/kernel.h>
 #include <linux/sched.h>
@@ -29,6 +30,7 @@
 #include <linux/sunrpc/svc.h>
 #include <linux/lockd/nlm.h>
 #include <linux/lockd/lockd.h>
+#include <linux/kthread.h>
 
 #define NLMDBG_FACILITY                NLMDBG_SVCLOCK
 
@@ -128,9 +130,9 @@ nlmsvc_lookup_block(struct nlm_file *file, struct nlm_lock *lock)
 
 static inline int nlm_cookie_match(struct nlm_cookie *a, struct nlm_cookie *b)
 {
-       if(a->len != b->len)
+       if (a->len != b->len)
                return 0;
-       if(memcmp(a->data,b->data,a->len))
+       if (memcmp(a->data, b->data, a->len))
                return 0;
        return 1;
 }
@@ -139,19 +141,19 @@ static inline int nlm_cookie_match(struct nlm_cookie *a, struct nlm_cookie *b)
  * Find a block with a given NLM cookie.
  */
 static inline struct nlm_block *
-nlmsvc_find_block(struct nlm_cookie *cookie,  struct sockaddr_in *sin)
+nlmsvc_find_block(struct nlm_cookie *cookie)
 {
        struct nlm_block *block;
 
        list_for_each_entry(block, &nlm_blocked, b_list) {
-               if (nlm_cookie_match(&block->b_call->a_args.cookie,cookie)
-                               && nlm_cmp_addr(sin, &block->b_host->h_addr))
+               if (nlm_cookie_match(&block->b_call->a_args.cookie,cookie))
                        goto found;
        }
 
        return NULL;
 
 found:
+       dprintk("nlmsvc_find_block(%s): block=%p\n", nlmdbg_cookie2a(cookie), block);
        kref_get(&block->b_count);
        return block;
 }
@@ -165,20 +167,21 @@ found:
  * request, but (as I found out later) that's because some implementations
  * do just this. Never mind the standards comittees, they support our
  * logging industries.
+ *
+ * 10 years later: I hope we can safely ignore these old and broken
+ * clients by now. Let's fix this so we can uniquely identify an incoming
+ * GRANTED_RES message by cookie, without having to rely on the client's IP
+ * address. --okir
  */
-static inline struct nlm_block *
-nlmsvc_create_block(struct svc_rqst *rqstp, struct nlm_file *file,
-                               struct nlm_lock *lock, struct nlm_cookie *cookie)
+static struct nlm_block *
+nlmsvc_create_block(struct svc_rqst *rqstp, struct nlm_host *host,
+                   struct nlm_file *file, struct nlm_lock *lock,
+                   struct nlm_cookie *cookie)
 {
        struct nlm_block        *block;
-       struct nlm_host         *host;
        struct nlm_rqst         *call = NULL;
 
-       /* Create host handle for callback */
-       host = nlmsvc_lookup_host(rqstp, lock->caller, lock->len);
-       if (host == NULL)
-               return NULL;
-
+       nlm_get_host(host);
        call = nlm_alloc_call(host);
        if (call == NULL)
                return NULL;
@@ -197,7 +200,7 @@ nlmsvc_create_block(struct svc_rqst *rqstp, struct nlm_file *file,
        /* Set notifier function for VFS, and init args */
        call->a_args.lock.fl.fl_flags |= FL_SLEEP;
        call->a_args.lock.fl.fl_lmops = &nlmsvc_lock_operations;
-       call->a_args.cookie = *cookie;  /* see above */
+       nlmclnt_next_cookie(&call->a_args.cookie);
 
        dprintk("lockd: created block %p...\n", block);
 
@@ -205,6 +208,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 */
@@ -225,8 +229,7 @@ failed:
 }
 
 /*
- * Delete a block. If the lock was cancelled or the grant callback
- * failed, unlock is set to 1.
+ * Delete a block.
  * It is the caller's responsibility to check whether the file
  * can be closed hereafter.
  */
@@ -249,13 +252,14 @@ static void nlmsvc_free_block(struct kref *kref)
        dprintk("lockd: freeing block %p...\n", block);
 
        /* Remove block from file's list of blocks */
-       down(&file->f_sema);
+       mutex_lock(&file->f_mutex);
        list_del_init(&block->b_flist);
-       up(&file->f_sema);
+       mutex_unlock(&file->f_mutex);
 
        nlmsvc_freegrantargs(block->b_call);
        nlm_release_call(block->b_call);
        nlm_release_file(block->b_file);
+       kfree(block->b_fl);
        kfree(block);
 }
 
@@ -276,7 +280,7 @@ void nlmsvc_traverse_blocks(struct nlm_host *host,
        struct nlm_block *block, *next;
 
 restart:
-       down(&file->f_sema);
+       mutex_lock(&file->f_mutex);
        list_for_each_entry_safe(block, next, &file->f_blocks, b_flist) {
                if (!match(block->b_host, host))
                        continue;
@@ -285,12 +289,12 @@ restart:
                if (list_empty(&block->b_list))
                        continue;
                kref_get(&block->b_count);
-               up(&file->f_sema);
+               mutex_unlock(&file->f_mutex);
                nlmsvc_unlink_block(block);
                nlmsvc_release_block(block);
                goto restart;
        }
-       up(&file->f_sema);
+       mutex_unlock(&file->f_mutex);
 }
 
 /*
@@ -323,52 +327,124 @@ static void nlmsvc_freegrantargs(struct nlm_rqst *call)
 {
        if (call->a_args.lock.oh.data != call->a_owner)
                kfree(call->a_args.lock.oh.data);
+
+       locks_release_private(&call->a_args.lock.fl);
+}
+
+/*
+ * Deferred lock request handling for non-blocking lock
+ */
+static __be32
+nlmsvc_defer_lock_rqst(struct svc_rqst *rqstp, struct nlm_block *block)
+{
+       __be32 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, ntohl(status));
+
+       return status;
 }
 
 /*
  * Attempt to establish a lock, and if it can't be granted, block it
  * if required.
  */
-u32
+__be32
 nlmsvc_lock(struct svc_rqst *rqstp, struct nlm_file *file,
-                       struct nlm_lock *lock, int wait, struct nlm_cookie *cookie)
+           struct nlm_host *host, struct nlm_lock *lock, int wait,
+           struct nlm_cookie *cookie, int reclaim)
 {
-       struct nlm_block        *block, *newblock = NULL;
+       struct nlm_block        *block = NULL;
        int                     error;
-       u32                     ret;
+       __be32                  ret;
 
        dprintk("lockd: nlmsvc_lock(%s/%ld, ty=%d, pi=%d, %Ld-%Ld, bl=%d)\n",
-                               file->f_file->f_dentry->d_inode->i_sb->s_id,
-                               file->f_file->f_dentry->d_inode->i_ino,
+                               file->f_file->f_path.dentry->d_inode->i_sb->s_id,
+                               file->f_file->f_path.dentry->d_inode->i_ino,
                                lock->fl.fl_type, lock->fl.fl_pid,
                                (long long)lock->fl.fl_start,
                                (long long)lock->fl.fl_end,
                                wait);
 
-
-       lock->fl.fl_flags &= ~FL_SLEEP;
-again:
        /* Lock file against concurrent access */
-       down(&file->f_sema);
-       /* Get existing block (in case client is busy-waiting) */
+       mutex_lock(&file->f_mutex);
+       /* 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, host, 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 (locks_in_grace() && !reclaim) {
+               ret = nlm_lck_denied_grace_period;
+               goto out;
+       }
+       if (reclaim && !locks_in_grace()) {
+               ret = nlm_lck_denied_grace_period;
+               goto out;
+       }
 
-       switch(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:
-                       break;
+                       /*
+                        * If this is a blocking request for an
+                        * already pending lock request then we need
+                        * to put it back on lockd's block list
+                        */
+                       if (wait)
+                               break;
+                       ret = nlm_lck_denied;
+                       goto out;
+               case FILE_LOCK_DEFERRED:
+                       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;
@@ -377,31 +453,12 @@ again:
                        goto out;
        }
 
-       ret = nlm_lck_denied;
-       if (!wait)
-               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_sema as nlmsvc_create_block may try to
-        * to claim it while doing host garbage collection */
-       if (newblock == NULL) {
-               up(&file->f_sema);
-               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:
-       up(&file->f_sema);
-       nlmsvc_release_block(newblock);
+       mutex_unlock(&file->f_mutex);
        nlmsvc_release_block(block);
        dprintk("lockd: nlmsvc_lock returned %u\n", ret);
        return ret;
@@ -410,30 +467,94 @@ out:
 /*
  * Test for presence of a conflicting lock.
  */
-u32
-nlmsvc_testlock(struct nlm_file *file, struct nlm_lock *lock,
-                                      struct nlm_lock *conflock)
+__be32
+nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file,
+               struct nlm_host *host, 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_dentry->d_inode->i_sb->s_id,
-                               file->f_file->f_dentry->d_inode->i_ino,
+                               file->f_file->f_path.dentry->d_inode->i_sb->s_id,
+                               file->f_file->f_path.dentry->d_inode->i_ino,
                                lock->fl.fl_type,
                                (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, host, 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);
+                       ret = nlm_lck_denied;
+                       goto out;
+               }
+               if (block->b_flags & B_GOT_CALLBACK) {
+                       nlmsvc_unlink_block(block);
+                       if (block->b_fl != NULL
+                                       && block->b_fl->fl_type != F_UNLCK) {
+                               lock->fl = *block->b_fl;
+                               goto conf_lock;
+                       } else {
+                               ret = nlm_granted;
+                               goto out;
+                       }
+               }
+               ret = nlm_drop_reply;
+               goto out;
+       }
+
+       if (locks_in_grace()) {
+               ret = nlm_lck_denied_grace_period;
+               goto out;
+       }
+       error = vfs_test_lock(file->f_file, &lock->fl);
+       if (error == FILE_LOCK_DEFERRED) {
+               ret = nlmsvc_defer_lock_rqst(rqstp, block);
+               goto out;
+       }
+       if (error) {
+               ret = nlm_lck_denied_nolocks;
+               goto out;
+       }
+       if (lock->fl.fl_type == F_UNLCK) {
+               ret = nlm_granted;
+               goto out;
        }
 
-       return nlm_granted;
+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;
 }
 
 /*
@@ -443,14 +564,14 @@ nlmsvc_testlock(struct nlm_file *file, struct nlm_lock *lock,
  * afterwards. In this case the block will still be there, and hence
  * must be removed.
  */
-u32
+__be32
 nlmsvc_unlock(struct nlm_file *file, struct nlm_lock *lock)
 {
        int     error;
 
        dprintk("lockd: nlmsvc_unlock(%s/%ld, pi=%d, %Ld-%Ld)\n",
-                               file->f_file->f_dentry->d_inode->i_sb->s_id,
-                               file->f_file->f_dentry->d_inode->i_ino,
+                               file->f_file->f_path.dentry->d_inode->i_sb->s_id,
+                               file->f_file->f_path.dentry->d_inode->i_ino,
                                lock->fl.fl_pid,
                                (long long)lock->fl.fl_start,
                                (long long)lock->fl.fl_end);
@@ -459,7 +580,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;
 }
@@ -471,23 +592,28 @@ nlmsvc_unlock(struct nlm_file *file, struct nlm_lock *lock)
  * be in progress.
  * The calling procedure must check whether the file can be closed.
  */
-u32
+__be32
 nlmsvc_cancel_blocked(struct nlm_file *file, struct nlm_lock *lock)
 {
        struct nlm_block        *block;
        int status = 0;
 
        dprintk("lockd: nlmsvc_cancel(%s/%ld, pi=%d, %Ld-%Ld)\n",
-                               file->f_file->f_dentry->d_inode->i_sb->s_id,
-                               file->f_file->f_dentry->d_inode->i_ino,
+                               file->f_file->f_path.dentry->d_inode->i_sb->s_id,
+                               file->f_file->f_path.dentry->d_inode->i_ino,
                                lock->fl.fl_pid,
                                (long long)lock->fl.fl_start,
                                (long long)lock->fl.fl_end);
 
-       down(&file->f_sema);
+       if (locks_in_grace())
+               return nlm_lck_denied_grace_period;
+
+       mutex_lock(&file->f_mutex);
        block = nlmsvc_lookup_block(file, lock);
-       up(&file->f_sema);
+       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);
        }
@@ -495,6 +621,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.
  *
@@ -523,9 +706,10 @@ static int nlmsvc_same_owner(struct file_lock *fl1, struct file_lock *fl2)
        return fl1->fl_owner == fl2->fl_owner && fl1->fl_pid == fl2->fl_pid;
 }
 
-struct lock_manager_operations nlmsvc_lock_operations = {
+const struct lock_manager_operations nlmsvc_lock_operations = {
        .fl_compare_owner = nlmsvc_same_owner,
        .fl_notify = nlmsvc_notify_blocked,
+       .fl_grant = nlmsvc_grant_deferred,
 };
 
 /*
@@ -548,6 +732,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);
 
@@ -561,20 +747,22 @@ 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 FILE_LOCK_DEFERRED:
+               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__);
+                               -error, __func__);
                nlmsvc_insert_block(block, 10 * HZ);
+               nlmsvc_release_block(block);
                return;
        }
 
@@ -583,14 +771,20 @@ callback:
        dprintk("lockd: GRANTing blocked lock.\n");
        block->b_granted = 1;
 
-       /* Schedule next grant callback in 30 seconds */
-       nlmsvc_insert_block(block, 30 * HZ);
+       /* keep block on the list, but don't reattempt until the RPC
+        * completes or the submission fails
+        */
+       nlmsvc_insert_block(block, NLM_NEVER);
 
-       /* Call the client */
-       kref_get(&block->b_count);
-       if (nlm_async_call(block->b_call, NLMPROC_GRANTED_MSG,
-                                               &nlmsvc_grant_ops) < 0)
-               nlmsvc_release_block(block);
+       /* Call the client -- use a soft RPC task since nlmsvc_retry_blocked
+        * will queue up a new one if this one times out
+        */
+       error = nlm_async_call(block->b_call, NLMPROC_GRANTED_MSG,
+                               &nlmsvc_grant_ops);
+
+       /* RPC submission failed, wait a bit and retry */
+       if (error < 0)
+               nlmsvc_insert_block(block, 10 * HZ);
 }
 
 /*
@@ -609,6 +803,18 @@ static void nlmsvc_grant_callback(struct rpc_task *task, void *data)
 
        dprintk("lockd: GRANT_MSG RPC callback\n");
 
+       lock_kernel();
+       /* if the block is not on a list at this point then it has
+        * been invalidated. Don't try to requeue it.
+        *
+        * FIXME: it's possible that the block is removed from the list
+        * after this check but before the nlmsvc_insert_block. In that
+        * case it will be added back. Perhaps we need better locking
+        * for nlm_blocked?
+        */
+       if (list_empty(&block->b_list))
+               goto out;
+
        /* Technically, we should down the file semaphore here. Since we
         * move the block towards the head of the queue only, no harm
         * can be done, though. */
@@ -621,13 +827,17 @@ static void nlmsvc_grant_callback(struct rpc_task *task, void *data)
        }
        nlmsvc_insert_block(block, timeout);
        svc_wake_up(block->b_daemon);
+out:
+       unlock_kernel();
 }
 
 static void nlmsvc_grant_release(void *data)
 {
        struct nlm_rqst         *call = data;
 
+       lock_kernel();
        nlmsvc_release_block(call->a_block);
+       unlock_kernel();
 }
 
 static const struct rpc_call_ops nlmsvc_grant_ops = {
@@ -640,20 +850,17 @@ static const struct rpc_call_ops nlmsvc_grant_ops = {
  * block.
  */
 void
-nlmsvc_grant_reply(struct svc_rqst *rqstp, struct nlm_cookie *cookie, u32 status)
+nlmsvc_grant_reply(struct nlm_cookie *cookie, __be32 status)
 {
        struct nlm_block        *block;
-       struct nlm_file         *file;
 
-       dprintk("grant_reply: looking for cookie %x, host (%08x), s=%d \n", 
-               *(unsigned int *)(cookie->data), 
-               ntohl(rqstp->rq_addr.sin_addr.s_addr), status);
-       if (!(block = nlmsvc_find_block(cookie, &rqstp->rq_addr)))
+       dprintk("grant_reply: looking for cookie %x, s=%d \n",
+               *(unsigned int *)(cookie->data), status);
+       if (!(block = nlmsvc_find_block(cookie)))
                return;
-       file = block->b_file;
 
        if (block) {
-               if (status == NLM_LCK_DENIED_GRACE_PERIOD) {
+               if (status == nlm_lck_denied_grace_period) {
                        /* Try again in a couple of seconds */
                        nlmsvc_insert_block(block, 10 * HZ);
                } else {
@@ -665,6 +872,23 @@ nlmsvc_grant_reply(struct svc_rqst *rqstp, struct nlm_cookie *cookie, u32 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
@@ -676,21 +900,24 @@ nlmsvc_retry_blocked(void)
        unsigned long   timeout = MAX_SCHEDULE_TIMEOUT;
        struct nlm_block *block;
 
-       while (!list_empty(&nlm_blocked)) {
+       while (!list_empty(&nlm_blocked) && !kthread_should_stop()) {
                block = list_entry(nlm_blocked.next, struct nlm_block, b_list);
 
                if (block->b_when == NLM_NEVER)
                        break;
-               if (time_after(block->b_when,jiffies)) {
+               if (time_after(block->b_when, jiffies)) {
                        timeout = block->b_when - jiffies;
                        break;
                }
 
                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;