diff options
author | Trond Myklebust <Trond.Myklebust@netapp.com> | 2008-04-01 20:26:22 -0400 |
---|---|---|
committer | Trond Myklebust <Trond.Myklebust@netapp.com> | 2008-04-19 16:53:49 -0400 |
commit | 5f50c0c6d644d6c8180d9079c13c5d9de3adeb34 (patch) | |
tree | 8cc145c4c3fafc1ea23e0e20929238e6318a44a5 /fs/lockd/clntproc.c | |
parent | 6b4b3a752b3464f2fd9fe2837fb19270c23c1d6b (diff) |
NLM/lockd: Fix a race when cancelling a blocking lock
We shouldn't remove the lock from the list of blocked locks until the
CANCEL call has completed since we may be racing with a GRANTED callback.
Also ensure that we send an UNLOCK if the CANCEL request failed. Normally
that should only happen if the process gets hit with a fatal signal.
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs/lockd/clntproc.c')
-rw-r--r-- | fs/lockd/clntproc.c | 43 |
1 files changed, 34 insertions, 9 deletions
diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c index ea1a6940af22..37d1aa20a607 100644 --- a/fs/lockd/clntproc.c +++ b/fs/lockd/clntproc.c | |||
@@ -510,6 +510,7 @@ nlmclnt_lock(struct nlm_rqst *req, struct file_lock *fl) | |||
510 | struct nlm_res *resp = &req->a_res; | 510 | struct nlm_res *resp = &req->a_res; |
511 | struct nlm_wait *block = NULL; | 511 | struct nlm_wait *block = NULL; |
512 | unsigned char fl_flags = fl->fl_flags; | 512 | unsigned char fl_flags = fl->fl_flags; |
513 | unsigned char fl_type; | ||
513 | int status = -ENOLCK; | 514 | int status = -ENOLCK; |
514 | 515 | ||
515 | if (nsm_monitor(host) < 0) { | 516 | if (nsm_monitor(host) < 0) { |
@@ -525,13 +526,16 @@ nlmclnt_lock(struct nlm_rqst *req, struct file_lock *fl) | |||
525 | 526 | ||
526 | block = nlmclnt_prepare_block(host, fl); | 527 | block = nlmclnt_prepare_block(host, fl); |
527 | again: | 528 | again: |
529 | /* | ||
530 | * Initialise resp->status to a valid non-zero value, | ||
531 | * since 0 == nlm_lck_granted | ||
532 | */ | ||
533 | resp->status = nlm_lck_blocked; | ||
528 | for(;;) { | 534 | for(;;) { |
529 | /* Reboot protection */ | 535 | /* Reboot protection */ |
530 | fl->fl_u.nfs_fl.state = host->h_state; | 536 | fl->fl_u.nfs_fl.state = host->h_state; |
531 | status = nlmclnt_call(req, NLMPROC_LOCK); | 537 | status = nlmclnt_call(req, NLMPROC_LOCK); |
532 | if (status < 0) | 538 | if (status < 0) |
533 | goto out_unblock; | ||
534 | if (!req->a_args.block) | ||
535 | break; | 539 | break; |
536 | /* Did a reclaimer thread notify us of a server reboot? */ | 540 | /* Did a reclaimer thread notify us of a server reboot? */ |
537 | if (resp->status == nlm_lck_denied_grace_period) | 541 | if (resp->status == nlm_lck_denied_grace_period) |
@@ -540,15 +544,22 @@ again: | |||
540 | break; | 544 | break; |
541 | /* Wait on an NLM blocking lock */ | 545 | /* Wait on an NLM blocking lock */ |
542 | status = nlmclnt_block(block, req, NLMCLNT_POLL_TIMEOUT); | 546 | status = nlmclnt_block(block, req, NLMCLNT_POLL_TIMEOUT); |
543 | /* if we were interrupted. Send a CANCEL request to the server | ||
544 | * and exit | ||
545 | */ | ||
546 | if (status < 0) | 547 | if (status < 0) |
547 | goto out_unblock; | 548 | break; |
548 | if (resp->status != nlm_lck_blocked) | 549 | if (resp->status != nlm_lck_blocked) |
549 | break; | 550 | break; |
550 | } | 551 | } |
551 | 552 | ||
553 | /* if we were interrupted while blocking, then cancel the lock request | ||
554 | * and exit | ||
555 | */ | ||
556 | if (resp->status == nlm_lck_blocked) { | ||
557 | if (!req->a_args.block) | ||
558 | goto out_unlock; | ||
559 | if (nlmclnt_cancel(host, req->a_args.block, fl) == 0) | ||
560 | goto out_unblock; | ||
561 | } | ||
562 | |||
552 | if (resp->status == nlm_granted) { | 563 | if (resp->status == nlm_granted) { |
553 | down_read(&host->h_rwsem); | 564 | down_read(&host->h_rwsem); |
554 | /* Check whether or not the server has rebooted */ | 565 | /* Check whether or not the server has rebooted */ |
@@ -562,16 +573,30 @@ again: | |||
562 | printk(KERN_WARNING "%s: VFS is out of sync with lock manager!\n", __FUNCTION__); | 573 | printk(KERN_WARNING "%s: VFS is out of sync with lock manager!\n", __FUNCTION__); |
563 | up_read(&host->h_rwsem); | 574 | up_read(&host->h_rwsem); |
564 | fl->fl_flags = fl_flags; | 575 | fl->fl_flags = fl_flags; |
576 | status = 0; | ||
565 | } | 577 | } |
578 | if (status < 0) | ||
579 | goto out_unlock; | ||
566 | status = nlm_stat_to_errno(resp->status); | 580 | status = nlm_stat_to_errno(resp->status); |
567 | out_unblock: | 581 | out_unblock: |
568 | nlmclnt_finish_block(block); | 582 | nlmclnt_finish_block(block); |
569 | /* Cancel the blocked request if it is still pending */ | ||
570 | if (resp->status == nlm_lck_blocked) | ||
571 | nlmclnt_cancel(host, req->a_args.block, fl); | ||
572 | out: | 583 | out: |
573 | nlm_release_call(req); | 584 | nlm_release_call(req); |
574 | return status; | 585 | return status; |
586 | out_unlock: | ||
587 | /* Fatal error: ensure that we remove the lock altogether */ | ||
588 | dprintk("lockd: lock attempt ended in fatal error.\n" | ||
589 | " Attempting to unlock.\n"); | ||
590 | nlmclnt_finish_block(block); | ||
591 | fl_type = fl->fl_type; | ||
592 | fl->fl_type = F_UNLCK; | ||
593 | down_read(&host->h_rwsem); | ||
594 | do_vfs_lock(fl); | ||
595 | up_read(&host->h_rwsem); | ||
596 | fl->fl_type = fl_type; | ||
597 | fl->fl_flags = fl_flags; | ||
598 | nlmclnt_async_call(req, NLMPROC_UNLOCK, &nlmclnt_unlock_ops); | ||
599 | return status; | ||
575 | } | 600 | } |
576 | 601 | ||
577 | /* | 602 | /* |