Skip to content

Commit 8bb6898

Browse files
fdmananakdave
authored andcommitted
btrfs: fix directory logging due to race with concurrent index key deletion
Sometimes we log a directory without holding its VFS lock, so while we logging it, dir index entries may be added or removed. This typically happens when logging a dentry from a parent directory that points to a new directory, through log_new_dir_dentries(), or when while logging some other inode we also need to log its parent directories (through btrfs_log_all_parents()). This means that while we are at log_dir_items(), we may not find a dir index key we found before, because it was deleted in the meanwhile, so a call to btrfs_search_slot() may return 1 (key not found). In that case we return from log_dir_items() with a success value (the variable 'err' has a value of 0). This can lead to a few problems, specially in the case where 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 1, we end up returning from log_dir_items() with success (0) 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. So fix this by making log_dir_items() move on to the next dir index key if it does not find the one it was looking for. Reported-by: David Arendt <[email protected]> Link: https://lore.kernel.org/linux-btrfs/[email protected]/ 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 6d3d970 commit 8bb6898

File tree

1 file changed

+15
-6
lines changed

1 file changed

+15
-6
lines changed

fs/btrfs/tree-log.c

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3857,17 +3857,26 @@ static noinline int log_dir_items(struct btrfs_trans_handle *trans,
38573857
btrfs_release_path(path);
38583858

38593859
/*
3860-
* Find the first key from this transaction again. See the note for
3861-
* log_new_dir_dentries, if we're logging a directory recursively we
3862-
* won't be holding its i_mutex, which means we can modify the directory
3863-
* while we're logging it. If we remove an entry between our first
3864-
* search and this search we'll not find the key again and can just
3865-
* bail.
3860+
* Find the first key from this transaction again or the one we were at
3861+
* in the loop below in case we had to reschedule. We may be logging the
3862+
* directory without holding its VFS lock, which happen when logging new
3863+
* dentries (through log_new_dir_dentries()) or in some cases when we
3864+
* need to log the parent directory of an inode. This means a dir index
3865+
* key might be deleted from the inode's root, and therefore we may not
3866+
* find it anymore. If we can't find it, just move to the next key. We
3867+
* can not bail out and ignore, because if we do that we will simply
3868+
* not log dir index keys that come after the one that was just deleted
3869+
* and we can end up logging a dir index range that ends at (u64)-1
3870+
* (@last_offset is initialized to that), resulting in removing dir
3871+
* entries we should not remove at log replay time.
38663872
*/
38673873
search:
38683874
ret = btrfs_search_slot(NULL, root, &min_key, path, 0, 0);
3875+
if (ret > 0)
3876+
ret = btrfs_next_item(root, path);
38693877
if (ret < 0)
38703878
err = ret;
3879+
/* If ret is 1, there are no more keys in the inode's root. */
38713880
if (ret != 0)
38723881
goto done;
38733882

0 commit comments

Comments
 (0)