Skip to content

Conversation

loemraw
Copy link
Contributor

@loemraw loemraw commented Jul 29, 2025

This patch introduces the ability to resize a btrfs filesystem while it is not mounted via a new --offline flag. Currently only increasing the size of the filesystem is supported, though I believe it would be possible to implement shrinking the filesystem to the end of the last device extent.

This is a more general, and hopefully more useful, solution to the problem I was trying to solve with the
("btrfs-progs: add slack space for mkfs --shrink") patch. This patch should enable users to resize a filesystem without the higher capabilities needed for mounting a filesystem.

@Forza-tng
Copy link
Contributor

Will this allow for resizing a fs that would normally go ro due to enospc during mount?

@loemraw
Copy link
Contributor Author

loemraw commented Jul 29, 2025

Will this allow for resizing a fs that would normally go ro due to enospc during mount?

Yeah, I don't see why not!

@adam900710
Copy link
Collaborator

Will this allow for resizing a fs that would normally go ro due to enospc during mount?
Yeah, I don't see why not!

Unfortunately not that simple. If the fs has metadata full, it may still fail.
As this is still using regular transaction protection, metadata still need space for COW.

Thankfully this one is only mostly modifying chunk tree, which is not that common to exhaust system chunks.
But your btrfs_start_transaction() is using fs root, which is the wrong target, and can lead to unexpected ENOSPC error.

Please use chunk root instead since you're only modifying chunk tree.

Furthermore there are a lot of code style problems, like unexpected new lines splitting variable definitions (e.g. between new_size and old_total inside offline_resize()).
IIRC you can use checkpatch.pl from the kernel, as we mostly follow the same code style here.

And the parameter list for that function is also pretty bad. I'd say normally we put more important parameter (like path, which specifies a whole fs) first, and more specific parameters like new size after that.
Furthermore, the amount parameter as char doesn't make much sense, why not just parse the value and directly pass a s64 instead?

@adam900710
Copy link
Collaborator

And a lot of error handling is missing.

E.g. if btrfs_update_device() failed, you should abort the current transaction other than continue to commit the transaction.
This also gets rid of the unnecessary ret2 variable.

@loemraw
Copy link
Contributor Author

loemraw commented Jul 30, 2025

Will this allow for resizing a fs that would normally go ro due to enospc during mount?
Yeah, I don't see why not!

Unfortunately not that simple. If the fs has metadata full, it may still fail. As this is still using regular transaction protection, metadata still need space for COW.

Ahh yeah, that makes sense.

Thankfully this one is only mostly modifying chunk tree, which is not that common to exhaust system chunks. But your btrfs_start_transaction() is using fs root, which is the wrong target, and can lead to unexpected ENOSPC error.

Good catch...

I don't have the best understanding of btrfs transactions. When I was testing this patch it appeared like the filesystem was being resized correctly, why would this be the case if I'm passing the wrong target? Was it making changes to the wrong tree? Was I just getting lucky?

Please use chunk root instead since you're only modifying chunk tree.

Will do.

Furthermore there are a lot of code style problems, like unexpected new lines splitting variable definitions (e.g. between new_size and old_total inside offline_resize()). IIRC you can use checkpatch.pl from the kernel, as we mostly follow the same code style here.

Will check this in v2.

And the parameter list for that function is also pretty bad. I'd say normally we put more important parameter (like path, which specifies a whole fs) first, and more specific parameters like new size after that. Furthermore, the amount parameter as char doesn't make much sense, why not just parse the value and directly pass a s64 instead?

I'll rethink the parameter ordering.

I pass the amount parameter as a char because I call check_offline_resize_args inside offline_resize and I wanted to keep all of the argument logic together.

@loemraw
Copy link
Contributor Author

loemraw commented Jul 30, 2025

And a lot of error handling is missing.

E.g. if btrfs_update_device() failed, you should abort the current transaction other than continue to commit the transaction. This also gets rid of the unnecessary ret2 variable.

