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 *)¬e + 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