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