• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 23825567702

31 Mar 2026 12:42PM UTC coverage: 72.404% (+0.006%) from 72.398%
23825567702

push

github

daandemeyer
terminal-util: fix boot hang from ANSI terminal size queries

Since v257, terminal_fix_size() is called during early boot via
console_setup() → reset_dev_console_fd() to query terminal dimensions
via ANSI escape sequences. This has caused intermittent boot hangs
where the system gets stuck with a blinking cursor and requires a
keypress to continue (see systemd/systemd#35499).

The function tries CSI 18 first, then falls back to DSR if that fails.
Previously, each method independently opened a non-blocking fd, disabled
echo/icanon, ran its query, restored termios, and closed its fd. This
created two problems:

1. Echo window between CSI 18 and DSR fallback: After CSI 18 times out
   and restores termios (re-enabling ECHO and ICANON), there is a brief
   window before DSR disables them again. If the terminal's CSI 18
   response arrives during this window, it is echoed back to the
   terminal — where the terminal interprets \e[8;rows;cols t as a
   "resize text area" command — and the response bytes land in the
   canonical line buffer as stale input that can confuse the DSR
   response parser.

2. Cursor left at bottom-right on DSR timeout: The DSR method worked by
   sending two DSR queries — one to save the cursor position, then
   moving the cursor to (32766,32766) and sending another to read the
   clamped position. If neither response was received (timeout), the
   cursor restore was skipped (conditional on saved_row > 0), leaving
   the cursor at the bottom-right corner of the terminal. The
   subsequent terminal_reset_ansi_seq() then moved it to the beginning
   of the last line via \e[1G, making boot output appear at the bottom
   of the screen — giving the appearance of a hang even when the system
   was still booting.

This commit fixes both issues:

- terminal_fix_size() now opens the non-blocking fd and configures
  termios once for both query methods, so echo stays disabled for the
  entire CSI 18 → DSR fallback sequence with no gap. tcflu... (continued)

22 of 57 new or added lines in 3 files covered. (38.6%)

834 existing lines in 52 files now uncovered.

318485 of 439872 relevant lines covered (72.4%)

1162379.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.8
/src/basic/compress.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <stdio.h>
4
#include <sys/mman.h>
5
#include <sys/stat.h>
6
#include <unistd.h>
7

8
#if HAVE_LZ4
9
#include <lz4.h>
10
#include <lz4hc.h>
11
#include <lz4frame.h>
12
#endif
13

14
#if HAVE_XZ
15
#include <lzma.h>
16
#endif
17

18
#if HAVE_ZSTD
19
#include <zstd.h>
20
#include <zstd_errors.h>
21
#endif
22

23
#include "sd-dlopen.h"
24

25
#include "alloc-util.h"
26
#include "bitfield.h"
27
#include "compress.h"
28
#include "dlfcn-util.h"
29
#include "fileio.h"
30
#include "io-util.h"
31
#include "log.h"
32
#include "string-table.h"
33
#include "string-util.h"
34
#include "unaligned.h"
35

36
#if HAVE_LZ4
37
static void *lz4_dl = NULL;
38

39
static DLSYM_PROTOTYPE(LZ4F_compressBegin) = NULL;
40
static DLSYM_PROTOTYPE(LZ4F_compressBound) = NULL;
41
static DLSYM_PROTOTYPE(LZ4F_compressEnd) = NULL;
42
static DLSYM_PROTOTYPE(LZ4F_compressUpdate) = NULL;
43
static DLSYM_PROTOTYPE(LZ4F_createCompressionContext) = NULL;
44
static DLSYM_PROTOTYPE(LZ4F_createDecompressionContext) = NULL;
45
static DLSYM_PROTOTYPE(LZ4F_decompress) = NULL;
46
static DLSYM_PROTOTYPE(LZ4F_freeCompressionContext) = NULL;
47
static DLSYM_PROTOTYPE(LZ4F_freeDecompressionContext) = NULL;
48
static DLSYM_PROTOTYPE(LZ4F_isError) = NULL;
49
static DLSYM_PROTOTYPE(LZ4_compress_HC) = NULL;
50
/* These are used in test-compress.c so we don't make them static. */
51
// NOLINTBEGIN(misc-use-internal-linkage)
52
DLSYM_PROTOTYPE(LZ4_compress_default) = NULL;
53
DLSYM_PROTOTYPE(LZ4_decompress_safe) = NULL;
54
DLSYM_PROTOTYPE(LZ4_decompress_safe_partial) = NULL;
55
DLSYM_PROTOTYPE(LZ4_versionNumber) = NULL;
56
// NOLINTEND(misc-use-internal-linkage)
57

58
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(LZ4F_compressionContext_t, sym_LZ4F_freeCompressionContext, LZ4F_freeCompressionContextp, NULL);
1✔
59
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(LZ4F_decompressionContext_t, sym_LZ4F_freeDecompressionContext, LZ4F_freeDecompressionContextp, NULL);
3✔
60
#endif
61

62
#if HAVE_ZSTD
63
static void *zstd_dl = NULL;
64

65
static DLSYM_PROTOTYPE(ZSTD_CCtx_setParameter) = NULL;
66
static DLSYM_PROTOTYPE(ZSTD_compress) = NULL;
67
static DLSYM_PROTOTYPE(ZSTD_compressStream2) = NULL;
68
static DLSYM_PROTOTYPE(ZSTD_createCCtx) = NULL;
69
static DLSYM_PROTOTYPE(ZSTD_createDCtx) = NULL;
70
static DLSYM_PROTOTYPE(ZSTD_CStreamInSize) = NULL;
71
static DLSYM_PROTOTYPE(ZSTD_CStreamOutSize) = NULL;
72
static DLSYM_PROTOTYPE(ZSTD_decompressStream) = NULL;
73
static DLSYM_PROTOTYPE(ZSTD_DStreamInSize) = NULL;
74
static DLSYM_PROTOTYPE(ZSTD_DStreamOutSize) = NULL;
75
static DLSYM_PROTOTYPE(ZSTD_freeCCtx) = NULL;
76
static DLSYM_PROTOTYPE(ZSTD_freeDCtx) = NULL;
77
static DLSYM_PROTOTYPE(ZSTD_getErrorCode) = NULL;
78
static DLSYM_PROTOTYPE(ZSTD_getErrorName) = NULL;
79
static DLSYM_PROTOTYPE(ZSTD_getFrameContentSize) = NULL;
80
static DLSYM_PROTOTYPE(ZSTD_isError) = NULL;
81

82
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ZSTD_CCtx*, sym_ZSTD_freeCCtx, ZSTD_freeCCtxp, NULL);
21✔
83
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ZSTD_DCtx*, sym_ZSTD_freeDCtx, ZSTD_freeDCtxp, NULL);
5,072,868✔
84

85
static int zstd_ret_to_errno(size_t ret) {
7✔
86
        switch (sym_ZSTD_getErrorCode(ret)) {
7✔
87
        case ZSTD_error_dstSize_tooSmall:
88
                return -ENOBUFS;
89
        case ZSTD_error_memory_allocation:
×
90
                return -ENOMEM;
×
91
        default:
1✔
92
                return -EBADMSG;
1✔
93
        }
94
}
95
#endif
96

97
#if HAVE_XZ
98
static void *lzma_dl = NULL;
99

100
static DLSYM_PROTOTYPE(lzma_code) = NULL;
101
static DLSYM_PROTOTYPE(lzma_easy_encoder) = NULL;
102
static DLSYM_PROTOTYPE(lzma_end) = NULL;
103
static DLSYM_PROTOTYPE(lzma_stream_buffer_encode) = NULL;
104
static DLSYM_PROTOTYPE(lzma_stream_decoder) = NULL;
105
static DLSYM_PROTOTYPE(lzma_lzma_preset) = NULL;
106

107
/* We can't just do _cleanup_(sym_lzma_end) because a compiler bug makes
108
 * this fail with:
109
 * ../src/basic/compress.c: In function ‘decompress_blob_xz’:
110
 * ../src/basic/compress.c:304:9: error: cleanup argument not a function
111
 *   304 |         _cleanup_(sym_lzma_end) lzma_stream s = LZMA_STREAM_INIT;
112
 *       |         ^~~~~~~~~
113
 */
114
static inline void lzma_end_wrapper(lzma_stream *ls) {
2,935✔
115
        sym_lzma_end(ls);
2,935✔
116
}
2,935✔
117
#endif
118

119
#define ALIGN_8(l) ALIGN_TO(l, sizeof(size_t))
120

121
static const char* const compression_table[_COMPRESSION_MAX] = {
122
        [COMPRESSION_NONE] = "NONE",
123
        [COMPRESSION_XZ]   = "XZ",
124
        [COMPRESSION_LZ4]  = "LZ4",
125
        [COMPRESSION_ZSTD] = "ZSTD",
126
};
127

128
static const char* const compression_lowercase_table[_COMPRESSION_MAX] = {
129
        [COMPRESSION_NONE] = "none",
130
        [COMPRESSION_XZ]   = "xz",
131
        [COMPRESSION_LZ4]  = "lz4",
132
        [COMPRESSION_ZSTD] = "zstd",
133
};
134

135
DEFINE_STRING_TABLE_LOOKUP(compression, Compression);
1,013✔
136
DEFINE_STRING_TABLE_LOOKUP(compression_lowercase, Compression);
62✔
137

138
bool compression_supported(Compression c) {
2,669✔
139
        static const unsigned supported =
2,669✔
140
                (1U << COMPRESSION_NONE) |
141
                (1U << COMPRESSION_XZ) * HAVE_XZ |
142
                (1U << COMPRESSION_LZ4) * HAVE_LZ4 |
143
                (1U << COMPRESSION_ZSTD) * HAVE_ZSTD;
144

145
        assert(c >= 0);
2,669✔
146
        assert(c < _COMPRESSION_MAX);
2,669✔
147

148
        return BIT_SET(supported, c);
2,669✔
149
}
150

151
int dlopen_lzma(void) {
2,954✔
152
#if HAVE_XZ
153
        SD_ELF_NOTE_DLOPEN(
2,954✔
154
                        "lzma",
155
                        "Support lzma compression in journal and coredump files",
156
                        COMPRESSION_PRIORITY_XZ,
157
                        "liblzma.so.5");
158

159
        return dlopen_many_sym_or_warn(
2,954✔
160
                        &lzma_dl,
161
                        "liblzma.so.5", LOG_DEBUG,
162
                        DLSYM_ARG(lzma_code),
163
                        DLSYM_ARG(lzma_easy_encoder),
164
                        DLSYM_ARG(lzma_end),
165
                        DLSYM_ARG(lzma_stream_buffer_encode),
166
                        DLSYM_ARG(lzma_lzma_preset),
167
                        DLSYM_ARG(lzma_stream_decoder));
168
#else
169
        return -EOPNOTSUPP;
170
#endif
171
}
172

173
int compress_blob_xz(const void *src, uint64_t src_size,
18✔
174
                     void *dst, size_t dst_alloc_size, size_t *dst_size, int level) {
175

176
        assert(src);
18✔
177
        assert(src_size > 0);
18✔
178
        assert(dst);
18✔
179
        assert(dst_alloc_size > 0);
18✔
180
        assert(dst_size);
18✔
181

182
#if HAVE_XZ
183
        lzma_options_lzma opt = {
18✔
184
                1u << 20u, NULL, 0, LZMA_LC_DEFAULT, LZMA_LP_DEFAULT,
185
                LZMA_PB_DEFAULT, LZMA_MODE_FAST, 128, LZMA_MF_HC3, 4
186
        };
187
        lzma_filter filters[] = {
18✔
188
                { LZMA_FILTER_LZMA2, &opt },
189
                { LZMA_VLI_UNKNOWN, NULL }
190
        };
191
        lzma_ret ret;
18✔
192
        size_t out_pos = 0;
18✔
193
        int r;
18✔
194

195
        r = dlopen_lzma();
18✔
196
        if (r < 0)
18✔
197
                return r;
18✔
198

199
        if (level >= 0) {
18✔
UNCOV
200
                r = sym_lzma_lzma_preset(&opt, (uint32_t) level);
×
201
                if (r < 0)
202
                        return r;
203
        }
204

205
        /* Returns < 0 if we couldn't compress the data or the
206
         * compressed result is longer than the original */
207

208
        if (src_size < 80)
18✔
209
                return -ENOBUFS;
210

211
        ret = sym_lzma_stream_buffer_encode(filters, LZMA_CHECK_NONE, NULL,
18✔
212
                                        src, src_size, dst, &out_pos, dst_alloc_size);
213
        if (ret != LZMA_OK)
18✔
214
                return -ENOBUFS;
215

216
        *dst_size = out_pos;
15✔
217
        return 0;
15✔
218
#else
219
        return -EPROTONOSUPPORT;
220
#endif
221
}
222

223
int dlopen_lz4(void) {
3,254✔
224
#if HAVE_LZ4
225
        SD_ELF_NOTE_DLOPEN(
3,254✔
226
                        "lz4",
227
                        "Support lz4 compression in journal and coredump files",
228
                        COMPRESSION_PRIORITY_LZ4,
229
                        "liblz4.so.1");
230

231
        return dlopen_many_sym_or_warn(
3,254✔
232
                        &lz4_dl,
233
                        "liblz4.so.1", LOG_DEBUG,
234
                        DLSYM_ARG(LZ4F_compressBegin),
235
                        DLSYM_ARG(LZ4F_compressBound),
236
                        DLSYM_ARG(LZ4F_compressEnd),
237
                        DLSYM_ARG(LZ4F_compressUpdate),
238
                        DLSYM_ARG(LZ4F_createCompressionContext),
239
                        DLSYM_ARG(LZ4F_createDecompressionContext),
240
                        DLSYM_ARG(LZ4F_decompress),
241
                        DLSYM_ARG(LZ4F_freeCompressionContext),
242
                        DLSYM_ARG(LZ4F_freeDecompressionContext),
243
                        DLSYM_ARG(LZ4F_isError),
244
                        DLSYM_ARG(LZ4_compress_default),
245
                        DLSYM_ARG(LZ4_compress_HC),
246
                        DLSYM_ARG(LZ4_decompress_safe),
247
                        DLSYM_ARG(LZ4_decompress_safe_partial),
248
                        DLSYM_ARG(LZ4_versionNumber));
249
#else
250
        return -EOPNOTSUPP;
251
#endif
252
}
253

254
int compress_blob_lz4(const void *src, uint64_t src_size,
172✔
255
                      void *dst, size_t dst_alloc_size, size_t *dst_size, int level) {
256

257
        assert(src);
172✔
258
        assert(src_size > 0);
172✔
259
        assert(dst);
172✔
260
        assert(dst_alloc_size > 0);
172✔
261
        assert(dst_size);
172✔
262

263
#if HAVE_LZ4
264
        int r;
172✔
265

266
        r = dlopen_lz4();
172✔
267
        if (r < 0)
172✔
268
                return r;
269
        /* Returns < 0 if we couldn't compress the data or the
270
         * compressed result is longer than the original */
271

272
        if (src_size < 9)
172✔
273
                return -ENOBUFS;
274

275
        if (level <= 0)
172✔
276
                r = sym_LZ4_compress_default(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8);
172✔
277
        else
UNCOV
278
                r = sym_LZ4_compress_HC(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8, level);
×
279
        if (r <= 0)
172✔
280
                return -ENOBUFS;
281

282
        unaligned_write_le64(dst, src_size);
159✔
283
        *dst_size = r + 8;
159✔
284

285
        return 0;
159✔
286
#else
287
        return -EPROTONOSUPPORT;
288
#endif
289
}
290

291
int dlopen_zstd(void) {
5,073,659✔
292
#if HAVE_ZSTD
293
        SD_ELF_NOTE_DLOPEN(
5,073,659✔
294
                        "zstd",
295
                        "Support zstd compression in journal and coredump files",
296
                        COMPRESSION_PRIORITY_ZSTD,
297
                        "libzstd.so.1");
298

299
        return dlopen_many_sym_or_warn(
5,073,659✔
300
                        &zstd_dl,
301
                        "libzstd.so.1", LOG_DEBUG,
302
                        DLSYM_ARG(ZSTD_getErrorCode),
303
                        DLSYM_ARG(ZSTD_compress),
304
                        DLSYM_ARG(ZSTD_getFrameContentSize),
305
                        DLSYM_ARG(ZSTD_decompressStream),
306
                        DLSYM_ARG(ZSTD_getErrorName),
307
                        DLSYM_ARG(ZSTD_DStreamOutSize),
308
                        DLSYM_ARG(ZSTD_CStreamInSize),
309
                        DLSYM_ARG(ZSTD_CStreamOutSize),
310
                        DLSYM_ARG(ZSTD_CCtx_setParameter),
311
                        DLSYM_ARG(ZSTD_compressStream2),
312
                        DLSYM_ARG(ZSTD_DStreamInSize),
313
                        DLSYM_ARG(ZSTD_freeCCtx),
314
                        DLSYM_ARG(ZSTD_freeDCtx),
315
                        DLSYM_ARG(ZSTD_isError),
316
                        DLSYM_ARG(ZSTD_createDCtx),
317
                        DLSYM_ARG(ZSTD_createCCtx));
318
#else
319
        return -EOPNOTSUPP;
320
#endif
321
}
322

323
int compress_blob_zstd(
763✔
324
                const void *src, uint64_t src_size,
325
                void *dst, size_t dst_alloc_size, size_t *dst_size, int level) {
326

327
        assert(src);
763✔
328
        assert(src_size > 0);
763✔
329
        assert(dst);
763✔
330
        assert(dst_alloc_size > 0);
763✔
331
        assert(dst_size);
763✔
332

333
#if HAVE_ZSTD
334
        size_t k;
763✔
335
        int r;
763✔
336

337
        r = dlopen_zstd();
763✔
338
        if (r < 0)
763✔
339
                return r;
340

341
        k = sym_ZSTD_compress(dst, dst_alloc_size, src, src_size, level < 0 ? 0 : level);
763✔
342
        if (sym_ZSTD_isError(k))
763✔
343
                return zstd_ret_to_errno(k);
6✔
344

345
        *dst_size = k;
757✔
346
        return 0;
757✔
347
#else
348
        return -EPROTONOSUPPORT;
349
#endif
350
}
351

352
int decompress_blob_xz(
2,661✔
353
                const void *src,
354
                uint64_t src_size,
355
                void **dst,
356
                size_t* dst_size,
357
                size_t dst_max) {
358

359
        assert(src);
2,661✔
360
        assert(src_size > 0);
2,661✔
361
        assert(dst);
2,661✔
362
        assert(dst_size);
2,661✔
363

364
#if HAVE_XZ
365
        int r;
2,661✔
366

367
        r = dlopen_lzma();
2,661✔
368
        if (r < 0)
2,661✔
369
                return r;
2,661✔
370

371
        _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT;
2,661✔
372
        lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0);
2,661✔
373
        if (ret != LZMA_OK)
2,661✔
374
                return -ENOMEM;
375

376
        size_t space = MIN(src_size * 2, dst_max ?: SIZE_MAX);
2,661✔
377
        if (!greedy_realloc(dst, space, 1))
2,661✔
378
                return -ENOMEM;
379

380
        s.next_in = src;
2,661✔
381
        s.avail_in = src_size;
2,661✔
382

383
        s.next_out = *dst;
2,661✔
384
        s.avail_out = space;
2,661✔
385

386
        for (;;) {
2,847✔
387
                size_t used;
2,754✔
388

389
                ret = sym_lzma_code(&s, LZMA_FINISH);
2,754✔
390
                if (ret == LZMA_STREAM_END)
2,754✔
391
                        break;
392
                if (ret != LZMA_OK)
99✔
393
                        return -ENOMEM;
394

395
                if (dst_max > 0 && (space - s.avail_out) >= dst_max)
93✔
396
                        break;
397
                if (dst_max > 0 && space == dst_max)
93✔
398
                        return -ENOBUFS;
399

400
                used = space - s.avail_out;
93✔
401
                /* Silence static analyzers, space is bounded by allocation size */
402
                assert(space <= SIZE_MAX / 2);
93✔
403
                space = MIN(2 * space, dst_max ?: SIZE_MAX);
93✔
404
                if (!greedy_realloc(dst, space, 1))
93✔
405
                        return -ENOMEM;
406

407
                s.avail_out = space - used;
93✔
408
                s.next_out = *(uint8_t**)dst + used;
93✔
409
        }
410

411
        *dst_size = space - s.avail_out;
2,655✔
412
        return 0;
2,655✔
413
#else
414
        return -EPROTONOSUPPORT;
415
#endif
416
}
417

