Skip to content

Commit 86e8aa0

Browse files
committed
Btrfs: unpin logs if rename exchange operation fails
If rename exchange operations fail at some point after we pinned any of the logs, we end up aborting the current transaction but never unpin the logs, which leaves concurrent tasks that are trying to sync the logs (as part of an fsync request from user space) blocked forever and preventing the filesystem from being unmountable. Fix this by safely unpinning the log. Signed-off-by: Filipe Manana <[email protected]>
1 parent c990161 commit 86e8aa0

File tree

1 file changed

+36
-2
lines changed

1 file changed

+36
-2
lines changed

fs/btrfs/inode.c

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9412,6 +9412,8 @@ static int btrfs_rename_exchange(struct inode *old_dir,
94129412
u64 new_idx = 0;
94139413
u64 root_objectid;
94149414
int ret;
9415+
bool root_log_pinned = false;
9416+
bool dest_log_pinned = false;
94159417

94169418
/* we only allow rename subvolume link between subvolumes */
94179419
if (old_ino != BTRFS_FIRST_FREE_OBJECTID && root != dest)
@@ -9464,6 +9466,7 @@ static int btrfs_rename_exchange(struct inode *old_dir,
94649466
if (ret)
94659467
goto out_fail;
94669468
btrfs_pin_log_trans(root);
9469+
root_log_pinned = true;
94679470
}
94689471

94699472
/* And now for the dest. */
@@ -9479,6 +9482,7 @@ static int btrfs_rename_exchange(struct inode *old_dir,
94799482
if (ret)
94809483
goto out_fail;
94819484
btrfs_pin_log_trans(dest);
9485+
dest_log_pinned = true;
94829486
}
94839487

94849488
/* Update inode version and ctime/mtime. */
@@ -9557,17 +9561,47 @@ static int btrfs_rename_exchange(struct inode *old_dir,
95579561
if (new_inode->i_nlink == 1)
95589562
BTRFS_I(new_inode)->dir_index = new_idx;
95599563

9560-
if (old_ino != BTRFS_FIRST_FREE_OBJECTID) {
9564+
if (root_log_pinned) {
95619565
parent = new_dentry->d_parent;
95629566
btrfs_log_new_name(trans, old_inode, old_dir, parent);
95639567
btrfs_end_log_trans(root);
9568+
root_log_pinned = false;
95649569
}
9565-
if (new_ino != BTRFS_FIRST_FREE_OBJECTID) {
9570+
if (dest_log_pinned) {
95669571
parent = old_dentry->d_parent;
95679572
btrfs_log_new_name(trans, new_inode, new_dir, parent);
95689573
btrfs_end_log_trans(dest);
9574+
dest_log_pinned = false;
95699575
}
95709576
out_fail:
9577+
/*
9578+
* If we have pinned a log and an error happened, we unpin tasks
9579+
* trying to sync the log and force them to fallback to a transaction
9580+
* commit if the log currently contains any of the inodes involved in
9581+
* this rename operation (to ensure we do not persist a log with an
9582+
* inconsistent state for any of these inodes or leading to any
9583+
* inconsistencies when replayed). If the transaction was aborted, the
9584+
* abortion reason is propagated to userspace when attempting to commit
9585+
* the transaction. If the log does not contain any of these inodes, we
9586+
* allow the tasks to sync it.
9587+
*/
9588+
if (ret && (root_log_pinned || dest_log_pinned)) {
9589+
if (btrfs_inode_in_log(old_dir, root->fs_info->generation) ||
9590+
btrfs_inode_in_log(new_dir, root->fs_info->generation) ||
9591+
btrfs_inode_in_log(old_inode, root->fs_info->generation) ||
9592+
(new_inode &&
9593+
btrfs_inode_in_log(new_inode, root->fs_info->generation)))
9594+
btrfs_set_log_full_commit(root->fs_info, trans);
9595+
9596+
if (root_log_pinned) {
9597+
btrfs_end_log_trans(root);
9598+
root_log_pinned = false;
9599+
}
9600+
if (dest_log_pinned) {
9601+
btrfs_end_log_trans(dest);
9602+
dest_log_pinned = false;
9603+
}
9604+
}
95719605
ret = btrfs_end_transaction(trans, root);
95729606
out_notrans:
95739607
if (new_ino == BTRFS_FIRST_FREE_OBJECTID)

0 commit comments

Comments
 (0)