Commit 1bcb3440 authored by Jeff Layton's avatar Jeff Layton Committed by Ilya Dryomov
Browse files

ceph: only use d_name directly when parent is locked



Ben reported tripping the BUG_ON in create_request_message during some
performance testing. Analysis of the vmcore showed that the length of
the r_dentry->d_name string changed after we allocated the buffer, but
before we encoded it.

build_dentry_path returns pointers to d_name in the common case of
non-snapped dentries, but this optimization isn't safe unless the parent
directory is locked. When it isn't, have the code make a copy of the
d_name while holding the d_lock.

Cc: stable@vger.kernel.org
Reported-by: default avatarBen England <bengland@redhat.com>
Signed-off-by: default avatarJeff Layton <jlayton@kernel.org>
Reviewed-by: default avatar"Yan, Zheng" <zyan@redhat.com>
Signed-off-by: default avatarIlya Dryomov <idryomov@gmail.com>
parent 085b7755
Loading
Loading
Loading
Loading
+50 −11
Original line number Diff line number Diff line
@@ -2161,10 +2161,39 @@ retry:
	return path;
}

/* Duplicate the dentry->d_name.name safely */
static int clone_dentry_name(struct dentry *dentry, const char **ppath,
			     int *ppathlen)
{
	u32 len;
	char *name;

retry:
	len = READ_ONCE(dentry->d_name.len);
	name = kmalloc(len + 1, GFP_NOFS);
	if (!name)
		return -ENOMEM;

	spin_lock(&dentry->d_lock);
	if (dentry->d_name.len != len) {
		spin_unlock(&dentry->d_lock);
		kfree(name);
		goto retry;
	}
	memcpy(name, dentry->d_name.name, len);
	spin_unlock(&dentry->d_lock);

	name[len] = '\0';
	*ppath = name;
	*ppathlen = len;
	return 0;
}

static int build_dentry_path(struct dentry *dentry, struct inode *dir,
			     const char **ppath, int *ppathlen, u64 *pino,
			     int *pfreepath)
			     bool *pfreepath, bool parent_locked)
{
	int ret;
	char *path;

	rcu_read_lock();
@@ -2173,8 +2202,15 @@ static int build_dentry_path(struct dentry *dentry, struct inode *dir,
	if (dir && ceph_snap(dir) == CEPH_NOSNAP) {
		*pino = ceph_ino(dir);
		rcu_read_unlock();
		if (parent_locked) {
			*ppath = dentry->d_name.name;
			*ppathlen = dentry->d_name.len;
		} else {
			ret = clone_dentry_name(dentry, ppath, ppathlen);
			if (ret)
				return ret;
			*pfreepath = true;
		}
		return 0;
	}
	rcu_read_unlock();
@@ -2182,13 +2218,13 @@ static int build_dentry_path(struct dentry *dentry, struct inode *dir,
	if (IS_ERR(path))
		return PTR_ERR(path);
	*ppath = path;
	*pfreepath = 1;
	*pfreepath = true;
	return 0;
}

static int build_inode_path(struct inode *inode,
			    const char **ppath, int *ppathlen, u64 *pino,
			    int *pfreepath)
			    bool *pfreepath)
{
	struct dentry *dentry;
	char *path;
@@ -2204,7 +2240,7 @@ static int build_inode_path(struct inode *inode,
	if (IS_ERR(path))
		return PTR_ERR(path);
	*ppath = path;
	*pfreepath = 1;
	*pfreepath = true;
	return 0;
}

@@ -2215,7 +2251,7 @@ static int build_inode_path(struct inode *inode,
static int set_request_path_attr(struct inode *rinode, struct dentry *rdentry,
				  struct inode *rdiri, const char *rpath,
				  u64 rino, const char **ppath, int *pathlen,
				  u64 *ino, int *freepath)
				  u64 *ino, bool *freepath, bool parent_locked)
{
	int r = 0;

@@ -2225,7 +2261,7 @@ static int set_request_path_attr(struct inode *rinode, struct dentry *rdentry,
		     ceph_snap(rinode));
	} else if (rdentry) {
		r = build_dentry_path(rdentry, rdiri, ppath, pathlen, ino,
					freepath);
					freepath, parent_locked);
		dout(" dentry %p %llx/%.*s\n", rdentry, *ino, *pathlen,
		     *ppath);
	} else if (rpath || rino) {
@@ -2251,7 +2287,7 @@ static struct ceph_msg *create_request_message(struct ceph_mds_client *mdsc,
	const char *path2 = NULL;
	u64 ino1 = 0, ino2 = 0;
	int pathlen1 = 0, pathlen2 = 0;
	int freepath1 = 0, freepath2 = 0;
	bool freepath1 = false, freepath2 = false;
	int len;
	u16 releases;
	void *p, *end;
@@ -2259,16 +2295,19 @@ static struct ceph_msg *create_request_message(struct ceph_mds_client *mdsc,

	ret = set_request_path_attr(req->r_inode, req->r_dentry,
			      req->r_parent, req->r_path1, req->r_ino1.ino,
			      &path1, &pathlen1, &ino1, &freepath1);
			      &path1, &pathlen1, &ino1, &freepath1,
			      test_bit(CEPH_MDS_R_PARENT_LOCKED,
					&req->r_req_flags));
	if (ret < 0) {
		msg = ERR_PTR(ret);
		goto out;
	}

	/* If r_old_dentry is set, then assume that its parent is locked */
	ret = set_request_path_attr(NULL, req->r_old_dentry,
			      req->r_old_dentry_dir,
			      req->r_path2, req->r_ino2.ino,
			      &path2, &pathlen2, &ino2, &freepath2);
			      &path2, &pathlen2, &ino2, &freepath2, true);
	if (ret < 0) {
		msg = ERR_PTR(ret);
		goto out_free1;