Ok, makes sense will fix in v2.

@adam900710
Copy link
Collaborator

I don't have the best understanding of btrfs transactions. When I was testing this patch it appeared like the filesystem was being resized correctly, why would this be the case if I'm passing the wrong target? Was it making changes to the wrong tree? Was I just getting lucky?

The target root for btrfs_start_transaction() is only for space reservation purpose. For btrfs-progs, it's only checking if we have enough space. So a wrong root here won't cause a problem, until the metadata space is almost exhausted.
In that case, btrfs_start_transaction() will return ENOSPC.

I pass the amount parameter as a char because I call check_offline_resize_args inside offline_resize and I wanted to keep all of the argument logic together.

You can check how other codes in btrfs-progs is doing.
For most cases, if we want to pass a size (or a diff of size), we would pass u64 or s64 instead, and do the parsing before calling the final function.

There are exceptions like the existing check_resize_args() where amount can be the string cancel.
But for most cases, the string parsing is done as soon as possible.
E.g. the size variable inside cmd_filesystem_mkswapfile().

In your particular case, we won't support anything other than a number, thus parsing it early will be a more common solution.

BTW, for your update, you can just force push the same branch. No need to create a new PR.

@loemraw
Copy link
Contributor Author

loemraw commented Jul 31, 2025

I pass the amount parameter as a char because I call check_offline_resize_args inside offline_resize and I wanted to keep all of the argument logic together.

You can check how other codes in btrfs-progs is doing. For most cases, if we want to pass a size (or a diff of size), we would pass u64 or s64 instead, and do the parsing before calling the final function.

There are exceptions like the existing check_resize_args() where amount can be the string cancel. But for most cases, the string parsing is done as soon as possible. E.g. the size variable inside cmd_filesystem_mkswapfile().

In your particular case, we won't support anything other than a number, thus parsing it early will be a more common solution.

In this case there is a difference between passing "+1G" and "1G" that would be lost if I just passed a u64. "+1G" increases the filesystem size by 1G and "1G" sets the filesystem size to 1G. I could pass a u64 along with a boolean to indicate whether the size is relative to the existing filesystem size, but it feels like this logic would be better encapsulated inside check_offline_resize_args.

@Zygo
Copy link

Zygo commented Jul 31, 2025

Will this allow for resizing a fs that would normally go ro due to enospc during mount?

An offline tool that increases a device's size--and does nothing else--is much more likely to succeed in that situation than mount + fi-resize, but it can still fail in one really specific case.

mount does a number of things that can result in enospc failure. Off the top of my head: orphan inode reclaim, the snapshot cleaner thread, tearing down an incomplete reloc tree, and resuming a balance (this is the only one that can be turned off by a mount option). Avoiding these other tasks makes success far more likely.

The chunk tree is stored in the system chunk, which is a dedicated contiguous storage area that is usually already large enough for two copies of the chunk tree. To run out of space there, you'd need hundreds of thousands of chunks, like a single profile 100 TiB filesystem that has no unallocated space at the moment that the chunk tree needs to be enlarged, with a system chunk that still has the original 32MB size from mkfs. On the other hand, while that's a rare case, it would be an especially painful one that you can't fix with this method.

Users can run into this case fairly often when they have filesystems that are close to this threshold size (100 TiB or multiples thereof). A smaller filesystem doesn't allocate enough chunks to require a new system chunk, while a bigger filesystem would have unallocated space when it needs a new system chunk. It also comes up if you're running any striped profile (raid0, raid10, raid5, or raid6) and you don't do something to reduce dev_extent fragmentation--I hit this with a 26T filesystem once, that had 250k chunks after several drive upgrades and naive balances.

If you're willing to accept overwriting a metadata page in place, you can resize a device by finding the page in the chunk tree where the device item is, changing the size field in the dev item, and updating the csum (in addition to the superblock changes, which are already overwrite-in-place, and repeated across all mirrors). Increasing or decreasing a device size (without relocating any chunks) doesn't require any new allocations.

