|
@@ -5580,9 +5580,33 @@ static int btrfs_log_all_parents(struct btrfs_trans_handle *trans,
|
|
|
|
|
|
dir_inode = btrfs_iget(fs_info->sb, &inode_key,
|
|
dir_inode = btrfs_iget(fs_info->sb, &inode_key,
|
|
root, NULL);
|
|
root, NULL);
|
|
- /* If parent inode was deleted, skip it. */
|
|
|
|
- if (IS_ERR(dir_inode))
|
|
|
|
- continue;
|
|
|
|
|
|
+ /*
|
|
|
|
+ * If the parent inode was deleted, return an error to
|
|
|
|
+ * fallback to a transaction commit. This is to prevent
|
|
|
|
+ * getting an inode that was moved from one parent A to
|
|
|
|
+ * a parent B, got its former parent A deleted and then
|
|
|
|
+ * it got fsync'ed, from existing at both parents after
|
|
|
|
+ * a log replay (and the old parent still existing).
|
|
|
|
+ * Example:
|
|
|
|
+ *
|
|
|
|
+ * mkdir /mnt/A
|
|
|
|
+ * mkdir /mnt/B
|
|
|
|
+ * touch /mnt/B/bar
|
|
|
|
+ * sync
|
|
|
|
+ * mv /mnt/B/bar /mnt/A/bar
|
|
|
|
+ * mv -T /mnt/A /mnt/B
|
|
|
|
+ * fsync /mnt/B/bar
|
|
|
|
+ * <power fail>
|
|
|
|
+ *
|
|
|
|
+ * If we ignore the old parent B which got deleted,
|
|
|
|
+ * after a log replay we would have file bar linked
|
|
|
|
+ * at both parents and the old parent B would still
|
|
|
|
+ * exist.
|
|
|
|
+ */
|
|
|
|
+ if (IS_ERR(dir_inode)) {
|
|
|
|
+ ret = PTR_ERR(dir_inode);
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
|
|
if (ctx)
|
|
if (ctx)
|
|
ctx->log_new_dentries = false;
|
|
ctx->log_new_dentries = false;
|