aboutsummaryrefslogtreecommitdiffstats
path: root/fs/lockd
diff options
context:
space:
mode:
authorTrond Myklebust <Trond.Myklebust@netapp.com>2005-06-22 13:16:31 -0400
committerTrond Myklebust <Trond.Myklebust@netapp.com>2005-06-22 16:07:42 -0400
commitecdbf769b2cb8903e07cd482334c714d89fd1146 (patch)
tree9d02ce4daee662c2711762564662cebc521e3da3 /fs/lockd
parent4f15e2b1f4f3a56e46201714b39436c32218d547 (diff)
[PATCH] NLM: fix a client-side race on blocking locks.
If the lock blocks, the server may send us a GRANTED message that races with the reply to our LOCK request. Make sure that we catch the GRANTED by queueing up our request on the nlm_blocked list before we send off the first LOCK rpc call. Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs/lockd')
-rw-r--r--fs/lockd/clntlock.c99
-rw-r--r--fs/lockd/clntproc.c40
2 files changed, 90 insertions, 49 deletions
diff --git a/fs/lockd/clntlock.c b/fs/lockd/clntlock.c
index 44adb84183b6..006bb9e14579 100644
--- a/fs/lockd/clntlock.c
+++ b/fs/lockd/clntlock.c
@@ -42,23 +42,51 @@ struct nlm_wait {
42static LIST_HEAD(nlm_blocked); 42static LIST_HEAD(nlm_blocked);
43 43
44/* 44/*
45 * Block on a lock 45 * Queue up a lock for blocking so that the GRANTED request can see it
46 */ 46 */
47int 47int nlmclnt_prepare_block(struct nlm_rqst *req, struct nlm_host *host, struct file_lock *fl)
48nlmclnt_block(struct nlm_host *host, struct file_lock *fl, u32 *statp) 48{
49 struct nlm_wait *block;
50
51 BUG_ON(req->a_block != NULL);
52 block = kmalloc(sizeof(*block), GFP_KERNEL);
53 if (block == NULL)
54 return -ENOMEM;
55 block->b_host = host;
56 block->b_lock = fl;
57 init_waitqueue_head(&block->b_wait);
58 block->b_status = NLM_LCK_BLOCKED;
59
60 list_add(&block->b_list, &nlm_blocked);
61 req->a_block = block;
62
63 return 0;
64}
65
66void nlmclnt_finish_block(struct nlm_rqst *req)
49{ 67{
50 struct nlm_wait block, **head; 68 struct nlm_wait *block = req->a_block;
51 int err;
52 u32 pstate;
53 69
54 block.b_host = host; 70 if (block == NULL)
55 block.b_lock = fl; 71 return;
56 init_waitqueue_head(&block.b_wait); 72 req->a_block = NULL;
57 block.b_status = NLM_LCK_BLOCKED; 73 list_del(&block->b_list);
58 list_add(&block.b_list, &nlm_blocked); 74 kfree(block);
75}
59 76
60 /* Remember pseudo nsm state */ 77/*
61 pstate = host->h_state; 78 * Block on a lock
79 */
80long nlmclnt_block(struct nlm_rqst *req, long timeout)
81{
82 struct nlm_wait *block = req->a_block;
83 long ret;
84
85 /* A borken server might ask us to block even if we didn't
86 * request it. Just say no!
87 */
88 if (!req->a_args.block)
89 return -EAGAIN;
62 90
63 /* Go to sleep waiting for GRANT callback. Some servers seem 91 /* Go to sleep waiting for GRANT callback. Some servers seem
64 * to lose callbacks, however, so we're going to poll from 92 * to lose callbacks, however, so we're going to poll from
@@ -68,23 +96,16 @@ nlmclnt_block(struct nlm_host *host, struct file_lock *fl, u32 *statp)
68 * a 1 minute timeout would do. See the comment before 96 * a 1 minute timeout would do. See the comment before
69 * nlmclnt_lock for an explanation. 97 * nlmclnt_lock for an explanation.
70 */ 98 */
71 sleep_on_timeout(&block.b_wait, 30*HZ); 99 ret = wait_event_interruptible_timeout(block->b_wait,
100 block->b_status != NLM_LCK_BLOCKED,
101 timeout);
72 102
73 list_del(&block.b_list); 103 if (block->b_status != NLM_LCK_BLOCKED) {
74 104 req->a_res.status = block->b_status;
75 if (!signalled()) { 105 block->b_status = NLM_LCK_BLOCKED;
76 *statp = block.b_status;
77 return 0;
78 } 106 }
79 107
80 /* Okay, we were interrupted. Cancel the pending request 108 return ret;
81 * unless the server has rebooted.
82 */
83 if (pstate == host->h_state && (err = nlmclnt_cancel(host, fl)) < 0)
84 printk(KERN_NOTICE
85 "lockd: CANCEL call failed (errno %d)\n", -err);
86
87 return -ERESTARTSYS;
88} 109}
89 110
90/* 111/*
@@ -94,27 +115,23 @@ u32
94nlmclnt_grant(struct nlm_lock *lock) 115nlmclnt_grant(struct nlm_lock *lock)
95{ 116{
96 struct nlm_wait *block; 117 struct nlm_wait *block;
118 u32 res = nlm_lck_denied;
97 119
98 /* 120 /*
99 * Look up blocked request based on arguments. 121 * Look up blocked request based on arguments.
100 * Warning: must not use cookie to match it! 122 * Warning: must not use cookie to match it!
101 */ 123 */
102 list_for_each_entry(block, &nlm_blocked, b_list) { 124 list_for_each_entry(block, &nlm_blocked, b_list) {
103 if (nlm_compare_locks(block->b_lock, &lock->fl)) 125 if (nlm_compare_locks(block->b_lock, &lock->fl)) {
104 break; 126 /* Alright, we found a lock. Set the return status
127 * and wake up the caller
128 */
129 block->b_status = NLM_LCK_GRANTED;
130 wake_up(&block->b_wait);
131 res = nlm_granted;
132 }
105 } 133 }
106 134 return res;
107 /* Ooops, no blocked request found. */
108 if (block == NULL)
109 return nlm_lck_denied;
110
111 /* Alright, we found the lock. Set the return status and
112 * wake up the caller.
113 */
114 block->b_status = NLM_LCK_GRANTED;
115 wake_up(&block->b_wait);
116
117 return nlm_granted;
118} 135}
119 136
120/* 137/*
diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c
index a4407619b1f1..fd77ed1d710d 100644
--- a/fs/lockd/clntproc.c
+++ b/fs/lockd/clntproc.c
@@ -21,6 +21,7 @@
21 21
22#define NLMDBG_FACILITY NLMDBG_CLIENT 22#define NLMDBG_FACILITY NLMDBG_CLIENT
23#define NLMCLNT_GRACE_WAIT (5*HZ) 23#define NLMCLNT_GRACE_WAIT (5*HZ)
24#define NLMCLNT_POLL_TIMEOUT (30*HZ)
24 25
25static int nlmclnt_test(struct nlm_rqst *, struct file_lock *); 26static int nlmclnt_test(struct nlm_rqst *, struct file_lock *);
26static int nlmclnt_lock(struct nlm_rqst *, struct file_lock *); 27static int nlmclnt_lock(struct nlm_rqst *, struct file_lock *);
@@ -553,7 +554,8 @@ nlmclnt_lock(struct nlm_rqst *req, struct file_lock *fl)
553{ 554{
554 struct nlm_host *host = req->a_host; 555 struct nlm_host *host = req->a_host;
555 struct nlm_res *resp = &req->a_res; 556 struct nlm_res *resp = &req->a_res;
556 int status; 557 long timeout;
558 int status;
557 559
558 if (!host->h_monitored && nsm_monitor(host) < 0) { 560 if (!host->h_monitored && nsm_monitor(host) < 0) {
559 printk(KERN_NOTICE "lockd: failed to monitor %s\n", 561 printk(KERN_NOTICE "lockd: failed to monitor %s\n",
@@ -562,15 +564,32 @@ nlmclnt_lock(struct nlm_rqst *req, struct file_lock *fl)
562 goto out; 564 goto out;
563 } 565 }
564 566
565 do { 567 if (req->a_args.block) {
566 if ((status = nlmclnt_call(req, NLMPROC_LOCK)) >= 0) { 568 status = nlmclnt_prepare_block(req, host, fl);
567 if (resp->status != NLM_LCK_BLOCKED)
568 break;
569 status = nlmclnt_block(host, fl, &resp->status);
570 }
571 if (status < 0) 569 if (status < 0)
572 goto out; 570 goto out;
573 } while (resp->status == NLM_LCK_BLOCKED && req->a_args.block); 571 }
572 for(;;) {
573 status = nlmclnt_call(req, NLMPROC_LOCK);
574 if (status < 0)
575 goto out_unblock;
576 if (resp->status != NLM_LCK_BLOCKED)
577 break;
578 /* Wait on an NLM blocking lock */
579 timeout = nlmclnt_block(req, NLMCLNT_POLL_TIMEOUT);
580 /* Did a reclaimer thread notify us of a server reboot? */
581 if (resp->status == NLM_LCK_DENIED_GRACE_PERIOD)
582 continue;
583 if (resp->status != NLM_LCK_BLOCKED)
584 break;
585 if (timeout >= 0)
586 continue;
587 /* We were interrupted. Send a CANCEL request to the server
588 * and exit
589 */
590 status = (int)timeout;
591 goto out_unblock;
592 }
574 593
575 if (resp->status == NLM_LCK_GRANTED) { 594 if (resp->status == NLM_LCK_GRANTED) {
576 fl->fl_u.nfs_fl.state = host->h_state; 595 fl->fl_u.nfs_fl.state = host->h_state;
@@ -579,6 +598,11 @@ nlmclnt_lock(struct nlm_rqst *req, struct file_lock *fl)
579 do_vfs_lock(fl); 598 do_vfs_lock(fl);
580 } 599 }
581 status = nlm_stat_to_errno(resp->status); 600 status = nlm_stat_to_errno(resp->status);
601out_unblock:
602 nlmclnt_finish_block(req);
603 /* Cancel the blocked request if it is still pending */
604 if (resp->status == NLM_LCK_BLOCKED)
605 nlmclnt_cancel(host, fl);
582out: 606out:
583 nlmclnt_release_lockargs(req); 607 nlmclnt_release_lockargs(req);
584 return status; 608 return status;