lockd: fix a leak in nlmsvc_testlock asynchronous request handling
[safe/jmp/linux-2.6] / fs / lockd / svclock.c
index 5c054b2..84c4d5e 100644 (file)
@@ -171,19 +171,14 @@ found:
  * 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;
-
        call = nlm_alloc_call(host);
        if (call == NULL)
                return NULL;
@@ -210,6 +205,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 +257,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 +328,31 @@ static void nlmsvc_freegrantargs(struct nlm_rqst *call)
 }
 
 /*
+ * 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.
  */
@@ -338,7 +360,8 @@ __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;
+       struct nlm_host         *host;
        int                     error;
        __be32                  ret;
 
@@ -350,30 +373,64 @@ nlmsvc_lock(struct svc_rqst *rqstp, struct nlm_file *file,
                                (long long)lock->fl.fl_end,
                                wait);
 
+       /* Create host handle for callback */
+       host = nlmsvc_lookup_host(rqstp, lock->caller, lock->len);
+       if (host == NULL)
+               return nlm_lck_denied_nolocks;
 
-       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, nlm_get_host(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 (!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,27 +444,13 @@ 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);
+       nlm_release_host(host);
        dprintk("lockd: nlmsvc_lock returned %u\n", ret);
        return ret;
 }
@@ -416,9 +459,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 +474,81 @@ 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);
+               struct nlm_host *host;
+
+               if (conf == NULL)
+                       return nlm_granted;
+               /* Create host handle for callback */
+               host = nlmsvc_lookup_host(rqstp, lock->caller, lock->len);
+               if (host == NULL) {
+                       kfree(conf);
+                       return nlm_lck_denied_nolocks;
+               }
+               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) {
+                       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);
+                               ret = nlm_granted;
+                               goto out;
+                       }
+               }
+               ret = nlm_drop_reply;
+               goto out;
        }
 
-       return nlm_granted;
+       error = vfs_test_lock(file->f_file, &lock->fl);
+       if (error == -EINPROGRESS) {
+               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;
+       }
+
+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 +574,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 +603,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 +612,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 +700,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 +723,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 +738,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,10 +767,7 @@ callback:
        nlmsvc_insert_block(block, 30 * HZ);
 
        /* 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);
+       nlm_async_call(block->b_call, NLMPROC_GRANTED_MSG, &nlmsvc_grant_ops);
 }
 
 /*
@@ -645,7 +817,7 @@ static const struct rpc_call_ops nlmsvc_grant_ops = {
  * block.
  */
 void
-nlmsvc_grant_reply(struct nlm_cookie *cookie, u32 status)
+nlmsvc_grant_reply(struct nlm_cookie *cookie, __be32 status)
 {
        struct nlm_block        *block;
 
@@ -655,7 +827,7 @@ nlmsvc_grant_reply(struct nlm_cookie *cookie, u32 status)
                return;
 
        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 {
@@ -667,6 +839,23 @@ nlmsvc_grant_reply(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
@@ -690,9 +879,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;