|
| 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