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 buildenv_t *BuildEnv; 41 static buildenv_t **BuildEnvTail = &BuildEnv; 42 43 extern char **environ; 44 45 __dead2 void 46 _dfatal(const char *file __unused, int line __unused, const char *func, 47 int do_errno, const char *ctl, ...) 48 { 49 va_list va; 50 51 fprintf(stderr, "%s: ", func); 52 va_start(va, ctl); 53 vfprintf(stderr, ctl, va); 54 va_end(va); 55 if (do_errno & 1) 56 fprintf(stderr, ": %s", strerror(errno)); 57 fprintf(stderr, "\n"); 58 fflush(stderr); 59 60 if (do_errno & 2) 61 kill(getpid(), SIGQUIT); 62 exit(1); 63 } 64 65 void 66 _ddprintf(int tab, const char *ctl, ...) 67 { 68 va_list va; 69 70 if (tab) 71 printf("%*.*s", tab, tab, ""); 72 va_start(va, ctl); 73 vfprintf(stdout, ctl, va); 74 va_end(va); 75 } 76 77 char * 78 strdup_or_null(char *str) 79 { 80 if (str && str[0]) 81 return(strdup(str)); 82 return NULL; 83 } 84 85 static const char *DLogNames[] = { 86 "00_last_results.log", 87 "01_success_list.log", 88 "02_failure_list.log", 89 "03_ignored_list.log", 90 "04_skipped_list.log", 91 "05_abnormal_command_output.log", 92 "06_obsolete_packages.log" 93 }; 94 95 static int DLogFd[DLOG_COUNT]; 96 static pthread_mutex_t DLogFdMutex; 97 98 #define arysize(ary) (sizeof((ary)) / sizeof((ary)[0])) 99 100 static int 101 dlogfd(int which, int modes) 102 { 103 char *path; 104 int fd; 105 106 which &= DLOG_MASK; 107 if ((fd = DLogFd[which]) > 0) 108 return fd; 109 pthread_mutex_lock(&DLogFdMutex); 110 if ((fd = DLogFd[which]) <= 0) { 111 asprintf(&path, "%s/%s", LogsPath, DLogNames[which]); 112 fd = open(path, modes, 0666); 113 DLogFd[which] = fd; 114 free(path); 115 } 116 pthread_mutex_unlock(&DLogFdMutex); 117 118 return fd; 119 } 120 121 122 void 123 dlogreset(void) 124 { 125 int i; 126 127 ddassert(DLOG_COUNT == arysize(DLogNames)); 128 for (i = 0; i < DLOG_COUNT; ++i) { 129 if (DLogFd[i] > 0) { 130 close(DLogFd[i]); 131 DLogFd[i] = -1; 132 } 133 (void)dlogfd(i, O_RDWR|O_CREAT|O_TRUNC|O_APPEND); 134 } 135 } 136 137 void 138 _dlog(int which, const char *ctl, ...) 139 { 140 va_list va; 141 char *buf; 142 char *ptr; 143 size_t len; 144 int fd; 145 int filter; 146 147 filter = which; 148 which &= DLOG_MASK; 149 150 ddassert((uint)which < DLOG_COUNT); 151 va_start(va, ctl); 152 vasprintf(&buf, ctl, va); 153 va_end(va); 154 len = strlen(buf); 155 156 /* 157 * The special sequence ## right-justfies the text after the ##. 158 * 159 * NOTE: Alignment test includes \n so use 80 instead of 79 to 160 * leave one char unused on a 80-column terminal. 161 */ 162 if ((ptr = strstr(buf, "##")) != NULL) { 163 size_t l2; 164 size_t l1; 165 char *b2; 166 int spc; 167 168 l1 = (int)(ptr - buf); 169 l2 = len - l1 - 2; 170 if (l1 <= 80 - l2) { 171 spc = 80 - l1 - l2; 172 buf[l1] = 0; 173 asprintf(&b2, "%s%*.*s%s", 174 buf, spc, spc, "", ptr + 2); 175 } else { 176 buf[l1] = 0; 177 asprintf(&b2, "%s%s", buf, ptr + 2); 178 } 179 len = strlen(b2); 180 free(buf); 181 buf = b2; 182 } 183 184 /* 185 * All logs also go to log 00. 186 */ 187 if (which != DLOG_ALL) { 188 fd = dlogfd(DLOG_ALL, O_RDWR|O_CREAT|O_APPEND); 189 if (fd > 0) 190 write(fd, buf, len); 191 } 192 193 /* 194 * Nominal log target 195 */ 196 fd = dlogfd(which, O_RDWR|O_CREAT|O_APPEND); 197 write(fd, buf, len); 198 199 /* 200 * If ncurses is not being used, all log output also goes 201 * to stdout, unless filtered. 202 */ 203 if ((UseNCurses == 0 || (filter & DLOG_STDOUT)) && 204 (filter & DLOG_FILTER) == 0) { 205 if (ColorOpt) { 206 if (filter & DLOG_GRN) 207 write(1, "\x1b[0;32m", 7); 208 if (filter & DLOG_RED) 209 write(1, "\x1b[0;31m", 7); 210 } 211 write(1, buf, len); 212 if (ColorOpt && (filter & (DLOG_GRN|DLOG_RED))) { 213 write(1, "\x1b[0;39m", 7); 214 } 215 } 216 free(buf); 217 } 218 219 int 220 dlog00_fd(void) 221 { 222 return(dlogfd(DLOG_ALL, O_RDWR|O_CREAT|O_APPEND)); 223 } 224 225 /* 226 * Bulk and Build environment control. These routines are only called 227 * unthreaded or when dsynth threads are idle. 228 */ 229 void 230 addbuildenv(const char *label, const char *data, int type) 231 { 232 buildenv_t *env; 233 234 env = calloc(1, sizeof(*env)); 235 env->a1 = strdup(label); 236 env->a2 = strdup(data); 237 env->label = env->a1; 238 env->data = env->a2; 239 env->type = type; 240 *BuildEnvTail = env; 241 BuildEnvTail = &env->next; 242 } 243 244 void 245 delbuildenv(const char *label) 246 { 247 buildenv_t **envp; 248 buildenv_t *env; 249 250 envp = &BuildEnv; 251 while ((env = *envp) != NULL) { 252 if (strcmp(env->label, label) == 0) { 253 *envp = env->next; 254 if (env->a1) 255 free(env->a1); 256 if (env->a2) 257 free(env->a2); 258 free(env); 259 } else { 260 envp = &env->next; 261 } 262 } 263 BuildEnvTail = envp; 264 } 265 266 void 267 freestrp(char **strp) 268 { 269 if (*strp) { 270 free(*strp); 271 *strp = NULL; 272 } 273 } 274 275 void 276 dupstrp(char **strp) 277 { 278 if (*strp) 279 *strp = strdup(*strp); 280 } 281 282 int 283 ipcreadmsg(int fd, wmsg_t *msg) 284 { 285 size_t res; 286 ssize_t r; 287 char *ptr; 288 289 res = sizeof(*msg); 290 ptr = (char *)(void *)msg; 291 while (res) { 292 r = read(fd, ptr, res); 293 if (r <= 0) { 294 if (errno == EINTR) 295 continue; 296 return -1; 297 } 298 res -= (size_t)r; 299 ptr += r; 300 } 301 return 0; 302 } 303 304 int 305 ipcwritemsg(int fd, wmsg_t *msg) 306 { 307 size_t res; 308 ssize_t r; 309 char *ptr; 310 311 res = sizeof(*msg); 312 ptr = (char *)(void *)msg; 313 while (res) { 314 r = write(fd, ptr, res); 315 if (r < 0) { 316 if (errno == EINTR) 317 continue; 318 return -1; 319 } 320 res -= (size_t)r; 321 ptr += r; 322 } 323 return 0; 324 } 325 326 int 327 askyn(const char *ctl, ...) 328 { 329 va_list va; 330 char buf[256]; 331 int res = 0; 332 333 if (YesOpt) 334 return 1; 335 336 for (;;) { 337 va_start(va, ctl); 338 vprintf(ctl, va); 339 va_end(va); 340 fflush(stdout); 341 if (fgets(buf, sizeof(buf), stdin) == NULL) 342 break; 343 if (buf[0] == 'y' || buf[0] == 'Y') { 344 res = 1; 345 break; 346 } 347 if (buf[0] == 'n' || buf[0] == 'N') { 348 res = 0; 349 break; 350 } 351 printf("Please type y/n\n"); 352 } 353 return res; 354 } 355 356 /* 357 * Get swap% used 0.0-1.0. 358 * 359 * NOTE: This routine is intended to return quickly. 360 * 361 * NOTE: swap_cache (caching for hard drives) is persistent and should 362 * not be counted for our purposes. 363 */ 364 double 365 getswappct(int *noswapp) 366 { 367 long swap_size = 0; 368 long swap_anon = 0; 369 long swap_cache __unused = 0; 370 size_t len; 371 double dswap; 372 373 len = sizeof(swap_size); 374 sysctlbyname("vm.swap_size", &swap_size, &len, NULL, 0); 375 len = sizeof(swap_size); 376 sysctlbyname("vm.swap_anon_use", &swap_anon, &len, NULL, 0); 377 len = sizeof(swap_size); 378 sysctlbyname("vm.swap_cache_use", &swap_cache, &len, NULL, 0); 379 if (swap_size) { 380 dswap = (double)(swap_anon /*+swap_cache*/) / (double)swap_size; 381 *noswapp = 0; 382 } else { 383 dswap = 0.0; 384 *noswapp = 1; 385 } 386 return dswap; 387 } 388 389 /* 390 * dexec_open()/fgets/dexec_close() 391 * 392 * Similar to popen() but directly exec()s the argument list (cav[0] must 393 * be an absolute path). 394 * 395 * If xenv is non-NULL its an array of local buildenv_t's to be used. 396 * The array is terminated with a NULL xenv->label. 397 * 398 * If with_env is non-zero the configured environment is included. 399 * 400 * If with_mvars is non-zero the make environment is passed as VAR=DATA 401 * elements on the command line. 402 */ 403 FILE * 404 dexec_open(const char **cav, int cac, pid_t *pidp, buildenv_t *xenv, 405 int with_env, int with_mvars) 406 { 407 buildenv_t *benv; 408 const char **cenv; 409 char *allocary[MAXCAC*2]; 410 int env_basei; 411 int envi; 412 int alloci; 413 int nullfd; 414 int fds[2]; 415 pid_t pid; 416 FILE *fp; 417 418 env_basei = 0; 419 while (environ[env_basei]) 420 ++env_basei; 421 cenv = calloc(env_basei + MAXCAC, sizeof(char *)); 422 env_basei = 0; 423 for (envi = 0; envi < env_basei; ++envi) 424 cenv[envi] = environ[envi]; 425 426 alloci = 0; 427 for (benv = BuildEnv; benv; benv = benv->next) { 428 if (with_mvars && 429 (benv->type & BENV_CMDMASK) == BENV_MAKECONF) { 430 asprintf(&allocary[alloci], "%s=%s", 431 benv->label, benv->data); 432 cav[cac++] = allocary[alloci]; 433 ++alloci; 434 } 435 if (with_env && 436 (benv->type & BENV_PKGLIST) && 437 (benv->type & BENV_CMDMASK) == BENV_ENVIRONMENT) { 438 asprintf(&allocary[alloci], "%s=%s", 439 benv->label, benv->data); 440 cenv[envi++] = allocary[alloci]; 441 ++alloci; 442 } 443 ddassert(cac < MAXCAC && envi - env_basei < MAXCAC); 444 } 445 446 /* 447 * Extra environment specific to this particular dexec 448 */ 449 while (xenv && xenv->label) { 450 asprintf(&allocary[alloci], "%s=%s", 451 xenv->label, xenv->data); 452 cenv[envi++] = allocary[alloci]; 453 ++alloci; 454 ++xenv; 455 } 456 457 cav[cac] = NULL; 458 cenv[envi] = NULL; 459 460 if (pipe(fds) < 0) 461 dfatal_errno("pipe"); 462 nullfd = open("/dev/null", O_RDWR); 463 if (nullfd < 0) 464 dfatal_errno("open(\"/dev/null\")"); 465 466 /* 467 * We have to be very careful using vfork(), do only the bare 468 * minimum necessary in the child to set it up and exec it. 469 */ 470 pid = vfork(); 471 if (pid == 0) { 472 #if 0 473 int i; 474 printf("%s", cav[0]); 475 for (i = 0; cav[i]; ++i) 476 printf(" %s", cav[i]); 477 printf("\n"); 478 printf("ENV: "); 479 for (i = 0; cenv[i]; ++i) 480 printf(" %s", cenv[i]); 481 #endif 482 483 if (fds[1] != 1) { 484 dup2(fds[1], 1); 485 close(fds[1]); 486 } 487 close(fds[0]); /* safety */ 488 dup2(nullfd, 0); /* no questions! */ 489 closefrom(3); /* be nice */ 490 491 execve(cav[0], (void *)cav, (void *)cenv); 492 write(2, "EXEC FAILURE\n", 13); 493 _exit(1); 494 } 495 close(nullfd); 496 close(fds[1]); 497 if (pid < 0) { 498 close(fds[0]); 499 dfatal_errno("vfork failed"); 500 } 501 fp = fdopen(fds[0], "r"); 502 *pidp = pid; 503 504 while (--alloci >= 0) 505 free(allocary[alloci]); 506 free(cenv); 507 508 return fp; 509 } 510 511 int 512 dexec_close(FILE *fp, pid_t pid) 513 { 514 pid_t rpid; 515 int status; 516 517 fclose(fp); 518 while ((rpid = waitpid(pid, &status, 0)) != pid) { 519 if (rpid < 0) { 520 if (errno == EINTR) 521 continue; 522 return 1; 523 } 524 } 525 return (WEXITSTATUS(status)); 526 } 527 528 const char * 529 getphasestr(worker_phase_t phaseid) 530 { 531 const char *phase; 532 533 switch(phaseid) { 534 case PHASE_PENDING: 535 phase = "pending"; 536 break; 537 case PHASE_INSTALL_PKGS: 538 phase = "install-pkgs"; 539 break; 540 case PHASE_CHECK_SANITY: 541 phase = "check-sanity"; 542 break; 543 case PHASE_PKG_DEPENDS: 544 phase = "pkg-depends"; 545 break; 546 case PHASE_FETCH_DEPENDS: 547 phase = "fetch-depends"; 548 break; 549 case PHASE_FETCH: 550 phase = "fetch"; 551 break; 552 case PHASE_CHECKSUM: 553 phase = "checksum"; 554 break; 555 case PHASE_EXTRACT_DEPENDS: 556 phase = "extract-depends"; 557 break; 558 case PHASE_EXTRACT: 559 phase = "extract"; 560 break; 561 case PHASE_PATCH_DEPENDS: 562 phase = "patch-depends"; 563 break; 564 case PHASE_PATCH: 565 phase = "patch"; 566 break; 567 case PHASE_BUILD_DEPENDS: 568 phase = "build-depends"; 569 break; 570 case PHASE_LIB_DEPENDS: 571 phase = "lib-depends"; 572 break; 573 case PHASE_CONFIGURE: 574 phase = "configure"; 575 break; 576 case PHASE_BUILD: 577 phase = "build"; 578 break; 579 case PHASE_RUN_DEPENDS: 580 phase = "run-depends"; 581 break; 582 case PHASE_STAGE: 583 phase = "stage"; 584 break; 585 case PHASE_TEST: 586 phase = "test"; 587 break; 588 case PHASE_CHECK_PLIST: 589 phase = "check-plist"; 590 break; 591 case PHASE_PACKAGE: 592 phase = "package"; 593 break; 594 case PHASE_INSTALL_MTREE: 595 phase = "install-mtree"; 596 break; 597 case PHASE_INSTALL: 598 phase = "install"; 599 break; 600 case PHASE_DEINSTALL: 601 phase = "deinstall"; 602 break; 603 default: 604 phase = "Run-Unknown"; 605 break; 606 } 607 return phase; 608 } 609 610 int 611 readlogline(monitorlog_t *log, char **bufp) 612 { 613 int r; 614 int n; 615 616 /* 617 * Reset buffer as an optimization to avoid unnecessary 618 * shifts. 619 */ 620 *bufp = NULL; 621 if (log->buf_beg == log->buf_end) { 622 log->buf_beg = 0; 623 log->buf_end = 0; 624 log->buf_scan = 0; 625 } 626 627 /* 628 * Look for newline, handle discard mode 629 */ 630 again: 631 for (n = log->buf_scan; n < log->buf_end; ++n) { 632 if (log->buf[n] == '\n') { 633 *bufp = log->buf + log->buf_beg; 634 r = n - log->buf_beg; 635 log->buf_beg = n + 1; 636 log->buf_scan = n + 1; 637 638 if (log->buf_discard_mode == 0) 639 return r; 640 log->buf_discard_mode = 0; 641 goto again; 642 } 643 } 644 645 /* 646 * Handle overflow 647 */ 648 if (n == sizeof(log->buf)) { 649 if (log->buf_beg) { 650 /* 651 * Shift the buffer to make room and read more data. 652 */ 653 bcopy(log->buf + log->buf_beg, 654 log->buf, 655 n - log->buf_beg); 656 log->buf_end -= log->buf_beg; 657 log->buf_scan -= log->buf_beg; 658 n -= log->buf_beg; 659 log->buf_beg = 0; 660 } else if (log->buf_discard_mode) { 661 /* 662 * Overflow. If in discard mode just throw it all 663 * away. Stay in discard mode. 664 */ 665 log->buf_beg = 0; 666 log->buf_end = 0; 667 log->buf_scan = 0; 668 } else { 669 /* 670 * Overflow. If not in discard mode return a truncated 671 * line and enter discard mode. 672 * 673 * The caller will temporarily set ptr[r] = 0 so make 674 * sure that does not overflow our buffer as we are not 675 * at a newline. 676 * 677 * (log->buf_beg is 0); 678 */ 679 *bufp = log->buf + log->buf_beg; 680 r = n - 1; 681 log->buf_beg = n; 682 log->buf_scan = n; 683 log->buf_discard_mode = 1; 684 685 return r; 686 } 687 } 688 689 /* 690 * Read more data. If there is no data pending then return -1, 691 * otherwise loop up to see if more complete line(s) are available. 692 */ 693 r = pread(log->fd, 694 log->buf + log->buf_end, 695 sizeof(log->buf) - log->buf_end, 696 log->offset); 697 if (r <= 0) 698 return -1; 699 log->offset += r; 700 log->buf_end += r; 701 goto again; 702 } 703