418
int decompress_blob_lz4(
2,806✔
419
                const void *src,
420
                uint64_t src_size,
421
                void **dst,
422
                size_t* dst_size,
423
                size_t dst_max) {
424

425
        assert(src);
2,806✔
426
        assert(src_size > 0);
2,806✔
427
        assert(dst);
2,806✔
428
        assert(dst_size);
2,806✔
429

430
#if HAVE_LZ4
431
        char* out;
2,806✔
432
        int r, size; /* LZ4 uses int for size */
2,806✔
433

434
        r = dlopen_lz4();
2,806✔
435
        if (r < 0)
2,806✔
436
                return r;
437

438
        if (src_size <= 8)
2,806✔
439
                return -EBADMSG;
440

441
        size = unaligned_read_le64(src);
2,804✔
442
        if (size < 0 || (unsigned) size != unaligned_read_le64(src))
2,804✔
443
                return -EFBIG;
444
        out = greedy_realloc(dst, size, 1);
2,800✔
445
        if (!out)
2,800✔
446
                return -ENOMEM;
447

448
        r = sym_LZ4_decompress_safe((char*)src + 8, out, src_size - 8, size);
2,800✔
449
        if (r < 0 || r != size)
2,800✔
450
                return -EBADMSG;
451

452
        *dst_size = size;
2,800✔
453
        return 0;
2,800✔
454
#else
455
        return -EPROTONOSUPPORT;
456
#endif
457
}
458

