summaryrefslogtreecommitdiffstats
path: root/fs/btrfs/tree-log.c
diff options
context:
space:
mode:
authorFilipe Manana <fdmanana@suse.com>2018-10-09 10:05:29 -0400
committerDavid Sterba <dsterba@suse.com>2018-10-15 11:23:39 -0400
commit0f375eed92b5a407657532637ed9652611a682f5 (patch)
tree1b4f5a2d0d0db821893045d69dc3dee5ca7276c5 /fs/btrfs/tree-log.c
parentf2d72f42d5fa3bf33761d9e47201745f624fcff5 (diff)
Btrfs: fix wrong dentries after fsync of file that got its parent replaced
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: stable@vger.kernel.org # 4.4+ Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
Diffstat (limited to 'fs/btrfs/tree-log.c')
-rw-r--r--fs/btrfs/tree-log.c30
1 files changed, 27 insertions, 3 deletions
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index a5e08a73653e..0dba09334a16 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -5580,9 +5580,33 @@ static int btrfs_log_all_parents(struct btrfs_trans_handle *trans,
5580 5580
5581 dir_inode = btrfs_iget(fs_info->sb, &inode_key, 5581 dir_inode = btrfs_iget(fs_info->sb, &inode_key,
5582 root, NULL); 5582 root, NULL);
5583 /* If parent inode was deleted, skip it. */ 5583 /*
5584 if (IS_ERR(dir_inode)) 5584 * If the parent inode was deleted, return an error to
5585 continue; 5585 * fallback to a transaction commit. This is to prevent
5586 * getting an inode that was moved from one parent A to
5587 * a parent B, got its former parent A deleted and then
5588 * it got fsync'ed, from existing at both parents after
5589 * a log replay (and the old parent still existing).
5590 * Example:
5591 *
5592 * mkdir /mnt/A
5593 * mkdir /mnt/B
5594 * touch /mnt/B/bar
5595 * sync
5596 * mv /mnt/B/bar /mnt/A/bar
5597 * mv -T /mnt/A /mnt/B
5598 * fsync /mnt/B/bar
5599 * <power fail>
5600 *
5601 * If we ignore the old parent B which got deleted,
5602 * after a log replay we would have file bar linked
5603 * at both parents and the old parent B would still
5604 * exist.
5605 */
5606 if (IS_ERR(dir_inode)) {
5607 ret = PTR_ERR(dir_inode);
5608 goto out;
5609 }
5586 5610
5587 if (ctx) 5611 if (ctx)
5588 ctx->log_new_dentries = false; 5612 ctx->log_new_dentries = false;