]> git.ipfire.org Git - pakfire.git/blame - src/libpakfire/compress.c
progress: Implement setting the title as a separate call
[pakfire.git] / src / libpakfire / compress.c
CommitLineData
c352b776
MT
1/*#############################################################################
2# #
3# Pakfire - The IPFire package management system #
4# Copyright (C) 2021 Pakfire development team #
5# #
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. #
10# #
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. #
15# #
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/>. #
18# #
19#############################################################################*/
20
64f977a3 21#include <errno.h>
c352b776 22#include <stdio.h>
99018421 23#include <stdlib.h>
c352b776
MT
24#include <string.h>
25
79824416 26#include <archive.h>
c352b776 27#include <lzma.h>
9476d502 28#include <zstd.h>
c352b776
MT
29
30#include <pakfire/compress.h>
bb7f09cd
MT
31#include <pakfire/file.h>
32#include <pakfire/filelist.h>
79824416
MT
33#include <pakfire/logging.h>
34#include <pakfire/progressbar.h>
24553159 35#include <pakfire/string.h>
79824416 36#include <pakfire/util.h>
c352b776
MT
37
38// Read up to N bytes for analyze the magic
39#define MAX_MAGIC_LENGTH 6
40
5cd454df
MT
41// Compression/Decompression buffer size
42#define BUFFER_SIZE 64 * 1024
43
0bed1e1d
MT
44// Settings for XZ
45#define XZ_COMPRESSION_LEVEL 6
46
9476d502
MT
47// Settings for ZSTD
48#define ZSTD_COMPRESSION_LEVEL 7
49
c352b776
MT
50const struct compressor {
51 char magic[MAX_MAGIC_LENGTH];
52 size_t magic_length;
53 FILE* (*open)(FILE* f, const char* mode);
54} compressors[] = {
55 // XZ
56 { { 0xFD, '7', 'z', 'X', 'Z', 0x00 }, 6, pakfire_xzfopen, },
60732b6e
MT
57 // ZSTD
58 { { 0x28, 0xb5, 0x2f, 0xfd }, 4, pakfire_zstdfopen, },
59 // End
c352b776
MT
60 { "", 0, NULL, },
61};
62
c352b776
MT
63// Try to guess the compression
64FILE* pakfire_xfopen(FILE* f, const char* mode) {
65 char buffer[MAX_MAGIC_LENGTH];
66
64f977a3
MT
67 if (!f) {
68 errno = EBADFD;
c352b776 69 return NULL;
64f977a3
MT
70 }
71
72 if (!mode) {
73 errno = EINVAL;
74 return NULL;
75 }
76
77 // This only works for reading files
78 if (*mode != 'r') {
79 errno = ENOTSUP;
80 return NULL;
81 }
c352b776
MT
82
83 fpos_t pos;
84
85 // Store the position
86 int r = fgetpos(f, &pos);
87 if (r < 0)
88 return NULL;
89
90 // Read a couple of bytes
91 size_t bytes_read = fread(buffer, 1, sizeof(buffer), f);
92
93 // Reset position
94 r = fsetpos(f, &pos);
95 if (r < 0)
96 return NULL;
97
98 // Check if we could read anything
99 if (!bytes_read || bytes_read < sizeof(buffer))
100 return f;
101
102 // Analyze magic
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)
106 continue;
107
108 // Compare the magic value
109 r = memcmp(c->magic, buffer, c->magic_length);
110 if (r)
111 continue;
112
113 // We found a match!
114 return c->open(f, mode);
115 }
116
117 // Nothing seems to match
49492077 118 errno = ENOTSUP;
c352b776
MT
119 return f;
120}
121
5cd454df
MT
122struct xz_cookie {
123 FILE* f;
124 char mode;
125 lzma_stream stream;
126 int done;
127
128 uint8_t buffer[BUFFER_SIZE];
129};
130
c352b776
MT
131static ssize_t xz_read(void* data, char* buffer, size_t size) {
132 struct xz_cookie* cookie = (struct xz_cookie*)data;
133 if (!cookie)
134 return -1;
135
48187ff3
MT
136 // Do not read when mode is "w"
137 if (cookie->mode == 'w')
6923552a 138 return -1;
48187ff3 139
c352b776
MT
140 lzma_action action = LZMA_RUN;
141
142 // Set output to allocated buffer
143 cookie->stream.next_out = (uint8_t *)buffer;
144 cookie->stream.avail_out = size;
145
146 while (1) {
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,
5cd454df 151 1, sizeof(cookie->buffer), cookie->f);
c352b776
MT
152
153 // Break if the input file could not be read
154 if (ferror(cookie->f))
155 return -1;
156
157 // Finish after we have reached the end of the input file
5e12758c 158 if (feof(cookie->f))
c352b776 159 cookie->done = 1;
c352b776
MT
160 }
161
162 lzma_ret ret = lzma_code(&cookie->stream, action);
163
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) {
167 cookie->done = 1;
168 return size - cookie->stream.avail_out;
169 }
170
171 // Break on all other unexpected errors
172 if (ret != LZMA_OK)
173 return -1;
174
175 // When we have read enough to fill the entire output buffer, we return
176 if (cookie->stream.avail_out == 0)
177 return size;
178
179 if (cookie->done)
180 return -1;
181 }
182}
183
0bed1e1d
MT
184static ssize_t xz_write(void* data, const char* buffer, size_t size) {
185 struct xz_cookie* cookie = (struct xz_cookie*)data;
186 if (!cookie)
187 return -1;
188
189 // Do not write when mode is "r"
190 if (cookie->mode == 'r')
6923552a 191 return -1;
0bed1e1d
MT
192
193 // Return nothing when there is no input
194 if (size == 0)
195 return 0;
196
197 // Set input to allocated buffer
9d816df3
MT
198 cookie->stream.next_in = (uint8_t *)buffer;
199 cookie->stream.avail_in = size;
0bed1e1d
MT
200
201 while (1) {
202 cookie->stream.next_out = cookie->buffer;
203 cookie->stream.avail_out = sizeof(cookie->buffer);
204
205 lzma_ret ret = lzma_code(&cookie->stream, LZMA_RUN);
206 if (ret != LZMA_OK)
207 return -1;
208
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);
212
213 if (bytes_written != bytes_to_write)
214 return -1;
215 }
216
217 // Report that all data has been written
218 if (cookie->stream.avail_in == 0)
219 return size;
220 }
221}
222
c352b776
MT
223static int xz_close(void* data) {
224 struct xz_cookie* cookie = (struct xz_cookie*)data;
99018421
MT
225 if (!cookie)
226 return -1;
227
228 if (cookie->mode == 'w') {
229 while (1) {
230 cookie->stream.next_out = cookie->buffer;
231 cookie->stream.avail_out = sizeof(cookie->buffer);
232
233 lzma_ret ret = lzma_code(&cookie->stream, LZMA_FINISH);
234 if (ret != LZMA_OK && ret != LZMA_STREAM_END)
235 return -1;
236
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);
240
241 if (bytes_written != bytes_to_write)
242 return -1;
243 }
244
245 if (ret == LZMA_STREAM_END)
246 break;
247 }
248 }
c352b776 249
c352b776
MT
250 lzma_end(&cookie->stream);
251
252 // Close input file
99018421
MT
253 int r = fclose(cookie->f);
254 free(cookie);
c352b776 255
99018421 256 return r;
c352b776
MT
257}
258
99018421
MT
259static cookie_io_functions_t xz_functions = {
260 .read = xz_read,
261 .write = xz_write,
262 .seek = NULL,
263 .close = xz_close,
264};
265
c352b776 266FILE* pakfire_xzfopen(FILE* f, const char* mode) {
99018421
MT
267 lzma_ret ret;
268
48187ff3
MT
269 if (!f) {
270 errno = EBADFD;
271 return NULL;
272 }
273
274 if (!mode) {
275 errno = EINVAL;
276 return NULL;
277 }
278
99018421
MT
279 struct xz_cookie* cookie = calloc(1, sizeof(*cookie));
280 if (!cookie)
281 return NULL;
c352b776 282
99018421
MT
283 cookie->f = f;
284 cookie->mode = *mode;
0bed1e1d 285
99018421 286 switch (cookie->mode) {
0bed1e1d 287 case 'r':
99018421 288 ret = lzma_stream_decoder(&cookie->stream, UINT64_MAX, 0);
0bed1e1d
MT
289 break;
290
291 case 'w':
99018421 292 ret = lzma_easy_encoder(&cookie->stream, XZ_COMPRESSION_LEVEL, LZMA_CHECK_SHA256);
0bed1e1d
MT
293 break;
294
295 default:
296 errno = ENOTSUP;
2d4af759 297 goto ERROR;
0bed1e1d
MT
298 }
299
c352b776 300 if (ret != LZMA_OK)
2d4af759 301 goto ERROR;
c352b776 302
99018421 303 return fopencookie(cookie, mode, xz_functions);
2d4af759
MT
304
305ERROR:
306 free(cookie);
307 return NULL;
c352b776 308}
9476d502
MT
309
310// ZSTD
311
312struct zstd_cookie {
313 FILE* f;
314 char mode;
315 int done;
316
317 // Encoder
318 ZSTD_CStream* cstream;
319 ZSTD_inBuffer in;
320
321 // Decoder
322 ZSTD_DStream* dstream;
323 ZSTD_outBuffer out;
324
325 uint8_t buffer[BUFFER_SIZE];
326};
327
328static ssize_t zstd_read(void* data, char* buffer, size_t size) {
329 struct zstd_cookie* cookie = (struct zstd_cookie*)data;
330 if (!cookie)
331 return -1;
332
333 // Do not read when mode is "w"
334 if (cookie->mode == 'w')
335 return -1;
336
337 if (cookie->done)
338 return 0;
339
340 size_t r = 0;
341
342 // Configure output buffer
343 cookie->out.dst = buffer;
344 cookie->out.pos = 0;
345 cookie->out.size = size;
346
347 while (1) {
348 if (!feof(cookie->f) && (cookie->in.pos == cookie->in.size)) {
349 cookie->in.pos = 0;
350 cookie->in.size = fread(cookie->buffer, 1, sizeof(cookie->buffer), cookie->f);
351 }
352
353 if (r || cookie->in.size)
354 r = ZSTD_decompressStream(cookie->dstream, &cookie->out, &cookie->in);
355
356 if (r == 0 && feof(cookie->f)) {
357 cookie->done = 1;
358 return cookie->out.pos;
359 }
360
361 if (ZSTD_isError(r))
362 return -1;
363
364 // Buffer full
365 if (cookie->out.pos == size)
366 return size;
367 }
368}
369
99a1527a
MT
370static ssize_t zstd_write(void* data, const char* buffer, size_t size) {
371 struct zstd_cookie* cookie = (struct zstd_cookie*)data;
372 if (!cookie)
373 return -1;
374
375 // Do not write when mode is "r"
376 if (cookie->mode == 'r')
377 return -1;
378
379 // Return nothing when there is no input
380 if (size == 0)
381 return 0;
382
383 // Configure input buffer
384 cookie->in.src = buffer;
385 cookie->in.pos = 0;
386 cookie->in.size = size;
387
388 while (1) {
389 cookie->out.pos = 0;
390
391 size_t r = ZSTD_compressStream(cookie->cstream, &cookie->out, &cookie->in);
392 if (ZSTD_isError(r))
393 return -1;
394
395 if (cookie->out.pos > 0) {
396 size_t bytes_written = fwrite(cookie->buffer, 1, cookie->out.pos, cookie->f);
397
398 if (bytes_written != cookie->out.pos)
399 return -1;
400 }
401
402 // Return when all input has been written
403 if (cookie->in.pos == size)
404 return size;
405 }
406}
407
9476d502
MT
408static int zstd_close(void* data) {
409 struct zstd_cookie* cookie = (struct zstd_cookie*)data;
410 if (!cookie)
411 return -1;
412
99a1527a
MT
413 if (cookie->mode == 'w') {
414 while (1) {
415 // Reset output buffer
416 cookie->out.pos = 0;
417
418 size_t r = ZSTD_endStream(cookie->cstream, &cookie->out);
419 if (ZSTD_isError(r))
420 return -1;
421
422 if (cookie->out.pos > 0) {
423 size_t bytes_written = fwrite(cookie->buffer, 1, cookie->out.pos, cookie->f);
424
425 if (bytes_written != cookie->out.pos)
426 return -1;
427 }
428
429 if (r == 0)
430 break;
431 }
432 }
9476d502
MT
433
434 int r = fclose(cookie->f);
435
436 // Free everything
437 if (cookie->cstream)
438 ZSTD_freeCStream(cookie->cstream);
439 if (cookie->dstream)
440 ZSTD_freeDStream(cookie->dstream);
441 free(cookie);
442
443 return r;
444}
445
446static cookie_io_functions_t zstd_functions = {
447 .read = zstd_read,
99a1527a 448 .write = zstd_write,
9476d502
MT
449 .seek = NULL,
450 .close = zstd_close,
451};
452
453FILE* pakfire_zstdfopen(FILE* f, const char* mode) {
454 if (!f) {
455 errno = EBADFD;
456 return NULL;
457 }
458
459 if (!mode) {
460 errno = EINVAL;
461 return NULL;
462 }
463
464 struct zstd_cookie* cookie = calloc(1, sizeof(*cookie));
465 if (!cookie)
466 return NULL;
467
468 cookie->f = f;
469 cookie->mode = *mode;
470
471 size_t r;
472 switch (cookie->mode) {
473 case 'r':
474 // Allocate stream
475 cookie->dstream = ZSTD_createDStream();
476 if (!cookie->dstream)
477 goto ERROR;
478
479 // Initialize stream
480 r = ZSTD_initDStream(cookie->dstream);
481 if (ZSTD_isError(r))
482 goto ERROR;
483
484 cookie->in.src = cookie->buffer;
485 cookie->in.pos = 0;
486 cookie->in.size = 0;
487 break;
488
489 case 'w':
490 // Allocate stream
491 cookie->cstream = ZSTD_createCStream();
492 if (!cookie->cstream)
493 goto ERROR;
494
495 // Initialize stream
496 r = ZSTD_initCStream(cookie->cstream, ZSTD_COMPRESSION_LEVEL);
497 if (ZSTD_isError(r))
498 goto ERROR;
499
500 cookie->out.dst = cookie->buffer;
501 cookie->out.pos = 0;
502 cookie->out.size = sizeof(cookie->buffer);
503 break;
504
505 default:
506 errno = ENOTSUP;
507 goto ERROR;
508 }
509
510 return fopencookie(cookie, mode, zstd_functions);
511
512ERROR:
513 if (cookie->cstream)
514 ZSTD_freeCStream(cookie->cstream);
515 if (cookie->dstream)
516 ZSTD_freeDStream(cookie->dstream);
517 free(cookie);
518
519 return NULL;
520}
79824416 521
d9302cc3
MT
522/*
523 Helper function to conditionally walk through an archive
524 and perform actions based on the callback.
525*/
526int pakfire_walk(struct pakfire* pakfire, struct archive* archive,
527 pakfire_walk_callback callback, pakfire_walk_filter_callback filter_callback,
528 void* p) {
529 struct archive_entry* entry = NULL;
662ff8e8 530 const char* path = NULL;
d9302cc3
MT
531 int r;
532
533 // Walk through the archive
534 for (;;) {
535 r = archive_read_next_header(archive, &entry);
536
537 // Handle the return code
538 switch (r) {
0e25bebb
MT
539 // Fall through if everything is okay
540 case ARCHIVE_OK:
541 break;
542
d9302cc3
MT
543 // Return OK when we reached the end of the archive
544 case ARCHIVE_EOF:
545 return 0;
546
547 // Raise any other errors
548 default:
549 return r;
550 }
551
662ff8e8
MT
552 path = archive_entry_pathname(entry);
553
554 DEBUG(pakfire, "Walking through %s...\n", path);
555
d9302cc3
MT
556 // Call the filter callback before we call the actual callback
557 if (filter_callback) {
558 r = filter_callback(pakfire, archive, entry, p);
559
560 // Handle the return code
561 switch (r) {
562 case PAKFIRE_WALK_OK:
563 break;
564
2480e018
MT
565 case PAKFIRE_WALK_END:
566 DEBUG(pakfire, "Filter callback sent END\n");
d9302cc3
MT
567 return 0;
568
569 case PAKFIRE_WALK_SKIP:
570 DEBUG(pakfire, "Filter callback sent SKIP\n");
571 continue;
572
2480e018
MT
573 case PAKFIRE_WALK_DONE:
574 DEBUG(pakfire, "Filter callback sent DONE\n");
f8a46d2f
MT
575
576 // Clear the callback function
577 filter_callback = NULL;
578 break;
579
d9302cc3
MT
580 // Raise any other errors
581 default:
662ff8e8 582 DEBUG(pakfire, "Filter callback returned an error: %d\n", r);
d9302cc3
MT
583 return r;
584 }
585 }
586
587 // Run callback
588 if (callback) {
589 r = callback(pakfire, archive, entry, p);
590
591 // Handle the return code
592 switch (r) {
593 case PAKFIRE_WALK_OK:
594 break;
595
596 case PAKFIRE_WALK_DONE:
597 DEBUG(pakfire, "Callback sent DONE\n");
598 return 0;
599
600 // Raise any other errors
601 default:
602 return r;
603 }
604 }
605 }
606
607 return 0;
608}
609
79824416
MT
610// Common extraction
611
612struct pakfire_extract {
613 // Reference to Pakfire
614 struct pakfire* pakfire;
615
a3b276d1
MT
616 // Flags
617 int flags;
618
79824416
MT
619 // The archive to extract
620 struct archive* archive;
621
d05af3ba
MT
622 // The filelist of all extracted files
623 struct pakfire_filelist* filelist;
624
79824416 625 // Prepend this prefix
1ce8f42e 626 const char* prefix;
79824416
MT
627
628 // The writer
629 struct archive* writer;
630
32bbeefc
MT
631 // The progress indicator
632 struct pakfire_progress* progress;
79824416
MT
633};
634
79824416
MT
635static void pakfire_extract_progress(void* p) {
636 struct pakfire_extract* data = (struct pakfire_extract*)p;
637
638 // Fetch how many bytes have been read
639 const size_t position = archive_filter_bytes(data->archive, -1);
640
32bbeefc
MT
641 // Update progress
642 pakfire_progress_update(data->progress, position);
79824416
MT
643}
644
14eedd80
MT
645static int __pakfire_extract(struct pakfire* pakfire, struct archive* a,
646 struct archive_entry* entry, void* p) {
bb7f09cd 647 struct pakfire_file* file = NULL;
729568d0 648 struct vfs_cap_data cap_data = {};
79824416
MT
649 char buffer[PATH_MAX];
650 int r;
651
14eedd80
MT
652 struct pakfire_extract* data = (struct pakfire_extract*)p;
653
79824416
MT
654 // Fetch path
655 const char* path = archive_entry_pathname(entry);
656
e990815d
MT
657 // Make sure we have a leading slash on the filelist
658 if (!pakfire_string_startswith(path, "/")) {
659 r = pakfire_string_format(buffer, "/%s", path);
660 if (r)
661 goto ERROR;
662
663 // Store the new name
664 archive_entry_set_pathname(entry, buffer);
665
666 // Update the path pointer
667 path = archive_entry_pathname(entry);
668 }
669
24553159
MT
670 // Generate a file object
671 r = pakfire_file_create_from_archive_entry(&file, pakfire, entry);
672 if (r)
673 goto ERROR;
674
e5503d53
MT
675 // Add entry to filelist (if requested)
676 if (data->filelist) {
e5503d53 677 // Append the file to the list
2f88682d 678 r = pakfire_filelist_add(data->filelist, file);
e5503d53
MT
679 if (r)
680 goto ERROR;
681 }
682
24553159
MT
683 const int configfile = pakfire_file_has_flag(file, PAKFIRE_FILE_CONFIG);
684
79824416
MT
685 // Prepend the prefix
686 if (*data->prefix) {
687 // Compose file path
688 r = pakfire_path_join(buffer, data->prefix, path);
56796f84 689 if (r) {
79824416 690 ERROR(pakfire, "Could not compose file path: %m\n");
24553159 691 goto ERROR;
79824416
MT
692 }
693
694 // Set file path
695 archive_entry_set_pathname(entry, buffer);
696
697 // Update hardlink destination
698 const char* link = archive_entry_hardlink(entry);
699 if (link) {
700 r = pakfire_path_join(buffer, data->prefix, link);
56796f84 701 if (r) {
79824416 702 ERROR(pakfire, "Could not compose hardlink path: %m\n");
24553159 703 goto ERROR;
79824416
MT
704 }
705
706 // Set hardlink path
707 archive_entry_set_hardlink(entry, buffer);
708 }
709 }
710
24553159
MT
711 if (configfile) {
712 // Fetch path again since we changed it
713 path = archive_entry_pathname(entry);
714
715 if (pakfire_path_exists(path)) {
716 DEBUG(pakfire, "The configuration file %s exists\n",
717 pakfire_file_get_path(file));
718
719 r = pakfire_string_format(buffer, "%s.paknew", path);
720 if (r) {
721 ERROR(pakfire, "Could not compose path for configuration file: %m\n");
722 goto ERROR;
723 }
724
725 // Set the path again
726 archive_entry_set_pathname(entry, buffer);
727 }
728 }
729
79824416 730 // Create file & extract payload
a3b276d1 731 if (data->writer) {
07c1c6e0
MT
732 // Fetch path again since we changed it
733 path = archive_entry_pathname(entry);
734
cf71c992
MT
735 DEBUG(pakfire, "Extracting %s\n", path);
736
89ee0833
MT
737 // Remove any extended attributes which we never write to disk
738 archive_entry_xattr_clear(entry);
739
729568d0
MT
740 // Set capabilities
741 if (pakfire_file_has_caps(file)) {
742 r = pakfire_file_write_fcaps(file, &cap_data);
743 if (r)
744 goto ERROR;
745
746 // Store capabilities in archive entry
747 archive_entry_xattr_add_entry(entry, "security.capability",
748 &cap_data, sizeof(cap_data));
749 }
750
89ee0833 751 // Write payload
a3b276d1
MT
752 r = archive_read_extract2(data->archive, entry, data->writer);
753 switch (r) {
754 case ARCHIVE_OK:
755 r = 0;
756 break;
79824416 757
a3b276d1
MT
758 case ARCHIVE_WARN:
759 ERROR(pakfire, "%s\n", archive_error_string(data->writer));
79824416 760
a3b276d1
MT
761 // Pretend everything has been okay
762 r = 0;
763 break;
79824416 764
a3b276d1
MT
765 case ARCHIVE_FATAL:
766 ERROR(pakfire, "%s\n", archive_error_string(data->writer));
767 r = 1;
768 break;
769 }
79824416
MT
770 }
771
bb7f09cd
MT
772ERROR:
773 if (file)
774 pakfire_file_unref(file);
775
79824416
MT
776 return r;
777}
778
779int pakfire_extract(struct pakfire* pakfire, struct archive* archive,
bb7f09cd 780 size_t size, struct pakfire_filelist* filelist,
f8a46d2f
MT
781 const char* prefix, const char* message,
782 pakfire_walk_filter_callback filter_callback, int flags) {
32bbeefc 783 int progress_flags = PAKFIRE_PROGRESS_SHOW_PERCENTAGE;
79824416
MT
784 int r = 1;
785
1ce8f42e 786 // Use / if no prefix is set
a623c421 787 if (!prefix)
1ce8f42e 788 prefix = "/";
a623c421 789
79824416 790 struct pakfire_extract data = {
d05af3ba
MT
791 .pakfire = pakfire,
792 .archive = archive,
793 .filelist = filelist,
1ce8f42e 794 .prefix = prefix,
d05af3ba
MT
795 .flags = flags,
796 .writer = NULL,
79824416
MT
797 };
798
a3b276d1
MT
799 // Is this a dry run?
800 const int dry_run = flags & PAKFIRE_EXTRACT_DRY_RUN;
801
79824416 802 // Allocate writer
a3b276d1
MT
803 if (!dry_run) {
804 data.writer = pakfire_make_archive_disk_writer(pakfire, 1);
805 if (!data.writer) {
806 ERROR(pakfire, "Could not create disk writer: %m\n");
807 r = 1;
808 goto ERROR;
809 }
79824416
MT
810 }
811
32bbeefc
MT
812 // Should we show any progress?
813 if (flags & PAKFIRE_EXTRACT_NO_PROGRESS)
814 progress_flags |= PAKFIRE_PROGRESS_NO_PROGRESS;
79824416 815
32bbeefc
MT
816 // Show throughput?
817 if (flags & PAKFIRE_EXTRACT_SHOW_THROUGHPUT)
818 progress_flags |= PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED;
79824416 819
32bbeefc 820 // Create the progressbar indicator
d1076560
MT
821 r = pakfire_progress_create(&data.progress, pakfire, progress_flags);
822 if (r)
823 goto ERROR;
824
825 // Set the title
826 r = pakfire_progress_set_title(data.progress, "%s", message);
32bbeefc
MT
827 if (r)
828 goto ERROR;
829
830 // Register progress callback
831 archive_read_extract_set_progress_callback(data.archive,
832 pakfire_extract_progress, &data);
833
834 // Start progress
835 r = pakfire_progress_start(data.progress, size);
836 if (r)
837 goto ERROR;
79824416 838
14eedd80 839 // Walk through the entire archive
f8a46d2f 840 r = pakfire_walk(pakfire, archive, __pakfire_extract, filter_callback, &data);
14eedd80
MT
841 if (r)
842 goto ERROR;
79824416
MT
843
844 // Finish the progressbar
32bbeefc
MT
845 r = pakfire_progress_finish(data.progress);
846 if (r)
847 goto ERROR;
79824416
MT
848
849ERROR:
32bbeefc
MT
850 if (data.progress)
851 pakfire_progress_unref(data.progress);
79824416
MT
852 if (data.writer)
853 archive_write_free(data.writer);
854
855 return r;
856}
1e0d6cca
MT
857
858// Common compression
859
860struct pakfire_compress {
861 // Reference to Pakfire
862 struct pakfire* pakfire;
863
864 // Flags
865 int flags;
866
867 // The archive to write to
868 struct archive* archive;
869
377c9b98
MT
870 // Resolver for hardlinks
871 struct archive_entry_linkresolver* linkresolver;
872
1e0d6cca
MT
873 // The filelist of all files to write
874 struct pakfire_filelist* filelist;
875
876 // The progressbar
877 struct pakfire_progressbar* progressbar;
878
879 // Digests to write to the archive
880 int digests;
881};
882
883static int pakfire_compress_progressbar_create(struct pakfire_progressbar** progressbar,
884 const char* message, int flags) {
885 int r;
886
887 // Create the progressbar
888 r = pakfire_progressbar_create(progressbar, NULL);
889 if (r)
890 return r;
891
892 // Add message
893 if (message) {
894 r = pakfire_progressbar_add_string(*progressbar, "%s", message);
895 if (r)
896 return r;
897 }
898
899 // Add bar
900 r = pakfire_progressbar_add_bar(*progressbar);
901 if (r)
902 return r;
903
904 // Add throughput
905 if (flags & PAKFIRE_COMPRESS_SHOW_THROUGHPUT) {
906 r = pakfire_progressbar_add_transfer_speed(*progressbar);
907 if (r)
908 return r;
909 }
910
bd94d753
MT
911 // Show how many bytes have been written
912 r = pakfire_progressbar_add_bytes_transferred(*progressbar);
913 if (r)
914 return r;
915
1e0d6cca
MT
916 // Add percentage
917 r = pakfire_progressbar_add_percentage(*progressbar);
918 if (r)
919 return r;
920
921 // Success
922 return 0;
923}
924
377c9b98
MT
925static int __pakfire_compress_entry(struct pakfire* pakfire, struct pakfire_file* file,
926 struct pakfire_compress* data, struct archive_entry* entry) {
1e0d6cca 927 FILE* f = NULL;
377c9b98 928 int r;
1e0d6cca 929
e990815d
MT
930 const char* path = archive_entry_pathname(entry);
931
932 // Remove any leading slahes
933 while (*path == '/')
934 path++;
935
936 archive_entry_set_pathname(entry, path);
937
1e0d6cca
MT
938 // Write the header
939 r = archive_write_header(data->archive, entry);
940 if (r) {
941 ERROR(pakfire, "Error writing file header: %s\n",
942 archive_error_string(data->archive));
943 goto ERROR;
944 }
945
377c9b98
MT
946 // Copy the data if there is any
947 if (archive_entry_size(entry)) {
1e0d6cca
MT
948 // Open the file
949 f = pakfire_file_open(file);
950 if (!f) {
951 r = 1;
952 goto ERROR;
953 }
954
955 // Copy the payload into the archive
956 r = pakfire_archive_copy_data_from_file(pakfire, data->archive, f);
957 if (r)
958 goto ERROR;
959 }
960
961 // Write trailer
962 r = archive_write_finish_entry(data->archive);
963 if (r)
964 goto ERROR;
965
377c9b98
MT
966ERROR:
967 if (f)
968 fclose(f);
969
970 return r;
971}
972
973static 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;
976 int r = 1;
977
978 struct pakfire_compress* data = (struct pakfire_compress*)p;
979
980 // Fetch the file size
981 const size_t size = pakfire_file_get_size(file);
982
983 // Generate file metadata into an archive entry
984 entry = pakfire_file_archive_entry(file, data->digests);
985 if (!entry) {
986 r = 1;
987 goto ERROR;
988 }
989
990 // Perform search for hardlinks
991 archive_entry_linkify(data->linkresolver, &entry, &sparse_entry);
992
993 // Write the main entry
994 if (entry) {
995 r = __pakfire_compress_entry(pakfire, file, data, entry);
996 if (r)
997 goto ERROR;
998 }
999
1000 // Write the sparse entry
1001 if (sparse_entry) {
1002 r = __pakfire_compress_entry(pakfire, file, data, sparse_entry);
1003 if (r)
1004 goto ERROR;
1005 }
1006
1007 // Query the link resolver for any more entries
1008 for (;;) {
1009 // Free the entry
1010 if (entry) {
1011 archive_entry_free(entry);
1012 entry = NULL;
1013 }
1014
1015 // Free the sparse entry
1016 if (sparse_entry) {
1017 archive_entry_free(sparse_entry);
1018 sparse_entry = NULL;
1019 }
1020
1021 // Fetch the next entry
1022 archive_entry_linkify(data->linkresolver, &entry, &sparse_entry);
1023 if (!entry)
1024 break;
1025
1026 // Write the entry to the archive
1027 r = __pakfire_compress_entry(pakfire, file, data, entry);
1028 if (r)
1029 goto ERROR;
1030 }
1031
1e0d6cca
MT
1032 // Update the progressbar
1033 if (data->progressbar)
1034 pakfire_progressbar_increment(data->progressbar, size);
1035
1036ERROR:
1e0d6cca
MT
1037 if (entry)
1038 archive_entry_free(entry);
377c9b98
MT
1039 if (sparse_entry)
1040 archive_entry_free(sparse_entry);
1e0d6cca
MT
1041
1042 return r;
1043}
1044
1045int pakfire_compress(struct pakfire* pakfire, struct archive* archive,
1046 struct pakfire_filelist* filelist, const char* message, int flags, int digests) {
1047 int r = 1;
1048
1049 struct pakfire_compress data = {
1050 .pakfire = pakfire,
1051 .archive = archive,
1052 .filelist = filelist,
1053 .flags = flags,
1054 .digests = digests,
1055 };
1056
1057 // Should we show a progress bar?
1058 const int no_progress = flags & PAKFIRE_COMPRESS_NO_PROGRESS;
1059
1060 // Fetch the length of the filelist
1061 const size_t size = pakfire_filelist_total_size(filelist);
1062
1063 // Create the progressbar
1064 if (!no_progress) {
1065 r = pakfire_compress_progressbar_create(&data.progressbar, message, flags);
1066 if (r)
1067 goto ERROR;
1068
1069 // Start progressbar
1070 pakfire_progressbar_start(data.progressbar, size);
1071 }
1072
377c9b98
MT
1073 // Setup the link resolver
1074 data.linkresolver = archive_entry_linkresolver_new();
1075 if (!data.linkresolver) {
1076 ERROR(pakfire, "Could not setup link resolver: m\n");
1077 goto ERROR;
1078 }
1079
1080 // Set the link resolver strategy
1081 archive_entry_linkresolver_set_strategy(data.linkresolver, archive_format(archive));
1082
1e0d6cca 1083 // Walk through the entire filelist
a3f3c077 1084 r = pakfire_filelist_walk(filelist, __pakfire_compress, &data, 0);
1e0d6cca
MT
1085 if (r)
1086 goto ERROR;
1087
1088 // Finish the progressbar
1089 if (data.progressbar)
1090 pakfire_progressbar_finish(data.progressbar);
1091
1092ERROR:
1093 if (data.progressbar)
1094 pakfire_progressbar_unref(data.progressbar);
377c9b98
MT
1095 if (data.linkresolver)
1096 archive_entry_linkresolver_free(data.linkresolver);
1e0d6cca
MT
1097
1098 return r;
1099}
e66c6bbc
MT
1100
1101int pakfire_compress_create_archive(struct pakfire* pakfire, struct archive** archive,
1102 FILE* f, const enum pakfire_compressions compression, const unsigned int level) {
1103 struct archive* a = NULL;
1104 char value[16];
1105 int r;
1106
1107 // Open a new archive
1108 a = archive_write_new();
1109 if (!a) {
1110 ERROR(pakfire, "archive_write_new() failed\n");
1111 r = 1;
1112 goto ERROR;
1113 }
1114
1115 // Use the PAX format
1116 r = archive_write_set_format_pax(a);
1117 if (r) {
1118 ERROR(pakfire, "Could not set format to PAX: %s\n", archive_error_string(a));
1119 goto ERROR;
1120 }
1121
1122 // Store any extended attributes in the SCHILY headers
1123 r = archive_write_set_format_option(a, "pax", "xattrheader", "SCHILY");
1124 if (r) {
1125 ERROR(pakfire, "Could not set xattrheader option: %s\n", archive_error_string(a));
1126 goto ERROR;
1127 }
1128
1129 switch (compression) {
1130 case PAKFIRE_COMPRESS_ZSTD:
1131 // Enable Zstd
1132 r = archive_write_add_filter_zstd(a);
1133 if (r) {
1134 ERROR(pakfire, "Could not enable Zstandard compression: %s\n",
1135 archive_error_string(a));
1136 goto ERROR;
1137 }
1138
1139 // Do not pad the last block
1140 archive_write_set_bytes_in_last_block(a, 1);
1141
1142 // Set compression level
1143 if (level) {
1144 r = pakfire_string_format(value, "%u", level);
1145 if (r)
1146 goto ERROR;
1147
1148 r = archive_write_set_filter_option(a, NULL, "compression-level", value);
1149 if (r) {
1150 ERROR(pakfire, "Could not set Zstandard compression level: %s\n",
1151 archive_error_string(a));
1152 goto ERROR;
1153 }
1154 }
177f5ff3
MT
1155
1156#if ARCHIVE_VERSION_NUMBER >= 3006000
1157 // Fetch numbers of processors
1158 long processors = sysconf(_SC_NPROCESSORS_ONLN);
1159
1160 if (processors > 1) {
1161 r = pakfire_string_format(value, "%ld", processors);
1162 if (r) {
1163 ERROR(pakfire, "Could not format threads: %m\n");
1164 goto ERROR;
1165 }
1166
1167 // Try using multiple threads
1d7a641f 1168 r = archive_write_set_filter_option(a, NULL, "threads", value);
177f5ff3 1169 if (r) {
1d7a641f
MT
1170 ERROR(pakfire, "Could not enable %ld threads for compression: %s\n",
1171 processors, archive_error_string(a));
177f5ff3
MT
1172 goto ERROR;
1173 }
1174 }
1175#endif
e66c6bbc
MT
1176 }
1177
1178 // Write archive to f
1179 if (f) {
1180 r = archive_write_open_FILE(a, f);
1181 if (r) {
1182 ERROR(pakfire, "archive_write_open_FILE() failed: %s\n", archive_error_string(a));
1183 goto ERROR;
1184 }
1185 }
1186
1187 // Success
1188 *archive = a;
1189
1190 return 0;
1191
1192ERROR:
1193 if (a)
1194 archive_write_free(a);
1195
1196 return r;
1197}