aboutsummaryrefslogtreecommitdiffstats
path: root/fs/nfs/unlink.c
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/nfs/unlink.c
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/nfs/unlink.c')
-rw-r--r--fs/nfs/unlink.c114
1 files changed, 99 insertions, 15 deletions
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/**