Skip to content

Commit d2c6df3

Browse files
fdmananagregkh
authored andcommitted
Btrfs: fix wrong dentries after fsync of file that got its parent replaced
commit 0f375ee upstream. In a scenario like the following: mkdir /mnt/A # inode 258 mkdir /mnt/B # inode 259 touch /mnt/B/bar # inode 260 sync mv /mnt/B/bar /mnt/A/bar mv -T /mnt/A /mnt/B fsync /mnt/B/bar <power fail> After replaying the log we end up with file bar having 2 hard links, both with the name 'bar' and one in the directory with inode number 258 and the other in the directory with inode number 259. Also, we end up with the directory inode 259 still existing and with the directory inode 258 still named as 'A', instead of 'B'. In this scenario, file 'bar' should only have one hard link, located at directory inode 258, the directory inode 259 should not exist anymore and the name for directory inode 258 should be 'B'. This incorrect behaviour happens because when attempting to log the old parents of an inode, we skip any parents that no longer exist. Fix this by forcing a full commit if an old parent no longer exists. A test case for fstests follows soon. CC: [email protected] # 4.4+ Signed-off-by: Filipe Manana <[email protected]> Signed-off-by: David Sterba <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 55f21e1 commit d2c6df3

File tree

1 file changed

+27
-3
lines changed

1 file changed

+27
-3
lines changed

fs/btrfs/tree-log.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5583,9 +5583,33 @@ static int btrfs_log_all_parents(struct btrfs_trans_handle *trans,
55835583

55845584
dir_inode = btrfs_iget(fs_info->sb, &inode_key,
55855585
root, NULL);
5586-
/* If parent inode was deleted, skip it. */
5587-
if (IS_ERR(dir_inode))
5588-
continue;
5586+
/*
5587+
* If the parent inode was deleted, return an error to
5588+
* fallback to a transaction commit. This is to prevent
5589+
* getting an inode that was moved from one parent A to
5590+
* a parent B, got its former parent A deleted and then
5591+
* it got fsync'ed, from existing at both parents after
5592+
* a log replay (and the old parent still existing).
5593+
* Example:
5594+
*
5595+
* mkdir /mnt/A
5596+
* mkdir /mnt/B
5597+
* touch /mnt/B/bar
5598+
* sync
5599+
* mv /mnt/B/bar /mnt/A/bar
5600+
* mv -T /mnt/A /mnt/B
5601+
* fsync /mnt/B/bar
5602+
* <power fail>
5603+
*
5604+
* If we ignore the old parent B which got deleted,
5605+
* after a log replay we would have file bar linked
5606+
* at both parents and the old parent B would still
5607+
* exist.
5608+
*/
5609+
if (IS_ERR(dir_inode)) {
5610+
ret = PTR_ERR(dir_inode);
5611+
goto out;
5612+
}
55895613

55905614
if (ctx)
55915615
ctx->log_new_dentries = false;

0 commit comments

Comments
 (0)