Fix i_mutex vs. readdir handling in nfsd
Commit14f7dd63
("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 commit14f7dd63
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__. Commit05f4f678
("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>
This commit is contained in:
Родитель
1ba0c7dbbb
Коммит
2f9092e102
|
@ -1248,6 +1248,8 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
|
|||
int err;
|
||||
struct qstr this;
|
||||
|
||||
WARN_ON_ONCE(!mutex_is_locked(&base->d_inode->i_mutex));
|
||||
|
||||
err = __lookup_one_len(name, &this, base, len);
|
||||
if (err)
|
||||
return ERR_PTR(err);
|
||||
|
|
|
@ -229,21 +229,23 @@ nfsd4_list_rec_dir(struct dentry *dir, recdir_func *f)
|
|||
goto out;
|
||||
status = vfs_readdir(filp, nfsd4_build_namelist, &names);
|
||||
fput(filp);
|
||||
mutex_lock(&dir->d_inode->i_mutex);
|
||||
while (!list_empty(&names)) {
|
||||
entry = list_entry(names.next, struct name_list, list);
|
||||
|
||||
dentry = lookup_one_len(entry->name, dir, HEXDIR_LEN-1);
|
||||
if (IS_ERR(dentry)) {
|
||||
status = PTR_ERR(dentry);
|
||||
goto out;
|
||||
break;
|
||||
}
|
||||
status = f(dir, dentry);
|
||||
dput(dentry);
|
||||
if (status)
|
||||
goto out;
|
||||
break;
|
||||
list_del(&entry->list);
|
||||
kfree(entry);
|
||||
}
|
||||
mutex_unlock(&dir->d_inode->i_mutex);
|
||||
out:
|
||||
while (!list_empty(&names)) {
|
||||
entry = list_entry(names.next, struct name_list, list);
|
||||
|
@ -254,36 +256,6 @@ out:
|
|||
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
|
||||
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);
|
||||
dentry = lookup_one_len(name, rec_dir.dentry, namlen);
|
||||
mutex_unlock(&rec_dir.dentry->d_inode->i_mutex);
|
||||
if (IS_ERR(dentry)) {
|
||||
status = PTR_ERR(dentry);
|
||||
return status;
|
||||
goto out_unlock;
|
||||
}
|
||||
status = -ENOENT;
|
||||
if (!dentry->d_inode)
|
||||
goto out;
|
||||
|
||||
status = nfsd4_clear_clid_dir(rec_dir.dentry, dentry);
|
||||
status = vfs_rmdir(rec_dir.dentry->d_inode, dentry);
|
||||
out:
|
||||
dput(dentry);
|
||||
out_unlock:
|
||||
mutex_unlock(&rec_dir.dentry->d_inode->i_mutex);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
@ -348,7 +320,7 @@ purge_old(struct dentry *parent, struct dentry *child)
|
|||
if (nfs4_has_reclaimed_state(child->d_name.name, false))
|
||||
return 0;
|
||||
|
||||
status = nfsd4_clear_clid_dir(parent, child);
|
||||
status = vfs_rmdir(parent->d_inode, child);
|
||||
if (status)
|
||||
printk("failed to remove client recovery directory %s\n",
|
||||
child->d_name.name);
|
||||
|
|
|
@ -1890,8 +1890,8 @@ static int nfsd_buffered_filldir(void *__buf, const char *name, int namlen,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int nfsd_buffered_readdir(struct file *file, filldir_t func,
|
||||
struct readdir_cd *cdp, loff_t *offsetp)
|
||||
static __be32 nfsd_buffered_readdir(struct file *file, filldir_t func,
|
||||
struct readdir_cd *cdp, loff_t *offsetp)
|
||||
{
|
||||
struct readdir_data buf;
|
||||
struct buffered_dirent *de;
|
||||
|
@ -1901,11 +1901,12 @@ static int nfsd_buffered_readdir(struct file *file, filldir_t func,
|
|||
|
||||
buf.dirent = (void *)__get_free_page(GFP_KERNEL);
|
||||
if (!buf.dirent)
|
||||
return -ENOMEM;
|
||||
return nfserrno(-ENOMEM);
|
||||
|
||||
offset = *offsetp;
|
||||
|
||||
while (1) {
|
||||
struct inode *dir_inode = file->f_path.dentry->d_inode;
|
||||
unsigned int reclen;
|
||||
|
||||
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,
|
|||
if (!size)
|
||||
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;
|
||||
while (size > 0) {
|
||||
offset = de->offset;
|
||||
|
||||
if (func(cdp, de->name, de->namlen, de->offset,
|
||||
de->ino, de->d_type))
|
||||
goto done;
|
||||
break;
|
||||
|
||||
if (cdp->err != nfs_ok)
|
||||
goto done;
|
||||
break;
|
||||
|
||||
reclen = ALIGN(sizeof(*de) + de->namlen,
|
||||
sizeof(u64));
|
||||
size -= 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);
|
||||
}
|
||||
|
||||
done:
|
||||
free_page((unsigned long)(buf.dirent));
|
||||
|
||||
if (host_err)
|
||||
|
|
Загрузка…
Ссылка в новой задаче