1 /*#############################################################################
3 # Pakfire - The IPFire package management system #
4 # Copyright (C) 2021 Pakfire development team #
6 # This program is free software: you can redistribute it and/or modify #
7 # it under the terms of the GNU General Public License as published by #
8 # the Free Software Foundation, either version 3 of the License, or #
9 # (at your option) any later version. #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
19 #############################################################################*/
30 #include <pakfire/ctx.h>
31 #include <pakfire/compress.h>
32 #include <pakfire/file.h>
33 #include <pakfire/filelist.h>
34 #include <pakfire/logging.h>
35 #include <pakfire/path.h>
36 #include <pakfire/progress.h>
37 #include <pakfire/string.h>
38 #include <pakfire/util.h>
40 // Read up to N bytes for analyze the magic
41 #define MAX_MAGIC_LENGTH 6
43 // Compression/Decompression buffer size
44 #define BUFFER_SIZE 64 * 1024
47 #define XZ_COMPRESSION_LEVEL 6
50 #define ZSTD_COMPRESSION_LEVEL 7
52 const struct compressor
{
53 char magic
[MAX_MAGIC_LENGTH
];
55 FILE* (*open
)(FILE* f
, const char* mode
);
58 { { 0xFD, '7', 'z', 'X', 'Z', 0x00 }, 6, pakfire_xzfopen
, },
60 { { 0x28, 0xb5, 0x2f, 0xfd }, 4, pakfire_zstdfopen
, },
65 // Try to guess the compression
66 FILE* pakfire_xfopen(FILE* f
, const char* mode
) {
67 char buffer
[MAX_MAGIC_LENGTH
];
79 // This only works for reading files
88 int r
= fgetpos(f
, &pos
);
92 // Read a couple of bytes
93 size_t bytes_read
= fread(buffer
, 1, sizeof(buffer
), f
);
100 // Check if we could read anything
101 if (!bytes_read
|| bytes_read
< sizeof(buffer
))
105 for (const struct compressor
* c
= compressors
; c
->open
; c
++) {
106 // Check if we have read enough data
107 if (bytes_read
< c
->magic_length
)
110 // Compare the magic value
111 r
= memcmp(c
->magic
, buffer
, c
->magic_length
);
116 return c
->open(f
, mode
);
119 // Nothing seems to match
130 uint8_t buffer
[BUFFER_SIZE
];
133 static ssize_t
xz_read(void* data
, char* buffer
, size_t size
) {
134 struct xz_cookie
* cookie
= (struct xz_cookie
*)data
;
138 // Do not read when mode is "w"
139 if (cookie
->mode
== 'w')
142 lzma_action action
= LZMA_RUN
;
144 // Set output to allocated buffer
145 cookie
->stream
.next_out
= (uint8_t *)buffer
;
146 cookie
->stream
.avail_out
= size
;
149 // Read something when the input buffer is empty
150 if (cookie
->stream
.avail_in
== 0) {
151 cookie
->stream
.next_in
= cookie
->buffer
;
152 cookie
->stream
.avail_in
= fread(cookie
->buffer
,
153 1, sizeof(cookie
->buffer
), cookie
->f
);
155 // Break if the input file could not be read
156 if (ferror(cookie
->f
))
159 // Finish after we have reached the end of the input file
164 lzma_ret ret
= lzma_code(&cookie
->stream
, action
);
166 // If the stream has ended, we just send the
167 // remaining output and mark that we are done.
168 if (ret
== LZMA_STREAM_END
) {
170 return size
- cookie
->stream
.avail_out
;
173 // Break on all other unexpected errors
177 // When we have read enough to fill the entire output buffer, we return
178 if (cookie
->stream
.avail_out
== 0)
186 static ssize_t
xz_write(void* data
, const char* buffer
, size_t size
) {
187 struct xz_cookie
* cookie
= (struct xz_cookie
*)data
;
191 // Do not write when mode is "r"
192 if (cookie
->mode
== 'r')
195 // Return nothing when there is no input
199 // Set input to allocated buffer
200 cookie
->stream
.next_in
= (uint8_t *)buffer
;
201 cookie
->stream
.avail_in
= size
;
204 cookie
->stream
.next_out
= cookie
->buffer
;
205 cookie
->stream
.avail_out
= sizeof(cookie
->buffer
);
207 lzma_ret ret
= lzma_code(&cookie
->stream
, LZMA_RUN
);
211 size_t bytes_to_write
= sizeof(cookie
->buffer
) - cookie
->stream
.avail_out
;
212 if (bytes_to_write
) {
213 size_t bytes_written
= fwrite(cookie
->buffer
, 1, bytes_to_write
, cookie
->f
);
215 if (bytes_written
!= bytes_to_write
)
219 // Report that all data has been written
220 if (cookie
->stream
.avail_in
== 0)
225 static int xz_close(void* data
) {
226 struct xz_cookie
* cookie
= (struct xz_cookie
*)data
;
230 if (cookie
->mode
== 'w') {
232 cookie
->stream
.next_out
= cookie
->buffer
;
233 cookie
->stream
.avail_out
= sizeof(cookie
->buffer
);
235 lzma_ret ret
= lzma_code(&cookie
->stream
, LZMA_FINISH
);
236 if (ret
!= LZMA_OK
&& ret
!= LZMA_STREAM_END
)
239 size_t bytes_to_write
= sizeof(cookie
->buffer
) - cookie
->stream
.avail_out
;
240 if (bytes_to_write
) {
241 size_t bytes_written
= fwrite(cookie
->buffer
, 1, bytes_to_write
, cookie
->f
);
243 if (bytes_written
!= bytes_to_write
)
247 if (ret
== LZMA_STREAM_END
)
252 lzma_end(&cookie
->stream
);
255 int r
= fclose(cookie
->f
);
261 static cookie_io_functions_t xz_functions
= {
268 FILE* pakfire_xzfopen(FILE* f
, const char* mode
) {
281 struct xz_cookie
* cookie
= calloc(1, sizeof(*cookie
));
286 cookie
->mode
= *mode
;
288 switch (cookie
->mode
) {
290 ret
= lzma_stream_decoder(&cookie
->stream
, UINT64_MAX
, 0);
294 ret
= lzma_easy_encoder(&cookie
->stream
, XZ_COMPRESSION_LEVEL
, LZMA_CHECK_SHA256
);
305 return fopencookie(cookie
, mode
, xz_functions
);
320 ZSTD_CStream
* cstream
;
324 ZSTD_DStream
* dstream
;
327 uint8_t buffer
[BUFFER_SIZE
];
330 static ssize_t
zstd_read(void* data
, char* buffer
, size_t size
) {
331 struct zstd_cookie
* cookie
= (struct zstd_cookie
*)data
;
335 // Do not read when mode is "w"
336 if (cookie
->mode
== 'w')
344 // Configure output buffer
345 cookie
->out
.dst
= buffer
;
347 cookie
->out
.size
= size
;
350 if (!feof(cookie
->f
) && (cookie
->in
.pos
== cookie
->in
.size
)) {
352 cookie
->in
.size
= fread(cookie
->buffer
, 1, sizeof(cookie
->buffer
), cookie
->f
);
355 if (r
|| cookie
->in
.size
)
356 r
= ZSTD_decompressStream(cookie
->dstream
, &cookie
->out
, &cookie
->in
);
358 if (r
== 0 && feof(cookie
->f
)) {
360 return cookie
->out
.pos
;
367 if (cookie
->out
.pos
== size
)
372 static ssize_t
zstd_write(void* data
, const char* buffer
, size_t size
) {
373 struct zstd_cookie
* cookie
= (struct zstd_cookie
*)data
;
377 // Do not write when mode is "r"
378 if (cookie
->mode
== 'r')
381 // Return nothing when there is no input
385 // Configure input buffer
386 cookie
->in
.src
= buffer
;
388 cookie
->in
.size
= size
;
393 size_t r
= ZSTD_compressStream(cookie
->cstream
, &cookie
->out
, &cookie
->in
);
397 if (cookie
->out
.pos
> 0) {
398 size_t bytes_written
= fwrite(cookie
->buffer
, 1, cookie
->out
.pos
, cookie
->f
);
400 if (bytes_written
!= cookie
->out
.pos
)
404 // Return when all input has been written
405 if (cookie
->in
.pos
== size
)
410 static int zstd_close(void* data
) {
411 struct zstd_cookie
* cookie
= (struct zstd_cookie
*)data
;
415 if (cookie
->mode
== 'w') {
417 // Reset output buffer
420 size_t r
= ZSTD_endStream(cookie
->cstream
, &cookie
->out
);
424 if (cookie
->out
.pos
> 0) {
425 size_t bytes_written
= fwrite(cookie
->buffer
, 1, cookie
->out
.pos
, cookie
->f
);
427 if (bytes_written
!= cookie
->out
.pos
)
436 int r
= fclose(cookie
->f
);
440 ZSTD_freeCStream(cookie
->cstream
);
442 ZSTD_freeDStream(cookie
->dstream
);
448 static cookie_io_functions_t zstd_functions
= {
455 FILE* pakfire_zstdfopen(FILE* f
, const char* mode
) {
466 struct zstd_cookie
* cookie
= calloc(1, sizeof(*cookie
));
471 cookie
->mode
= *mode
;
474 switch (cookie
->mode
) {
477 cookie
->dstream
= ZSTD_createDStream();
478 if (!cookie
->dstream
)
482 r
= ZSTD_initDStream(cookie
->dstream
);
486 cookie
->in
.src
= cookie
->buffer
;
493 cookie
->cstream
= ZSTD_createCStream();
494 if (!cookie
->cstream
)
498 r
= ZSTD_initCStream(cookie
->cstream
, ZSTD_COMPRESSION_LEVEL
);
502 cookie
->out
.dst
= cookie
->buffer
;
504 cookie
->out
.size
= sizeof(cookie
->buffer
);
512 return fopencookie(cookie
, mode
, zstd_functions
);
516 ZSTD_freeCStream(cookie
->cstream
);
518 ZSTD_freeDStream(cookie
->dstream
);
525 Helper function to conditionally walk through an archive
526 and perform actions based on the callback.
528 int pakfire_walk(struct pakfire_ctx
* ctx
, struct archive
* archive
,
529 pakfire_walk_callback callback
, pakfire_walk_filter_callback filter_callback
,
531 struct archive_entry
* entry
= NULL
;
532 const char* path
= NULL
;
535 // Walk through the archive
537 r
= archive_read_next_header(archive
, &entry
);
539 // Handle the return code
541 // Fall through if everything is okay
545 // Return OK when we reached the end of the archive
549 // Raise any other errors
554 path
= archive_entry_pathname(entry
);
556 CTX_DEBUG(ctx
, "Walking through %s...\n", path
);
558 // Call the filter callback before we call the actual callback
559 if (filter_callback
) {
560 r
= filter_callback(ctx
, archive
, entry
, p
);
562 // Handle the return code
564 case PAKFIRE_WALK_OK
:
567 case PAKFIRE_WALK_END
:
568 CTX_DEBUG(ctx
, "Filter callback sent END\n");
571 case PAKFIRE_WALK_SKIP
:
572 CTX_DEBUG(ctx
, "Filter callback sent SKIP\n");
575 case PAKFIRE_WALK_DONE
:
576 CTX_DEBUG(ctx
, "Filter callback sent DONE\n");
578 // Clear the callback function
579 filter_callback
= NULL
;
582 case PAKFIRE_WALK_AGAIN
:
583 CTX_DEBUG(ctx
, "Filter callback sent AGAIN\n");
586 // Raise any other errors
588 CTX_DEBUG(ctx
, "Filter callback returned an error: %d\n", r
);
595 r
= callback(ctx
, archive
, entry
, p
);
597 // Handle the return code
599 case PAKFIRE_WALK_OK
:
602 case PAKFIRE_WALK_DONE
:
603 CTX_DEBUG(ctx
, "Callback sent DONE\n");
606 // Raise any other errors
618 struct pakfire_extract
{
619 // Reference to Pakfire
620 struct pakfire
* pakfire
;
625 // The archive to extract
626 struct archive
* archive
;
628 // The filelist of all extracted files
629 struct pakfire_filelist
* filelist
;
631 // Prepend this prefix
635 struct archive
* writer
;
637 // The progress indicator
638 struct pakfire_progress
* progress
;
641 static void pakfire_extract_progress(void* p
) {
642 struct pakfire_extract
* data
= (struct pakfire_extract
*)p
;
644 // Fetch how many bytes have been read
645 const size_t position
= archive_filter_bytes(data
->archive
, -1);
648 pakfire_progress_update(data
->progress
, position
);
651 static int __pakfire_extract(struct pakfire_ctx
* ctx
, struct archive
* a
,
652 struct archive_entry
* entry
, void* p
) {
653 struct pakfire_file
* file
= NULL
;
654 struct vfs_cap_data cap_data
= {};
655 char buffer
[PATH_MAX
];
658 struct pakfire_extract
* data
= (struct pakfire_extract
*)p
;
661 const char* path
= archive_entry_pathname(entry
);
663 // Make sure we have a leading slash on the filelist
664 if (!pakfire_string_startswith(path
, "/")) {
665 r
= pakfire_string_format(buffer
, "/%s", path
);
669 // Store the new name
670 archive_entry_set_pathname(entry
, buffer
);
672 // Update the path pointer
673 path
= archive_entry_pathname(entry
);
676 // Generate a file object
677 r
= pakfire_file_create_from_archive_entry(&file
, data
->pakfire
, entry
);
681 // Add entry to filelist (if requested)
682 if (data
->filelist
) {
683 // Append the file to the list
684 r
= pakfire_filelist_add(data
->filelist
, file
);
689 const int configfile
= pakfire_file_has_flag(file
, PAKFIRE_FILE_CONFIG
);
691 // Prepend the prefix
694 r
= pakfire_path_append(buffer
, data
->prefix
, path
);
696 CTX_ERROR(ctx
, "Could not compose file path: %m\n");
701 archive_entry_set_pathname(entry
, buffer
);
703 // Update hardlink destination
704 const char* link
= archive_entry_hardlink(entry
);
706 r
= pakfire_path_append(buffer
, data
->prefix
, link
);
708 CTX_ERROR(ctx
, "Could not compose hardlink path: %m\n");
713 archive_entry_set_hardlink(entry
, buffer
);
718 // Fetch path again since we changed it
719 path
= archive_entry_pathname(entry
);
721 if (pakfire_path_exists(path
)) {
722 CTX_DEBUG(ctx
, "The configuration file %s exists\n",
723 pakfire_file_get_path(file
));
725 r
= pakfire_string_format(buffer
, "%s.paknew", path
);
727 CTX_ERROR(ctx
, "Could not compose path for configuration file: %m\n");
731 // Set the path again
732 archive_entry_set_pathname(entry
, buffer
);
736 // Create file & extract payload
738 // Fetch path again since we changed it
739 path
= archive_entry_pathname(entry
);
741 CTX_DEBUG(ctx
, "Extracting %s\n", path
);
743 // Remove any extended attributes which we never write to disk
744 archive_entry_xattr_clear(entry
);
747 if (pakfire_file_has_caps(file
)) {
748 r
= pakfire_file_write_fcaps(file
, &cap_data
);
752 // Store capabilities in archive entry
753 archive_entry_xattr_add_entry(entry
, "security.capability",
754 &cap_data
, sizeof(cap_data
));
758 r
= archive_read_extract2(data
->archive
, entry
, data
->writer
);
765 CTX_ERROR(ctx
, "%s\n", archive_error_string(data
->writer
));
767 // Pretend everything has been okay
772 CTX_ERROR(ctx
, "%s\n", archive_error_string(data
->writer
));
780 pakfire_file_unref(file
);
785 int pakfire_extract(struct pakfire
* pakfire
, struct archive
* archive
,
786 size_t size
, struct pakfire_filelist
* filelist
,
787 const char* prefix
, const char* message
,
788 pakfire_walk_filter_callback filter_callback
, int flags
) {
789 int progress_flags
= PAKFIRE_PROGRESS_SHOW_PERCENTAGE
;
792 struct pakfire_ctx
* ctx
= pakfire_ctx(pakfire
);
794 // Use / if no prefix is set
798 struct pakfire_extract data
= {
801 .filelist
= filelist
,
807 // Is this a dry run?
808 const int dry_run
= flags
& PAKFIRE_EXTRACT_DRY_RUN
;
812 data
.writer
= pakfire_make_archive_disk_writer(pakfire
, 1);
814 ERROR(pakfire
, "Could not create disk writer: %m\n");
820 // Should we show any progress?
821 if (flags
& PAKFIRE_EXTRACT_NO_PROGRESS
)
822 progress_flags
|= PAKFIRE_PROGRESS_NO_PROGRESS
;
825 if (flags
& PAKFIRE_EXTRACT_SHOW_THROUGHPUT
)
826 progress_flags
|= PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED
;
828 // Create the progress indicator
829 r
= pakfire_progress_create(&data
.progress
, ctx
, progress_flags
, NULL
);
834 r
= pakfire_progress_set_title(data
.progress
, "%s", message
);
838 // Register progress callback
839 archive_read_extract_set_progress_callback(data
.archive
,
840 pakfire_extract_progress
, &data
);
843 r
= pakfire_progress_start(data
.progress
, size
);
847 // Walk through the entire archive
848 r
= pakfire_walk(ctx
, archive
, __pakfire_extract
, filter_callback
, &data
);
852 // Finish the progress
853 r
= pakfire_progress_finish(data
.progress
);
859 pakfire_progress_unref(data
.progress
);
861 archive_write_free(data
.writer
);
863 pakfire_ctx_unref(ctx
);
868 // Common compression
870 struct pakfire_compress
{
871 // Reference to Pakfire
872 struct pakfire
* pakfire
;
877 // The archive to write to
878 struct archive
* archive
;
880 // Resolver for hardlinks
881 struct archive_entry_linkresolver
* linkresolver
;
883 // The filelist of all files to write
884 struct pakfire_filelist
* filelist
;
886 // The progress indicator
887 struct pakfire_progress
* progress
;
889 // Digests to write to the archive
893 static int pakfire_copy_data_from_file(struct pakfire
* pakfire
,
894 struct archive
* archive
, FILE* f
) {
895 char buffer
[BUFFER_SIZE
];
897 ssize_t bytes_read
= 0;
898 ssize_t bytes_written
= 0;
900 // Read file from the very beginning - also allows calling this multiple times
903 // Loop through the entire length of the file
905 // Read a block from file
906 bytes_read
= fread(buffer
, 1, sizeof(buffer
), f
);
908 // Check if any error occured
910 ERROR(pakfire
, "Read error: %m\n");
914 // Write the block to the archive
915 bytes_written
= archive_write_data(archive
, buffer
, bytes_read
);
916 if (bytes_written
< bytes_read
) {
917 ERROR(pakfire
, "Write error: %s\n", archive_error_string(archive
));
925 static int __pakfire_compress_entry(struct pakfire
* pakfire
, struct pakfire_file
* file
,
926 struct pakfire_compress
* data
, struct archive_entry
* entry
) {
930 const char* path
= archive_entry_pathname(entry
);
932 // Remove any leading slahes
936 archive_entry_set_pathname(entry
, path
);
939 r
= archive_write_header(data
->archive
, entry
);
941 ERROR(pakfire
, "Error writing file header: %s\n",
942 archive_error_string(data
->archive
));
946 // Copy the data if there is any
947 if (archive_entry_size(entry
)) {
949 f
= pakfire_file_open(file
);
955 // Copy the payload into the archive
956 r
= pakfire_copy_data_from_file(pakfire
, data
->archive
, f
);
962 r
= archive_write_finish_entry(data
->archive
);
973 static int __pakfire_compress(struct pakfire
* pakfire
, struct pakfire_file
* file
, void* p
) {
974 struct archive_entry
* entry
= NULL
;
975 struct archive_entry
* sparse_entry
= NULL
;
978 struct pakfire_compress
* data
= (struct pakfire_compress
*)p
;
980 // Fetch the file size
981 const size_t size
= pakfire_file_get_size(file
);
983 // Generate file metadata into an archive entry
984 entry
= pakfire_file_archive_entry(file
, data
->digests
);
990 // Perform search for hardlinks
991 archive_entry_linkify(data
->linkresolver
, &entry
, &sparse_entry
);
993 // Write the main entry
995 r
= __pakfire_compress_entry(pakfire
, file
, data
, entry
);
1000 // Write the sparse entry
1002 r
= __pakfire_compress_entry(pakfire
, file
, data
, sparse_entry
);
1007 // Query the link resolver for any more entries
1011 archive_entry_free(entry
);
1015 // Free the sparse entry
1017 archive_entry_free(sparse_entry
);
1018 sparse_entry
= NULL
;
1021 // Fetch the next entry
1022 archive_entry_linkify(data
->linkresolver
, &entry
, &sparse_entry
);
1026 // Write the entry to the archive
1027 r
= __pakfire_compress_entry(pakfire
, file
, data
, entry
);
1032 // Update the progress
1033 pakfire_progress_increment(data
->progress
, size
);
1037 archive_entry_free(entry
);
1039 archive_entry_free(sparse_entry
);
1044 int pakfire_compress(struct pakfire
* pakfire
, struct archive
* archive
,
1045 struct pakfire_filelist
* filelist
, const char* message
, int flags
, int digests
) {
1046 int progress_flags
=
1047 PAKFIRE_PROGRESS_SHOW_PERCENTAGE
|
1048 PAKFIRE_PROGRESS_SHOW_BYTES_TRANSFERRED
;
1051 struct pakfire_ctx
* ctx
= pakfire_ctx(pakfire
);
1053 struct pakfire_compress data
= {
1056 .filelist
= filelist
,
1061 // Should we show a progress bar?
1062 if (flags
& PAKFIRE_COMPRESS_NO_PROGRESS
)
1063 progress_flags
|= PAKFIRE_PROGRESS_NO_PROGRESS
;
1065 // Should we show throughput?
1066 if (flags
& PAKFIRE_COMPRESS_SHOW_THROUGHPUT
)
1067 progress_flags
|= PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED
;
1069 // Fetch the length of the filelist
1070 const size_t size
= pakfire_filelist_total_size(filelist
);
1072 // Create the progress indicator
1073 r
= pakfire_progress_create(&data
.progress
, ctx
, progress_flags
, NULL
);
1077 // Set progress title
1078 r
= pakfire_progress_set_title(data
.progress
, "%s", message
);
1083 r
= pakfire_progress_start(data
.progress
, size
);
1087 // Setup the link resolver
1088 data
.linkresolver
= archive_entry_linkresolver_new();
1089 if (!data
.linkresolver
) {
1090 ERROR(pakfire
, "Could not setup link resolver: m\n");
1094 // Set the link resolver strategy
1095 archive_entry_linkresolver_set_strategy(data
.linkresolver
, archive_format(archive
));
1097 // Walk through the entire filelist
1098 r
= pakfire_filelist_walk(filelist
, __pakfire_compress
, &data
, 0);
1102 // Finish the progress
1104 pakfire_progress_finish(data
.progress
);
1108 pakfire_progress_unref(data
.progress
);
1109 if (data
.linkresolver
)
1110 archive_entry_linkresolver_free(data
.linkresolver
);
1112 pakfire_ctx_unref(ctx
);
1117 int pakfire_compress_create_archive(struct pakfire
* pakfire
, struct archive
** archive
,
1118 FILE* f
, const enum pakfire_compressions compression
, const unsigned int level
) {
1119 struct archive
* a
= NULL
;
1123 // Open a new archive
1124 a
= archive_write_new();
1126 ERROR(pakfire
, "archive_write_new() failed\n");
1131 // Use the PAX format
1132 r
= archive_write_set_format_pax(a
);
1134 ERROR(pakfire
, "Could not set format to PAX: %s\n", archive_error_string(a
));
1138 // Store any extended attributes in the SCHILY headers
1139 r
= archive_write_set_format_option(a
, "pax", "xattrheader", "SCHILY");
1141 ERROR(pakfire
, "Could not set xattrheader option: %s\n", archive_error_string(a
));
1145 switch (compression
) {
1146 case PAKFIRE_COMPRESS_ZSTD
:
1148 r
= archive_write_add_filter_zstd(a
);
1150 ERROR(pakfire
, "Could not enable Zstandard compression: %s\n",
1151 archive_error_string(a
));
1155 // Do not pad the last block
1156 archive_write_set_bytes_in_last_block(a
, 1);
1158 // Set compression level
1160 r
= pakfire_string_format(value
, "%u", level
);
1164 r
= archive_write_set_filter_option(a
, NULL
, "compression-level", value
);
1166 ERROR(pakfire
, "Could not set Zstandard compression level: %s\n",
1167 archive_error_string(a
));
1172 #if ARCHIVE_VERSION_NUMBER >= 3006000
1173 // Fetch numbers of processors
1174 long processors
= sysconf(_SC_NPROCESSORS_ONLN
);
1176 if (processors
> 1) {
1177 r
= pakfire_string_format(value
, "%ld", processors
);
1179 ERROR(pakfire
, "Could not format threads: %m\n");
1183 // Try using multiple threads
1184 r
= archive_write_set_filter_option(a
, NULL
, "threads", value
);
1186 ERROR(pakfire
, "Could not enable %ld threads for compression: %s\n",
1187 processors
, archive_error_string(a
));
1194 // Write archive to f
1196 r
= archive_write_open_FILE(a
, f
);
1198 ERROR(pakfire
, "archive_write_open_FILE() failed: %s\n", archive_error_string(a
));
1210 archive_write_free(a
);