xref: /dflybsd-src/usr.bin/dsynth/config.c (revision d78d3a2272f5ecf9e0b570e362128240417a1b85)
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 UseTmpfs;
42 int NumCores = 1;
43 int MaxBulk = 8;
44 int MaxWorkers = 8;
45 int MaxJobs = 8;
46 int UseTmpfsWork = 1;
47 int UseTmpfsBase = 1;
48 int UseNCurses = -1;		/* indicates default operation (enabled) */
49 int LeveragePrebuilt = 0;
50 int WorkerProcFlags = 0;
51 long PhysMem;
52 const char *OperatingSystemName = "Unknown";
53 const char *ArchitectureName = "unknown";
54 const char *MachineName = "unknown";
55 const char *VersionName = "unknown";
56 const char *ReleaseName = "unknown";
57 const char *DPortsPath = "/usr/dports";
58 const char *CCachePath = "/build/ccache";
59 const char *PackagesPath = "/build/synth/live_packages";
60 const char *RepositoryPath = "/build/synth/live_packages/All";
61 const char *OptionsPath = "/build/synth/options";
62 const char *DistFilesPath = "/build/synth/distfiles";
63 const char *BuildBase = "/build/synth/build";
64 const char *LogsPath = "/build/synth/logs";
65 const char *SystemPath = "/";
66 const char *ProfileLabel = "[LiveSystem]";
67 
68 const char *ConfigBase = "/etc/dsynth";
69 const char *AltConfigBase = "/usr/local/etc/dsynth";
70 
71 static void parseConfigFile(const char *path);
72 static void parseProfile(const char *cpath, const char *path);
73 static char *stripwhite(char *str);
74 static int truefalse(const char *str);
75 static char *dokernsysctl(int m1, int m2);
76 static void getElfInfo(const char *path);
77 
78 void
79 ParseConfiguration(int isworker)
80 {
81 	struct stat st;
82 	size_t len;
83 	int reln;
84 	char *synth_config;
85 	char *buf;
86 
87 	/*
88 	 * Get the default OperatingSystemName, ArchitectureName, and
89 	 * ReleaseName.
90 	 */
91 	OperatingSystemName = dokernsysctl(CTL_KERN, KERN_OSTYPE);
92 	ArchitectureName = dokernsysctl(CTL_HW, HW_MACHINE_ARCH);
93 	MachineName = dokernsysctl(CTL_HW, HW_MACHINE);
94 	ReleaseName = dokernsysctl(CTL_KERN, KERN_OSRELEASE);
95 
96 	/*
97 	 * Retrieve resource information from the system.  Note that
98 	 * NumCores and PhysMem will also be used for dynamic load
99 	 * management.
100 	 */
101 	NumCores = 1;
102 	len = sizeof(NumCores);
103 	if (sysctlbyname("hw.ncpu", &NumCores, &len, NULL, 0) < 0)
104 		dfatal_errno("Cannot get hw.ncpu");
105 
106 	len = sizeof(PhysMem);
107 	if (sysctlbyname("hw.physmem", &PhysMem, &len, NULL, 0) < 0)
108 		dfatal_errno("Cannot get hw.physmem");
109 	if (PkgDepMemoryTarget == 0)
110 		PkgDepMemoryTarget = PhysMem / 2;
111 
112 	/*
113 	 * Calculate nominal defaults.
114 	 */
115 	MaxBulk = NumCores;
116 	MaxWorkers = MaxBulk / 2;
117 	if (MaxWorkers > (int)((PhysMem + (ONEGB/2)) / ONEGB))
118 		MaxWorkers = (PhysMem + (ONEGB/2)) / ONEGB;
119 
120 	if (MaxBulk < 1)
121 		MaxBulk = 1;
122 	if (MaxWorkers < 1)
123 		MaxWorkers = 1;
124 	if (MaxJobs < 1)
125 		MaxJobs = 1;
126 
127 	/*
128 	 * Configuration file must exist.  Look for it in
129 	 * "/etc/dsynth" and "/usr/local/etc/dsynth".
130 	 */
131 	asprintf(&synth_config, "%s/dsynth.ini", ConfigBase);
132 	if (stat(synth_config, &st) < 0)
133 		asprintf(&synth_config, "%s/dsynth.ini", AltConfigBase);
134 
135 	if (stat(synth_config, &st) < 0) {
136 		dfatal("Configuration file missing, "
137 		       "could not find %s/dsynth.ini or %s/dsynth.ini\n",
138 		       ConfigBase,
139 		       AltConfigBase);
140 	}
141 
142 	/*
143 	 * Parse the configuration file(s).  This may override some of
144 	 * the above defaults.
145 	 */
146 	parseConfigFile(synth_config);
147 	parseProfile(synth_config, ProfileLabel);
148 
149 	/*
150 	 * If this is a dsynth WORKER exec it handles a single slot,
151 	 * just set MaxWorkers to 1.
152 	 */
153 	if (isworker)
154 		MaxWorkers = 1;
155 
156 	if (stat(DPortsPath, &st) < 0)
157 		dfatal("Directory missing: %s", DPortsPath);
158 	if (stat(PackagesPath, &st) < 0)
159 		dfatal("Directory missing: %s", PackagesPath);
160 	if (stat(OptionsPath, &st) < 0)
161 		dfatal("Directory missing: %s", OptionsPath);
162 	if (stat(DistFilesPath, &st) < 0)
163 		dfatal("Directory missing: %s", DistFilesPath);
164 	if (stat(BuildBase, &st) < 0)
165 		dfatal("Directory missing: %s", BuildBase);
166 	if (stat(LogsPath, &st) < 0)
167 		dfatal("Directory missing: %s", LogsPath);
168 	if (stat(SystemPath, &st) < 0)
169 		dfatal("Directory missing: %s", SystemPath);
170 
171 	/*
172 	 * Now use the SystemPath to retrieve file information from /bin/sh,
173 	 * and use this to set OperatingSystemName, ArchitectureName,
174 	 * MachineName, and ReleaseName.
175 	 *
176 	 * Since this method is used to build for specific releases, require
177 	 * that it succeed.
178 	 */
179 	asprintf(&buf, "%s/bin/sh", SystemPath);
180 	getElfInfo(buf);
181 	free(buf);
182 
183 	/*
184 	 * Calculate VersionName from OperatingSystemName and ReleaseName.
185 	 */
186 	if (strchr(ReleaseName, '-')) {
187 		reln = strchr(ReleaseName, '-') - ReleaseName;
188 		asprintf(&buf, "%s %*.*s-SYNTH",
189 			 OperatingSystemName,
190 			 reln, reln, ReleaseName);
191 	} else {
192 		asprintf(&buf, "%s %s-SYNTH",
193 			 OperatingSystemName,
194 			 ReleaseName);
195 	}
196 	VersionName = buf;
197 
198 	/*
199 	 * If RepositoryPath is under PackagesPath, make sure it
200 	 * is created.
201 	 */
202 	if (strncmp(RepositoryPath, PackagesPath, strlen(PackagesPath)) == 0) {
203 		if (stat(RepositoryPath, &st) < 0) {
204 			if (mkdir(RepositoryPath, 0755) < 0)
205 				dfatal_errno("Cannot mkdir '%s'",
206 					     RepositoryPath);
207 		}
208 	}
209 
210 	if (stat(RepositoryPath, &st) < 0)
211 		dfatal("Directory missing: %s", RepositoryPath);
212 }
213 
214 void
215 DoConfigure(void)
216 {
217 	dfatal("Not Implemented");
218 }
219 
220 static void
221 parseConfigFile(const char *path)
222 {
223 	char buf[1024];
224 	char copy[1024];
225 	FILE *fp;
226 	char *l1;
227 	char *l2;
228 	size_t len;
229 	int mode = -1;
230 	int lineno = 0;
231 
232 	fp = fopen(path, "r");
233 	if (fp == NULL) {
234 		ddprintf(0, "Warning: Config file %s does not exist\n", path);
235 		return;
236 	}
237 	if (DebugOpt >= 2)
238 		ddprintf(0, "ParseConfig %s\n", path);
239 
240 	while (fgets(buf, sizeof(buf), fp) != NULL) {
241 		++lineno;
242 		len = strlen(buf);
243 		if (len == 0 || buf[len-1] != '\n')
244 			continue;
245 		buf[--len] = 0;
246 
247 		/*
248 		 * Remove any trailing whitespace, ignore empty lines.
249 		 */
250 		while (len > 0 && isspace(buf[len-1]))
251 			--len;
252 		if (len == 0)
253 			continue;
254 		buf[len] = 0;
255 
256 		/*
257 		 * ignore comments
258 		 */
259 		if (buf[0] == ';' || buf[0] == '#')
260 			continue;
261 		if (buf[0] == '[') {
262 			if (strcmp(buf, "[Global Configuration]") == 0)
263 				mode = 0;	/* parse global config */
264 			else if (strcmp(buf, ProfileLabel) == 0)
265 				mode = 1;	/* use profile */
266 			else
267 				mode = -1;	/* ignore profile */
268 			continue;
269 		}
270 
271 		bcopy(buf, copy, len + 1);
272 
273 		l1 = strtok(copy, "=");
274 		if (l1 == NULL) {
275 			dfatal("Syntax error in config line %d: %s\n",
276 			       lineno, buf);
277 		}
278 		l2 = strtok(NULL, " \t\n");
279 		l1 = stripwhite(l1);
280 		l2 = stripwhite(l2);
281 
282 		switch(mode) {
283 		case 0:
284 			/*
285 			 * Global Configuration
286 			 */
287 			if (strcmp(l1, "profile_selected") == 0) {
288 				asprintf(&l2, "[%s]", l2);
289 				ProfileLabel = l2;
290 			} else {
291 				dfatal("Unknown directive in config "
292 				       "line %d: %s\n", lineno, buf);
293 			}
294 			break;
295 		case 1:
296 			/*
297 			 * Selected Profile
298 			 */
299 			l2 = strdup(l2);
300 			if (strcmp(l1, "Operating_system") == 0) {
301 				OperatingSystemName = l2;
302 			} else if (strcmp(l1, "Directory_packages") == 0) {
303 				PackagesPath = l2;
304 			} else if (strcmp(l1, "Directory_repository") == 0) {
305 				RepositoryPath = l2;
306 			} else if (strcmp(l1, "Directory_portsdir") == 0) {
307 				DPortsPath = l2;
308 			} else if (strcmp(l1, "Directory_options") == 0) {
309 				OptionsPath = l2;
310 			} else if (strcmp(l1, "Directory_distfiles") == 0) {
311 				DistFilesPath = l2;
312 			} else if (strcmp(l1, "Directory_buildbase") == 0) {
313 				BuildBase = l2;
314 			} else if (strcmp(l1, "Directory_logs") == 0) {
315 				LogsPath = l2;
316 			} else if (strcmp(l1, "Directory_ccache") == 0) {
317 				CCachePath = l2;
318 			} else if (strcmp(l1, "Directory_system") == 0) {
319 				SystemPath = l2;
320 			} else if (strcmp(l1, "Number_of_builders") == 0) {
321 				MaxWorkers = strtol(l2, NULL, 0);
322 				if (MaxWorkers == 0)
323 					MaxWorkers = NumCores / 2 + 1;
324 				else
325 				if (MaxWorkers < 0 || MaxWorkers > MAXWORKERS) {
326 					dfatal("Config: Number_of_builders "
327 					       "must range %d..%d",
328 					       1, MAXWORKERS);
329 				}
330 				free(l2);
331 			} else if (strcmp(l1, "Max_jobs_per_builder") == 0) {
332 				MaxJobs = strtol(l2, NULL, 0);
333 				if (MaxJobs == 0) {
334 					MaxJobs = NumCores;
335 				} else
336 				if (MaxJobs < 0 || MaxJobs > MAXJOBS) {
337 					dfatal("Config: Max_jobs_per_builder "
338 					       "must range %d..%d",
339 					       1, MAXJOBS);
340 				}
341 				free(l2);
342 			} else if (strcmp(l1, "Tmpfs_workdir") == 0) {
343 				UseTmpfsWork = truefalse(l2);
344 				dassert(UseTmpfsWork == 1,
345 					"Config: Tmpfs_workdir must be "
346 					"set to true, 'false' not supported");
347 			} else if (strcmp(l1, "Tmpfs_localbase") == 0) {
348 				UseTmpfsBase = truefalse(l2);
349 				dassert(UseTmpfsBase == 1,
350 					"Config: Tmpfs_localbase must be "
351 					"set to true, 'false' not supported");
352 			} else if (strcmp(l1, "Display_with_ncurses") == 0) {
353 				if (UseNCurses == -1)
354 					UseNCurses = truefalse(l2);
355 			} else if (strcmp(l1, "leverage_prebuilt") == 0) {
356 				LeveragePrebuilt = truefalse(l2);
357 				dassert(LeveragePrebuilt == 0,
358 					"Config: leverage_prebuilt not "
359 					"supported and must be set to false");
360 			} else {
361 				dfatal("Unknown directive in profile section "
362 				       "line %d: %s\n", lineno, buf);
363 			}
364 			break;
365 		default:
366 			/*
367 			 * Ignore unselected profile
368 			 */
369 			break;
370 		}
371 	}
372 	fclose(fp);
373 }
374 
375 /*
376  * NOTE: profile has brackets, e.g. "[LiveSystem]".
377  */
378 static void
379 parseProfile(const char *cpath, const char *profile)
380 {
381 	char buf[1024];
382 	char copy[1024];
383 	char *ppath;
384 	FILE *fp;
385 	char *l1;
386 	char *l2;
387 	int len;
388 	int plen;
389 	int lineno = 0;
390 
391 	len = strlen(cpath);
392 	while (len && cpath[len-1] != '/')
393 		--len;
394 	if (len == 0)
395 		++len;
396 	plen = strlen(profile);
397 	ddassert(plen > 2 && profile[0] == '[' && profile[plen-1] == ']');
398 
399 	asprintf(&ppath, "%*.*s%*.*s-make.conf",
400 		 len, len, cpath, plen - 2, plen - 2, profile + 1);
401 	fp = fopen(ppath, "r");
402 	if (fp == NULL) {
403 		ddprintf(0, "Warning: Profile %s does not exist\n", ppath);
404 		return;
405 	}
406 	if (DebugOpt >= 2)
407 		ddprintf(0, "ParseProfile %s\n", ppath);
408 	free(ppath);
409 
410 	while (fgets(buf, sizeof(buf), fp) != NULL) {
411 		++lineno;
412 		len = strlen(buf);
413 		if (len == 0 || buf[len-1] != '\n')
414 			continue;
415 		buf[--len] = 0;
416 
417 		/*
418 		 * Remove any trailing whitespace, ignore empty lines.
419 		 */
420 		while (len > 0 && isspace(buf[len-1]))
421 			--len;
422 		buf[len] = 0;
423 		stripwhite(buf);
424 		len = strlen(buf);
425 		if (len == 0)
426 			continue;
427 
428 		/*
429 		 * Ignore comments.
430 		 */
431 		if (buf[0] == ';' || buf[0] == '#')
432 			continue;
433 
434 		bcopy(buf, copy, len + 1);
435 		l1 = strtok(copy, "=");
436 		if (l1 == NULL) {
437 			dfatal("Syntax error in profile line %d: %s\n",
438 			       lineno, buf);
439 		}
440 		l2 = strtok(NULL, " \t\n");
441 		if (l2 == NULL) {
442 			dfatal("Syntax error in profile line %d: %s\n",
443 			       lineno, buf);
444 		}
445 		l1 = stripwhite(l1);
446 		l2 = stripwhite(l2);
447 
448 		/*
449 		 * Add to builder environment
450 		 */
451 		addbuildenv(l1, l2, BENV_MAKECONF);
452 		if (DebugOpt >= 2)
453 			ddprintf(4, "%s=%s\n", l1, l2);
454 	}
455 	fclose(fp);
456 	if (DebugOpt >= 2)
457 		ddprintf(0, "ParseProfile finished\n");
458 }
459 
460 static char *
461 stripwhite(char *str)
462 {
463 	size_t len;
464 
465 	len = strlen(str);
466 	while (len > 0 && isspace(str[len-1]))
467 		--len;
468 	str[len] =0;
469 
470 	while (*str && isspace(*str))
471 		++str;
472 	return str;
473 }
474 
475 static int
476 truefalse(const char *str)
477 {
478 	if (strcmp(str, "0") == 0)
479 		return 0;
480 	if (strcmp(str, "1") == 0)
481 		return 1;
482 	if (strcasecmp(str, "false") == 0)
483 		return 0;
484 	if (strcasecmp(str, "true") == 0)
485 		return 1;
486 	dfatal("syntax error for boolean '%s': "
487 	       "must be '0', '1', 'false', or 'true'", str);
488 	return 0;
489 }
490 
491 static char *
492 dokernsysctl(int m1, int m2)
493 {
494 	int mib[] = { m1, m2 };
495 	char buf[1024];
496 	size_t len;
497 
498 	len = sizeof(buf) - 1;
499 	if (sysctl(mib, 2, buf, &len, NULL, 0) < 0)
500 		dfatal_errno("sysctl for system/architecture");
501 	buf[len] = 0;
502 	return(strdup(buf));
503 }
504 
505 struct NoteTag {
506 	Elf_Note note;
507 	char osname1[12];
508 	int version;		/* e.g. 500702 -> 5.7 */
509 	int x1;
510 	int x2;
511 	int x3;
512 	char osname2[12];
513 	int zero;
514 };
515 
516 static void
517 getElfInfo(const char *path)
518 {
519 	struct NoteTag note;
520 	char *cmd;
521 	char *base;
522 	FILE *fp;
523 	size_t size;
524 	size_t n;
525 	int r;
526 	uint32_t addr;
527 	uint32_t v[4];
528 
529 	asprintf(&cmd, "readelf -x .note.tag %s", path);
530 	fp = popen(cmd, "r");
531 	dassert_errno(fp, "Cannot run: %s", cmd);
532 	n = 0;
533 
534 	while (n != sizeof(note) &&
535 	       (base = fgetln(fp, &size)) != NULL && size) {
536 		base[--size] = 0;
537 		if (strncmp(base, "  0x", 3) != 0)
538 			continue;
539 		r = sscanf(base, "%x %x %x %x %x",
540 			   &addr, &v[0], &v[1], &v[2], &v[3]);
541 		v[0] = ntohl(v[0]);
542 		v[1] = ntohl(v[1]);
543 		v[2] = ntohl(v[2]);
544 		v[3] = ntohl(v[3]);
545 		if (r < 2)
546 			continue;
547 		r = (r - 1) * sizeof(v[0]);
548 		if (n + r > sizeof(note))
549 			r = sizeof(note) - n;
550 		bcopy((char *)v, (char *)&note + n, r);
551 		n += r;
552 	}
553 	pclose(fp);
554 
555 	if (n != sizeof(note))
556 		dfatal("Unable to parse output from: %s", cmd);
557 	if (strncmp(OperatingSystemName, note.osname1, sizeof(note.osname1))) {
558 		dfatal("%s ELF, mismatch OS name %.*s vs %s",
559 		       path, (int)sizeof(note.osname1),
560 		       note.osname1, OperatingSystemName);
561 	}
562 	free(cmd);
563 	asprintf(&cmd, "%d.%d",
564 		note.version / 100000,
565 		(note.version % 100000) / 100);
566 	ReleaseName = cmd;
567 }
568