Commit 2f9092e1 authored by David Woodhouse's avatar David Woodhouse Committed by Al Viro

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: default avatarDavid Woodhouse <David.Woodhouse@intel.com>
Umm-I-can-live-with-that-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
Reported-by: default avatarJ. R. Okajima <hooanon05@yahoo.co.jp>
Tested-by: default avatarJ. Bruce Fields <bfields@citi.umich.edu>
LKML-Reference: <8036.1237474444@jrobl>
Cc: stable@kernel.org
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent 1ba0c7db
...@@ -1248,6 +1248,8 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len) ...@@ -1248,6 +1248,8 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
int err; int err;
struct qstr this; struct qstr this;
WARN_ON_ONCE(!mutex_is_locked(&base->d_inode->i_mutex));
err = __lookup_one_len(name, &this, base, len); err = __lookup_one_len(name, &this, base, len);
if (err) if (err)
return ERR_PTR(err); return ERR_PTR(err);
......
...@@ -229,21 +229,23 @@ nfsd4_list_rec_dir(struct dentry *dir, recdir_func *f) ...@@ -229,21 +229,23 @@ nfsd4_list_rec_dir(struct dentry *dir, recdir_func *f)
goto out; goto out;
status = vfs_readdir(filp, nfsd4_build_namelist, &names); status = vfs_readdir(filp, nfsd4_build_namelist, &names);
fput(filp); fput(filp);
mutex_lock(&dir->d_inode->i_mutex);
while (!list_empty(&names)) { while (!list_empty(&names)) {
entry = list_entry(names.next, struct name_list, list); entry = list_entry(names.next, struct name_list, list);
dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1); dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1);
if (IS_ERR(dentry)) { if (IS_ERR(dentry)) {
status = PTR_ERR(dentry); status = PTR_ERR(dentry);
goto out; break;
} }
status = f(dir, dentry); status = f(dir, dentry);
dput(dentry); dput(dentry);
if (status) if (status)
goto out; break;
list_del(&entry->list); list_del(&entry->list);
kfree(entry); kfree(entry);
} }
mutex_unlock(&dir->d_inode->i_mutex);
out: out:
while (!list_empty(&names)) { while (!list_empty(&names)) {
entry = list_entry(names.next, struct name_list, list); entry = list_entry(names.next, struct name_list, list);
...@@ -254,36 +256,6 @@ out: ...@@ -254,36 +256,6 @@ out:
return status; return status;
} }
static int
nfsd4_remove_clid_file(struct dentry *dir, struct dentry *dentry)
{
int status;
if (!S_ISREG(dir->d_inode->i_mode)) {
printk("nfsd4: non-file found in client recovery directory\n");
return -EINVAL;
}
mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
status = vfs_unlink(dir->d_inode, dentry);
mutex_unlock(&dir->d_inode->i_mutex);
return status;
}
static int
nfsd4_clear_clid_dir(struct dentry *dir, struct dentry *dentry)
{
int status;
/* For now this directory should already be empty, but we empty it of
* any regular files anyway, just in case the directory was created by
* a kernel from the future.... */
nfsd4_list_rec_dir(dentry, nfsd4_remove_clid_file);
mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_PARENT);
status = vfs_rmdir(dir->d_inode, dentry);
mutex_unlock(&dir->d_inode->i_mutex);
return status;
}
static int static int
nfsd4_unlink_clid_dir(char *name, int namlen) nfsd4_unlink_clid_dir(char *name, int namlen)
{ {
...@@ -294,18 +266,18 @@ nfsd4_unlink_clid_dir(char *name, int namlen) ...@@ -294,18 +266,18 @@ nfsd4_unlink_clid_dir(char *name, int namlen)
mutex_lock(&rec_dir.dentry->d_inode->i_mutex); mutex_lock(&rec_dir.dentry->d_inode->i_mutex);
dentry = lookup_one_len(name, rec_dir.dentry, namlen); dentry = lookup_one_len(name, rec_dir.dentry, namlen);
mutex_unlock(&rec_dir.dentry->d_inode->i_mutex);
if (IS_ERR(dentry)) { if (IS_ERR(dentry)) {
status = PTR_ERR(dentry); status = PTR_ERR(dentry);
return status; goto out_unlock;
} }
status = -ENOENT; status = -ENOENT;
if (!dentry->d_inode) if (!dentry->d_inode)
goto out; goto out;
status = vfs_rmdir(rec_dir.dentry->d_inode, dentry);
status = nfsd4_clear_clid_dir(rec_dir.dentry, dentry);
out: out:
dput(dentry); dput(dentry);
out_unlock:
mutex_unlock(&rec_dir.dentry->d_inode->i_mutex);
return status; return status;
} }
...@@ -348,7 +320,7 @@ purge_old(struct dentry *parent, struct dentry *child) ...@@ -348,7 +320,7 @@ purge_old(struct dentry *parent, struct dentry *child)
if (nfs4_has_reclaimed_state(child->d_name.name, false)) if (nfs4_has_reclaimed_state(child->d_name.name, false))
return 0; return 0;
status = nfsd4_clear_clid_dir(parent, child); status = vfs_rmdir(parent->d_inode, child);
if (status) if (status)
printk("failed to remove client recovery directory %s\n", printk("failed to remove client recovery directory %s\n",
child->d_name.name); child->d_name.name);
......
...@@ -1890,7 +1890,7 @@ static int nfsd_buffered_filldir(void *__buf, const char *name, int namlen, ...@@ -1890,7 +1890,7 @@ static int nfsd_buffered_filldir(void *__buf, const char *name, int namlen,
return 0; return 0;
} }
static int nfsd_buffered_readdir(struct file *file, filldir_t func, static __be32 nfsd_buffered_readdir(struct file *file, filldir_t func,
struct readdir_cd *cdp, loff_t *offsetp) struct readdir_cd *cdp, loff_t *offsetp)
{ {
struct readdir_data buf; struct readdir_data buf;
...@@ -1901,11 +1901,12 @@ static int nfsd_buffered_readdir(struct file *file, filldir_t func, ...@@ -1901,11 +1901,12 @@ static int nfsd_buffered_readdir(struct file *file, filldir_t func,
buf.dirent = (void *)__get_free_page(GFP_KERNEL); buf.dirent = (void *)__get_free_page(GFP_KERNEL);
if (!buf.dirent) if (!buf.dirent)
return -ENOMEM; return nfserrno(-ENOMEM);
offset = *offsetp; offset = *offsetp;
while (1) { while (1) {
struct inode *dir_inode = file->f_path.dentry->d_inode;
unsigned int reclen; unsigned int reclen;
cdp->err = nfserr_eof; /* will be cleared on successful read */ cdp->err = nfserr_eof; /* will be cleared on successful read */
...@@ -1924,26 +1925,38 @@ static int nfsd_buffered_readdir(struct file *file, filldir_t func, ...@@ -1924,26 +1925,38 @@ static int nfsd_buffered_readdir(struct file *file, filldir_t func,
if (!size) if (!size)
break; break;
/*
* Various filldir functions may end up calling back into
* lookup_one_len() and the file system's ->lookup() method.
* These expect i_mutex to be held, as it would within readdir.
*/
host_err = mutex_lock_killable(&dir_inode->i_mutex);
if (host_err)
break;
de = (struct buffered_dirent *)buf.dirent; de = (struct buffered_dirent *)buf.dirent;
while (size > 0) { while (size > 0) {
offset = de->offset; offset = de->offset;
if (func(cdp, de->name, de->namlen, de->offset, if (func(cdp, de->name, de->namlen, de->offset,
de->ino, de->d_type)) de->ino, de->d_type))
goto done; break;
if (cdp->err != nfs_ok) if (cdp->err != nfs_ok)
goto done; break;
reclen = ALIGN(sizeof(*de) + de->namlen, reclen = ALIGN(sizeof(*de) + de->namlen,
sizeof(u64)); sizeof(u64));
size -= reclen; size -= reclen;
de = (struct buffered_dirent *)((char *)de + reclen); de = (struct buffered_dirent *)((char *)de + reclen);
} }
mutex_unlock(&dir_inode->i_mutex);
if (size > 0) /* We bailed out early */
break;
offset = vfs_llseek(file, 0, SEEK_CUR); offset = vfs_llseek(file, 0, SEEK_CUR);
} }
done:
free_page((unsigned long)(buf.dirent)); free_page((unsigned long)(buf.dirent));
if (host_err) if (host_err)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment