1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * BTRFS filesystem implementation for U-Boot
4  *
5  * 2017 Marek Behun, CZ.NIC, marek.behun@nic.cz
6  */
7 
8 #include "btrfs.h"
9 #include <log.h>
10 #include <malloc.h>
11 #include <linux/lzo.h>
12 #include <linux/zstd.h>
13 #include <linux/compat.h>
14 #include <u-boot/zlib.h>
15 #include <asm/unaligned.h>
16 
17 /* Header for each segment, LE32, recording the compressed size */
18 #define LZO_LEN		4
decompress_lzo(const u8 * cbuf,u32 clen,u8 * dbuf,u32 dlen)19 static u32 decompress_lzo(const u8 *cbuf, u32 clen, u8 *dbuf, u32 dlen)
20 {
21 	u32 tot_len, tot_in, in_len, res;
22 	size_t out_len;
23 	int ret;
24 
25 	if (clen < LZO_LEN)
26 		return -1;
27 
28 	tot_len = le32_to_cpu(get_unaligned((u32 *)cbuf));
29 	tot_in = 0;
30 	cbuf += LZO_LEN;
31 	clen -= LZO_LEN;
32 	tot_len -= LZO_LEN;
33 	tot_in += LZO_LEN;
34 
35 	if (tot_len == 0 && dlen)
36 		return -1;
37 	if (tot_len < LZO_LEN)
38 		return -1;
39 
40 	res = 0;
41 
42 	while (tot_len > LZO_LEN) {
43 		u32 rem_page;
44 
45 		in_len = le32_to_cpu(get_unaligned((u32 *)cbuf));
46 		cbuf += LZO_LEN;
47 		clen -= LZO_LEN;
48 
49 		if (in_len > clen || tot_len < LZO_LEN + in_len)
50 			return -1;
51 
52 		tot_len -= (LZO_LEN + in_len);
53 		tot_in += (LZO_LEN + in_len);
54 
55 		out_len = dlen;
56 		ret = lzo1x_decompress_safe(cbuf, in_len, dbuf, &out_len);
57 		if (ret != LZO_E_OK)
58 			return -1;
59 
60 		cbuf += in_len;
61 		clen -= in_len;
62 		dbuf += out_len;
63 		dlen -= out_len;
64 
65 		res += out_len;
66 
67 		/*
68 		 * If the 4 bytes header does not fit to the rest of the page we
69 		 * have to move to next one, or we read some garbage.
70 		 */
71 		rem_page = PAGE_SIZE - (tot_in % PAGE_SIZE);
72 		if (rem_page < LZO_LEN) {
73 			cbuf += rem_page;
74 			tot_in += rem_page;
75 			clen -= rem_page;
76 			tot_len -= rem_page;
77 		}
78 	}
79 
80 	return res;
81 }
82 
83 /* from zutil.h */
84 #define PRESET_DICT 0x20
85 
decompress_zlib(const u8 * _cbuf,u32 clen,u8 * dbuf,u32 dlen)86 static u32 decompress_zlib(const u8 *_cbuf, u32 clen, u8 *dbuf, u32 dlen)
87 {
88 	int wbits = MAX_WBITS, ret = -1;
89 	z_stream stream;
90 	u8 *cbuf;
91 	u32 res;
92 
93 	memset(&stream, 0, sizeof(stream));
94 
95 	cbuf = (u8 *) _cbuf;
96 
97 	stream.total_in = 0;
98 
99 	stream.next_out = dbuf;
100 	stream.avail_out = dlen;
101 	stream.total_out = 0;
102 
103 	/* skip adler32 check if deflate and no dictionary */
104 	if (clen > 2 && !(cbuf[1] & PRESET_DICT) &&
105 	    ((cbuf[0] & 0x0f) == Z_DEFLATED) &&
106 	    !(((cbuf[0] << 8) + cbuf[1]) % 31)) {
107 		wbits = -((cbuf[0] >> 4) + 8);
108 		cbuf += 2;
109 		clen -= 2;
110 	}
111 
112 	if (Z_OK != inflateInit2(&stream, wbits))
113 		return -1;
114 
115 	while (stream.total_in < clen) {
116 		stream.next_in = cbuf + stream.total_in;
117 		stream.avail_in = min((u32) (clen - stream.total_in),
118 					current_fs_info->sectorsize);
119 
120 		ret = inflate(&stream, Z_NO_FLUSH);
121 		if (ret != Z_OK)
122 			break;
123 	}
124 
125 	res = stream.total_out;
126 	inflateEnd(&stream);
127 
128 	if (ret != Z_STREAM_END)
129 		return -1;
130 
131 	return res;
132 }
133 
134 #define ZSTD_BTRFS_MAX_WINDOWLOG 17
135 #define ZSTD_BTRFS_MAX_INPUT (1 << ZSTD_BTRFS_MAX_WINDOWLOG)
136 
decompress_zstd(const u8 * cbuf,u32 clen,u8 * dbuf,u32 dlen)137 static u32 decompress_zstd(const u8 *cbuf, u32 clen, u8 *dbuf, u32 dlen)
138 {
139 	ZSTD_DStream *dstream;
140 	ZSTD_inBuffer in_buf;
141 	ZSTD_outBuffer out_buf;
142 	void *workspace;
143 	size_t wsize;
144 	u32 res = -1;
145 
146 	wsize = ZSTD_DStreamWorkspaceBound(ZSTD_BTRFS_MAX_INPUT);
147 	workspace = malloc(wsize);
148 	if (!workspace) {
149 		debug("%s: cannot allocate workspace of size %zu\n", __func__,
150 		      wsize);
151 		return -1;
152 	}
153 
154 	dstream = ZSTD_initDStream(ZSTD_BTRFS_MAX_INPUT, workspace, wsize);
155 	if (!dstream) {
156 		printf("%s: ZSTD_initDStream failed\n", __func__);
157 		goto err_free;
158 	}
159 
160 	in_buf.src = cbuf;
161 	in_buf.pos = 0;
162 	in_buf.size = clen;
163 
164 	out_buf.dst = dbuf;
165 	out_buf.pos = 0;
166 	out_buf.size = dlen;
167 
168 	while (1) {
169 		size_t ret;
170 
171 		ret = ZSTD_decompressStream(dstream, &out_buf, &in_buf);
172 		if (ZSTD_isError(ret)) {
173 			printf("%s: ZSTD_decompressStream error %d\n", __func__,
174 			       ZSTD_getErrorCode(ret));
175 			goto err_free;
176 		}
177 
178 		if (in_buf.pos >= clen || !ret)
179 			break;
180 	}
181 
182 	res = out_buf.pos;
183 
184 err_free:
185 	free(workspace);
186 	return res;
187 }
188 
btrfs_decompress(u8 type,const char * c,u32 clen,char * d,u32 dlen)189 u32 btrfs_decompress(u8 type, const char *c, u32 clen, char *d, u32 dlen)
190 {
191 	u32 res;
192 	const u8 *cbuf;
193 	u8 *dbuf;
194 
195 	cbuf = (const u8 *) c;
196 	dbuf = (u8 *) d;
197 
198 	switch (type) {
199 	case BTRFS_COMPRESS_NONE:
200 		res = dlen < clen ? dlen : clen;
201 		memcpy(dbuf, cbuf, res);
202 		return res;
203 	case BTRFS_COMPRESS_ZLIB:
204 		return decompress_zlib(cbuf, clen, dbuf, dlen);
205 	case BTRFS_COMPRESS_LZO:
206 		return decompress_lzo(cbuf, clen, dbuf, dlen);
207 	case BTRFS_COMPRESS_ZSTD:
208 		return decompress_zstd(cbuf, clen, dbuf, dlen);
209 	default:
210 		printf("%s: Unsupported compression in extent: %i\n", __func__,
211 		       type);
212 		return -1;
213 	}
214 }
215