1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd
5 Copyright 2014 Ronny Chevalier
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
25 #include "alloc-util.h"
30 #include "path-util.h"
31 #include "random-util.h"
37 # define XZ_OK -EPROTONOSUPPORT
43 # define LZ4_OK -EPROTONOSUPPORT
46 typedef int (compress_blob_t
)(const void *src
, uint64_t src_size
,
47 void *dst
, size_t dst_alloc_size
, size_t *dst_size
);
48 typedef int (decompress_blob_t
)(const void *src
, uint64_t src_size
,
49 void **dst
, size_t *dst_alloc_size
,
50 size_t* dst_size
, size_t dst_max
);
51 typedef int (decompress_sw_t
)(const void *src
, uint64_t src_size
,
52 void **buffer
, size_t *buffer_size
,
53 const void *prefix
, size_t prefix_len
,
56 typedef int (compress_stream_t
)(int fdf
, int fdt
, uint64_t max_bytes
);
57 typedef int (decompress_stream_t
)(int fdf
, int fdt
, uint64_t max_size
);
59 #if HAVE_XZ || HAVE_LZ4
60 static void test_compress_decompress(int compression
,
61 compress_blob_t compress
,
62 decompress_blob_t decompress
,
67 size_t csize
, usize
= 0;
68 _cleanup_free_
char *decompressed
= NULL
;
71 log_info("/* testing %s %s blob compression/decompression */",
72 object_compressed_to_string(compression
), data
);
74 r
= compress(data
, data_len
, compressed
, sizeof(compressed
), &csize
);
76 log_info_errno(r
, "compression failed: %m");
80 r
= decompress(compressed
, csize
,
81 (void **) &decompressed
, &usize
, &csize
, 0);
83 assert_se(decompressed
);
84 assert_se(memcmp(decompressed
, data
, data_len
) == 0);
87 r
= decompress("garbage", 7,
88 (void **) &decompressed
, &usize
, &csize
, 0);
91 /* make sure to have the minimal lz4 compressed size */
92 r
= decompress("00000000\1g", 9,
93 (void **) &decompressed
, &usize
, &csize
, 0);
96 r
= decompress("\100000000g", 9,
97 (void **) &decompressed
, &usize
, &csize
, 0);
100 memzero(decompressed
, usize
);
103 static void test_decompress_startswith(int compression
,
104 compress_blob_t compress
,
105 decompress_sw_t decompress_sw
,
111 _cleanup_free_
char *compressed1
= NULL
, *compressed2
= NULL
, *decompressed
= NULL
;
112 size_t csize
, usize
= 0, len
;
115 log_info("/* testing decompress_startswith with %s on %.20s text */",
116 object_compressed_to_string(compression
), data
);
118 #define BUFSIZE_1 512
119 #define BUFSIZE_2 20000
121 compressed
= compressed1
= malloc(BUFSIZE_1
);
122 assert_se(compressed1
);
123 r
= compress(data
, data_len
, compressed
, BUFSIZE_1
, &csize
);
125 log_info_errno(r
, "compression failed: %m");
128 compressed
= compressed2
= malloc(BUFSIZE_2
);
129 assert_se(compressed2
);
130 r
= compress(data
, data_len
, compressed
, BUFSIZE_2
, &csize
);
137 r
= decompress_sw(compressed
, csize
, (void **) &decompressed
, &usize
, data
, len
, '\0');
139 r
= decompress_sw(compressed
, csize
, (void **) &decompressed
, &usize
, data
, len
, 'w');
141 r
= decompress_sw(compressed
, csize
, (void **) &decompressed
, &usize
, "barbarbar", 9, ' ');
143 r
= decompress_sw(compressed
, csize
, (void **) &decompressed
, &usize
, data
, len
- 1, data
[len
-1]);
145 r
= decompress_sw(compressed
, csize
, (void **) &decompressed
, &usize
, data
, len
- 1, 'w');
147 r
= decompress_sw(compressed
, csize
, (void **) &decompressed
, &usize
, data
, len
, '\0');
151 static void test_compress_stream(int compression
,
153 compress_stream_t compress
,
154 decompress_stream_t decompress
,
155 const char *srcfile
) {
157 _cleanup_close_
int src
= -1, dst
= -1, dst2
= -1;
158 char pattern
[] = "/tmp/systemd-test.compressed.XXXXXX",
159 pattern2
[] = "/tmp/systemd-test.compressed.XXXXXX";
161 _cleanup_free_
char *cmd
= NULL
, *cmd2
= NULL
;
164 r
= find_binary(cat
, NULL
);
166 log_error_errno(r
, "Skipping %s, could not find %s binary: %m", __func__
, cat
);
170 log_debug("/* testing %s compression */",
171 object_compressed_to_string(compression
));
173 log_debug("/* create source from %s */", srcfile
);
175 assert_se((src
= open(srcfile
, O_RDONLY
|O_CLOEXEC
)) >= 0);
177 log_debug("/* test compression */");
179 assert_se((dst
= mkostemp_safe(pattern
)) >= 0);
181 assert_se(compress(src
, dst
, -1) == 0);
184 assert_se(asprintf(&cmd
, "%s %s | diff %s -", cat
, pattern
, srcfile
) > 0);
185 assert_se(system(cmd
) == 0);
188 log_debug("/* test decompression */");
190 assert_se((dst2
= mkostemp_safe(pattern2
)) >= 0);
192 assert_se(stat(srcfile
, &st
) == 0);
194 assert_se(lseek(dst
, 0, SEEK_SET
) == 0);
195 r
= decompress(dst
, dst2
, st
.st_size
);
198 assert_se(asprintf(&cmd2
, "diff %s %s", srcfile
, pattern2
) > 0);
199 assert_se(system(cmd2
) == 0);
201 log_debug("/* test faulty decompression */");
203 assert_se(lseek(dst
, 1, SEEK_SET
) == 1);
204 r
= decompress(dst
, dst2
, st
.st_size
);
205 assert_se(IN_SET(r
, 0, -EBADMSG
));
207 assert_se(lseek(dst
, 0, SEEK_SET
) == 0);
208 assert_se(lseek(dst2
, 0, SEEK_SET
) == 0);
209 r
= decompress(dst
, dst2
, st
.st_size
- 1);
210 assert_se(r
== -EFBIG
);
212 assert_se(unlink(pattern
) == 0);
213 assert_se(unlink(pattern2
) == 0);
218 static void test_lz4_decompress_partial(void) {
220 size_t buf_size
= sizeof(buf
), compressed
;
222 _cleanup_free_
char *huge
= NULL
;
224 #define HUGE_SIZE (4096*1024)
225 huge
= malloc(HUGE_SIZE
);
226 memset(huge
, 'x', HUGE_SIZE
);
227 memcpy(huge
, "HUGE=", 5);
229 #if LZ4_VERSION_NUMBER >= 10700
230 r
= LZ4_compress_default(huge
, buf
, HUGE_SIZE
, buf_size
);
232 r
= LZ4_compress_limitedOutput(huge
, buf
, HUGE_SIZE
, buf_size
);
236 log_info("Compressed %i → %zu", HUGE_SIZE
, compressed
);
238 r
= LZ4_decompress_safe(buf
, huge
, r
, HUGE_SIZE
);
240 log_info("Decompressed → %i", r
);
242 r
= LZ4_decompress_safe_partial(buf
, huge
,
246 log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE
, r
);
248 /* We expect this to fail, because that's how current lz4 works. If this
249 * call succeeds, then lz4 has been fixed, and we need to change our code.
251 r
= LZ4_decompress_safe_partial(buf
, huge
,
255 log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE
-1, r
);
259 int main(int argc
, char *argv
[]) {
260 #if HAVE_XZ || HAVE_LZ4
262 "text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF"
263 "foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF";
265 /* The file to test compression on can be specified as the first argument */
266 const char *srcfile
= argc
> 1 ? argv
[1] : argv
[0];
268 char data
[512] = "random\0";
270 char huge
[4096*1024];
271 memset(huge
, 'x', sizeof(huge
));
272 memcpy(huge
, "HUGE=", 5);
275 log_set_max_level(LOG_DEBUG
);
277 random_bytes(data
+ 7, sizeof(data
) - 7);
280 test_compress_decompress(OBJECT_COMPRESSED_XZ
, compress_blob_xz
, decompress_blob_xz
,
281 text
, sizeof(text
), false);
282 test_compress_decompress(OBJECT_COMPRESSED_XZ
, compress_blob_xz
, decompress_blob_xz
,
283 data
, sizeof(data
), true);
285 test_decompress_startswith(OBJECT_COMPRESSED_XZ
,
286 compress_blob_xz
, decompress_startswith_xz
,
287 text
, sizeof(text
), false);
288 test_decompress_startswith(OBJECT_COMPRESSED_XZ
,
289 compress_blob_xz
, decompress_startswith_xz
,
290 data
, sizeof(data
), true);
291 test_decompress_startswith(OBJECT_COMPRESSED_XZ
,
292 compress_blob_xz
, decompress_startswith_xz
,
293 huge
, sizeof(huge
), true);
295 test_compress_stream(OBJECT_COMPRESSED_XZ
, "xzcat",
296 compress_stream_xz
, decompress_stream_xz
, srcfile
);
298 log_info("/* XZ test skipped */");
302 test_compress_decompress(OBJECT_COMPRESSED_LZ4
, compress_blob_lz4
, decompress_blob_lz4
,
303 text
, sizeof(text
), false);
304 test_compress_decompress(OBJECT_COMPRESSED_LZ4
, compress_blob_lz4
, decompress_blob_lz4
,
305 data
, sizeof(data
), true);
307 test_decompress_startswith(OBJECT_COMPRESSED_LZ4
,
308 compress_blob_lz4
, decompress_startswith_lz4
,
309 text
, sizeof(text
), false);
310 test_decompress_startswith(OBJECT_COMPRESSED_LZ4
,
311 compress_blob_lz4
, decompress_startswith_lz4
,
312 data
, sizeof(data
), true);
313 test_decompress_startswith(OBJECT_COMPRESSED_LZ4
,
314 compress_blob_lz4
, decompress_startswith_lz4
,
315 huge
, sizeof(huge
), true);
317 test_compress_stream(OBJECT_COMPRESSED_LZ4
, "lz4cat",
318 compress_stream_lz4
, decompress_stream_lz4
, srcfile
);
320 test_lz4_decompress_partial();
322 log_info("/* LZ4 test skipped */");
327 return EXIT_TEST_SKIP
;