Skip to content

Commit 2c3c259

Browse files
committed
u-boot: v2025.10: add btrfs zstd decompression fix
Same patch as v2026.01 — fs/btrfs/compression.c is identical. Tested on Helios4 (Marvell A388). Placed in both board_helios4/ (board-specific BOOTPATCHDIR) and v2025.10/ root (shared, for boards like rockpi-e, orangepi4-lts).
1 parent 5617ff3 commit 2c3c259

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 | 66 +++++++++++++++++++++++++++++++++++++++++++----
40+
1 file changed, 62 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,70 @@
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, out_len;
52+
+ void *workspace;
53+
+ unsigned long long fcs;
54+
+ u8 *tmp = NULL;
55+
+ u8 *out_buf = dbuf;
56+
+
57+
+ out_len = dlen;
58+
59+
- abuf_init_set(&in, (u8 *)cbuf, clen);
60+
- abuf_init_set(&out, dbuf, dlen);
61+
+ /*
62+
+ * Find the actual compressed frame size. BTRFS stores compressed
63+
+ * extents padded to sector boundaries, but zstd_decompress_dctx()
64+
+ * requires the exact frame size without trailing padding.
65+
+ */
66+
+ frame_csize = zstd_find_frame_compressed_size(cbuf, clen);
67+
+ if (!zstd_is_error(frame_csize))
68+
+ clen = frame_csize;
69+
70+
- return zstd_decompress(&in, &out);
71+
+ /*
72+
+ * BTRFS compresses in sector-sized blocks, so the zstd frame may
73+
+ * decompress to a full sector (e.g. 4096) even when the actual
74+
+ * data (ram_bytes) is smaller. Allocate a larger buffer when needed
75+
+ * to avoid ZSTD_error_dstSize_tooSmall.
76+
+ */
77+
+ fcs = ZSTD_getFrameContentSize(cbuf, clen);
78+
+ if (fcs != ZSTD_CONTENTSIZE_ERROR &&
79+
+ fcs != ZSTD_CONTENTSIZE_UNKNOWN && fcs > dlen) {
80+
+ if (fcs > SIZE_MAX)
81+
+ return -1;
82+
+ tmp = malloc(fcs);
83+
+ if (!tmp)
84+
+ return -1;
85+
+ out_buf = tmp;
86+
+ out_len = fcs;
87+
+ }
88+
+
89+
+ wsize = zstd_dctx_workspace_bound();
90+
+ workspace = malloc(wsize);
91+
+ if (!workspace) {
92+
+ free(tmp);
93+
+ return -1;
94+
+ }
95+
+
96+
+ ctx = zstd_init_dctx(workspace, wsize);
97+
+ if (!ctx) {
98+
+ free(workspace);
99+
+ free(tmp);
100+
+ return -1;
101+
+ }
102+
+
103+
+ ret = zstd_decompress_dctx(ctx, out_buf, out_len, cbuf, clen);
104+
+ free(workspace);
105+
+
106+
+ if (zstd_is_error(ret)) {
107+
+ free(tmp);
108+
+ return -1;
109+
+ }
110+
+
111+
+ if (tmp) {
112+
+ memcpy(dbuf, tmp, dlen);
113+
+ free(tmp);
114+
+ }
115+
+
116+
+ return dlen;
117+
}
118+
119+
u32 btrfs_decompress(u8 type, const char *c, u32 clen, char *d, u32 dlen)
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 | 66 +++++++++++++++++++++++++++++++++++++++++++----
40+
1 file changed, 62 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,70 @@
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, out_len;
52+
+ void *workspace;
53+
+ unsigned long long fcs;
54+
+ u8 *tmp = NULL;
55+
+ u8 *out_buf = dbuf;
56+
+
57+
+ out_len = dlen;
58+
59+
- abuf_init_set(&in, (u8 *)cbuf, clen);
60+
- abuf_init_set(&out, dbuf, dlen);
61+
+ /*
62+
+ * Find the actual compressed frame size. BTRFS stores compressed
63+
+ * extents padded to sector boundaries, but zstd_decompress_dctx()
64+
+ * requires the exact frame size without trailing padding.
65+
+ */
66+
+ frame_csize = zstd_find_frame_compressed_size(cbuf, clen);
67+
+ if (!zstd_is_error(frame_csize))
68+
+ clen = frame_csize;
69+
70+
- return zstd_decompress(&in, &out);
71+
+ /*
72+
+ * BTRFS compresses in sector-sized blocks, so the zstd frame may
73+
+ * decompress to a full sector (e.g. 4096) even when the actual
74+
+ * data (ram_bytes) is smaller. Allocate a larger buffer when needed
75+
+ * to avoid ZSTD_error_dstSize_tooSmall.
76+
+ */
77+
+ fcs = ZSTD_getFrameContentSize(cbuf, clen);
78+
+ if (fcs != ZSTD_CONTENTSIZE_ERROR &&
79+
+ fcs != ZSTD_CONTENTSIZE_UNKNOWN && fcs > dlen) {
80+
+ if (fcs > SIZE_MAX)
81+
+ return -1;
82+
+ tmp = malloc(fcs);
83+
+ if (!tmp)
84+
+ return -1;
85+
+ out_buf = tmp;
86+
+ out_len = fcs;
87+
+ }
88+
+
89+
+ wsize = zstd_dctx_workspace_bound();
90+
+ workspace = malloc(wsize);
91+
+ if (!workspace) {
92+
+ free(tmp);
93+
+ return -1;
94+
+ }
95+
+
96+
+ ctx = zstd_init_dctx(workspace, wsize);
97+
+ if (!ctx) {
98+
+ free(workspace);
99+
+ free(tmp);
100+
+ return -1;
101+
+ }
102+
+
103+
+ ret = zstd_decompress_dctx(ctx, out_buf, out_len, cbuf, clen);
104+
+ free(workspace);
105+
+
106+
+ if (zstd_is_error(ret)) {
107+
+ free(tmp);
108+
+ return -1;
109+
+ }
110+
+
111+
+ if (tmp) {
112+
+ memcpy(dbuf, tmp, dlen);
113+
+ free(tmp);
114+
+ }
115+
+
116+
+ return dlen;
117+
}
118+
119+
u32 btrfs_decompress(u8 type, const char *c, u32 clen, char *d, u32 dlen)

0 commit comments

Comments
 (0)