459
int decompress_blob_zstd(
250,610✔
460
                const void *src,
461
                uint64_t src_size,
462
                void **dst,
463
                size_t *dst_size,
464
                size_t dst_max) {
465

466
        assert(src);
250,610✔
467
        assert(src_size > 0);
250,610✔
468
        assert(dst);
250,610✔
469
        assert(dst_size);
250,610✔
470

471
#if HAVE_ZSTD
472
        uint64_t size;
250,610✔
473
        int r;
250,610✔
474

475
        r = dlopen_zstd();
250,610✔
476
        if (r < 0)
250,610✔
477
                return r;
250,610✔
478

479
        size = sym_ZSTD_getFrameContentSize(src, src_size);
250,610✔
480
        if (IN_SET(size, ZSTD_CONTENTSIZE_ERROR, ZSTD_CONTENTSIZE_UNKNOWN))
250,610✔
481
                return -EBADMSG;
482

483
        if (dst_max > 0 && size > dst_max)
250,604✔
484
                size = dst_max;
×
485
        if (size > SIZE_MAX)
250,604✔
486
                return -E2BIG;
487

488
        if (!(greedy_realloc(dst, MAX(sym_ZSTD_DStreamOutSize(), size), 1)))
250,604✔
489
                return -ENOMEM;
490

491
        _cleanup_(ZSTD_freeDCtxp) ZSTD_DCtx *dctx = sym_ZSTD_createDCtx();
501,208✔
492
        if (!dctx)
250,604✔
493
                return -ENOMEM;
494

495
        ZSTD_inBuffer input = {
250,604✔
496
                .src = src,
497
                .size = src_size,
498
        };
499
        ZSTD_outBuffer output = {
501,208✔
500
                .dst = *dst,
250,604✔
501
                .size = MALLOC_SIZEOF_SAFE(*dst),
250,604✔
502
        };
503

504
        size_t k = sym_ZSTD_decompressStream(dctx, &output, &input);
250,604✔
505
        if (sym_ZSTD_isError(k))
250,604✔
506
                return log_debug_errno(zstd_ret_to_errno(k), "ZSTD decoder failed: %s", sym_ZSTD_getErrorName(k));
×
507
        if (output.pos < size)
250,604✔
508
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "ZSTD decoded less data than indicated, probably corrupted stream.");
×
509

