aboutsummaryrefslogtreecommitdiffstats
path: root/fs/nfs
diff options
context:
space:
mode:
authorTrond Myklebust <Trond.Myklebust@netapp.com>2008-12-23 15:21:38 -0500
committerTrond Myklebust <Trond.Myklebust@netapp.com>2008-12-23 15:21:38 -0500
commit343104308a33c4f1e23c8e841ede95e97b870842 (patch)
treee7772538627a0a2176bd23cd3b2f1acbacd24592 /fs/nfs
parent0cb2659b818eca99235e17c04291cfa9985c14f7 (diff)
NFSv4: Fix up another delegation related race
When we can update_open_stateid(), we need to be certain that we don't race with a delegation return. While we could do this by grabbing the nfs_client->cl_lock, a dedicated spin lock in the delegation structure will scale better. Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs/nfs')
-rw-r--r--fs/nfs/delegation.c7
-rw-r--r--fs/nfs/delegation.h1
-rw-r--r--fs/nfs/nfs4proc.c80
3 files changed, 60 insertions, 28 deletions
diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index cc563cfa6940..e0cb4ee3b23e 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -140,13 +140,17 @@ static struct nfs_delegation *nfs_detach_delegation_locked(struct nfs_inode *nfs
140 140
141 if (delegation == NULL) 141 if (delegation == NULL)
142 goto nomatch; 142 goto nomatch;
143 spin_lock(&delegation->lock);
143 if (stateid != NULL && memcmp(delegation->stateid.data, stateid->data, 144 if (stateid != NULL && memcmp(delegation->stateid.data, stateid->data,
144 sizeof(delegation->stateid.data)) != 0) 145 sizeof(delegation->stateid.data)) != 0)
145 goto nomatch; 146 goto nomatch_unlock;
146 list_del_rcu(&delegation->super_list); 147 list_del_rcu(&delegation->super_list);
147 nfsi->delegation_state = 0; 148 nfsi->delegation_state = 0;
148 rcu_assign_pointer(nfsi->delegation, NULL); 149 rcu_assign_pointer(nfsi->delegation, NULL);
150 spin_unlock(&delegation->lock);
149 return delegation; 151 return delegation;
152nomatch_unlock:
153 spin_unlock(&delegation->lock);
150nomatch: 154nomatch:
151 return NULL; 155 return NULL;
152} 156}
@@ -172,6 +176,7 @@ int nfs_inode_set_delegation(struct inode *inode, struct rpc_cred *cred, struct
172 delegation->change_attr = nfsi->change_attr; 176 delegation->change_attr = nfsi->change_attr;
173 delegation->cred = get_rpccred(cred); 177 delegation->cred = get_rpccred(cred);
174 delegation->inode = inode; 178 delegation->inode = inode;
179 spin_lock_init(&delegation->lock);
175 180
176 spin_lock(&clp->cl_lock); 181 spin_lock(&clp->cl_lock);
177 if (rcu_dereference(nfsi->delegation) != NULL) { 182 if (rcu_dereference(nfsi->delegation) != NULL) {
diff --git a/fs/nfs/delegation.h b/fs/nfs/delegation.h
index f1c5e2a5d88e..8299c6220e95 100644
--- a/fs/nfs/delegation.h
+++ b/fs/nfs/delegation.h
@@ -22,6 +22,7 @@ struct nfs_delegation {
22 long flags; 22 long flags;
23 loff_t maxsize; 23 loff_t maxsize;
24 __u64 change_attr; 24 __u64 change_attr;
25 spinlock_t lock;
25 struct rcu_head rcu; 26 struct rcu_head rcu;
26}; 27};
27 28
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 83e700a2b0c0..254cbff103f5 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -388,9 +388,8 @@ static void nfs_set_open_stateid(struct nfs4_state *state, nfs4_stateid *stateid
388 write_sequnlock(&state->seqlock); 388 write_sequnlock(&state->seqlock);
389} 389}
390 390
391static void update_open_stateid(struct nfs4_state *state, nfs4_stateid *open_stateid, nfs4_stateid *deleg_stateid, int open_flags) 391static void __update_open_stateid(struct nfs4_state *state, nfs4_stateid *open_stateid, const nfs4_stateid *deleg_stateid, int open_flags)
392{ 392{
393 open_flags &= (FMODE_READ|FMODE_WRITE);
394 /* 393 /*
395 * Protect the call to nfs4_state_set_mode_locked and 394 * Protect the call to nfs4_state_set_mode_locked and
396 * serialise the stateid update 395 * serialise the stateid update
@@ -408,6 +407,45 @@ static void update_open_stateid(struct nfs4_state *state, nfs4_stateid *open_sta
408 spin_unlock(&state->owner->so_lock); 407 spin_unlock(&state->owner->so_lock);
409} 408}
410 409
410static int update_open_stateid(struct nfs4_state *state, nfs4_stateid *open_stateid, nfs4_stateid *delegation, int open_flags)
411{
412 struct nfs_inode *nfsi = NFS_I(state->inode);
413 struct nfs_delegation *deleg_cur;
414 int ret = 0;
415
416 open_flags &= (FMODE_READ|FMODE_WRITE);
417
418 rcu_read_lock();
419 deleg_cur = rcu_dereference(nfsi->delegation);
420 if (deleg_cur == NULL)
421 goto no_delegation;
422
423 spin_lock(&deleg_cur->lock);
424 if (nfsi->delegation != deleg_cur ||
425 (deleg_cur->type & open_flags) != open_flags)
426 goto no_delegation_unlock;
427
428 if (delegation == NULL)
429 delegation = &deleg_cur->stateid;
430 else if (memcmp(deleg_cur->stateid.data, delegation->data, NFS4_STATEID_SIZE) != 0)
431 goto no_delegation_unlock;
432
433 __update_open_stateid(state, open_stateid, &deleg_cur->stateid, open_flags);
434 ret = 1;
435no_delegation_unlock:
436 spin_unlock(&deleg_cur->lock);
437no_delegation:
438 rcu_read_unlock();
439
440 if (!ret && open_stateid != NULL) {
441 __update_open_stateid(state, open_stateid, NULL, open_flags);
442 ret = 1;
443 }
444
445 return ret;
446}
447
448
411static void nfs4_return_incompatible_delegation(struct inode *inode, mode_t open_flags) 449static void nfs4_return_incompatible_delegation(struct inode *inode, mode_t open_flags)
412{ 450{
413 struct nfs_delegation *delegation; 451 struct nfs_delegation *delegation;
@@ -431,23 +469,23 @@ static struct nfs4_state *nfs4_try_open_cached(struct nfs4_opendata *opendata)
431 nfs4_stateid stateid; 469 nfs4_stateid stateid;
432 int ret = -EAGAIN; 470 int ret = -EAGAIN;
433 471
434 rcu_read_lock();
435 delegation = rcu_dereference(nfsi->delegation);
436 for (;;) { 472 for (;;) {
437 if (can_open_cached(state, open_mode)) { 473 if (can_open_cached(state, open_mode)) {
438 spin_lock(&state->owner->so_lock); 474 spin_lock(&state->owner->so_lock);
439 if (can_open_cached(state, open_mode)) { 475 if (can_open_cached(state, open_mode)) {
440 update_open_stateflags(state, open_mode); 476 update_open_stateflags(state, open_mode);
441 spin_unlock(&state->owner->so_lock); 477 spin_unlock(&state->owner->so_lock);
442 rcu_read_unlock();
443 goto out_return_state; 478 goto out_return_state;
444 } 479 }
445 spin_unlock(&state->owner->so_lock); 480 spin_unlock(&state->owner->so_lock);
446 } 481 }
447 if (delegation == NULL) 482 rcu_read_lock();
448 break; 483 delegation = rcu_dereference(nfsi->delegation);
449 if (!can_open_delegated(delegation, open_mode)) 484 if (delegation == NULL ||
485 !can_open_delegated(delegation, open_mode)) {
486 rcu_read_unlock();
450 break; 487 break;
488 }
451 /* Save the delegation */ 489 /* Save the delegation */
452 memcpy(stateid.data, delegation->stateid.data, sizeof(stateid.data)); 490 memcpy(stateid.data, delegation->stateid.data, sizeof(stateid.data));
453 rcu_read_unlock(); 491 rcu_read_unlock();
@@ -455,19 +493,11 @@ static struct nfs4_state *nfs4_try_open_cached(struct nfs4_opendata *opendata)
455 if (ret != 0) 493 if (ret != 0)
456 goto out; 494 goto out;
457 ret = -EAGAIN; 495 ret = -EAGAIN;
458 rcu_read_lock(); 496
459 delegation = rcu_dereference(nfsi->delegation); 497 /* Try to update the stateid using the delegation */
460 /* If no delegation, try a cached open */ 498 if (update_open_stateid(state, NULL, &stateid, open_mode))
461 if (delegation == NULL) 499 goto out_return_state;
462 continue;
463 /* Is the delegation still valid? */
464 if (memcmp(stateid.data, delegation->stateid.data, sizeof(stateid.data)) != 0)
465 continue;
466 rcu_read_unlock();
467 update_open_stateid(state, NULL, &stateid, open_mode);
468 goto out_return_state;
469 } 500 }
470 rcu_read_unlock();
471out: 501out:
472 return ERR_PTR(ret); 502 return ERR_PTR(ret);
473out_return_state: 503out_return_state:
@@ -480,7 +510,6 @@ static struct nfs4_state *nfs4_opendata_to_nfs4_state(struct nfs4_opendata *data
480 struct inode *inode; 510 struct inode *inode;
481 struct nfs4_state *state = NULL; 511 struct nfs4_state *state = NULL;
482 struct nfs_delegation *delegation; 512 struct nfs_delegation *delegation;
483 nfs4_stateid *deleg_stateid = NULL;
484 int ret; 513 int ret;
485 514
486 if (!data->rpc_done) { 515 if (!data->rpc_done) {
@@ -516,12 +545,9 @@ static struct nfs4_state *nfs4_opendata_to_nfs4_state(struct nfs4_opendata *data
516 data->owner->so_cred, 545 data->owner->so_cred,
517 &data->o_res); 546 &data->o_res);
518 } 547 }
519 rcu_read_lock(); 548
520 delegation = rcu_dereference(NFS_I(inode)->delegation); 549 update_open_stateid(state, &data->o_res.stateid, NULL,
521 if (delegation != NULL) 550 data->o_arg.open_flags);
522 deleg_stateid = &delegation->stateid;
523 update_open_stateid(state, &data->o_res.stateid, deleg_stateid, data->o_arg.open_flags);
524 rcu_read_unlock();
525 iput(inode); 551 iput(inode);
526out: 552out:
527 return state; 553 return state;