diff options
Diffstat (limited to 'fs/btrfs/tree-log.c')
-rw-r--r-- | fs/btrfs/tree-log.c | 30 |
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; |