diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 98c119b0291660bd164e96ff023c845d4f515293..c92d9b83bb38fc65542bc4b929d9945de3d478dd 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -9412,6 +9412,8 @@ static int btrfs_rename_exchange(struct inode *old_dir, u64 new_idx = 0; u64 root_objectid; int ret; + bool root_log_pinned = false; + bool dest_log_pinned = false; /* we only allow rename subvolume link between subvolumes */ if (old_ino != BTRFS_FIRST_FREE_OBJECTID && root != dest) @@ -9464,6 +9466,7 @@ static int btrfs_rename_exchange(struct inode *old_dir, if (ret) goto out_fail; btrfs_pin_log_trans(root); + root_log_pinned = true; } /* And now for the dest. */ @@ -9479,6 +9482,7 @@ static int btrfs_rename_exchange(struct inode *old_dir, if (ret) goto out_fail; btrfs_pin_log_trans(dest); + dest_log_pinned = true; } /* Update inode version and ctime/mtime. */ @@ -9557,17 +9561,47 @@ static int btrfs_rename_exchange(struct inode *old_dir, if (new_inode->i_nlink == 1) BTRFS_I(new_inode)->dir_index = new_idx; - if (old_ino != BTRFS_FIRST_FREE_OBJECTID) { + if (root_log_pinned) { parent = new_dentry->d_parent; btrfs_log_new_name(trans, old_inode, old_dir, parent); btrfs_end_log_trans(root); + root_log_pinned = false; } - if (new_ino != BTRFS_FIRST_FREE_OBJECTID) { + if (dest_log_pinned) { parent = old_dentry->d_parent; btrfs_log_new_name(trans, new_inode, new_dir, parent); btrfs_end_log_trans(dest); + dest_log_pinned = false; } out_fail: + /* + * If we have pinned a log and an error happened, we unpin tasks + * trying to sync the log and force them to fallback to a transaction + * commit if the log currently contains any of the inodes involved in + * this rename operation (to ensure we do not persist a log with an + * inconsistent state for any of these inodes or leading to any + * inconsistencies when replayed). If the transaction was aborted, the + * abortion reason is propagated to userspace when attempting to commit + * the transaction. If the log does not contain any of these inodes, we + * allow the tasks to sync it. + */ + if (ret && (root_log_pinned || dest_log_pinned)) { + if (btrfs_inode_in_log(old_dir, root->fs_info->generation) || + btrfs_inode_in_log(new_dir, root->fs_info->generation) || + btrfs_inode_in_log(old_inode, root->fs_info->generation) || + (new_inode && + btrfs_inode_in_log(new_inode, root->fs_info->generation))) + btrfs_set_log_full_commit(root->fs_info, trans); + + if (root_log_pinned) { + btrfs_end_log_trans(root); + root_log_pinned = false; + } + if (dest_log_pinned) { + btrfs_end_log_trans(dest); + dest_log_pinned = false; + } + } ret = btrfs_end_transaction(trans, root); out_notrans: if (new_ino == BTRFS_FIRST_FREE_OBJECTID)