diff options
author | Al Viro <viro@zeniv.linux.org.uk> | 2016-04-28 23:56:31 -0400 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2016-05-09 11:39:45 -0400 |
commit | 884be175351e73c515303118150f195dd611787c (patch) | |
tree | ba85a80a6422df631b18af344132c6fcb67f1594 /fs/nfs/unlink.c | |
parent | 9ac3d3e8460e3fa6f3a9a39c2049904005016db6 (diff) |
nfs: per-name sillyunlink exclusion
use d_alloc_parallel() for sillyunlink/lookup exclusion and
explicit rwsem (nfs_rmdir() being a writer and nfs_call_unlink() -
a reader) for rmdir/sillyunlink one.
That ought to make lookup/readdir/!O_CREAT atomic_open really
parallel on NFS.
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'fs/nfs/unlink.c')
-rw-r--r-- | fs/nfs/unlink.c | 193 |
1 files changed, 51 insertions, 142 deletions
diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c index 7d6deaccf89b..1868246f56e6 100644 --- a/fs/nfs/unlink.c +++ b/fs/nfs/unlink.c | |||
@@ -30,45 +30,11 @@ | |||
30 | static void | 30 | static void |
31 | nfs_free_unlinkdata(struct nfs_unlinkdata *data) | 31 | nfs_free_unlinkdata(struct nfs_unlinkdata *data) |
32 | { | 32 | { |
33 | iput(data->dir); | ||
34 | put_rpccred(data->cred); | 33 | put_rpccred(data->cred); |
35 | kfree(data->args.name.name); | 34 | kfree(data->args.name.name); |
36 | kfree(data); | 35 | kfree(data); |
37 | } | 36 | } |
38 | 37 | ||
39 | #define NAME_ALLOC_LEN(len) ((len+16) & ~15) | ||
40 | /** | ||
41 | * nfs_copy_dname - copy dentry name to data structure | ||
42 | * @dentry: pointer to dentry | ||
43 | * @data: nfs_unlinkdata | ||
44 | */ | ||
45 | static int nfs_copy_dname(struct dentry *dentry, struct nfs_unlinkdata *data) | ||
46 | { | ||
47 | char *str; | ||
48 | int len = dentry->d_name.len; | ||
49 | |||
50 | str = kmemdup(dentry->d_name.name, NAME_ALLOC_LEN(len), GFP_KERNEL); | ||
51 | if (!str) | ||
52 | return -ENOMEM; | ||
53 | data->args.name.len = len; | ||
54 | data->args.name.name = str; | ||
55 | return 0; | ||
56 | } | ||
57 | |||
58 | static void nfs_free_dname(struct nfs_unlinkdata *data) | ||
59 | { | ||
60 | kfree(data->args.name.name); | ||
61 | data->args.name.name = NULL; | ||
62 | data->args.name.len = 0; | ||
63 | } | ||
64 | |||
65 | static void nfs_dec_sillycount(struct inode *dir) | ||
66 | { | ||
67 | struct nfs_inode *nfsi = NFS_I(dir); | ||
68 | if (atomic_dec_return(&nfsi->silly_count) == 1) | ||
69 | wake_up(&nfsi->waitqueue); | ||
70 | } | ||
71 | |||
72 | /** | 38 | /** |
73 | * nfs_async_unlink_done - Sillydelete post-processing | 39 | * nfs_async_unlink_done - Sillydelete post-processing |
74 | * @task: rpc_task of the sillydelete | 40 | * @task: rpc_task of the sillydelete |
@@ -78,7 +44,7 @@ static void nfs_dec_sillycount(struct inode *dir) | |||
78 | static void nfs_async_unlink_done(struct rpc_task *task, void *calldata) | 44 | static void nfs_async_unlink_done(struct rpc_task *task, void *calldata) |
79 | { | 45 | { |
80 | struct nfs_unlinkdata *data = calldata; | 46 | struct nfs_unlinkdata *data = calldata; |
81 | struct inode *dir = data->dir; | 47 | struct inode *dir = d_inode(data->dentry->d_parent); |
82 | 48 | ||
83 | trace_nfs_sillyrename_unlink(data, task->tk_status); | 49 | trace_nfs_sillyrename_unlink(data, task->tk_status); |
84 | if (!NFS_PROTO(dir)->unlink_done(task, dir)) | 50 | if (!NFS_PROTO(dir)->unlink_done(task, dir)) |
@@ -95,17 +61,21 @@ static void nfs_async_unlink_done(struct rpc_task *task, void *calldata) | |||
95 | static void nfs_async_unlink_release(void *calldata) | 61 | static void nfs_async_unlink_release(void *calldata) |
96 | { | 62 | { |
97 | struct nfs_unlinkdata *data = calldata; | 63 | struct nfs_unlinkdata *data = calldata; |
98 | struct super_block *sb = data->dir->i_sb; | 64 | struct dentry *dentry = data->dentry; |
65 | struct super_block *sb = dentry->d_sb; | ||
99 | 66 | ||
100 | nfs_dec_sillycount(data->dir); | 67 | up_read_non_owner(&NFS_I(d_inode(dentry->d_parent))->rmdir_sem); |
68 | d_lookup_done(dentry); | ||
101 | nfs_free_unlinkdata(data); | 69 | nfs_free_unlinkdata(data); |
70 | dput(dentry); | ||
102 | nfs_sb_deactive(sb); | 71 | nfs_sb_deactive(sb); |
103 | } | 72 | } |
104 | 73 | ||
105 | static void nfs_unlink_prepare(struct rpc_task *task, void *calldata) | 74 | static void nfs_unlink_prepare(struct rpc_task *task, void *calldata) |
106 | { | 75 | { |
107 | struct nfs_unlinkdata *data = calldata; | 76 | struct nfs_unlinkdata *data = calldata; |
108 | NFS_PROTO(data->dir)->unlink_rpc_prepare(task, data); | 77 | struct inode *dir = d_inode(data->dentry->d_parent); |
78 | NFS_PROTO(dir)->unlink_rpc_prepare(task, data); | ||
109 | } | 79 | } |
110 | 80 | ||
111 | static const struct rpc_call_ops nfs_unlink_ops = { | 81 | static const struct rpc_call_ops nfs_unlink_ops = { |
@@ -114,7 +84,7 @@ static const struct rpc_call_ops nfs_unlink_ops = { | |||
114 | .rpc_call_prepare = nfs_unlink_prepare, | 84 | .rpc_call_prepare = nfs_unlink_prepare, |
115 | }; | 85 | }; |
116 | 86 | ||
117 | static int nfs_do_call_unlink(struct dentry *parent, struct inode *dir, struct nfs_unlinkdata *data) | 87 | static void nfs_do_call_unlink(struct nfs_unlinkdata *data) |
118 | { | 88 | { |
119 | struct rpc_message msg = { | 89 | struct rpc_message msg = { |
120 | .rpc_argp = &data->args, | 90 | .rpc_argp = &data->args, |
@@ -129,10 +99,31 @@ static int nfs_do_call_unlink(struct dentry *parent, struct inode *dir, struct n | |||
129 | .flags = RPC_TASK_ASYNC, | 99 | .flags = RPC_TASK_ASYNC, |
130 | }; | 100 | }; |
131 | struct rpc_task *task; | 101 | struct rpc_task *task; |
102 | struct inode *dir = d_inode(data->dentry->d_parent); | ||
103 | nfs_sb_active(dir->i_sb); | ||
104 | data->args.fh = NFS_FH(dir); | ||
105 | nfs_fattr_init(data->res.dir_attr); | ||
106 | |||
107 | NFS_PROTO(dir)->unlink_setup(&msg, dir); | ||
108 | |||
109 | task_setup_data.rpc_client = NFS_CLIENT(dir); | ||
110 | task = rpc_run_task(&task_setup_data); | ||
111 | if (!IS_ERR(task)) | ||
112 | rpc_put_task_async(task); | ||
113 | } | ||
114 | |||
115 | static int nfs_call_unlink(struct dentry *dentry, struct nfs_unlinkdata *data) | ||
116 | { | ||
117 | struct inode *dir = d_inode(dentry->d_parent); | ||
132 | struct dentry *alias; | 118 | struct dentry *alias; |
133 | 119 | ||
134 | alias = d_lookup(parent, &data->args.name); | 120 | down_read_non_owner(&NFS_I(dir)->rmdir_sem); |
135 | if (alias != NULL) { | 121 | alias = d_alloc_parallel(dentry->d_parent, &data->args.name, &data->wq); |
122 | if (IS_ERR(alias)) { | ||
123 | up_read_non_owner(&NFS_I(dir)->rmdir_sem); | ||
124 | return 0; | ||
125 | } | ||
126 | if (!d_in_lookup(alias)) { | ||
136 | int ret; | 127 | int ret; |
137 | void *devname_garbage = NULL; | 128 | void *devname_garbage = NULL; |
138 | 129 | ||
@@ -140,10 +131,8 @@ static int nfs_do_call_unlink(struct dentry *parent, struct inode *dir, struct n | |||
140 | * Hey, we raced with lookup... See if we need to transfer | 131 | * Hey, we raced with lookup... See if we need to transfer |
141 | * the sillyrename information to the aliased dentry. | 132 | * the sillyrename information to the aliased dentry. |
142 | */ | 133 | */ |
143 | nfs_free_dname(data); | ||
144 | ret = nfs_copy_dname(alias, data); | ||
145 | spin_lock(&alias->d_lock); | 134 | spin_lock(&alias->d_lock); |
146 | if (ret == 0 && d_really_is_positive(alias) && | 135 | if (d_really_is_positive(alias) && |
147 | !(alias->d_flags & DCACHE_NFSFS_RENAMED)) { | 136 | !(alias->d_flags & DCACHE_NFSFS_RENAMED)) { |
148 | devname_garbage = alias->d_fsdata; | 137 | devname_garbage = alias->d_fsdata; |
149 | alias->d_fsdata = data; | 138 | alias->d_fsdata = data; |
@@ -152,8 +141,8 @@ static int nfs_do_call_unlink(struct dentry *parent, struct inode *dir, struct n | |||
152 | } else | 141 | } else |
153 | ret = 0; | 142 | ret = 0; |
154 | spin_unlock(&alias->d_lock); | 143 | spin_unlock(&alias->d_lock); |
155 | nfs_dec_sillycount(dir); | ||
156 | dput(alias); | 144 | dput(alias); |
145 | up_read_non_owner(&NFS_I(dir)->rmdir_sem); | ||
157 | /* | 146 | /* |
158 | * If we'd displaced old cached devname, free it. At that | 147 | * If we'd displaced old cached devname, free it. At that |
159 | * point dentry is definitely not a root, so we won't need | 148 | * point dentry is definitely not a root, so we won't need |
@@ -162,95 +151,18 @@ static int nfs_do_call_unlink(struct dentry *parent, struct inode *dir, struct n | |||
162 | kfree(devname_garbage); | 151 | kfree(devname_garbage); |
163 | return ret; | 152 | return ret; |
164 | } | 153 | } |
165 | data->dir = igrab(dir); | 154 | data->dentry = alias; |
166 | if (!data->dir) { | 155 | nfs_do_call_unlink(data); |
167 | nfs_dec_sillycount(dir); | ||
168 | return 0; | ||
169 | } | ||
170 | nfs_sb_active(dir->i_sb); | ||
171 | data->args.fh = NFS_FH(dir); | ||
172 | nfs_fattr_init(data->res.dir_attr); | ||
173 | |||
174 | NFS_PROTO(dir)->unlink_setup(&msg, dir); | ||
175 | |||
176 | task_setup_data.rpc_client = NFS_CLIENT(dir); | ||
177 | task = rpc_run_task(&task_setup_data); | ||
178 | if (!IS_ERR(task)) | ||
179 | rpc_put_task_async(task); | ||
180 | return 1; | 156 | return 1; |
181 | } | 157 | } |
182 | 158 | ||
183 | static int nfs_call_unlink(struct dentry *dentry, struct nfs_unlinkdata *data) | ||
184 | { | ||
185 | struct dentry *parent; | ||
186 | struct inode *dir; | ||
187 | int ret = 0; | ||
188 | |||
189 | |||
190 | parent = dget_parent(dentry); | ||
191 | if (parent == NULL) | ||
192 | goto out_free; | ||
193 | dir = d_inode(parent); | ||
194 | /* Non-exclusive lock protects against concurrent lookup() calls */ | ||
195 | spin_lock(&dir->i_lock); | ||
196 | if (atomic_inc_not_zero(&NFS_I(dir)->silly_count) == 0) { | ||
197 | /* Deferred delete */ | ||
198 | hlist_add_head(&data->list, &NFS_I(dir)->silly_list); | ||
199 | spin_unlock(&dir->i_lock); | ||
200 | ret = 1; | ||
201 | goto out_dput; | ||
202 | } | ||
203 | spin_unlock(&dir->i_lock); | ||
204 | ret = nfs_do_call_unlink(parent, dir, data); | ||
205 | out_dput: | ||
206 | dput(parent); | ||
207 | out_free: | ||
208 | return ret; | ||
209 | } | ||
210 | |||
211 | void nfs_wait_on_sillyrename(struct dentry *dentry) | ||
212 | { | ||
213 | struct nfs_inode *nfsi = NFS_I(d_inode(dentry)); | ||
214 | |||
215 | wait_event(nfsi->waitqueue, atomic_read(&nfsi->silly_count) <= 1); | ||
216 | } | ||
217 | |||
218 | void nfs_block_sillyrename(struct dentry *dentry) | ||
219 | { | ||
220 | struct nfs_inode *nfsi = NFS_I(d_inode(dentry)); | ||
221 | |||
222 | wait_event(nfsi->waitqueue, atomic_cmpxchg(&nfsi->silly_count, 1, 0) == 1); | ||
223 | } | ||
224 | |||
225 | void nfs_unblock_sillyrename(struct dentry *dentry) | ||
226 | { | ||
227 | struct inode *dir = d_inode(dentry); | ||
228 | struct nfs_inode *nfsi = NFS_I(dir); | ||
229 | struct nfs_unlinkdata *data; | ||
230 | |||
231 | atomic_inc(&nfsi->silly_count); | ||
232 | wake_up(&nfsi->waitqueue); | ||
233 | spin_lock(&dir->i_lock); | ||
234 | while (!hlist_empty(&nfsi->silly_list)) { | ||
235 | if (!atomic_inc_not_zero(&nfsi->silly_count)) | ||
236 | break; | ||
237 | data = hlist_entry(nfsi->silly_list.first, struct nfs_unlinkdata, list); | ||
238 | hlist_del(&data->list); | ||
239 | spin_unlock(&dir->i_lock); | ||
240 | if (nfs_do_call_unlink(dentry, dir, data) == 0) | ||
241 | nfs_free_unlinkdata(data); | ||
242 | spin_lock(&dir->i_lock); | ||
243 | } | ||
244 | spin_unlock(&dir->i_lock); | ||
245 | } | ||
246 | |||
247 | /** | 159 | /** |
248 | * nfs_async_unlink - asynchronous unlinking of a file | 160 | * nfs_async_unlink - asynchronous unlinking of a file |
249 | * @dir: parent directory of dentry | 161 | * @dir: parent directory of dentry |
250 | * @dentry: dentry to unlink | 162 | * @dentry: dentry to unlink |
251 | */ | 163 | */ |
252 | static int | 164 | static int |
253 | nfs_async_unlink(struct inode *dir, struct dentry *dentry) | 165 | nfs_async_unlink(struct dentry *dentry, struct qstr *name) |
254 | { | 166 | { |
255 | struct nfs_unlinkdata *data; | 167 | struct nfs_unlinkdata *data; |
256 | int status = -ENOMEM; | 168 | int status = -ENOMEM; |
@@ -259,13 +171,18 @@ nfs_async_unlink(struct inode *dir, struct dentry *dentry) | |||
259 | data = kzalloc(sizeof(*data), GFP_KERNEL); | 171 | data = kzalloc(sizeof(*data), GFP_KERNEL); |
260 | if (data == NULL) | 172 | if (data == NULL) |
261 | goto out; | 173 | goto out; |
174 | data->args.name.name = kstrdup(name->name, GFP_KERNEL); | ||
175 | if (!data->args.name.name) | ||
176 | goto out_free; | ||
177 | data->args.name.len = name->len; | ||
262 | 178 | ||
263 | data->cred = rpc_lookup_cred(); | 179 | data->cred = rpc_lookup_cred(); |
264 | if (IS_ERR(data->cred)) { | 180 | if (IS_ERR(data->cred)) { |
265 | status = PTR_ERR(data->cred); | 181 | status = PTR_ERR(data->cred); |
266 | goto out_free; | 182 | goto out_free_name; |
267 | } | 183 | } |
268 | data->res.dir_attr = &data->dir_attr; | 184 | data->res.dir_attr = &data->dir_attr; |
185 | init_waitqueue_head(&data->wq); | ||
269 | 186 | ||
270 | status = -EBUSY; | 187 | status = -EBUSY; |
271 | spin_lock(&dentry->d_lock); | 188 | spin_lock(&dentry->d_lock); |
@@ -285,6 +202,8 @@ nfs_async_unlink(struct inode *dir, struct dentry *dentry) | |||
285 | out_unlock: | 202 | out_unlock: |
286 | spin_unlock(&dentry->d_lock); | 203 | spin_unlock(&dentry->d_lock); |
287 | put_rpccred(data->cred); | 204 | put_rpccred(data->cred); |
205 | out_free_name: | ||
206 | kfree(data->args.name.name); | ||
288 | out_free: | 207 | out_free: |
289 | kfree(data); | 208 | kfree(data); |
290 | out: | 209 | out: |
@@ -303,17 +222,15 @@ out: | |||
303 | void | 222 | void |
304 | nfs_complete_unlink(struct dentry *dentry, struct inode *inode) | 223 | nfs_complete_unlink(struct dentry *dentry, struct inode *inode) |
305 | { | 224 | { |
306 | struct nfs_unlinkdata *data = NULL; | 225 | struct nfs_unlinkdata *data; |
307 | 226 | ||
308 | spin_lock(&dentry->d_lock); | 227 | spin_lock(&dentry->d_lock); |
309 | if (dentry->d_flags & DCACHE_NFSFS_RENAMED) { | 228 | dentry->d_flags &= ~DCACHE_NFSFS_RENAMED; |
310 | dentry->d_flags &= ~DCACHE_NFSFS_RENAMED; | 229 | data = dentry->d_fsdata; |
311 | data = dentry->d_fsdata; | 230 | dentry->d_fsdata = NULL; |
312 | dentry->d_fsdata = NULL; | ||
313 | } | ||
314 | spin_unlock(&dentry->d_lock); | 231 | spin_unlock(&dentry->d_lock); |
315 | 232 | ||
316 | if (data != NULL && (NFS_STALE(inode) || !nfs_call_unlink(dentry, data))) | 233 | if (NFS_STALE(inode) || !nfs_call_unlink(dentry, data)) |
317 | nfs_free_unlinkdata(data); | 234 | nfs_free_unlinkdata(data); |
318 | } | 235 | } |
319 | 236 | ||
@@ -560,18 +477,10 @@ nfs_sillyrename(struct inode *dir, struct dentry *dentry) | |||
560 | /* queue unlink first. Can't do this from rpc_release as it | 477 | /* queue unlink first. Can't do this from rpc_release as it |
561 | * has to allocate memory | 478 | * has to allocate memory |
562 | */ | 479 | */ |
563 | error = nfs_async_unlink(dir, dentry); | 480 | error = nfs_async_unlink(dentry, &sdentry->d_name); |
564 | if (error) | 481 | if (error) |
565 | goto out_dput; | 482 | goto out_dput; |
566 | 483 | ||
567 | /* populate unlinkdata with the right dname */ | ||
568 | error = nfs_copy_dname(sdentry, | ||
569 | (struct nfs_unlinkdata *)dentry->d_fsdata); | ||
570 | if (error) { | ||
571 | nfs_cancel_async_unlink(dentry); | ||
572 | goto out_dput; | ||
573 | } | ||
574 | |||
575 | /* run the rename task, undo unlink if it fails */ | 484 | /* run the rename task, undo unlink if it fails */ |
576 | task = nfs_async_rename(dir, dir, dentry, sdentry, | 485 | task = nfs_async_rename(dir, dir, dentry, sdentry, |
577 | nfs_complete_sillyrename); | 486 | nfs_complete_sillyrename); |