Unverified Commit 193feb69 authored by Christian Brauner's avatar Christian Brauner
Browse files

Merge patch series 'Fix shmem_rename2 directory offset calculation' of...

Merge patch series 'Fix shmem_rename2 directory offset calculation' of https://lore.kernel.org/r/20240415152057.4605-1-cel@kernel.org

Pull shmem_rename2() offset fixes from Chuck Lever:

The existing code in shmem_rename2() allocates a fresh directory
offset value when renaming over an existing destination entry. User
space does not expect this behavior. In particular, applications
that rename while walking a directory can loop indefinitely because
they never reach the end of the directory.

* 'Fix shmem_rename2 directory offset calculation' of https://lore.kernel.org/r/20240415152057.4605-1-cel@kernel.org

: (3 commits)
  shmem: Fix shmem_rename2()
  libfs: Add simple_offset_rename() API
  libfs: Fix simple_offset_rename_exchange()

 fs/libfs.c         | 55 +++++++++++++++++++++++++++++++++++++++++-----
 include/linux/fs.h |  2 ++
 mm/shmem.c         |  3 +--
 3 files changed, 52 insertions(+), 8 deletions(-)

Signed-off-by: default avatarChristian Brauner <brauner@kernel.org>
parents c6854e5a ad191eb6
Loading
Loading
Loading
Loading
+49 −6
Original line number Diff line number Diff line
@@ -295,6 +295,18 @@ int simple_offset_add(struct offset_ctx *octx, struct dentry *dentry)
	return 0;
}

static int simple_offset_replace(struct offset_ctx *octx, struct dentry *dentry,
				 long offset)
{
	int ret;

	ret = mtree_store(&octx->mt, offset, dentry, GFP_KERNEL);
	if (ret)
		return ret;
	offset_set(dentry, offset);
	return 0;
}

/**
 * simple_offset_remove - Remove an entry to a directory's offset map
 * @octx: directory offset ctx to be updated
@@ -345,6 +357,36 @@ int simple_offset_empty(struct dentry *dentry)
	return ret;
}

/**
 * simple_offset_rename - handle directory offsets for rename
 * @old_dir: parent directory of source entry
 * @old_dentry: dentry of source entry
 * @new_dir: parent_directory of destination entry
 * @new_dentry: dentry of destination
 *
 * Caller provides appropriate serialization.
 *
 * User space expects the directory offset value of the replaced
 * (new) directory entry to be unchanged after a rename.
 *
 * Returns zero on success, a negative errno value on failure.
 */
int simple_offset_rename(struct inode *old_dir, struct dentry *old_dentry,
			 struct inode *new_dir, struct dentry *new_dentry)
{
	struct offset_ctx *old_ctx = old_dir->i_op->get_offset_ctx(old_dir);
	struct offset_ctx *new_ctx = new_dir->i_op->get_offset_ctx(new_dir);
	long new_offset = dentry2offset(new_dentry);

	simple_offset_remove(old_ctx, old_dentry);

	if (new_offset) {
		offset_set(new_dentry, 0);
		return simple_offset_replace(new_ctx, old_dentry, new_offset);
	}
	return simple_offset_add(new_ctx, old_dentry);
}

/**
 * simple_offset_rename_exchange - exchange rename with directory offsets
 * @old_dir: parent of dentry being moved
@@ -352,6 +394,9 @@ int simple_offset_empty(struct dentry *dentry)
 * @new_dir: destination parent
 * @new_dentry: destination dentry
 *
 * This API preserves the directory offset values. Caller provides
 * appropriate serialization.
 *
 * Returns zero on success. Otherwise a negative errno is returned and the
 * rename is rolled back.
 */
@@ -369,11 +414,11 @@ int simple_offset_rename_exchange(struct inode *old_dir,
	simple_offset_remove(old_ctx, old_dentry);
	simple_offset_remove(new_ctx, new_dentry);

	ret = simple_offset_add(new_ctx, old_dentry);
	ret = simple_offset_replace(new_ctx, old_dentry, new_index);
	if (ret)
		goto out_restore;

	ret = simple_offset_add(old_ctx, new_dentry);
	ret = simple_offset_replace(old_ctx, new_dentry, old_index);
	if (ret) {
		simple_offset_remove(new_ctx, old_dentry);
		goto out_restore;
@@ -388,10 +433,8 @@ int simple_offset_rename_exchange(struct inode *old_dir,
	return 0;

out_restore:
	offset_set(old_dentry, old_index);
	mtree_store(&old_ctx->mt, old_index, old_dentry, GFP_KERNEL);
	offset_set(new_dentry, new_index);
	mtree_store(&new_ctx->mt, new_index, new_dentry, GFP_KERNEL);
	(void)simple_offset_replace(old_ctx, old_dentry, old_index);
	(void)simple_offset_replace(new_ctx, new_dentry, new_index);
	return ret;
}

+2 −0
Original line number Diff line number Diff line
@@ -3353,6 +3353,8 @@ void simple_offset_init(struct offset_ctx *octx);
int simple_offset_add(struct offset_ctx *octx, struct dentry *dentry);
void simple_offset_remove(struct offset_ctx *octx, struct dentry *dentry);
int simple_offset_empty(struct dentry *dentry);
int simple_offset_rename(struct inode *old_dir, struct dentry *old_dentry,
			 struct inode *new_dir, struct dentry *new_dentry);
int simple_offset_rename_exchange(struct inode *old_dir,
				  struct dentry *old_dentry,
				  struct inode *new_dir,
+1 −2
Original line number Diff line number Diff line
@@ -3473,8 +3473,7 @@ static int shmem_rename2(struct mnt_idmap *idmap,
			return error;
	}

	simple_offset_remove(shmem_get_offset_ctx(old_dir), old_dentry);
	error = simple_offset_add(shmem_get_offset_ctx(new_dir), old_dentry);
	error = simple_offset_rename(old_dir, old_dentry, new_dir, new_dentry);
	if (error)
		return error;