510
        *dst_size = size;
250,604✔
511
        return 0;
250,604✔
512
#else
513
        return -EPROTONOSUPPORT;
514
#endif
515
}
516

517
int decompress_blob(
255,797✔
518
                Compression compression,
519
                const void *src,
520
                uint64_t src_size,
521
                void **dst,
522
                size_t* dst_size,
523
                size_t dst_max) {
524

525
        switch (compression) {
255,797✔
526
        case COMPRESSION_XZ:
2,650✔
527
                return decompress_blob_xz(
2,650✔
528
                                src, src_size,
529
                                dst, dst_size, dst_max);
530
        case COMPRESSION_LZ4:
2,651✔
531
                return decompress_blob_lz4(
2,651✔
532
                                src, src_size,
533
                                dst, dst_size, dst_max);
534
        case COMPRESSION_ZSTD:
250,496✔
535
                return decompress_blob_zstd(
250,496✔
536
                                src, src_size,
537
                                dst, dst_size, dst_max);
538
        default:
539
                return -EPROTONOSUPPORT;
540
        }
541
}
542

543
int decompress_startswith_xz(
270✔
544
                const void *src,
545
                uint64_t src_size,
546
                void **buffer,
547
                const void *prefix,
548
                size_t prefix_len,
549
                uint8_t extra) {
550

551
        /* Checks whether the decompressed blob starts with the mentioned prefix. The byte extra needs to
552
         * follow the prefix */
553

554
        assert(src);
270✔
555
        assert(src_size > 0);
270✔
556
        assert(buffer);
270✔
557
        assert(prefix);
270✔
558

559
#if HAVE_XZ
560
        int r;
270✔
561

562
        r = dlopen_lzma();
270✔
563
        if (r < 0)
270✔
564
                return r;
270✔
565

566
        _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT;
270✔
567
        lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0);
270✔
568
        if (ret != LZMA_OK)
270✔
569
                return -EBADMSG;
570

571
        if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1)))
270✔
572
                return -ENOMEM;
573

574
        size_t allocated = MALLOC_SIZEOF_SAFE(*buffer);
270✔
575

576
        s.next_in = src;
270✔
577
        s.avail_in = src_size;
270✔
578

579
        s.next_out = *buffer;
270✔
580
        s.avail_out = allocated;
270✔
581

582
        for (;;) {
270✔
583
                ret = sym_lzma_code(&s, LZMA_FINISH);
270✔
584

585
                if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END))
270✔
586
                        return -EBADMSG;
587

588
                if (allocated - s.avail_out >= prefix_len + 1)
270✔
589
                        return memcmp(*buffer, prefix, prefix_len) == 0 &&
540✔
590
                                ((const uint8_t*) *buffer)[prefix_len] == extra;
267✔
591

592
                if (ret == LZMA_STREAM_END)
×
593
                        return 0;
594

595
                s.avail_out += allocated;
×
596

597
                if (!(greedy_realloc(buffer, allocated * 2, 1)))
×
598
                        return -ENOMEM;
599

600
                allocated = MALLOC_SIZEOF_SAFE(*buffer);
×
601
                s.next_out = *(uint8_t**)buffer + allocated - s.avail_out;
×
602
        }
603

604
#else
605
        return -EPROTONOSUPPORT;
606
#endif
607
}
608

