diff options
author | Trond Myklebust <Trond.Myklebust@netapp.com> | 2008-12-23 15:21:38 -0500 |
---|---|---|
committer | Trond Myklebust <Trond.Myklebust@netapp.com> | 2008-12-23 15:21:38 -0500 |
commit | 343104308a33c4f1e23c8e841ede95e97b870842 (patch) | |
tree | e7772538627a0a2176bd23cd3b2f1acbacd24592 /fs | |
parent | 0cb2659b818eca99235e17c04291cfa9985c14f7 (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')
-rw-r--r-- | fs/nfs/delegation.c | 7 | ||||
-rw-r--r-- | fs/nfs/delegation.h | 1 | ||||
-rw-r--r-- | fs/nfs/nfs4proc.c | 80 |
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; |
152 | nomatch_unlock: | ||
153 | spin_unlock(&delegation->lock); | ||
150 | nomatch: | 154 | nomatch: |
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 | ||
391 | static void update_open_stateid(struct nfs4_state *state, nfs4_stateid *open_stateid, nfs4_stateid *deleg_stateid, int open_flags) | 391 | static 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 | ||
410 | static 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; | ||
435 | no_delegation_unlock: | ||
436 | spin_unlock(&deleg_cur->lock); | ||
437 | no_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 | |||
411 | static void nfs4_return_incompatible_delegation(struct inode *inode, mode_t open_flags) | 449 | static 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(); | ||
471 | out: | 501 | out: |
472 | return ERR_PTR(ret); | 502 | return ERR_PTR(ret); |
473 | out_return_state: | 503 | out_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); |
526 | out: | 552 | out: |
527 | return state; | 553 | return state; |