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 // Enable legacy logging
31 #define PAKFIRE_LEGACY_LOGGING
33 #include <pakfire/ctx.h>
34 #include <pakfire/compress.h>
35 #include <pakfire/file.h>
36 #include <pakfire/filelist.h>
37 #include <pakfire/logging.h>
38 #include <pakfire/path.h>
39 #include <pakfire/progress.h>
40 #include <pakfire/string.h>
41 #include <pakfire/util.h>
43 // Read up to N bytes for analyze the magic
44 #define MAX_MAGIC_LENGTH 6
46 // Compression/Decompression buffer size
47 #define BUFFER_SIZE 64 * 1024
50 #define XZ_COMPRESSION_LEVEL 6
53 #define ZSTD_COMPRESSION_LEVEL 7
55 const struct compressor
{
56 char magic
[MAX_MAGIC_LENGTH
];
58 FILE* (*open
)(FILE* f
, const char* mode
);
61 { { 0xFD, '7', 'z', 'X', 'Z', 0x00 }, 6, pakfire_xzfopen
, },
63 { { 0x28, 0xb5, 0x2f, 0xfd }, 4, pakfire_zstdfopen
, },
68 // Try to guess the compression
69 FILE* pakfire_xfopen(FILE* f
, const char* mode
) {
70 char buffer
[MAX_MAGIC_LENGTH
];
82 // This only works for reading files
91 int r
= fgetpos(f
, &pos
);
95 // Read a couple of bytes
96 size_t bytes_read
= fread(buffer
, 1, sizeof(buffer
), f
);
103 // Check if we could read anything
104 if (!bytes_read
|| bytes_read
< sizeof(buffer
))
108 for (const struct compressor
* c
= compressors
; c
->open
; c
++) {
109 // Check if we have read enough data
110 if (bytes_read
< c
->magic_length
)
113 // Compare the magic value
114 r
= memcmp(c
->magic
, buffer
, c
->magic_length
);
119 return c
->open(f
, mode
);
122 // Nothing seems to match
133 uint8_t buffer
[BUFFER_SIZE
];
136 static ssize_t
xz_read(void* data
, char* buffer
, size_t size
) {
137 struct xz_cookie
* cookie
= (struct xz_cookie
*)data
;
141 // Do not read when mode is "w"
142 if (cookie
->mode
== 'w')
145 lzma_action action
= LZMA_RUN
;
147 // Set output to allocated buffer
148 cookie
->stream
.next_out
= (uint8_t *)buffer
;
149 cookie
->stream
.avail_out
= size
;
152 // Read something when the input buffer is empty
153 if (cookie
->stream
.avail_in
== 0) {
154 cookie
->stream
.next_in
= cookie
->buffer
;
155 cookie
->stream
.avail_in
= fread(cookie
->buffer
,
156 1, sizeof(cookie
->buffer
), cookie
->f
);
158 // Break if the input file could not be read
159 if (ferror(cookie
->f
))
162 // Finish after we have reached the end of the input file
167 lzma_ret ret
= lzma_code(&cookie
->stream
, action
);
169 // If the stream has ended, we just send the
170 // remaining output and mark that we are done.
171 if (ret
== LZMA_STREAM_END
) {
173 return size
- cookie
->stream
.avail_out
;
176 // Break on all other unexpected errors
180 // When we have read enough to fill the entire output buffer, we return
181 if (cookie
->stream
.avail_out
== 0)
189 static ssize_t
xz_write(void* data
, const char* buffer
, size_t size
) {
190 struct xz_cookie
* cookie
= (struct xz_cookie
*)data
;
194 // Do not write when mode is "r"
195 if (cookie
->mode
== 'r')
198 // Return nothing when there is no input
202 // Set input to allocated buffer
203 cookie
->stream
.next_in
= (uint8_t *)buffer
;
204 cookie
->stream
.avail_in
= size
;
207 cookie
->stream
.next_out
= cookie
->buffer
;
208 cookie
->stream
.avail_out
= sizeof(cookie
->buffer
);
210 lzma_ret ret
= lzma_code(&cookie
->stream
, LZMA_RUN
);
214 size_t bytes_to_write
= sizeof(cookie
->buffer
) - cookie
->stream
.avail_out
;
215 if (bytes_to_write
) {
216 size_t bytes_written
= fwrite(cookie
->buffer
, 1, bytes_to_write
, cookie
->f
);
218 if (bytes_written
!= bytes_to_write
)
222 // Report that all data has been written
223 if (cookie
->stream
.avail_in
== 0)
228 static int xz_close(void* data
) {
229 struct xz_cookie
* cookie
= (struct xz_cookie
*)data
;
233 if (cookie
->mode
== 'w') {
235 cookie
->stream
.next_out
= cookie
->buffer
;
236 cookie
->stream
.avail_out
= sizeof(cookie
->buffer
);
238 lzma_ret ret
= lzma_code(&cookie
->stream
, LZMA_FINISH
);
239 if (ret
!= LZMA_OK
&& ret
!= LZMA_STREAM_END
)
242 size_t bytes_to_write
= sizeof(cookie
->buffer
) - cookie
->stream
.avail_out
;
243 if (bytes_to_write
) {
244 size_t bytes_written
= fwrite(cookie
->buffer
, 1, bytes_to_write
, cookie
->f
);
246 if (bytes_written
!= bytes_to_write
)
250 if (ret
== LZMA_STREAM_END
)
255 lzma_end(&cookie
->stream
);
258 int r
= fclose(cookie
->f
);
264 static cookie_io_functions_t xz_functions
= {
271 FILE* pakfire_xzfopen(FILE* f
, const char* mode
) {
284 struct xz_cookie
* cookie
= calloc(1, sizeof(*cookie
));
289 cookie
->mode
= *mode
;
291 switch (cookie
->mode
) {
293 ret
= lzma_stream_decoder(&cookie
->stream
, UINT64_MAX
, 0);
297 ret
= lzma_easy_encoder(&cookie
->stream
, XZ_COMPRESSION_LEVEL
, LZMA_CHECK_SHA256
);
308 return fopencookie(cookie
, mode
, xz_functions
);
323 ZSTD_CStream
* cstream
;
327 ZSTD_DStream
* dstream
;
330 uint8_t buffer
[BUFFER_SIZE
];
333 static ssize_t
zstd_read(void* data
, char* buffer
, size_t size
) {
334 struct zstd_cookie
* cookie
= (struct zstd_cookie
*)data
;
338 // Do not read when mode is "w"
339 if (cookie
->mode
== 'w')
347 // Configure output buffer
348 cookie
->out
.dst
= buffer
;
350 cookie
->out
.size
= size
;
353 if (!feof(cookie
->f
) && (cookie
->in
.pos
== cookie
->in
.size
)) {
355 cookie
->in
.size
= fread(cookie
->buffer
, 1, sizeof(cookie
->buffer
), cookie
->f
);
358 if (r
|| cookie
->in
.size
)
359 r
= ZSTD_decompressStream(cookie
->dstream
, &cookie
->out
, &cookie
->in
);
361 if (r
== 0 && feof(cookie
->f
)) {
363 return cookie
->out
.pos
;
370 if (cookie
->out
.pos
== size
)
375 static ssize_t
zstd_write(void* data
, const char* buffer
, size_t size
) {
376 struct zstd_cookie
* cookie
= (struct zstd_cookie
*)data
;
380 // Do not write when mode is "r"
381 if (cookie
->mode
== 'r')
384 // Return nothing when there is no input
388 // Configure input buffer
389 cookie
->in
.src
= buffer
;
391 cookie
->in
.size
= size
;
396 size_t r
= ZSTD_compressStream(cookie
->cstream
, &cookie
->out
, &cookie
->in
);
400 if (cookie
->out
.pos
> 0) {
401 size_t bytes_written
= fwrite(cookie
->buffer
, 1, cookie
->out
.pos
, cookie
->f
);
403 if (bytes_written
!= cookie
->out
.pos
)
407 // Return when all input has been written
408 if (cookie
->in
.pos
== size
)
413 static int zstd_close(void* data
) {
414 struct zstd_cookie
* cookie
= (struct zstd_cookie
*)data
;
418 if (cookie
->mode
== 'w') {
420 // Reset output buffer
423 size_t r
= ZSTD_endStream(cookie
->cstream
, &cookie
->out
);
427 if (cookie
->out
.pos
> 0) {
428 size_t bytes_written
= fwrite(cookie
->buffer
, 1, cookie
->out
.pos
, cookie
->f
);
430 if (bytes_written
!= cookie
->out
.pos
)
439 int r
= fclose(cookie
->f
);
443 ZSTD_freeCStream(cookie
->cstream
);
445 ZSTD_freeDStream(cookie
->dstream
);
451 static cookie_io_functions_t zstd_functions
= {
458 FILE* pakfire_zstdfopen(FILE* f
, const char* mode
) {
469 struct zstd_cookie
* cookie
= calloc(1, sizeof(*cookie
));
474 cookie
->mode
= *mode
;
477 switch (cookie
->mode
) {
480 cookie
->dstream
= ZSTD_createDStream();
481 if (!cookie
->dstream
)
485 r
= ZSTD_initDStream(cookie
->dstream
);
489 cookie
->in
.src
= cookie
->buffer
;
496 cookie
->cstream
= ZSTD_createCStream();
497 if (!cookie
->cstream
)
501 r
= ZSTD_initCStream(cookie
->cstream
, ZSTD_COMPRESSION_LEVEL
);
505 cookie
->out
.dst
= cookie
->buffer
;
507 cookie
->out
.size
= sizeof(cookie
->buffer
);
515 return fopencookie(cookie
, mode
, zstd_functions
);
519 ZSTD_freeCStream(cookie
->cstream
);
521 ZSTD_freeDStream(cookie
->dstream
);
528 Helper function to conditionally walk through an archive
529 and perform actions based on the callback.
531 int pakfire_walk(struct pakfire_ctx
* ctx
, struct archive
* archive
,
532 pakfire_walk_callback callback
, pakfire_walk_filter_callback filter_callback
,
534 struct archive_entry
* entry
= NULL
;
537 // Walk through the archive
539 r
= archive_read_next_header(archive
, &entry
);
541 // Handle the return code
543 // Fall through if everything is okay
547 // Return OK when we reached the end of the archive
551 // Raise any other errors
556 // Call the filter callback before we call the actual callback
557 if (filter_callback
) {
558 r
= filter_callback(ctx
, archive
, entry
, p
);
560 // Handle the return code
562 case PAKFIRE_WALK_OK
:
565 case PAKFIRE_WALK_END
:
566 CTX_DEBUG(ctx
, "Filter callback sent END\n");
569 case PAKFIRE_WALK_SKIP
:
570 CTX_DEBUG(ctx
, "Filter callback sent SKIP\n");
573 case PAKFIRE_WALK_DONE
:
574 CTX_DEBUG(ctx
, "Filter callback sent DONE\n");
576 // Clear the callback function
577 filter_callback
= NULL
;
580 case PAKFIRE_WALK_AGAIN
:
581 CTX_DEBUG(ctx
, "Filter callback sent AGAIN\n");
584 // Raise any other errors
586 CTX_DEBUG(ctx
, "Filter callback returned an error: %d\n", r
);
593 r
= callback(ctx
, archive
, entry
, p
);
595 // Handle the return code
597 case PAKFIRE_WALK_OK
:
600 case PAKFIRE_WALK_DONE
:
601 CTX_DEBUG(ctx
, "Callback sent DONE\n");
604 // Raise any other errors
616 struct pakfire_extract
{
617 // Reference to Pakfire
618 struct pakfire
* pakfire
;
623 // The archive to extract
624 struct archive
* archive
;
626 // The filelist of all extracted files
627 struct pakfire_filelist
* filelist
;
629 // Prepend this prefix
633 struct archive
* writer
;
635 // The progress indicator
636 struct pakfire_progress
* progress
;
639 static void pakfire_extract_progress(void* p
) {
640 struct pakfire_extract
* data
= (struct pakfire_extract
*)p
;
642 // Fetch how many bytes have been read
643 const size_t position
= archive_filter_bytes(data
->archive
, -1);
646 pakfire_progress_update(data
->progress
, position
);
649 static int __pakfire_extract(struct pakfire_ctx
* ctx
, struct archive
* a
,
650 struct archive_entry
* entry
, void* p
) {
651 struct pakfire_file
* file
= NULL
;
652 struct vfs_cap_data cap_data
= {};
653 char buffer
[PATH_MAX
];
656 struct pakfire_extract
* data
= (struct pakfire_extract
*)p
;
659 const char* path
= archive_entry_pathname(entry
);
661 // Make sure we have a leading slash on the filelist
662 if (!pakfire_string_startswith(path
, "/")) {
663 r
= pakfire_string_format(buffer
, "/%s", path
);
667 // Store the new name
668 archive_entry_set_pathname(entry
, buffer
);
670 // Update the path pointer
671 path
= archive_entry_pathname(entry
);
674 // Generate a file object
675 r
= pakfire_file_create_from_archive_entry(&file
, data
->pakfire
, entry
);
679 // Add entry to filelist (if requested)
680 if (data
->filelist
) {
681 // Append the file to the list
682 r
= pakfire_filelist_add(data
->filelist
, file
);
687 const int configfile
= pakfire_file_has_flag(file
, PAKFIRE_FILE_CONFIG
);
689 // Prepend the prefix
692 r
= pakfire_path_append(buffer
, data
->prefix
, path
);
694 CTX_ERROR(ctx
, "Could not compose file path: %m\n");
699 archive_entry_set_pathname(entry
, buffer
);
701 // Update hardlink destination
702 const char* link
= archive_entry_hardlink(entry
);
704 r
= pakfire_path_append(buffer
, data
->prefix
, link
);
706 CTX_ERROR(ctx
, "Could not compose hardlink path: %m\n");
711 archive_entry_set_hardlink(entry
, buffer
);
716 // Fetch path again since we changed it
717 path
= archive_entry_pathname(entry
);
719 if (pakfire_path_exists(path
)) {
720 CTX_DEBUG(ctx
, "The configuration file %s exists\n",
721 pakfire_file_get_path(file
));
723 r
= pakfire_string_format(buffer
, "%s.paknew", path
);
725 CTX_ERROR(ctx
, "Could not compose path for configuration file: %m\n");
729 // Set the path again
730 archive_entry_set_pathname(entry
, buffer
);
734 // Create file & extract payload
736 // Fetch path again since we changed it
737 path
= archive_entry_pathname(entry
);
739 CTX_DEBUG(ctx
, "Extracting %s\n", path
);
741 // Remove any extended attributes which we never write to disk
742 archive_entry_xattr_clear(entry
);
745 if (pakfire_file_has_caps(file
)) {
746 r
= pakfire_file_write_fcaps(file
, &cap_data
);
750 // Store capabilities in archive entry
751 archive_entry_xattr_add_entry(entry
, "security.capability",
752 &cap_data
, sizeof(cap_data
));
756 r
= archive_read_extract2(data
->archive
, entry
, data
->writer
);
763 CTX_ERROR(ctx
, "%s\n", archive_error_string(data
->writer
));
765 // Pretend everything has been okay
770 CTX_ERROR(ctx
, "%s\n", archive_error_string(data
->writer
));
778 pakfire_file_unref(file
);
783 int pakfire_extract(struct pakfire
* pakfire
, struct archive
* archive
,
784 size_t size
, struct pakfire_filelist
* filelist
,
785 const char* prefix
, const char* message
,
786 pakfire_walk_filter_callback filter_callback
, int flags
) {
787 int progress_flags
= PAKFIRE_PROGRESS_SHOW_PERCENTAGE
;
790 struct pakfire_ctx
* ctx
= pakfire_ctx(pakfire
);
792 // Use / if no prefix is set
796 struct pakfire_extract data
= {
799 .filelist
= filelist
,
805 // Is this a dry run?
806 const int dry_run
= flags
& PAKFIRE_EXTRACT_DRY_RUN
;
810 data
.writer
= pakfire_make_archive_disk_writer(pakfire
, 1);
812 ERROR(pakfire
, "Could not create disk writer: %m\n");
818 // Should we show any progress?
819 if (flags
& PAKFIRE_EXTRACT_NO_PROGRESS
)
820 progress_flags
|= PAKFIRE_PROGRESS_NO_PROGRESS
;
823 if (flags
& PAKFIRE_EXTRACT_SHOW_THROUGHPUT
)
824 progress_flags
|= PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED
;
826 // Create the progress indicator
827 r
= pakfire_progress_create(&data
.progress
, ctx
, progress_flags
, NULL
);
832 r
= pakfire_progress_set_title(data
.progress
, "%s", message
);
836 // Register progress callback
837 archive_read_extract_set_progress_callback(data
.archive
,
838 pakfire_extract_progress
, &data
);
841 r
= pakfire_progress_start(data
.progress
, size
);
845 // Walk through the entire archive
846 r
= pakfire_walk(ctx
, archive
, __pakfire_extract
, filter_callback
, &data
);
850 // Finish the progress
851 r
= pakfire_progress_finish(data
.progress
);
857 pakfire_progress_unref(data
.progress
);
859 archive_write_free(data
.writer
);
861 pakfire_ctx_unref(ctx
);
866 // Common compression
868 struct pakfire_compress
{
869 // Reference to Pakfire
870 struct pakfire
* pakfire
;
875 // The archive to write to
876 struct archive
* archive
;
878 // Resolver for hardlinks
879 struct archive_entry_linkresolver
* linkresolver
;
881 // The filelist of all files to write
882 struct pakfire_filelist
* filelist
;
884 // The progress indicator
885 struct pakfire_progress
* progress
;
887 // Digests to write to the archive
891 static int pakfire_copy_data_from_file(struct pakfire
* pakfire
,
892 struct archive
* archive
, FILE* f
) {
893 char buffer
[BUFFER_SIZE
];
895 ssize_t bytes_read
= 0;
896 ssize_t bytes_written
= 0;
898 // Read file from the very beginning - also allows calling this multiple times
901 // Loop through the entire length of the file
903 // Read a block from file
904 bytes_read
= fread(buffer
, 1, sizeof(buffer
), f
);
906 // Check if any error occured
908 ERROR(pakfire
, "Read error: %m\n");
912 // Write the block to the archive
913 bytes_written
= archive_write_data(archive
, buffer
, bytes_read
);
914 if (bytes_written
< bytes_read
) {
915 ERROR(pakfire
, "Write error: %s\n", archive_error_string(archive
));
923 static int __pakfire_compress_entry(struct pakfire
* pakfire
, struct pakfire_file
* file
,
924 struct pakfire_compress
* data
, struct archive_entry
* entry
) {
928 const char* path
= archive_entry_pathname(entry
);
930 // Remove any leading slahes
934 archive_entry_set_pathname(entry
, path
);
937 r
= archive_write_header(data
->archive
, entry
);
939 ERROR(pakfire
, "Error writing file header: %s\n",
940 archive_error_string(data
->archive
));
944 // Copy the data if there is any
945 if (archive_entry_size(entry
)) {
947 f
= pakfire_file_open(file
);
953 // Copy the payload into the archive
954 r
= pakfire_copy_data_from_file(pakfire
, data
->archive
, f
);
960 r
= archive_write_finish_entry(data
->archive
);
971 static int __pakfire_compress(struct pakfire
* pakfire
, struct pakfire_file
* file
, void* p
) {
972 struct archive_entry
* entry
= NULL
;
973 struct archive_entry
* sparse_entry
= NULL
;
976 struct pakfire_compress
* data
= (struct pakfire_compress
*)p
;
978 // Fetch the file size
979 const size_t size
= pakfire_file_get_size(file
);
981 // Generate file metadata into an archive entry
982 entry
= pakfire_file_archive_entry(file
, data
->digests
);
988 // Perform search for hardlinks
989 archive_entry_linkify(data
->linkresolver
, &entry
, &sparse_entry
);
991 // Write the main entry
993 r
= __pakfire_compress_entry(pakfire
, file
, data
, entry
);
998 // Write the sparse entry
1000 r
= __pakfire_compress_entry(pakfire
, file
, data
, sparse_entry
);
1005 // Query the link resolver for any more entries
1009 archive_entry_free(entry
);
1013 // Free the sparse entry
1015 archive_entry_free(sparse_entry
);
1016 sparse_entry
= NULL
;
1019 // Fetch the next entry
1020 archive_entry_linkify(data
->linkresolver
, &entry
, &sparse_entry
);
1024 // Write the entry to the archive
1025 r
= __pakfire_compress_entry(pakfire
, file
, data
, entry
);
1030 // Update the progress
1031 pakfire_progress_increment(data
->progress
, size
);
1035 archive_entry_free(entry
);
1037 archive_entry_free(sparse_entry
);
1042 int pakfire_compress(struct pakfire
* pakfire
, struct archive
* archive
,
1043 struct pakfire_filelist
* filelist
, const char* message
, int flags
, int digests
) {
1044 int progress_flags
=
1045 PAKFIRE_PROGRESS_SHOW_PERCENTAGE
|
1046 PAKFIRE_PROGRESS_SHOW_BYTES_TRANSFERRED
;
1049 struct pakfire_ctx
* ctx
= pakfire_ctx(pakfire
);
1051 struct pakfire_compress data
= {
1054 .filelist
= filelist
,
1059 // Should we show a progress bar?
1060 if (flags
& PAKFIRE_COMPRESS_NO_PROGRESS
)
1061 progress_flags
|= PAKFIRE_PROGRESS_NO_PROGRESS
;
1063 // Should we show throughput?
1064 if (flags
& PAKFIRE_COMPRESS_SHOW_THROUGHPUT
)
1065 progress_flags
|= PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED
;
1067 // Fetch the length of the filelist
1068 const size_t size
= pakfire_filelist_total_size(filelist
);
1070 // Create the progress indicator
1071 r
= pakfire_progress_create(&data
.progress
, ctx
, progress_flags
, NULL
);
1075 // Set progress title
1076 r
= pakfire_progress_set_title(data
.progress
, "%s", message
);
1081 r
= pakfire_progress_start(data
.progress
, size
);
1085 // Setup the link resolver
1086 data
.linkresolver
= archive_entry_linkresolver_new();
1087 if (!data
.linkresolver
) {
1088 ERROR(pakfire
, "Could not setup link resolver: m\n");
1092 // Set the link resolver strategy
1093 archive_entry_linkresolver_set_strategy(data
.linkresolver
, archive_format(archive
));
1095 // Walk through the entire filelist
1096 r
= pakfire_filelist_walk(filelist
, __pakfire_compress
, &data
, 0);
1100 // Finish the progress
1102 pakfire_progress_finish(data
.progress
);
1106 pakfire_progress_unref(data
.progress
);
1107 if (data
.linkresolver
)
1108 archive_entry_linkresolver_free(data
.linkresolver
);
1110 pakfire_ctx_unref(ctx
);
1115 int pakfire_compress_create_archive(struct pakfire
* pakfire
, struct archive
** archive
,
1116 FILE* f
, const enum pakfire_compressions compression
, const unsigned int level
) {
1117 struct archive
* a
= NULL
;
1121 // Open a new archive
1122 a
= archive_write_new();
1124 ERROR(pakfire
, "archive_write_new() failed\n");
1129 // Use the PAX format
1130 r
= archive_write_set_format_pax(a
);
1132 ERROR(pakfire
, "Could not set format to PAX: %s\n", archive_error_string(a
));
1136 // Store any extended attributes in the SCHILY headers
1137 r
= archive_write_set_format_option(a
, "pax", "xattrheader", "SCHILY");
1139 ERROR(pakfire
, "Could not set xattrheader option: %s\n", archive_error_string(a
));
1143 switch (compression
) {
1144 case PAKFIRE_COMPRESS_ZSTD
:
1146 r
= archive_write_add_filter_zstd(a
);
1148 ERROR(pakfire
, "Could not enable Zstandard compression: %s\n",
1149 archive_error_string(a
));
1153 // Do not pad the last block
1154 archive_write_set_bytes_in_last_block(a
, 1);
1156 // Set compression level
1158 r
= pakfire_string_format(value
, "%u", level
);
1162 r
= archive_write_set_filter_option(a
, NULL
, "compression-level", value
);
1164 ERROR(pakfire
, "Could not set Zstandard compression level: %s\n",
1165 archive_error_string(a
));
1170 #if ARCHIVE_VERSION_NUMBER >= 3006000
1171 // Fetch numbers of processors
1172 long processors
= sysconf(_SC_NPROCESSORS_ONLN
);
1174 if (processors
> 1) {
1175 r
= pakfire_string_format(value
, "%ld", processors
);
1177 ERROR(pakfire
, "Could not format threads: %m\n");
1181 // Try using multiple threads
1182 r
= archive_write_set_filter_option(a
, NULL
, "threads", value
);
1184 ERROR(pakfire
, "Could not enable %ld threads for compression: %s\n",
1185 processors
, archive_error_string(a
));
1192 // Write archive to f
1194 r
= archive_write_open_FILE(a
, f
);
1196 ERROR(pakfire
, "archive_write_open_FILE() failed: %s\n", archive_error_string(a
));
1208 archive_write_free(a
);