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 "random-util.h"
36 # define XZ_OK -EPROTONOSUPPORT
42 # define LZ4_OK -EPROTONOSUPPORT
45 typedef int (compress_blob_t
)(const void *src
, uint64_t src_size
,
46 void *dst
, size_t dst_alloc_size
, size_t *dst_size
);
47 typedef int (decompress_blob_t
)(const void *src
, uint64_t src_size
,
48 void **dst
, size_t *dst_alloc_size
,
49 size_t* dst_size
, size_t dst_max
);
50 typedef int (decompress_sw_t
)(const void *src
, uint64_t src_size
,
51 void **buffer
, size_t *buffer_size
,
52 const void *prefix
, size_t prefix_len
,
55 typedef int (compress_stream_t
)(int fdf
, int fdt
, uint64_t max_bytes
);
56 typedef int (decompress_stream_t
)(int fdf
, int fdt
, uint64_t max_size
);
58 #if HAVE_XZ || HAVE_LZ4
59 static void test_compress_decompress(int compression
,
60 compress_blob_t compress
,
61 decompress_blob_t decompress
,
66 size_t csize
, usize
= 0;
67 _cleanup_free_
char *decompressed
= NULL
;
70 log_info("/* testing %s %s blob compression/decompression */",
71 object_compressed_to_string(compression
), data
);
73 r
= compress(data
, data_len
, compressed
, sizeof(compressed
), &csize
);
75 log_info_errno(r
, "compression failed: %m");
79 r
= decompress(compressed
, csize
,
80 (void **) &decompressed
, &usize
, &csize
, 0);
82 assert_se(decompressed
);
83 assert_se(memcmp(decompressed
, data
, data_len
) == 0);
86 r
= decompress("garbage", 7,
87 (void **) &decompressed
, &usize
, &csize
, 0);
90 /* make sure to have the minimal lz4 compressed size */
91 r
= decompress("00000000\1g", 9,
92 (void **) &decompressed
, &usize
, &csize
, 0);
95 r
= decompress("\100000000g", 9,
96 (void **) &decompressed
, &usize
, &csize
, 0);
99 memzero(decompressed
, usize
);
102 static void test_decompress_startswith(int compression
,
103 compress_blob_t compress
,
104 decompress_sw_t decompress_sw
,
110 _cleanup_free_
char *compressed1
= NULL
, *compressed2
= NULL
, *decompressed
= NULL
;
111 size_t csize
, usize
= 0, len
;
114 log_info("/* testing decompress_startswith with %s on %.20s text */",
115 object_compressed_to_string(compression
), data
);
117 #define BUFSIZE_1 512
118 #define BUFSIZE_2 20000
120 compressed
= compressed1
= malloc(BUFSIZE_1
);
121 assert_se(compressed1
);
122 r
= compress(data
, data_len
, compressed
, BUFSIZE_1
, &csize
);
124 log_info_errno(r
, "compression failed: %m");
127 compressed
= compressed2
= malloc(BUFSIZE_2
);
128 assert_se(compressed2
);
129 r
= compress(data
, data_len
, compressed
, BUFSIZE_2
, &csize
);
136 r
= decompress_sw(compressed
, csize
, (void **) &decompressed
, &usize
, data
, len
, '\0');
138 r
= decompress_sw(compressed
, csize
, (void **) &decompressed
, &usize
, data
, len
, 'w');
140 r
= decompress_sw(compressed
, csize
, (void **) &decompressed
, &usize
, "barbarbar", 9, ' ');
142 r
= decompress_sw(compressed
, csize
, (void **) &decompressed
, &usize
, data
, len
- 1, data
[len
-1]);
144 r
= decompress_sw(compressed
, csize
, (void **) &decompressed
, &usize
, data
, len
- 1, 'w');
146 r
= decompress_sw(compressed
, csize
, (void **) &decompressed
, &usize
, data
, len
, '\0');
150 static void test_compress_stream(int compression
,
152 compress_stream_t compress
,
153 decompress_stream_t decompress
,
154 const char *srcfile
) {
156 _cleanup_close_
int src
= -1, dst
= -1, dst2
= -1;
157 char pattern
[] = "/tmp/systemd-test.compressed.XXXXXX",
158 pattern2
[] = "/tmp/systemd-test.compressed.XXXXXX";
160 _cleanup_free_
char *cmd
= NULL
, *cmd2
;
163 log_debug("/* testing %s compression */",
164 object_compressed_to_string(compression
));
166 log_debug("/* create source from %s */", srcfile
);
168 assert_se((src
= open(srcfile
, O_RDONLY
|O_CLOEXEC
)) >= 0);
170 log_debug("/* test compression */");
172 assert_se((dst
= mkostemp_safe(pattern
)) >= 0);
174 assert_se(compress(src
, dst
, -1) == 0);
177 assert_se(asprintf(&cmd
, "%s %s | diff %s -", cat
, pattern
, srcfile
) > 0);
178 assert_se(system(cmd
) == 0);
181 log_debug("/* test decompression */");
183 assert_se((dst2
= mkostemp_safe(pattern2
)) >= 0);
185 assert_se(stat(srcfile
, &st
) == 0);
187 assert_se(lseek(dst
, 0, SEEK_SET
) == 0);
188 r
= decompress(dst
, dst2
, st
.st_size
);
191 assert_se(asprintf(&cmd2
, "diff %s %s", srcfile
, pattern2
) > 0);
192 assert_se(system(cmd2
) == 0);
194 log_debug("/* test faulty decompression */");
196 assert_se(lseek(dst
, 1, SEEK_SET
) == 1);
197 r
= decompress(dst
, dst2
, st
.st_size
);
198 assert_se(IN_SET(r
, 0, -EBADMSG
));
200 assert_se(lseek(dst
, 0, SEEK_SET
) == 0);
201 assert_se(lseek(dst2
, 0, SEEK_SET
) == 0);
202 r
= decompress(dst
, dst2
, st
.st_size
- 1);
203 assert_se(r
== -EFBIG
);
205 assert_se(unlink(pattern
) == 0);
206 assert_se(unlink(pattern2
) == 0);
211 static void test_lz4_decompress_partial(void) {
213 size_t buf_size
= sizeof(buf
), compressed
;
215 _cleanup_free_
char *huge
= NULL
;
217 #define HUGE_SIZE (4096*1024)
218 huge
= malloc(HUGE_SIZE
);
219 memset(huge
, 'x', HUGE_SIZE
);
220 memcpy(huge
, "HUGE=", 5);
222 #if LZ4_VERSION_NUMBER >= 10700
223 r
= LZ4_compress_default(huge
, buf
, HUGE_SIZE
, buf_size
);
225 r
= LZ4_compress_limitedOutput(huge
, buf
, HUGE_SIZE
, buf_size
);
229 log_info("Compressed %i → %zu", HUGE_SIZE
, compressed
);
231 r
= LZ4_decompress_safe(buf
, huge
, r
, HUGE_SIZE
);
233 log_info("Decompressed → %i", r
);
235 r
= LZ4_decompress_safe_partial(buf
, huge
,
239 log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE
, r
);
241 /* We expect this to fail, because that's how current lz4 works. If this
242 * call succeeds, then lz4 has been fixed, and we need to change our code.
244 r
= LZ4_decompress_safe_partial(buf
, huge
,
248 log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE
-1, r
);
252 int main(int argc
, char *argv
[]) {
253 #if HAVE_XZ || HAVE_LZ4
255 "text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF"
256 "foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF";
258 /* The file to test compression on can be specified as the first argument */
259 const char *srcfile
= argc
> 1 ? argv
[1] : argv
[0];
261 char data
[512] = "random\0";
263 char huge
[4096*1024];
264 memset(huge
, 'x', sizeof(huge
));
265 memcpy(huge
, "HUGE=", 5);
268 log_set_max_level(LOG_DEBUG
);
270 random_bytes(data
+ 7, sizeof(data
) - 7);
273 test_compress_decompress(OBJECT_COMPRESSED_XZ
, compress_blob_xz
, decompress_blob_xz
,
274 text
, sizeof(text
), false);
275 test_compress_decompress(OBJECT_COMPRESSED_XZ
, compress_blob_xz
, decompress_blob_xz
,
276 data
, sizeof(data
), true);
278 test_decompress_startswith(OBJECT_COMPRESSED_XZ
,
279 compress_blob_xz
, decompress_startswith_xz
,
280 text
, sizeof(text
), false);
281 test_decompress_startswith(OBJECT_COMPRESSED_XZ
,
282 compress_blob_xz
, decompress_startswith_xz
,
283 data
, sizeof(data
), true);
284 test_decompress_startswith(OBJECT_COMPRESSED_XZ
,
285 compress_blob_xz
, decompress_startswith_xz
,
286 huge
, sizeof(huge
), true);
288 test_compress_stream(OBJECT_COMPRESSED_XZ
, "xzcat",
289 compress_stream_xz
, decompress_stream_xz
, srcfile
);
291 log_info("/* XZ test skipped */");
295 test_compress_decompress(OBJECT_COMPRESSED_LZ4
, compress_blob_lz4
, decompress_blob_lz4
,
296 text
, sizeof(text
), false);
297 test_compress_decompress(OBJECT_COMPRESSED_LZ4
, compress_blob_lz4
, decompress_blob_lz4
,
298 data
, sizeof(data
), true);
300 test_decompress_startswith(OBJECT_COMPRESSED_LZ4
,
301 compress_blob_lz4
, decompress_startswith_lz4
,
302 text
, sizeof(text
), false);
303 test_decompress_startswith(OBJECT_COMPRESSED_LZ4
,
304 compress_blob_lz4
, decompress_startswith_lz4
,
305 data
, sizeof(data
), true);
306 test_decompress_startswith(OBJECT_COMPRESSED_LZ4
,
307 compress_blob_lz4
, decompress_startswith_lz4
,
308 huge
, sizeof(huge
), true);
310 test_compress_stream(OBJECT_COMPRESSED_LZ4
, "lz4cat",
311 compress_stream_lz4
, decompress_stream_lz4
, srcfile
);
313 test_lz4_decompress_partial();
315 log_info("/* LZ4 test skipped */");
320 return EXIT_TEST_SKIP
;