609
int decompress_startswith_lz4(
270✔
610
                const void *src,
611
                uint64_t src_size,
612
                void **buffer,
613
                const void *prefix,
614
                size_t prefix_len,
615
                uint8_t extra) {
616

617
        /* Checks whether the decompressed blob starts with the mentioned prefix. The byte extra needs to
618
         * follow the prefix */
619

620
        assert(src);
270✔
621
        assert(src_size > 0);
270✔
622
        assert(buffer);
270✔
623
        assert(prefix);
270✔
624

625
#if HAVE_LZ4
626
        size_t allocated;
270✔
627
        int r;
270✔
628

629
        r = dlopen_lz4();
270✔
630
        if (r < 0)
270✔
631
                return r;
632

633
        if (src_size <= 8)
270✔
634
                return -EBADMSG;
635

636
        if (!(greedy_realloc(buffer, ALIGN_8(prefix_len + 1), 1)))
270✔
637
                return -ENOMEM;
638
        allocated = MALLOC_SIZEOF_SAFE(*buffer);
270✔
639

640
        r = sym_LZ4_decompress_safe_partial(
540✔
641
                        (char*)src + 8,
642
                        *buffer,
643
                        src_size - 8,
270✔
644
                        prefix_len + 1,
270✔
645
                        allocated);
646

647
        /* One lz4 < 1.8.3, we might get "failure" (r < 0), or "success" where just a part of the buffer is
648
         * decompressed. But if we get a smaller amount of bytes than requested, we don't know whether there
649
         * isn't enough data to fill the requested size or whether we just got a partial answer.
650
         */
651
        if (r < 0 || (size_t) r < prefix_len + 1) {
270✔
652
                size_t size;
×
653

654
                if (sym_LZ4_versionNumber() >= 10803)
×
655
                        /* We trust that the newer lz4 decompresses the number of bytes we
656
                         * requested if available in the compressed string. */
657
                        return 0;
×
658

659
                if (r > 0)
×
660
                        /* Compare what we have first, in case of mismatch we can
661
                         * shortcut the full comparison. */
662
                        if (memcmp(*buffer, prefix, r) != 0)
×
663
                                return 0;
664

665
                /* Before version 1.8.3, lz4 always tries to decode full a "sequence",
666
                 * so in pathological cases might need to decompress the full field. */
667
                r = decompress_blob_lz4(src, src_size, buffer, &size, 0);
×
668
                if (r < 0)
×
669
                        return r;
670

671
                if (size < prefix_len + 1)
×
672
                        return 0;
673
        }
674

675
        return memcmp(*buffer, prefix, prefix_len) == 0 &&
270✔
676
                ((const uint8_t*) *buffer)[prefix_len] == extra;
267✔
677
#else
678
        return -EPROTONOSUPPORT;
679
#endif
680
}
681

682
int decompress_startswith_zstd(
4,822,251✔
683
                const void *src,
684
                uint64_t src_size,
685
                void **buffer,
686
                const void *prefix,
687
                size_t prefix_len,
688
                uint8_t extra) {
689

690
        assert(src);
4,822,251✔
691
        assert(src_size > 0);
4,822,251✔
692
        assert(buffer);
4,822,251✔
693
        assert(prefix);
4,822,251✔
694

695
#if HAVE_ZSTD
696
        int r;
4,822,251✔
697

698
        r = dlopen_zstd();
4,822,251✔
699
        if (r < 0)
4,822,251✔
700
                return r;
4,822,251✔
701

702
        uint64_t size = sym_ZSTD_getFrameContentSize(src, src_size);
4,822,251✔
703
        if (IN_SET(size, ZSTD_CONTENTSIZE_ERROR, ZSTD_CONTENTSIZE_UNKNOWN))
4,822,251✔
704
                return -EBADMSG;
705

706
        if (size < prefix_len + 1)
4,822,251✔
707
                return 0; /* Decompressed text too short to match the prefix and extra */
708

709
        _cleanup_(ZSTD_freeDCtxp) ZSTD_DCtx *dctx = sym_ZSTD_createDCtx();
9,644,502✔
710
        if (!dctx)
4,822,251✔
711
                return -ENOMEM;
712

713
        if (!(greedy_realloc(buffer, MAX(sym_ZSTD_DStreamOutSize(), prefix_len + 1), 1)))
4,822,251✔
714
                return -ENOMEM;
715

716
        ZSTD_inBuffer input = {
4,822,251✔
717
                .src = src,
718
                .size = src_size,
719
        };
720
        ZSTD_outBuffer output = {
9,644,502✔
721
                .dst = *buffer,
4,822,251✔
722
                .size = MALLOC_SIZEOF_SAFE(*buffer),
4,822,251✔
723
        };
724
        size_t k;
4,822,251✔
725

726
        k = sym_ZSTD_decompressStream(dctx, &output, &input);
4,822,251✔
727
        if (sym_ZSTD_isError(k)) {
4,822,251✔
728
                log_debug("ZSTD decoder failed: %s", sym_ZSTD_getErrorName(k));
×
729
                return zstd_ret_to_errno(k);
×
730
        }
731
        assert(output.pos >= prefix_len + 1);
4,822,251✔
732

733
        return memcmp(*buffer, prefix, prefix_len) == 0 &&
4,822,251✔
734
                ((const uint8_t*) *buffer)[prefix_len] == extra;
19,827✔
735
#else
736
        return -EPROTONOSUPPORT;
737
#endif
738
}
739

740
int decompress_startswith(
4,821,993✔
741
                Compression compression,
742
                const void *src,
743
                uint64_t src_size,
744
                void **buffer,
745
                const void *prefix,
746
                size_t prefix_len,
747
                uint8_t extra) {
748

749
        switch (compression) {
4,821,993✔
750

751
        case COMPRESSION_XZ:
4✔
752
                return decompress_startswith_xz(
4✔
753
                                src, src_size,
754
                                buffer,
755
                                prefix, prefix_len,
756
                                extra);
757

758
        case COMPRESSION_LZ4:
4✔
759
                return decompress_startswith_lz4(
4✔
760
                                src, src_size,
761
                                buffer,
762
                                prefix, prefix_len,
763
                                extra);
764
        case COMPRESSION_ZSTD:
4,821,985✔
765
                return decompress_startswith_zstd(
4,821,985✔
766
                                src, src_size,
767
                                buffer,
768
                                prefix, prefix_len,
769
                                extra);
770
        default:
771
                return -EBADMSG;
772
        }
773
}
774

775
int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) {
1✔
776
        assert(fdf >= 0);
1✔
777
        assert(fdt >= 0);
1✔
778

779
#if HAVE_XZ
780
        int r;
1✔
781

782
        r = dlopen_lzma();
1✔
783
        if (r < 0)
1✔
784
                return r;
1✔
785

786
        _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT;
1✔
787
        lzma_ret ret = sym_lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64);
1✔
788
        if (ret != LZMA_OK)
1✔
789
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
790
                                       "Failed to initialize XZ encoder: code %u",
791
                                       ret);
792

793
        uint8_t buf[BUFSIZ], out[BUFSIZ];
794
        lzma_action action = LZMA_RUN;
795
        for (;;) {
8✔
796
                if (s.avail_in == 0 && action == LZMA_RUN) {
8✔
797
                        size_t m = sizeof(buf);
7✔
798
                        ssize_t n;
7✔
799

800
                        if (max_bytes != UINT64_MAX && (uint64_t) m > max_bytes)
7✔
801
                                m = (size_t) max_bytes;
×
802

803
                        n = read(fdf, buf, m);
7✔
804
                        if (n < 0)
7✔
805
                                return -errno;
×
806
                        if (n == 0)
7✔
807
                                action = LZMA_FINISH;
808
                        else {
809
                                s.next_in = buf;
6✔
810
                                s.avail_in = n;
6✔
811

812
                                if (max_bytes != UINT64_MAX) {
6✔
813
                                        assert(max_bytes >= (uint64_t) n);
×
814
                                        max_bytes -= n;
×
815
                                }
816
                        }
817
                }
818

819
                if (s.avail_out == 0) {
8✔
820
                        s.next_out = out;
2✔
821
                        s.avail_out = sizeof(out);
2✔
822
                }
823

824
                ret = sym_lzma_code(&s, action);
8✔
825
                if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END))
