1 /* $NetBSD: meta.c,v 1.54 2016/03/11 07:01:21 sjg Exp $ */ 2 3 /* 4 * Implement 'meta' mode. 5 * Adapted from John Birrell's patches to FreeBSD make. 6 * --sjg 7 */ 8 /* 9 * Copyright (c) 2009-2016, Juniper Networks, Inc. 10 * Portions Copyright (c) 2009, John Birrell. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 #if defined(USE_META) 34 35 #ifdef HAVE_CONFIG_H 36 # include "config.h" 37 #endif 38 #include <sys/stat.h> 39 #include <sys/ioctl.h> 40 #ifdef HAVE_LIBGEN_H 41 #include <libgen.h> 42 #elif !defined(HAVE_DIRNAME) 43 char * dirname(char *); 44 #endif 45 #include <errno.h> 46 #if !defined(HAVE_CONFIG_H) || defined(HAVE_ERR_H) 47 #include <err.h> 48 #endif 49 50 #include "make.h" 51 #include "job.h" 52 53 #ifdef HAVE_FILEMON_H 54 # include <filemon.h> 55 #endif 56 #if !defined(USE_FILEMON) && defined(FILEMON_SET_FD) 57 # define USE_FILEMON 58 #endif 59 60 static BuildMon Mybm; /* for compat */ 61 static Lst metaBailiwick; /* our scope of control */ 62 static char *metaBailiwickStr; /* string storage for the list */ 63 static Lst metaIgnorePaths; /* paths we deliberately ignore */ 64 static char *metaIgnorePathsStr; /* string storage for the list */ 65 66 #ifndef MAKE_META_IGNORE_PATHS 67 #define MAKE_META_IGNORE_PATHS ".MAKE.META.IGNORE_PATHS" 68 #endif 69 70 Boolean useMeta = FALSE; 71 static Boolean useFilemon = FALSE; 72 static Boolean writeMeta = FALSE; 73 static Boolean metaEnv = FALSE; /* don't save env unless asked */ 74 static Boolean metaVerbose = FALSE; 75 static Boolean metaIgnoreCMDs = FALSE; /* ignore CMDs in .meta files */ 76 static Boolean metaCurdirOk = FALSE; /* write .meta in .CURDIR Ok? */ 77 static Boolean metaSilent = FALSE; /* if we have a .meta be SILENT */ 78 79 extern Boolean forceJobs; 80 extern Boolean comatMake; 81 extern char **environ; 82 83 #define MAKE_META_PREFIX ".MAKE.META.PREFIX" 84 85 #ifndef N2U 86 # define N2U(n, u) (((n) + ((u) - 1)) / (u)) 87 #endif 88 #ifndef ROUNDUP 89 # define ROUNDUP(n, u) (N2U((n), (u)) * (u)) 90 #endif 91 92 #if !defined(HAVE_STRSEP) 93 # define strsep(s, d) stresep((s), (d), 0) 94 #endif 95 96 /* 97 * Filemon is a kernel module which snoops certain syscalls. 98 * 99 * C chdir 100 * E exec 101 * F [v]fork 102 * L [sym]link 103 * M rename 104 * R read 105 * W write 106 * S stat 107 * 108 * See meta_oodate below - we mainly care about 'E' and 'R'. 109 * 110 * We can still use meta mode without filemon, but 111 * the benefits are more limited. 112 */ 113 #ifdef USE_FILEMON 114 # ifndef _PATH_FILEMON 115 # define _PATH_FILEMON "/dev/filemon" 116 # endif 117 118 /* 119 * Open the filemon device. 120 */ 121 static void 122 filemon_open(BuildMon *pbm) 123 { 124 int retry; 125 126 pbm->mon_fd = pbm->filemon_fd = -1; 127 if (!useFilemon) 128 return; 129 130 for (retry = 5; retry >= 0; retry--) { 131 if ((pbm->filemon_fd = open(_PATH_FILEMON, O_RDWR)) >= 0) 132 break; 133 } 134 135 if (pbm->filemon_fd < 0) { 136 useFilemon = FALSE; 137 warn("Could not open %s", _PATH_FILEMON); 138 return; 139 } 140 141 /* 142 * We use a file outside of '.' 143 * to avoid a FreeBSD kernel bug where unlink invalidates 144 * cwd causing getcwd to do a lot more work. 145 * We only care about the descriptor. 146 */ 147 pbm->mon_fd = mkTempFile("filemon.XXXXXX", NULL); 148 if (ioctl(pbm->filemon_fd, FILEMON_SET_FD, &pbm->mon_fd) < 0) { 149 err(1, "Could not set filemon file descriptor!"); 150 } 151 /* we don't need these once we exec */ 152 (void)fcntl(pbm->mon_fd, F_SETFD, FD_CLOEXEC); 153 (void)fcntl(pbm->filemon_fd, F_SETFD, FD_CLOEXEC); 154 } 155 156 /* 157 * Read the build monitor output file and write records to the target's 158 * metadata file. 159 */ 160 static void 161 filemon_read(FILE *mfp, int fd) 162 { 163 char buf[BUFSIZ]; 164 int n; 165 166 /* Check if we're not writing to a meta data file.*/ 167 if (mfp == NULL) { 168 if (fd >= 0) 169 close(fd); /* not interested */ 170 return; 171 } 172 /* rewind */ 173 (void)lseek(fd, (off_t)0, SEEK_SET); 174 175 fprintf(mfp, "\n-- filemon acquired metadata --\n"); 176 177 while ((n = read(fd, buf, sizeof(buf))) > 0) { 178 fwrite(buf, 1, n, mfp); 179 } 180 fflush(mfp); 181 close(fd); 182 } 183 #endif 184 185 /* 186 * when realpath() fails, 187 * we use this, to clean up ./ and ../ 188 */ 189 static void 190 eat_dots(char *buf, size_t bufsz, int dots) 191 { 192 char *cp; 193 char *cp2; 194 const char *eat; 195 size_t eatlen; 196 197 switch (dots) { 198 case 1: 199 eat = "/./"; 200 eatlen = 2; 201 break; 202 case 2: 203 eat = "/../"; 204 eatlen = 3; 205 break; 206 default: 207 return; 208 } 209 210 do { 211 cp = strstr(buf, eat); 212 if (cp) { 213 cp2 = cp + eatlen; 214 if (dots == 2 && cp > buf) { 215 do { 216 cp--; 217 } while (cp > buf && *cp != '/'); 218 } 219 if (*cp == '/') { 220 strlcpy(cp, cp2, bufsz - (cp - buf)); 221 } else { 222 return; /* can't happen? */ 223 } 224 } 225 } while (cp); 226 } 227 228 static char * 229 meta_name(struct GNode *gn, char *mname, size_t mnamelen, 230 const char *dname, 231 const char *tname) 232 { 233 char buf[MAXPATHLEN]; 234 char cwd[MAXPATHLEN]; 235 char *rp; 236 char *cp; 237 char *tp; 238 char *p[4]; /* >= number of possible uses */ 239 int i; 240 241 i = 0; 242 if (!dname) 243 dname = Var_Value(".OBJDIR", gn, &p[i++]); 244 if (!tname) 245 tname = Var_Value(TARGET, gn, &p[i++]); 246 247 if (realpath(dname, cwd)) 248 dname = cwd; 249 250 /* 251 * Weed out relative paths from the target file name. 252 * We have to be careful though since if target is a 253 * symlink, the result will be unstable. 254 * So we use realpath() just to get the dirname, and leave the 255 * basename as given to us. 256 */ 257 if ((cp = strrchr(tname, '/'))) { 258 if (realpath(tname, buf)) { 259 if ((rp = strrchr(buf, '/'))) { 260 rp++; 261 cp++; 262 if (strcmp(cp, rp) != 0) 263 strlcpy(rp, cp, sizeof(buf) - (rp - buf)); 264 } 265 tname = buf; 266 } else { 267 /* 268 * We likely have a directory which is about to be made. 269 * We pretend realpath() succeeded, to have a chance 270 * of generating the same meta file name that we will 271 * next time through. 272 */ 273 if (tname[0] == '/') { 274 strlcpy(buf, tname, sizeof(buf)); 275 } else { 276 snprintf(buf, sizeof(buf), "%s/%s", cwd, tname); 277 } 278 eat_dots(buf, sizeof(buf), 1); /* ./ */ 279 eat_dots(buf, sizeof(buf), 2); /* ../ */ 280 tname = buf; 281 } 282 } 283 /* on some systems dirname may modify its arg */ 284 tp = bmake_strdup(tname); 285 if (strcmp(dname, dirname(tp)) == 0) 286 snprintf(mname, mnamelen, "%s.meta", tname); 287 else { 288 snprintf(mname, mnamelen, "%s/%s.meta", dname, tname); 289 290 /* 291 * Replace path separators in the file name after the 292 * current object directory path. 293 */ 294 cp = mname + strlen(dname) + 1; 295 296 while (*cp != '\0') { 297 if (*cp == '/') 298 *cp = '_'; 299 cp++; 300 } 301 } 302 free(tp); 303 for (i--; i >= 0; i--) { 304 free(p[i]); 305 } 306 return (mname); 307 } 308 309 /* 310 * Return true if running ${.MAKE} 311 * Bypassed if target is flagged .MAKE 312 */ 313 static int 314 is_submake(void *cmdp, void *gnp) 315 { 316 static char *p_make = NULL; 317 static int p_len; 318 char *cmd = cmdp; 319 GNode *gn = gnp; 320 char *mp = NULL; 321 char *cp; 322 char *cp2; 323 int rc = 0; /* keep looking */ 324 325 if (!p_make) { 326 p_make = Var_Value(".MAKE", gn, &cp); 327 p_len = strlen(p_make); 328 } 329 cp = strchr(cmd, '$'); 330 if ((cp)) { 331 mp = Var_Subst(NULL, cmd, gn, VARF_WANTRES); 332 cmd = mp; 333 } 334 cp2 = strstr(cmd, p_make); 335 if ((cp2)) { 336 switch (cp2[p_len]) { 337 case '\0': 338 case ' ': 339 case '\t': 340 case '\n': 341 rc = 1; 342 break; 343 } 344 if (cp2 > cmd && rc > 0) { 345 switch (cp2[-1]) { 346 case ' ': 347 case '\t': 348 case '\n': 349 break; 350 default: 351 rc = 0; /* no match */ 352 break; 353 } 354 } 355 } 356 free(mp); 357 return (rc); 358 } 359 360 typedef struct meta_file_s { 361 FILE *fp; 362 GNode *gn; 363 } meta_file_t; 364 365 static int 366 printCMD(void *cmdp, void *mfpp) 367 { 368 meta_file_t *mfp = mfpp; 369 char *cmd = cmdp; 370 char *cp = NULL; 371 372 if (strchr(cmd, '$')) { 373 cmd = cp = Var_Subst(NULL, cmd, mfp->gn, VARF_WANTRES); 374 } 375 fprintf(mfp->fp, "CMD %s\n", cmd); 376 free(cp); 377 return 0; 378 } 379 380 /* 381 * Certain node types never get a .meta file 382 */ 383 #define SKIP_META_TYPE(_type) do { \ 384 if ((gn->type & __CONCAT(OP_, _type))) { \ 385 if (DEBUG(META)) { \ 386 fprintf(debug_file, "Skipping meta for %s: .%s\n", \ 387 gn->name, __STRING(_type)); \ 388 } \ 389 return (NULL); \ 390 } \ 391 } while (0) 392 393 static FILE * 394 meta_create(BuildMon *pbm, GNode *gn) 395 { 396 meta_file_t mf; 397 char buf[MAXPATHLEN]; 398 char objdir[MAXPATHLEN]; 399 char **ptr; 400 const char *dname; 401 const char *tname; 402 char *fname; 403 const char *cp; 404 char *p[4]; /* >= possible uses */ 405 int i; 406 struct stat fs; 407 408 409 /* This may be a phony node which we don't want meta data for... */ 410 /* Skip .meta for .BEGIN, .END, .ERROR etc as well. */ 411 /* Or it may be explicitly flagged as .NOMETA */ 412 SKIP_META_TYPE(NOMETA); 413 /* Unless it is explicitly flagged as .META */ 414 if (!(gn->type & OP_META)) { 415 SKIP_META_TYPE(PHONY); 416 SKIP_META_TYPE(SPECIAL); 417 SKIP_META_TYPE(MAKE); 418 } 419 420 mf.fp = NULL; 421 422 i = 0; 423 424 dname = Var_Value(".OBJDIR", gn, &p[i++]); 425 tname = Var_Value(TARGET, gn, &p[i++]); 426 427 /* The object directory may not exist. Check it.. */ 428 if (stat(dname, &fs) != 0) { 429 if (DEBUG(META)) 430 fprintf(debug_file, "Skipping meta for %s: no .OBJDIR\n", 431 gn->name); 432 goto out; 433 } 434 /* Check if there are no commands to execute. */ 435 if (Lst_IsEmpty(gn->commands)) { 436 if (DEBUG(META)) 437 fprintf(debug_file, "Skipping meta for %s: no commands\n", 438 gn->name); 439 goto out; 440 } 441 442 /* make sure these are canonical */ 443 if (realpath(dname, objdir)) 444 dname = objdir; 445 446 /* If we aren't in the object directory, don't create a meta file. */ 447 if (!metaCurdirOk && strcmp(curdir, dname) == 0) { 448 if (DEBUG(META)) 449 fprintf(debug_file, "Skipping meta for %s: .OBJDIR == .CURDIR\n", 450 gn->name); 451 goto out; 452 } 453 if (!(gn->type & OP_META)) { 454 /* We do not generate .meta files for sub-makes */ 455 if (Lst_ForEach(gn->commands, is_submake, gn)) { 456 if (DEBUG(META)) 457 fprintf(debug_file, "Skipping meta for %s: .MAKE\n", 458 gn->name); 459 goto out; 460 } 461 } 462 463 if (metaVerbose) { 464 char *mp; 465 466 /* Describe the target we are building */ 467 mp = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", gn, VARF_WANTRES); 468 if (*mp) 469 fprintf(stdout, "%s\n", mp); 470 free(mp); 471 } 472 /* Get the basename of the target */ 473 if ((cp = strrchr(tname, '/')) == NULL) { 474 cp = tname; 475 } else { 476 cp++; 477 } 478 479 fflush(stdout); 480 481 if (!writeMeta) 482 /* Don't create meta data. */ 483 goto out; 484 485 fname = meta_name(gn, pbm->meta_fname, sizeof(pbm->meta_fname), 486 dname, tname); 487 488 #ifdef DEBUG_META_MODE 489 if (DEBUG(META)) 490 fprintf(debug_file, "meta_create: %s\n", fname); 491 #endif 492 493 if ((mf.fp = fopen(fname, "w")) == NULL) 494 err(1, "Could not open meta file '%s'", fname); 495 496 fprintf(mf.fp, "# Meta data file %s\n", fname); 497 498 mf.gn = gn; 499 500 Lst_ForEach(gn->commands, printCMD, &mf); 501 502 fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf))); 503 fprintf(mf.fp, "TARGET %s\n", tname); 504 505 if (metaEnv) { 506 for (ptr = environ; *ptr != NULL; ptr++) 507 fprintf(mf.fp, "ENV %s\n", *ptr); 508 } 509 510 fprintf(mf.fp, "-- command output --\n"); 511 fflush(mf.fp); 512 513 Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL); 514 Var_Append(".MAKE.META.CREATED", fname, VAR_GLOBAL); 515 516 gn->type |= OP_META; /* in case anyone wants to know */ 517 if (metaSilent) { 518 gn->type |= OP_SILENT; 519 } 520 out: 521 for (i--; i >= 0; i--) { 522 free(p[i]); 523 } 524 525 return (mf.fp); 526 } 527 528 static Boolean 529 boolValue(char *s) 530 { 531 switch(*s) { 532 case '0': 533 case 'N': 534 case 'n': 535 case 'F': 536 case 'f': 537 return FALSE; 538 } 539 return TRUE; 540 } 541 542 /* 543 * Initialization we need before reading makefiles. 544 */ 545 void 546 meta_init(void) 547 { 548 #ifdef USE_FILEMON 549 /* this allows makefiles to test if we have filemon support */ 550 Var_Set(".MAKE.PATH_FILEMON", _PATH_FILEMON, VAR_GLOBAL, 0); 551 #endif 552 } 553 554 555 /* 556 * Initialization we need after reading makefiles. 557 */ 558 void 559 meta_mode_init(const char *make_mode) 560 { 561 static int once = 0; 562 char *cp; 563 564 useMeta = TRUE; 565 useFilemon = TRUE; 566 writeMeta = TRUE; 567 568 if (make_mode) { 569 if (strstr(make_mode, "env")) 570 metaEnv = TRUE; 571 if (strstr(make_mode, "verb")) 572 metaVerbose = TRUE; 573 if (strstr(make_mode, "read")) 574 writeMeta = FALSE; 575 if (strstr(make_mode, "nofilemon")) 576 useFilemon = FALSE; 577 if ((cp = strstr(make_mode, "curdirok="))) { 578 metaCurdirOk = boolValue(&cp[9]); 579 } 580 if ((cp = strstr(make_mode, "silent="))) { 581 metaSilent = boolValue(&cp[7]); 582 } 583 if (strstr(make_mode, "ignore-cmd")) 584 metaIgnoreCMDs = TRUE; 585 /* for backwards compatability */ 586 Var_Set(".MAKE.META_CREATED", "${.MAKE.META.CREATED}", VAR_GLOBAL, 0); 587 Var_Set(".MAKE.META_FILES", "${.MAKE.META.FILES}", VAR_GLOBAL, 0); 588 } 589 if (metaVerbose && !Var_Exists(MAKE_META_PREFIX, VAR_GLOBAL)) { 590 /* 591 * The default value for MAKE_META_PREFIX 592 * prints the absolute path of the target. 593 * This works be cause :H will generate '.' if there is no / 594 * and :tA will resolve that to cwd. 595 */ 596 Var_Set(MAKE_META_PREFIX, "Building ${.TARGET:H:tA}/${.TARGET:T}", VAR_GLOBAL, 0); 597 } 598 if (once) 599 return; 600 once = 1; 601 memset(&Mybm, 0, sizeof(Mybm)); 602 /* 603 * We consider ourselves master of all within ${.MAKE.META.BAILIWICK} 604 */ 605 metaBailiwick = Lst_Init(FALSE); 606 metaBailiwickStr = Var_Subst(NULL, "${.MAKE.META.BAILIWICK:O:u:tA}", 607 VAR_GLOBAL, VARF_WANTRES); 608 if (metaBailiwickStr) { 609 str2Lst_Append(metaBailiwick, metaBailiwickStr, NULL); 610 } 611 /* 612 * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS} 613 */ 614 metaIgnorePaths = Lst_Init(FALSE); 615 Var_Append(MAKE_META_IGNORE_PATHS, 616 "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}", VAR_GLOBAL); 617 metaIgnorePathsStr = Var_Subst(NULL, 618 "${" MAKE_META_IGNORE_PATHS ":O:u:tA}", VAR_GLOBAL, 619 VARF_WANTRES); 620 if (metaIgnorePathsStr) { 621 str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr, NULL); 622 } 623 } 624 625 /* 626 * In each case below we allow for job==NULL 627 */ 628 void 629 meta_job_start(Job *job, GNode *gn) 630 { 631 BuildMon *pbm; 632 633 if (job != NULL) { 634 pbm = &job->bm; 635 } else { 636 pbm = &Mybm; 637 } 638 pbm->mfp = meta_create(pbm, gn); 639 #ifdef USE_FILEMON_ONCE 640 /* compat mode we open the filemon dev once per command */ 641 if (job == NULL) 642 return; 643 #endif 644 #ifdef USE_FILEMON 645 if (pbm->mfp != NULL && useFilemon) { 646 filemon_open(pbm); 647 } else { 648 pbm->mon_fd = pbm->filemon_fd = -1; 649 } 650 #endif 651 } 652 653 /* 654 * The child calls this before doing anything. 655 * It does not disturb our state. 656 */ 657 void 658 meta_job_child(Job *job) 659 { 660 #ifdef USE_FILEMON 661 BuildMon *pbm; 662 663 if (job != NULL) { 664 pbm = &job->bm; 665 } else { 666 pbm = &Mybm; 667 } 668 if (pbm->mfp != NULL) { 669 close(fileno(pbm->mfp)); 670 if (useFilemon) { 671 pid_t pid; 672 673 pid = getpid(); 674 if (ioctl(pbm->filemon_fd, FILEMON_SET_PID, &pid) < 0) { 675 err(1, "Could not set filemon pid!"); 676 } 677 } 678 } 679 #endif 680 } 681 682 void 683 meta_job_error(Job *job, GNode *gn, int flags, int status) 684 { 685 char cwd[MAXPATHLEN]; 686 BuildMon *pbm; 687 688 if (job != NULL) { 689 pbm = &job->bm; 690 if (!gn) 691 gn = job->node; 692 } else { 693 pbm = &Mybm; 694 } 695 if (pbm->mfp != NULL) { 696 fprintf(pbm->mfp, "*** Error code %d%s\n", 697 status, 698 (flags & JOB_IGNERR) ? 699 "(ignored)" : ""); 700 } 701 if (gn) { 702 Var_Set(".ERROR_TARGET", gn->path ? gn->path : gn->name, VAR_GLOBAL, 0); 703 } 704 getcwd(cwd, sizeof(cwd)); 705 Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL, 0); 706 if (pbm->meta_fname[0]) { 707 Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL, 0); 708 } 709 meta_job_finish(job); 710 } 711 712 void 713 meta_job_output(Job *job, char *cp, const char *nl) 714 { 715 BuildMon *pbm; 716 717 if (job != NULL) { 718 pbm = &job->bm; 719 } else { 720 pbm = &Mybm; 721 } 722 if (pbm->mfp != NULL) { 723 if (metaVerbose) { 724 static char *meta_prefix = NULL; 725 static int meta_prefix_len; 726 727 if (!meta_prefix) { 728 char *cp2; 729 730 meta_prefix = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", 731 VAR_GLOBAL, VARF_WANTRES); 732 if ((cp2 = strchr(meta_prefix, '$'))) 733 meta_prefix_len = cp2 - meta_prefix; 734 else 735 meta_prefix_len = strlen(meta_prefix); 736 } 737 if (strncmp(cp, meta_prefix, meta_prefix_len) == 0) { 738 cp = strchr(cp+1, '\n'); 739 if (!cp++) 740 return; 741 } 742 } 743 fprintf(pbm->mfp, "%s%s", cp, nl); 744 } 745 } 746 747 void 748 meta_cmd_finish(void *pbmp) 749 { 750 #ifdef USE_FILEMON 751 BuildMon *pbm = pbmp; 752 753 if (!pbm) 754 pbm = &Mybm; 755 756 if (pbm->filemon_fd >= 0) { 757 close(pbm->filemon_fd); 758 filemon_read(pbm->mfp, pbm->mon_fd); 759 pbm->filemon_fd = pbm->mon_fd = -1; 760 } 761 #endif 762 } 763 764 void 765 meta_job_finish(Job *job) 766 { 767 BuildMon *pbm; 768 769 if (job != NULL) { 770 pbm = &job->bm; 771 } else { 772 pbm = &Mybm; 773 } 774 if (pbm->mfp != NULL) { 775 meta_cmd_finish(pbm); 776 fclose(pbm->mfp); 777 pbm->mfp = NULL; 778 pbm->meta_fname[0] = '\0'; 779 } 780 } 781 782 void 783 meta_finish(void) 784 { 785 Lst_Destroy(metaBailiwick, NULL); 786 free(metaBailiwickStr); 787 Lst_Destroy(metaIgnorePaths, NULL); 788 free(metaIgnorePathsStr); 789 } 790 791 /* 792 * Fetch a full line from fp - growing bufp if needed 793 * Return length in bufp. 794 */ 795 static int 796 fgetLine(char **bufp, size_t *szp, int o, FILE *fp) 797 { 798 char *buf = *bufp; 799 size_t bufsz = *szp; 800 struct stat fs; 801 int x; 802 803 if (fgets(&buf[o], bufsz - o, fp) != NULL) { 804 check_newline: 805 x = o + strlen(&buf[o]); 806 if (buf[x - 1] == '\n') 807 return x; 808 /* 809 * We need to grow the buffer. 810 * The meta file can give us a clue. 811 */ 812 if (fstat(fileno(fp), &fs) == 0) { 813 size_t newsz; 814 char *p; 815 816 newsz = ROUNDUP((fs.st_size / 2), BUFSIZ); 817 if (newsz <= bufsz) 818 newsz = ROUNDUP(fs.st_size, BUFSIZ); 819 if (DEBUG(META)) 820 fprintf(debug_file, "growing buffer %u -> %u\n", 821 (unsigned)bufsz, (unsigned)newsz); 822 p = bmake_realloc(buf, newsz); 823 if (p) { 824 *bufp = buf = p; 825 *szp = bufsz = newsz; 826 /* fetch the rest */ 827 if (!fgets(&buf[x], bufsz - x, fp)) 828 return x; /* truncated! */ 829 goto check_newline; 830 } 831 } 832 } 833 return 0; 834 } 835 836 static int 837 prefix_match(void *p, void *q) 838 { 839 const char *prefix = p; 840 const char *path = q; 841 size_t n = strlen(prefix); 842 843 return (0 == strncmp(path, prefix, n)); 844 } 845 846 static int 847 string_match(const void *p, const void *q) 848 { 849 const char *p1 = p; 850 const char *p2 = q; 851 852 return strcmp(p1, p2); 853 } 854 855 856 /* 857 * When running with 'meta' functionality, a target can be out-of-date 858 * if any of the references in its meta data file is more recent. 859 * We have to track the latestdir on a per-process basis. 860 */ 861 #define LCWD_VNAME_FMT ".meta.%d.lcwd" 862 #define LDIR_VNAME_FMT ".meta.%d.ldir" 863 864 /* 865 * It is possible that a .meta file is corrupted, 866 * if we detect this we want to reproduce it. 867 * Setting oodate TRUE will have that effect. 868 */ 869 #define CHECK_VALID_META(p) if (!(p && *p)) { \ 870 warnx("%s: %d: malformed", fname, lineno); \ 871 oodate = TRUE; \ 872 continue; \ 873 } 874 875 #define DEQUOTE(p) if (*p == '\'') { \ 876 char *ep; \ 877 p++; \ 878 if ((ep = strchr(p, '\''))) \ 879 *ep = '\0'; \ 880 } 881 882 Boolean 883 meta_oodate(GNode *gn, Boolean oodate) 884 { 885 static char *tmpdir = NULL; 886 static char cwd[MAXPATHLEN]; 887 char lcwd_vname[64]; 888 char ldir_vname[64]; 889 char lcwd[MAXPATHLEN]; 890 char latestdir[MAXPATHLEN]; 891 char fname[MAXPATHLEN]; 892 char fname1[MAXPATHLEN]; 893 char fname2[MAXPATHLEN]; 894 char fname3[MAXPATHLEN]; 895 char *p; 896 char *cp; 897 char *link_src; 898 char *move_target; 899 static size_t cwdlen = 0; 900 static size_t tmplen = 0; 901 FILE *fp; 902 Boolean needOODATE = FALSE; 903 Lst missingFiles; 904 905 if (oodate) 906 return oodate; /* we're done */ 907 908 missingFiles = Lst_Init(FALSE); 909 910 /* 911 * We need to check if the target is out-of-date. This includes 912 * checking if the expanded command has changed. This in turn 913 * requires that all variables are set in the same way that they 914 * would be if the target needs to be re-built. 915 */ 916 Make_DoAllVar(gn); 917 918 meta_name(gn, fname, sizeof(fname), NULL, NULL); 919 920 #ifdef DEBUG_META_MODE 921 if (DEBUG(META)) 922 fprintf(debug_file, "meta_oodate: %s\n", fname); 923 #endif 924 925 if ((fp = fopen(fname, "r")) != NULL) { 926 static char *buf = NULL; 927 static size_t bufsz; 928 int lineno = 0; 929 int lastpid = 0; 930 int pid; 931 int f = 0; 932 int x; 933 LstNode ln; 934 struct stat fs; 935 936 if (!buf) { 937 bufsz = 8 * BUFSIZ; 938 buf = bmake_malloc(bufsz); 939 } 940 941 if (!cwdlen) { 942 if (getcwd(cwd, sizeof(cwd)) == NULL) 943 err(1, "Could not get current working directory"); 944 cwdlen = strlen(cwd); 945 } 946 strlcpy(lcwd, cwd, sizeof(lcwd)); 947 strlcpy(latestdir, cwd, sizeof(latestdir)); 948 949 if (!tmpdir) { 950 tmpdir = getTmpdir(); 951 tmplen = strlen(tmpdir); 952 } 953 954 /* we want to track all the .meta we read */ 955 Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL); 956 957 ln = Lst_First(gn->commands); 958 while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) { 959 lineno++; 960 if (buf[x - 1] == '\n') 961 buf[x - 1] = '\0'; 962 else { 963 warnx("%s: %d: line truncated at %u", fname, lineno, x); 964 oodate = TRUE; 965 break; 966 } 967 link_src = NULL; 968 move_target = NULL; 969 /* Find the start of the build monitor section. */ 970 if (!f) { 971 if (strncmp(buf, "-- filemon", 10) == 0) { 972 f = 1; 973 continue; 974 } 975 if (strncmp(buf, "# buildmon", 10) == 0) { 976 f = 1; 977 continue; 978 } 979 } 980 981 /* Delimit the record type. */ 982 p = buf; 983 #ifdef DEBUG_META_MODE 984 if (DEBUG(META)) 985 fprintf(debug_file, "%s: %d: %s\n", fname, lineno, buf); 986 #endif 987 strsep(&p, " "); 988 if (f) { 989 /* 990 * We are in the 'filemon' output section. 991 * Each record from filemon follows the general form: 992 * 993 * <key> <pid> <data> 994 * 995 * Where: 996 * <key> is a single letter, denoting the syscall. 997 * <pid> is the process that made the syscall. 998 * <data> is the arguments (of interest). 999 */ 1000 switch(buf[0]) { 1001 case '#': /* comment */ 1002 case 'V': /* version */ 1003 break; 1004 default: 1005 /* 1006 * We need to track pathnames per-process. 1007 * 1008 * Each process run by make, starts off in the 'CWD' 1009 * recorded in the .meta file, if it chdirs ('C') 1010 * elsewhere we need to track that - but only for 1011 * that process. If it forks ('F'), we initialize 1012 * the child to have the same cwd as its parent. 1013 * 1014 * We also need to track the 'latestdir' of 1015 * interest. This is usually the same as cwd, but 1016 * not if a process is reading directories. 1017 * 1018 * Each time we spot a different process ('pid') 1019 * we save the current value of 'latestdir' in a 1020 * variable qualified by 'lastpid', and 1021 * re-initialize 'latestdir' to any pre-saved 1022 * value for the current 'pid' and 'CWD' if none. 1023 */ 1024 CHECK_VALID_META(p); 1025 pid = atoi(p); 1026 if (pid > 0 && pid != lastpid) { 1027 char *ldir; 1028 char *tp; 1029 1030 if (lastpid > 0) { 1031 /* We need to remember these. */ 1032 Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0); 1033 Var_Set(ldir_vname, latestdir, VAR_GLOBAL, 0); 1034 } 1035 snprintf(lcwd_vname, sizeof(lcwd_vname), LCWD_VNAME_FMT, pid); 1036 snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid); 1037 lastpid = pid; 1038 ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp); 1039 if (ldir) { 1040 strlcpy(latestdir, ldir, sizeof(latestdir)); 1041 free(tp); 1042 } 1043 ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp); 1044 if (ldir) { 1045 strlcpy(lcwd, ldir, sizeof(lcwd)); 1046 free(tp); 1047 } 1048 } 1049 /* Skip past the pid. */ 1050 if (strsep(&p, " ") == NULL) 1051 continue; 1052 #ifdef DEBUG_META_MODE 1053 if (DEBUG(META)) 1054 fprintf(debug_file, "%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n", 1055 fname, lineno, 1056 pid, buf[0], cwd, lcwd, latestdir); 1057 #endif 1058 break; 1059 } 1060 1061 CHECK_VALID_META(p); 1062 1063 /* Process according to record type. */ 1064 switch (buf[0]) { 1065 case 'X': /* eXit */ 1066 Var_Delete(lcwd_vname, VAR_GLOBAL); 1067 Var_Delete(ldir_vname, VAR_GLOBAL); 1068 lastpid = 0; /* no need to save ldir_vname */ 1069 break; 1070 1071 case 'F': /* [v]Fork */ 1072 { 1073 char cldir[64]; 1074 int child; 1075 1076 child = atoi(p); 1077 if (child > 0) { 1078 snprintf(cldir, sizeof(cldir), LCWD_VNAME_FMT, child); 1079 Var_Set(cldir, lcwd, VAR_GLOBAL, 0); 1080 snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child); 1081 Var_Set(cldir, latestdir, VAR_GLOBAL, 0); 1082 #ifdef DEBUG_META_MODE 1083 if (DEBUG(META)) 1084 fprintf(debug_file, "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n", 1085 fname, lineno, 1086 child, cwd, lcwd, latestdir); 1087 #endif 1088 } 1089 } 1090 break; 1091 1092 case 'C': /* Chdir */ 1093 /* Update lcwd and latest directory. */ 1094 strlcpy(latestdir, p, sizeof(latestdir)); 1095 strlcpy(lcwd, p, sizeof(lcwd)); 1096 Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0); 1097 Var_Set(ldir_vname, lcwd, VAR_GLOBAL, 0); 1098 #ifdef DEBUG_META_MODE 1099 if (DEBUG(META)) 1100 fprintf(debug_file, "%s: %d: cwd=%s ldir=%s\n", fname, lineno, cwd, lcwd); 1101 #endif 1102 break; 1103 1104 case 'M': /* renaMe */ 1105 /* 1106 * For 'M'oves we want to check 1107 * the src as for 'R'ead 1108 * and the target as for 'W'rite. 1109 */ 1110 cp = p; /* save this for a second */ 1111 /* now get target */ 1112 if (strsep(&p, " ") == NULL) 1113 continue; 1114 CHECK_VALID_META(p); 1115 move_target = p; 1116 p = cp; 1117 /* 'L' and 'M' put single quotes around the args */ 1118 DEQUOTE(p); 1119 DEQUOTE(move_target); 1120 /* FALLTHROUGH */ 1121 case 'D': /* unlink */ 1122 if (*p == '/' && !Lst_IsEmpty(missingFiles)) { 1123 /* remove p from the missingFiles list if present */ 1124 if ((ln = Lst_Find(missingFiles, p, string_match)) != NULL) { 1125 char *tp = Lst_Datum(ln); 1126 Lst_Remove(missingFiles, ln); 1127 free(tp); 1128 ln = NULL; /* we're done with it */ 1129 } 1130 } 1131 if (buf[0] == 'M') { 1132 /* the target of the mv is a file 'W'ritten */ 1133 #ifdef DEBUG_META_MODE 1134 if (DEBUG(META)) 1135 fprintf(debug_file, "meta_oodate: M %s -> %s\n", 1136 p, move_target); 1137 #endif 1138 p = move_target; 1139 goto check_write; 1140 } 1141 break; 1142 case 'L': /* Link */ 1143 /* 1144 * For 'L'inks check 1145 * the src as for 'R'ead 1146 * and the target as for 'W'rite. 1147 */ 1148 link_src = p; 1149 /* now get target */ 1150 if (strsep(&p, " ") == NULL) 1151 continue; 1152 CHECK_VALID_META(p); 1153 /* 'L' and 'M' put single quotes around the args */ 1154 DEQUOTE(p); 1155 DEQUOTE(link_src); 1156 #ifdef DEBUG_META_MODE 1157 if (DEBUG(META)) 1158 fprintf(debug_file, "meta_oodate: L %s -> %s\n", 1159 link_src, p); 1160 #endif 1161 /* FALLTHROUGH */ 1162 case 'W': /* Write */ 1163 check_write: 1164 /* 1165 * If a file we generated within our bailiwick 1166 * but outside of .OBJDIR is missing, 1167 * we need to do it again. 1168 */ 1169 /* ignore non-absolute paths */ 1170 if (*p != '/') 1171 break; 1172 1173 if (Lst_IsEmpty(metaBailiwick)) 1174 break; 1175 1176 /* ignore cwd - normal dependencies handle those */ 1177 if (strncmp(p, cwd, cwdlen) == 0) 1178 break; 1179 1180 if (!Lst_ForEach(metaBailiwick, prefix_match, p)) 1181 break; 1182 1183 /* tmpdir might be within */ 1184 if (tmplen > 0 && strncmp(p, tmpdir, tmplen) == 0) 1185 break; 1186 1187 /* ignore anything containing the string "tmp" */ 1188 if ((strstr("tmp", p))) 1189 break; 1190 1191 if ((link_src != NULL && lstat(p, &fs) < 0) || 1192 (link_src == NULL && stat(p, &fs) < 0)) { 1193 if (Lst_Find(missingFiles, p, string_match) == NULL) 1194 Lst_AtEnd(missingFiles, bmake_strdup(p)); 1195 } 1196 break; 1197 check_link_src: 1198 p = link_src; 1199 link_src = NULL; 1200 #ifdef DEBUG_META_MODE 1201 if (DEBUG(META)) 1202 fprintf(debug_file, "meta_oodate: L src %s\n", p); 1203 #endif 1204 /* FALLTHROUGH */ 1205 case 'R': /* Read */ 1206 case 'E': /* Exec */ 1207 /* 1208 * Check for runtime files that can't 1209 * be part of the dependencies because 1210 * they are _expected_ to change. 1211 */ 1212 if (*p == '/' && 1213 Lst_ForEach(metaIgnorePaths, prefix_match, p)) { 1214 #ifdef DEBUG_META_MODE 1215 if (DEBUG(META)) 1216 fprintf(debug_file, "meta_oodate: ignoring: %s\n", 1217 p); 1218 #endif 1219 break; 1220 } 1221 1222 /* 1223 * The rest of the record is the file name. 1224 * Check if it's not an absolute path. 1225 */ 1226 { 1227 char *sdirs[4]; 1228 char **sdp; 1229 int sdx = 0; 1230 int found = 0; 1231 1232 if (*p == '/') { 1233 sdirs[sdx++] = p; /* done */ 1234 } else { 1235 if (strcmp(".", p) == 0) 1236 continue; /* no point */ 1237 1238 /* Check vs latestdir */ 1239 snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p); 1240 sdirs[sdx++] = fname1; 1241 1242 if (strcmp(latestdir, lcwd) != 0) { 1243 /* Check vs lcwd */ 1244 snprintf(fname2, sizeof(fname2), "%s/%s", lcwd, p); 1245 sdirs[sdx++] = fname2; 1246 } 1247 if (strcmp(lcwd, cwd) != 0) { 1248 /* Check vs cwd */ 1249 snprintf(fname3, sizeof(fname3), "%s/%s", cwd, p); 1250 sdirs[sdx++] = fname3; 1251 } 1252 } 1253 sdirs[sdx++] = NULL; 1254 1255 for (sdp = sdirs; *sdp && !found; sdp++) { 1256 #ifdef DEBUG_META_MODE 1257 if (DEBUG(META)) 1258 fprintf(debug_file, "%s: %d: looking for: %s\n", fname, lineno, *sdp); 1259 #endif 1260 if (stat(*sdp, &fs) == 0) { 1261 found = 1; 1262 p = *sdp; 1263 } 1264 } 1265 if (found) { 1266 #ifdef DEBUG_META_MODE 1267 if (DEBUG(META)) 1268 fprintf(debug_file, "%s: %d: found: %s\n", fname, lineno, p); 1269 #endif 1270 if (!S_ISDIR(fs.st_mode) && 1271 fs.st_mtime > gn->mtime) { 1272 if (DEBUG(META)) 1273 fprintf(debug_file, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p); 1274 oodate = TRUE; 1275 } else if (S_ISDIR(fs.st_mode)) { 1276 /* Update the latest directory. */ 1277 realpath(p, latestdir); 1278 } 1279 } else if (errno == ENOENT && *p == '/' && 1280 strncmp(p, cwd, cwdlen) != 0) { 1281 /* 1282 * A referenced file outside of CWD is missing. 1283 * We cannot catch every eventuality here... 1284 */ 1285 if (Lst_Find(missingFiles, p, string_match) == NULL) 1286 Lst_AtEnd(missingFiles, bmake_strdup(p)); 1287 } 1288 } 1289 if (buf[0] == 'E') { 1290 /* previous latestdir is no longer relevant */ 1291 strlcpy(latestdir, lcwd, sizeof(latestdir)); 1292 } 1293 break; 1294 default: 1295 break; 1296 } 1297 if (!oodate && buf[0] == 'L' && link_src != NULL) 1298 goto check_link_src; 1299 } else if (strcmp(buf, "CMD") == 0) { 1300 /* 1301 * Compare the current command with the one in the 1302 * meta data file. 1303 */ 1304 if (ln == NULL) { 1305 if (DEBUG(META)) 1306 fprintf(debug_file, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno); 1307 oodate = TRUE; 1308 } else { 1309 char *cmd = (char *)Lst_Datum(ln); 1310 Boolean hasOODATE = FALSE; 1311 1312 if (strstr(cmd, "$?")) 1313 hasOODATE = TRUE; 1314 else if ((cp = strstr(cmd, ".OODATE"))) { 1315 /* check for $[{(].OODATE[:)}] */ 1316 if (cp > cmd + 2 && cp[-2] == '$') 1317 hasOODATE = TRUE; 1318 } 1319 if (hasOODATE) { 1320 needOODATE = TRUE; 1321 if (DEBUG(META)) 1322 fprintf(debug_file, "%s: %d: cannot compare command using .OODATE\n", fname, lineno); 1323 } 1324 cmd = Var_Subst(NULL, cmd, gn, VARF_WANTRES|VARF_UNDEFERR); 1325 1326 if ((cp = strchr(cmd, '\n'))) { 1327 int n; 1328 1329 /* 1330 * This command contains newlines, we need to 1331 * fetch more from the .meta file before we 1332 * attempt a comparison. 1333 */ 1334 /* first put the newline back at buf[x - 1] */ 1335 buf[x - 1] = '\n'; 1336 do { 1337 /* now fetch the next line */ 1338 if ((n = fgetLine(&buf, &bufsz, x, fp)) <= 0) 1339 break; 1340 x = n; 1341 lineno++; 1342 if (buf[x - 1] != '\n') { 1343 warnx("%s: %d: line truncated at %u", fname, lineno, x); 1344 break; 1345 } 1346 cp = strchr(++cp, '\n'); 1347 } while (cp); 1348 if (buf[x - 1] == '\n') 1349 buf[x - 1] = '\0'; 1350 } 1351 if (!hasOODATE && 1352 !(gn->type & OP_NOMETA_CMP) && 1353 strcmp(p, cmd) != 0) { 1354 if (DEBUG(META)) 1355 fprintf(debug_file, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd); 1356 if (!metaIgnoreCMDs) 1357 oodate = TRUE; 1358 } 1359 free(cmd); 1360 ln = Lst_Succ(ln); 1361 } 1362 } else if (strcmp(buf, "CWD") == 0) { 1363 /* 1364 * Check if there are extra commands now 1365 * that weren't in the meta data file. 1366 */ 1367 if (!oodate && ln != NULL) { 1368 if (DEBUG(META)) 1369 fprintf(debug_file, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno); 1370 oodate = TRUE; 1371 } 1372 if (strcmp(p, cwd) != 0) { 1373 if (DEBUG(META)) 1374 fprintf(debug_file, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir); 1375 oodate = TRUE; 1376 } 1377 } 1378 } 1379 1380 fclose(fp); 1381 if (!Lst_IsEmpty(missingFiles)) { 1382 if (DEBUG(META)) 1383 fprintf(debug_file, "%s: missing files: %s...\n", 1384 fname, (char *)Lst_Datum(Lst_First(missingFiles))); 1385 oodate = TRUE; 1386 } 1387 } else { 1388 if ((gn->type & OP_META)) { 1389 if (DEBUG(META)) 1390 fprintf(debug_file, "%s: required but missing\n", fname); 1391 oodate = TRUE; 1392 } 1393 } 1394 1395 Lst_Destroy(missingFiles, (FreeProc *)free); 1396 1397 if (oodate && needOODATE) { 1398 /* 1399 * Target uses .OODATE which is empty; or we wouldn't be here. 1400 * We have decided it is oodate, so .OODATE needs to be set. 1401 * All we can sanely do is set it to .ALLSRC. 1402 */ 1403 Var_Delete(OODATE, gn); 1404 Var_Set(OODATE, Var_Value(ALLSRC, gn, &cp), gn, 0); 1405 free(cp); 1406 } 1407 return oodate; 1408 } 1409 1410 /* support for compat mode */ 1411 1412 static int childPipe[2]; 1413 1414 void 1415 meta_compat_start(void) 1416 { 1417 #ifdef USE_FILEMON_ONCE 1418 /* 1419 * We need to re-open filemon for each cmd. 1420 */ 1421 BuildMon *pbm = &Mybm; 1422 1423 if (pbm->mfp != NULL && useFilemon) { 1424 filemon_open(pbm); 1425 } else { 1426 pbm->mon_fd = pbm->filemon_fd = -1; 1427 } 1428 #endif 1429 if (pipe(childPipe) < 0) 1430 Punt("Cannot create pipe: %s", strerror(errno)); 1431 /* Set close-on-exec flag for both */ 1432 (void)fcntl(childPipe[0], F_SETFD, FD_CLOEXEC); 1433 (void)fcntl(childPipe[1], F_SETFD, FD_CLOEXEC); 1434 } 1435 1436 void 1437 meta_compat_child(void) 1438 { 1439 meta_job_child(NULL); 1440 if (dup2(childPipe[1], 1) < 0 || 1441 dup2(1, 2) < 0) { 1442 execError("dup2", "pipe"); 1443 _exit(1); 1444 } 1445 } 1446 1447 void 1448 meta_compat_parent(void) 1449 { 1450 FILE *fp; 1451 char buf[BUFSIZ]; 1452 1453 close(childPipe[1]); /* child side */ 1454 fp = fdopen(childPipe[0], "r"); 1455 while (fgets(buf, sizeof(buf), fp)) { 1456 meta_job_output(NULL, buf, ""); 1457 printf("%s", buf); 1458 } 1459 fclose(fp); 1460 } 1461 1462 #endif /* USE_META */ 1463