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 while (fgets(buf, sizeof(buf), fp) != NULL) { 333 ++lineno; 334 len = strlen(buf); 335 if (len == 0 || buf[len-1] != '\n') 336 continue; 337 buf[--len] = 0; 338 339 /* 340 * Remove any trailing whitespace, ignore empty lines. 341 */ 342 while (len > 0 && isspace(buf[len-1])) 343 --len; 344 if (len == 0) 345 continue; 346 buf[len] = 0; 347 348 /* 349 * ignore comments 350 */ 351 if (buf[0] == ';' || buf[0] == '#') 352 continue; 353 if (buf[0] == '[') { 354 if (strcmp(buf, "[Global Configuration]") == 0) 355 mode = 0; /* parse global config */ 356 else if (strcmp(buf, ProfileLabel) == 0) 357 mode = 1; /* use profile */ 358 else 359 mode = -1; /* ignore profile */ 360 continue; 361 } 362 363 bcopy(buf, copy, len + 1); 364 365 l1 = strtok(copy, "="); 366 if (l1 == NULL) { 367 dfatal("Syntax error in config line %d: %s\n", 368 lineno, buf); 369 } 370 l2 = strtok(NULL, " \t\n"); 371 if (l2 == NULL) { 372 dfatal("Syntax error in config line %d: %s\n", 373 lineno, buf); 374 } 375 l1 = stripwhite(l1); 376 l2 = stripwhite(l2); 377 378 switch(mode) { 379 case 0: 380 /* 381 * Global Configuration 382 */ 383 if (strcmp(l1, "profile_selected") == 0) { 384 Profile = strdup(l2); 385 asprintf(&l2, "[%s]", l2); 386 ProfileLabel = l2; 387 } else { 388 dfatal("Unknown directive in config " 389 "line %d: %s\n", lineno, buf); 390 } 391 break; 392 case 1: 393 /* 394 * Selected Profile 395 */ 396 l2 = strdup(l2); 397 if (strcmp(l1, "Operating_system") == 0) { 398 OperatingSystemName = l2; 399 } else if (strcmp(l1, "Directory_packages") == 0) { 400 PackagesPath = l2; 401 } else if (strcmp(l1, "Directory_repository") == 0) { 402 RepositoryPath = l2; 403 } else if (strcmp(l1, "Directory_portsdir") == 0) { 404 DPortsPath = l2; 405 } else if (strcmp(l1, "Directory_options") == 0) { 406 OptionsPath = l2; 407 } else if (strcmp(l1, "Directory_distfiles") == 0) { 408 DistFilesPath = l2; 409 } else if (strcmp(l1, "Directory_buildbase") == 0) { 410 BuildBase = l2; 411 } else if (strcmp(l1, "Directory_logs") == 0) { 412 LogsPath = l2; 413 } else if (strcmp(l1, "Directory_ccache") == 0) { 414 CCachePath = l2; 415 } else if (strcmp(l1, "Directory_system") == 0) { 416 SystemPath = l2; 417 } else if (strcmp(l1, "Package_suffix") == 0) { 418 UsePkgSufx = l2; 419 dassert(strcmp(l2, ".tgz") == 0 || 420 strcmp(l2, ".tar") == 0 || 421 strcmp(l2, ".txz") == 0 || 422 strcmp(l2, ".tbz") == 0, 423 "Config: Unknown Package_suffix," 424 "specify .tgz .tar .txz or .tbz"); 425 } else if (strcmp(l1, "Number_of_builders") == 0) { 426 MaxWorkers = strtol(l2, NULL, 0); 427 if (MaxWorkers == 0) 428 MaxWorkers = NumCores / 2 + 1; 429 else 430 if (MaxWorkers < 0 || MaxWorkers > MAXWORKERS) { 431 dfatal("Config: Number_of_builders " 432 "must range %d..%d", 433 1, MAXWORKERS); 434 } 435 free(l2); 436 } else if (strcmp(l1, "Max_jobs_per_builder") == 0) { 437 MaxJobs = strtol(l2, NULL, 0); 438 if (MaxJobs == 0) { 439 MaxJobs = NumCores; 440 } else 441 if (MaxJobs < 0 || MaxJobs > MAXJOBS) { 442 dfatal("Config: Max_jobs_per_builder " 443 "must range %d..%d", 444 1, MAXJOBS); 445 } 446 free(l2); 447 } else if (strcmp(l1, "Tmpfs_workdir") == 0) { 448 UseTmpfsWork = truefalse(l2); 449 dassert(UseTmpfsWork == 1, 450 "Config: Tmpfs_workdir must be " 451 "set to true, 'false' not supported"); 452 } else if (strcmp(l1, "Tmpfs_localbase") == 0) { 453 UseTmpfsBase = truefalse(l2); 454 dassert(UseTmpfsBase == 1, 455 "Config: Tmpfs_localbase must be " 456 "set to true, 'false' not supported"); 457 } else if (strcmp(l1, "Display_with_ncurses") == 0) { 458 if (UseNCurses == -1) 459 UseNCurses = truefalse(l2); 460 } else if (strcmp(l1, "leverage_prebuilt") == 0) { 461 LeveragePrebuilt = truefalse(l2); 462 dassert(LeveragePrebuilt == 0, 463 "Config: leverage_prebuilt not " 464 "supported and must be set to false"); 465 } else { 466 dfatal("Unknown directive in profile section " 467 "line %d: %s\n", lineno, buf); 468 } 469 break; 470 default: 471 /* 472 * Ignore unselected profile 473 */ 474 break; 475 } 476 } 477 fclose(fp); 478 } 479 480 /* 481 * NOTE: profile has brackets, e.g. "[LiveSystem]". 482 */ 483 static void 484 parseProfile(const char *cpath, const char *profile) 485 { 486 char buf[1024]; 487 char copy[1024]; 488 char *ppath; 489 FILE *fp; 490 char *l1; 491 char *l2; 492 int len; 493 int plen; 494 int lineno = 0; 495 496 len = strlen(cpath); 497 while (len && cpath[len-1] != '/') 498 --len; 499 if (len == 0) 500 ++len; 501 plen = strlen(profile); 502 ddassert(plen > 2 && profile[0] == '[' && profile[plen-1] == ']'); 503 504 asprintf(&ppath, "%*.*s%*.*s-make.conf", 505 len, len, cpath, plen - 2, plen - 2, profile + 1); 506 fp = fopen(ppath, "r"); 507 if (fp == NULL) { 508 ddprintf(0, "Warning: Profile %s does not exist\n", ppath); 509 return; 510 } 511 if (DebugOpt >= 2) 512 ddprintf(0, "ParseProfile %s\n", ppath); 513 free(ppath); 514 515 while (fgets(buf, sizeof(buf), fp) != NULL) { 516 ++lineno; 517 len = strlen(buf); 518 if (len == 0 || buf[len-1] != '\n') 519 continue; 520 buf[--len] = 0; 521 522 /* 523 * Remove any trailing whitespace, ignore empty lines. 524 */ 525 while (len > 0 && isspace(buf[len-1])) 526 --len; 527 buf[len] = 0; 528 stripwhite(buf); 529 530 /* 531 * Allow empty lines, ignore comments. 532 */ 533 len = strlen(buf); 534 if (len == 0) 535 continue; 536 if (buf[0] == ';' || buf[0] == '#') 537 continue; 538 539 /* 540 * Require env variable name 541 */ 542 bcopy(buf, copy, len + 1); 543 l1 = strtok(copy, "="); 544 if (l1 == NULL) { 545 dfatal("Syntax error in profile line %d: %s\n", 546 lineno, buf); 547 } 548 549 /* 550 * Allow empty assignment 551 */ 552 l2 = strtok(NULL, " \t\n"); 553 if (l2 == NULL) 554 l2 = l1 + strlen(l1); 555 556 l1 = stripwhite(l1); 557 l2 = stripwhite(l2); 558 559 /* 560 * Add to builder environment 561 */ 562 addbuildenv(l1, l2, BENV_MAKECONF); 563 if (DebugOpt >= 2) 564 ddprintf(4, "%s=%s\n", l1, l2); 565 } 566 fclose(fp); 567 if (DebugOpt >= 2) 568 ddprintf(0, "ParseProfile finished\n"); 569 } 570 571 static char * 572 stripwhite(char *str) 573 { 574 size_t len; 575 576 len = strlen(str); 577 while (len > 0 && isspace(str[len-1])) 578 --len; 579 str[len] =0; 580 581 while (*str && isspace(*str)) 582 ++str; 583 return str; 584 } 585 586 static int 587 truefalse(const char *str) 588 { 589 if (strcmp(str, "0") == 0) 590 return 0; 591 if (strcmp(str, "1") == 0) 592 return 1; 593 if (strcasecmp(str, "false") == 0) 594 return 0; 595 if (strcasecmp(str, "true") == 0) 596 return 1; 597 dfatal("syntax error for boolean '%s': " 598 "must be '0', '1', 'false', or 'true'", str); 599 return 0; 600 } 601 602 static char * 603 dokernsysctl(int m1, int m2) 604 { 605 int mib[] = { m1, m2 }; 606 char buf[1024]; 607 size_t len; 608 609 len = sizeof(buf) - 1; 610 if (sysctl(mib, 2, buf, &len, NULL, 0) < 0) 611 dfatal_errno("sysctl for system/architecture"); 612 buf[len] = 0; 613 return(strdup(buf)); 614 } 615 616 struct NoteTag { 617 Elf_Note note; 618 char osname1[12]; 619 int version; /* e.g. 500702 -> 5.7 */ 620 int x1; 621 int x2; 622 int x3; 623 char osname2[12]; 624 int zero; 625 }; 626 627 static void 628 getElfInfo(const char *path) 629 { 630 struct NoteTag note; 631 char *cmd; 632 char *base; 633 FILE *fp; 634 size_t size; 635 size_t n; 636 int r; 637 uint32_t addr; 638 uint32_t v[4]; 639 640 asprintf(&cmd, "readelf -x .note.tag %s", path); 641 fp = popen(cmd, "r"); 642 dassert_errno(fp, "Cannot run: %s", cmd); 643 n = 0; 644 645 while (n != sizeof(note) && 646 (base = fgetln(fp, &size)) != NULL && size) { 647 base[--size] = 0; 648 if (strncmp(base, " 0x", 3) != 0) 649 continue; 650 r = sscanf(base, "%x %x %x %x %x", 651 &addr, &v[0], &v[1], &v[2], &v[3]); 652 v[0] = ntohl(v[0]); 653 v[1] = ntohl(v[1]); 654 v[2] = ntohl(v[2]); 655 v[3] = ntohl(v[3]); 656 if (r < 2) 657 continue; 658 r = (r - 1) * sizeof(v[0]); 659 if (n + r > sizeof(note)) 660 r = sizeof(note) - n; 661 bcopy((char *)v, (char *)¬e + n, r); 662 n += r; 663 } 664 pclose(fp); 665 666 if (n != sizeof(note)) 667 dfatal("Unable to parse output from: %s", cmd); 668 if (strncmp(OperatingSystemName, note.osname1, sizeof(note.osname1))) { 669 dfatal("%s ELF, mismatch OS name %.*s vs %s", 670 path, (int)sizeof(note.osname1), 671 note.osname1, OperatingSystemName); 672 } 673 free(cmd); 674 asprintf(&cmd, "%d.%d", 675 note.version / 100000, 676 (note.version % 100000) / 100); 677 ReleaseName = cmd; 678 } 679 680 static char * 681 checkhook(const char *scriptname) 682 { 683 struct stat st; 684 char *path; 685 686 asprintf(&path, "%s/%s", ConfigBase, scriptname); 687 if (stat(path, &st) < 0 || (st.st_mode & 0111) == 0) { 688 free(path); 689 return NULL; 690 } 691 UsingHooks = 1; 692 693 return path; 694 } 695