8✔
826
                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
×
827
                                               "Compression failed: code %u",
828
                                               ret);
829

830
                if (s.avail_out == 0 || ret == LZMA_STREAM_END) {
8✔
831
                        ssize_t n, k;
2✔
832

833
                        n = sizeof(out) - s.avail_out;
2✔
834

835
                        k = loop_write(fdt, out, n);
2✔
836
                        if (k < 0)
2✔
837
                                return k;
838

839
                        if (ret == LZMA_STREAM_END) {
2✔
840
                                if (ret_uncompressed_size)
1✔
841
                                        *ret_uncompressed_size = s.total_in;
1✔
842

843
                                if (s.total_in == 0)
1✔
844
                                        log_debug("XZ compression finished (no input data)");
×
845
                                else
846
                                        log_debug("XZ compression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)",
1✔
847
                                                  s.total_in, s.total_out,
848
                                                  (double) s.total_out / s.total_in * 100);
849

850
                                return 0;
1✔
851
                        }
852
                }
853
        }
854
#else
855
        return -EPROTONOSUPPORT;
856
#endif
857
}
858

859
#define LZ4_BUFSIZE (512*1024u)
860

861
int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) {
1✔
862

863
#if HAVE_LZ4
864
        LZ4F_errorCode_t c;
1✔
865
        _cleanup_(LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL;
×
866
        _cleanup_free_ void *in_buff = NULL;
×
867
        _cleanup_free_ char *out_buff = NULL;
1✔
868
        size_t out_allocsize, n, offset = 0, frame_size;
1✔
869
        uint64_t total_in = 0, total_out;
1✔
870
        int r;
1✔
871
        static const LZ4F_preferences_t preferences = {
1✔
872
                .frameInfo.blockSizeID = 5,
873
        };
874

875
        r = dlopen_lz4();
1✔
876
        if (r < 0)
1✔
877
                return r;
878

879
        c = sym_LZ4F_createCompressionContext(&ctx, LZ4F_VERSION);
1✔
880
        if (sym_LZ4F_isError(c))
1✔
881
                return -ENOMEM;
882

883
        frame_size = sym_LZ4F_compressBound(LZ4_BUFSIZE, &preferences);
1✔
884
        out_allocsize = frame_size + 64*1024; /* add some space for header and trailer */
1✔
885
        out_buff = malloc(out_allocsize);
1✔
886
        if (!out_buff)
1✔
887
                return -ENOMEM;
888

889
        in_buff = malloc(LZ4_BUFSIZE);
1✔
890
        if (!in_buff)
1✔
891
                return -ENOMEM;
892

893
        n = offset = total_out = sym_LZ4F_compressBegin(ctx, out_buff, out_allocsize, &preferences);
1✔
894
        if (sym_LZ4F_isError(n))
1✔
895
                return -EINVAL;
896

897
        log_debug("Buffer size is %zu bytes, header size %zu bytes.", out_allocsize, n);
1✔
898

899
        for (;;) {
2✔
900
                ssize_t k;
2✔
901

902
                k = loop_read(fdf, in_buff, LZ4_BUFSIZE, true);
2✔
903
                if (k < 0)
2✔
904
                        return k;
×
905
                if (k == 0)
2✔
906
                        break;
907
                n = sym_LZ4F_compressUpdate(ctx, out_buff + offset, out_allocsize - offset,
1✔
908
                                        in_buff, k, NULL);
909
                if (sym_LZ4F_isError(n))
1✔
910
                        return -ENOTRECOVERABLE;
911

912
                total_in += k;
1✔
913
                offset += n;
1✔
914
                total_out += n;
1✔
915

916
                if (max_bytes != UINT64_MAX && total_out > (size_t) max_bytes)
1✔
917
                        return log_debug_errno(SYNTHETIC_ERRNO(EFBIG),
×
918
                                               "Compressed stream longer than %" PRIu64 " bytes", max_bytes);
919

920
                if (out_allocsize - offset < frame_size + 4) {
1✔
921
                        k = loop_write(fdt, out_buff, offset);
×
922
                        if (k < 0)
×
923
                                return k;
924
                        offset = 0;
925
                }
926
        }
927

928
        n = sym_LZ4F_compressEnd(ctx, out_buff + offset, out_allocsize - offset, NULL);
1✔
929
        if (sym_LZ4F_isError(n))
1✔
930
                return -ENOTRECOVERABLE;
931

932
        offset += n;
1✔
933
        total_out += n;
1✔
934
        r = loop_write(fdt, out_buff, offset);
1✔
935
        if (r < 0)
1✔
936
                return r;
937

938
        if (ret_uncompressed_size)
1✔
939
                *ret_uncompressed_size = total_in;
1✔
940

941
        if (total_in == 0)
1✔
942
                log_debug("LZ4 compression finished (no input data)");
×
943
        else
944
                log_debug("LZ4 compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)",
1✔
945
                          total_in, total_out,
946
                          (double) total_out / total_in * 100);
947

948
        return 0;
949
#else
950
        return -EPROTONOSUPPORT;
951
#endif
952
}
953

954
int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) {
3✔
955
        assert(fdf >= 0);
3✔
956
        assert(fdt >= 0);
3✔
957

958
#if HAVE_XZ
959
        int r;
3✔
960

961
        r = dlopen_lzma();
3✔
962
        if (r < 0)
3✔
963
                return r;
3✔
964

965
        _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT;
3✔
966
        lzma_ret ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0);
3✔
967
        if (ret != LZMA_OK)
3✔
968
                return log_debug_errno(SYNTHETIC_ERRNO(ENOMEM),
×
969
                                       "Failed to initialize XZ decoder: code %u",
970
                                       ret);
971

972
        uint8_t buf[BUFSIZ], out[BUFSIZ];
973
        lzma_action action = LZMA_RUN;
974
        for (;;) {
15✔
975
                if (s.avail_in == 0 && action == LZMA_RUN) {
15✔
976
                        ssize_t n;
5✔
977

978
                        n = read(fdf, buf, sizeof(buf));
5✔
979
                        if (n < 0)
5✔
980
                                return -errno;
×
981
                        if (n == 0)
5✔
982
                                action = LZMA_FINISH;
983
                        else {
984
                                s.next_in = buf;
5✔
985
                                s.avail_in = n;
5✔
986
                        }
987
                }
988

989
                if (s.avail_out == 0) {
15✔
990
                        s.next_out = out;
13✔
991
                        s.avail_out = sizeof(out);
13✔
992
                }
993

994
                ret = sym_lzma_code(&s, action);
15✔
995
                if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END))
15✔
996
                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
1✔
997
                                               "Decompression failed: code %u",
998
                                               ret);
999

