xref: /dragonfly/usr.bin/dsynth/config.c (revision d9d30518)
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 int MetaVersion = 2;
78 
79 /*
80  * Hooks are scripts in ConfigBase
81  */
82 int UsingHooks;
83 const char *HookRunStart;
84 const char *HookRunEnd;
85 const char *HookPkgSuccess;
86 const char *HookPkgFailure;
87 const char *HookPkgIgnored;
88 const char *HookPkgSkipped;
89 
90 const char *ConfigBase;				/* The config base we found */
91 const char *ConfigBase1 = "/etc/dsynth";
92 const char *ConfigBase2 = "/usr/local/etc/dsynth";
93 
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);
101 
102 void
103 ParseConfiguration(int isworker)
104 {
105 	struct stat st;
106 	size_t len;
107 	int reln;
108 	char *synth_config;
109 	char *buf;
110 	char *osreldate;
111 
112 	/*
113 	 * Get the default OperatingSystemName, ArchitectureName, and
114 	 * ReleaseName.
115 	 */
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;
122 
123 	/*
124 	 * Retrieve resource information from the system.  Note that
125 	 * NumCores and PhysMem will also be used for dynamic load
126 	 * management.
127 	 */
128 	NumCores = 1;
129 	len = sizeof(NumCores);
130 	if (sysctlbyname("hw.ncpu", &NumCores, &len, NULL, 0) < 0)
131 		dfatal_errno("Cannot get hw.ncpu");
132 
133 	len = sizeof(PhysMem);
134 	if (sysctlbyname("hw.physmem", &PhysMem, &len, NULL, 0) < 0)
135 		dfatal_errno("Cannot get hw.physmem");
136 
137 	/*
138 	 * Calculate nominal defaults.
139 	 */
140 	MaxBulk = NumCores;
141 	MaxWorkers = MaxBulk / 2;
142 	if (MaxWorkers > (int)((PhysMem + (ONEGB/2)) / ONEGB))
143 		MaxWorkers = (PhysMem + (ONEGB/2)) / ONEGB;
144 
145 	if (MaxBulk < 1)
146 		MaxBulk = 1;
147 	if (MaxWorkers < 1)
148 		MaxWorkers = 1;
149 	if (MaxJobs < 1)
150 		MaxJobs = 1;
151 
152 	/*
153 	 * Configuration file must exist.  Look for it in
154 	 * "/etc/dsynth" and "/usr/local/etc/dsynth".
155 	 */
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);
161 	}
162 
163 	if (stat(synth_config, &st) < 0) {
164 		dfatal("Configuration file missing, "
165 		       "could not find %s/dsynth.ini or %s/dsynth.ini\n",
166 		       ConfigBase1,
167 		       ConfigBase2);
168 	}
169 
170 	/*
171 	 * Check to see what hooks we have
172 	 */
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");
179 
180 	/*
181 	 * Parse the configuration file(s).  This may override some of
182 	 * the above defaults.
183 	 */
184 	parseConfigFile(synth_config);
185 	parseProfile(synth_config, ProfileLabel);
186 
187 	/*
188 	 * Figure out whether CCache is configured.  Also set UseUsrSrc
189 	 * if it exists under the system path.
190 	 *
191 	 * Not supported for the moment
192 	 */
193 	if (strcmp(CCachePath, "disabled") != 0) {
194 		/* dfatal("Directory_ccache is not supported, please\n"
195 		       " set to 'disabled'\n"); */
196 		UseCCache = 1;
197 	}
198 	asprintf(&buf, "%s/usr/src/sys/Makefile", SystemPath);
199 	if (stat(buf, &st) == 0)
200 		UseUsrSrc = 1;
201 	free(buf);
202 
203 	/*
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.
207 	 *
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)
211 	 */
212 	if (PkgDepMemoryTarget == 0) {
213 		if (UseCCache)
214 			PkgDepMemoryTarget = PhysMem / 3;
215 		else
216 			PkgDepMemoryTarget = PhysMem / 3;
217 	}
218 
219 	/*
220 	 * If this is a dsynth WORKER exec it handles a single slot,
221 	 * just set MaxWorkers to 1.
222 	 */
223 	if (isworker)
224 		MaxWorkers = 1;
225 
226 	/*
227 	 * Final check
228 	 */
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);
245 
246 	/*
247 	 * Now use the SystemPath to retrieve file information from /bin/sh,
248 	 * and use this to set OperatingSystemName, ArchitectureName,
249 	 * MachineName, and ReleaseName.
250 	 *
251 	 * Since this method is used to build for specific releases, require
252 	 * that it succeed.
253 	 */
254 	asprintf(&buf, "%s/bin/sh", SystemPath);
255 	getElfInfo(buf);
256 	free(buf);
257 
258 	/*
259 	 * Calculate VersionName from OperatingSystemName and ReleaseName.
260 	 */
261 	if (strchr(ReleaseName, '-')) {
262 		reln = strchr(ReleaseName, '-') - ReleaseName;
263 		asprintf(&buf, "%s %*.*s-SYNTH",
264 			 OperatingSystemName,
265 			 reln, reln, ReleaseName);
266 		VersionName = buf;
267 		asprintf(&buf, "%*.*s-SYNTH", reln, reln, ReleaseName);
268 		VersionOnlyName = buf;
269 	} else {
270 		asprintf(&buf, "%s %s-SYNTH",
271 			 OperatingSystemName,
272 			 ReleaseName);
273 		asprintf(&buf, "%s-SYNTH", ReleaseName);
274 		VersionOnlyName = buf;
275 	}
276 
277 	/*
278 	 * Get __DragonFly_version from the system header via SystemPath
279 	 */
280 	{
281 		char *ptr;
282 		FILE *fp;
283 
284 		asprintf(&buf, "%s/usr/include/sys/param.h", SystemPath);
285 		fp = fopen(buf, "r");
286 		if (fp == NULL)
287 			dpanic_errno("Cannot open %s", buf);
288 		while ((ptr = fgetln(fp, &len)) != NULL) {
289 			if (len == 0 || ptr[len-1] != '\n')
290 				continue;
291 			ptr[len-1] = 0;
292 			if (strncmp(ptr, "#define __DragonFly_version", 27))
293 				continue;
294 			ptr += 27;
295 			ptr = strtok(ptr, " \t\r\n");
296 			VersionFromParamHeader = strdup(ptr);
297 			break;
298 		}
299 		fclose(fp);
300 
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);
305 		}
306 	}
307 
308 	/*
309 	 * If RepositoryPath is under PackagesPath, make sure it
310 	 * is created.
311 	 */
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'",
316 					     RepositoryPath);
317 		}
318 	}
319 
320 	if (stat(RepositoryPath, &st) < 0)
321 		dfatal("Directory missing: %s", RepositoryPath);
322 
323 	/*
324 	 * StatsBase, StatsFilePath, StatsLockPath
325 	 */
326 	asprintf(&StatsBase, "%s/stats", LogsPath);
327 	asprintf(&StatsFilePath, "%s/monitor.dat", StatsBase);
328 	asprintf(&StatsLockPath, "%s/monitor.lk", StatsBase);
329 }
330 
331 void
332 DoConfigure(void)
333 {
334 	dfatal("Not Implemented");
335 }
336 
337 static void
338 parseConfigFile(const char *path)
339 {
340 	char buf[1024];
341 	char copy[1024];
342 	FILE *fp;
343 	char *l1;
344 	char *l2;
345 	size_t len;
346 	int mode = -1;
347 	int lineno = 0;
348 
349 	fp = fopen(path, "r");
350 	if (fp == NULL) {
351 		ddprintf(0, "Warning: Config file %s does not exist\n", path);
352 		return;
353 	}
354 	if (DebugOpt >= 2)
355 		ddprintf(0, "ParseConfig %s\n", path);
356 
357 	if (ProfileOverrideOpt) {
358 		Profile = strdup(ProfileOverrideOpt);
359 		asprintf(&l2, "[%s]", Profile);
360 		ProfileLabel = l2;
361 	}
362 
363 	while (fgets(buf, sizeof(buf), fp) != NULL) {
364 		++lineno;
365 		len = strlen(buf);
366 		if (len == 0 || buf[len-1] != '\n')
367 			continue;
368 		buf[--len] = 0;
369 
370 		/*
371 		 * Remove any trailing whitespace, ignore empty lines.
372 		 */
373 		while (len > 0 && isspace(buf[len-1]))
374 			--len;
375 		if (len == 0)
376 			continue;
377 		buf[len] = 0;
378 
379 		/*
380 		 * ignore comments
381 		 */
382 		if (buf[0] == ';' || buf[0] == '#')
383 			continue;
384 		if (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 */
389 			else
390 				mode = -1;	/* ignore profile */
391 			continue;
392 		}
393 
394 		bcopy(buf, copy, len + 1);
395 
396 		l1 = strtok(copy, "=");
397 		if (l1 == NULL) {
398 			dfatal("Syntax error in config line %d: %s\n",
399 			       lineno, buf);
400 		}
401 		l2 = strtok(NULL, " \t\n");
402 		if (l2 == NULL) {
403 			dfatal("Syntax error in config line %d: %s\n",
404 			       lineno, buf);
405 		}
406 		l1 = stripwhite(l1);
407 		l2 = stripwhite(l2);
408 
409 		switch(mode) {
410 		case 0:
411 			/*
412 			 * Global Configuration
413 			 */
414 			if (strcmp(l1, "profile_selected") == 0) {
415 				if (ProfileOverrideOpt == NULL) {
416 					Profile = strdup(l2);
417 					asprintf(&l2, "[%s]", l2);
418 					ProfileLabel = l2;
419 				}
420 			} else {
421 				dfatal("Unknown directive in config "
422 				       "line %d: %s\n", lineno, buf);
423 			}
424 			break;
425 		case 1:
426 			/*
427 			 * Selected Profile
428 			 */
429 			l2 = strdup(l2);
430 			if (strcmp(l1, "Operating_system") == 0) {
431 				OperatingSystemName = l2;
432 			} else if (strcmp(l1, "Directory_packages") == 0) {
433 				PackagesPath = l2;
434 			} else if (strcmp(l1, "Directory_repository") == 0) {
435 				RepositoryPath = l2;
436 			} else if (strcmp(l1, "Directory_portsdir") == 0) {
437 				DPortsPath = l2;
438 			} else if (strcmp(l1, "Directory_options") == 0) {
439 				OptionsPath = l2;
440 			} else if (strcmp(l1, "Directory_distfiles") == 0) {
441 				DistFilesPath = l2;
442 			} else if (strcmp(l1, "Directory_buildbase") == 0) {
443 				BuildBase = l2;
444 			} else if (strcmp(l1, "Directory_logs") == 0) {
445 				LogsPath = l2;
446 			} else if (strcmp(l1, "Directory_ccache") == 0) {
447 				CCachePath = l2;
448 			} else if (strcmp(l1, "Directory_system") == 0) {
449 				SystemPath = l2;
450 			} else if (strcmp(l1, "Package_suffix") == 0) {
451 				UsePkgSufx = l2;
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);
463 				if (MaxWorkers == 0)
464 					MaxWorkers = NumCores / 2 + 1;
465 				else
466 				if (MaxWorkers < 0 || MaxWorkers > MAXWORKERS) {
467 					dfatal("Config: Number_of_builders "
468 					       "must range %d..%d",
469 					       1, MAXWORKERS);
470 				}
471 				free(l2);
472 			} else if (strcmp(l1, "Max_jobs_per_builder") == 0) {
473 				MaxJobs = strtol(l2, NULL, 0);
474 				if (MaxJobs == 0) {
475 					MaxJobs = NumCores;
476 				} else
477 				if (MaxJobs < 0 || MaxJobs > MAXJOBS) {
478 					dfatal("Config: Max_jobs_per_builder "
479 					       "must range %d..%d",
480 					       1, MAXJOBS);
481 				}
482 				free(l2);
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) {
502 				if (truefalse(l2)) {
503 					WorkerProcFlags |=
504 						WORKER_PROC_CHECK_PLIST;
505 				}
506 			} else {
507 				dfatal("Unknown directive in profile section "
508 				       "line %d: %s\n", lineno, buf);
509 			}
510 			break;
511 		default:
512 			/*
513 			 * Ignore unselected profile
514 			 */
515 			break;
516 		}
517 	}
518 	fclose(fp);
519 }
520 
521 /*
522  * NOTE: profile has brackets, e.g. "[LiveSystem]".
523  */
524 static void
525 parseProfile(const char *cpath, const char *profile)
526 {
527 	char buf[1024];
528 	char copy[1024];
529 	char *ppath;
530 	FILE *fp;
531 	char *l1;
532 	char *l2;
533 	int len;
534 	int plen;
535 	int lineno = 0;
536 
537 	len = strlen(cpath);
538 	while (len && cpath[len-1] != '/')
539 		--len;
540 	if (len == 0)
541 		++len;
542 	plen = strlen(profile);
543 	ddassert(plen > 2 && profile[0] == '[' && profile[plen-1] == ']');
544 
545 	asprintf(&ppath, "%*.*s%*.*s-make.conf",
546 		 len, len, cpath, plen - 2, plen - 2, profile + 1);
547 	fp = fopen(ppath, "r");
548 	if (fp == NULL) {
549 		ddprintf(0, "Warning: Profile %s does not exist\n", ppath);
550 		return;
551 	}
552 	if (DebugOpt >= 2)
553 		ddprintf(0, "ParseProfile %s\n", ppath);
554 	free(ppath);
555 
556 	while (fgets(buf, sizeof(buf), fp) != NULL) {
557 		++lineno;
558 		len = strlen(buf);
559 		if (len == 0 || buf[len-1] != '\n')
560 			continue;
561 		buf[--len] = 0;
562 
563 		/*
564 		 * Remove any trailing whitespace, ignore empty lines.
565 		 */
566 		while (len > 0 && isspace(buf[len-1]))
567 			--len;
568 		buf[len] = 0;
569 		stripwhite(buf);
570 
571 		/*
572 		 * Allow empty lines, ignore comments.
573 		 */
574 		len = strlen(buf);
575 		if (len == 0)
576 			continue;
577 		if (buf[0] == ';' || buf[0] == '#')
578 			continue;
579 
580 		/*
581 		 * Require env variable name
582 		 */
583 		bcopy(buf, copy, len + 1);
584 		l1 = strtok(copy, "=");
585 		if (l1 == NULL) {
586 			dfatal("Syntax error in profile line %d: %s\n",
587 			       lineno, buf);
588 		}
589 
590 		/*
591 		 * Allow empty assignment
592 		 */
593 		l2 = strtok(NULL, " \t\n");
594 		if (l2 == NULL)
595 			l2 = l1 + strlen(l1);
596 
597 		l1 = stripwhite(l1);
598 		l2 = stripwhite(l2);
599 
600 		/*
601 		 * Add to builder environment
602 		 */
603 		addbuildenv(l1, l2, BENV_MAKECONF);
604 		if (DebugOpt >= 2)
605 			ddprintf(4, "%s=%s\n", l1, l2);
606 	}
607 	fclose(fp);
608 	if (DebugOpt >= 2)
609 		ddprintf(0, "ParseProfile finished\n");
610 }
611 
612 static char *
613 stripwhite(char *str)
614 {
615 	size_t len;
616 
617 	len = strlen(str);
618 	while (len > 0 && isspace(str[len-1]))
619 		--len;
620 	str[len] =0;
621 
622 	while (*str && isspace(*str))
623 		++str;
624 	return str;
625 }
626 
627 static int
628 truefalse(const char *str)
629 {
630 	if (strcmp(str, "0") == 0)
631 		return 0;
632 	if (strcmp(str, "1") == 0)
633 		return 1;
634 	if (strcasecmp(str, "false") == 0)
635 		return 0;
636 	if (strcasecmp(str, "true") == 0)
637 		return 1;
638 	dfatal("syntax error for boolean '%s': "
639 	       "must be '0', '1', 'false', or 'true'", str);
640 	return 0;
641 }
642 
643 static char *
644 dokernsysctl(int m1, int m2)
645 {
646 	int mib[] = { m1, m2 };
647 	char buf[1024];
648 	size_t len;
649 
650 	len = sizeof(buf) - 1;
651 	if (sysctl(mib, 2, buf, &len, NULL, 0) < 0)
652 		dfatal_errno("sysctl for system/architecture");
653 	buf[len] = 0;
654 	return(strdup(buf));
655 }
656 
657 struct NoteTag {
658 	Elf_Note note;
659 	char osname1[12];
660 	int version;		/* e.g. 500702 -> 5.7 */
661 	int x1;
662 	int x2;
663 	int x3;
664 	char osname2[12];
665 	int zero;
666 };
667 
668 static void
669 getElfInfo(const char *path)
670 {
671 	struct NoteTag note;
672 	char *cmd;
673 	char *base;
674 	FILE *fp;
675 	size_t size;
676 	size_t n;
677 	int r;
678 	uint32_t addr;
679 	uint32_t v[4];
680 
681 	asprintf(&cmd, "readelf -x .note.tag %s", path);
682 	fp = popen(cmd, "r");
683 	dassert_errno(fp, "Cannot run: %s", cmd);
684 	n = 0;
685 
686 	while (n != sizeof(note) &&
687 	       (base = fgetln(fp, &size)) != NULL && size) {
688 		base[--size] = 0;
689 		if (strncmp(base, "  0x", 3) != 0)
690 			continue;
691 		r = sscanf(base, "%x %x %x %x %x",
692 			   &addr, &v[0], &v[1], &v[2], &v[3]);
693 		v[0] = ntohl(v[0]);
694 		v[1] = ntohl(v[1]);
695 		v[2] = ntohl(v[2]);
696 		v[3] = ntohl(v[3]);
697 		if (r < 2)
698 			continue;
699 		r = (r - 1) * sizeof(v[0]);
700 		if (n + r > sizeof(note))
701 			r = sizeof(note) - n;
702 		bcopy((char *)v, (char *)&note + n, r);
703 		n += r;
704 	}
705 	pclose(fp);
706 
707 	if (n != sizeof(note))
708 		dfatal("Unable to parse output from: %s", cmd);
709 	if (strncmp(OperatingSystemName, note.osname1, sizeof(note.osname1))) {
710 		dfatal("%s ELF, mismatch OS name %.*s vs %s",
711 		       path, (int)sizeof(note.osname1),
712 		       note.osname1, OperatingSystemName);
713 	}
714 	free(cmd);
715 	if (note.version) {
716 		asprintf(&cmd, "%d.%d",
717 			note.version / 100000,
718 			(note.version % 100000) / 100);
719 	} else if (note.zero) {
720 		asprintf(&cmd, "%d.%d",
721 			note.zero / 100000,
722 			(note.zero % 100000) / 100);
723 	} else {
724 		dfatal("%s ELF, cannot extract version info", path);
725 	}
726 	ReleaseName = cmd;
727 }
728 
729 static char *
730 checkhook(const char *scriptname)
731 {
732 	struct stat st;
733 	char *path;
734 
735 	asprintf(&path, "%s/%s", ConfigBase, scriptname);
736 	if (stat(path, &st) < 0 || (st.st_mode & 0111) == 0) {
737 		free(path);
738 		return NULL;
739 	}
740 	UsingHooks = 1;
741 
742 	return path;
743 }
744