Skip to content

Commit 6d3d970

Browse files
fdmananakdave
authored andcommitted
btrfs: fix missing error handling when logging directory items
When logging a directory, at log_dir_items(), if we get an error when attempting to search the subvolume tree for a dir index item, we end up returning 0 (success) from log_dir_items() because 'err' is left with a value of 0. This can lead to a few problems, specially in the case the variable 'last_offset' has a value of (u64)-1 (and it's initialized to that when it was declared): 1) By returning from log_dir_items() with success (0) and a value of (u64)-1 for '*last_offset_ret', we end up not logging any other dir index keys that follow the missing, just deleted, index key. The (u64)-1 value makes log_directory_changes() not call log_dir_items() again; 2) Before returning with success (0), log_dir_items(), will log a dir index range item covering a range from the last old dentry index (stored in the variable 'last_old_dentry_offset') to the value of 'last_offset'. If 'last_offset' has a value of (u64)-1, then it means if the log is persisted and replayed after a power failure, it will cause deletion of all the directory entries that have an index number between last_old_dentry_offset + 1 and (u64)-1; 3) We can end up returning from log_dir_items() with ctx->last_dir_item_offset having a lower value than inode->last_dir_index_offset, because the former is set to the current key we are processing at process_dir_items_leaf(), and at the end of log_directory_changes() we set inode->last_dir_index_offset to the current value of ctx->last_dir_item_offset. So if for example a deletion of a lower dir index key happened, we set ctx->last_dir_item_offset to that index value, then if we return from log_dir_items() because btrfs_search_slot() returned an error, we end up returning without any error from log_dir_items() and then log_directory_changes() sets inode->last_dir_index_offset to a lower value than it had before. This can result in unpredictable and unexpected behaviour when we need to log again the directory in the same transaction, and can result in ending up with a log tree leaf that has duplicated keys, as we do batch insertions of dir index keys into a log tree. Fix this by setting 'err' to the value of 'ret' in case btrfs_search_slot() or btrfs_previous_item() returned an error. That will result in falling back to a full transaction commit. Reported-by: David Arendt <[email protected]> Link: https://lore.kernel.org/linux-btrfs/[email protected]/ Fixes: e02119d ("Btrfs: Add a write ahead tree log to optimize synchronous operations") CC: [email protected] # 4.14+ Reviewed-by: Josef Bacik <[email protected]> Signed-off-by: Filipe Manana <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent 85e79ec commit 6d3d970

File tree

1 file changed

+9
-0
lines changed

1 file changed

+9
-0
lines changed

fs/btrfs/tree-log.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3826,7 +3826,10 @@ static noinline int log_dir_items(struct btrfs_trans_handle *trans,
38263826
path->slots[0]);
38273827
if (tmp.type == BTRFS_DIR_INDEX_KEY)
38283828
last_old_dentry_offset = tmp.offset;
3829+
} else if (ret < 0) {
3830+
err = ret;
38293831
}
3832+
38303833
goto done;
38313834
}
38323835

@@ -3846,7 +3849,11 @@ static noinline int log_dir_items(struct btrfs_trans_handle *trans,
38463849
*/
38473850
if (tmp.type == BTRFS_DIR_INDEX_KEY)
38483851
last_old_dentry_offset = tmp.offset;
3852+
} else if (ret < 0) {
3853+
err = ret;
3854+
goto done;
38493855
}
3856+
38503857
btrfs_release_path(path);
38513858

38523859
/*
@@ -3859,6 +3866,8 @@ static noinline int log_dir_items(struct btrfs_trans_handle *trans,
38593866
*/
38603867
search:
38613868
ret = btrfs_search_slot(NULL, root, &min_key, path, 0, 0);
3869+
if (ret < 0)
3870+
err = ret;
38623871
if (ret != 0)
38633872
goto done;
38643873

0 commit comments

Comments
 (0)