]> git.ipfire.org Git - people/stevee/pakfire.git/blame - src/libpakfire/build.c
snapshots: Pass path instead of file descriptor
[people/stevee/pakfire.git] / src / libpakfire / build.c
CommitLineData
1a276007
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
21#include <errno.h>
22#include <stdlib.h>
22a0733e 23#include <sys/mount.h>
cee6363a 24#include <unistd.h>
57e2cf99 25#include <uuid/uuid.h>
1a276007
MT
26
27#include <pakfire/build.h>
f877fdfa 28#include <pakfire/cgroup.h>
1a276007 29#include <pakfire/dist.h>
ae7968a7 30#include <pakfire/file.h>
e545f6ec 31#include <pakfire/i18n.h>
8e99f22d 32#include <pakfire/jail.h>
1a276007 33#include <pakfire/logging.h>
163851bc 34#include <pakfire/mount.h>
4c07774f 35#include <pakfire/package.h>
48c6f2e7 36#include <pakfire/packager.h>
1a276007
MT
37#include <pakfire/parser.h>
38#include <pakfire/private.h>
4651122b 39#include <pakfire/repo.h>
99a56775 40#include <pakfire/request.h>
106d2edd 41#include <pakfire/scriptlet.h>
c0f2502a 42#include <pakfire/snapshot.h>
1a276007
MT
43#include <pakfire/util.h>
44
5a06668c
MT
45#define CCACHE_DIR "/var/cache/ccache"
46
882ae03b
MT
47// We guarantee 2 GiB of memory to every build container
48#define PAKFIRE_BUILD_GUARANTEED_MEMORY (size_t)2 * 1024 * 1024 * 1024
49
de4c8fe6
MT
50// We allow only up to 2048 processes/threads for every build container
51#define PAKFIRE_BUILD_PID_LIMIT (size_t)2048
52
abbad00b
MT
53struct pakfire_build {
54 struct pakfire* pakfire;
55 int nrefs;
56
57 // Flags
58 int flags;
59
60 // Build ID
61 uuid_t id;
f877fdfa
MT
62 char _id[UUID_STR_LEN];
63
a84624be
MT
64 char target[PATH_MAX];
65
f877fdfa
MT
66 // cgroup
67 struct pakfire_cgroup* cgroup;
753ddf74
MT
68
69 // Jail
70 struct pakfire_jail* jail;
e545f6ec
MT
71
72 // Local build repo
73 struct pakfire_repo* repo;
8dbb69f9
MT
74
75 // States
76 int init:1;
abbad00b
MT
77};
78
1a276007
MT
79static const char* stages[] = {
80 "prepare",
81 "build",
82 "test",
83 "install",
84 NULL,
85};
86
87#define TEMPLATE \
88 "#!/bin/bash --login\n" \
89 "\n" \
90 "set -e\n" \
91 "set -x\n" \
92 "\n" \
93 "%%{_%s}\n" \
94 "\n" \
95 "exit 0\n"
96
c0f2502a
MT
97static int pakfire_build_has_flag(struct pakfire_build* build, int flag) {
98 return build->flags & flag;
99}
100
5f6e42a2 101/*
7a347de7 102 This function enables the local repository so that it can be accessed by
5f6e42a2
MT
103 a pakfire instance running inside the chroot.
104*/
105static int pakfire_build_enable_repos(struct pakfire_build* build) {
106 char repopath[PATH_MAX];
5f6e42a2
MT
107 int r = 1;
108
5f6e42a2 109 // Fetch repository configuration
e545f6ec 110 char* config = pakfire_repo_get_config(build->repo);
5f6e42a2
MT
111 if (!config) {
112 ERROR(build->pakfire, "Could not generate repository configuration: %m\n");
113 goto ERROR;
114 }
115
e545f6ec 116 const char* path = pakfire_repo_get_path(build->repo);
5f6e42a2
MT
117
118 // Make sure the source directory exists
119 r = pakfire_mkdir(path, 0700);
120 if (r && errno != EEXIST) {
121 ERROR(build->pakfire, "Could not create repository directory %s: %m\n", path);
122 goto ERROR;
123 }
124
125 // Bind-mount the repository data read-only
7a347de7 126 r = pakfire_jail_bind(build->jail, path, path, MS_RDONLY);
5f6e42a2
MT
127 if (r) {
128 ERROR(build->pakfire, "Could not bind-mount the repository at %s: %m\n", path);
129 goto ERROR;
130 }
131
132 // Make path for configuration file
133 r = pakfire_make_path(build->pakfire, repopath,
134 PAKFIRE_CONFIG_DIR "/repos/" PAKFIRE_REPO_LOCAL ".repo");
135 if (r < 0)
136 goto ERROR;
137
c2803561
MT
138 // Write repository configuration
139 r = pakfire_file_write(build->pakfire, repopath, 0, 0, 0644, "%s", config);
140 if (r) {
5f6e42a2 141 ERROR(build->pakfire, "Could not write repository configuration: %m\n");
5f6e42a2
MT
142 goto ERROR;
143 }
144
5f6e42a2 145ERROR:
5f6e42a2
MT
146 if (config)
147 free(config);
5f6e42a2
MT
148
149 return r;
150}
151
152/*
153 Drops the user into a shell
154*/
155static int pakfire_build_shell(struct pakfire_build* build) {
156 int r;
157
158 // Export local repository if running in interactive mode
159 r = pakfire_build_enable_repos(build);
160 if (r)
161 return r;
162
163 // Run shell
164 return pakfire_jail_shell(build->jail);
165}
166
779e16de 167static int pakfire_build_run_script(struct pakfire_build* build, const char* filename,
12b9b39f 168 const char* args[], char** output) {
8d0f3a35
MT
169 char* script = NULL;
170 size_t size = 0;
171 char path[PATH_MAX];
172
779e16de 173 DEBUG(build->pakfire, "Running build script '%s'...\n", filename);
8d0f3a35
MT
174
175 // Make the source path
176 pakfire_path_join(path, PAKFIRE_SCRIPTS_DIR, filename);
177
178 // Open the source script
179 FILE* f = fopen(path, "r");
180 if (!f) {
779e16de 181 ERROR(build->pakfire, "Could not open %s: %m\n", path);
8d0f3a35
MT
182 return 1;
183 }
184
185 // Read the script into memory
186 int r = pakfire_read_file_into_buffer(f, &script, &size);
187 if (r) {
779e16de 188 ERROR(build->pakfire, "Could not read script into memory: %m\n");
8d0f3a35
MT
189 goto ERROR;
190 }
191
8e99f22d 192 // Execute the script
001bcbd7 193 r = pakfire_jail_exec_script(build->jail, script, size, args, output);
8d0f3a35 194 if (r) {
779e16de 195 ERROR(build->pakfire, "Script '%s' failed with status %d\n", filename, r);
8d0f3a35
MT
196 }
197
198ERROR:
199 if (script)
200 free(script);
201
202 return r;
203}
204
bd95a433 205static int pakfire_build_find_dependencies(struct pakfire_build* build,
1bbbfb9e 206 struct pakfire_package* pkg, struct pakfire_filelist* filelist, const char* buildroot) {
12b9b39f
MT
207 char* provides = NULL;
208 char* requires = NULL;
5b0b3dc2
MT
209 char path[PATH_MAX];
210
211 // Allocate path to write the filelist to
bd95a433 212 int r = pakfire_make_path(build->pakfire, path, "tmp/.pakfire-filelist.XXXXXX");
5b0b3dc2
MT
213 if (r < 0)
214 return 1;
215
216 // Create a temporary file
217 FILE* f = pakfire_mktemp(path);
218 if (!f)
219 goto ERROR;
220
221 // Write filelist to the temporary file
222 r = pakfire_filelist_export(filelist, f);
223 fclose(f);
224 if (r) {
bd95a433 225 ERROR(build->pakfire, "Could not export filelist: %m\n");
5b0b3dc2
MT
226 goto ERROR;
227 }
228
bd95a433 229 const char* root = pakfire_get_path(build->pakfire);
5b0b3dc2
MT
230
231 // Pass buildroot and the filelist as arguments
232 const char* args[] = {
233 buildroot, pakfire_path_relpath(root, path), NULL
234 };
235
236 // Find all provides
779e16de 237 r = pakfire_build_run_script(build, "find-provides", args, &provides);
5b0b3dc2 238 if (r) {
bd95a433 239 ERROR(build->pakfire, "find-provides returned with error %d\n", r);
5b0b3dc2
MT
240 goto ERROR;
241 }
242
243 // Find all requires
779e16de 244 r = pakfire_build_run_script(build, "find-requires", args, &requires);
5b0b3dc2 245 if (r) {
bd95a433 246 ERROR(build->pakfire, "find-requires returned with error %d\n", r);
5b0b3dc2
MT
247 goto ERROR;
248 }
249
250 // Add all provides to the package
251 if (provides) {
83d93f33
MT
252 r = pakfire_str2deps(build->pakfire, pkg,
253 pakfire_package_add_provides, provides);
254 if (r) {
255 ERROR(build->pakfire, "Could not add provides: %m\n");
256 goto ERROR;
5b0b3dc2
MT
257 }
258 }
259
260 // Add all requires to the package
261 if (requires) {
83d93f33
MT
262 r = pakfire_str2deps(build->pakfire, pkg,
263 pakfire_package_add_requires, requires);
264 if (r) {
265 ERROR(build->pakfire, "Could not add provides: %m\n");
266 goto ERROR;
5b0b3dc2
MT
267 }
268 }
269
270 // Success
271 r = 0;
272
273ERROR:
12b9b39f 274 if (provides)
5b0b3dc2 275 free(provides);
12b9b39f 276 if (requires)
5b0b3dc2 277 free(requires);
5b0b3dc2
MT
278 unlink(path);
279
280 return r;
281}
282
73543ae3
MT
283static int append_to_array(const char*** array, const char* s) {
284 unsigned int length = 0;
285
286 // Determine the length of the existing array
287 if (*array) {
288 for (const char** element = *array; *element; element++)
289 length++;
290 }
291
292 // Allocate space
293 *array = reallocarray(*array, length + 2, sizeof(**array));
294 if (!*array)
295 return 1;
296
297 // Append s and terminate the array
298 (*array)[length] = s;
299 (*array)[length + 1] = NULL;
300
301 return 0;
302}
303
adfa3573
MT
304static int pakfire_build_package_add_files(struct pakfire_build* build,
305 struct pakfire_parser* makefile, const char* buildroot, const char* namespace,
306 struct pakfire_package* pkg, struct pakfire_packager* packager) {
1bbbfb9e 307 struct pakfire_filelist* filelist = NULL;
73543ae3
MT
308 char path[PATH_MAX];
309 int r = 1;
310
311 char** files = NULL;
312 const char** includes = NULL;
313 const char** excludes = NULL;
314
315 // Fetch filelist from makefile
316 files = pakfire_parser_get_split(makefile, namespace, "files", '\n');
317
318 // No files to package?
319 if (!files)
320 return 0;
321
73543ae3 322 // Convert to absolute path
adfa3573 323 pakfire_make_path(build->pakfire, path, buildroot);
73543ae3
MT
324
325 // Split into includes and excludes
326 for (char** file = files; *file; file++) {
327 if (**file == '!')
328 r = append_to_array(&excludes, *file);
329 else
330 r = append_to_array(&includes, *file);
331 if (r)
332 goto ERROR;
333 }
334
335 // Allocate a new filelist
adfa3573 336 r = pakfire_filelist_create(&filelist, build->pakfire);
73543ae3
MT
337 if (r)
338 goto ERROR;
339
340 // Scan for files
341 r = pakfire_filelist_scan(filelist, path, includes, excludes);
342 if (r)
343 goto ERROR;
344
ae7968a7 345 const size_t length = pakfire_filelist_size(filelist);
adfa3573 346 DEBUG(build->pakfire, "%zu file(s) found\n", length);
73543ae3 347
84556307
MT
348 // Nothing to do if the filelist is empty
349 if (!length)
350 goto ERROR;
351
5b0b3dc2 352 // Find dependencies
bd95a433 353 r = pakfire_build_find_dependencies(build, pkg, filelist, buildroot);
5b0b3dc2 354 if (r) {
adfa3573 355 ERROR(build->pakfire, "Finding dependencies failed: %m\n");
5b0b3dc2
MT
356 goto ERROR;
357 }
358
ae7968a7
MT
359 // Add all files to the package
360 for (unsigned int i = 0; i < length; i++) {
5803b5f6 361 struct pakfire_file* file = pakfire_filelist_get(filelist, i);
ae7968a7
MT
362
363 // Add the file to the package
364 r = pakfire_packager_add(
365 packager,
366 pakfire_file_get_abspath(file),
367 pakfire_file_get_path(file)
368 );
369 if (r) {
370 pakfire_file_unref(file);
371 goto ERROR;
372 }
373
3fca5032
MT
374 // Remove the file after it was packaged
375 r = pakfire_file_cleanup(file);
376 if (r) {
377 pakfire_file_unref(file);
378 goto ERROR;
379 }
380
ae7968a7
MT
381 pakfire_file_unref(file);
382 }
73543ae3
MT
383
384ERROR:
385 if (filelist)
386 pakfire_filelist_unref(filelist);
387 if (files) {
388 for (char** file = files; *file; file++)
389 free(*file);
390 free(files);
391 }
392 if (includes)
393 free(includes);
394 if (excludes)
395 free(excludes);
73543ae3
MT
396
397 return r;
398}
399
9df89a8f
MT
400static int pakfire_build_add_scriptlet_requires(struct pakfire_build* build,
401 struct pakfire_package* pkg, struct pakfire_scriptlet* scriptlet) {
12b9b39f 402 char* prerequires = NULL;
106d2edd
MT
403 char path[PATH_MAX];
404 size_t size;
405 int r;
406
9df89a8f 407 const char* root = pakfire_get_path(build->pakfire);
106d2edd
MT
408
409 // Make filename
9df89a8f 410 r = pakfire_make_path(build->pakfire, path, "tmp/.pakfire-scriptlet.XXXXXX");
106d2edd
MT
411 if (r < 0)
412 return r;
413
414 // Fetch scriptlet payload
415 const char* data = pakfire_scriptlet_get_data(scriptlet, &size);
416
417 // Create a temporary file
418 FILE* f = pakfire_mktemp(path);
419 if (!f)
420 return 1;
421
422 // Write scriptlet
423 ssize_t bytes_written = fwrite(data, 1, size, f);
424 if (bytes_written < 0) {
9df89a8f 425 ERROR(build->pakfire, "Could not write to %s: %m\n", path);
106d2edd
MT
426 fclose(f);
427 goto ERROR;
428 }
429
430 // Close file
431 fclose(f);
432
433 // Build commandline
434 const char* args[] = {
435 pakfire_path_relpath(root, path),
436 NULL,
437 };
438
439 // Find all pre-requires
779e16de 440 r = pakfire_build_run_script(build, "find-prerequires", args, &prerequires);
106d2edd
MT
441 if (r)
442 goto ERROR;
443
444 // Add all pre-requires to the package
445 if (prerequires) {
83d93f33
MT
446 r = pakfire_str2deps(build->pakfire, pkg,
447 pakfire_package_add_prerequires, prerequires);
448 if (r) {
449 ERROR(build->pakfire, "Could not add pre-requires: %m\n");
450 goto ERROR;
106d2edd
MT
451 }
452 }
453
454ERROR:
455 if (r && *path)
456 unlink(path);
12b9b39f 457 if (prerequires)
106d2edd 458 free(prerequires);
106d2edd
MT
459 return r;
460}
461
9df89a8f
MT
462static int pakfire_build_package_add_scriptlet(struct pakfire_build* build,
463 struct pakfire_package* pkg, struct pakfire_packager* packager,
464 const char* type, const char* data) {
106d2edd
MT
465 struct pakfire_scriptlet* scriptlet = NULL;
466 char* shell = NULL;
467 int r;
468
469 // Wrap scriptlet into a shell script
470 r = asprintf(&shell, "#!/bin/sh\n\nset -e\n\n%s\n\nexit 0\n", data);
471 if (r < 0)
472 goto ERROR;
473
474 // Create a scriptlet
9df89a8f 475 r = pakfire_scriptlet_create(&scriptlet, build->pakfire, type, shell, 0);
106d2edd
MT
476 if (r)
477 goto ERROR;
478
479 // Add it to the package
480 r = pakfire_packager_add_scriptlet(packager, scriptlet);
481 if (r) {
9df89a8f 482 ERROR(build->pakfire, "Could not add scriptlet %s\n", type);
106d2edd
MT
483 goto ERROR;
484 }
485
486 // Add scriptlet requirements
9df89a8f 487 r = pakfire_build_add_scriptlet_requires(build, pkg, scriptlet);
106d2edd 488 if (r) {
9df89a8f 489 ERROR(build->pakfire, "Could not add scriptlet requirements: %m\n");
106d2edd
MT
490 goto ERROR;
491 }
492
493 // Success
494 r = 0;
495
496ERROR:
497 if (scriptlet)
498 pakfire_scriptlet_unref(scriptlet);
499 if (shell)
500 free(shell);
501
502 return r;
503}
504
9df89a8f
MT
505static int pakfire_build_package_add_scriptlets(struct pakfire_build* build,
506 struct pakfire_parser* makefile, const char* namespace,
507 struct pakfire_package* pkg, struct pakfire_packager* packager) {
106d2edd
MT
508 char name[NAME_MAX];
509 int r;
510
511 for (const char** type = pakfire_scriptlet_types; *type; type++) {
512 r = pakfire_string_format(name, "scriptlet:%s", *type);
513 if (r < 0)
514 return r;
515
516 // Fetch the scriptlet
517 char* data = pakfire_parser_get(makefile, namespace, name);
518 if (!data)
519 continue;
520
521 // Add it to the package
9df89a8f 522 r = pakfire_build_package_add_scriptlet(build, pkg, packager, *type, data);
106d2edd
MT
523 if (r) {
524 free(data);
525 return r;
526 }
527
528 free(data);
529 }
530
531 return 0;
532}
533
0e177be7
MT
534static int pakfire_build_package(struct pakfire_build* build, struct pakfire_parser* makefile,
535 const char* buildroot, const char* namespace) {
31480bee 536 struct pakfire_package* pkg = NULL;
48c6f2e7 537 struct pakfire_packager* packager = NULL;
73543ae3 538
a50bde9c
MT
539 int r = 1;
540
541 // Expand the handle into the package name
4c07774f 542 char* name = pakfire_parser_expand(makefile, "packages", namespace);
a50bde9c 543 if (!name) {
0e177be7 544 ERROR(build->pakfire, "Could not get package name: %m\n");
a50bde9c
MT
545 goto ERROR;
546 }
547
0e177be7 548 INFO(build->pakfire, "Building package '%s'...\n", name);
a50bde9c 549
4c07774f 550 // Fetch build architecture
0e177be7 551 const char* arch = pakfire_get_arch(build->pakfire);
4c07774f
MT
552 if (!arch)
553 goto ERROR;
554
4c07774f 555 // Fetch package from makefile
6ebd55e2 556 r = pakfire_parser_create_package(makefile, &pkg, NULL, namespace, arch);
4c07774f 557 if (r) {
0e177be7 558 ERROR(build->pakfire, "Could not create package from makefile: %m\n");
4c07774f
MT
559 goto ERROR;
560 }
561
ca002cae
MT
562 // Set distribution
563 const char* distribution = pakfire_parser_get(makefile, NULL, "DISTRO_NAME");
564 if (distribution)
565 pakfire_package_set_distribution(pkg, distribution);
566
7996020a 567 // Set build ID
0e177be7 568 pakfire_package_set_build_id_from_uuid(pkg, &build->id);
7996020a 569
571539a7
MT
570 // Set source package
571 const char* source_name = pakfire_parser_get(makefile, NULL, "name");
572 if (source_name)
573 pakfire_package_set_source_name(pkg, source_name);
574
575 // Set source EVR
576 const char* source_evr = pakfire_parser_get(makefile, NULL, "evr");
577 if (source_evr)
578 pakfire_package_set_source_evr(pkg, source_evr);
579
580 // Set source arch
581 pakfire_package_set_source_arch(pkg, "src");
582
48c6f2e7 583 // Create a packager
0e177be7 584 r = pakfire_packager_create(&packager, build->pakfire, pkg);
48c6f2e7
MT
585 if (r)
586 goto ERROR;
587
73543ae3 588 // Add files
adfa3573 589 r = pakfire_build_package_add_files(build, makefile, buildroot, namespace,
5b0b3dc2 590 pkg, packager);
73543ae3
MT
591 if (r)
592 goto ERROR;
593
106d2edd 594 // Add scriptlets
9df89a8f 595 r = pakfire_build_package_add_scriptlets(build, makefile, namespace,
0e177be7 596 pkg, packager);
106d2edd
MT
597 if (r)
598 goto ERROR;
599
ae7968a7 600 // Write the finished package
0e177be7 601 r = pakfire_packager_finish_to_directory(packager, build->target, NULL);
ae7968a7 602 if (r) {
0e177be7 603 ERROR(build->pakfire, "pakfire_packager_finish() failed: %m\n");
ae7968a7
MT
604 goto ERROR;
605 }
606
4c07774f 607#ifdef ENABLE_DEBUG
23e22751 608 char* dump = pakfire_package_dump(pkg, PAKFIRE_PKG_DUMP_LONG);
4c07774f 609 if (dump) {
0e177be7 610 DEBUG(build->pakfire, "%s\n", dump);
4c07774f
MT
611 free(dump);
612 }
613#endif
a50bde9c
MT
614
615 // Success
616 r = 0;
617
618ERROR:
48c6f2e7
MT
619 if (packager)
620 pakfire_packager_unref(packager);
4c07774f
MT
621 if (pkg)
622 pakfire_package_unref(pkg);
a50bde9c
MT
623 if (name)
624 free(name);
625
626 return r;
627}
628
0e177be7
MT
629static int pakfire_build_packages(struct pakfire_build* build,
630 struct pakfire_parser* makefile, const char* buildroot) {
631 DEBUG(build->pakfire, "Creating packages...");
a50bde9c
MT
632 int r = 1;
633
634 // Fetch a list all all packages
635 char** packages = pakfire_parser_list_namespaces(makefile, "packages.package:*");
636 if (!packages) {
0e177be7 637 ERROR(build->pakfire, "Could not find any packages: %m\n");
a50bde9c
MT
638 goto ERROR;
639 }
640
641 unsigned int num_packages = 0;
642
643 // Count how many packages we have
644 for (char** package = packages; *package; package++)
645 num_packages++;
646
0e177be7 647 DEBUG(build->pakfire, "Found %d package(s)\n", num_packages);
a50bde9c
MT
648
649 // Build packages in reverse order
650 for (int i = num_packages - 1; i >= 0; i--) {
0e177be7 651 r = pakfire_build_package(build, makefile, buildroot, packages[i]);
a50bde9c
MT
652 if (r)
653 goto ERROR;
654 }
655
656 // Success
657 r = 0;
658
659ERROR:
660 if (packages)
661 free(packages);
662
663 return r;
664}
665
46748697
MT
666static int pakfire_build_stage(struct pakfire_build* build,
667 struct pakfire_parser* makefile, const char* stage) {
1a276007
MT
668 char template[1024];
669
670 // Prepare template for this stage
671 int r = pakfire_string_format(template, TEMPLATE, stage);
672 if (r < 0)
673 return r;
674
2ffd21f9
MT
675 // Fetch the environment
676 char** envp = pakfire_parser_make_environ(makefile);
677
1a276007
MT
678 // Create the build script
679 char* script = pakfire_parser_expand(makefile, "build", template);
680 if (!script) {
46748697
MT
681 ERROR(build->pakfire, "Could not generate the build script for stage '%s': %m\n",
682 stage);
1a276007
MT
683 goto ERROR;
684 }
685
46748697 686 INFO(build->pakfire, "Running build stage '%s'\n", stage);
1a276007 687
830f5d18 688 // Import environment
44d5ebfd
MT
689 // XXX is this a good idea?
690 r = pakfire_jail_import_env(build->jail, (const char**)envp);
830f5d18 691 if (r) {
46748697 692 ERROR(build->pakfire, "Could not import environment: %m\n");
830f5d18
MT
693 goto ERROR;
694 }
695
696 // Run the script
44d5ebfd 697 r = pakfire_jail_exec_script(build->jail, script, strlen(script), NULL, NULL);
1a276007 698 if (r) {
46748697 699 ERROR(build->pakfire, "Build stage '%s' failed with status %d\n", stage, r);
1a276007
MT
700 }
701
702ERROR:
2ffd21f9
MT
703 if (envp) {
704 for (char** e = envp; *e; e++)
705 free(*e);
706 free(envp);
707 }
1a276007
MT
708 if (script)
709 free(script);
710
711 return r;
712}
713
39f45b30
MT
714static const char* post_build_scripts[] = {
715 "remove-static-libs",
1f62ffae 716 "check-symlinks",
022b794e 717 "check-unsafe-files",
0b5f0bbc 718 "check-libraries",
16043831 719 "check-rpaths",
99aee237 720 "check-buildroot",
7e1fec6f 721 "check-include",
dd864160 722 "check-hardening",
ae703321 723 "check-interpreters",
2504194a 724 "check-fhs",
39f45b30 725 "compress-man-pages",
ffb65de6 726 "strip",
39f45b30
MT
727 NULL,
728};
729
247dd943
MT
730static int pakfire_build_run_post_build_scripts(
731 struct pakfire_build* build, const char* buildroot) {
39f45b30
MT
732 // Set default arguments for build scripts
733 const char* args[] = {
734 buildroot, NULL
735 };
736
737 // Run them one by one
738 for (const char** script = post_build_scripts; *script; script++) {
779e16de 739 int r = pakfire_build_run_script(build, *script, args, NULL);
39f45b30
MT
740 if (r)
741 return r;
742 }
743
744 return 0;
745}
746
87bb7749 747static int pakfire_build_makefile(struct pakfire_build* build, const char* path) {
657a5c72 748 struct pakfire_parser* makefile = NULL;
3d4011eb 749 char buildroot[PATH_MAX];
1a276007 750 struct pakfire_parser_error* error = NULL;
01b29895 751 int r = 1;
f0893704 752
87bb7749 753 const char* root = pakfire_get_path(build->pakfire);
3d4011eb
MT
754
755 // Create BUILDROOT
87bb7749 756 pakfire_make_path(build->pakfire, buildroot, "/tmp/.buildroot.XXXXXX");
3d4011eb 757 if (!pakfire_mkdtemp(buildroot))
f0893704 758 goto ERROR;
3d4011eb
MT
759
760 // Make relative BUILDROOT path
761 const char* buildroot_rel = pakfire_path_relpath(root, buildroot);
762 if (!buildroot_rel)
f0893704 763 goto ERROR;
3d4011eb 764
1a276007 765 // Read makefile
87bb7749 766 r = pakfire_read_makefile(&makefile, build->pakfire, path, &error);
1a276007
MT
767 if (r) {
768 if (error) {
87bb7749 769 ERROR(build->pakfire, "Could not parse makefile %s: %s\n", path,
1a276007
MT
770 pakfire_parser_error_get_message(error));
771 pakfire_parser_error_unref(error);
772 } else {
87bb7749 773 ERROR(build->pakfire, "Could not parse makefile %s: %m\n", path);
1a276007
MT
774 }
775
776 goto ERROR;
777 }
778
3d4011eb 779 // Set BUILDROOT
fea2e884 780 pakfire_parser_set(makefile, NULL, "BUILDROOT", buildroot_rel, 0);
3d4011eb 781
1a276007
MT
782 // Run through all build stages
783 for (const char** stage = stages; *stage; stage++) {
46748697 784 r = pakfire_build_stage(build, makefile, *stage);
1a276007
MT
785 if (r) {
786 // Drop to a shell for debugging
d4be0f06 787 if (pakfire_has_flag(build->pakfire, PAKFIRE_BUILD_INTERACTIVE))
5f6e42a2 788 pakfire_build_shell(build);
1a276007
MT
789
790 goto ERROR;
791 }
792 }
793
39f45b30 794 // Run post build scripts
247dd943 795 r = pakfire_build_run_post_build_scripts(build, buildroot_rel);
8d0f3a35
MT
796 if (r)
797 goto ERROR;
798
a50bde9c 799 // Create the packages
0e177be7 800 r = pakfire_build_packages(build, makefile, buildroot_rel);
a50bde9c 801 if (r) {
87bb7749 802 ERROR(build->pakfire, "Could not create packages: %m\n");
a50bde9c
MT
803 goto ERROR;
804 }
1a276007
MT
805
806ERROR:
807 if (makefile)
808 pakfire_parser_unref(makefile);
809
3d4011eb
MT
810 // Remove buildroot
811 pakfire_rmtree(buildroot, 0);
812
1a276007
MT
813 return r;
814}
815
abbad00b 816static void pakfire_build_free(struct pakfire_build* build) {
e545f6ec
MT
817 if (build->repo)
818 pakfire_repo_unref(build->repo);
819
753ddf74
MT
820 if (build->jail)
821 pakfire_jail_unref(build->jail);
822
49fd9926
MT
823 if (build->cgroup) {
824 // Destroy the cgroup
825 pakfire_cgroup_destroy(build->cgroup);
826
827 // Free it
828 pakfire_cgroup_unref(build->cgroup);
829 }
830
abbad00b
MT
831 pakfire_unref(build->pakfire);
832 free(build);
833}
834
835static int pakfire_build_parse_id(struct pakfire_build* build, const char* id) {
836 int r;
837
838 // Try parsing the Build ID
839 if (id) {
840 r = uuid_parse(id, build->id);
841 if (r) {
842 ERROR(build->pakfire, "Could not parse build ID '%s'\n", id);
1df321ff 843 errno = EINVAL;
abbad00b
MT
844 return r;
845 }
846
847 // Otherwise initialize the Build ID with something random
848 } else {
849 uuid_generate_random(build->id);
850 }
851
f877fdfa
MT
852 // Store the ID as string, too
853 uuid_unparse_lower(build->id, build->_id);
854
855 return 0;
856}
857
a84624be
MT
858/*
859 Sets the default target
860*/
861static int pakfire_build_set_default_target(struct pakfire_build* build) {
862 // Look for the "local" repository
e545f6ec
MT
863 if (build->repo) {
864 const char* target = pakfire_repo_get_path(build->repo);
a84624be
MT
865 if (target)
866 pakfire_string_set(build->target, target);
a84624be
MT
867 }
868
869 // Default to the current working directory
870 if (!*build->target) {
871 char* cwd = getcwd(build->target, sizeof(build->target));
872 if (!cwd) {
873 ERROR(build->pakfire, "getcwd() failed: %m\n");
874 return 1;
875 }
876 }
877
878 return 0;
879}
880
f877fdfa
MT
881/*
882 Sets up a new cgroup for this build
883*/
884static int pakfire_build_setup_cgroup(struct pakfire_build* build) {
885 char path[PATH_MAX];
886 int r;
887
888 // Compose path
889 r = pakfire_string_format(path, "pakfire/build-%s", build->_id);
890 if (r < 0) {
891 ERROR(build->pakfire, "Could not compose path for cgroup: %m\n");
892 return 1;
893 }
894
895 // Create a new cgroup
896 r = pakfire_cgroup_open(&build->cgroup, build->pakfire, path,
897 PAKFIRE_CGROUP_ENABLE_ACCOUNTING);
898 if (r) {
899 ERROR(build->pakfire, "Could not create cgroup for build %s: %m\n", build->_id);
900 return r;
901 }
902
882ae03b
MT
903 // Guarantee some minimum memory
904 r = pakfire_cgroup_set_guaranteed_memory(build->cgroup, PAKFIRE_BUILD_GUARANTEED_MEMORY);
905 if (r)
906 return r;
907
de4c8fe6
MT
908 // Set PID limit
909 r = pakfire_cgroup_set_pid_limit(build->cgroup, PAKFIRE_BUILD_PID_LIMIT);
910 if (r)
911 return r;
912
f877fdfa 913 // Done
abbad00b
MT
914 return 0;
915}
916
753ddf74
MT
917/*
918 Sets up a new jail for this build
919*/
920static int pakfire_build_setup_jail(struct pakfire_build* build) {
921 int r;
922
923 // Create a new jail
924 r = pakfire_jail_create(&build->jail, build->pakfire, 0);
925 if (r) {
926 ERROR(build->pakfire, "Could not create jail for build %s: %m\n", build->_id);
927 return r;
928 }
929
15503538
MT
930 // Connect the jail to our cgroup
931 r = pakfire_jail_set_cgroup(build->jail, build->cgroup);
932 if (r) {
933 ERROR(build->pakfire, "Could not set cgroup for jail: %m\n");
934 return r;
935 }
936
753ddf74
MT
937 // Done
938 return 0;
939}
940
5a06668c
MT
941/*
942 Sets up the ccache for this build
943*/
944static int pakfire_build_setup_ccache(struct pakfire_build* build) {
945 char path[PATH_MAX];
946 int r;
947
948 // Check if we want a ccache
949 if (pakfire_build_has_flag(build, PAKFIRE_BUILD_DISABLE_CCACHE)) {
950 DEBUG(build->pakfire, "ccache usage has been disabled for this build\n");
951 return 0;
952 }
953
954 // Compose path
955 r = pakfire_make_cache_path(build->pakfire, path, "%s", "ccache");
956 if (r < 0) {
957 ERROR(build->pakfire, "Could not compose ccache path: %m\n");
958 return 1;
959 }
960
961 DEBUG(build->pakfire, "Mounting ccache from %s\n", path);
962
963 // Ensure path exists
964 r = pakfire_mkdir(path, 0755);
965 if (r && errno != EEXIST) {
966 ERROR(build->pakfire, "Could not create ccache directory %s: %m\n", path);
967 return r;
968 }
969
970 // Bind-mount the directory
971 r = pakfire_jail_bind(build->jail, path, CCACHE_DIR, MS_NOSUID|MS_NOEXEC|MS_NODEV);
972 if (r) {
973 ERROR(build->pakfire, "Could not mount ccache: %m\n");
974 return r;
975 }
976
977 return 0;
978}
979
e545f6ec
MT
980static int pakfire_build_setup_repos(struct pakfire_build* build) {
981 int r;
982
983 // Create a new repository
984 r = pakfire_repo_create(&build->repo, build->pakfire, PAKFIRE_REPO_LOCAL);
985 if (r) {
986 ERROR(build->pakfire, "Could not create repository %s: %m\n", PAKFIRE_REPO_LOCAL);
987 return 1;
988 }
989
990 // Set description
991 pakfire_repo_set_description(build->repo, _("Locally Built Packages"));
992
993 // Set path
994 pakfire_repo_set_baseurl(build->repo, PAKFIRE_REPO_LOCAL_PATH);
995
996 // Set priority
997 pakfire_repo_set_priority(build->repo, PAKFIRE_REPO_LOCAL_PRIORITY);
998
999 return 0;
1000}
1001
abbad00b
MT
1002PAKFIRE_EXPORT int pakfire_build_create(struct pakfire_build** build,
1003 struct pakfire* pakfire, const char* id, int flags) {
1004 int r;
1005
1006 // Allocate build object
1007 struct pakfire_build* b = calloc(1, sizeof(*b));
1008 if (!b)
1009 return 1;
1010
1011 // Reference pakfire
1012 b->pakfire = pakfire_ref(pakfire);
1013
1014 // Initialize reference counter
1015 b->nrefs = 1;
1016
1017 // Copy flags
1018 b->flags = flags;
1019
1020 // Parse ID
1021 r = pakfire_build_parse_id(b, id);
1022 if (r)
1023 goto ERROR;
1024
e545f6ec
MT
1025 // Setup repos
1026 r = pakfire_build_setup_repos(b);
1027 if (r)
1028 goto ERROR;
1029
a84624be
MT
1030 // Set target
1031 r = pakfire_build_set_default_target(b);
1032 if (r)
1033 goto ERROR;
1034
f877fdfa
MT
1035 // Create cgroup
1036 r = pakfire_build_setup_cgroup(b);
1037 if (r)
1038 goto ERROR;
1039
753ddf74
MT
1040 // Create jail
1041 r = pakfire_build_setup_jail(b);
1042 if (r)
1043 goto ERROR;
1044
5a06668c
MT
1045 // Setup ccache
1046 r = pakfire_build_setup_ccache(b);
1047 if (r)
1048 goto ERROR;
1049
abbad00b
MT
1050 *build = b;
1051 return 0;
1052
1053ERROR:
1054 pakfire_build_free(b);
1055 return r;
1056}
1057
77a1964f 1058PAKFIRE_EXPORT struct pakfire_build* pakfire_build_ref(struct pakfire_build* build) {
abbad00b
MT
1059 ++build->nrefs;
1060
1061 return build;
1062}
1063
77a1964f 1064PAKFIRE_EXPORT struct pakfire_build* pakfire_build_unref(struct pakfire_build* build) {
abbad00b
MT
1065 if (--build->nrefs > 0)
1066 return build;
1067
1068 pakfire_build_free(build);
1069 return NULL;
1070}
1071
ea924657
MT
1072PAKFIRE_EXPORT int pakfire_build_set_target(
1073 struct pakfire_build* build, const char* target) {
1074 pakfire_string_set(build->target, target);
57e2cf99 1075
ea924657
MT
1076 return 0;
1077}
57e2cf99 1078
c0f2502a
MT
1079static int pakfire_build_install_packages(struct pakfire_build* build,
1080 int* snapshot_needs_update) {
1081 char** packages = NULL;
1082 int r = 1;
1083
1084 // Fetch configuration
1085 struct pakfire_config* config = pakfire_get_config(build->pakfire);
1086 if (!config) {
1087 ERROR(build->pakfire, "Could not fetch configuration: %m\n");
1088 r = 1;
1089 goto ERROR;
1090 }
1091
1092 // Fetch build environment
1093 const char* requires = pakfire_config_get(config, "build", "requires", NULL);
1094 if (!requires) {
1095 ERROR(build->pakfire, "No build requirements have been defined\n");
1096 goto ERROR;
1097 }
1098
1099 // Split requirements into packages
1100 packages = pakfire_split_string(requires, ',');
1101 if (!packages)
1102 goto ERROR;
1103
1104 int changed = 0;
1105
1106 // Install everything
1107 r = pakfire_install(build->pakfire, 0, 0, (const char**)packages, NULL, 0,
1108 &changed, NULL, NULL);
1109 if (r) {
1110 ERROR(build->pakfire, "Could not install build dependencies: %m\n");
1111 goto ERROR;
1112 }
1113
1114 // Mark snapshot as changed if new packages were installed
1115 if (changed)
1116 *snapshot_needs_update = 1;
1117
1118 // Update everything
1119 r = pakfire_sync(build->pakfire, 0, 0, &changed, NULL, NULL);
1120 if (r) {
1121 ERROR(build->pakfire, "Could not update packages: %m\n");
1122 goto ERROR;
1123 }
1124
1125 // Has anything changed?
1126 if (changed)
1127 *snapshot_needs_update = 1;
1128
1129 // Success
1130 r = 0;
1131
1132ERROR:
1133 if (config)
1134 pakfire_config_unref(config);
1135
1136 if (packages) {
1137 for (char** package = packages; *package; package++)
1138 free(*package);
1139 free(packages);
1140 }
1141
1142 return r;
1143}
1144
8dbb69f9
MT
1145/*
1146 Initializes the build environment
1147*/
1148static int pakfire_build_init(struct pakfire_build* build) {
c0f2502a
MT
1149 char path[PATH_MAX];
1150 int r;
1151
8dbb69f9
MT
1152 // Don't do it again
1153 if (build->init) {
1154 DEBUG(build->pakfire, "Build environment has already been initialized\n");
1155 return 0;
1156 }
1157
e545f6ec
MT
1158 // Refresh the local repository
1159 if (build->repo) {
f3c4b36a 1160 r = pakfire_repo_refresh(build->repo, 0);
e545f6ec
MT
1161 if (r) {
1162 ERROR(build->pakfire, "Could not refresh the local repository: %m\n");
1163 return 1;
1164 }
1165 }
1166
1ea7b360
MT
1167 // Compose path for snapshot
1168 r = pakfire_make_cache_path(build->pakfire, path, "%s", "snapshot.tar.zst");
1169 if (r < 0) {
1170 ERROR(build->pakfire, "Could not compose snapshot path: %m\n");
1171 return 1;
1172 }
1173
ffc2630d
MT
1174 // Tells us whether we need to (re-)create the snapshot
1175 int snapshot_needs_update = 0;
c0f2502a 1176
1ea7b360 1177 // Extract snapshot
ffc2630d 1178 if (!pakfire_build_has_flag(build, PAKFIRE_BUILD_DISABLE_SNAPSHOT)) {
ffc2630d 1179 // Try restoring the snapshot
4667a2ca
MT
1180 r = pakfire_snapshot_restore(build->pakfire, path);
1181 if (r && errno != ENOENT)
1182 return r;
c0f2502a
MT
1183 }
1184
c0f2502a
MT
1185 // Install or update any build dependencies
1186 r = pakfire_build_install_packages(build, &snapshot_needs_update);
1187 if (r)
1188 return r;
1189
ffc2630d
MT
1190 // Update the snapshot if there were changes
1191 if (!pakfire_build_has_flag(build, PAKFIRE_BUILD_DISABLE_SNAPSHOT) && snapshot_needs_update) {
c0f2502a 1192 // Create a new snapshot
4667a2ca 1193 r = pakfire_snapshot_create(build->pakfire, path);
c0f2502a
MT
1194 if (r)
1195 return r;
1196 }
1197
8dbb69f9
MT
1198 // Mark as initialized
1199 build->init = 1;
1200
c0f2502a
MT
1201 return 0;
1202}
1203
ea924657 1204PAKFIRE_EXPORT int pakfire_build_exec(struct pakfire_build* build, const char* path) {
33a8ba3c
MT
1205 struct pakfire_archive* archive = NULL;
1206 struct pakfire_package* package = NULL;
1207
1208 char makefile[PATH_MAX];
ea924657 1209 int r;
01b29895 1210
33a8ba3c
MT
1211 // Open source archive
1212 r = pakfire_archive_open(&archive, build->pakfire, path);
1213 if (r) {
1214 ERROR(build->pakfire, "Could not open source archive %s: %m\n", path);
1215 goto ERROR;
1216 }
1217
1218 // Fetch package metadata
1219 r = pakfire_archive_make_package(archive, NULL, &package);
1220 if (r) {
1221 ERROR(build->pakfire, "Could not read package metadata: %m\n");
1222 goto ERROR;
1223 }
1224
1225 // Fetch some information
1226 const char* nevra = pakfire_package_get_nevra(package);
1227 const char* name = pakfire_package_get_name(package);
1228
1229 INFO(build->pakfire, "Building %s...\n", nevra);
01b29895 1230
8dbb69f9
MT
1231 // Initialize the build environment
1232 r = pakfire_build_init(build);
1233 if (r)
c0f2502a 1234 goto ERROR;
c0f2502a 1235
01b29895
MT
1236 const char* packages[] = {
1237 path, NULL
1238 };
1239
1240 // Install the package into the build environment
ea924657 1241 r = pakfire_install(build->pakfire, 0, 0, packages, NULL, PAKFIRE_REQUEST_ESSENTIAL,
99a56775 1242 NULL, NULL, NULL);
01b29895 1243 if (r) {
ea924657 1244 ERROR(build->pakfire, "Could not install %s\n", path);
33a8ba3c 1245 goto ERROR;
01b29895
MT
1246 }
1247
33a8ba3c
MT
1248 // Compose path to makefile
1249 r = pakfire_string_format(makefile, "/usr/src/packages/%s/%s.nm", name, name);
1250 if (r < 0) {
1251 ERROR(build->pakfire, "Could not compose makefile path: %m\n");
01b29895 1252 goto ERROR;
33a8ba3c 1253 }
01b29895 1254
33a8ba3c 1255 // Run build
87bb7749 1256 r = pakfire_build_makefile(build, makefile);
01b29895 1257 if (r) {
33a8ba3c 1258 ERROR(build->pakfire, "Could not build %s: %m\n", nevra);
01b29895
MT
1259 goto ERROR;
1260 }
1261
01b29895 1262ERROR:
33a8ba3c
MT
1263 if (archive)
1264 pakfire_archive_unref(archive);
1265 if (package)
1266 pakfire_package_unref(package);
1267
01b29895
MT
1268 return r;
1269}
1270
ea924657
MT
1271/*
1272 Compatibility function to keep the legacy API.
1273*/
1274PAKFIRE_EXPORT int pakfire_build(struct pakfire* pakfire, const char* path,
1275 const char* target, const char* id, int flags) {
1276 struct pakfire_build* build = NULL;
1277 int r;
1278
1279 // Check if path is set
1280 if (!path) {
1281 errno = EINVAL;
1282 return 1;
1283 }
1284
1285 // Create a new build environment
1286 r = pakfire_build_create(&build, pakfire, id, flags);
1287 if (r)
1288 goto ERROR;
1289
1290 // Set target
1291 if (target) {
1292 r = pakfire_build_set_target(build, target);
1293 if (r)
1294 goto ERROR;
1295 }
1296
1297 // Run build
1298 r = pakfire_build_exec(build, path);
1299
1300ERROR:
1301 if (build)
1302 pakfire_build_unref(build);
1303
1304 return r;
1305}
1306
5f6e42a2
MT
1307int pakfire_build_clean(struct pakfire* pakfire, int flags) {
1308 struct pakfire_repo* local = NULL;
1309 int r = 0;
22a0733e 1310
5f6e42a2
MT
1311 // Fetch local repository
1312 local = pakfire_get_repo(pakfire, PAKFIRE_REPO_LOCAL);
1313 if (!local) {
1314 ERROR(pakfire, "Could not find repository %s: %m\n", PAKFIRE_REPO_LOCAL);
22a0733e
MT
1315 goto ERROR;
1316 }
1317
5f6e42a2
MT
1318 // Destroy everything in it
1319 r = pakfire_repo_clean(local, PAKFIRE_REPO_CLEAN_FLAGS_DESTROY);
1320 if (r)
22a0733e 1321 goto ERROR;
22a0733e
MT
1322
1323ERROR:
5f6e42a2
MT
1324 if (local)
1325 pakfire_repo_unref(local);
22a0733e
MT
1326
1327 return r;
1328}
1329
5f6e42a2
MT
1330/*
1331 This is a convenience function that sets up a build environment and
1332 then drops the user into an interactive shell.
1333*/
7f068a08 1334PAKFIRE_EXPORT int pakfire_shell(struct pakfire* pakfire, const char** packages) {
5f6e42a2 1335 struct pakfire_build* build = NULL;
48dc31f7 1336 int r;
1a276007 1337
5f6e42a2 1338 // Create a new build environment
db4f234f 1339 r = pakfire_build_create(&build, pakfire, NULL, PAKFIRE_BUILD_INTERACTIVE);
5f6e42a2
MT
1340 if (r) {
1341 ERROR(pakfire, "Could not create build: %m\n");
1342 goto ERROR;
7f068a08
MT
1343 }
1344
8dbb69f9
MT
1345 // Initialize the build environment
1346 r = pakfire_build_init(build);
1347 if (r)
1348 goto ERROR;
22a0733e 1349
5f6e42a2
MT
1350 // Install any additional packages
1351 if (packages) {
1352 r = pakfire_install(build->pakfire, 0, 0, packages, NULL, 0, NULL, NULL, NULL);
1353 if (r)
1354 return r;
397371db
MT
1355 }
1356
5f6e42a2
MT
1357 // Run shell
1358 r = pakfire_build_shell(build);
397371db
MT
1359
1360ERROR:
5f6e42a2
MT
1361 if (build)
1362 pakfire_build_unref(build);
397371db
MT
1363
1364 return r;
1365}