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
;
534 // Walk through the archive
536 r
= archive_read_next_header(archive
, &entry
);
538 // Handle the return code
540 // Fall through if everything is okay
544 // Return OK when we reached the end of the archive
548 // Raise any other errors
553 // Call the filter callback before we call the actual callback
554 if (filter_callback
) {
555 r
= filter_callback(ctx
, archive
, entry
, p
);
557 // Handle the return code
559 case PAKFIRE_WALK_OK
:
562 case PAKFIRE_WALK_END
:
563 CTX_DEBUG(ctx
, "Filter callback sent END\n");
566 case PAKFIRE_WALK_SKIP
:
567 CTX_DEBUG(ctx
, "Filter callback sent SKIP\n");
570 case PAKFIRE_WALK_DONE
:
571 CTX_DEBUG(ctx
, "Filter callback sent DONE\n");
573 // Clear the callback function
574 filter_callback
= NULL
;
577 case PAKFIRE_WALK_AGAIN
:
578 CTX_DEBUG(ctx
, "Filter callback sent AGAIN\n");
581 // Raise any other errors
583 CTX_DEBUG(ctx
, "Filter callback returned an error: %d\n", r
);
590 r
= callback(ctx
, archive
, entry
, p
);
592 // Handle the return code
594 case PAKFIRE_WALK_OK
:
597 case PAKFIRE_WALK_DONE
:
598 CTX_DEBUG(ctx
, "Callback sent DONE\n");
601 // Raise any other errors
613 struct pakfire_extract
{
614 // Reference to Pakfire
615 struct pakfire
* pakfire
;
620 // The archive to extract
621 struct archive
* archive
;
623 // The filelist of all extracted files
624 struct pakfire_filelist
* filelist
;
626 // Prepend this prefix
630 struct archive
* writer
;
632 // The progress indicator
633 struct pakfire_progress
* progress
;
636 static void pakfire_extract_progress(void* p
) {
637 struct pakfire_extract
* data
= (struct pakfire_extract
*)p
;
639 // Fetch how many bytes have been read
640 const size_t position
= archive_filter_bytes(data
->archive
, -1);
643 pakfire_progress_update(data
->progress
, position
);
646 static int __pakfire_extract(struct pakfire_ctx
* ctx
, struct archive
* a
,
647 struct archive_entry
* entry
, void* p
) {
648 struct pakfire_file
* file
= NULL
;
649 struct vfs_cap_data cap_data
= {};
650 char buffer
[PATH_MAX
];
653 struct pakfire_extract
* data
= (struct pakfire_extract
*)p
;
656 const char* path
= archive_entry_pathname(entry
);
658 // Make sure we have a leading slash on the filelist
659 if (!pakfire_string_startswith(path
, "/")) {
660 r
= pakfire_string_format(buffer
, "/%s", path
);
664 // Store the new name
665 archive_entry_set_pathname(entry
, buffer
);
667 // Update the path pointer
668 path
= archive_entry_pathname(entry
);
671 // Generate a file object
672 r
= pakfire_file_create_from_archive_entry(&file
, data
->pakfire
, entry
);
676 // Add entry to filelist (if requested)
677 if (data
->filelist
) {
678 // Append the file to the list
679 r
= pakfire_filelist_add(data
->filelist
, file
);
684 const int configfile
= pakfire_file_has_flag(file
, PAKFIRE_FILE_CONFIG
);
686 // Prepend the prefix
689 r
= pakfire_path_append(buffer
, data
->prefix
, path
);
691 CTX_ERROR(ctx
, "Could not compose file path: %m\n");
696 archive_entry_set_pathname(entry
, buffer
);
698 // Update hardlink destination
699 const char* link
= archive_entry_hardlink(entry
);
701 r
= pakfire_path_append(buffer
, data
->prefix
, link
);
703 CTX_ERROR(ctx
, "Could not compose hardlink path: %m\n");
708 archive_entry_set_hardlink(entry
, buffer
);
713 // Fetch path again since we changed it
714 path
= archive_entry_pathname(entry
);
716 if (pakfire_path_exists(path
)) {
717 CTX_DEBUG(ctx
, "The configuration file %s exists\n",
718 pakfire_file_get_path(file
));
720 r
= pakfire_string_format(buffer
, "%s.paknew", path
);
722 CTX_ERROR(ctx
, "Could not compose path for configuration file: %m\n");
726 // Set the path again
727 archive_entry_set_pathname(entry
, buffer
);
731 // Create file & extract payload
733 // Fetch path again since we changed it
734 path
= archive_entry_pathname(entry
);
736 CTX_DEBUG(ctx
, "Extracting %s\n", path
);
738 // Remove any extended attributes which we never write to disk
739 archive_entry_xattr_clear(entry
);
742 if (pakfire_file_has_caps(file
)) {
743 r
= pakfire_file_write_fcaps(file
, &cap_data
);
747 // Store capabilities in archive entry
748 archive_entry_xattr_add_entry(entry
, "security.capability",
749 &cap_data
, sizeof(cap_data
));
753 r
= archive_read_extract2(data
->archive
, entry
, data
->writer
);
760 CTX_ERROR(ctx
, "%s\n", archive_error_string(data
->writer
));
762 // Pretend everything has been okay
767 CTX_ERROR(ctx
, "%s\n", archive_error_string(data
->writer
));
775 pakfire_file_unref(file
);
780 int pakfire_extract(struct pakfire
* pakfire
, struct archive
* archive
,
781 size_t size
, struct pakfire_filelist
* filelist
,
782 const char* prefix
, const char* message
,
783 pakfire_walk_filter_callback filter_callback
, int flags
) {
784 int progress_flags
= PAKFIRE_PROGRESS_SHOW_PERCENTAGE
;
787 struct pakfire_ctx
* ctx
= pakfire_ctx(pakfire
);
789 // Use / if no prefix is set
793 struct pakfire_extract data
= {
796 .filelist
= filelist
,
802 // Is this a dry run?
803 const int dry_run
= flags
& PAKFIRE_EXTRACT_DRY_RUN
;
807 data
.writer
= pakfire_make_archive_disk_writer(pakfire
, 1);
809 ERROR(pakfire
, "Could not create disk writer: %m\n");
815 // Should we show any progress?
816 if (flags
& PAKFIRE_EXTRACT_NO_PROGRESS
)
817 progress_flags
|= PAKFIRE_PROGRESS_NO_PROGRESS
;
820 if (flags
& PAKFIRE_EXTRACT_SHOW_THROUGHPUT
)
821 progress_flags
|= PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED
;
823 // Create the progress indicator
824 r
= pakfire_progress_create(&data
.progress
, ctx
, progress_flags
, NULL
);
829 r
= pakfire_progress_set_title(data
.progress
, "%s", message
);
833 // Register progress callback
834 archive_read_extract_set_progress_callback(data
.archive
,
835 pakfire_extract_progress
, &data
);
838 r
= pakfire_progress_start(data
.progress
, size
);
842 // Walk through the entire archive
843 r
= pakfire_walk(ctx
, archive
, __pakfire_extract
, filter_callback
, &data
);
847 // Finish the progress
848 r
= pakfire_progress_finish(data
.progress
);
854 pakfire_progress_unref(data
.progress
);
856 archive_write_free(data
.writer
);
858 pakfire_ctx_unref(ctx
);
863 // Common compression
865 struct pakfire_compress
{
866 // Reference to Pakfire
867 struct pakfire
* pakfire
;
872 // The archive to write to
873 struct archive
* archive
;
875 // Resolver for hardlinks
876 struct archive_entry_linkresolver
* linkresolver
;
878 // The filelist of all files to write
879 struct pakfire_filelist
* filelist
;
881 // The progress indicator
882 struct pakfire_progress
* progress
;
884 // Digests to write to the archive
888 static int pakfire_copy_data_from_file(struct pakfire
* pakfire
,
889 struct archive
* archive
, FILE* f
) {
890 char buffer
[BUFFER_SIZE
];
892 ssize_t bytes_read
= 0;
893 ssize_t bytes_written
= 0;
895 // Read file from the very beginning - also allows calling this multiple times
898 // Loop through the entire length of the file
900 // Read a block from file
901 bytes_read
= fread(buffer
, 1, sizeof(buffer
), f
);
903 // Check if any error occured
905 ERROR(pakfire
, "Read error: %m\n");
909 // Write the block to the archive
910 bytes_written
= archive_write_data(archive
, buffer
, bytes_read
);
911 if (bytes_written
< bytes_read
) {
912 ERROR(pakfire
, "Write error: %s\n", archive_error_string(archive
));
920 static int __pakfire_compress_entry(struct pakfire
* pakfire
, struct pakfire_file
* file
,
921 struct pakfire_compress
* data
, struct archive_entry
* entry
) {
925 const char* path
= archive_entry_pathname(entry
);
927 // Remove any leading slahes
931 archive_entry_set_pathname(entry
, path
);
934 r
= archive_write_header(data
->archive
, entry
);
936 ERROR(pakfire
, "Error writing file header: %s\n",
937 archive_error_string(data
->archive
));
941 // Copy the data if there is any
942 if (archive_entry_size(entry
)) {
944 f
= pakfire_file_open(file
);
950 // Copy the payload into the archive
951 r
= pakfire_copy_data_from_file(pakfire
, data
->archive
, f
);
957 r
= archive_write_finish_entry(data
->archive
);
968 static int __pakfire_compress(struct pakfire
* pakfire
, struct pakfire_file
* file
, void* p
) {
969 struct archive_entry
* entry
= NULL
;
970 struct archive_entry
* sparse_entry
= NULL
;
973 struct pakfire_compress
* data
= (struct pakfire_compress
*)p
;
975 // Fetch the file size
976 const size_t size
= pakfire_file_get_size(file
);
978 // Generate file metadata into an archive entry
979 entry
= pakfire_file_archive_entry(file
, data
->digests
);
985 // Perform search for hardlinks
986 archive_entry_linkify(data
->linkresolver
, &entry
, &sparse_entry
);
988 // Write the main entry
990 r
= __pakfire_compress_entry(pakfire
, file
, data
, entry
);
995 // Write the sparse entry
997 r
= __pakfire_compress_entry(pakfire
, file
, data
, sparse_entry
);
1002 // Query the link resolver for any more entries
1006 archive_entry_free(entry
);
1010 // Free the sparse entry
1012 archive_entry_free(sparse_entry
);
1013 sparse_entry
= NULL
;
1016 // Fetch the next entry
1017 archive_entry_linkify(data
->linkresolver
, &entry
, &sparse_entry
);
1021 // Write the entry to the archive
1022 r
= __pakfire_compress_entry(pakfire
, file
, data
, entry
);
1027 // Update the progress
1028 pakfire_progress_increment(data
->progress
, size
);
1032 archive_entry_free(entry
);
1034 archive_entry_free(sparse_entry
);
1039 int pakfire_compress(struct pakfire
* pakfire
, struct archive
* archive
,
1040 struct pakfire_filelist
* filelist
, const char* message
, int flags
, int digests
) {
1041 int progress_flags
=
1042 PAKFIRE_PROGRESS_SHOW_PERCENTAGE
|
1043 PAKFIRE_PROGRESS_SHOW_BYTES_TRANSFERRED
;
1046 struct pakfire_ctx
* ctx
= pakfire_ctx(pakfire
);
1048 struct pakfire_compress data
= {
1051 .filelist
= filelist
,
1056 // Should we show a progress bar?
1057 if (flags
& PAKFIRE_COMPRESS_NO_PROGRESS
)
1058 progress_flags
|= PAKFIRE_PROGRESS_NO_PROGRESS
;
1060 // Should we show throughput?
1061 if (flags
& PAKFIRE_COMPRESS_SHOW_THROUGHPUT
)
1062 progress_flags
|= PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED
;
1064 // Fetch the length of the filelist
1065 const size_t size
= pakfire_filelist_total_size(filelist
);
1067 // Create the progress indicator
1068 r
= pakfire_progress_create(&data
.progress
, ctx
, progress_flags
, NULL
);
1072 // Set progress title
1073 r
= pakfire_progress_set_title(data
.progress
, "%s", message
);
1078 r
= pakfire_progress_start(data
.progress
, size
);
1082 // Setup the link resolver
1083 data
.linkresolver
= archive_entry_linkresolver_new();
1084 if (!data
.linkresolver
) {
1085 ERROR(pakfire
, "Could not setup link resolver: m\n");
1089 // Set the link resolver strategy
1090 archive_entry_linkresolver_set_strategy(data
.linkresolver
, archive_format(archive
));
1092 // Walk through the entire filelist
1093 r
= pakfire_filelist_walk(filelist
, __pakfire_compress
, &data
, 0);
1097 // Finish the progress
1099 pakfire_progress_finish(data
.progress
);
1103 pakfire_progress_unref(data
.progress
);
1104 if (data
.linkresolver
)
1105 archive_entry_linkresolver_free(data
.linkresolver
);
1107 pakfire_ctx_unref(ctx
);
1112 int pakfire_compress_create_archive(struct pakfire
* pakfire
, struct archive
** archive
,
1113 FILE* f
, const enum pakfire_compressions compression
, const unsigned int level
) {
1114 struct archive
* a
= NULL
;
1118 // Open a new archive
1119 a
= archive_write_new();
1121 ERROR(pakfire
, "archive_write_new() failed\n");
1126 // Use the PAX format
1127 r
= archive_write_set_format_pax(a
);
1129 ERROR(pakfire
, "Could not set format to PAX: %s\n", archive_error_string(a
));
1133 // Store any extended attributes in the SCHILY headers
1134 r
= archive_write_set_format_option(a
, "pax", "xattrheader", "SCHILY");
1136 ERROR(pakfire
, "Could not set xattrheader option: %s\n", archive_error_string(a
));
1140 switch (compression
) {
1141 case PAKFIRE_COMPRESS_ZSTD
:
1143 r
= archive_write_add_filter_zstd(a
);
1145 ERROR(pakfire
, "Could not enable Zstandard compression: %s\n",
1146 archive_error_string(a
));
1150 // Do not pad the last block
1151 archive_write_set_bytes_in_last_block(a
, 1);
1153 // Set compression level
1155 r
= pakfire_string_format(value
, "%u", level
);
1159 r
= archive_write_set_filter_option(a
, NULL
, "compression-level", value
);
1161 ERROR(pakfire
, "Could not set Zstandard compression level: %s\n",
1162 archive_error_string(a
));
1167 #if ARCHIVE_VERSION_NUMBER >= 3006000
1168 // Fetch numbers of processors
1169 long processors
= sysconf(_SC_NPROCESSORS_ONLN
);
1171 if (processors
> 1) {
1172 r
= pakfire_string_format(value
, "%ld", processors
);
1174 ERROR(pakfire
, "Could not format threads: %m\n");
1178 // Try using multiple threads
1179 r
= archive_write_set_filter_option(a
, NULL
, "threads", value
);
1181 ERROR(pakfire
, "Could not enable %ld threads for compression: %s\n",
1182 processors
, archive_error_string(a
));
1189 // Write archive to f
1191 r
= archive_write_open_FILE(a
, f
);
1193 ERROR(pakfire
, "archive_write_open_FILE() failed: %s\n", archive_error_string(a
));
1205 archive_write_free(a
);