]> git.ipfire.org Git - people/ms/pakfire.git/blame_incremental - src/libpakfire/packager.c
packager: Add new function that accepts a file object
[people/ms/pakfire.git] / src / libpakfire / packager.c
... / ...
CommitLineData
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
21#include <errno.h>
22#include <fcntl.h>
23#include <linux/limits.h>
24#include <stdlib.h>
25#include <sys/stat.h>
26#include <sys/types.h>
27#include <time.h>
28#include <unistd.h>
29
30#include <archive.h>
31#include <archive_entry.h>
32
33#include <json.h>
34
35#include <pakfire/archive.h>
36#include <pakfire/constants.h>
37#include <pakfire/logging.h>
38#include <pakfire/package.h>
39#include <pakfire/packager.h>
40#include <pakfire/pakfire.h>
41#include <pakfire/pwd.h>
42#include <pakfire/string.h>
43#include <pakfire/util.h>
44
45struct pakfire_packager {
46 struct pakfire* pakfire;
47 int nrefs;
48 time_t time_created;
49
50 struct pakfire_package* pkg;
51 char filename[PATH_MAX];
52
53 // Reader
54 struct archive* reader;
55
56 // Payload
57 struct archive* payload;
58 FILE* fpayload;
59 size_t installsize;
60 unsigned int files;
61
62 struct pakfire_scriptlet** scriptlets;
63 unsigned int num_scriptlets;
64};
65
66static int pakfire_packager_create_payload(struct pakfire_packager* p) {
67 char path[] = PAKFIRE_TMP_DIR "/pakfire-payload.XXXXXX";
68
69 p->payload = archive_write_new();
70 if (!p->payload) {
71 ERROR(p->pakfire, "archive_write_new() failed\n");
72 return 1;
73 }
74
75 // Use the PAX format
76 int r = archive_write_set_format_pax(p->payload);
77 if (r) {
78 ERROR(p->pakfire, "Could not set format to PAX: %s\n",
79 archive_error_string(p->payload));
80 return r;
81 }
82
83 // Create a new temporary file
84 p->fpayload = pakfire_mktemp(path);
85 if (!p->fpayload)
86 return 1;
87
88 // Unlink the file straight away
89 unlink(path);
90
91 // Write archive to file
92 r = archive_write_open_FILE(p->payload, p->fpayload);
93 if (r)
94 return r;
95
96 // Add a requirement for the cryptographic algorithms we are using
97 pakfire_package_add_requires(p->pkg, "pakfire(Digest-SHA512)");
98 pakfire_package_add_requires(p->pkg, "pakfire(Digest-SHA256)");
99
100 return 0;
101}
102
103static void pakfire_packager_free(struct pakfire_packager* packager) {
104 // Scriptlets
105 if (packager->scriptlets) {
106 for (unsigned int i = 0; i < packager->num_scriptlets; i++)
107 pakfire_scriptlet_unref(packager->scriptlets[i]);
108 free(packager->scriptlets);
109 }
110
111 // Payload
112 if (packager->payload)
113 archive_write_free(packager->payload);
114
115 if (packager->fpayload)
116 fclose(packager->fpayload);
117
118 if (packager->reader)
119 archive_read_free(packager->reader);
120
121 pakfire_package_unref(packager->pkg);
122 pakfire_unref(packager->pakfire);
123 free(packager);
124}
125
126int pakfire_packager_create(struct pakfire_packager** packager,
127 struct pakfire* pakfire, struct pakfire_package* pkg) {
128 struct pakfire_packager* p = calloc(1, sizeof(*p));
129 if (!p)
130 return ENOMEM;
131
132 int r = 1;
133
134 // Save creation time
135 p->time_created = time(NULL);
136
137 // Initialize reference counting
138 p->nrefs = 1;
139
140 // Store a reference to Pakfire
141 p->pakfire = pakfire_ref(pakfire);
142
143 // Store a reference to the package
144 p->pkg = pakfire_package_ref(pkg);
145
146 // Set build host
147 pakfire_package_set_build_host(pkg, pakfire_hostname());
148
149 // Set build time
150 pakfire_package_set_build_time(pkg, p->time_created);
151
152 // Create reader
153 p->reader = pakfire_make_archive_disk_reader(p->pakfire, 1);
154 if (!p->reader)
155 goto ERROR;
156
157 // Start payload
158 r = pakfire_packager_create_payload(p);
159 if (r)
160 goto ERROR;
161
162 *packager = p;
163
164 return 0;
165
166ERROR:
167 pakfire_packager_free(p);
168
169 return r;
170}
171
172struct pakfire_packager* pakfire_packager_ref(
173 struct pakfire_packager* packager) {
174 ++packager->nrefs;
175
176 return packager;
177}
178
179struct pakfire_packager* pakfire_packager_unref(
180 struct pakfire_packager* packager) {
181 if (--packager->nrefs > 0)
182 return packager;
183
184 pakfire_packager_free(packager);
185
186 return NULL;
187}
188
189const char* pakfire_packager_filename(struct pakfire_packager* packager) {
190 if (!*packager->filename) {
191 const char* filename = pakfire_package_get_filename(packager->pkg);
192
193 // Add arch
194 if (pakfire_package_is_source(packager->pkg))
195 pakfire_string_set(packager->filename, filename);
196 else {
197 const char* arch = pakfire_package_get_arch(packager->pkg);
198
199 pakfire_string_format(packager->filename, "%s/%s", arch, filename);
200 }
201 }
202
203 return packager->filename;
204}
205
206static struct archive_entry* pakfire_packager_create_file(
207 struct pakfire_packager* packager, const char* filename, size_t size, mode_t mode) {
208 // Create a new file entry
209 struct archive_entry* entry = archive_entry_new();
210 if (!entry)
211 return NULL;
212
213 // Set filename
214 archive_entry_set_pathname(entry, filename);
215
216 // This is a regular file
217 archive_entry_set_filetype(entry, AE_IFREG);
218 archive_entry_set_perm(entry, mode);
219
220 // Set size
221 archive_entry_set_size(entry, size);
222
223 // Set ownership
224 archive_entry_set_uname(entry, "root");
225 archive_entry_set_uid(entry, 0);
226 archive_entry_set_gname(entry, "root");
227 archive_entry_set_gid(entry, 0);
228
229 // Set times
230 archive_entry_set_birthtime(entry, packager->time_created, 0);
231 archive_entry_set_ctime(entry, packager->time_created, 0);
232 archive_entry_set_mtime(entry, packager->time_created, 0);
233 archive_entry_set_atime(entry, packager->time_created, 0);
234
235 return entry;
236}
237
238static int pakfire_packager_write_file_from_buffer(struct pakfire_packager* packager,
239 struct archive* a, const char* filename, mode_t mode, const char* buffer) {
240 size_t size = strlen(buffer);
241
242 // Create a new file
243 struct archive_entry* entry = pakfire_packager_create_file(packager, filename, size, mode);
244 if (!entry) {
245 ERROR(packager->pakfire, "Could not create file '%s'\n", filename);
246 return 1;
247 }
248
249 // This is the end of the header
250 int r = archive_write_header(a, entry);
251 if (r) {
252 ERROR(packager->pakfire, "Error writing header: %s\n", archive_error_string(a));
253 goto ERROR;
254 }
255
256 // Write content
257 r = archive_write_data(a, buffer, strlen(buffer));
258 if (r < 0) {
259 ERROR(packager->pakfire, "Error writing data: %s\n", archive_error_string(a));
260 goto ERROR;
261 }
262
263 // Success
264 r = 0;
265
266ERROR:
267 archive_entry_free(entry);
268
269 return r;
270}
271
272static int pakfire_packager_write_format(struct pakfire_packager* packager,
273 struct archive* a) {
274 const char buffer[] = TO_STRING(PACKAGE_FORMAT) "\n";
275
276 DEBUG(packager->pakfire, "Writing package format\n");
277
278 int r = pakfire_packager_write_file_from_buffer(packager, a,
279 "pakfire-format", 0444, buffer);
280 if (r)
281 return r;
282
283 // Add package format marker
284 pakfire_package_add_requires(packager->pkg,
285 "pakfire(PackageFormat-" TO_STRING(PACKAGE_FORMAT) ")");
286
287 return 0;
288}
289
290static char* pakfire_packager_make_metadata(struct pakfire_packager* packager) {
291 char* result = NULL;
292
293 // Convert all package metadata to JSON
294 struct json_object* md = pakfire_package_to_json(packager->pkg);
295 if (!md)
296 goto ERROR;
297
298 // Serialize JSON to file
299 const char* s = json_object_to_json_string_ext(md, 0);
300 if (!s)
301 goto ERROR;
302
303 // Copy result onto heap
304 result = strdup(s);
305 if (!result)
306 goto ERROR;
307
308ERROR:
309 // Free metadata
310 if (md)
311 json_object_put(md);
312
313 return result;
314}
315
316static int pakfire_packager_write_metadata(struct pakfire_packager* packager,
317 struct archive* a) {
318 // Make metadata
319 char* buffer = pakfire_packager_make_metadata(packager);
320 if (!buffer)
321 return 1;
322
323 DEBUG(packager->pakfire, "Generated package metadata:\n%s\n", buffer);
324
325 // Write buffer
326 int r = pakfire_packager_write_file_from_buffer(packager, a,
327 ".PKGINFO", 0444, buffer);
328
329 free(buffer);
330
331 return r;
332}
333
334static int pakfire_packager_copy_archive(struct pakfire_packager* packager,
335 struct archive* a, FILE* f) {
336 struct archive* payload = NULL;
337 struct archive_entry* entry = NULL;
338 int r;
339
340 DEBUG(packager->pakfire, "Copying payload\n");
341
342 // Reset fd to beginning of the file
343 rewind(f);
344
345 // Re-open the payload archive for reading
346 payload = archive_read_new();
347 if (!payload) {
348 ERROR(packager->pakfire, "Could not create archive reader: %m\n");
349 r = 1;
350 goto ERROR;
351 }
352
353 // We expect a tar archive
354 r = archive_read_support_format_tar(payload);
355 if (r) {
356 ERROR(packager->pakfire, "Could not add support for tar: %s\n",
357 archive_error_string(payload));
358 goto ERROR;
359 }
360
361 // Open the file handle
362 r = archive_read_open_FILE(payload, f);
363 if (r) {
364 ERROR(packager->pakfire, "Could not open payload: %s\n",
365 archive_error_string(payload));
366 goto ERROR;
367 }
368
369 // Copy all files into our archive
370 for (;;) {
371 r = archive_read_next_header(payload, &entry);
372
373 // End when we have reached the end of the archive
374 if (r == ARCHIVE_EOF) {
375 r = 0;
376 break;
377 }
378
379 // Raise any other errors
380 else if (r) {
381 ERROR(packager->pakfire, "Could not read next header: %s\n",
382 archive_error_string(payload));
383 goto ERROR;
384 }
385
386 // Write the entry
387 r = archive_write_header(a, entry);
388 if (r) {
389 ERROR(packager->pakfire, "Could not write entry: %s\n",
390 archive_error_string(a));
391 goto ERROR;
392 }
393
394 // Copy the data
395 r = pakfire_archive_copy_data(packager->pakfire, payload, a);
396 if (r)
397 goto ERROR;
398 }
399
400 // Success
401 r = 0;
402
403ERROR:
404 if (payload)
405 archive_read_free(payload);
406
407 return r;
408}
409
410static int pakfire_packager_write_scriptlet(struct pakfire_packager* packager,
411 struct archive* a, struct pakfire_scriptlet* scriptlet) {
412 char filename[PATH_MAX];
413 size_t size;
414 int r;
415
416 // Fetch type
417 const char* type = pakfire_scriptlet_get_type(scriptlet);
418
419 DEBUG(packager->pakfire, "Writing scriptlet '%s' to package\n", type);
420
421 // Make filename
422 r = pakfire_string_format(filename, ".scriptlets/%s", type);
423 if (r)
424 return r;
425
426 // Fetch scriptlet
427 const char* data = pakfire_scriptlet_get_data(scriptlet, &size);
428
429 // Write file
430 return pakfire_packager_write_file_from_buffer(packager, a, filename, 0544, data);
431}
432
433/*
434 This function is being called at the end when all data has been added to the package.
435
436 It will create a new archive and write the package to the given file descriptor.
437*/
438int pakfire_packager_finish(struct pakfire_packager* packager, FILE* f) {
439 struct archive* a = NULL;
440 int r = 1;
441
442 // Close the payload
443 if (packager->payload) {
444 r = archive_write_free(packager->payload);
445 if (r) {
446 ERROR(packager->pakfire, "Could not close payload: %s\n",
447 archive_error_string(packager->payload));
448 goto ERROR;
449 }
450 packager->payload = NULL;
451 }
452
453 // Add requires feature markers
454 if (pakfire_package_has_rich_deps(packager->pkg))
455 pakfire_package_add_requires(packager->pkg, "pakfire(RichDependencies)");
456
457 // Store total install size
458 pakfire_package_set_installsize(packager->pkg, packager->installsize);
459
460 // Dump package metadata
461 char* dump = pakfire_package_dump(packager->pkg, PAKFIRE_PKG_DUMP_LONG);
462 if (dump) {
463 INFO(packager->pakfire, "%s\n", dump);
464 free(dump);
465 }
466
467 // Open a new archive
468 a = archive_write_new();
469 if (!a) {
470 ERROR(packager->pakfire, "archive_write_new() failed\n");
471 goto ERROR;
472 }
473
474 // Use the PAX format
475 r = archive_write_set_format_pax(a);
476 if (r) {
477 ERROR(packager->pakfire, "Could not set format to PAX: %s\n",
478 archive_error_string(a));
479 goto ERROR;
480 }
481
482 // Store any extended attributes in the SCHILY headers
483 r = archive_write_set_format_option(a, "pax", "xattrheader", "SCHILY");
484 if (r) {
485 ERROR(packager->pakfire, "Could not set xattrheader option: %s\n",
486 archive_error_string(a));
487 return r;
488 }
489
490 // Enable Zstd
491 r = archive_write_add_filter_zstd(a);
492 if (r) {
493 ERROR(packager->pakfire, "Could not enable Zstandard compression: %s\n",
494 archive_error_string(a));
495 return r;
496 }
497
498 // Set compression level to highest
499 r = archive_write_set_filter_option(a, NULL, "compression-level", "22");
500 if (r) {
501 ERROR(packager->pakfire, "Could not set Zstandard compression level: %s\n",
502 archive_error_string(a));
503 return r;
504 }
505
506 // Add feature marker
507 pakfire_package_add_requires(packager->pkg, "pakfire(Compress-Zstandard)");
508
509 // Do not pad the last block
510 archive_write_set_bytes_in_last_block(a, 1);
511
512 // Write archive to f
513 r = archive_write_open_FILE(a, f);
514 if (r) {
515 ERROR(packager->pakfire, "archive_write_open_FILE() failed: %s\n",
516 archive_error_string(a));
517 goto ERROR;
518 }
519
520 // Start with the format file
521 r = pakfire_packager_write_format(packager, a);
522 if (r) {
523 ERROR(packager->pakfire, "Could not add format file to archive: %s\n",
524 archive_error_string(a));
525 goto ERROR;
526 }
527
528 // Write the metadata
529 r = pakfire_packager_write_metadata(packager, a);
530 if (r) {
531 ERROR(packager->pakfire, "Could not add metadata file to archive: %s\n",
532 archive_error_string(a));
533 goto ERROR;
534 }
535
536 // Write scriptlets
537 for (unsigned int i = 0; i < packager->num_scriptlets; i++) {
538 r = pakfire_packager_write_scriptlet(packager, a, packager->scriptlets[i]);
539 if (r) {
540 ERROR(packager->pakfire, "Could not add scriptlet to the archive: %m\n");
541 goto ERROR;
542 }
543 }
544
545 // Write the payload
546 if (packager->files) {
547 r = pakfire_packager_copy_archive(packager, a, packager->fpayload);
548 if (r) {
549 ERROR(packager->pakfire, "Could not add payload to archive: %s\n",
550 archive_error_string(a));
551 goto ERROR;
552 }
553 }
554
555 // Success
556 r = 0;
557
558ERROR:
559 if (a)
560 archive_write_free(a);
561
562 return r;
563}
564
565int pakfire_packager_finish_to_directory(struct pakfire_packager* packager,
566 const char* target, char** result) {
567 char path[PATH_MAX];
568 char tmppath[PATH_MAX];
569 int r = 1;
570
571 // target cannot be empty
572 if (!target) {
573 errno = EINVAL;
574 return 1;
575 }
576
577 // Get the filename of the package
578 const char* filename = pakfire_packager_filename(packager);
579 if (!filename) {
580 ERROR(packager->pakfire, "Could not generate filename for package: %m\n");
581 r = 1;
582 goto ERROR;
583 }
584
585 // Make the package path
586 r = pakfire_string_format(path, "%s/%s", target, filename);
587 if (r)
588 goto ERROR;
589
590 // Create the parent directory
591 r = pakfire_mkparentdir(path, 0755);
592 if (r)
593 goto ERROR;
594
595 // Create a temporary file in the target directory
596 r = pakfire_string_format(tmppath, "%s.XXXXXX", path);
597 if (r)
598 goto ERROR;
599
600 // Create a temporary result file
601 FILE* f = pakfire_mktemp(tmppath);
602 if (!f)
603 goto ERROR;
604
605 // Write the finished package
606 r = pakfire_packager_finish(packager, f);
607 fclose(f);
608
609 if (r) {
610 ERROR(packager->pakfire, "pakfire_packager_finish() failed: %m\n");
611 goto ERROR;
612 }
613
614 // Move the temporary file to destination
615 r = rename(tmppath, path);
616 if (r) {
617 ERROR(packager->pakfire, "Could not move %s to %s: %m\n", tmppath, path);
618 goto ERROR;
619 }
620
621 INFO(packager->pakfire, "Package written to %s\n", path);
622
623 // Store result path if requested
624 if (result) {
625 *result = strdup(path);
626 if (!*result) {
627 r = 1;
628 goto ERROR;
629 }
630 }
631
632 // Success
633 r = 0;
634
635ERROR:
636 // Remove temporary file
637 if (r && *tmppath)
638 unlink(tmppath);
639
640 return r;
641}
642
643static int pakfire_packager_add_file(struct pakfire_packager* packager,
644 struct pakfire_file* file) {
645 struct archive_entry* entry = NULL;
646 FILE* f = NULL;
647 int r;
648
649 // Check input
650 if (!file) {
651 errno = EINVAL;
652 return 1;
653 }
654
655 // Payload has already been closed
656 if (!packager->payload) {
657 ERROR(packager->pakfire, "Payload has already been closed\n");
658 errno = EPERM;
659 return 1;
660 }
661
662 // Fetch path
663 const char* path = pakfire_file_get_path(file);
664
665 // Fetch filetype
666 const mode_t filetype = pakfire_file_get_type(file);
667
668 // Files cannot have an empty path
669 if (!*path) {
670 ERROR(packager->pakfire, "Cannot add a file with an empty path\n");
671 errno = EPERM;
672 return 1;
673
674 // Hidden files cannot be added
675 } else if (*path == '.') {
676 ERROR(packager->pakfire, "Hidden files cannot be added to a package: %s\n", path);
677 errno = EPERM;
678 return 1;
679 }
680
681 DEBUG(packager->pakfire, "Adding file to payload: %s\n", path);
682
683 // Overwrite a couple of things for source archives
684 if (pakfire_package_is_source(packager->pkg)) {
685#if 0
686 // Reset permissions
687 pakfire_file_set_perms(file, 0644);
688#endif
689
690 // Reset file ownership
691 pakfire_file_set_user(file, "root");
692 pakfire_file_set_group(file, "root");
693 }
694
695 // Open the file
696 f = pakfire_file_open(file);
697 if (!f)
698 goto ERROR;
699
700#if 0
701 // Add digests for regular files
702 if (filetype == AE_IFREG) {
703 r = pakfire_packager_compute_digests(packager, entry, f);
704 if (r) {
705 ERROR(packager->pakfire, "Could not compute digests: %m\n")
706 goto ERROR;
707 }
708
709 // Rewind the file descriptor
710 rewind(f);
711 }
712#endif
713
714 // Generate file metadata into an archive entry
715 entry = pakfire_file_archive_entry(file);
716 if (!entry)
717 goto ERROR;
718
719 // Write the header
720 r = archive_write_header(packager->payload, entry);
721 if (r) {
722 ERROR(packager->pakfire, "Error writing file header: %s\n",
723 archive_error_string(packager->payload));
724 goto ERROR;
725 }
726
727 // Copy the data of regular files
728 if (filetype == S_IFREG) {
729 // Copy the payload into the archive
730 r = pakfire_archive_copy_data_from_file(packager->pakfire, packager->payload, f);
731 if (r)
732 goto ERROR;
733 }
734
735 // Increment installsize
736 packager->installsize += pakfire_file_get_size(file);
737
738 // Increment file counter
739 packager->files++;
740
741ERROR:
742 if (entry)
743 archive_entry_free(entry);
744 if (f)
745 fclose(f);
746
747 return r;
748}
749
750int pakfire_packager_add(struct pakfire_packager* packager,
751 const char* sourcepath, const char* path) {
752 struct pakfire_file* file = NULL;
753 int r = 1;
754
755 // Check if path is set
756 if (!sourcepath) {
757 errno = EINVAL;
758 return 1;
759 }
760
761 // Use basename if path isn't set
762 if (!path) {
763 path = strrchr(sourcepath, '/');
764 if (path)
765 path++;
766 }
767
768 // Create a new file entry
769 struct archive_entry* entry = archive_entry_new();
770 if (!entry)
771 return 1;
772
773 // Set the source path
774 archive_entry_copy_sourcepath(entry, sourcepath);
775
776 // Set path in archive
777 if (path)
778 archive_entry_set_pathname(entry, path);
779
780 // Read all attributes from file
781 r = archive_read_disk_entry_from_file(packager->reader, entry, -1, NULL);
782 if (r) {
783 ERROR(packager->pakfire, "Could not read attributes from %s: %m\n", path);
784 goto ERROR;
785 }
786
787 // Convert to file
788 r = pakfire_file_create_from_archive_entry(&file, packager->pakfire, entry);
789 if (r)
790 goto ERROR;
791
792 // Call the main function
793 r = pakfire_packager_add_file(packager, file);
794
795ERROR:
796 if (file)
797 pakfire_file_unref(file);
798
799 return r;
800}
801
802int pakfire_packager_add_scriptlet(struct pakfire_packager* packager,
803 struct pakfire_scriptlet* scriptlet) {
804 if (!scriptlet) {
805 errno = EINVAL;
806 return 1;
807 }
808
809 // Extend array
810 packager->scriptlets = reallocarray(packager->scriptlets,
811 packager->num_scriptlets + 1, sizeof(*packager->scriptlets));
812 if (!packager->scriptlets)
813 return 1;
814
815 // Append scriptlet
816 packager->scriptlets[packager->num_scriptlets++] = pakfire_scriptlet_ref(scriptlet);
817
818 return 0;
819}