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