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/compress.h>
31 #include <pakfire/file.h>
32 #include <pakfire/filelist.h>
33 #include <pakfire/logging.h>
34 #include <pakfire/progressbar.h>
35 #include <pakfire/string.h>
36 #include <pakfire/util.h>
38 // Read up to N bytes for analyze the magic
39 #define MAX_MAGIC_LENGTH 6
41 // Compression/Decompression buffer size
42 #define BUFFER_SIZE 64 * 1024
45 #define XZ_COMPRESSION_LEVEL 6
48 #define ZSTD_COMPRESSION_LEVEL 7
50 const struct compressor
{
51 char magic
[MAX_MAGIC_LENGTH
];
53 FILE* (*open
)(FILE* f
, const char* mode
);
56 { { 0xFD, '7', 'z', 'X', 'Z', 0x00 }, 6, pakfire_xzfopen
, },
58 { { 0x28, 0xb5, 0x2f, 0xfd }, 4, pakfire_zstdfopen
, },
63 // Try to guess the compression
64 FILE* pakfire_xfopen(FILE* f
, const char* mode
) {
65 char buffer
[MAX_MAGIC_LENGTH
];
77 // This only works for reading files
86 int r
= fgetpos(f
, &pos
);
90 // Read a couple of bytes
91 size_t bytes_read
= fread(buffer
, 1, sizeof(buffer
), f
);
98 // Check if we could read anything
99 if (!bytes_read
|| bytes_read
< sizeof(buffer
))
103 for (const struct compressor
* c
= compressors
; c
->open
; c
++) {
104 // Check if we have read enough data
105 if (bytes_read
< c
->magic_length
)
108 // Compare the magic value
109 r
= memcmp(c
->magic
, buffer
, c
->magic_length
);
114 return c
->open(f
, mode
);
117 // Nothing seems to match
128 uint8_t buffer
[BUFFER_SIZE
];
131 static ssize_t
xz_read(void* data
, char* buffer
, size_t size
) {
132 struct xz_cookie
* cookie
= (struct xz_cookie
*)data
;
136 // Do not read when mode is "w"
137 if (cookie
->mode
== 'w')
140 lzma_action action
= LZMA_RUN
;
142 // Set output to allocated buffer
143 cookie
->stream
.next_out
= (uint8_t *)buffer
;
144 cookie
->stream
.avail_out
= size
;
147 // Read something when the input buffer is empty
148 if (cookie
->stream
.avail_in
== 0) {
149 cookie
->stream
.next_in
= cookie
->buffer
;
150 cookie
->stream
.avail_in
= fread(cookie
->buffer
,
151 1, sizeof(cookie
->buffer
), cookie
->f
);
153 // Break if the input file could not be read
154 if (ferror(cookie
->f
))
157 // Finish after we have reached the end of the input file
162 lzma_ret ret
= lzma_code(&cookie
->stream
, action
);
164 // If the stream has ended, we just send the
165 // remaining output and mark that we are done.
166 if (ret
== LZMA_STREAM_END
) {
168 return size
- cookie
->stream
.avail_out
;
171 // Break on all other unexpected errors
175 // When we have read enough to fill the entire output buffer, we return
176 if (cookie
->stream
.avail_out
== 0)
184 static ssize_t
xz_write(void* data
, const char* buffer
, size_t size
) {
185 struct xz_cookie
* cookie
= (struct xz_cookie
*)data
;
189 // Do not write when mode is "r"
190 if (cookie
->mode
== 'r')
193 // Return nothing when there is no input
197 // Set input to allocated buffer
198 cookie
->stream
.next_in
= (uint8_t *)buffer
;
199 cookie
->stream
.avail_in
= size
;
202 cookie
->stream
.next_out
= cookie
->buffer
;
203 cookie
->stream
.avail_out
= sizeof(cookie
->buffer
);
205 lzma_ret ret
= lzma_code(&cookie
->stream
, LZMA_RUN
);
209 size_t bytes_to_write
= sizeof(cookie
->buffer
) - cookie
->stream
.avail_out
;
210 if (bytes_to_write
) {
211 size_t bytes_written
= fwrite(cookie
->buffer
, 1, bytes_to_write
, cookie
->f
);
213 if (bytes_written
!= bytes_to_write
)
217 // Report that all data has been written
218 if (cookie
->stream
.avail_in
== 0)
223 static int xz_close(void* data
) {
224 struct xz_cookie
* cookie
= (struct xz_cookie
*)data
;
228 if (cookie
->mode
== 'w') {
230 cookie
->stream
.next_out
= cookie
->buffer
;
231 cookie
->stream
.avail_out
= sizeof(cookie
->buffer
);
233 lzma_ret ret
= lzma_code(&cookie
->stream
, LZMA_FINISH
);
234 if (ret
!= LZMA_OK
&& ret
!= LZMA_STREAM_END
)
237 size_t bytes_to_write
= sizeof(cookie
->buffer
) - cookie
->stream
.avail_out
;
238 if (bytes_to_write
) {
239 size_t bytes_written
= fwrite(cookie
->buffer
, 1, bytes_to_write
, cookie
->f
);
241 if (bytes_written
!= bytes_to_write
)
245 if (ret
== LZMA_STREAM_END
)
250 lzma_end(&cookie
->stream
);
253 int r
= fclose(cookie
->f
);
259 static cookie_io_functions_t xz_functions
= {
266 FILE* pakfire_xzfopen(FILE* f
, const char* mode
) {
279 struct xz_cookie
* cookie
= calloc(1, sizeof(*cookie
));
284 cookie
->mode
= *mode
;
286 switch (cookie
->mode
) {
288 ret
= lzma_stream_decoder(&cookie
->stream
, UINT64_MAX
, 0);
292 ret
= lzma_easy_encoder(&cookie
->stream
, XZ_COMPRESSION_LEVEL
, LZMA_CHECK_SHA256
);
303 return fopencookie(cookie
, mode
, xz_functions
);
318 ZSTD_CStream
* cstream
;
322 ZSTD_DStream
* dstream
;
325 uint8_t buffer
[BUFFER_SIZE
];
328 static ssize_t
zstd_read(void* data
, char* buffer
, size_t size
) {
329 struct zstd_cookie
* cookie
= (struct zstd_cookie
*)data
;
333 // Do not read when mode is "w"
334 if (cookie
->mode
== 'w')
342 // Configure output buffer
343 cookie
->out
.dst
= buffer
;
345 cookie
->out
.size
= size
;
348 if (!feof(cookie
->f
) && (cookie
->in
.pos
== cookie
->in
.size
)) {
350 cookie
->in
.size
= fread(cookie
->buffer
, 1, sizeof(cookie
->buffer
), cookie
->f
);
353 if (r
|| cookie
->in
.size
)
354 r
= ZSTD_decompressStream(cookie
->dstream
, &cookie
->out
, &cookie
->in
);
356 if (r
== 0 && feof(cookie
->f
)) {
358 return cookie
->out
.pos
;
365 if (cookie
->out
.pos
== size
)
370 static ssize_t
zstd_write(void* data
, const char* buffer
, size_t size
) {
371 struct zstd_cookie
* cookie
= (struct zstd_cookie
*)data
;
375 // Do not write when mode is "r"
376 if (cookie
->mode
== 'r')
379 // Return nothing when there is no input
383 // Configure input buffer
384 cookie
->in
.src
= buffer
;
386 cookie
->in
.size
= size
;
391 size_t r
= ZSTD_compressStream(cookie
->cstream
, &cookie
->out
, &cookie
->in
);
395 if (cookie
->out
.pos
> 0) {
396 size_t bytes_written
= fwrite(cookie
->buffer
, 1, cookie
->out
.pos
, cookie
->f
);
398 if (bytes_written
!= cookie
->out
.pos
)
402 // Return when all input has been written
403 if (cookie
->in
.pos
== size
)
408 static int zstd_close(void* data
) {
409 struct zstd_cookie
* cookie
= (struct zstd_cookie
*)data
;
413 if (cookie
->mode
== 'w') {
415 // Reset output buffer
418 size_t r
= ZSTD_endStream(cookie
->cstream
, &cookie
->out
);
422 if (cookie
->out
.pos
> 0) {
423 size_t bytes_written
= fwrite(cookie
->buffer
, 1, cookie
->out
.pos
, cookie
->f
);
425 if (bytes_written
!= cookie
->out
.pos
)
434 int r
= fclose(cookie
->f
);
438 ZSTD_freeCStream(cookie
->cstream
);
440 ZSTD_freeDStream(cookie
->dstream
);
446 static cookie_io_functions_t zstd_functions
= {
453 FILE* pakfire_zstdfopen(FILE* f
, const char* mode
) {
464 struct zstd_cookie
* cookie
= calloc(1, sizeof(*cookie
));
469 cookie
->mode
= *mode
;
472 switch (cookie
->mode
) {
475 cookie
->dstream
= ZSTD_createDStream();
476 if (!cookie
->dstream
)
480 r
= ZSTD_initDStream(cookie
->dstream
);
484 cookie
->in
.src
= cookie
->buffer
;
491 cookie
->cstream
= ZSTD_createCStream();
492 if (!cookie
->cstream
)
496 r
= ZSTD_initCStream(cookie
->cstream
, ZSTD_COMPRESSION_LEVEL
);
500 cookie
->out
.dst
= cookie
->buffer
;
502 cookie
->out
.size
= sizeof(cookie
->buffer
);
510 return fopencookie(cookie
, mode
, zstd_functions
);
514 ZSTD_freeCStream(cookie
->cstream
);
516 ZSTD_freeDStream(cookie
->dstream
);
523 Helper function to conditionally walk through an archive
524 and perform actions based on the callback.
526 int pakfire_walk(struct pakfire
* pakfire
, struct archive
* archive
,
527 pakfire_walk_callback callback
, pakfire_walk_filter_callback filter_callback
,
529 struct archive_entry
* entry
= NULL
;
530 const char* path
= NULL
;
533 // Walk through the archive
535 r
= archive_read_next_header(archive
, &entry
);
537 // Handle the return code
539 // Fall through if everything is okay
543 // Return OK when we reached the end of the archive
547 // Raise any other errors
552 path
= archive_entry_pathname(entry
);
554 DEBUG(pakfire
, "Walking through %s...\n", path
);
556 // Call the filter callback before we call the actual callback
557 if (filter_callback
) {
558 r
= filter_callback(pakfire
, archive
, entry
, p
);
560 // Handle the return code
562 case PAKFIRE_WALK_OK
:
565 case PAKFIRE_WALK_END
:
566 DEBUG(pakfire
, "Filter callback sent END\n");
569 case PAKFIRE_WALK_SKIP
:
570 DEBUG(pakfire
, "Filter callback sent SKIP\n");
573 case PAKFIRE_WALK_DONE
:
574 DEBUG(pakfire
, "Filter callback sent DONE\n");
576 // Clear the callback function
577 filter_callback
= NULL
;
580 // Raise any other errors
582 DEBUG(pakfire
, "Filter callback returned an error: %d\n", r
);
589 r
= callback(pakfire
, archive
, entry
, p
);
591 // Handle the return code
593 case PAKFIRE_WALK_OK
:
596 case PAKFIRE_WALK_DONE
:
597 DEBUG(pakfire
, "Callback sent DONE\n");
600 // Raise any other errors
612 struct pakfire_extract
{
613 // Reference to Pakfire
614 struct pakfire
* pakfire
;
619 // The archive to extract
620 struct archive
* archive
;
622 // The filelist of all extracted files
623 struct pakfire_filelist
* filelist
;
625 // Prepend this prefix
626 char prefix
[PATH_MAX
];
629 struct archive
* writer
;
632 struct pakfire_progressbar
* progressbar
;
635 static int pakfire_extract_progressbar_create(struct pakfire_progressbar
** progressbar
,
636 const char* message
, int flags
) {
639 // Create the progressbar
640 r
= pakfire_progressbar_create(progressbar
, NULL
);
646 r
= pakfire_progressbar_add_string(*progressbar
, "%s", message
);
652 r
= pakfire_progressbar_add_bar(*progressbar
);
657 if (flags
& PAKFIRE_EXTRACT_SHOW_THROUGHPUT
) {
658 r
= pakfire_progressbar_add_transfer_speed(*progressbar
);
664 r
= pakfire_progressbar_add_percentage(*progressbar
);
672 static void pakfire_extract_progress(void* p
) {
673 struct pakfire_extract
* data
= (struct pakfire_extract
*)p
;
675 // Fetch how many bytes have been read
676 const size_t position
= archive_filter_bytes(data
->archive
, -1);
678 // Update progressbar
679 pakfire_progressbar_update(data
->progressbar
, position
);
682 static int __pakfire_extract(struct pakfire
* pakfire
, struct archive
* a
,
683 struct archive_entry
* entry
, void* p
) {
684 struct pakfire_file
* file
= NULL
;
685 struct vfs_cap_data cap_data
= {};
686 char buffer
[PATH_MAX
];
689 struct pakfire_extract
* data
= (struct pakfire_extract
*)p
;
692 const char* path
= archive_entry_pathname(entry
);
694 // Generate a file object
695 r
= pakfire_file_create_from_archive_entry(&file
, pakfire
, entry
);
699 // Add entry to filelist (if requested)
700 if (data
->filelist
) {
701 // Append the file to the list
702 r
= pakfire_filelist_add(data
->filelist
, file
);
707 const int configfile
= pakfire_file_has_flag(file
, PAKFIRE_FILE_CONFIG
);
709 // Prepend the prefix
712 r
= pakfire_path_join(buffer
, data
->prefix
, path
);
714 ERROR(pakfire
, "Could not compose file path: %m\n");
719 archive_entry_set_pathname(entry
, buffer
);
721 // Update hardlink destination
722 const char* link
= archive_entry_hardlink(entry
);
724 r
= pakfire_path_join(buffer
, data
->prefix
, link
);
726 ERROR(pakfire
, "Could not compose hardlink path: %m\n");
731 archive_entry_set_hardlink(entry
, buffer
);
736 // Fetch path again since we changed it
737 path
= archive_entry_pathname(entry
);
739 if (pakfire_path_exists(path
)) {
740 DEBUG(pakfire
, "The configuration file %s exists\n",
741 pakfire_file_get_path(file
));
743 r
= pakfire_string_format(buffer
, "%s.paknew", path
);
745 ERROR(pakfire
, "Could not compose path for configuration file: %m\n");
749 // Set the path again
750 archive_entry_set_pathname(entry
, buffer
);
754 // Create file & extract payload
756 // Fetch path again since we changed it
757 path
= archive_entry_pathname(entry
);
759 DEBUG(pakfire
, "Extracting %s\n", path
);
761 // Remove any extended attributes which we never write to disk
762 archive_entry_xattr_clear(entry
);
765 if (pakfire_file_has_caps(file
)) {
766 r
= pakfire_file_write_fcaps(file
, &cap_data
);
770 // Store capabilities in archive entry
771 archive_entry_xattr_add_entry(entry
, "security.capability",
772 &cap_data
, sizeof(cap_data
));
776 r
= archive_read_extract2(data
->archive
, entry
, data
->writer
);
783 ERROR(pakfire
, "%s\n", archive_error_string(data
->writer
));
785 // Pretend everything has been okay
790 ERROR(pakfire
, "%s\n", archive_error_string(data
->writer
));
798 pakfire_file_unref(file
);
803 int pakfire_extract(struct pakfire
* pakfire
, struct archive
* archive
,
804 size_t size
, struct pakfire_filelist
* filelist
,
805 const char* prefix
, const char* message
,
806 pakfire_walk_filter_callback filter_callback
, int flags
) {
809 // Use an empty string if no prefix set
813 struct pakfire_extract data
= {
816 .filelist
= filelist
,
821 // Is this a dry run?
822 const int dry_run
= flags
& PAKFIRE_EXTRACT_DRY_RUN
;
824 // Should we show a progress bar?
825 const int no_progress
= flags
& PAKFIRE_EXTRACT_NO_PROGRESS
;
827 // Set prefix (including pakfire path)
828 r
= pakfire_path(pakfire
, data
.prefix
, "%s", prefix
);
834 data
.writer
= pakfire_make_archive_disk_writer(pakfire
, 1);
836 ERROR(pakfire
, "Could not create disk writer: %m\n");
842 // Create the progressbar
844 r
= pakfire_extract_progressbar_create(&data
.progressbar
, message
, flags
);
848 // Register progress callback
849 archive_read_extract_set_progress_callback(data
.archive
,
850 pakfire_extract_progress
, &data
);
853 pakfire_progressbar_start(data
.progressbar
, size
);
856 // Walk through the entire archive
857 r
= pakfire_walk(pakfire
, archive
, __pakfire_extract
, filter_callback
, &data
);
861 // Finish the progressbar
862 if (data
.progressbar
)
863 pakfire_progressbar_finish(data
.progressbar
);
866 if (data
.progressbar
)
867 pakfire_progressbar_unref(data
.progressbar
);
869 archive_write_free(data
.writer
);
874 // Common compression
876 struct pakfire_compress
{
877 // Reference to Pakfire
878 struct pakfire
* pakfire
;
883 // The archive to write to
884 struct archive
* archive
;
886 // Resolver for hardlinks
887 struct archive_entry_linkresolver
* linkresolver
;
889 // The filelist of all files to write
890 struct pakfire_filelist
* filelist
;
893 struct pakfire_progressbar
* progressbar
;
895 // Digests to write to the archive
899 static int pakfire_compress_progressbar_create(struct pakfire_progressbar
** progressbar
,
900 const char* message
, int flags
) {
903 // Create the progressbar
904 r
= pakfire_progressbar_create(progressbar
, NULL
);
910 r
= pakfire_progressbar_add_string(*progressbar
, "%s", message
);
916 r
= pakfire_progressbar_add_bar(*progressbar
);
921 if (flags
& PAKFIRE_COMPRESS_SHOW_THROUGHPUT
) {
922 r
= pakfire_progressbar_add_transfer_speed(*progressbar
);
927 // Show how many bytes have been written
928 r
= pakfire_progressbar_add_bytes_transferred(*progressbar
);
933 r
= pakfire_progressbar_add_percentage(*progressbar
);
941 static int __pakfire_compress_entry(struct pakfire
* pakfire
, struct pakfire_file
* file
,
942 struct pakfire_compress
* data
, struct archive_entry
* entry
) {
947 r
= archive_write_header(data
->archive
, entry
);
949 ERROR(pakfire
, "Error writing file header: %s\n",
950 archive_error_string(data
->archive
));
954 // Copy the data if there is any
955 if (archive_entry_size(entry
)) {
957 f
= pakfire_file_open(file
);
963 // Copy the payload into the archive
964 r
= pakfire_archive_copy_data_from_file(pakfire
, data
->archive
, f
);
970 r
= archive_write_finish_entry(data
->archive
);
981 static int __pakfire_compress(struct pakfire
* pakfire
, struct pakfire_file
* file
, void* p
) {
982 struct archive_entry
* entry
= NULL
;
983 struct archive_entry
* sparse_entry
= NULL
;
986 struct pakfire_compress
* data
= (struct pakfire_compress
*)p
;
988 // Fetch the file size
989 const size_t size
= pakfire_file_get_size(file
);
991 // Generate file metadata into an archive entry
992 entry
= pakfire_file_archive_entry(file
, data
->digests
);
998 // Perform search for hardlinks
999 archive_entry_linkify(data
->linkresolver
, &entry
, &sparse_entry
);
1001 // Write the main entry
1003 r
= __pakfire_compress_entry(pakfire
, file
, data
, entry
);
1008 // Write the sparse entry
1010 r
= __pakfire_compress_entry(pakfire
, file
, data
, sparse_entry
);
1015 // Query the link resolver for any more entries
1019 archive_entry_free(entry
);
1023 // Free the sparse entry
1025 archive_entry_free(sparse_entry
);
1026 sparse_entry
= NULL
;
1029 // Fetch the next entry
1030 archive_entry_linkify(data
->linkresolver
, &entry
, &sparse_entry
);
1034 // Write the entry to the archive
1035 r
= __pakfire_compress_entry(pakfire
, file
, data
, entry
);
1040 // Update the progressbar
1041 if (data
->progressbar
)
1042 pakfire_progressbar_increment(data
->progressbar
, size
);
1046 archive_entry_free(entry
);
1048 archive_entry_free(sparse_entry
);
1053 int pakfire_compress(struct pakfire
* pakfire
, struct archive
* archive
,
1054 struct pakfire_filelist
* filelist
, const char* message
, int flags
, int digests
) {
1057 struct pakfire_compress data
= {
1060 .filelist
= filelist
,
1065 // Should we show a progress bar?
1066 const int no_progress
= flags
& PAKFIRE_COMPRESS_NO_PROGRESS
;
1068 // Fetch the length of the filelist
1069 const size_t size
= pakfire_filelist_total_size(filelist
);
1071 // Create the progressbar
1073 r
= pakfire_compress_progressbar_create(&data
.progressbar
, message
, flags
);
1077 // Start progressbar
1078 pakfire_progressbar_start(data
.progressbar
, size
);
1081 // Setup the link resolver
1082 data
.linkresolver
= archive_entry_linkresolver_new();
1083 if (!data
.linkresolver
) {
1084 ERROR(pakfire
, "Could not setup link resolver: m\n");
1088 // Set the link resolver strategy
1089 archive_entry_linkresolver_set_strategy(data
.linkresolver
, archive_format(archive
));
1091 // Walk through the entire filelist
1092 r
= pakfire_filelist_walk(filelist
, __pakfire_compress
, &data
, 0);
1096 // Finish the progressbar
1097 if (data
.progressbar
)
1098 pakfire_progressbar_finish(data
.progressbar
);
1101 if (data
.progressbar
)
1102 pakfire_progressbar_unref(data
.progressbar
);
1103 if (data
.linkresolver
)
1104 archive_entry_linkresolver_free(data
.linkresolver
);
1109 int pakfire_compress_create_archive(struct pakfire
* pakfire
, struct archive
** archive
,
1110 FILE* f
, const enum pakfire_compressions compression
, const unsigned int level
) {
1111 struct archive
* a
= NULL
;
1115 // Open a new archive
1116 a
= archive_write_new();
1118 ERROR(pakfire
, "archive_write_new() failed\n");
1123 // Use the PAX format
1124 r
= archive_write_set_format_pax(a
);
1126 ERROR(pakfire
, "Could not set format to PAX: %s\n", archive_error_string(a
));
1130 // Store any extended attributes in the SCHILY headers
1131 r
= archive_write_set_format_option(a
, "pax", "xattrheader", "SCHILY");
1133 ERROR(pakfire
, "Could not set xattrheader option: %s\n", archive_error_string(a
));
1137 switch (compression
) {
1138 case PAKFIRE_COMPRESS_ZSTD
:
1140 r
= archive_write_add_filter_zstd(a
);
1142 ERROR(pakfire
, "Could not enable Zstandard compression: %s\n",
1143 archive_error_string(a
));
1147 // Do not pad the last block
1148 archive_write_set_bytes_in_last_block(a
, 1);
1150 // Set compression level
1152 r
= pakfire_string_format(value
, "%u", level
);
1156 r
= archive_write_set_filter_option(a
, NULL
, "compression-level", value
);
1158 ERROR(pakfire
, "Could not set Zstandard compression level: %s\n",
1159 archive_error_string(a
));
1164 #if ARCHIVE_VERSION_NUMBER >= 3006000
1165 // Fetch numbers of processors
1166 long processors
= sysconf(_SC_NPROCESSORS_ONLN
);
1168 if (processors
> 1) {
1169 r
= pakfire_string_format(value
, "%ld", processors
);
1171 ERROR(pakfire
, "Could not format threads: %m\n");
1175 // Try using multiple threads
1176 r
= archive_write_set_filter_option(a
, NULL
, "threads", value
);
1178 ERROR(pakfire
, "Could not enable %ld threads for compression: %s\n",
1179 processors
, archive_error_string(a
));
1186 // Write archive to f
1188 r
= archive_write_open_FILE(a
, f
);
1190 ERROR(pakfire
, "archive_write_open_FILE() failed: %s\n", archive_error_string(a
));
1202 archive_write_free(a
);