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/util.h>
37 // Read up to N bytes for analyze the magic
38 #define MAX_MAGIC_LENGTH 6
40 // Compression/Decompression buffer size
41 #define BUFFER_SIZE 64 * 1024
44 #define XZ_COMPRESSION_LEVEL 6
47 #define ZSTD_COMPRESSION_LEVEL 7
49 const struct compressor
{
50 char magic
[MAX_MAGIC_LENGTH
];
52 FILE* (*open
)(FILE* f
, const char* mode
);
55 { { 0xFD, '7', 'z', 'X', 'Z', 0x00 }, 6, pakfire_xzfopen
, },
57 { { 0x28, 0xb5, 0x2f, 0xfd }, 4, pakfire_zstdfopen
, },
62 // Try to guess the compression
63 FILE* pakfire_xfopen(FILE* f
, const char* mode
) {
64 char buffer
[MAX_MAGIC_LENGTH
];
76 // This only works for reading files
85 int r
= fgetpos(f
, &pos
);
89 // Read a couple of bytes
90 size_t bytes_read
= fread(buffer
, 1, sizeof(buffer
), f
);
97 // Check if we could read anything
98 if (!bytes_read
|| bytes_read
< sizeof(buffer
))
102 for (const struct compressor
* c
= compressors
; c
->open
; c
++) {
103 // Check if we have read enough data
104 if (bytes_read
< c
->magic_length
)
107 // Compare the magic value
108 r
= memcmp(c
->magic
, buffer
, c
->magic_length
);
113 return c
->open(f
, mode
);
116 // Nothing seems to match
127 uint8_t buffer
[BUFFER_SIZE
];
130 static ssize_t
xz_read(void* data
, char* buffer
, size_t size
) {
131 struct xz_cookie
* cookie
= (struct xz_cookie
*)data
;
135 // Do not read when mode is "w"
136 if (cookie
->mode
== 'w')
139 lzma_action action
= LZMA_RUN
;
141 // Set output to allocated buffer
142 cookie
->stream
.next_out
= (uint8_t *)buffer
;
143 cookie
->stream
.avail_out
= size
;
146 // Read something when the input buffer is empty
147 if (cookie
->stream
.avail_in
== 0) {
148 cookie
->stream
.next_in
= cookie
->buffer
;
149 cookie
->stream
.avail_in
= fread(cookie
->buffer
,
150 1, sizeof(cookie
->buffer
), cookie
->f
);
152 // Break if the input file could not be read
153 if (ferror(cookie
->f
))
156 // Finish after we have reached the end of the input file
161 lzma_ret ret
= lzma_code(&cookie
->stream
, action
);
163 // If the stream has ended, we just send the
164 // remaining output and mark that we are done.
165 if (ret
== LZMA_STREAM_END
) {
167 return size
- cookie
->stream
.avail_out
;
170 // Break on all other unexpected errors
174 // When we have read enough to fill the entire output buffer, we return
175 if (cookie
->stream
.avail_out
== 0)
183 static ssize_t
xz_write(void* data
, const char* buffer
, size_t size
) {
184 struct xz_cookie
* cookie
= (struct xz_cookie
*)data
;
188 // Do not write when mode is "r"
189 if (cookie
->mode
== 'r')
192 // Return nothing when there is no input
196 // Set input to allocated buffer
197 cookie
->stream
.next_in
= (uint8_t *)buffer
;
198 cookie
->stream
.avail_in
= size
;
201 cookie
->stream
.next_out
= cookie
->buffer
;
202 cookie
->stream
.avail_out
= sizeof(cookie
->buffer
);
204 lzma_ret ret
= lzma_code(&cookie
->stream
, LZMA_RUN
);
208 size_t bytes_to_write
= sizeof(cookie
->buffer
) - cookie
->stream
.avail_out
;
209 if (bytes_to_write
) {
210 size_t bytes_written
= fwrite(cookie
->buffer
, 1, bytes_to_write
, cookie
->f
);
212 if (bytes_written
!= bytes_to_write
)
216 // Report that all data has been written
217 if (cookie
->stream
.avail_in
== 0)
222 static int xz_close(void* data
) {
223 struct xz_cookie
* cookie
= (struct xz_cookie
*)data
;
227 if (cookie
->mode
== 'w') {
229 cookie
->stream
.next_out
= cookie
->buffer
;
230 cookie
->stream
.avail_out
= sizeof(cookie
->buffer
);
232 lzma_ret ret
= lzma_code(&cookie
->stream
, LZMA_FINISH
);
233 if (ret
!= LZMA_OK
&& ret
!= LZMA_STREAM_END
)
236 size_t bytes_to_write
= sizeof(cookie
->buffer
) - cookie
->stream
.avail_out
;
237 if (bytes_to_write
) {
238 size_t bytes_written
= fwrite(cookie
->buffer
, 1, bytes_to_write
, cookie
->f
);
240 if (bytes_written
!= bytes_to_write
)
244 if (ret
== LZMA_STREAM_END
)
249 lzma_end(&cookie
->stream
);
252 int r
= fclose(cookie
->f
);
258 static cookie_io_functions_t xz_functions
= {
265 FILE* pakfire_xzfopen(FILE* f
, const char* mode
) {
278 struct xz_cookie
* cookie
= calloc(1, sizeof(*cookie
));
283 cookie
->mode
= *mode
;
285 switch (cookie
->mode
) {
287 ret
= lzma_stream_decoder(&cookie
->stream
, UINT64_MAX
, 0);
291 ret
= lzma_easy_encoder(&cookie
->stream
, XZ_COMPRESSION_LEVEL
, LZMA_CHECK_SHA256
);
302 return fopencookie(cookie
, mode
, xz_functions
);
317 ZSTD_CStream
* cstream
;
321 ZSTD_DStream
* dstream
;
324 uint8_t buffer
[BUFFER_SIZE
];
327 static ssize_t
zstd_read(void* data
, char* buffer
, size_t size
) {
328 struct zstd_cookie
* cookie
= (struct zstd_cookie
*)data
;
332 // Do not read when mode is "w"
333 if (cookie
->mode
== 'w')
341 // Configure output buffer
342 cookie
->out
.dst
= buffer
;
344 cookie
->out
.size
= size
;
347 if (!feof(cookie
->f
) && (cookie
->in
.pos
== cookie
->in
.size
)) {
349 cookie
->in
.size
= fread(cookie
->buffer
, 1, sizeof(cookie
->buffer
), cookie
->f
);
352 if (r
|| cookie
->in
.size
)
353 r
= ZSTD_decompressStream(cookie
->dstream
, &cookie
->out
, &cookie
->in
);
355 if (r
== 0 && feof(cookie
->f
)) {
357 return cookie
->out
.pos
;
364 if (cookie
->out
.pos
== size
)
369 static ssize_t
zstd_write(void* data
, const char* buffer
, size_t size
) {
370 struct zstd_cookie
* cookie
= (struct zstd_cookie
*)data
;
374 // Do not write when mode is "r"
375 if (cookie
->mode
== 'r')
378 // Return nothing when there is no input
382 // Configure input buffer
383 cookie
->in
.src
= buffer
;
385 cookie
->in
.size
= size
;
390 size_t r
= ZSTD_compressStream(cookie
->cstream
, &cookie
->out
, &cookie
->in
);
394 if (cookie
->out
.pos
> 0) {
395 size_t bytes_written
= fwrite(cookie
->buffer
, 1, cookie
->out
.pos
, cookie
->f
);
397 if (bytes_written
!= cookie
->out
.pos
)
401 // Return when all input has been written
402 if (cookie
->in
.pos
== size
)
407 static int zstd_close(void* data
) {
408 struct zstd_cookie
* cookie
= (struct zstd_cookie
*)data
;
412 if (cookie
->mode
== 'w') {
414 // Reset output buffer
417 size_t r
= ZSTD_endStream(cookie
->cstream
, &cookie
->out
);
421 if (cookie
->out
.pos
> 0) {
422 size_t bytes_written
= fwrite(cookie
->buffer
, 1, cookie
->out
.pos
, cookie
->f
);
424 if (bytes_written
!= cookie
->out
.pos
)
433 int r
= fclose(cookie
->f
);
437 ZSTD_freeCStream(cookie
->cstream
);
439 ZSTD_freeDStream(cookie
->dstream
);
445 static cookie_io_functions_t zstd_functions
= {
452 FILE* pakfire_zstdfopen(FILE* f
, const char* mode
) {
463 struct zstd_cookie
* cookie
= calloc(1, sizeof(*cookie
));
468 cookie
->mode
= *mode
;
471 switch (cookie
->mode
) {
474 cookie
->dstream
= ZSTD_createDStream();
475 if (!cookie
->dstream
)
479 r
= ZSTD_initDStream(cookie
->dstream
);
483 cookie
->in
.src
= cookie
->buffer
;
490 cookie
->cstream
= ZSTD_createCStream();
491 if (!cookie
->cstream
)
495 r
= ZSTD_initCStream(cookie
->cstream
, ZSTD_COMPRESSION_LEVEL
);
499 cookie
->out
.dst
= cookie
->buffer
;
501 cookie
->out
.size
= sizeof(cookie
->buffer
);
509 return fopencookie(cookie
, mode
, zstd_functions
);
513 ZSTD_freeCStream(cookie
->cstream
);
515 ZSTD_freeDStream(cookie
->dstream
);
523 struct pakfire_extract
{
524 // Reference to Pakfire
525 struct pakfire
* pakfire
;
530 // The archive to extract
531 struct archive
* archive
;
533 // The filelist of all extracted files
534 struct pakfire_filelist
* filelist
;
536 // Prepend this prefix
537 char prefix
[PATH_MAX
];
540 struct archive
* writer
;
543 struct pakfire_progressbar
* progressbar
;
546 static int pakfire_extract_progressbar_create(struct pakfire_progressbar
** progressbar
,
547 const char* message
, int flags
) {
550 // Create the progressbar
551 r
= pakfire_progressbar_create(progressbar
, NULL
);
557 r
= pakfire_progressbar_add_string(*progressbar
, "%s", message
);
563 r
= pakfire_progressbar_add_bar(*progressbar
);
568 if (flags
& PAKFIRE_EXTRACT_SHOW_THROUGHPUT
) {
569 r
= pakfire_progressbar_add_transfer_speed(*progressbar
);
575 r
= pakfire_progressbar_add_percentage(*progressbar
);
583 static void pakfire_extract_progress(void* p
) {
584 struct pakfire_extract
* data
= (struct pakfire_extract
*)p
;
586 // Fetch how many bytes have been read
587 const size_t position
= archive_filter_bytes(data
->archive
, -1);
589 // Update progressbar
590 pakfire_progressbar_update(data
->progressbar
, position
);
593 static int __pakfire_extract_entry(struct pakfire
* pakfire
, struct pakfire_extract
* data
,
594 struct archive_entry
* entry
) {
595 struct pakfire_file
* file
= NULL
;
596 char buffer
[PATH_MAX
];
600 const char* path
= archive_entry_pathname(entry
);
602 // Prepend the prefix
605 r
= pakfire_path_join(buffer
, data
->prefix
, path
);
607 ERROR(pakfire
, "Could not compose file path: %m\n");
612 archive_entry_set_pathname(entry
, buffer
);
614 // Update hardlink destination
615 const char* link
= archive_entry_hardlink(entry
);
617 r
= pakfire_path_join(buffer
, data
->prefix
, link
);
619 ERROR(pakfire
, "Could not compose hardlink path: %m\n");
624 archive_entry_set_hardlink(entry
, buffer
);
628 // Add entry to filelist (if requested)
629 if (data
->filelist
) {
630 r
= pakfire_file_create_from_archive_entry(&file
, pakfire
, entry
);
634 // Append the file to the list
635 r
= pakfire_filelist_append(data
->filelist
, file
);
640 // Create file & extract payload
642 DEBUG(pakfire
, "Extracting %s\n", path
);
644 r
= archive_read_extract2(data
->archive
, entry
, data
->writer
);
651 ERROR(pakfire
, "%s\n", archive_error_string(data
->writer
));
653 // Pretend everything has been okay
658 ERROR(pakfire
, "%s\n", archive_error_string(data
->writer
));
666 pakfire_file_unref(file
);
671 int pakfire_extract(struct pakfire
* pakfire
, struct archive
* archive
,
672 size_t size
, struct pakfire_filelist
* filelist
,
673 const char* prefix
, const char* message
, int flags
) {
676 struct pakfire_extract data
= {
679 .filelist
= filelist
,
684 // Is this a dry run?
685 const int dry_run
= flags
& PAKFIRE_EXTRACT_DRY_RUN
;
687 // Should we show a progress bar?
688 const int no_progress
= flags
& PAKFIRE_EXTRACT_NO_PROGRESS
;
690 // Set prefix (including pakfire path)
691 r
= pakfire_path(pakfire
, data
.prefix
, "%s", prefix
);
697 data
.writer
= pakfire_make_archive_disk_writer(pakfire
, 1);
699 ERROR(pakfire
, "Could not create disk writer: %m\n");
705 // Create the progressbar
707 r
= pakfire_extract_progressbar_create(&data
.progressbar
, message
, flags
);
711 // Register progress callback
712 archive_read_extract_set_progress_callback(data
.archive
,
713 pakfire_extract_progress
, &data
);
716 pakfire_progressbar_start(data
.progressbar
, size
);
719 struct archive_entry
* entry
= NULL
;
721 // Walk through the archive
723 r
= archive_read_next_header(archive
, &entry
);
725 // End when we have reached the end of the archive
726 if (r
== ARCHIVE_EOF
) {
731 // Raise any other errors
733 ERROR(pakfire
, "Could not read next header: %s\n",
734 archive_error_string(archive
));
739 r
= __pakfire_extract_entry(pakfire
, &data
, entry
);
744 // Finish the progressbar
745 if (data
.progressbar
)
746 pakfire_progressbar_finish(data
.progressbar
);
749 if (data
.progressbar
)
750 pakfire_progressbar_unref(data
.progressbar
);
752 archive_write_free(data
.writer
);