在 2026/8/1 05:37, Cole Munz 写道:
Btrfs pads compressed extents up to a sector boundary, so a file whose
size isn't sector-aligned still gets compressed as a frame whose
declared content size is the sector-rounded length, larger than the
extent's ram_bytes.
The padding at plain text level is completely common, since btrfs like
all other major fses are block device based, all IO including
compression is done at fs block level, so is the compressed data, which
still needs to be block aligned.
But your "larger than the extent's ram_bytes" is where I do not get.
The ram_bytes described the decompressed size of a compressed extent,
except inlined extents, the ram_bytes should always be fs block aligned.
I didn't see how things can go "larger than the extent's ram_bytes".
Can you provide more info about this, or better, provide the dump-tree
output for the involved files?
Thanks,
Qu
zstd_decompress_dctx()'s one-shot API requires the
destination buffer to cover the whole frame and fails with
ZSTD_error_dstSize_tooSmall otherwise, even though
btrfs_read_extent_reg() and btrfs_read_extent_inline() already
zero-fill any short tail. The file then reads back truncated or the
read fails outright, which is what breaks fdt apply on zstd-compressed
overlays.
Decompress into a bounce buffer sized to the frame when the declared
content size exceeds ram_bytes, and copy out only what the caller
asked for. Also trim the input with zstd_find_frame_compressed_size()
first, since the on-disk extent may carry trailing sector padding
after the frame.
The fix lives here rather than in the shared lib/zstd/zstd.c wrapper
on purpose: for FIT images, ximg and ubifs an undersized destination
really does mean a corrupt input and should keep failing hard. Armbian
carries the same caller-side fix for this exact failure on RK3399 and
ODROID-N2 boards (armbian/build#9651, #10208), where it showed up as
"zstd_decompress: failed to decompress: 70".
Fixes: 918adf8e0733 ("btrfs: Use U-Boot API for decompression")
Signed-off-by: Cole Munz <[email protected]>
---
fs/btrfs/compression.c | 76 +++++++++++++++++++++++++++++++++++++++---
1 file changed, 71 insertions(+), 5 deletions(-)
diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
index c69524d38ecc..017e61242a68 100644
--- a/fs/btrfs/compression.c
+++ b/fs/btrfs/compression.c
@@ -6,7 +6,7 @@
*/
#include "btrfs.h"
-#include <abuf.h>
+#include <limits.h>
#include <log.h>
#include <malloc.h>
#include <linux/lzo.h>
@@ -137,12 +137,78 @@ static u32 decompress_zlib(const u8 *_cbuf, u32 clen, u8
*dbuf, u32 dlen)
static u32 decompress_zstd(const u8 *cbuf, u32 clen, u8 *dbuf, u32 dlen)
{
- struct abuf in, out;
+ zstd_dctx *ctx;
+ zstd_frame_header fh;
+ size_t wsize, len, dst_capacity = dlen;
+ void *workspace;
+ u8 *dst = dbuf, *bounce = NULL;
+ u32 ret;
+
+ wsize = zstd_dctx_workspace_bound();
+ workspace = malloc(wsize);
+ if (!workspace)
+ return -1;
+
+ ctx = zstd_init_dctx(workspace, wsize);
+ if (!ctx) {
+ ret = -1;
+ goto out;
+ }
- abuf_init_set(&in, (u8 *)cbuf, clen);
- abuf_init_set(&out, dbuf, dlen);
+ /*
+ * Compressed extents are padded up to a sector boundary, so clen may
+ * include trailing junk after the actual zstd frame.
+ */
+ len = zstd_find_frame_compressed_size(cbuf, clen);
+ if (zstd_is_error(len)) {
+ ret = -1;
+ goto out;
+ }
- return zstd_decompress(&in, &out);
+ /*
+ * Btrfs compresses whole sectors, so a file whose size is not a
+ * multiple of the sector size still yields a frame whose content
+ * size is the sector-rounded length, which can be larger than dlen
+ * (ram_bytes from the extent item). zstd_decompress_dctx() requires
+ * its output buffer to cover the whole frame and would otherwise
+ * fail with ZSTD_error_dstSize_tooSmall, even though the extra bytes
+ * are just padding the caller is going to discard: both
+ * btrfs_read_extent_reg() and btrfs_read_extent_inline() already
+ * zero-fill any tail beyond what we return here. Decompress into a
+ * bounce buffer sized to the frame when that happens.
+ */
+ if (!zstd_get_frame_header(&fh, cbuf, len) &&
+ fh.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN &&
+ fh.frameContentSize > dlen) {
+ if (fh.frameContentSize > SIZE_MAX) {
+ ret = -1;
+ goto out;
+ }
+ bounce = malloc(fh.frameContentSize);
+ if (!bounce) {
+ ret = -1;
+ goto out;
+ }
+ dst = bounce;
+ dst_capacity = fh.frameContentSize;
+ }
+
+ len = zstd_decompress_dctx(ctx, dst, dst_capacity, cbuf, len);
+ if (zstd_is_error(len)) {
+ ret = -1;
+ goto out;
+ }
+
+ if (bounce) {
+ memcpy(dbuf, bounce, dlen);
+ ret = dlen;
+ } else {
+ ret = len;
+ }
+out:
+ free(bounce);
+ free(workspace);
+ return ret;
}
u32 btrfs_decompress(u8 type, const char *c, u32 clen, char *d, u32 dlen)