mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-12-27 10:01:39 -05:00
shmem: fix recovery on rename failures
maple_tree insertions can fail if we are seriously short on memory;
simple_offset_rename() does not recover well if it runs into that.
The same goes for simple_offset_rename_exchange().
Moreover, shmem_whiteout() expects that if it succeeds, the caller will
progress to d_move(), i.e. that shmem_rename2() won't fail past the
successful call of shmem_whiteout().
Not hard to fix, fortunately - mtree_store() can't fail if the index we
are trying to store into is already present in the tree as a singleton.
For simple_offset_rename_exchange() that's enough - we just need to be
careful about the order of operations.
For simple_offset_rename() solution is to preinsert the target into the
tree for new_dir; the rest can be done without any potentially failing
operations.
That preinsertion has to be done in shmem_rename2() rather than in
simple_offset_rename() itself - otherwise we'd need to deal with the
possibility of failure after successful shmem_whiteout().
Fixes: a2e459555c ("shmem: stable directory offsets")
Reviewed-by: Christian Brauner <brauner@kernel.org>
Reviewed-by: Chuck Lever <chuck.lever@oracle.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
24
mm/shmem.c
24
mm/shmem.c
@@ -4038,6 +4038,7 @@ static int shmem_rename2(struct mnt_idmap *idmap,
|
||||
{
|
||||
struct inode *inode = d_inode(old_dentry);
|
||||
int they_are_dirs = S_ISDIR(inode->i_mode);
|
||||
bool had_offset = false;
|
||||
int error;
|
||||
|
||||
if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE | RENAME_WHITEOUT))
|
||||
@@ -4050,16 +4051,23 @@ static int shmem_rename2(struct mnt_idmap *idmap,
|
||||
if (!simple_empty(new_dentry))
|
||||
return -ENOTEMPTY;
|
||||
|
||||
if (flags & RENAME_WHITEOUT) {
|
||||
error = shmem_whiteout(idmap, old_dir, old_dentry);
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
|
||||
error = simple_offset_rename(old_dir, old_dentry, new_dir, new_dentry);
|
||||
if (error)
|
||||
error = simple_offset_add(shmem_get_offset_ctx(new_dir), new_dentry);
|
||||
if (error == -EBUSY)
|
||||
had_offset = true;
|
||||
else if (unlikely(error))
|
||||
return error;
|
||||
|
||||
if (flags & RENAME_WHITEOUT) {
|
||||
error = shmem_whiteout(idmap, old_dir, old_dentry);
|
||||
if (error) {
|
||||
if (!had_offset)
|
||||
simple_offset_remove(shmem_get_offset_ctx(new_dir),
|
||||
new_dentry);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
simple_offset_rename(old_dir, old_dentry, new_dir, new_dentry);
|
||||
if (d_really_is_positive(new_dentry)) {
|
||||
(void) shmem_unlink(new_dir, new_dentry);
|
||||
if (they_are_dirs) {
|
||||
|
||||
Reference in New Issue
Block a user