diff options
author | J. Bruce Fields <bfields@redhat.com> | 2013-08-15 16:55:26 -0400 |
---|---|---|
committer | J. Bruce Fields <bfields@redhat.com> | 2013-08-30 17:30:52 -0400 |
commit | bf7bd3e98be5c74813bee6ad496139fb0a011b3b (patch) | |
tree | 9106fb2a3bdb88298db7aa90235ba28766c64361 /fs | |
parent | 3477565e6a73da7bb50fce6ac718b31eddb37fbb (diff) |
nfsd4: fix leak of inode reference on delegation failure
This fixes a regression from 68a3396178e6688ad7367202cdf0af8ed03c8727
"nfsd4: shut down more of delegation earlier".
After that commit, nfs4_set_delegation() failures result in
nfs4_put_delegation being called, but nfs4_put_delegation doesn't free
the nfs4_file that has already been set by alloc_init_deleg().
This can result in an oops on later unmounting the exported filesystem.
Note also delaying the fi_had_conflict check we're able to return a
better error (hence give 4.1 clients a better idea why the delegation
failed; though note CONFLICT isn't an exact match here, as that's
supposed to indicate a current conflict, but all we know here is that
there was one recently).
Reported-by: Toralf Förster <toralf.foerster@gmx.de>
Tested-by: Toralf Förster <toralf.foerster@gmx.de>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/nfsd/nfs4state.c | 31 |
1 files changed, 20 insertions, 11 deletions
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index eb9cf818002a..0874998a49cd 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c | |||
@@ -368,11 +368,8 @@ static struct nfs4_delegation * | |||
368 | alloc_init_deleg(struct nfs4_client *clp, struct nfs4_ol_stateid *stp, struct svc_fh *current_fh) | 368 | alloc_init_deleg(struct nfs4_client *clp, struct nfs4_ol_stateid *stp, struct svc_fh *current_fh) |
369 | { | 369 | { |
370 | struct nfs4_delegation *dp; | 370 | struct nfs4_delegation *dp; |
371 | struct nfs4_file *fp = stp->st_file; | ||
372 | 371 | ||
373 | dprintk("NFSD alloc_init_deleg\n"); | 372 | dprintk("NFSD alloc_init_deleg\n"); |
374 | if (fp->fi_had_conflict) | ||
375 | return NULL; | ||
376 | if (num_delegations > max_delegations) | 373 | if (num_delegations > max_delegations) |
377 | return NULL; | 374 | return NULL; |
378 | dp = delegstateid(nfs4_alloc_stid(clp, deleg_slab)); | 375 | dp = delegstateid(nfs4_alloc_stid(clp, deleg_slab)); |
@@ -389,8 +386,7 @@ alloc_init_deleg(struct nfs4_client *clp, struct nfs4_ol_stateid *stp, struct sv | |||
389 | INIT_LIST_HEAD(&dp->dl_perfile); | 386 | INIT_LIST_HEAD(&dp->dl_perfile); |
390 | INIT_LIST_HEAD(&dp->dl_perclnt); | 387 | INIT_LIST_HEAD(&dp->dl_perclnt); |
391 | INIT_LIST_HEAD(&dp->dl_recall_lru); | 388 | INIT_LIST_HEAD(&dp->dl_recall_lru); |
392 | get_nfs4_file(fp); | 389 | dp->dl_file = NULL; |
393 | dp->dl_file = fp; | ||
394 | dp->dl_type = NFS4_OPEN_DELEGATE_READ; | 390 | dp->dl_type = NFS4_OPEN_DELEGATE_READ; |
395 | fh_copy_shallow(&dp->dl_fh, ¤t_fh->fh_handle); | 391 | fh_copy_shallow(&dp->dl_fh, ¤t_fh->fh_handle); |
396 | dp->dl_time = 0; | 392 | dp->dl_time = 0; |
@@ -3044,22 +3040,35 @@ static int nfs4_setlease(struct nfs4_delegation *dp) | |||
3044 | return 0; | 3040 | return 0; |
3045 | } | 3041 | } |
3046 | 3042 | ||
3047 | static int nfs4_set_delegation(struct nfs4_delegation *dp) | 3043 | static int nfs4_set_delegation(struct nfs4_delegation *dp, struct nfs4_file *fp) |
3048 | { | 3044 | { |
3049 | struct nfs4_file *fp = dp->dl_file; | 3045 | int status; |
3050 | 3046 | ||
3051 | if (!fp->fi_lease) | 3047 | if (fp->fi_had_conflict) |
3052 | return nfs4_setlease(dp); | 3048 | return -EAGAIN; |
3049 | get_nfs4_file(fp); | ||
3050 | dp->dl_file = fp; | ||
3051 | if (!fp->fi_lease) { | ||
3052 | status = nfs4_setlease(dp); | ||
3053 | if (status) | ||
3054 | goto out_free; | ||
3055 | return 0; | ||
3056 | } | ||
3053 | spin_lock(&recall_lock); | 3057 | spin_lock(&recall_lock); |
3054 | if (fp->fi_had_conflict) { | 3058 | if (fp->fi_had_conflict) { |
3055 | spin_unlock(&recall_lock); | 3059 | spin_unlock(&recall_lock); |
3056 | return -EAGAIN; | 3060 | status = -EAGAIN; |
3061 | goto out_free; | ||
3057 | } | 3062 | } |
3058 | atomic_inc(&fp->fi_delegees); | 3063 | atomic_inc(&fp->fi_delegees); |
3059 | list_add(&dp->dl_perfile, &fp->fi_delegations); | 3064 | list_add(&dp->dl_perfile, &fp->fi_delegations); |
3060 | spin_unlock(&recall_lock); | 3065 | spin_unlock(&recall_lock); |
3061 | list_add(&dp->dl_perclnt, &dp->dl_stid.sc_client->cl_delegations); | 3066 | list_add(&dp->dl_perclnt, &dp->dl_stid.sc_client->cl_delegations); |
3062 | return 0; | 3067 | return 0; |
3068 | out_free: | ||
3069 | put_nfs4_file(fp); | ||
3070 | dp->dl_file = fp; | ||
3071 | return status; | ||
3063 | } | 3072 | } |
3064 | 3073 | ||
3065 | static void nfsd4_open_deleg_none_ext(struct nfsd4_open *open, int status) | 3074 | static void nfsd4_open_deleg_none_ext(struct nfsd4_open *open, int status) |
@@ -3134,7 +3143,7 @@ nfs4_open_delegation(struct net *net, struct svc_fh *fh, | |||
3134 | dp = alloc_init_deleg(oo->oo_owner.so_client, stp, fh); | 3143 | dp = alloc_init_deleg(oo->oo_owner.so_client, stp, fh); |
3135 | if (dp == NULL) | 3144 | if (dp == NULL) |
3136 | goto out_no_deleg; | 3145 | goto out_no_deleg; |
3137 | status = nfs4_set_delegation(dp); | 3146 | status = nfs4_set_delegation(dp, stp->st_file); |
3138 | if (status) | 3147 | if (status) |
3139 | goto out_free; | 3148 | goto out_free; |
3140 | 3149 | ||