1000
                if (s.avail_out == 0 || ret == LZMA_STREAM_END) {
14✔
1001
                        ssize_t n, k;
12✔
1002

1003
                        n = sizeof(out) - s.avail_out;
12✔
1004

1005
                        if (max_bytes != UINT64_MAX) {
12✔
1006
                                if (max_bytes < (uint64_t) n)
12✔
1007
                                        return -EFBIG;
1008

1009
                                max_bytes -= n;
11✔
1010
                        }
1011

1012
                        k = loop_write(fdt, out, n);
11✔
1013
                        if (k < 0)
11✔
1014
                                return k;
1015

1016
                        if (ret == LZMA_STREAM_END) {
11✔
1017
                                if (s.total_in == 0)
1✔
1018
                                        log_debug("XZ decompression finished (no input data)");
×
1019
                                else
1020
                                        log_debug("XZ decompression finished (%"PRIu64" -> %"PRIu64" bytes, %.1f%%)",
1✔
1021
                                                  s.total_in, s.total_out,
1022
                                                  (double) s.total_out / s.total_in * 100);
1023

1024
                                return 0;
1✔
1025
                        }
1026
                }
1027
        }
1028
#else
1029
        return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT),
1030
                               "Cannot decompress file. Compiled without XZ support.");
1031
#endif
1032
}
1033

1034
int decompress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) {
3✔
1035
#if HAVE_LZ4
1036
        size_t c;
3✔
1037
        _cleanup_(LZ4F_freeDecompressionContextp) LZ4F_decompressionContext_t ctx = NULL;
×
1038
        _cleanup_free_ char *buf = NULL;
3✔
1039
        char *src;
3✔
1040
        struct stat st;
3✔
1041
        int r;
3✔
1042
        size_t total_in = 0, total_out = 0;
3✔
1043

1044
        r = dlopen_lz4();
3✔
1045
        if (r < 0)
3✔
1046
                return r;
1047

1048
        c = sym_LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION);
3✔
1049
        if (sym_LZ4F_isError(c))
3✔
1050
                return -ENOMEM;
1051

1052
        if (fstat(fdf, &st) < 0)
3✔
1053
                return log_debug_errno(errno, "fstat() failed: %m");
×
1054

1055
        if (file_offset_beyond_memory_size(st.st_size))
3✔
1056
                return -EFBIG;
1057

1058
        buf = malloc(LZ4_BUFSIZE);
3✔
1059
        if (!buf)
3✔
1060
                return -ENOMEM;
1061

1062
        src = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fdf, 0);
3✔
1063
        if (src == MAP_FAILED)
3✔
1064
                return -errno;
×
1065

1066
        while (total_in < (size_t) st.st_size) {
5✔
1067
                size_t produced = LZ4_BUFSIZE;
3✔
1068
                size_t used = st.st_size - total_in;
3✔
1069

1070
                c = sym_LZ4F_decompress(ctx, buf, &produced, src + total_in, &used, NULL);
3✔
1071
                if (sym_LZ4F_isError(c)) {
3✔
1072
                        r = -EBADMSG;
×
1073
                        goto cleanup;
1✔
1074
                }
1075

1076
                total_in += used;
3✔
1077
                total_out += produced;
3✔
1078

1079
                if (max_bytes != UINT64_MAX && total_out > (size_t) max_bytes) {
3✔
1080
                        log_debug("Decompressed stream longer than %"PRIu64" bytes", max_bytes);
1✔
1081
                        r = -EFBIG;
1✔
1082
                        goto cleanup;
1✔
1083
                }
1084

1085
                r = loop_write(fdt, buf, produced);
2✔
1086
                if (r < 0)
2✔
1087
                        goto cleanup;
×
1088
        }
1089

1090
        if (total_in == 0)
2✔
1091
                log_debug("LZ4 decompression finished (no input data)");
×
1092
        else
1093
                log_debug("LZ4 decompression finished (%zu -> %zu bytes, %.1f%%)",
2✔
1094
                          total_in, total_out,
1095
                          (double) total_out / total_in * 100);
1096
        r = 0;
1097
 cleanup:
3✔
1098
        munmap(src, st.st_size);
3✔
1099
        return r;
3✔
1100
#else
1101
        return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT),
1102
                               "Cannot decompress file. Compiled without LZ4 support.");
1103
#endif
1104
}
1105

1106
int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) {
21✔
1107
        assert(fdf >= 0);
21✔
1108
        assert(fdt >= 0);
21✔
1109

1110
#if HAVE_ZSTD
1111
        _cleanup_(ZSTD_freeCCtxp) ZSTD_CCtx *cctx = NULL;
×
1112
        _cleanup_free_ void *in_buff = NULL, *out_buff = NULL;
21✔
1113
        size_t in_allocsize, out_allocsize;
21✔
1114
        size_t z;
21✔
1115
        uint64_t left = max_bytes, in_bytes = 0;
21✔
1116
        int r;
21✔
1117

1118
        r = dlopen_zstd();
21✔
1119
        if (r < 0)
21✔
1120
                return r;
1121

1122
        /* Create the context and buffers */
1123
        in_allocsize = sym_ZSTD_CStreamInSize();
21✔
1124
        out_allocsize = sym_ZSTD_CStreamOutSize();
21✔
1125
        in_buff = malloc(in_allocsize);
21✔
1126
        out_buff = malloc(out_allocsize);
21✔
1127
        cctx = sym_ZSTD_createCCtx();
21✔
1128
        if (!cctx || !out_buff || !in_buff)
21✔
1129
                return -ENOMEM;
1130

1131
        z = sym_ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag, 1);
21✔
1132
        if (sym_ZSTD_isError(z))
21✔
1133
                log_debug("Failed to enable ZSTD checksum, ignoring: %s", sym_ZSTD_getErrorName(z));
×
1134

1135
        /* This loop read from the input file, compresses that entire chunk,
1136
         * and writes all output produced to the output file.
1137
         */
1138
        for (;;) {
503✔
1139
                bool is_last_chunk;
524✔
1140
                ZSTD_inBuffer input = {
524✔
1141
                        .src = in_buff,
1142
                        .size = 0,
1143
                        .pos = 0
1144
                };
1145
                ssize_t red;
524✔
1146

1147
                red = loop_read(fdf, in_buff, in_allocsize, true);
524✔
1148
                if (red < 0)
524✔
1149
                        return red;
×
1150
                is_last_chunk = red == 0;
524✔
1151

1152
                in_bytes += (size_t) red;
524✔
1153
                input.size = (size_t) red;
524✔
1154

1155
                for (bool finished = false; !finished;) {
1,048✔
1156
                        ZSTD_outBuffer output = {
524✔
1157
                                .dst = out_buff,
1158
                                .size = out_allocsize,
1159
                                .pos = 0
1160
                        };
1161
                        size_t remaining;
524✔
1162
                        ssize_t wrote;
524✔
1163

1164
                        /* Compress into the output buffer and write all of the
1165
                         * output to the file so we can reuse the buffer next
1166
                         * iteration.
1167
                         */
1168
                        remaining = sym_ZSTD_compressStream2(
1,027✔
1169
                                cctx, &output, &input,
1170
                                is_last_chunk ? ZSTD_e_end : ZSTD_e_continue);
1171

1172
                        if (sym_ZSTD_isError(remaining)) {
524✔
1173
                                log_debug("ZSTD encoder failed: %s", sym_ZSTD_getErrorName(remaining));
×
1174
                                return zstd_ret_to_errno(remaining);
×
1175
                        }
1176

1177
                        if (left < output.pos)
524✔
1178
                                return -EFBIG;
1179

1180
                        wrote = loop_write_full(fdt, output.dst, output.pos, USEC_INFINITY);
524✔
1181
                        if (wrote < 0)
524✔
1182
                                return wrote;
1183

1184
                        left -= output.pos;
524✔
1185

1186
                        /* If we're on the last chunk we're finished when zstd
1187
                         * returns 0, which means its consumed all the input AND
1188
                         * finished the frame. Otherwise, we're finished when
1189
                         * we've consumed all the input.
1190
                         */
1191
                        finished = is_last_chunk ? (remaining == 0) : (input.pos == input.size);
524✔
1192
                }
1193

1194
                /* zstd only returns 0 when the input is completely consumed */
1195
                assert(input.pos == input.size);
524✔
1196
                if (is_last_chunk)
524✔
1197
                        break;
1198
        }
