ALSA: hda - iMac9,1 sound fixes
[safe/jmp/linux-2.6] / fs / lockd / svclock.c
index 7ab2e60..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;
 }
@@ -171,19 +173,15 @@ 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;
-
+       nlm_get_host(host);
        call = nlm_alloc_call(host);
        if (call == NULL)
                return NULL;
@@ -210,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 */
@@ -230,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.
  */
@@ -329,15 +327,17 @@ 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 u32
+static __be32
 nlmsvc_defer_lock_rqst(struct svc_rqst *rqstp, struct nlm_block *block)
 {
-       u32 status = nlm_lck_denied_nolocks;
+       __be32 status = nlm_lck_denied_nolocks;
 
        block->b_flags |= B_QUEUED;
 
@@ -351,7 +351,7 @@ nlmsvc_defer_lock_rqst(struct svc_rqst *rqstp, struct nlm_block *block)
                        status = nlm_drop_reply;
        }
        dprintk("lockd: nlmsvc_defer_lock_rqst block %p flags %d status %d\n",
-               block, block->b_flags, status);
+               block, block->b_flags, ntohl(status));
 
        return status;
 }
@@ -362,9 +362,10 @@ nlmsvc_defer_lock_rqst(struct svc_rqst *rqstp, struct nlm_block *block)
  */
 __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;
        __be32                  ret;
 
@@ -376,30 +377,74 @@ nlmsvc_lock(struct svc_rqst *rqstp, struct nlm_file *file,
                                (long long)lock->fl.fl_end,
                                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, 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, NULL);
-       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;
@@ -408,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_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;
@@ -443,9 +469,13 @@ out:
  */
 __be32
 nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file,
-               struct nlm_lock *lock, struct nlm_lock *conflock,
-               struct nlm_cookie *cookie)
+               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_path.dentry->d_inode->i_sb->s_id,
                                file->f_file->f_path.dentry->d_inode->i_ino,
@@ -453,22 +483,78 @@ nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file,
                                (long long)lock->fl.fl_start,
                                (long long)lock->fl.fl_end);
 
-       if (posix_test_lock(file->f_file, &lock->fl)) {
-               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;
-               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;
 }
 
 /*
@@ -494,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, NULL);
+       error = vfs_lock_file(file->f_file, F_SETLK, &lock->fl, NULL);
 
        return (error < 0)? nlm_lck_denied_nolocks : nlm_granted;
 }
@@ -519,10 +605,15 @@ nlmsvc_cancel_blocked(struct nlm_file *file, struct nlm_lock *lock)
                                (long long)lock->fl.fl_start,
                                (long long)lock->fl.fl_end);
 
+       if (locks_in_grace())
+               return nlm_lck_denied_grace_period;
+
        mutex_lock(&file->f_mutex);
        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);
        }
@@ -549,9 +640,8 @@ nlmsvc_update_deferred_block(struct nlm_block *block, struct file_lock *conf,
        else
                block->b_flags |= B_TIMED_OUT;
        if (conf) {
-               block->b_fl = kzalloc(sizeof(struct file_lock), GFP_KERNEL);
                if (block->b_fl)
-                       locks_copy_lock(block->b_fl, conf);
+                       __locks_copy_lock(block->b_fl, conf);
        }
 }
 
@@ -616,7 +706,7 @@ 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,
@@ -657,20 +747,20 @@ 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, NULL);
+       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;
@@ -681,11 +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 -- 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);
 
-       /* Call the client */
-       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);
 }
 
 /*
@@ -704,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. */
@@ -716,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 = {
@@ -785,12 +900,12 @@ 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;
                }