Skip to content

Commit 2e91b75

Browse files
committed
u-boot: v2026.01: fix BTRFS zstd decompression failure (error 70)
U-Boot's generic zstd_decompress() wrapper fails when used by BTRFS due to two sector-alignment mismatches: 1. Compressed extents are stored padded to sector boundaries (4096), but zstd_decompress_dctx() rejects trailing data after the frame. 2. BTRFS compresses in sector-sized blocks, so the zstd frame content size may exceed ram_bytes. When the output buffer is sized to ram_bytes, zstd_decompress_dctx() returns dstSize_tooSmall (error 70). Symptoms on zstd-compressed BTRFS partition: zstd_decompress: failed to decompress: 70 BTRFS: An error occurred while reading file /boot/boot.scr Fix by calling zstd_decompress_dctx() directly with: - zstd_find_frame_compressed_size() to strip sector padding from input - ZSTD_getFrameContentSize() to allocate a larger output buffer when the frame decompresses beyond the caller's buffer size Tested on Helios64 (RK3399) booting from BTRFS+zstd SD card.
1 parent 4cd04e6 commit 2e91b75

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2+
From: Igor Velkov <iav-armbian@draconpern.com>
3+
Date: Thu, 10 Apr 2026 00:00:00 +0000
4+
Subject: [PATCH] fs: btrfs: fix zstd decompression for BTRFS extents
5+
6+
The generic zstd_decompress() wrapper in lib/zstd/zstd.c works for FIT
7+
images but fails for BTRFS filesystem extents due to two issues:
8+
9+
1. Sector-aligned compressed size: BTRFS stores compressed extents
10+
padded to sector boundaries (4096 bytes). The on-disk size
11+
(disk_num_bytes) may be larger than the actual zstd frame.
12+
zstd_decompress_dctx() rejects trailing data after the frame,
13+
causing decompression failures for regular (non-inline) extents.
14+
15+
2. Sector-aligned decompressed size: BTRFS compresses in sector-sized
16+
blocks, so the zstd frame content size may exceed the actual data
17+
size (ram_bytes) stored in extent metadata. For example, a 3906
18+
byte file is compressed as a 4096 byte block. When the output
19+
buffer is sized to ram_bytes, zstd_decompress_dctx() fails with
20+
ZSTD_error_dstSize_tooSmall (error 70) for inline extents.
21+
22+
Both issues manifest as boot failures on zstd-compressed BTRFS:
23+
24+
zstd_decompress: failed to decompress: 70
25+
BTRFS: An error occurred while reading file /boot/boot.scr
26+
27+
Fix by calling zstd_decompress_dctx() directly with two adjustments:
28+
- Use zstd_find_frame_compressed_size() to strip sector padding from
29+
the input, giving zstd the exact frame size.
30+
- Use ZSTD_getFrameContentSize() to detect when the frame decompresses
31+
to more than dlen bytes, and allocate a temporary buffer for the
32+
full decompressed frame when needed.
33+
34+
This is consistent with decompress_lzo() and decompress_zlib() which
35+
also call their library functions directly without generic wrappers.
36+
37+
Signed-off-by: Igor Velkov <iav-armbian@draconpern.com>
38+
---
39+
fs/btrfs/compression.c | 65 ++++++++++++++++++++++++++++++++++++++++++----
40+
1 file changed, 61 insertions(+), 4 deletions(-)
41+
42+
diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
43+
--- a/fs/btrfs/compression.c
44+
+++ b/fs/btrfs/compression.c
45+
@@ -137,12 +137,69 @@
46+
47+
static u32 decompress_zstd(const u8 *cbuf, u32 clen, u8 *dbuf, u32 dlen)
48+
{
49+
- struct abuf in, out;
50+
+ zstd_dctx *ctx;
51+
+ size_t wsize, ret, frame_csize;
52+
+ void *workspace;
53+
+ unsigned long long fcs;
54+
+ u8 *tmp = NULL;
55+
+ u8 *out_buf = dbuf;
56+
+ u32 out_len = dlen;
57+
58+
- abuf_init_set(&in, (u8 *)cbuf, clen);
59+
- abuf_init_set(&out, dbuf, dlen);
60+
+ /*
61+
+ * Find the actual compressed frame size. BTRFS stores compressed
62+
+ * extents padded to sector boundaries, but zstd_decompress_dctx()
63+
+ * requires the exact frame size without trailing padding.
64+
+ */
65+
+ frame_csize = zstd_find_frame_compressed_size(cbuf, clen);
66+
+ if (!zstd_is_error(frame_csize))
67+
+ clen = frame_csize;
68+
69+
- return zstd_decompress(&in, &out);
70+
+ /*
71+
+ * BTRFS compresses in sector-sized blocks, so the zstd frame may
72+
+ * decompress to a full sector (e.g. 4096) even when the actual
73+
+ * data (ram_bytes) is smaller. Allocate a larger buffer when needed
74+
+ * to avoid ZSTD_error_dstSize_tooSmall.
75+
+ */
76+
+ fcs = ZSTD_getFrameContentSize(cbuf, clen);
77+
+ if (fcs != ZSTD_CONTENTSIZE_ERROR &&
78+
+ fcs != ZSTD_CONTENTSIZE_UNKNOWN && fcs > dlen) {
79+
+ if (fcs > SIZE_MAX)
80+
+ return -1;
81+
+ tmp = malloc(fcs);
82+
+ if (!tmp)
83+
+ return -1;
84+
+ out_buf = tmp;
85+
+ out_len = fcs;
86+
+ }
87+
+
88+
+ wsize = zstd_dctx_workspace_bound();
89+
+ workspace = malloc(wsize);
90+
+ if (!workspace) {
91+
+ free(tmp);
92+
+ return -1;
93+
+ }
94+
+
95+
+ ctx = zstd_init_dctx(workspace, wsize);
96+
+ if (!ctx) {
97+
+ free(workspace);
98+
+ free(tmp);
99+
+ return -1;
100+
+ }
101+
+
102+
+ ret = zstd_decompress_dctx(ctx, out_buf, out_len, cbuf, clen);
103+
+ free(workspace);
104+
+
105+
+ if (zstd_is_error(ret)) {
106+
+ free(tmp);
107+
+ return -1;
108+
+ }
109+
+
110+
+ if (tmp) {
111+
+ memcpy(dbuf, tmp, dlen);
112+
+ free(tmp);
113+
+ }
114+
+
115+
+ return dlen;
116+
}
117+
118+
u32 btrfs_decompress(u8 type, const char *c, u32 clen, char *d, u32 dlen)

0 commit comments

Comments
 (0)