xref: /dragonfly/usr.bin/dsynth/config.c (revision 631c21f2)
1 /*
2  * Copyright (c) 2019 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * This code uses concepts and configuration based on 'synth', by
8  * John R. Marino <draco@marino.st>, which was written in ada.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  *
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
19  *    distribution.
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.
23  *
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
35  * SUCH DAMAGE.
36  */
37 
38 #include "dsynth.h"
39 
40 int UseCCache;
41 int UseUsrSrc;
42 int UseTmpfs;
43 int NumCores = 1;
44 int MaxBulk = 8;
45 int MaxWorkers = 8;
46 int MaxJobs = 8;
47 int UseTmpfsWork = 1;
48 int UseTmpfsBase = 1;
49 int UseNCurses = -1;		/* indicates default operation (enabled) */
50 int LeveragePrebuilt = 0;
51 int WorkerProcFlags = 0;
52 int DeleteObsoletePkgs;
53 long PhysMem;
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;
72 char *StatsBase;
73 char *StatsFilePath;
74 char *StatsLockPath;
75 static const char *ProfileLabel = "[LiveSystem]"; /* with the brackets */
76 const char *Profile = "LiveSystem";		/* without the brackets */
77 
78 /*
79  * Hooks are scripts in ConfigBase
80  */
81 int UsingHooks;
82 const char *HookRunStart;
83 const char *HookRunEnd;
84 const char *HookPkgSuccess;
85 const char *HookPkgFailure;
86 const char *HookPkgIgnored;
87 const char *HookPkgSkipped;
88 
89 const char *ConfigBase;				/* The config base we found */
90 const char *ConfigBase1 = "/etc/dsynth";
91 const char *ConfigBase2 = "/usr/local/etc/dsynth";
92 
93 static void parseConfigFile(const char *path);
94 static void parseProfile(const char *cpath, const char *path);
95 static char *stripwhite(char *str);
96 static int truefalse(const char *str);
97 static char *dokernsysctl(int m1, int m2);
98 static void getElfInfo(const char *path);
99 static char *checkhook(const char *scriptname);
100 
101 void
102 ParseConfiguration(int isworker)
103 {
104 	struct stat st;
105 	size_t len;
106 	int reln;
107 	char *synth_config;
108 	char *buf;
109 	char *osreldate;
110 
111 	/*
112 	 * Get the default OperatingSystemName, ArchitectureName, and
113 	 * ReleaseName.
114 	 */
115 	OperatingSystemName = dokernsysctl(CTL_KERN, KERN_OSTYPE);
116 	ArchitectureName = dokernsysctl(CTL_HW, HW_MACHINE_ARCH);
117 	MachineName = dokernsysctl(CTL_HW, HW_MACHINE);
118 	ReleaseName = dokernsysctl(CTL_KERN, KERN_OSRELEASE);
119 	asprintf(&osreldate, "%d", getosreldate());
120 	VersionFromSysctl = osreldate;
121 
122 	/*
123 	 * Retrieve resource information from the system.  Note that
124 	 * NumCores and PhysMem will also be used for dynamic load
125 	 * management.
126 	 */
127 	NumCores = 1;
128 	len = sizeof(NumCores);
129 	if (sysctlbyname("hw.ncpu", &NumCores, &len, NULL, 0) < 0)
130 		dfatal_errno("Cannot get hw.ncpu");
131 
132 	len = sizeof(PhysMem);
133 	if (sysctlbyname("hw.physmem", &PhysMem, &len, NULL, 0) < 0)
134 		dfatal_errno("Cannot get hw.physmem");
135 
136 	/*
137 	 * Calculate nominal defaults.
138 	 */
139 	MaxBulk = NumCores;
140 	MaxWorkers = MaxBulk / 2;
141 	if (MaxWorkers > (int)((PhysMem + (ONEGB/2)) / ONEGB))
142 		MaxWorkers = (PhysMem + (ONEGB/2)) / ONEGB;
143 
144 	if (MaxBulk < 1)
145 		MaxBulk = 1;
146 	if (MaxWorkers < 1)
147 		MaxWorkers = 1;
148 	if (MaxJobs < 1)
149 		MaxJobs = 1;
150 
151 	/*
152 	 * Configuration file must exist.  Look for it in
153 	 * "/etc/dsynth" and "/usr/local/etc/dsynth".
154 	 */
155 	ConfigBase = ConfigBase1;
156 	asprintf(&synth_config, "%s/dsynth.ini", ConfigBase1);
157 	if (stat(synth_config, &st) < 0) {
158 		ConfigBase = ConfigBase2;
159 		asprintf(&synth_config, "%s/dsynth.ini", ConfigBase2);
160 	}
161 
162 	if (stat(synth_config, &st) < 0) {
163 		dfatal("Configuration file missing, "
164 		       "could not find %s/dsynth.ini or %s/dsynth.ini\n",
165 		       ConfigBase1,
166 		       ConfigBase2);
167 	}
168 
169 	/*
170 	 * Check to see what hooks we have
171 	 */
172 	HookRunStart = checkhook("hook_run_start");
173 	HookRunEnd = checkhook("hook_run_end");
174 	HookPkgSuccess = checkhook("hook_pkg_success");
175 	HookPkgFailure = checkhook("hook_pkg_failure");
176 	HookPkgIgnored = checkhook("hook_pkg_ignored");
177 	HookPkgSkipped = checkhook("hook_pkg_skipped");
178 
179 	/*
180 	 * Parse the configuration file(s).  This may override some of
181 	 * the above defaults.
182 	 */
183 	parseConfigFile(synth_config);
184 	parseProfile(synth_config, ProfileLabel);
185 
186 	/*
187 	 * Figure out whether CCache is configured.  Also set UseUsrSrc
188 	 * if it exists under the system path.
189 	 *
190 	 * Not supported for the moment
191 	 */
192 	if (strcmp(CCachePath, "disabled") != 0) {
193 		/* dfatal("Directory_ccache is not supported, please\n"
194 		       " set to 'disabled'\n"); */
195 		UseCCache = 1;
196 	}
197 	asprintf(&buf, "%s/usr/src/sys/Makefile", SystemPath);
198 	if (stat(buf, &st) == 0)
199 		UseUsrSrc = 1;
200 	free(buf);
201 
202 	/*
203 	 * Default pkg dependency memory target.  This is a heuristical
204 	 * calculation for how much memory we are willing to put towards
205 	 * pkg install dependencies.  The builder count is reduced as needed.
206 	 *
207 	 * Reduce the target even further when CCache is enabled due to
208 	 * its added overhead (even though it doesn't use tmpfs).
209 	 * (NOT CURRENTLY IMPLEMENTED, LEAVE THE SAME)
210 	 */
211 	if (PkgDepMemoryTarget == 0) {
212 		if (UseCCache)
213 			PkgDepMemoryTarget = PhysMem / 3;
214 		else
215 			PkgDepMemoryTarget = PhysMem / 3;
216 	}
217 
218 	/*
219 	 * If this is a dsynth WORKER exec it handles a single slot,
220 	 * just set MaxWorkers to 1.
221 	 */
222 	if (isworker)
223 		MaxWorkers = 1;
224 
225 	/*
226 	 * Final check
227 	 */
228 	if (stat(DPortsPath, &st) < 0)
229 		dfatal("Directory missing: %s", DPortsPath);
230 	if (stat(PackagesPath, &st) < 0)
231 		dfatal("Directory missing: %s", PackagesPath);
232 	if (stat(OptionsPath, &st) < 0)
233 		dfatal("Directory missing: %s", OptionsPath);
234 	if (stat(DistFilesPath, &st) < 0)
235 		dfatal("Directory missing: %s", DistFilesPath);
236 	if (stat(BuildBase, &st) < 0)
237 		dfatal("Directory missing: %s", BuildBase);
238 	if (stat(LogsPath, &st) < 0)
239 		dfatal("Directory missing: %s", LogsPath);
240 	if (stat(SystemPath, &st) < 0)
241 		dfatal("Directory missing: %s", SystemPath);
242 	if (UseCCache && stat(CCachePath, &st) < 0)
243 		dfatal("Directory missing: %s", CCachePath);
244 
245 	/*
246 	 * Now use the SystemPath to retrieve file information from /bin/sh,
247 	 * and use this to set OperatingSystemName, ArchitectureName,
248 	 * MachineName, and ReleaseName.
249 	 *
250 	 * Since this method is used to build for specific releases, require
251 	 * that it succeed.
252 	 */
253 	asprintf(&buf, "%s/bin/sh", SystemPath);
254 	getElfInfo(buf);
255 	free(buf);
256 
257 	/*
258 	 * Calculate VersionName from OperatingSystemName and ReleaseName.
259 	 */
260 	if (strchr(ReleaseName, '-')) {
261 		reln = strchr(ReleaseName, '-') - ReleaseName;
262 		asprintf(&buf, "%s %*.*s-SYNTH",
263 			 OperatingSystemName,
264 			 reln, reln, ReleaseName);
265 		VersionName = buf;
266 		asprintf(&buf, "%*.*s-SYNTH", reln, reln, ReleaseName);
267 		VersionOnlyName = buf;
268 	} else {
269 		asprintf(&buf, "%s %s-SYNTH",
270 			 OperatingSystemName,
271 			 ReleaseName);
272 		asprintf(&buf, "%s-SYNTH", ReleaseName);
273 		VersionOnlyName = buf;
274 	}
275 
276 	/*
277 	 * Get __DragonFly_version from the system header via SystemPath
278 	 */
279 	{
280 		char *ptr;
281 		FILE *fp;
282 
283 		asprintf(&buf, "%s/usr/include/sys/param.h", SystemPath);
284 		fp = fopen(buf, "r");
285 		if (fp == NULL)
286 			dpanic_errno("Cannot open %s", buf);
287 		while ((ptr = fgetln(fp, &len)) != NULL) {
288 			if (len == 0 || ptr[len-1] != '\n')
289 				continue;
290 			ptr[len-1] = 0;
291 			if (strncmp(ptr, "#define __DragonFly_version", 27))
292 				continue;
293 			ptr += 27;
294 			ptr = strtok(ptr, " \t\r\n");
295 			VersionFromParamHeader = strdup(ptr);
296 			break;
297 		}
298 		fclose(fp);
299 
300 		/* Warn the user that World/kernel are out of sync */
301 		if (strcmp(VersionFromSysctl, VersionFromParamHeader)) {
302 			dlog(DLOG_ALL, "Kernel (%s) out of sync with world (%s) on %s\n",
303 			    VersionFromSysctl, VersionFromParamHeader, SystemPath);
304 		}
305 	}
306 
307 	/*
308 	 * If RepositoryPath is under PackagesPath, make sure it
309 	 * is created.
310 	 */
311 	if (strncmp(RepositoryPath, PackagesPath, strlen(PackagesPath)) == 0) {
312 		if (stat(RepositoryPath, &st) < 0) {
313 			if (mkdir(RepositoryPath, 0755) < 0)
314 				dfatal_errno("Cannot mkdir '%s'",
315 					     RepositoryPath);
316 		}
317 	}
318 
319 	if (stat(RepositoryPath, &st) < 0)
320 		dfatal("Directory missing: %s", RepositoryPath);
321 
322 	/*
323 	 * StatsBase, StatsFilePath, StatsLockPath
324 	 */
325 	asprintf(&StatsBase, "%s/stats", LogsPath);
326 	asprintf(&StatsFilePath, "%s/monitor.dat", StatsBase);
327 	asprintf(&StatsLockPath, "%s/monitor.lk", StatsBase);
328 }
329 
330 void
331 DoConfigure(void)
332 {
333 	dfatal("Not Implemented");
334 }
335 
336 static void
337 parseConfigFile(const char *path)
338 {
339 	char buf[1024];
340 	char copy[1024];
341 	FILE *fp;
342 	char *l1;
343 	char *l2;
344 	size_t len;
345 	int mode = -1;
346 	int lineno = 0;
347 
348 	fp = fopen(path, "r");
349 	if (fp == NULL) {
350 		ddprintf(0, "Warning: Config file %s does not exist\n", path);
351 		return;
352 	}
353 	if (DebugOpt >= 2)
354 		ddprintf(0, "ParseConfig %s\n", path);
355 
356 	if (ProfileOverrideOpt) {
357 		Profile = strdup(ProfileOverrideOpt);
358 		asprintf(&l2, "[%s]", Profile);
359 		ProfileLabel = l2;
360 	}
361 
362 	while (fgets(buf, sizeof(buf), fp) != NULL) {
363 		++lineno;
364 		len = strlen(buf);
365 		if (len == 0 || buf[len-1] != '\n')
366 			continue;
367 		buf[--len] = 0;
368 
369 		/*
370 		 * Remove any trailing whitespace, ignore empty lines.
371 		 */
372 		while (len > 0 && isspace(buf[len-1]))
373 			--len;
374 		if (len == 0)
375 			continue;
376 		buf[len] = 0;
377 
378 		/*
379 		 * ignore comments
380 		 */
381 		if (buf[0] == ';' || buf[0] == '#')
382 			continue;
383 		if (buf[0] == '[') {
384 			if (strcmp(buf, "[Global Configuration]") == 0)
385 				mode = 0;	/* parse global config */
386 			else if (strcmp(buf, ProfileLabel) == 0)
387 				mode = 1;	/* use profile */
388 			else
389 				mode = -1;	/* ignore profile */
390 			continue;
391 		}
392 
393 		bcopy(buf, copy, len + 1);
394 
395 		l1 = strtok(copy, "=");
396 		if (l1 == NULL) {
397 			dfatal("Syntax error in config line %d: %s\n",
398 			       lineno, buf);
399 		}
400 		l2 = strtok(NULL, " \t\n");
401 		if (l2 == NULL) {
402 			dfatal("Syntax error in config line %d: %s\n",
403 			       lineno, buf);
404 		}
405 		l1 = stripwhite(l1);
406 		l2 = stripwhite(l2);
407 
408 		switch(mode) {
409 		case 0:
410 			/*
411 			 * Global Configuration
412 			 */
413 			if (strcmp(l1, "profile_selected") == 0) {
414 				if (ProfileOverrideOpt == NULL) {
415 					Profile = strdup(l2);
416 					asprintf(&l2, "[%s]", l2);
417 					ProfileLabel = l2;
418 				}
419 			} else {
420 				dfatal("Unknown directive in config "
421 				       "line %d: %s\n", lineno, buf);
422 			}
423 			break;
424 		case 1:
425 			/*
426 			 * Selected Profile
427 			 */
428 			l2 = strdup(l2);
429 			if (strcmp(l1, "Operating_system") == 0) {
430 				OperatingSystemName = l2;
431 			} else if (strcmp(l1, "Directory_packages") == 0) {
432 				PackagesPath = l2;
433 			} else if (strcmp(l1, "Directory_repository") == 0) {
434 				RepositoryPath = l2;
435 			} else if (strcmp(l1, "Directory_portsdir") == 0) {
436 				DPortsPath = l2;
437 			} else if (strcmp(l1, "Directory_options") == 0) {
438 				OptionsPath = l2;
439 			} else if (strcmp(l1, "Directory_distfiles") == 0) {
440 				DistFilesPath = l2;
441 			} else if (strcmp(l1, "Directory_buildbase") == 0) {
442 				BuildBase = l2;
443 			} else if (strcmp(l1, "Directory_logs") == 0) {
444 				LogsPath = l2;
445 			} else if (strcmp(l1, "Directory_ccache") == 0) {
446 				CCachePath = l2;
447 			} else if (strcmp(l1, "Directory_system") == 0) {
448 				SystemPath = l2;
449 			} else if (strcmp(l1, "Package_suffix") == 0) {
450 				UsePkgSufx = l2;
451 				dassert(strcmp(l2, ".tgz") == 0 ||
452 					strcmp(l2, ".tar") == 0 ||
453 					strcmp(l2, ".txz") == 0 ||
454 					strcmp(l2, ".tzst") == 0 ||
455 					strcmp(l2, ".tbz") == 0,
456 					"Config: Unknown Package_suffix,"
457 					"specify .tgz .tar .txz .tbz or .tzst");
458 			} else if (strcmp(l1, "Number_of_builders") == 0) {
459 				MaxWorkers = strtol(l2, NULL, 0);
460 				if (MaxWorkers == 0)
461 					MaxWorkers = NumCores / 2 + 1;
462 				else
463 				if (MaxWorkers < 0 || MaxWorkers > MAXWORKERS) {
464 					dfatal("Config: Number_of_builders "
465 					       "must range %d..%d",
466 					       1, MAXWORKERS);
467 				}
468 				free(l2);
469 			} else if (strcmp(l1, "Max_jobs_per_builder") == 0) {
470 				MaxJobs = strtol(l2, NULL, 0);
471 				if (MaxJobs == 0) {
472 					MaxJobs = NumCores;
473 				} else
474 				if (MaxJobs < 0 || MaxJobs > MAXJOBS) {
475 					dfatal("Config: Max_jobs_per_builder "
476 					       "must range %d..%d",
477 					       1, MAXJOBS);
478 				}
479 				free(l2);
480 			} else if (strcmp(l1, "Tmpfs_workdir") == 0) {
481 				UseTmpfsWork = truefalse(l2);
482 				dassert(UseTmpfsWork == 1,
483 					"Config: Tmpfs_workdir must be "
484 					"set to true, 'false' not supported");
485 			} else if (strcmp(l1, "Tmpfs_localbase") == 0) {
486 				UseTmpfsBase = truefalse(l2);
487 				dassert(UseTmpfsBase == 1,
488 					"Config: Tmpfs_localbase must be "
489 					"set to true, 'false' not supported");
490 			} else if (strcmp(l1, "Display_with_ncurses") == 0) {
491 				if (UseNCurses == -1)
492 					UseNCurses = truefalse(l2);
493 			} else if (strcmp(l1, "leverage_prebuilt") == 0) {
494 				LeveragePrebuilt = truefalse(l2);
495 				dassert(LeveragePrebuilt == 0,
496 					"Config: leverage_prebuilt not "
497 					"supported and must be set to false");
498 			} else {
499 				dfatal("Unknown directive in profile section "
500 				       "line %d: %s\n", lineno, buf);
501 			}
502 			break;
503 		default:
504 			/*
505 			 * Ignore unselected profile
506 			 */
507 			break;
508 		}
509 	}
510 	fclose(fp);
511 }
512 
513 /*
514  * NOTE: profile has brackets, e.g. "[LiveSystem]".
515  */
516 static void
517 parseProfile(const char *cpath, const char *profile)
518 {
519 	char buf[1024];
520 	char copy[1024];
521 	char *ppath;
522 	FILE *fp;
523 	char *l1;
524 	char *l2;
525 	int len;
526 	int plen;
527 	int lineno = 0;
528 
529 	len = strlen(cpath);
530 	while (len && cpath[len-1] != '/')
531 		--len;
532 	if (len == 0)
533 		++len;
534 	plen = strlen(profile);
535 	ddassert(plen > 2 && profile[0] == '[' && profile[plen-1] == ']');
536 
537 	asprintf(&ppath, "%*.*s%*.*s-make.conf",
538 		 len, len, cpath, plen - 2, plen - 2, profile + 1);
539 	fp = fopen(ppath, "r");
540 	if (fp == NULL) {
541 		ddprintf(0, "Warning: Profile %s does not exist\n", ppath);
542 		return;
543 	}
544 	if (DebugOpt >= 2)
545 		ddprintf(0, "ParseProfile %s\n", ppath);
546 	free(ppath);
547 
548 	while (fgets(buf, sizeof(buf), fp) != NULL) {
549 		++lineno;
550 		len = strlen(buf);
551 		if (len == 0 || buf[len-1] != '\n')
552 			continue;
553 		buf[--len] = 0;
554 
555 		/*
556 		 * Remove any trailing whitespace, ignore empty lines.
557 		 */
558 		while (len > 0 && isspace(buf[len-1]))
559 			--len;
560 		buf[len] = 0;
561 		stripwhite(buf);
562 
563 		/*
564 		 * Allow empty lines, ignore comments.
565 		 */
566 		len = strlen(buf);
567 		if (len == 0)
568 			continue;
569 		if (buf[0] == ';' || buf[0] == '#')
570 			continue;
571 
572 		/*
573 		 * Require env variable name
574 		 */
575 		bcopy(buf, copy, len + 1);
576 		l1 = strtok(copy, "=");
577 		if (l1 == NULL) {
578 			dfatal("Syntax error in profile line %d: %s\n",
579 			       lineno, buf);
580 		}
581 
582 		/*
583 		 * Allow empty assignment
584 		 */
585 		l2 = strtok(NULL, " \t\n");
586 		if (l2 == NULL)
587 			l2 = l1 + strlen(l1);
588 
589 		l1 = stripwhite(l1);
590 		l2 = stripwhite(l2);
591 
592 		/*
593 		 * Add to builder environment
594 		 */
595 		addbuildenv(l1, l2, BENV_MAKECONF);
596 		if (DebugOpt >= 2)
597 			ddprintf(4, "%s=%s\n", l1, l2);
598 	}
599 	fclose(fp);
600 	if (DebugOpt >= 2)
601 		ddprintf(0, "ParseProfile finished\n");
602 }
603 
604 static char *
605 stripwhite(char *str)
606 {
607 	size_t len;
608 
609 	len = strlen(str);
610 	while (len > 0 && isspace(str[len-1]))
611 		--len;
612 	str[len] =0;
613 
614 	while (*str && isspace(*str))
615 		++str;
616 	return str;
617 }
618 
619 static int
620 truefalse(const char *str)
621 {
622 	if (strcmp(str, "0") == 0)
623 		return 0;
624 	if (strcmp(str, "1") == 0)
625 		return 1;
626 	if (strcasecmp(str, "false") == 0)
627 		return 0;
628 	if (strcasecmp(str, "true") == 0)
629 		return 1;
630 	dfatal("syntax error for boolean '%s': "
631 	       "must be '0', '1', 'false', or 'true'", str);
632 	return 0;
633 }
634 
635 static char *
636 dokernsysctl(int m1, int m2)
637 {
638 	int mib[] = { m1, m2 };
639 	char buf[1024];
640 	size_t len;
641 
642 	len = sizeof(buf) - 1;
643 	if (sysctl(mib, 2, buf, &len, NULL, 0) < 0)
644 		dfatal_errno("sysctl for system/architecture");
645 	buf[len] = 0;
646 	return(strdup(buf));
647 }
648 
649 struct NoteTag {
650 	Elf_Note note;
651 	char osname1[12];
652 	int version;		/* e.g. 500702 -> 5.7 */
653 	int x1;
654 	int x2;
655 	int x3;
656 	char osname2[12];
657 	int zero;
658 };
659 
660 static void
661 getElfInfo(const char *path)
662 {
663 	struct NoteTag note;
664 	char *cmd;
665 	char *base;
666 	FILE *fp;
667 	size_t size;
668 	size_t n;
669 	int r;
670 	uint32_t addr;
671 	uint32_t v[4];
672 
673 	asprintf(&cmd, "readelf -x .note.tag %s", path);
674 	fp = popen(cmd, "r");
675 	dassert_errno(fp, "Cannot run: %s", cmd);
676 	n = 0;
677 
678 	while (n != sizeof(note) &&
679 	       (base = fgetln(fp, &size)) != NULL && size) {
680 		base[--size] = 0;
681 		if (strncmp(base, "  0x", 3) != 0)
682 			continue;
683 		r = sscanf(base, "%x %x %x %x %x",
684 			   &addr, &v[0], &v[1], &v[2], &v[3]);
685 		v[0] = ntohl(v[0]);
686 		v[1] = ntohl(v[1]);
687 		v[2] = ntohl(v[2]);
688 		v[3] = ntohl(v[3]);
689 		if (r < 2)
690 			continue;
691 		r = (r - 1) * sizeof(v[0]);
692 		if (n + r > sizeof(note))
693 			r = sizeof(note) - n;
694 		bcopy((char *)v, (char *)&note + n, r);
695 		n += r;
696 	}
697 	pclose(fp);
698 
699 	if (n != sizeof(note))
700 		dfatal("Unable to parse output from: %s", cmd);
701 	if (strncmp(OperatingSystemName, note.osname1, sizeof(note.osname1))) {
702 		dfatal("%s ELF, mismatch OS name %.*s vs %s",
703 		       path, (int)sizeof(note.osname1),
704 		       note.osname1, OperatingSystemName);
705 	}
706 	free(cmd);
707 	if (note.version) {
708 		asprintf(&cmd, "%d.%d",
709 			note.version / 100000,
710 			(note.version % 100000) / 100);
711 	} else if (note.zero) {
712 		asprintf(&cmd, "%d.%d",
713 			note.zero / 100000,
714 			(note.zero % 100000) / 100);
715 	} else {
716 		dfatal("%s ELF, cannot extract version info", path);
717 	}
718 	ReleaseName = cmd;
719 }
720 
721 static char *
722 checkhook(const char *scriptname)
723 {
724 	struct stat st;
725 	char *path;
726 
727 	asprintf(&path, "%s/%s", ConfigBase, scriptname);
728 	if (stat(path, &st) < 0 || (st.st_mode & 0111) == 0) {
729 		free(path);
730 		return NULL;
731 	}
732 	UsingHooks = 1;
733 
734 	return path;
735 }
736