diff options
author | David Woodhouse <dwmw2@infradead.org> | 2009-04-20 18:18:37 -0400 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2009-04-20 23:01:16 -0400 |
commit | 2f9092e1020246168b1309b35e085ecd7ff9ff72 (patch) | |
tree | f8318c1e62e789718ae7637869f6c075b815bcb2 /fs/nfsd/nfs4recover.c | |
parent | 1ba0c7dbbbc24230394100c5f0d0df38cb400cff (diff) |
Fix i_mutex vs. readdir handling in nfsd
Commit 14f7dd63 ("Copy XFS readdir hack into nfsd code") introduced a
bug to generic code which had been extant for a long time in the XFS
version -- it started to call through into lookup_one_len() and hence
into the file systems' ->lookup() methods without i_mutex held on the
directory.
This patch fixes it by locking the directory's i_mutex again before
calling the filldir functions. The original deadlocks which commit
14f7dd63 was designed to avoid are still avoided, because they were due
to fs-internal locking, not i_mutex.
While we're at it, fix the return type of nfsd_buffered_readdir() which
should be a __be32 not an int -- it's an NFS errno, not a Linux errno.
And return nfserrno(-ENOMEM) when allocation fails, not just -ENOMEM.
Sparse would have caught that, if it wasn't so busy bitching about
__cold__.
Commit 05f4f678 ("nfsd4: don't do lookup within readdir in recovery
code") introduced a similar problem with calling lookup_one_len()
without i_mutex, which this patch also addresses. To fix that, it was
necessary to fix the called functions so that they expect i_mutex to be
held; that part was done by J. Bruce Fields.
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
Umm-I-can-live-with-that-by: Al Viro <viro@zeniv.linux.org.uk>
Reported-by: J. R. Okajima <hooanon05@yahoo.co.jp>
Tested-by: J. Bruce Fields <bfields@citi.umich.edu>
LKML-Reference: <8036.1237474444@jrobl>
Cc: stable@kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'fs/nfsd/nfs4recover.c')
-rw-r--r-- | fs/nfsd/nfs4recover.c | 46 |
1 files changed, 9 insertions, 37 deletions
diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c index 3444c0052a87..5275097a7565 100644 --- a/fs/nfsd/nfs4recover.c +++ b/fs/nfsd/nfs4recover.c | |||
@@ -229,21 +229,23 @@ nfsd4_list_rec_dir(struct dentry *dir, recdir_func *f) | |||
229 | goto out; | 229 | goto out; |
230 | status = vfs_readdir(filp, nfsd4_build_namelist, &names); | 230 | status = vfs_readdir(filp, nfsd4_build_namelist, &names); |
231 | fput(filp); | 231 | fput(filp); |
232 | mutex_lock(&dir->d_inode->i_mutex); | ||
232 | while (!list_empty(&names)) { | 233 | while (!list_empty(&names)) { |
233 | entry = list_entry(names.next, struct name_list, list); | 234 | entry = list_entry(names.next, struct name_list, list); |
234 | 235 | ||
235 | dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1); | 236 | dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1); |
236 | if (IS_ERR(dentry)) { | 237 | if (IS_ERR(dentry)) { |
237 | status = PTR_ERR(dentry); | 238 | status = PTR_ERR(dentry); |
238 | goto out; | 239 | break; |
239 | } | 240 | } |
240 | status = f(dir, dentry); | 241 | status = f(dir, dentry); |
241 | dput(dentry); | 242 | dput(dentry); |
242 | if (status) | 243 | if (status) |
243 | goto out; | 244 | break; |
244 | list_del(&entry->list); | 245 | list_del(&entry->list); |
245 | kfree(entry); | 246 | kfree(entry); |
246 | } | 247 | } |
248 | mutex_unlock(&dir->d_inode->i_mutex); | ||
247 | out: | 249 | out: |
248 | while (!list_empty(&names)) { | 250 | while (!list_empty(&names)) { |
249 | entry = list_entry(names.next, struct name_list, list); | 251 | entry = list_entry(names.next, struct name_list, list); |
@@ -255,36 +257,6 @@ out: | |||
255 | } | 257 | } |
256 | 258 | ||
257 | static int | 259 | static int |
258 | nfsd4_remove_clid_file(struct dentry *dir, struct dentry *dentry) | ||
259 | { | ||
260 | int status; | ||
261 | |||
262 | if (!S_ISREG(dir->d_inode->i_mode)) { | ||
263 | printk("nfsd4: non-file found in client recovery directory\n"); | ||
264 | return -EINVAL; | ||
265 | } | ||
266 | mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT); | ||
267 | status = vfs_unlink(dir->d_inode, dentry); | ||
268 | mutex_unlock(&dir->d_inode->i_mutex); | ||
269 | return status; | ||
270 | } | ||
271 | |||
272 | static int | ||
273 | nfsd4_clear_clid_dir(struct dentry *dir, struct dentry *dentry) | ||
274 | { | ||
275 | int status; | ||
276 | |||
277 | /* For now this directory should already be empty, but we empty it of | ||
278 | * any regular files anyway, just in case the directory was created by | ||
279 | * a kernel from the future.... */ | ||
280 | nfsd4_list_rec_dir(dentry, nfsd4_remove_clid_file); | ||
281 | mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT); | ||
282 | status = vfs_rmdir(dir->d_inode, dentry); | ||
283 | mutex_unlock(&dir->d_inode->i_mutex); | ||
284 | return status; | ||
285 | } | ||
286 | |||
287 | static int | ||
288 | nfsd4_unlink_clid_dir(char *name, int namlen) | 260 | nfsd4_unlink_clid_dir(char *name, int namlen) |
289 | { | 261 | { |
290 | struct dentry *dentry; | 262 | struct dentry *dentry; |
@@ -294,18 +266,18 @@ nfsd4_unlink_clid_dir(char *name, int namlen) | |||
294 | 266 | ||
295 | mutex_lock(&rec_dir.dentry->d_inode->i_mutex); | 267 | mutex_lock(&rec_dir.dentry->d_inode->i_mutex); |
296 | dentry = lookup_one_len(name, rec_dir.dentry, namlen); | 268 | dentry = lookup_one_len(name, rec_dir.dentry, namlen); |
297 | mutex_unlock(&rec_dir.dentry->d_inode->i_mutex); | ||
298 | if (IS_ERR(dentry)) { | 269 | if (IS_ERR(dentry)) { |
299 | status = PTR_ERR(dentry); | 270 | status = PTR_ERR(dentry); |
300 | return status; | 271 | goto out_unlock; |
301 | } | 272 | } |
302 | status = -ENOENT; | 273 | status = -ENOENT; |
303 | if (!dentry->d_inode) | 274 | if (!dentry->d_inode) |
304 | goto out; | 275 | goto out; |
305 | 276 | status = vfs_rmdir(rec_dir.dentry->d_inode, dentry); | |
306 | status = nfsd4_clear_clid_dir(rec_dir.dentry, dentry); | ||
307 | out: | 277 | out: |
308 | dput(dentry); | 278 | dput(dentry); |
279 | out_unlock: | ||
280 | mutex_unlock(&rec_dir.dentry->d_inode->i_mutex); | ||
309 | return status; | 281 | return status; |
310 | } | 282 | } |
311 | 283 | ||
@@ -348,7 +320,7 @@ purge_old(struct dentry *parent, struct dentry *child) | |||
348 | if (nfs4_has_reclaimed_state(child->d_name.name, false)) | 320 | if (nfs4_has_reclaimed_state(child->d_name.name, false)) |
349 | return 0; | 321 | return 0; |
350 | 322 | ||
351 | status = nfsd4_clear_clid_dir(parent, child); | 323 | status = vfs_rmdir(parent->d_inode, child); |
352 | if (status) | 324 | if (status) |
353 | printk("failed to remove client recovery directory %s\n", | 325 | printk("failed to remove client recovery directory %s\n", |
354 | child->d_name.name); | 326 | child->d_name.name); |