2 * Copyright (c) 2019 The DragonFly Project. All rights reserved.
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@backplane.com>
7 * This code uses concepts and configuration based on 'synth', by
8 * John R. Marino <draco@marino.st>, which was written in ada.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in
18 * the documentation and/or other materials provided with the
20 * 3. Neither the name of The DragonFly Project nor the names of its
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific, prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
32 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
34 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 worker_t WorkerAry[MAXWORKERS];
42 pthread_mutex_t WorkerMutex;
43 pthread_cond_t WorkerCond;
45 static int build_find_leaves(pkg_t *parent, pkg_t *pkg, pkg_t ***build_tailp,
46 int *app, int *hasworkp, int level, int first);
47 static void build_clear_trav(pkg_t *pkg);
48 static void startbuild(pkg_t **build_listp, pkg_t ***build_tailp);
49 static int qsort_depi(const void *pkg1, const void *pkg2);
50 static int qsort_idep(const void *pkg1, const void *pkg2);
51 static void startworker(pkg_t *pkg, worker_t *work);
52 static void cleanworker(worker_t *work);
53 static void waitbuild(int whilematch);
54 static void workercomplete(worker_t *work);
55 static void *childBuilderThread(void *arg);
56 static int childInstallPkgDeps(worker_t *work);
57 static void childInstallPkgDeps_recurse(FILE *fp, pkglink_t *list, int undoit);
58 static void dophase(worker_t *work, wmsg_t *wmsg,
59 int wdog, int phaseid, const char *phase);
60 static void phaseReapAll(void);
61 static void phaseTerminateSignal(int sig);
62 static char *buildskipreason(pkg_t *pkg);
63 static int copyfile(char *src, char *dst);
65 static worker_t *SigWork;
66 static int FDMaster = -1;
68 static int DynamicMaxWorkers;
74 int BuildSuccessCount;
77 * Initialize the WorkerAry[]
80 DoInitBuild(int slot_override)
86 ddassert(slot_override < 0 || MaxWorkers == 1);
88 bzero(WorkerAry, MaxWorkers * sizeof(worker_t));
89 pthread_mutex_init(&WorkerMutex, NULL);
91 for (i = 0; i < MaxWorkers; ++i) {
93 work->index = (slot_override >= 0) ? slot_override : i;
94 work->state = WORKER_NONE;
95 asprintf(&work->basedir, "%s/SL%02d", BuildBase, work->index);
96 pthread_cond_init(&work->cond, NULL);
101 * Create required sub-directories. The base directories must already
102 * exist as a dsynth configuration safety.
104 if (stat(RepositoryPath, &st) < 0) {
105 if (mkdir(RepositoryPath, 0755) < 0)
106 dfatal("Cannot mkdir %s\n", RepositoryPath);
109 BuildInitialized = 1;
110 DynamicMaxWorkers = MaxWorkers;
114 * Called by the frontend to clean-up any hanging mounts.
121 ddassert(BuildInitialized);
123 for (i = 0; i < MaxWorkers; ++i) {
124 DoWorkerUnmounts(&WorkerAry[i]);
132 pkg_t *build_list = NULL;
133 pkg_t **build_tail = &build_list;
138 for (scan = pkgs; scan; scan = scan->bnext)
142 * The pkg and pkg-static binaries are needed. If already present
143 * then assume that the template is also valid, otherwise build
146 scan = GetPkgPkg(pkgs);
149 * Create our template. The template will be missing pkg
152 if ((scan->flags & (PKGF_SUCCESS | PKGF_PACKAGED)) == 0) {
157 * This will clear the screen and set-up our gui, so sleep
158 * a little first in case the user wants to see what was
162 pthread_mutex_lock(&WorkerMutex);
166 if ((scan->flags & (PKGF_SUCCESS | PKGF_PACKAGED)) == 0) {
171 * Build pkg/pkg-static.
174 build_tail = &scan->build_next;
175 startbuild(&build_list, &build_tail);
178 if (scan->flags & PKGF_NOBUILD)
179 dfatal("Unable to build 'pkg'");
180 if (scan->flags & PKGF_ERROR)
181 dfatal("Error building 'pkg'");
182 if ((scan->flags & PKGF_SUCCESS) == 0)
183 dfatal("Error building 'pkg'");
186 * Install pkg/pkg-static into the template
190 "tar --exclude '+*' --exclude '*/man/*' "
191 "-xvzpf %s/%s > /dev/null 2>&1",
197 dfatal("Command failed: %s\n", buf);
202 * Nominal bulk build sequence
207 for (scan = pkgs; scan; scan = scan->bnext) {
208 ddprintf(0, "SCANLEAVES %08x %s\n",
209 scan->flags, scan->portdir);
210 scan->flags |= PKGF_BUILDLOOP;
212 * NOTE: We must still find dependencies if PACKAGED
213 * to fill in the gaps, as some of them may
214 * need to be rebuilt.
216 if (scan->flags & (PKGF_SUCCESS | PKGF_FAILURE |
217 PKGF_ERROR | PKGF_NOBUILD)) {
219 ddprintf(0, "%s: already built\n",
224 build_find_leaves(NULL, scan, &build_tail,
225 &ap, &haswork, 0, first);
226 ddprintf(0, "TOPLEVEL %s %08x\n",
229 scan->flags &= ~PKGF_BUILDLOOP;
230 build_clear_trav(scan);
234 startbuild(&build_list, &build_tail);
236 if (haswork == 0 && RunningWorkers) {
237 waitbuild(RunningWorkers);
241 pthread_mutex_unlock(&WorkerMutex);
243 ddprintf(0, "BuildCount %d\n", BuildCount);
247 * Traverse the packages (pkg) depends on recursively until we find
248 * a leaf to build or report as unbuildable. Calculates and assigns a
249 * dependency count. Returns all parallel-buildable packages.
251 * (pkg) itself is only added to the list if it is immediately buildable.
255 build_find_leaves(pkg_t *parent, pkg_t *pkg, pkg_t ***build_tailp,
256 int *app, int *hasworkp, int level, int first)
265 * Already on build list, possibly in-progress, tell caller that
268 ddprintf(level, "sbuild_find_leaves %d %s %08x {\n",
269 level, pkg->portdir, pkg->flags);
270 if (pkg->flags & PKGF_BUILDLIST) {
271 ddprintf(level, "} (already on build list)\n");
272 *app |= PKGF_NOTREADY;
273 return (pkg->idep_count);
280 PKGLIST_FOREACH(link, &pkg->idepon_list) {
285 ddprintf(level, "check %s %08x\t", scan->portdir, scan->flags);
288 * When accounting for a successful build, just bump
289 * idep_count by one. scan->idep_count will heavily
290 * overlap packages that we count down multiple branches.
292 * We must still recurse through PACKAGED packages as
293 * some of their dependencies might be missing.
295 if (scan->flags & PKGF_SUCCESS) {
296 ddprintf(0, "SUCCESS - OK\n");
300 if (scan->flags & PKGF_ERROR) {
301 ddprintf(0, "ERROR - OK (propogate failure upward)\n");
302 *app |= PKGF_NOBUILD_S;
305 if (scan->flags & PKGF_NOBUILD) {
306 ddprintf(0, "NOBUILD - OK "
307 "(propogate failure upward)\n");
308 *app |= PKGF_NOBUILD_S;
313 * If already on build-list this dependency is not ready.
315 if (scan->flags & PKGF_BUILDLIST) {
316 ddprintf(0, " [BUILDLIST]");
317 *app |= PKGF_NOTREADY;
321 * If not packaged this dependency is not ready for
324 if ((scan->flags & PKGF_PACKAGED) == 0) {
325 ddprintf(0, " [NOT_PACKAGED]");
326 *app |= PKGF_NOTREADY;
330 * Reduce search complexity, if we have already processed
331 * scan in the traversal it will either already be on the
332 * build list or it will not be buildable. Either way
333 * the parent is not buildable.
335 if (scan->flags & PKGF_BUILDTRAV) {
336 ddprintf(0, " [BUILDTRAV]\n");
337 *app |= PKGF_NOTREADY;
342 * Assert on dependency loop
345 if (scan->flags & PKGF_BUILDLOOP) {
346 dfatal("pkg dependency loop %s -> %s",
347 parent->portdir, scan->portdir);
349 scan->flags |= PKGF_BUILDLOOP;
351 ddprintf(0, " SUBRECURSION {\n");
352 idep_count += build_find_leaves(pkg, scan, build_tailp,
355 scan->flags &= ~PKGF_BUILDLOOP;
357 if (apsub & PKGF_NOBUILD) {
358 ddprintf(level, "} (sub-nobuild)\n");
359 } else if (apsub & PKGF_ERROR) {
360 ddprintf(level, "} (sub-error)\n");
361 } else if (apsub & PKGF_NOTREADY) {
362 ddprintf(level, "} (sub-notready)\n");
364 ddprintf(level, "} (sub-ok)\n");
368 pkg->idep_count = idep_count;
369 pkg->flags |= PKGF_BUILDTRAV;
372 * Incorporate scan results into pkg state.
374 if ((pkg->flags & PKGF_NOBUILD) == 0 && (*app & PKGF_NOBUILD)) {
376 } else if ((pkg->flags & PKGF_ERROR) == 0 && (*app & PKGF_ERROR)) {
379 pkg->flags |= *app & ~PKGF_NOTREADY;
382 * Clear PACKAGED bit if sub-dependencies aren't clean
384 if ((pkg->flags & PKGF_PACKAGED) &&
385 (pkg->flags & (PKGF_NOTREADY|PKGF_ERROR|PKGF_NOBUILD))) {
386 pkg->flags &= ~PKGF_PACKAGED;
387 ddassert(pkg->pkgfile);
388 asprintf(&buf, "%s/%s", RepositoryPath, pkg->pkgfile);
389 if (remove(buf) < 0) {
391 "[XXX] %s DELETE-PACKAGE %s (failed)\n",
395 "[XXX] %s DELETE-PACKAGE %s "
396 "(due to dependencies)\n",
405 * Handle propagated flags
407 if (pkg->flags & PKGF_ERROR) {
408 ddprintf(level, "} (ERROR - %s)\n", pkg->portdir);
409 } else if (pkg->flags & PKGF_NOBUILD) {
410 ddprintf(level, "} (SKIPPED - %s)\n", pkg->portdir);
411 } else if (*app & PKGF_NOTREADY) {
413 * We don't set PKGF_NOTREADY in the pkg, it is strictly
414 * a transient flag propagated via build_find_leaves().
416 * Just don't add the package to the list.
419 } else if (pkg->flags & PKGF_SUCCESS) {
420 ddprintf(level, "} (SUCCESS - %s)\n", pkg->portdir);
421 } else if (pkg->flags & PKGF_DUMMY) {
422 ddprintf(level, "} (DUMMY/META - SUCCESS)\n");
423 pkg->flags |= PKGF_SUCCESS;
426 dlog(DLOG_ALL, "[XXX] %s META-ALREADY-BUILT\n",
430 dlog(DLOG_SUCC, "[XXX] %s meta-node complete\n",
433 } else if (pkg->flags & PKGF_PACKAGED) {
435 * We can just mark the pkg successful. If this is
436 * the first pass, we count this as an initial pruning
437 * pass and reduce BuildTotal.
439 ddprintf(level, "} (PACKAGED - SUCCESS)\n");
440 pkg->flags |= PKGF_SUCCESS;
443 dlog(DLOG_ALL, "[XXX] %s ALREADY-BUILT\n",
449 * All dependencies are successful, queue new work
450 * and indicate not-ready to the parent (since our
451 * package has to be built).
454 ddprintf(level, "} (ADDLIST - %s)\n", pkg->portdir);
455 pkg->flags |= PKGF_BUILDLIST;
457 *build_tailp = &pkg->build_next;
458 *app |= PKGF_NOTREADY;
466 build_clear_trav(pkg_t *pkg)
471 pkg->flags &= ~PKGF_BUILDTRAV;
472 PKGLIST_FOREACH(link, &pkg->idepon_list) {
474 if (scan && (scan->flags & PKGF_BUILDTRAV))
475 build_clear_trav(scan);
480 * Take a list of pkg ready to go, sort it, and assign it to worker
481 * slots. This routine blocks in waitbuild() until it can dispose of
484 * WorkerMutex is held by the caller.
488 startbuild(pkg_t **build_listp, pkg_t ***build_tailp)
499 static int IterateWorker;
504 if (*build_listp == NULL)
511 for (pkg = *build_listp; pkg; pkg = pkg->build_next)
513 idep_ary = calloc(count, sizeof(pkg_t *));
514 depi_ary = calloc(count, sizeof(pkg_t *));
517 for (pkg = *build_listp; pkg; pkg = pkg->build_next) {
518 idep_ary[count] = pkg;
519 depi_ary[count] = pkg;
524 * idep_ary - sorted by #of dependencies this pkg has.
525 * depi_ary - sorted by #of other packages that depend on this pkg.
527 qsort(idep_ary, count, sizeof(pkg_t *), qsort_idep);
528 qsort(depi_ary, count, sizeof(pkg_t *), qsort_depi);
534 * Half the workers build based on the highest depi count,
535 * the other half build based on the highest idep count.
537 * This is an attempt to get projects which many other projects
538 * depend on built first, but to also try to build large projects
539 * (which tend to have a lot of dependencies) earlier rather than
540 * later so the end of the bulk run doesn't inefficiently build
541 * the last few huge projects.
543 * Loop until we manage to assign slots to everyone. We do not
544 * wait for build completion.
546 * This is the point where we handle DUMMY packages (these are
547 * dummy unflavored packages which 'cover' all the flavors for
548 * a package). These are not real packages are marked SUCCESS
549 * at this time because their dependencies (the flavors) have all
552 while (idep_index != count || depi_index != count) {
557 * Find candidate to start sorted by depi or idep.
560 while (idep_index < count) {
561 ipkg = idep_ary[idep_index];
565 PKGF_ERROR | PKGF_NOBUILD |
566 PKGF_RUNNING)) == 0) {
574 while (depi_index < count) {
575 pkgi = depi_ary[depi_index];
579 PKGF_ERROR | PKGF_NOBUILD |
580 PKGF_RUNNING)) == 0) {
587 if (ipkg == NULL && pkgi == NULL)
589 ddassert(ipkg && pkgi);
592 * Block while no slots are available. waitbuild()
593 * will clean out any DONE states.
595 while (RunningWorkers >= DynamicMaxWorkers) {
596 waitbuild(RunningWorkers);
600 * Find an available worker slot, there should be at
603 for (i = 0; i < MaxWorkers; ++i) {
604 n = IterateWorker % MaxWorkers;
605 work = &WorkerAry[n];
607 if (work->state == WORKER_DONE ||
608 work->state == WORKER_FAILED) {
609 workercomplete(work);
611 if (work->state == WORKER_NONE ||
612 work->state == WORKER_IDLE) {
613 if (n <= MaxWorkers / 2) {
614 startworker(pkgi, work);
616 startworker(ipkg, work);
623 ddassert(i != MaxWorkers);
628 * We disposed of the whole list
633 *build_tailp = build_listp;
636 typedef const pkg_t *pkg_tt;
639 qsort_idep(const void *pkg1_arg, const void *pkg2_arg)
641 const pkg_t *pkg1 = *(const pkg_tt *)pkg1_arg;
642 const pkg_t *pkg2 = *(const pkg_tt *)pkg2_arg;
644 return (pkg2->idep_count - pkg1->idep_count);
648 qsort_depi(const void *pkg1_arg, const void *pkg2_arg)
650 const pkg_t *pkg1 = *(const pkg_tt *)pkg1_arg;
651 const pkg_t *pkg2 = *(const pkg_tt *)pkg2_arg;
653 return (pkg2->depi_count - pkg1->depi_count);
657 * Frontend starts a pkg up on a worker
659 * WorkerMutex must be held.
662 startworker(pkg_t *pkg, worker_t *work)
664 switch(work->state) {
666 pthread_create(&work->td, NULL, childBuilderThread, work);
667 work->state = WORKER_IDLE;
670 dlog(DLOG_ALL, "[%03d] %s START\n", work->index, pkg->portdir);
672 pkg->flags |= PKGF_RUNNING;
674 pthread_cond_signal(&work->cond);
684 dfatal("startworker: Unexpected state %d for worker %d",
685 work->state, work->index);
691 cleanworker(worker_t *work)
693 work->state = WORKER_PENDING;
695 work->accum_error = 0;
696 work->start_time = time(NULL);
700 * Frontend finishes up a completed pkg on a worker.
702 * If the worker is in a FAILED state we clean the pkg out but (for now)
703 * leave it in its failed state so we can debug. At this point
704 * workercomplete() will be called every once in a while on the state
705 * and we have to deal with the NULL pkg.
707 * WorkerMutex must be held.
710 workercomplete(worker_t *work)
715 * Steady state FAILED case.
717 if (work->state == WORKER_FAILED) {
718 if (work->pkg == NULL)
723 * Process pkg out of the worker
726 if (pkg->flags & (PKGF_ERROR|PKGF_NOBUILD)) {
727 pkg->flags |= PKGF_FAILURE;
728 if (pkg->flags & PKGF_NOBUILD) {
732 reason = buildskipreason(pkg);
733 dlog(DLOG_SKIP, "[%03d] %s skipped due to%s\n",
734 work->index, pkg->portdir, reason);
738 dlog(DLOG_FAIL, "[%03d] %s build-failed\n",
739 work->index, pkg->portdir);
747 t = time(NULL) - work->start_time;
753 "[%03d] %s\tbuild-succeded in %02d:%02d:%02d\n",
754 work->index, pkg->portdir, h, m, s);
755 pkg->flags |= PKGF_SUCCESS;
759 pkg->flags &= ~PKGF_BUILDLIST;
760 dlog(DLOG_ALL, "[%03d] %s FINISHED (%s)\n",
763 ((pkg->flags & PKGF_SUCCESS) ? "success" : "failure"));
764 pkg->flags &= ~PKGF_RUNNING;
768 if (work->state == WORKER_FAILED) {
769 dlog(DLOG_ALL, "[%03d] XXX/XXX WORKER IS IN A FAILED STATE\n",
772 work->state = WORKER_IDLE;
777 * Wait for one or more workers to complete.
779 * WorkerMutex must be held.
782 waitbuild(int whilematch)
784 static time_t wblast_time;
793 while (RunningWorkers == whilematch) {
794 for (i = 0; i < MaxWorkers; ++i) {
795 work = &WorkerAry[i];
796 if (work->state == WORKER_DONE ||
797 work->state == WORKER_FAILED) {
798 workercomplete(work);
800 pthread_cond_signal(&work->cond);
806 if (RunningWorkers == whilematch) {
807 clock_gettime(CLOCK_REALTIME, &ts);
810 pthread_cond_timedwait(&WorkerCond, &WorkerMutex, &ts);
814 * Dynamically reduce MaxWorkers based on the load. When
815 * the load exceeds 2 x ncpus we reduce the number of workers
816 * by (2 * load / ncpus), but go no lower than 4.
819 if (wblast_time == 0 || wblast_time != t) {
823 getloadavg(dload, 3);
824 if (dload[0] > NumCores * 2) {
825 int reduce = dload[0] * 2.0 / NumCores;
827 reduce = MaxWorkers - reduce;
830 if (reduce > MaxWorkers)
832 if (DynamicMaxWorkers != reduce) {
840 DynamicMaxWorkers = reduce;
842 } else if (DynamicMaxWorkers != MaxWorkers) {
844 "[XXX] Load %6.2f Adjust MaxWorkers "
846 dload[0], DynamicMaxWorkers, MaxWorkers);
847 DynamicMaxWorkers = MaxWorkers;
855 * Worker pthread (WorkerAry)
857 * This thread belongs to the dsynth master process and handled a worker slot.
858 * (this_thread) -> WORKER fork/exec (WorkerPocess) -> (pty) -> sub-processes.
861 childBuilderThread(void *arg)
863 char *envary[1] = { NULL };
864 worker_t *work = arg;
873 pthread_mutex_lock(&WorkerMutex);
874 while (work->terminate == 0) {
877 switch(work->state) {
882 * Fork the management process for the pkg operation
883 * on this worker slot.
885 * This process will set up the environment, do the
886 * mounts, will become the reaper, and will process
887 * pipe commands and handle chroot operations.
889 * NOTE: If SOCK_CLOEXEC is not supported WorkerMutex
890 * is sufficient to interlock F_SETFD FD_CLOEXEC
894 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC,
895 PF_UNSPEC, work->fds)) {
896 dfatal_errno("socketpair() during worker fork");
898 snprintf(slotbuf, sizeof(slotbuf), "%d", work->index);
899 snprintf(fdbuf, sizeof(fdbuf), "3");
905 * We pass the salve descriptor in fd 3 and close all
906 * other descriptors for security.
908 pthread_mutex_unlock(&WorkerMutex);
912 dup2(work->fds[1], 3);
914 fcntl(3, F_SETFD, 0);
915 execle(DSynthExecPath, DSynthExecPath,
916 "WORKER", slotbuf, fdbuf,
917 work->pkg->portdir, work->pkg->pkgfile,
919 write(2, "EXECLE FAILURE\n", 15);
922 pthread_mutex_lock(&WorkerMutex);
924 work->phase = PHASE_PENDING;
927 work->state = WORKER_RUNNING;
931 * Poll for status updates, if NULL is returned
932 * and status is non-zero, the communications link
933 * failed unexpectedly.
936 pthread_mutex_unlock(&WorkerMutex);
937 status = ipcreadmsg(work->fds[0], &wmsg);
938 pthread_mutex_lock(&WorkerMutex);
942 * Normal message, can include normal
943 * termination which changes us over
948 case WMSG_CMD_INSTALL_PKGS:
949 wmsg.cmd = WMSG_RES_INSTALL_PKGS;
950 wmsg.status = childInstallPkgDeps(work);
951 pthread_mutex_unlock(&WorkerMutex);
952 ipcwritemsg(work->fds[0], &wmsg);
953 pthread_mutex_lock(&WorkerMutex);
955 case WMSG_CMD_STATUS_UPDATE:
956 work->phase = wmsg.phase;
957 work->lines = wmsg.lines;
959 case WMSG_CMD_SUCCESS:
960 work->flags |= WORKERF_SUCCESS;
962 case WMSG_CMD_FAILURE:
963 work->flags |= WORKERF_FAILURE;
972 pthread_mutex_unlock(&WorkerMutex);
973 while (waitpid(work->pid, &status, 0) < 0 &&
977 pthread_mutex_lock(&WorkerMutex);
979 if (work->flags & WORKERF_SUCCESS) {
980 pkg->flags |= PKGF_SUCCESS;
981 work->state = WORKER_DONE;
982 } else if (work->flags & WORKERF_FAILURE) {
983 pkg->flags |= PKGF_FAILURE;
984 work->state = WORKER_DONE;
986 pkg->flags |= PKGF_FAILURE;
987 work->state = WORKER_FAILED;
989 work->flags |= WORKERF_STATUS_UPDATE;
990 pthread_cond_signal(&WorkerCond);
995 * pkg remains attached until frontend processes the
996 * completion. The frontend will then set the state
1002 * A worker failure means that the worker did not
1003 * send us a WMSG_CMD_SUCCESS or WMSG_CMD_FAILURE
1004 * ipc before terminating.
1006 * We just sit in this state until the front-end
1007 * does something about it.
1011 dfatal("worker: Unexpected state %d for worker %d",
1012 work->state, work->index);
1018 * The dsynth frontend will poll us approximately once
1019 * a second (its variable).
1022 pthread_cond_wait(&work->cond, &WorkerMutex);
1026 * Scrap the comm socket if running, this should cause the worker
1027 * process to kill its sub-programs and cleanup.
1029 if (work->state == WORKER_RUNNING) {
1030 pthread_mutex_unlock(&WorkerMutex);
1031 close(work->fds[0]);
1032 while (waitpid(work->pid, &status, 0) < 0 &&
1034 pthread_mutex_lock(&WorkerMutex);
1040 work->state = WORKER_EXITING;
1041 pthread_cond_signal(&WorkerCond);
1042 pthread_mutex_unlock(&WorkerMutex);
1048 * Install all the binary packages (we have already built them) that
1049 * the current work package depends on, without duplicates, in a script
1050 * which will be run from within the specified work jail.
1052 * Locked by WorkerMutex (global)
1055 childInstallPkgDeps(worker_t *work)
1060 if (PKGLIST_EMPTY(&work->pkg->idepon_list))
1063 asprintf(&buf, "%s/tmp/dsynth_install_pkgs", work->basedir);
1064 fp = fopen(buf, "w");
1065 ddassert(fp != NULL);
1066 fprintf(fp, "#!/bin/sh\n");
1068 fchmod(fileno(fp), 0755);
1070 childInstallPkgDeps_recurse(fp, &work->pkg->idepon_list, 0);
1071 childInstallPkgDeps_recurse(fp, &work->pkg->idepon_list, 1);
1072 fprintf(fp, "\nexit 0\n");
1079 childInstallPkgDeps_recurse(FILE *fp, pkglink_t *list, int undoit)
1084 PKGLIST_FOREACH(link, list) {
1088 if (pkg->dsynth_install_flg == 1) {
1089 pkg->dsynth_install_flg = 0;
1090 childInstallPkgDeps_recurse(fp,
1096 if (pkg->dsynth_install_flg) {
1097 if (DebugOpt && pkg->pkgfile) {
1098 fprintf(fp, "echo 'AlreadyHave %s'\n",
1104 childInstallPkgDeps_recurse(fp, &pkg->idepon_list, undoit);
1105 if (pkg->dsynth_install_flg)
1107 pkg->dsynth_install_flg = 1;
1110 * If this is a dummy node with no package, the originator
1111 * is requesting a flavored package. We select the default
1112 * flavor which we presume is the first one.
1114 if (pkg->pkgfile == NULL && (pkg->flags & PKGF_DUMMY)) {
1115 pkg_t *spkg = pkg->idepon_list.next->pkg;
1120 "echo 'DUMMY use %s (%p)'\n",
1121 pkg->portdir, pkg->pkgfile);
1124 "echo 'CANNOT FIND DEFAULT FLAVOR "
1131 * Generate package installation command
1134 fprintf(fp, "echo 'Installing /packages/All/%s'\n",
1136 fprintf(fp, "pkg install -q -y /packages/All/%s "
1140 fprintf(fp, "echo 'CANNOT FIND PKG FOR %s'\n",
1147 * Worker process interactions.
1149 * The worker process is responsible for managing the build of a single
1150 * package. It is exec'd by the master dsynth and only loads the
1153 * This process does not run in the chroot. It will become the reaper for
1154 * all sub-processes and it will enter the chroot to execute various phases.
1155 * It catches SIGINTR, SIGHUP, and SIGPIPE and will iterate, terminate, and
1156 * reap all sub-process upon kill or exit.
1158 * The command line forwarded to this function is:
1160 * WORKER slot# socketfd portdir/subdir
1166 * SSL_NO_VERIFY_PEER=1
1167 * USE_PACKAGE_DEPENDS_ONLY=1 (exec_phase_depends)
1169 * PORT_DBDIR=/options For ports options
1170 * PACKAGE_BUILDING=yes Don't build packages that aren't legally
1171 * buildable for a binary repo.
1172 * PKG_DBDIR=/var/db/pkg
1173 * PKG_CACHEDIR=/var/cache/pkg
1174 * PKG_CREATE_VERBOSE=yes Ensure periodic output during packaging
1175 * (custom environment)
1176 * PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
1177 * UNAME_s=DragonFly (example)
1178 * UNAME_v=DragonFly 5.7-SYNTH (example)
1179 * UNAME_p=x86_64 (example)
1180 * UNAME_m=x86_64 (example)
1181 * UNAME_r=5.7-SYNTH (example)
1182 * NO_DEPENDS=yes (exec_phase)
1183 * DISTDIR=/distfiles
1184 * WRKDIRPREFIX=/construction
1186 * MAKE_JOBS_NUMBER=n
1190 * /usr/local/sbin/pkg-static install /packages/All/<the pkg pkg>
1191 * /usr/local/sbin/pkg-static install /packages/All/<pkg>
1192 * (for all dependencies)
1194 * PHASES: make -C path FLAVOR=flavor <phase>
1212 * package e.g. /construction/lang/perl5.28/pkg/perl5-5.28.2.txz
1218 WorkerProcess(int ac, char **av)
1225 int status __unused;
1227 int do_install_phase;
1240 dlog(DLOG_ALL, "WORKER PROCESS %d- bad arguments\n", getpid());
1243 slot = strtol(av[1], NULL, 0);
1244 fd = strtol(av[2], NULL, 0); /* master<->slave messaging */
1247 flavor = strchr(portdir, '@');
1250 bzero(&wmsg, sizeof(wmsg));
1252 setproctitle("WORKER [%02d] STARTUP %s", slot, portdir);
1254 if (strcmp(portdir, "ports-mgmt/pkg") == 0)
1257 signal(SIGTERM, phaseTerminateSignal);
1258 signal(SIGINT, phaseTerminateSignal);
1259 signal(SIGHUP, phaseTerminateSignal);
1262 * Set up the environment
1264 setenv("TERM", "dumb", 1);
1265 setenv("USER", "root", 1);
1266 setenv("HOME", "/root", 1);
1267 setenv("LANG", "C", 1);
1268 setenv("SSL_NO_VERIFY_PEER", "1", 1);
1269 setenv("USE_PACKAGE_DEPENDS_ONLY", "1", 1);
1270 setenv("PORTSDIR", "/xports", 1);
1271 setenv("PORT_DBDIR", "/options", 1);
1272 setenv("PKG_DBDIR", "/var/db/pkg", 1);
1273 setenv("PKG_CACHEDIR", "/var/cache/pkg", 1);
1276 "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin",
1279 setenv("UNAME_s", OperatingSystemName, 1);
1280 setenv("UNAME_v", VersionName, 1);
1281 setenv("UNAME_p", ArchitectureName, 1);
1282 setenv("UNAME_m", MachineName, 1);
1283 setenv("UNAME_r", ReleaseName, 1);
1285 setenv("NO_DEPENDS", "yes", 1);
1286 setenv("DISTDIR", "/distfiles", 1);
1287 setenv("WRKDIRPREFIX", "/construction", 1);
1288 setenv("BATCH", "yes", 1);
1291 * Special consideration
1293 * PACKAGE_BUILDING - Disallow packaging ports which do not allow
1294 * for binary distribution.
1296 * PKG_CREATE_VERBOSE - Ensure periodic output during the packaging
1297 * process to avoid a watchdog timeout.
1300 setenv("PACKAGE_BUILDING", "yes", 1);
1301 setenv("PKG_CREATE_VERBOSE", "yes", 1);
1303 asprintf(&buf, "%d", MaxJobs);
1304 setenv("MAKE_JOBS_NUMBER", buf, 1);
1308 setenv("FLAVOR", flavor, 1);
1311 * Load environment from profile
1313 for (benv = BuildEnv; benv; benv = benv->next) {
1314 if (DebugOpt >= 2) {
1315 dlog(DLOG_ALL, "[%03d] ENV %s=%s\n",
1316 slot, benv->label, benv->data);
1318 setenv(benv->label, benv->data, 1);
1324 if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) < 0)
1325 dfatal_errno("procctl() - Cannot become reaper");
1328 * Initialize a worker structure
1332 bzero(&pkg, sizeof(pkg));
1333 pkg.portdir = portdir; /* sans flavor */
1334 pkg.pkgfile = pkgfile;
1335 if (strchr(portdir, '/'))
1336 len = strchr(portdir, '/') - portdir;
1343 asprintf(&pkg.logfile,
1344 "%s/%*.*s___%s%s%s.log",
1345 LogsPath, len, len, portdir,
1346 ((portdir[len] == '/') ? portdir + len + 1 : portdir + len),
1347 (flavor ? "@" : ""),
1348 (flavor ? flavor : ""));
1349 tmpfd = open(pkg.logfile, O_RDWR|O_CREAT|O_TRUNC, 0666);
1351 dlog(DLOG_ALL, "[%03d] %s LOGFILE %s\n",
1352 slot, pkg.portdir, pkg.logfile);
1355 dlog(DLOG_ALL, "[%03d] LOGFILE %s (create failed)\n",
1360 * Setup the work structure. Because this is an exec'd sub-process,
1361 * there is only one work structure.
1363 work = &WorkerAry[0];
1364 work->flavor = flavor;
1367 work->start_time = time(NULL);
1370 * Do mounts and start the phases
1373 setproctitle("WORKER [%02d] MOUNTS %s", slot, portdir);
1374 DoWorkerMounts(work);
1376 wmsg.cmd = WMSG_CMD_INSTALL_PKGS;
1377 ipcwritemsg(fd, &wmsg);
1378 status = ipcreadmsg(fd, &wmsg);
1379 if (status < 0 || wmsg.cmd != WMSG_RES_INSTALL_PKGS)
1380 dfatal("pkg installation handshake failed");
1381 do_install_phase = wmsg.status;
1383 wmsg.cmd = WMSG_CMD_STATUS_UPDATE;
1384 wmsg.phase = PHASE_INSTALL_PKGS;
1387 status = ipcwritemsg(fd, &wmsg);
1390 dophase(work, &wmsg,
1391 WDOG5, PHASE_PACKAGE, "package");
1393 if (do_install_phase) {
1394 dophase(work, &wmsg,
1395 WDOG4, PHASE_INSTALL_PKGS, "setup");
1397 dophase(work, &wmsg,
1398 WDOG2, PHASE_CHECK_SANITY, "check-sanity");
1399 dophase(work, &wmsg,
1400 WDOG2, PHASE_PKG_DEPENDS, "pkg-depends");
1401 dophase(work, &wmsg,
1402 WDOG7, PHASE_FETCH_DEPENDS, "fetch-depends");
1403 dophase(work, &wmsg,
1404 WDOG7, PHASE_FETCH, "fetch");
1405 dophase(work, &wmsg,
1406 WDOG2, PHASE_CHECKSUM, "checksum");
1407 dophase(work, &wmsg,
1408 WDOG3, PHASE_EXTRACT_DEPENDS, "extract-depends");
1409 dophase(work, &wmsg,
1410 WDOG3, PHASE_EXTRACT, "extract");
1411 dophase(work, &wmsg,
1412 WDOG2, PHASE_PATCH_DEPENDS, "patch-depends");
1413 dophase(work, &wmsg,
1414 WDOG2, PHASE_PATCH, "patch");
1415 dophase(work, &wmsg,
1416 WDOG5, PHASE_BUILD_DEPENDS, "build-depends");
1417 dophase(work, &wmsg,
1418 WDOG5, PHASE_LIB_DEPENDS, "lib-depends");
1419 dophase(work, &wmsg,
1420 WDOG3, PHASE_CONFIGURE, "configure");
1421 dophase(work, &wmsg,
1422 WDOG9, PHASE_BUILD, "build");
1423 dophase(work, &wmsg,
1424 WDOG5, PHASE_RUN_DEPENDS, "run-depends");
1425 dophase(work, &wmsg,
1426 WDOG5, PHASE_STAGE, "stage");
1428 dophase(work, &wmsg,
1429 WDOG5, PHASE_TEST, "test");
1431 dophase(work, &wmsg,
1432 WDOG1, PHASE_CHECK_PLIST, "check-plist");
1433 dophase(work, &wmsg,
1434 WDOG5, PHASE_PACKAGE, "package");
1436 dophase(work, &wmsg,
1437 WDOG5, PHASE_INSTALL_MTREE, "install-mtree");
1438 dophase(work, &wmsg,
1439 WDOG5, PHASE_INSTALL, "install");
1440 dophase(work, &wmsg,
1441 WDOG5, PHASE_DEINSTALL, "deinstall");
1445 setproctitle("WORKER [%02d] CLEANUP %s", slot, portdir);
1448 * Copy the package to the repo.
1450 if (work->accum_error == 0) {
1454 asprintf(&b1, "%s/construction/%s/pkg/%s",
1455 work->basedir, pkg.portdir, pkg.pkgfile);
1456 asprintf(&b2, "%s/%s", RepositoryPath, pkg.pkgfile);
1457 if (copyfile(b1, b2)) {
1458 ++work->accum_error;
1459 dlog(DLOG_ALL, "[%03d] %s Unable to copy %s to %s\n",
1460 work->index, pkg.portdir, b1, b2);
1466 DoWorkerUnmounts(work);
1468 if (work->accum_error) {
1469 wmsg.cmd = WMSG_CMD_FAILURE;
1471 wmsg.cmd = WMSG_CMD_SUCCESS;
1474 status = ipcwritemsg(fd, &wmsg);
1478 dophase(worker_t *work, wmsg_t *wmsg, int wdog, int phaseid, const char *phase)
1480 pkg_t *pkg = work->pkg;
1493 if (work->accum_error)
1495 setproctitle("WORKER [%02d] %8.8s %s",
1496 work->index, phase, pkg->portdir);
1497 wmsg->phase = phaseid;
1498 if (ipcwritemsg(work->fds[0], wmsg) < 0) {
1499 dlog(DLOG_ALL, "[%03d] %s Lost Communication with dsynth, "
1500 "aborting worker\n",
1501 work->index, pkg->portdir);
1502 ++work->accum_error;
1507 * Execute the port make command in chroot on a pty
1511 if ((pid = forkpty(&fdmaster, NULL, NULL, NULL)) == 0) {
1513 if (chdir(work->basedir) < 0)
1514 dfatal_errno("chdir in phase initialization");
1515 if (chroot(work->basedir) < 0)
1516 dfatal_errno("chroot in phase initialization");
1519 * Execute the appropriate command.
1522 case PHASE_INSTALL_PKGS:
1523 snprintf(buf, sizeof(buf), "/tmp/dsynth_install_pkgs");
1524 execl(buf, buf, NULL);
1527 snprintf(buf, sizeof(buf), "/xports/%s", pkg->portdir);
1528 execl(MAKE_BINARY, MAKE_BINARY, "-C", buf, phase, NULL);
1534 FDMaster = fdmaster; /* for signal handler */
1537 fdlog = open(pkg->logfile, O_RDWR|O_CREAT|O_APPEND, 0644);
1539 dlog(DLOG_ALL, "[%03d] %s Cannot open logfile '%s': %s\n",
1540 work->index, pkg->portdir,
1541 pkg->logfile, strerror(errno));
1543 start_time = time(NULL);
1544 last_time = start_time;
1545 wdog_time = start_time;
1552 pfd.events = POLLIN;
1555 poll(&pfd, 1, 1000);
1557 r = read(fdmaster, buf, sizeof(buf));
1559 if (r < 0 && errno == EINTR)
1563 wdog_time = time(NULL);
1564 if (r > 0 && fdlog >= 0)
1565 write(fdlog, buf, r);
1573 * Generally speaking update status once a second.
1574 * This also allows us to detect if the management
1575 * dsynth process has gone away.
1577 next_time = time(NULL);
1578 if (next_time != last_time) {
1584 * Send status update to the worker management thread
1585 * in the master dsynth process. Remember, *WE* are
1586 * the worker management process sub-fork.
1588 if (ipcwritemsg(work->fds[0], wmsg) < 0)
1590 last_time = next_time;
1595 getloadavg(dload, 3);
1596 dv = dload[2] / NumCores;
1597 if (dv < (double)NumCores) {
1600 if (dv > 4.0 * NumCores)
1601 dv = 4.0 * NumCores;
1602 wdog_scaled = wdog * dv / NumCores;
1608 if (next_time - wdog_time >= wdog_scaled * 60) {
1609 snprintf(buf, sizeof(buf),
1611 "WATCHDOG TIMEOUT FOR %s in %s "
1612 "after %d minutes\n"
1615 pkg->portdir, phase, wdog_scaled, pid);
1617 write(fdlog, buf, strlen(buf));
1619 "[%03d] WATCHDOG TIMEOUT FOR %s in %s "
1620 "after %d minutes (%d min scaled)\n",
1621 work->index, pkg->portdir, phase,
1624 ++work->accum_error;
1630 * Check process exit. Normally the pty will EOF
1631 * but if background processes remain we need to
1632 * check here to see if our primary exec is done,
1633 * so we can break out and reap those processes.
1635 if (waitpid(pid, &status, WNOHANG) == pid &&
1636 WIFEXITED(status)) {
1642 next_time = time(NULL);
1644 setproctitle("WORKER [%02d] EXITREAP %s",
1645 work->index, pkg->portdir);
1647 while (WIFEXITED(status) == 0) {
1648 if (waitpid(pid, &status, 0) < 0) {
1655 if (WEXITSTATUS(status)) {
1656 dlog(DLOG_ALL, "[%03d] %s Build phase '%s' failed\n",
1657 work->index, pkg->portdir, phase);
1658 ++work->accum_error;
1662 * Kill any processes still running (sometimes processes end up in
1663 * the background during a dports build), and clean up any other
1664 * children that we have inherited.
1677 last_time = next_time - start_time;
1679 m = last_time / 60 % 60;
1680 h = last_time / 3600;
1682 fp = fdopen(fdlog, "a");
1684 dlog(DLOG_ALL, "[%03d] %s Cannot fdopen fdlog: %s %d\n",
1685 work->index, pkg->portdir,
1686 strerror(errno), fstat(fdlog, &st));
1691 fprintf(fp, "--------\n");
1692 if (work->accum_error) {
1693 fprintf(fp, "PHASE %s FAILED %02d:%02d:%02d\n",
1696 fprintf(fp, "PHASE %s SUCCEEDED %02d:%02d:%02d\n",
1699 last_time = next_time - work->start_time;
1701 m = last_time / 60 % 60;
1702 h = last_time / 3600;
1703 if (phaseid == PHASE_PACKAGE) {
1704 fprintf(fp, "TOTAL TIME %02d:%02d:%02d\n", h, m, s);
1706 fprintf(fp, "--------\n");
1717 struct reaper_status rs;
1720 while (procctl(P_PID, getpid(), PROC_REAP_STATUS, &rs) == 0) {
1721 if ((rs.flags & PROC_REAP_ACQUIRE) == 0)
1723 if (rs.pid_head < 0)
1725 if (kill(rs.pid_head, SIGKILL) == 0) {
1726 while (waitpid(rs.pid_head, &status, 0) < 0)
1730 while (wait3(&status, 0, NULL) > 0)
1735 phaseTerminateSignal(int sig __unused)
1740 kill(SigPid, SIGKILL);
1743 DoWorkerUnmounts(SigWork);
1749 buildskipreason(pkg_t *pkg)
1753 char *reason = NULL;
1758 PKGLIST_FOREACH(link, &pkg->idepon_list) {
1762 if ((scan->flags & (PKGF_ERROR | PKGF_NOBUILD)) == 0)
1764 len = strlen(scan->portdir);
1765 reason = realloc(reason, tot + len + 8);
1766 snprintf(reason + tot, len + 8, " %s", scan->portdir);
1767 tot += strlen(reason + tot);
1773 * Copy a (package) file from (src) to (dst), use an intermediate file and
1774 * rename to ensure that interruption does not leave us with a corrupt
1777 * This is called by the WORKER process.
1779 * (dsynth management thread -> WORKER process -> sub-processes)
1781 #define COPYBLKSIZE 32768
1784 copyfile(char *src, char *dst)
1793 asprintf(&tmp, "%s.new", dst);
1794 buf = malloc(COPYBLKSIZE);
1796 fd1 = open(src, O_RDONLY|O_CLOEXEC);
1797 fd2 = open(tmp, O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0644);
1798 while ((r = read(fd1, buf, COPYBLKSIZE)) > 0) {
1799 if (write(fd2, buf, r) != r)
1807 if (rename(tmp, dst)) {