aboutsummaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorTrond Myklebust <Trond.Myklebust@netapp.com>2007-10-15 18:17:53 -0400
committerTrond Myklebust <Trond.Myklebust@netapp.com>2007-10-19 17:19:16 -0400
commit565277f63c616e11c37309a1e98c052d18ebbb55 (patch)
tree60fdddc5a1c97df696392e47ead71d33d39e487f /fs
parent61e930a904966cc37e0a3404276f0b73037e57ca (diff)
NFS: Fix a race in sillyrename
lookup() and sillyrename() can race one another because the sillyrename() completion cannot take the parent directory's inode->i_mutex since the latter may be held by whoever is calling dput(). We therefore have little option but to add extra locking to ensure that nfs_lookup() and nfs_atomic_open() do not race with the sillyrename completion. If somebody has looked up the sillyrenamed file in the meantime, we just transfer the sillydelete information to the new dentry. Please refer to the bug-report at http://bugzilla.linux-nfs.org/show_bug.cgi?id=150 Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs')
-rw-r--r--fs/nfs/dir.c14
-rw-r--r--fs/nfs/inode.c3
-rw-r--r--fs/nfs/nfs4proc.c6
-rw-r--r--fs/nfs/unlink.c114
4 files changed, 119 insertions, 18 deletions
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 8ec7fbd8240c..35334539d947 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -562,6 +562,7 @@ static int nfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
562 nfs_fattr_init(&fattr); 562 nfs_fattr_init(&fattr);
563 desc->entry = &my_entry; 563 desc->entry = &my_entry;
564 564
565 nfs_block_sillyrename(dentry);
565 while(!desc->entry->eof) { 566 while(!desc->entry->eof) {
566 res = readdir_search_pagecache(desc); 567 res = readdir_search_pagecache(desc);
567 568
@@ -592,6 +593,7 @@ static int nfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
592 break; 593 break;
593 } 594 }
594 } 595 }
596 nfs_unblock_sillyrename(dentry);
595 unlock_kernel(); 597 unlock_kernel();
596 if (res > 0) 598 if (res > 0)
597 res = 0; 599 res = 0;
@@ -866,6 +868,7 @@ struct dentry_operations nfs_dentry_operations = {
866static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, struct nameidata *nd) 868static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, struct nameidata *nd)
867{ 869{
868 struct dentry *res; 870 struct dentry *res;
871 struct dentry *parent;
869 struct inode *inode = NULL; 872 struct inode *inode = NULL;
870 int error; 873 int error;
871 struct nfs_fh fhandle; 874 struct nfs_fh fhandle;
@@ -894,26 +897,31 @@ static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, stru
894 goto out_unlock; 897 goto out_unlock;
895 } 898 }
896 899
900 parent = dentry->d_parent;
901 /* Protect against concurrent sillydeletes */
902 nfs_block_sillyrename(parent);
897 error = NFS_PROTO(dir)->lookup(dir, &dentry->d_name, &fhandle, &fattr); 903 error = NFS_PROTO(dir)->lookup(dir, &dentry->d_name, &fhandle, &fattr);
898 if (error == -ENOENT) 904 if (error == -ENOENT)
899 goto no_entry; 905 goto no_entry;
900 if (error < 0) { 906 if (error < 0) {
901 res = ERR_PTR(error); 907 res = ERR_PTR(error);
902 goto out_unlock; 908 goto out_unblock_sillyrename;
903 } 909 }
904 inode = nfs_fhget(dentry->d_sb, &fhandle, &fattr); 910 inode = nfs_fhget(dentry->d_sb, &fhandle, &fattr);
905 res = (struct dentry *)inode; 911 res = (struct dentry *)inode;
906 if (IS_ERR(res)) 912 if (IS_ERR(res))
907 goto out_unlock; 913 goto out_unblock_sillyrename;
908 914
909no_entry: 915no_entry:
910 res = d_materialise_unique(dentry, inode); 916 res = d_materialise_unique(dentry, inode);
911 if (res != NULL) { 917 if (res != NULL) {
912 if (IS_ERR(res)) 918 if (IS_ERR(res))
913 goto out_unlock; 919 goto out_unblock_sillyrename;
914 dentry = res; 920 dentry = res;
915 } 921 }
916 nfs_set_verifier(dentry, nfs_save_change_attribute(dir)); 922 nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
923out_unblock_sillyrename:
924 nfs_unblock_sillyrename(parent);
917out_unlock: 925out_unlock:
918 unlock_kernel(); 926 unlock_kernel();
919out: 927out:
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 6d2f2a3eccf8..173e294dffc5 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -1169,6 +1169,9 @@ static void init_once(struct kmem_cache * cachep, void *foo)
1169 INIT_RADIX_TREE(&nfsi->nfs_page_tree, GFP_ATOMIC); 1169 INIT_RADIX_TREE(&nfsi->nfs_page_tree, GFP_ATOMIC);
1170 nfsi->ncommit = 0; 1170 nfsi->ncommit = 0;
1171 nfsi->npages = 0; 1171 nfsi->npages = 0;
1172 atomic_set(&nfsi->silly_count, 1);
1173 INIT_HLIST_HEAD(&nfsi->silly_list);
1174 init_waitqueue_head(&nfsi->waitqueue);
1172 nfs4_init_once(nfsi); 1175 nfs4_init_once(nfsi);
1173} 1176}
1174 1177
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index cb99fd90a9ac..2cb3b8b71ee7 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -1372,6 +1372,7 @@ out_close:
1372struct dentry * 1372struct dentry *
1373nfs4_atomic_open(struct inode *dir, struct dentry *dentry, struct nameidata *nd) 1373nfs4_atomic_open(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
1374{ 1374{
1375 struct dentry *parent;
1375 struct path path = { 1376 struct path path = {
1376 .mnt = nd->mnt, 1377 .mnt = nd->mnt,
1377 .dentry = dentry, 1378 .dentry = dentry,
@@ -1394,6 +1395,9 @@ nfs4_atomic_open(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
1394 cred = rpcauth_lookupcred(NFS_CLIENT(dir)->cl_auth, 0); 1395 cred = rpcauth_lookupcred(NFS_CLIENT(dir)->cl_auth, 0);
1395 if (IS_ERR(cred)) 1396 if (IS_ERR(cred))
1396 return (struct dentry *)cred; 1397 return (struct dentry *)cred;
1398 parent = dentry->d_parent;
1399 /* Protect against concurrent sillydeletes */
1400 nfs_block_sillyrename(parent);
1397 state = nfs4_do_open(dir, &path, nd->intent.open.flags, &attr, cred); 1401 state = nfs4_do_open(dir, &path, nd->intent.open.flags, &attr, cred);
1398 put_rpccred(cred); 1402 put_rpccred(cred);
1399 if (IS_ERR(state)) { 1403 if (IS_ERR(state)) {
@@ -1401,12 +1405,14 @@ nfs4_atomic_open(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
1401 d_add(dentry, NULL); 1405 d_add(dentry, NULL);
1402 nfs_set_verifier(dentry, nfs_save_change_attribute(dir)); 1406 nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
1403 } 1407 }
1408 nfs_unblock_sillyrename(parent);
1404 return (struct dentry *)state; 1409 return (struct dentry *)state;
1405 } 1410 }
1406 res = d_add_unique(dentry, igrab(state->inode)); 1411 res = d_add_unique(dentry, igrab(state->inode));
1407 if (res != NULL) 1412 if (res != NULL)
1408 path.dentry = res; 1413 path.dentry = res;
1409 nfs_set_verifier(path.dentry, nfs_save_change_attribute(dir)); 1414 nfs_set_verifier(path.dentry, nfs_save_change_attribute(dir));
1415 nfs_unblock_sillyrename(parent);
1410 nfs4_intent_set_file(nd, &path, state); 1416 nfs4_intent_set_file(nd, &path, state);
1411 return res; 1417 return res;
1412} 1418}
diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c
index 1aed850d18f2..6ecd46c967c8 100644
--- a/fs/nfs/unlink.c
+++ b/fs/nfs/unlink.c
@@ -14,6 +14,7 @@
14 14
15 15
16struct nfs_unlinkdata { 16struct nfs_unlinkdata {
17 struct hlist_node list;
17 struct nfs_removeargs args; 18 struct nfs_removeargs args;
18 struct nfs_removeres res; 19 struct nfs_removeres res;
19 struct inode *dir; 20 struct inode *dir;
@@ -52,6 +53,20 @@ static int nfs_copy_dname(struct dentry *dentry, struct nfs_unlinkdata *data)
52 return 0; 53 return 0;
53} 54}
54 55
56static void nfs_free_dname(struct nfs_unlinkdata *data)
57{
58 kfree(data->args.name.name);
59 data->args.name.name = NULL;
60 data->args.name.len = 0;
61}
62
63static void nfs_dec_sillycount(struct inode *dir)
64{
65 struct nfs_inode *nfsi = NFS_I(dir);
66 if (atomic_dec_return(&nfsi->silly_count) == 1)
67 wake_up(&nfsi->waitqueue);
68}
69
55/** 70/**
56 * nfs_async_unlink_init - Initialize the RPC info 71 * nfs_async_unlink_init - Initialize the RPC info
57 * task: rpc_task of the sillydelete 72 * task: rpc_task of the sillydelete
@@ -95,6 +110,8 @@ static void nfs_async_unlink_done(struct rpc_task *task, void *calldata)
95static void nfs_async_unlink_release(void *calldata) 110static void nfs_async_unlink_release(void *calldata)
96{ 111{
97 struct nfs_unlinkdata *data = calldata; 112 struct nfs_unlinkdata *data = calldata;
113
114 nfs_dec_sillycount(data->dir);
98 nfs_free_unlinkdata(data); 115 nfs_free_unlinkdata(data);
99} 116}
100 117
@@ -104,33 +121,100 @@ static const struct rpc_call_ops nfs_unlink_ops = {
104 .rpc_release = nfs_async_unlink_release, 121 .rpc_release = nfs_async_unlink_release,
105}; 122};
106 123
107static int nfs_call_unlink(struct dentry *dentry, struct nfs_unlinkdata *data) 124static int nfs_do_call_unlink(struct dentry *parent, struct inode *dir, struct nfs_unlinkdata *data)
108{ 125{
109 struct rpc_task *task; 126 struct rpc_task *task;
127 struct dentry *alias;
128
129 alias = d_lookup(parent, &data->args.name);
130 if (alias != NULL) {
131 int ret = 0;
132 /*
133 * Hey, we raced with lookup... See if we need to transfer
134 * the sillyrename information to the aliased dentry.
135 */
136 nfs_free_dname(data);
137 spin_lock(&alias->d_lock);
138 if (!(alias->d_flags & DCACHE_NFSFS_RENAMED)) {
139 alias->d_fsdata = data;
140 alias->d_flags ^= DCACHE_NFSFS_RENAMED;
141 ret = 1;
142 }
143 spin_unlock(&alias->d_lock);
144 nfs_dec_sillycount(dir);
145 dput(alias);
146 return ret;
147 }
148 data->dir = igrab(dir);
149 if (!data->dir) {
150 nfs_dec_sillycount(dir);
151 return 0;
152 }
153 data->args.fh = NFS_FH(dir);
154 nfs_fattr_init(&data->res.dir_attr);
155
156 task = rpc_run_task(NFS_CLIENT(dir), RPC_TASK_ASYNC, &nfs_unlink_ops, data);
157 if (!IS_ERR(task))
158 rpc_put_task(task);
159 return 1;
160}
161
162static int nfs_call_unlink(struct dentry *dentry, struct nfs_unlinkdata *data)
163{
110 struct dentry *parent; 164 struct dentry *parent;
111 struct inode *dir; 165 struct inode *dir;
166 int ret = 0;
112 167
113 if (nfs_copy_dname(dentry, data) < 0)
114 goto out_free;
115 168
116 parent = dget_parent(dentry); 169 parent = dget_parent(dentry);
117 if (parent == NULL) 170 if (parent == NULL)
118 goto out_free; 171 goto out_free;
119 dir = igrab(parent->d_inode); 172 dir = parent->d_inode;
173 if (nfs_copy_dname(dentry, data) == 0)
174 goto out_dput;
175 /* Non-exclusive lock protects against concurrent lookup() calls */
176 spin_lock(&dir->i_lock);
177 if (atomic_inc_not_zero(&NFS_I(dir)->silly_count) == 0) {
178 /* Deferred delete */
179 hlist_add_head(&data->list, &NFS_I(dir)->silly_list);
180 spin_unlock(&dir->i_lock);
181 ret = 1;
182 goto out_dput;
183 }
184 spin_unlock(&dir->i_lock);
185 ret = nfs_do_call_unlink(parent, dir, data);
186out_dput:
120 dput(parent); 187 dput(parent);
121 if (dir == NULL) 188out_free:
122 goto out_free; 189 return ret;
190}
123 191
124 data->dir = dir; 192void nfs_block_sillyrename(struct dentry *dentry)
125 data->args.fh = NFS_FH(dir); 193{
126 nfs_fattr_init(&data->res.dir_attr); 194 struct nfs_inode *nfsi = NFS_I(dentry->d_inode);
127 195
128 task = rpc_run_task(NFS_CLIENT(dir), RPC_TASK_ASYNC, &nfs_unlink_ops, data); 196 wait_event(nfsi->waitqueue, atomic_cmpxchg(&nfsi->silly_count, 1, 0) == 1);
129 if (!IS_ERR(task)) 197}
130 rpc_put_task(task); 198
131 return 1; 199void nfs_unblock_sillyrename(struct dentry *dentry)
132out_free: 200{
133 return 0; 201 struct inode *dir = dentry->d_inode;
202 struct nfs_inode *nfsi = NFS_I(dir);
203 struct nfs_unlinkdata *data;
204
205 atomic_inc(&nfsi->silly_count);
206 spin_lock(&dir->i_lock);
207 while (!hlist_empty(&nfsi->silly_list)) {
208 if (!atomic_inc_not_zero(&nfsi->silly_count))
209 break;
210 data = hlist_entry(nfsi->silly_list.first, struct nfs_unlinkdata, list);
211 hlist_del(&data->list);
212 spin_unlock(&dir->i_lock);
213 if (nfs_do_call_unlink(dentry, dir, data) == 0)
214 nfs_free_unlinkdata(data);
215 spin_lock(&dir->i_lock);
216 }
217 spin_unlock(&dir->i_lock);
134} 218}
135 219
136/** 220/**