1199

1200
        if (ret_uncompressed_size)
21✔
1201
                *ret_uncompressed_size = in_bytes;
21✔
1202

1203
        if (in_bytes == 0)
21✔
1204
                log_debug("ZSTD compression finished (no input data)");
×
1205
        else
1206
                log_debug("ZSTD compression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)",
21✔
1207
                          in_bytes, max_bytes - left, (double) (max_bytes - left) / in_bytes * 100);
1208

1209
        return 0;
1210
#else
1211
        return -EPROTONOSUPPORT;
1212
#endif
1213
}
1214

1215
int decompress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) {
13✔
1216
        assert(fdf >= 0);
13✔
1217
        assert(fdt >= 0);
13✔
1218

1219
#if HAVE_ZSTD
1220
        _cleanup_(ZSTD_freeDCtxp) ZSTD_DCtx *dctx = NULL;
×
1221
        _cleanup_free_ void *in_buff = NULL, *out_buff = NULL;
13✔
1222
        size_t in_allocsize, out_allocsize;
13✔
1223
        size_t last_result = 0;
13✔
1224
        uint64_t left = max_bytes, in_bytes = 0;
13✔
1225
        int r;
13✔
1226

1227
        r = dlopen_zstd();
13✔
1228
        if (r < 0)
13✔
1229
                return r;
1230
        /* Create the context and buffers */
1231
        in_allocsize = sym_ZSTD_DStreamInSize();
13✔
1232
        out_allocsize = sym_ZSTD_DStreamOutSize();
13✔
1233
        in_buff = malloc(in_allocsize);
13✔
1234
        out_buff = malloc(out_allocsize);
13✔
1235
        dctx = sym_ZSTD_createDCtx();
13✔
1236
        if (!dctx || !out_buff || !in_buff)
13✔
1237
                return -ENOMEM;
1238

1239
        /* This loop assumes that the input file is one or more concatenated
1240
         * zstd streams. This example won't work if there is trailing non-zstd
1241
         * data at the end, but streaming decompression in general handles this
1242
         * case. ZSTD_decompressStream() returns 0 exactly when the frame is
1243
         * completed, and doesn't consume input after the frame.
1244
         */
1245
        for (;;) {
69✔
1246
                bool has_error = false;
41✔
1247
                ZSTD_inBuffer input = {
41✔
1248
                        .src = in_buff,
1249
                        .size = 0,
1250
                        .pos = 0
1251
                };
1252
                ssize_t red;
41✔
1253

1254
                red = loop_read(fdf, in_buff, in_allocsize, true);
41✔
1255
                if (red < 0)
41✔
1256
                        return red;
2✔
1257
                if (red == 0)
41✔
1258
                        break;
1259

1260
                in_bytes += (size_t) red;
31✔
1261
                input.size = (size_t) red;
31✔
1262
                input.pos = 0;
31✔
1263

1264
                /* Given a valid frame, zstd won't consume the last byte of the
1265
                 * frame until it has flushed all of the decompressed data of
1266
                 * the frame. So input.pos < input.size means frame is not done
1267
                 * or there is still output available.
1268
                 */
1269
                while (input.pos < input.size) {
266✔
1270
                        ZSTD_outBuffer output = {
238✔
1271
                                .dst = out_buff,
1272
                                .size = out_allocsize,
1273
                                .pos = 0
1274
                        };
1275
                        ssize_t wrote;
238✔
1276
                        /* The return code is zero if the frame is complete, but
1277
                         * there may be multiple frames concatenated together.
1278
                         * Zstd will automatically reset the context when a
1279
                         * frame is complete. Still, calling ZSTD_DCtx_reset()
1280
                         * can be useful to reset the context to a clean state,
1281
                         * for instance if the last decompression call returned
1282
                         * an error.
1283
                         */
1284
                        last_result = sym_ZSTD_decompressStream(dctx, &output, &input);
238✔
1285
                        if (sym_ZSTD_isError(last_result)) {
238✔
1286
                                has_error = true;
1✔
1287
                                break;
1✔
1288
                        }
1289

1290
                        if (left < output.pos)
237✔
1291
                                return -EFBIG;
2✔
1292

1293
                        wrote = loop_write_full(fdt, output.dst, output.pos, USEC_INFINITY);
236✔
1294
                        if (wrote < 0)
236✔
1295
                                return wrote;
1296

1297
                        left -= output.pos;
235✔
1298
                }
1299
                if (has_error)
29✔
1300
                        break;
1301
        }
1302

1303
        if (in_bytes == 0)
11✔
1304
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "ZSTD decoder failed: no data read");
×
1305

1306
        if (last_result != 0) {
11✔
1307
                /* The last return value from ZSTD_decompressStream did not end
1308
                 * on a frame, but we reached the end of the file! We assume
1309
                 * this is an error, and the input was truncated.
1310
                 */
1311
                log_debug("ZSTD decoder failed: %s", sym_ZSTD_getErrorName(last_result));
1✔
1312
                return zstd_ret_to_errno(last_result);
1✔
1313
        }
1314

1315
        if (in_bytes == 0)
10✔
1316
                log_debug("ZSTD decompression finished (no input data)");
1317
        else
1318
                log_debug("ZSTD decompression finished (%" PRIu64 " -> %" PRIu64 " bytes, %.1f%%)",
10✔
1319
                          in_bytes,
1320
                          max_bytes - left,
1321
                          (double) (max_bytes - left) / in_bytes * 100);
1322
        return 0;
1323
#else
1324
        return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT),
1325
                               "Cannot decompress file. Compiled without ZSTD support.");
1326
#endif
1327
}
1328

1329
int decompress_stream(const char *filename, int fdf, int fdt, uint64_t max_bytes) {
10✔
1330

1331
        if (endswith(filename, ".lz4"))
10✔
1332
                return decompress_stream_lz4(fdf, fdt, max_bytes);
×
1333
        if (endswith(filename, ".xz"))
10✔
1334
                return decompress_stream_xz(fdf, fdt, max_bytes);
×
1335
        if (endswith(filename, ".zst"))
10✔
1336
                return decompress_stream_zstd(fdf, fdt, max_bytes);
10✔
1337

1338
        return -EPROTONOSUPPORT;
1339
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc