]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
btrfs: scrub: always update btrfs_scrub_progress::last_physical
authorQu Wenruo <wqu@suse.com>
Mon, 3 Nov 2025 02:21:09 +0000 (12:51 +1030)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 19 Jan 2026 12:09:42 +0000 (13:09 +0100)
[ Upstream commit 54df8b80cc63aa0f22c4590cad11542731ed43ff ]

[BUG]
When a scrub failed immediately without any byte scrubbed, the returned
btrfs_scrub_progress::last_physical will always be 0, even if there is a
non-zero @start passed into btrfs_scrub_dev() for resume cases.

This will reset the progress and make later scrub resume start from the
beginning.

[CAUSE]
The function btrfs_scrub_dev() accepts a @progress parameter to copy its
updated progress to the caller, there are cases where we either don't
touch progress::last_physical at all or copy 0 into last_physical:

- last_physical not updated at all
  If some error happened before scrubbing any super block or chunk, we
  will not copy the progress, leaving the @last_physical untouched.

  E.g. failed to allocate @sctx, scrubbing a missing device or even
  there is already a running scrub and so on.

  All those cases won't touch @progress at all, resulting the
  last_physical untouched and will be left as 0 for most cases.

- Error out before scrubbing any bytes
  In those case we allocated @sctx, and sctx->stat.last_physical is all
  zero (initialized by kvzalloc()).
  Unfortunately some critical errors happened during
  scrub_enumerate_chunks() or scrub_supers() before any stripe is really
  scrubbed.

  In that case although we will copy sctx->stat back to @progress, since
  no byte is really scrubbed, last_physical will be overwritten to 0.

[FIX]
Make sure the parameter @progress always has its @last_physical member
updated to @start parameter inside btrfs_scrub_dev().

At the very beginning of the function, set @progress->last_physical to
@start, so that even if we error out without doing progress copying,
last_physical is still at @start.

Then after we got @sctx allocated, set sctx->stat.last_physical to
@start, this will make sure even if we didn't get any byte scrubbed, at
the progress copying stage the @last_physical is not left as zero.

This should resolve the resume progress reset problem.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
fs/btrfs/scrub.c

index aac4ee58809524f24cbfe0897c5e04f5105dc1da..3d5cb6e6b3bbe203e5b1e795bb026d20e2c55064 100644 (file)
@@ -4090,6 +4090,10 @@ int btrfs_scrub_dev(struct btrfs_fs_info *fs_info, u64 devid, u64 start,
        unsigned int nofs_flag;
        bool need_commit = false;
 
+       /* Set the basic fallback @last_physical before we got a sctx. */
+       if (progress)
+               progress->last_physical = start;
+
        if (btrfs_fs_closing(fs_info))
                return -EAGAIN;
 
@@ -4126,6 +4130,7 @@ int btrfs_scrub_dev(struct btrfs_fs_info *fs_info, u64 devid, u64 start,
        sctx = scrub_setup_ctx(fs_info, is_dev_replace);
        if (IS_ERR(sctx))
                return PTR_ERR(sctx);
+       sctx->stat.last_physical = start;
 
        ret = scrub_workers_get(fs_info, is_dev_replace);
        if (ret)