@adam900710
Copy link
Collaborator

In this case there is a difference between passing "+1G" and "1G" that would be lost if I just passed a u64. "+1G" increases the filesystem size by 1G and "1G" sets the filesystem size to 1G.

Just use s64

@loemraw
Copy link
Contributor Author

loemraw commented Aug 6, 2025

Force pushed with a v2

  • use chunk root instead of fs root
  • fix offline resize error handling
  • fix variable declarations to not have newlines
  • fix parameter list to have more important arguments first

In this case there is a difference between passing "+1G" and "1G" that would be lost if I just passed a u64. "+1G" increases the filesystem size by 1G and "1G" sets the filesystem size to 1G.

Just use s64

I'm still passing the amount as a cstring because a s64 doesn't convey the difference between "+1G" and "1G".

@kdave kdave force-pushed the devel branch 2 times, most recently from f660864 to a452b1e Compare August 8, 2025 17:39
@loemraw
Copy link
Contributor Author

loemraw commented Aug 13, 2025

@adam900710 ping for v2 review

@loemraw loemraw force-pushed the feature-offline-resize branch 3 times, most recently from f4799f1 to 7a8c4e5 Compare August 21, 2025 23:15
@kdave kdave force-pushed the devel branch 2 times, most recently from 05e416f to 539b0cb Compare August 27, 2025 02:30
@loemraw loemraw force-pushed the feature-offline-resize branch 2 times, most recently from b53afc2 to 62510de Compare August 29, 2025 00:13
@loemraw
Copy link
Contributor Author

loemraw commented Aug 29, 2025

Just sent out v3!

I now have 3 commits the first refactors how check_resize_args parses the amount string into it's own function to parse_resize_args so it can be used in check_offline_resize_args. The second commit is the actual offline resize logic which does not allow for multi-device resizing and uses the new parse_resize_args. The third is a new test for offline resize.

Copy link
Collaborator

@adam900710 adam900710 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks good to me overall. I like the extracted parser.

Mostly minor problems like extra holes, some error handling timing, and missing man page update.

@loemraw loemraw force-pushed the feature-offline-resize branch from 62510de to 4837b98 Compare September 2, 2025 18:22
@loemraw
Copy link
Contributor Author

loemraw commented Sep 2, 2025

Just sent out v4 with updated documentation + fixes

@loemraw loemraw force-pushed the feature-offline-resize branch 2 times, most recently from 310731d to 2403e3c Compare September 3, 2025 17:43
@kdave kdave force-pushed the devel branch 2 times, most recently from 36aa4fc to 7ab197e Compare September 10, 2025 17:30
@kdave
Copy link
Owner

kdave commented Sep 11, 2025

Looks good to me, there are some minor coding things that I might fix once this is in devel as they're more stylistic than functional. Regarding the options, we'll probably settle on --offline for the cases where mounted and unmounted command would make sense. The alternative --unmounted is explicit but longer to type and I think offline is used in thisss context without much confusion.

@loemraw
Copy link
Contributor Author

loemraw commented Sep 11, 2025

Thank you!

Extract resize argument parsing logic from check_resize_args() into a
separate parse_resize_args() function. This refactoring improves code
organization and makes the parsing logic reusable for future features
like offline resize.

No functional changes to existing resize behavior.

Signed-off-by: Leo Martins <[email protected]>
Add support for resizing btrfs filesystems while unmounted using a new
--offline flag. This enables filesystem resizing without requiring mount
privileges or dealing with mounted filesystem constraints.

The offline resize functionality currently only supports increasing the
size of single-device filesystem. It works on both block devices and
regular files. For regular files it also truncates the file to the new
size.

This provides a safer alternative for users who need to resize
filesystems in environments where mounting may not be possible
or desirable, such as in containers or during system recovery.

Signed-off-by: Leo Martins <[email protected]>
Add comprehensive test coverage for the new --offline resize feature.

