Skip to content

Commit e518ff9

Browse files
fdmananaopsiff
authored andcommitted
btrfs: fix log tree replay failure due to file with 0 links and extents
commit 0a32e4f upstream. If we log a new inode (not persisted in a past transaction) that has 0 links and extents, then log another inode with an higher inode number, we end up with failing to replay the log tree with -EINVAL. The steps for this are: 1) create new file A 2) write some data to file A 3) open an fd on file A 4) unlink file A 5) fsync file A using the previously open fd 6) create file B (has higher inode number than file A) 7) fsync file B 8) power fail before current transaction commits Now when attempting to mount the fs, the log replay will fail with -ENOENT at replay_one_extent() when attempting to replay the first extent of file A. The failure comes when trying to open the inode for file A in the subvolume tree, since it doesn't exist. Before commit 5f61b96 ("btrfs: fix inode lookup error handling during log replay"), the returned error was -EIO instead of -ENOENT, since we converted any errors when attempting to read an inode during log replay to -EIO. The reason for this is that the log replay procedure fails to ignore the current inode when we are at the stage LOG_WALK_REPLAY_ALL, our current inode has 0 links and last inode we processed in the previous stage has a non 0 link count. In other words, the issue is that at replay_one_extent() we only update wc->ignore_cur_inode if the current replay stage is LOG_WALK_REPLAY_INODES. Fix this by updating wc->ignore_cur_inode whenever we find an inode item regardless of the current replay stage. This is a simple solution and easy to backport, but later we can do other alternatives like avoid logging extents or inode items other than the inode item for inodes with a link count of 0. The problem with the wc->ignore_cur_inode logic has been around since commit f2d72f4 ("Btrfs: fix warning when replaying log after fsync of a tmpfile") but it only became frequent to hit since the more recent commit 5e85262 ("btrfs: fix fsync of files with no hard links not persisting deletion"), because we stopped skipping inodes with a link count of 0 when logging, while before the problem would only be triggered if trying to replay a log tree created with an older kernel which has a logged inode with 0 links. A test case for fstests will be submitted soon. Reported-by: Peter Jung <ptr1337@cachyos.org> Link: https://lore.kernel.org/linux-btrfs/fce139db-4458-4788-bb97-c29acf6cb1df@cachyos.org/ Reported-by: burneddi <burneddi@protonmail.com> Link: https://lore.kernel.org/linux-btrfs/lh4W-Lwc0Mbk-QvBhhQyZxf6VbM3E8VtIvU3fPIQgweP_Q1n7wtlUZQc33sYlCKYd-o6rryJQfhHaNAOWWRKxpAXhM8NZPojzsJPyHMf2qY=@protonmail.com/#t Reported-by: Russell Haley <yumpusamongus@gmail.com> Link: https://lore.kernel.org/linux-btrfs/598ecc75-eb80-41b3-83c2-f2317fbb9864@gmail.com/ Fixes: f2d72f4 ("Btrfs: fix warning when replaying log after fsync of a tmpfile") CC: stable@vger.kernel.org # 5.4+ Reviewed-by: Boris Burkov <boris@bur.io> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> (cherry picked from commit 807c422740068eaffcd4f82cc9860593581dc673)
1 parent ac7fa34 commit e518ff9

1 file changed

Lines changed: 30 additions & 18 deletions

File tree

fs/btrfs/tree-log.c

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,7 @@ struct walk_control {
324324

325325
/*
326326
* Ignore any items from the inode currently being processed. Needs
327-
* to be set every time we find a BTRFS_INODE_ITEM_KEY and we are in
328-
* the LOG_WALK_REPLAY_INODES stage.
327+
* to be set every time we find a BTRFS_INODE_ITEM_KEY.
329328
*/
330329
bool ignore_cur_inode;
331330

@@ -2447,32 +2446,45 @@ static int replay_one_buffer(struct btrfs_root *log, struct extent_buffer *eb,
24472446

24482447
nritems = btrfs_header_nritems(eb);
24492448
for (i = 0; i < nritems; i++) {
2450-
btrfs_item_key_to_cpu(eb, &key, i);
2449+
struct btrfs_inode_item *inode_item;
24512450

2452-
/* inode keys are done during the first stage */
2453-
if (key.type == BTRFS_INODE_ITEM_KEY &&
2454-
wc->stage == LOG_WALK_REPLAY_INODES) {
2455-
struct btrfs_inode_item *inode_item;
2456-
u32 mode;
2451+
btrfs_item_key_to_cpu(eb, &key, i);
24572452

2458-
inode_item = btrfs_item_ptr(eb, i,
2459-
struct btrfs_inode_item);
2453+
if (key.type == BTRFS_INODE_ITEM_KEY) {
2454+
inode_item = btrfs_item_ptr(eb, i, struct btrfs_inode_item);
24602455
/*
2461-
* If we have a tmpfile (O_TMPFILE) that got fsync'ed
2462-
* and never got linked before the fsync, skip it, as
2463-
* replaying it is pointless since it would be deleted
2464-
* later. We skip logging tmpfiles, but it's always
2465-
* possible we are replaying a log created with a kernel
2466-
* that used to log tmpfiles.
2456+
* An inode with no links is either:
2457+
*
2458+
* 1) A tmpfile (O_TMPFILE) that got fsync'ed and never
2459+
* got linked before the fsync, skip it, as replaying
2460+
* it is pointless since it would be deleted later.
2461+
* We skip logging tmpfiles, but it's always possible
2462+
* we are replaying a log created with a kernel that
2463+
* used to log tmpfiles;
2464+
*
2465+
* 2) A non-tmpfile which got its last link deleted
2466+
* while holding an open fd on it and later got
2467+
* fsynced through that fd. We always log the
2468+
* parent inodes when inode->last_unlink_trans is
2469+
* set to the current transaction, so ignore all the
2470+
* inode items for this inode. We will delete the
2471+
* inode when processing the parent directory with
2472+
* replay_dir_deletes().
24672473
*/
24682474
if (btrfs_inode_nlink(eb, inode_item) == 0) {
24692475
wc->ignore_cur_inode = true;
24702476
continue;
24712477
} else {
24722478
wc->ignore_cur_inode = false;
24732479
}
2474-
ret = replay_xattr_deletes(wc->trans, root, log,
2475-
path, key.objectid);
2480+
}
2481+
2482+
/* Inode keys are done during the first stage. */
2483+
if (key.type == BTRFS_INODE_ITEM_KEY &&
2484+
wc->stage == LOG_WALK_REPLAY_INODES) {
2485+
u32 mode;
2486+
2487+
ret = replay_xattr_deletes(wc->trans, root, log, path, key.objectid);
24762488
if (ret)
24772489
break;
24782490
mode = btrfs_inode_mode(eb, inode_item);

0 commit comments

Comments
 (0)