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
49 int UseNCurses
= -1; /* indicates default operation (enabled) */
50 int LeveragePrebuilt
= 0;
51 int WorkerProcFlags
= 0;
52 int DeleteObsoletePkgs
;
54 const char *OperatingSystemName
= "Unknown"; /* e.g. "DragonFly" */
55 const char *ArchitectureName
= "unknown"; /* e.g. "x86_64" */
56 const char *MachineName
= "unknown"; /* e.g. "x86_64" */
57 const char *VersionName
= "unknown"; /* e.g. "DragonFly 5.7-SYNTH" */
58 const char *VersionOnlyName
= "unknown"; /* e.g. "5.7-SYNTH" */
59 const char *VersionFromParamHeader
= "unknown"; /* e.g. "500704" */
60 const char *VersionFromSysctl
= "unknown"; /* e.g. "500704" */
61 const char *ReleaseName
= "unknown"; /* e.g. "5.7" */
62 const char *DPortsPath
= "/usr/dports";
63 const char *CCachePath
= DISABLED_STR
;
64 const char *PackagesPath
= "/build/synth/live_packages";
65 const char *RepositoryPath
= "/build/synth/live_packages/All";
66 const char *OptionsPath
= "/build/synth/options";
67 const char *DistFilesPath
= "/build/synth/distfiles";
68 const char *BuildBase
= "/build/synth/build";
69 const char *LogsPath
= "/build/synth/logs";
70 const char *SystemPath
= "/";
71 const char *UsePkgSufx
= USE_PKG_SUFX
;
75 static const char *ProfileLabel
= "[LiveSystem]"; /* with the brackets */
76 const char *Profile
= "LiveSystem"; /* without the brackets */
80 * Hooks are scripts in ConfigBase
83 const char *HookRunStart
;
84 const char *HookRunEnd
;
85 const char *HookPkgSuccess
;
86 const char *HookPkgFailure
;
87 const char *HookPkgIgnored
;
88 const char *HookPkgSkipped
;
90 const char *ConfigBase
; /* The config base we found */
91 const char *ConfigBase1
= "/etc/dsynth";
92 const char *ConfigBase2
= "/usr/local/etc/dsynth";
94 static void parseConfigFile(const char *path
);
95 static void parseProfile(const char *cpath
, const char *path
);
96 static char *stripwhite(char *str
);
97 static int truefalse(const char *str
);
98 static char *dokernsysctl(int m1
, int m2
);
99 static void getElfInfo(const char *path
);
100 static char *checkhook(const char *scriptname
);
103 ParseConfiguration(int isworker
)
113 * Get the default OperatingSystemName, ArchitectureName, and
116 OperatingSystemName
= dokernsysctl(CTL_KERN
, KERN_OSTYPE
);
117 ArchitectureName
= dokernsysctl(CTL_HW
, HW_MACHINE_ARCH
);
118 MachineName
= dokernsysctl(CTL_HW
, HW_MACHINE
);
119 ReleaseName
= dokernsysctl(CTL_KERN
, KERN_OSRELEASE
);
120 asprintf(&osreldate
, "%d", getosreldate());
121 VersionFromSysctl
= osreldate
;
124 * Retrieve resource information from the system. Note that
125 * NumCores and PhysMem will also be used for dynamic load
129 len
= sizeof(NumCores
);
130 if (sysctlbyname("hw.ncpu", &NumCores
, &len
, NULL
, 0) < 0)
131 dfatal_errno("Cannot get hw.ncpu");
133 len
= sizeof(PhysMem
);
134 if (sysctlbyname("hw.physmem", &PhysMem
, &len
, NULL
, 0) < 0)
135 dfatal_errno("Cannot get hw.physmem");
138 * Calculate nominal defaults.
141 MaxWorkers
= MaxBulk
/ 2;
142 if (MaxWorkers
> (int)((PhysMem
+ (ONEGB
/2)) / ONEGB
))
143 MaxWorkers
= (PhysMem
+ (ONEGB
/2)) / ONEGB
;
153 * Configuration file must exist. Look for it in
154 * "/etc/dsynth" and "/usr/local/etc/dsynth".
156 ConfigBase
= ConfigBase1
;
157 asprintf(&synth_config
, "%s/dsynth.ini", ConfigBase1
);
158 if (stat(synth_config
, &st
) < 0) {
159 ConfigBase
= ConfigBase2
;
160 asprintf(&synth_config
, "%s/dsynth.ini", ConfigBase2
);
163 if (stat(synth_config
, &st
) < 0) {
164 dfatal("Configuration file missing, "
165 "could not find %s/dsynth.ini or %s/dsynth.ini\n",
171 * Check to see what hooks we have
173 HookRunStart
= checkhook("hook_run_start");
174 HookRunEnd
= checkhook("hook_run_end");
175 HookPkgSuccess
= checkhook("hook_pkg_success");
176 HookPkgFailure
= checkhook("hook_pkg_failure");
177 HookPkgIgnored
= checkhook("hook_pkg_ignored");
178 HookPkgSkipped
= checkhook("hook_pkg_skipped");
181 * Parse the configuration file(s). This may override some of
182 * the above defaults.
184 parseConfigFile(synth_config
);
185 parseProfile(synth_config
, ProfileLabel
);
188 * Figure out whether CCache is configured. Also set UseUsrSrc
189 * if it exists under the system path.
191 * Not supported for the moment
193 if (strcmp(CCachePath
, "disabled") != 0) {
194 /* dfatal("Directory_ccache is not supported, please\n"
195 " set to 'disabled'\n"); */
198 asprintf(&buf
, "%s/usr/src/sys/Makefile", SystemPath
);
199 if (stat(buf
, &st
) == 0)
204 * Default pkg dependency memory target. This is a heuristical
205 * calculation for how much memory we are willing to put towards
206 * pkg install dependencies. The builder count is reduced as needed.
208 * Reduce the target even further when CCache is enabled due to
209 * its added overhead (even though it doesn't use tmpfs).
210 * (NOT CURRENTLY IMPLEMENTED, LEAVE THE SAME)
212 if (PkgDepMemoryTarget
== 0) {
214 PkgDepMemoryTarget
= PhysMem
/ 3;
216 PkgDepMemoryTarget
= PhysMem
/ 3;
220 * If this is a dsynth WORKER exec it handles a single slot,
221 * just set MaxWorkers to 1.
229 if (stat(DPortsPath
, &st
) < 0)
230 dfatal("Directory missing: %s", DPortsPath
);
231 if (stat(PackagesPath
, &st
) < 0)
232 dfatal("Directory missing: %s", PackagesPath
);
233 if (stat(OptionsPath
, &st
) < 0)
234 dfatal("Directory missing: %s", OptionsPath
);
235 if (stat(DistFilesPath
, &st
) < 0)
236 dfatal("Directory missing: %s", DistFilesPath
);
237 if (stat(BuildBase
, &st
) < 0)
238 dfatal("Directory missing: %s", BuildBase
);
239 if (stat(LogsPath
, &st
) < 0)
240 dfatal("Directory missing: %s", LogsPath
);
241 if (stat(SystemPath
, &st
) < 0)
242 dfatal("Directory missing: %s", SystemPath
);
243 if (UseCCache
&& stat(CCachePath
, &st
) < 0)
244 dfatal("Directory missing: %s", CCachePath
);
247 * Now use the SystemPath to retrieve file information from /bin/sh,
248 * and use this to set OperatingSystemName, ArchitectureName,
249 * MachineName, and ReleaseName.
251 * Since this method is used to build for specific releases, require
254 asprintf(&buf
, "%s/bin/sh", SystemPath
);
259 * Calculate VersionName from OperatingSystemName and ReleaseName.
261 if (strchr(ReleaseName
, '-')) {
262 reln
= strchr(ReleaseName
, '-') - ReleaseName
;
263 asprintf(&buf
, "%s %*.*s-SYNTH",
265 reln
, reln
, ReleaseName
);
267 asprintf(&buf
, "%*.*s-SYNTH", reln
, reln
, ReleaseName
);
268 VersionOnlyName
= buf
;
270 asprintf(&buf
, "%s %s-SYNTH",
273 asprintf(&buf
, "%s-SYNTH", ReleaseName
);
274 VersionOnlyName
= buf
;
278 * Get __DragonFly_version from the system header via SystemPath
284 asprintf(&buf
, "%s/usr/include/sys/param.h", SystemPath
);
285 fp
= fopen(buf
, "r");
287 dpanic_errno("Cannot open %s", buf
);
288 while ((ptr
= fgetln(fp
, &len
)) != NULL
) {
289 if (len
== 0 || ptr
[len
-1] != '\n')
292 if (strncmp(ptr
, "#define __DragonFly_version", 27))
295 ptr
= strtok(ptr
, " \t\r\n");
296 VersionFromParamHeader
= strdup(ptr
);
301 /* Warn the user that World/kernel are out of sync */
302 if (strcmp(VersionFromSysctl
, VersionFromParamHeader
)) {
303 dlog(DLOG_ALL
, "Kernel (%s) out of sync with world (%s) on %s\n",
304 VersionFromSysctl
, VersionFromParamHeader
, SystemPath
);
309 * If RepositoryPath is under PackagesPath, make sure it
312 if (strncmp(RepositoryPath
, PackagesPath
, strlen(PackagesPath
)) == 0) {
313 if (stat(RepositoryPath
, &st
) < 0) {
314 if (mkdir(RepositoryPath
, 0755) < 0)
315 dfatal_errno("Cannot mkdir '%s'",
320 if (stat(RepositoryPath
, &st
) < 0)
321 dfatal("Directory missing: %s", RepositoryPath
);
324 * StatsBase, StatsFilePath, StatsLockPath
326 asprintf(&StatsBase
, "%s/stats", LogsPath
);
327 asprintf(&StatsFilePath
, "%s/monitor.dat", StatsBase
);
328 asprintf(&StatsLockPath
, "%s/monitor.lk", StatsBase
);
334 dfatal("Not Implemented");
338 parseConfigFile(const char *path
)
349 fp
= fopen(path
, "r");
351 ddprintf(0, "Warning: Config file %s does not exist\n", path
);
355 ddprintf(0, "ParseConfig %s\n", path
);
357 if (ProfileOverrideOpt
) {
358 Profile
= strdup(ProfileOverrideOpt
);
359 asprintf(&l2
, "[%s]", Profile
);
363 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
366 if (len
== 0 || buf
[len
-1] != '\n')
371 * Remove any trailing whitespace, ignore empty lines.
373 while (len
> 0 && isspace(buf
[len
-1]))
382 if (buf
[0] == ';' || buf
[0] == '#')
385 if (strcmp(buf
, "[Global Configuration]") == 0)
386 mode
= 0; /* parse global config */
387 else if (strcmp(buf
, ProfileLabel
) == 0)
388 mode
= 1; /* use profile */
390 mode
= -1; /* ignore profile */
394 bcopy(buf
, copy
, len
+ 1);
396 l1
= strtok(copy
, "=");
398 dfatal("Syntax error in config line %d: %s\n",
401 l2
= strtok(NULL
, " \t\n");
403 dfatal("Syntax error in config line %d: %s\n",
412 * Global Configuration
414 if (strcmp(l1
, "profile_selected") == 0) {
415 if (ProfileOverrideOpt
== NULL
) {
416 Profile
= strdup(l2
);
417 asprintf(&l2
, "[%s]", l2
);
421 dfatal("Unknown directive in config "
422 "line %d: %s\n", lineno
, buf
);
430 if (strcmp(l1
, "Operating_system") == 0) {
431 OperatingSystemName
= l2
;
432 } else if (strcmp(l1
, "Directory_packages") == 0) {
434 } else if (strcmp(l1
, "Directory_repository") == 0) {
436 } else if (strcmp(l1
, "Directory_portsdir") == 0) {
438 } else if (strcmp(l1
, "Directory_options") == 0) {
440 } else if (strcmp(l1
, "Directory_distfiles") == 0) {
442 } else if (strcmp(l1
, "Directory_buildbase") == 0) {
444 } else if (strcmp(l1
, "Directory_logs") == 0) {
446 } else if (strcmp(l1
, "Directory_ccache") == 0) {
448 } else if (strcmp(l1
, "Directory_system") == 0) {
450 } else if (strcmp(l1
, "Package_suffix") == 0) {
452 dassert(strcmp(l2
, ".tgz") == 0 ||
453 strcmp(l2
, ".tar") == 0 ||
454 strcmp(l2
, ".txz") == 0 ||
455 strcmp(l2
, ".tzst") == 0 ||
456 strcmp(l2
, ".tbz") == 0,
457 "Config: Unknown Package_suffix,"
458 "specify .tgz .tar .txz .tbz or .tzst");
459 } else if (strcmp(l1
, "Meta_version") == 0) {
460 MetaVersion
= strtol(l2
, NULL
, 0);
461 } else if (strcmp(l1
, "Number_of_builders") == 0) {
462 MaxWorkers
= strtol(l2
, NULL
, 0);
464 MaxWorkers
= NumCores
/ 2 + 1;
466 if (MaxWorkers
< 0 || MaxWorkers
> MAXWORKERS
) {
467 dfatal("Config: Number_of_builders "
472 } else if (strcmp(l1
, "Max_jobs_per_builder") == 0) {
473 MaxJobs
= strtol(l2
, NULL
, 0);
477 if (MaxJobs
< 0 || MaxJobs
> MAXJOBS
) {
478 dfatal("Config: Max_jobs_per_builder "
483 } else if (strcmp(l1
, "Tmpfs_workdir") == 0) {
484 UseTmpfsWork
= truefalse(l2
);
485 dassert(UseTmpfsWork
== 1,
486 "Config: Tmpfs_workdir must be "
487 "set to true, 'false' not supported");
488 } else if (strcmp(l1
, "Tmpfs_localbase") == 0) {
489 UseTmpfsBase
= truefalse(l2
);
490 dassert(UseTmpfsBase
== 1,
491 "Config: Tmpfs_localbase must be "
492 "set to true, 'false' not supported");
493 } else if (strcmp(l1
, "Display_with_ncurses") == 0) {
494 if (UseNCurses
== -1)
495 UseNCurses
= truefalse(l2
);
496 } else if (strcmp(l1
, "leverage_prebuilt") == 0) {
497 LeveragePrebuilt
= truefalse(l2
);
498 dassert(LeveragePrebuilt
== 0,
499 "Config: leverage_prebuilt not "
500 "supported and must be set to false");
501 } else if (strcmp(l1
, "Check_plist") == 0) {
504 WORKER_PROC_CHECK_PLIST
;
506 } else if (strcmp(l1
, "Numa_setsize") == 0) {
507 NumaSetSize
= strtol(l2
, NULL
, 0);
510 dfatal("Unknown directive in profile section "
511 "line %d: %s\n", lineno
, buf
);
516 * Ignore unselected profile
525 * NOTE: profile has brackets, e.g. "[LiveSystem]".
528 parseProfile(const char *cpath
, const char *profile
)
541 while (len
&& cpath
[len
-1] != '/')
545 plen
= strlen(profile
);
546 ddassert(plen
> 2 && profile
[0] == '[' && profile
[plen
-1] == ']');
548 asprintf(&ppath
, "%*.*s%*.*s-make.conf",
549 len
, len
, cpath
, plen
- 2, plen
- 2, profile
+ 1);
550 fp
= fopen(ppath
, "r");
552 ddprintf(0, "Warning: Profile %s does not exist\n", ppath
);
556 ddprintf(0, "ParseProfile %s\n", ppath
);
559 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
562 if (len
== 0 || buf
[len
-1] != '\n')
567 * Remove any trailing whitespace, ignore empty lines.
569 while (len
> 0 && isspace(buf
[len
-1]))
575 * Allow empty lines, ignore comments.
580 if (buf
[0] == ';' || buf
[0] == '#')
584 * Require env variable name
586 bcopy(buf
, copy
, len
+ 1);
587 l1
= strtok(copy
, "=");
589 dfatal("Syntax error in profile line %d: %s\n",
594 * Allow empty assignment
596 l2
= strtok(NULL
, " \t\n");
598 l2
= l1
+ strlen(l1
);
604 * Add to builder environment
606 addbuildenv(l1
, l2
, BENV_MAKECONF
);
608 ddprintf(4, "%s=%s\n", l1
, l2
);
612 ddprintf(0, "ParseProfile finished\n");
616 stripwhite(char *str
)
621 while (len
> 0 && isspace(str
[len
-1]))
625 while (*str
&& isspace(*str
))
631 truefalse(const char *str
)
633 if (strcmp(str
, "0") == 0)
635 if (strcmp(str
, "1") == 0)
637 if (strcasecmp(str
, "false") == 0)
639 if (strcasecmp(str
, "true") == 0)
641 dfatal("syntax error for boolean '%s': "
642 "must be '0', '1', 'false', or 'true'", str
);
647 dokernsysctl(int m1
, int m2
)
649 int mib
[] = { m1
, m2
};
653 len
= sizeof(buf
) - 1;
654 if (sysctl(mib
, 2, buf
, &len
, NULL
, 0) < 0)
655 dfatal_errno("sysctl for system/architecture");
663 int version
; /* e.g. 500702 -> 5.7 */
672 getElfInfo(const char *path
)
684 asprintf(&cmd
, "readelf -x .note.tag %s", path
);
685 fp
= popen(cmd
, "r");
686 dassert_errno(fp
, "Cannot run: %s", cmd
);
689 while (n
!= sizeof(note
) &&
690 (base
= fgetln(fp
, &size
)) != NULL
&& size
) {
692 if (strncmp(base
, " 0x", 3) != 0)
694 r
= sscanf(base
, "%x %x %x %x %x",
695 &addr
, &v
[0], &v
[1], &v
[2], &v
[3]);
702 r
= (r
- 1) * sizeof(v
[0]);
703 if (n
+ r
> sizeof(note
))
704 r
= sizeof(note
) - n
;
705 bcopy((char *)v
, (char *)¬e
+ n
, r
);
710 if (n
!= sizeof(note
))
711 dfatal("Unable to parse output from: %s", cmd
);
712 if (strncmp(OperatingSystemName
, note
.osname1
, sizeof(note
.osname1
))) {
713 dfatal("%s ELF, mismatch OS name %.*s vs %s",
714 path
, (int)sizeof(note
.osname1
),
715 note
.osname1
, OperatingSystemName
);
719 asprintf(&cmd
, "%d.%d",
720 note
.version
/ 100000,
721 (note
.version
% 100000) / 100);
722 } else if (note
.zero
) {
723 asprintf(&cmd
, "%d.%d",
725 (note
.zero
% 100000) / 100);
727 dfatal("%s ELF, cannot extract version info", path
);
733 checkhook(const char *scriptname
)
738 asprintf(&path
, "%s/%s", ConfigBase
, scriptname
);
739 if (stat(path
, &st
) < 0 || (st
.st_mode
& 0111) == 0) {