The test suite covers:
- Valid resize operations: incremental (+1G, 1:+1G) and absolute (2G)
- Invalid operations that should fail: shrinking, multi-device, cancel
- Error conditions: invalid device IDs, malformed size arguments
- Option compatibility: --offline with --enqueue should be rejected
- Mount state validation: offline resize should fail on mounted filesystems

Each test creates temporary filesystem images, performs resize operations,
and verifies the resulting filesystem size matches expectations. Error
cases use run_mustfail to ensure proper failure handling.

Signed-off-by: Leo Martins <[email protected]>
Add documentation for the new --offline feature in
btrfs-filesystem.rst. Changed the warning to reflect that it is now
possible to resize a btrfs image, but you need to specify the --offline
flag. Highlighted that --offline flag only supports increasing the size
of single device filesystems.

Signed-off-by: Leo Martins <[email protected]>
@loemraw loemraw force-pushed the feature-offline-resize branch from 2403e3c to 208cf2e Compare September 11, 2025 17:27
@adam900710 adam900710 merged commit c7a017a into kdave:devel Sep 11, 2025
1 check passed
@adam900710
Copy link
Collaborator

Merged. Thanks.

kdave pushed a commit that referenced this pull request Sep 12, 2025
Add support for resizing btrfs filesystems while unmounted using a new
option --offline, without requiring mount privileges or dealing with
mounted filesystem constraints.

Current limitations:

- increase size
- single-device filesystem

It works on both block devices and regular files. For regular files it
also truncates the file to the new size.

This provides an alternative for users who need to resize filesystems in
environments where mounting may not be possible or desirable, such as in
containers or during system recovery.

Pull-request: #1007
Signed-off-by: Leo Martins <[email protected]>
@kdave kdave added this to the v16.6.2 milestone Sep 12, 2025
@kdave
Copy link
Owner

kdave commented Sep 12, 2025

A few notes from the post-merge review. I've fixed some of that directly, less trivial updates will be probably in separate commits. I've also edited the changelogs so it's closer to the style I'm trying to keep for the whole progs.

  • unhandled errors (fstat)

  • error messages lacking information about what went wrong, like a size too large? print what is the size and what is the limit

  • mismatching types, "int ret = false"

  • use explicit "if (strcpy(...) == 0)" instead of "if (!strcpy(...))"

  • no "\n" in error() or warning()

  • comments are sentence ended with "."

  • new includes should be added to the right section in the list

  • changelog: try to write more specific statements instead of generic phrases "the code opptimizes the behaviour so that" unless it's really just the only thing the code does

  • changelog style tip: provide information that's useful also in the future

  • changelog: use consistent subject styles, with prefixes when it's for a command or for tests etc

  • tests: write shorter variables, possibly without "_",

  • tests: "rm -f -- ..."

kdave pushed a commit that referenced this pull request Sep 12, 2025
Add support for resizing btrfs filesystems while unmounted using a new
option --offline, without requiring mount privileges or dealing with
mounted filesystem constraints.

Current limitations:

- increase size
- single-device filesystem

It works on both block devices and regular files. For regular files it
also truncates the file to the new size.

This provides an alternative for users who need to resize filesystems in
environments where mounting may not be possible or desirable, such as in
containers or during system recovery.

Pull-request: #1007
Signed-off-by: Leo Martins <[email protected]>
kdave pushed a commit that referenced this pull request Sep 29, 2025
Add support for resizing btrfs filesystems while unmounted using a new
option --offline, without requiring mount privileges or dealing with
mounted filesystem constraints.

Current limitations:

- increase size
- single-device filesystem

It works on both block devices and regular files. For regular files it
also truncates the file to the new size.

This provides an alternative for users who need to resize filesystems in
environments where mounting may not be possible or desirable, such as in
containers or during system recovery.

Pull-request: #1007
Signed-off-by: Leo Martins <[email protected]>
@kdave kdave modified the milestones: v16.6.2, v6.17 Sep 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants