1253883Ssjg/*      $NetBSD: meta.c,v 1.32 2013/06/25 00:20:54 sjg Exp $ */
2236769Sobrien
3236769Sobrien/*
4236769Sobrien * Implement 'meta' mode.
5236769Sobrien * Adapted from John Birrell's patches to FreeBSD make.
6236769Sobrien * --sjg
7236769Sobrien */
8236769Sobrien/*
9236769Sobrien * Copyright (c) 2009-2010, Juniper Networks, Inc.
10236769Sobrien * Portions Copyright (c) 2009, John Birrell.
11236769Sobrien *
12236769Sobrien * Redistribution and use in source and binary forms, with or without
13236769Sobrien * modification, are permitted provided that the following conditions
14236769Sobrien * are met:
15236769Sobrien * 1. Redistributions of source code must retain the above copyright
16236769Sobrien *    notice, this list of conditions and the following disclaimer.
17236769Sobrien * 2. Redistributions in binary form must reproduce the above copyright
18236769Sobrien *    notice, this list of conditions and the following disclaimer in the
19236769Sobrien *    documentation and/or other materials provided with the distribution.
20236769Sobrien *
21236769Sobrien * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22236769Sobrien * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23236769Sobrien * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24236769Sobrien * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25236769Sobrien * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26236769Sobrien * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27236769Sobrien * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28236769Sobrien * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29236769Sobrien * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30236769Sobrien * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31236769Sobrien * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32236769Sobrien */
33236769Sobrien#if defined(USE_META)
34236769Sobrien
35236769Sobrien#ifdef HAVE_CONFIG_H
36236769Sobrien# include "config.h"
37236769Sobrien#endif
38236769Sobrien#include <sys/stat.h>
39236769Sobrien#include <sys/ioctl.h>
40236769Sobrien#include <fcntl.h>
41236769Sobrien#include <libgen.h>
42236769Sobrien#include <errno.h>
43236769Sobrien#if !defined(HAVE_CONFIG_H) || defined(HAVE_ERR_H)
44236769Sobrien#include <err.h>
45236769Sobrien#endif
46236769Sobrien
47236769Sobrien#include "make.h"
48236769Sobrien#include "job.h"
49236769Sobrien
50236769Sobrien#ifdef HAVE_FILEMON_H
51236769Sobrien# include <filemon.h>
52236769Sobrien#endif
53236769Sobrien#if !defined(USE_FILEMON) && defined(FILEMON_SET_FD)
54236769Sobrien# define USE_FILEMON
55236769Sobrien#endif
56236769Sobrien
57236769Sobrienstatic BuildMon Mybm;			/* for compat */
58253883Ssjgstatic Lst metaBailiwick;		/* our scope of control */
59253883Ssjgstatic Lst metaIgnorePaths;		/* paths we deliberately ignore */
60236769Sobrien
61253883Ssjg#ifndef MAKE_META_IGNORE_PATHS
62253883Ssjg#define MAKE_META_IGNORE_PATHS ".MAKE.META.IGNORE_PATHS"
63253883Ssjg#endif
64253883Ssjg
65236769SobrienBoolean useMeta = FALSE;
66236769Sobrienstatic Boolean useFilemon = FALSE;
67236769Sobrienstatic Boolean writeMeta = FALSE;
68236769Sobrienstatic Boolean metaEnv = FALSE;		/* don't save env unless asked */
69236769Sobrienstatic Boolean metaVerbose = FALSE;
70236769Sobrienstatic Boolean metaIgnoreCMDs = FALSE;	/* ignore CMDs in .meta files */
71236769Sobrienstatic Boolean metaCurdirOk = FALSE;	/* write .meta in .CURDIR Ok? */
72236769Sobrienstatic Boolean metaSilent = FALSE;	/* if we have a .meta be SILENT */
73236769Sobrien
74236769Sobrienextern Boolean forceJobs;
75236769Sobrienextern Boolean comatMake;
76238152Sobrienextern char    **environ;
77236769Sobrien
78236769Sobrien#define	MAKE_META_PREFIX	".MAKE.META.PREFIX"
79236769Sobrien
80236769Sobrien#ifndef N2U
81236769Sobrien# define N2U(n, u)   (((n) + ((u) - 1)) / (u))
82236769Sobrien#endif
83236769Sobrien#ifndef ROUNDUP
84236769Sobrien# define ROUNDUP(n, u)   (N2U((n), (u)) * (u))
85236769Sobrien#endif
86236769Sobrien
87236769Sobrien#if !defined(HAVE_STRSEP)
88236769Sobrien# define strsep(s, d) stresep((s), (d), 0)
89236769Sobrien#endif
90236769Sobrien
91236769Sobrien/*
92236769Sobrien * Filemon is a kernel module which snoops certain syscalls.
93236769Sobrien *
94236769Sobrien * C chdir
95236769Sobrien * E exec
96236769Sobrien * F [v]fork
97236769Sobrien * L [sym]link
98236769Sobrien * M rename
99236769Sobrien * R read
100236769Sobrien * W write
101236769Sobrien * S stat
102236769Sobrien *
103236769Sobrien * See meta_oodate below - we mainly care about 'E' and 'R'.
104236769Sobrien *
105236769Sobrien * We can still use meta mode without filemon, but
106236769Sobrien * the benefits are more limited.
107236769Sobrien */
108236769Sobrien#ifdef USE_FILEMON
109236769Sobrien# ifndef _PATH_FILEMON
110236769Sobrien#   define _PATH_FILEMON "/dev/filemon"
111236769Sobrien# endif
112236769Sobrien
113236769Sobrien/*
114236769Sobrien * Open the filemon device.
115236769Sobrien */
116236769Sobrienstatic void
117236769Sobrienfilemon_open(BuildMon *pbm)
118236769Sobrien{
119236769Sobrien    int retry;
120236769Sobrien
121236769Sobrien    pbm->mon_fd = pbm->filemon_fd = -1;
122236769Sobrien    if (!useFilemon)
123236769Sobrien	return;
124236769Sobrien
125236769Sobrien    for (retry = 5; retry >= 0; retry--) {
126236769Sobrien	if ((pbm->filemon_fd = open(_PATH_FILEMON, O_RDWR)) >= 0)
127236769Sobrien	    break;
128236769Sobrien    }
129236769Sobrien
130236769Sobrien    if (pbm->filemon_fd < 0) {
131236769Sobrien	useFilemon = FALSE;
132236769Sobrien	warn("Could not open %s", _PATH_FILEMON);
133236769Sobrien	return;
134236769Sobrien    }
135236769Sobrien
136236769Sobrien    /*
137236769Sobrien     * We use a file outside of '.'
138236769Sobrien     * to avoid a FreeBSD kernel bug where unlink invalidates
139236769Sobrien     * cwd causing getcwd to do a lot more work.
140236769Sobrien     * We only care about the descriptor.
141236769Sobrien     */
142236769Sobrien    pbm->mon_fd = mkTempFile("filemon.XXXXXX", NULL);
143236769Sobrien    if (ioctl(pbm->filemon_fd, FILEMON_SET_FD, &pbm->mon_fd) < 0) {
144236769Sobrien	err(1, "Could not set filemon file descriptor!");
145236769Sobrien    }
146236769Sobrien    /* we don't need these once we exec */
147236769Sobrien    (void)fcntl(pbm->mon_fd, F_SETFD, 1);
148236769Sobrien    (void)fcntl(pbm->filemon_fd, F_SETFD, 1);
149236769Sobrien}
150236769Sobrien
151236769Sobrien/*
152236769Sobrien * Read the build monitor output file and write records to the target's
153236769Sobrien * metadata file.
154236769Sobrien */
155236769Sobrienstatic void
156236769Sobrienfilemon_read(FILE *mfp, int fd)
157236769Sobrien{
158236769Sobrien    FILE *fp;
159236769Sobrien    char buf[BUFSIZ];
160236769Sobrien
161236769Sobrien    /* Check if we're not writing to a meta data file.*/
162236769Sobrien    if (mfp == NULL) {
163236769Sobrien	if (fd >= 0)
164236769Sobrien	    close(fd);			/* not interested */
165236769Sobrien	return;
166236769Sobrien    }
167236769Sobrien    /* rewind */
168236769Sobrien    (void)lseek(fd, (off_t)0, SEEK_SET);
169236769Sobrien    if ((fp = fdopen(fd, "r")) == NULL)
170236769Sobrien	err(1, "Could not read build monitor file '%d'", fd);
171236769Sobrien
172236769Sobrien    fprintf(mfp, "-- filemon acquired metadata --\n");
173236769Sobrien
174236769Sobrien    while (fgets(buf, sizeof(buf), fp)) {
175236769Sobrien	fprintf(mfp, "%s", buf);
176236769Sobrien    }
177236769Sobrien    fflush(mfp);
178236769Sobrien    clearerr(fp);
179236769Sobrien    fclose(fp);
180236769Sobrien}
181236769Sobrien#endif
182236769Sobrien
183236769Sobrien/*
184236769Sobrien * when realpath() fails,
185236769Sobrien * we use this, to clean up ./ and ../
186236769Sobrien */
187236769Sobrienstatic void
188236769Sobrieneat_dots(char *buf, size_t bufsz, int dots)
189236769Sobrien{
190236769Sobrien    char *cp;
191236769Sobrien    char *cp2;
192236769Sobrien    const char *eat;
193236769Sobrien    size_t eatlen;
194236769Sobrien
195236769Sobrien    switch (dots) {
196236769Sobrien    case 1:
197236769Sobrien	eat = "/./";
198236769Sobrien	eatlen = 2;
199236769Sobrien	break;
200236769Sobrien    case 2:
201236769Sobrien	eat = "/../";
202236769Sobrien	eatlen = 3;
203236769Sobrien	break;
204236769Sobrien    default:
205236769Sobrien	return;
206236769Sobrien    }
207236769Sobrien
208236769Sobrien    do {
209236769Sobrien	cp = strstr(buf, eat);
210236769Sobrien	if (cp) {
211236769Sobrien	    cp2 = cp + eatlen;
212236769Sobrien	    if (dots == 2 && cp > buf) {
213236769Sobrien		do {
214236769Sobrien		    cp--;
215236769Sobrien		} while (cp > buf && *cp != '/');
216236769Sobrien	    }
217236769Sobrien	    if (*cp == '/') {
218236769Sobrien		strlcpy(cp, cp2, bufsz - (cp - buf));
219236769Sobrien	    } else {
220236769Sobrien		return;			/* can't happen? */
221236769Sobrien	    }
222236769Sobrien	}
223236769Sobrien    } while (cp);
224236769Sobrien}
225236769Sobrien
226236769Sobrienstatic char *
227236769Sobrienmeta_name(struct GNode *gn, char *mname, size_t mnamelen,
228236769Sobrien	  const char *dname,
229236769Sobrien	  const char *tname)
230236769Sobrien{
231236769Sobrien    char buf[MAXPATHLEN];
232236769Sobrien    char cwd[MAXPATHLEN];
233236769Sobrien    char *rp;
234236769Sobrien    char *cp;
235236769Sobrien    char *tp;
236236769Sobrien    char *p[4];				/* >= number of possible uses */
237236769Sobrien    int i;
238236769Sobrien
239236769Sobrien    i = 0;
240236769Sobrien    if (!dname)
241236769Sobrien	dname = Var_Value(".OBJDIR", gn, &p[i++]);
242236769Sobrien    if (!tname)
243236769Sobrien	tname = Var_Value(TARGET, gn, &p[i++]);
244236769Sobrien
245236769Sobrien    if (realpath(dname, cwd))
246236769Sobrien	dname = cwd;
247236769Sobrien
248236769Sobrien    /*
249236769Sobrien     * Weed out relative paths from the target file name.
250236769Sobrien     * We have to be careful though since if target is a
251236769Sobrien     * symlink, the result will be unstable.
252236769Sobrien     * So we use realpath() just to get the dirname, and leave the
253236769Sobrien     * basename as given to us.
254236769Sobrien     */
255236769Sobrien    if ((cp = strrchr(tname, '/'))) {
256236769Sobrien	if (realpath(tname, buf)) {
257236769Sobrien	    if ((rp = strrchr(buf, '/'))) {
258236769Sobrien		rp++;
259236769Sobrien		cp++;
260236769Sobrien		if (strcmp(cp, rp) != 0)
261236769Sobrien		    strlcpy(rp, cp, sizeof(buf) - (rp - buf));
262236769Sobrien	    }
263236769Sobrien	    tname = buf;
264236769Sobrien	} else {
265236769Sobrien	    /*
266236769Sobrien	     * We likely have a directory which is about to be made.
267236769Sobrien	     * We pretend realpath() succeeded, to have a chance
268236769Sobrien	     * of generating the same meta file name that we will
269236769Sobrien	     * next time through.
270236769Sobrien	     */
271236769Sobrien	    if (tname[0] == '/') {
272236769Sobrien		strlcpy(buf, tname, sizeof(buf));
273236769Sobrien	    } else {
274236769Sobrien		snprintf(buf, sizeof(buf), "%s/%s", cwd, tname);
275236769Sobrien	    }
276236769Sobrien	    eat_dots(buf, sizeof(buf), 1);	/* ./ */
277236769Sobrien	    eat_dots(buf, sizeof(buf), 2);	/* ../ */
278236769Sobrien	    tname = buf;
279236769Sobrien	}
280236769Sobrien    }
281236769Sobrien    /* on some systems dirname may modify its arg */
282236769Sobrien    tp = bmake_strdup(tname);
283236769Sobrien    if (strcmp(dname, dirname(tp)) == 0)
284236769Sobrien	snprintf(mname, mnamelen, "%s.meta", tname);
285236769Sobrien    else {
286236769Sobrien	snprintf(mname, mnamelen, "%s/%s.meta", dname, tname);
287236769Sobrien
288236769Sobrien	/*
289236769Sobrien	 * Replace path separators in the file name after the
290236769Sobrien	 * current object directory path.
291236769Sobrien	 */
292236769Sobrien	cp = mname + strlen(dname) + 1;
293236769Sobrien
294236769Sobrien	while (*cp != '\0') {
295236769Sobrien	    if (*cp == '/')
296236769Sobrien		*cp = '_';
297236769Sobrien	    cp++;
298236769Sobrien	}
299236769Sobrien    }
300236769Sobrien    free(tp);
301236769Sobrien    for (i--; i >= 0; i--) {
302236769Sobrien	if (p[i])
303236769Sobrien	    free(p[i]);
304236769Sobrien    }
305236769Sobrien    return (mname);
306236769Sobrien}
307236769Sobrien
308236769Sobrien/*
309236769Sobrien * Return true if running ${.MAKE}
310236769Sobrien * Bypassed if target is flagged .MAKE
311236769Sobrien */
312236769Sobrienstatic int
313236769Sobrienis_submake(void *cmdp, void *gnp)
314236769Sobrien{
315236769Sobrien    static char *p_make = NULL;
316236769Sobrien    static int p_len;
317236769Sobrien    char  *cmd = cmdp;
318236769Sobrien    GNode *gn = gnp;
319236769Sobrien    char *mp = NULL;
320236769Sobrien    char *cp;
321236769Sobrien    char *cp2;
322236769Sobrien    int rc = 0;				/* keep looking */
323236769Sobrien
324236769Sobrien    if (!p_make) {
325236769Sobrien	p_make = Var_Value(".MAKE", gn, &cp);
326236769Sobrien	p_len = strlen(p_make);
327236769Sobrien    }
328236769Sobrien    cp = strchr(cmd, '$');
329236769Sobrien    if ((cp)) {
330236769Sobrien	mp = Var_Subst(NULL, cmd, gn, FALSE);
331236769Sobrien	cmd = mp;
332236769Sobrien    }
333236769Sobrien    cp2 = strstr(cmd, p_make);
334236769Sobrien    if ((cp2)) {
335236769Sobrien	switch (cp2[p_len]) {
336236769Sobrien	case '\0':
337236769Sobrien	case ' ':
338236769Sobrien	case '\t':
339236769Sobrien	case '\n':
340236769Sobrien	    rc = 1;
341236769Sobrien	    break;
342236769Sobrien	}
343236769Sobrien	if (cp2 > cmd && rc > 0) {
344236769Sobrien	    switch (cp2[-1]) {
345236769Sobrien	    case ' ':
346236769Sobrien	    case '\t':
347236769Sobrien	    case '\n':
348236769Sobrien		break;
349236769Sobrien	    default:
350236769Sobrien		rc = 0;			/* no match */
351236769Sobrien		break;
352236769Sobrien	    }
353236769Sobrien	}
354236769Sobrien    }
355236769Sobrien    if (mp)
356236769Sobrien	free(mp);
357236769Sobrien    return (rc);
358236769Sobrien}
359236769Sobrien
360236769Sobrientypedef struct meta_file_s {
361236769Sobrien    FILE *fp;
362236769Sobrien    GNode *gn;
363236769Sobrien} meta_file_t;
364236769Sobrien
365236769Sobrienstatic int
366236769SobrienprintCMD(void *cmdp, void *mfpp)
367236769Sobrien{
368236769Sobrien    meta_file_t *mfp = mfpp;
369236769Sobrien    char *cmd = cmdp;
370236769Sobrien    char *cp = NULL;
371236769Sobrien
372236769Sobrien    if (strchr(cmd, '$')) {
373236769Sobrien	cmd = cp = Var_Subst(NULL, cmd, mfp->gn, FALSE);
374236769Sobrien    }
375236769Sobrien    fprintf(mfp->fp, "CMD %s\n", cmd);
376236769Sobrien    if (cp)
377236769Sobrien	free(cp);
378236769Sobrien    return 0;
379236769Sobrien}
380236769Sobrien
381236769Sobrien/*
382236769Sobrien * Certain node types never get a .meta file
383236769Sobrien */
384236769Sobrien#define SKIP_META_TYPE(_type) do { \
385236769Sobrien    if ((gn->type & __CONCAT(OP_, _type))) {	\
386236769Sobrien	if (DEBUG(META)) { \
387236769Sobrien	    fprintf(debug_file, "Skipping meta for %s: .%s\n", \
388236769Sobrien		    gn->name, __STRING(_type));		       \
389236769Sobrien	} \
390236769Sobrien	return (NULL); \
391236769Sobrien    } \
392236769Sobrien} while (0)
393236769Sobrien
394236769Sobrienstatic FILE *
395236769Sobrienmeta_create(BuildMon *pbm, GNode *gn)
396236769Sobrien{
397236769Sobrien    meta_file_t mf;
398236769Sobrien    char buf[MAXPATHLEN];
399236769Sobrien    char objdir[MAXPATHLEN];
400236769Sobrien    char **ptr;
401236769Sobrien    const char *dname;
402236769Sobrien    const char *tname;
403236769Sobrien    char *fname;
404236769Sobrien    const char *cp;
405236769Sobrien    char *p[4];				/* >= possible uses */
406236769Sobrien    int i;
407236769Sobrien    struct stat fs;
408236769Sobrien
409236769Sobrien
410236769Sobrien    /* This may be a phony node which we don't want meta data for... */
411236769Sobrien    /* Skip .meta for .BEGIN, .END, .ERROR etc as well. */
412236769Sobrien    /* Or it may be explicitly flagged as .NOMETA */
413236769Sobrien    SKIP_META_TYPE(NOMETA);
414236769Sobrien    /* Unless it is explicitly flagged as .META */
415236769Sobrien    if (!(gn->type & OP_META)) {
416236769Sobrien	SKIP_META_TYPE(PHONY);
417236769Sobrien	SKIP_META_TYPE(SPECIAL);
418236769Sobrien	SKIP_META_TYPE(MAKE);
419236769Sobrien    }
420236769Sobrien
421236769Sobrien    mf.fp = NULL;
422236769Sobrien
423236769Sobrien    i = 0;
424236769Sobrien
425236769Sobrien    dname = Var_Value(".OBJDIR", gn, &p[i++]);
426236769Sobrien    tname = Var_Value(TARGET, gn, &p[i++]);
427236769Sobrien
428236769Sobrien    /* The object directory may not exist. Check it.. */
429236769Sobrien    if (stat(dname, &fs) != 0) {
430236769Sobrien	if (DEBUG(META))
431236769Sobrien	    fprintf(debug_file, "Skipping meta for %s: no .OBJDIR\n",
432236769Sobrien		    gn->name);
433236769Sobrien	goto out;
434236769Sobrien    }
435236769Sobrien    /* Check if there are no commands to execute. */
436236769Sobrien    if (Lst_IsEmpty(gn->commands)) {
437236769Sobrien	if (DEBUG(META))
438236769Sobrien	    fprintf(debug_file, "Skipping meta for %s: no commands\n",
439236769Sobrien		    gn->name);
440236769Sobrien	goto out;
441236769Sobrien    }
442236769Sobrien
443236769Sobrien    /* make sure these are canonical */
444236769Sobrien    if (realpath(dname, objdir))
445236769Sobrien	dname = objdir;
446236769Sobrien
447236769Sobrien    /* If we aren't in the object directory, don't create a meta file. */
448236769Sobrien    if (!metaCurdirOk && strcmp(curdir, dname) == 0) {
449236769Sobrien	if (DEBUG(META))
450236769Sobrien	    fprintf(debug_file, "Skipping meta for %s: .OBJDIR == .CURDIR\n",
451236769Sobrien		    gn->name);
452236769Sobrien	goto out;
453236769Sobrien    }
454236769Sobrien    if (!(gn->type & OP_META)) {
455236769Sobrien	/* We do not generate .meta files for sub-makes */
456236769Sobrien	if (Lst_ForEach(gn->commands, is_submake, gn)) {
457236769Sobrien	    if (DEBUG(META))
458236769Sobrien		fprintf(debug_file, "Skipping meta for %s: .MAKE\n",
459236769Sobrien			gn->name);
460236769Sobrien	    goto out;
461236769Sobrien	}
462236769Sobrien    }
463236769Sobrien
464236769Sobrien    if (metaVerbose) {
465236769Sobrien	char *mp;
466236769Sobrien
467236769Sobrien	/* Describe the target we are building */
468236769Sobrien	mp = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", gn, 0);
469236769Sobrien	if (*mp)
470236769Sobrien	    fprintf(stdout, "%s\n", mp);
471236769Sobrien	free(mp);
472236769Sobrien    }
473236769Sobrien    /* Get the basename of the target */
474236769Sobrien    if ((cp = strrchr(tname, '/')) == NULL) {
475236769Sobrien	cp = tname;
476236769Sobrien    } else {
477236769Sobrien	cp++;
478236769Sobrien    }
479236769Sobrien
480236769Sobrien    fflush(stdout);
481236769Sobrien
482236769Sobrien    if (strcmp(cp, makeDependfile) == 0)
483236769Sobrien	goto out;
484236769Sobrien
485236769Sobrien    if (!writeMeta)
486236769Sobrien	/* Don't create meta data. */
487236769Sobrien	goto out;
488236769Sobrien
489236769Sobrien    fname = meta_name(gn, pbm->meta_fname, sizeof(pbm->meta_fname),
490236769Sobrien		      dname, tname);
491236769Sobrien
492236769Sobrien#ifdef DEBUG_META_MODE
493236769Sobrien    if (DEBUG(META))
494236769Sobrien	fprintf(debug_file, "meta_create: %s\n", fname);
495236769Sobrien#endif
496236769Sobrien
497236769Sobrien    if ((mf.fp = fopen(fname, "w")) == NULL)
498236769Sobrien	err(1, "Could not open meta file '%s'", fname);
499236769Sobrien
500236769Sobrien    fprintf(mf.fp, "# Meta data file %s\n", fname);
501236769Sobrien
502236769Sobrien    mf.gn = gn;
503236769Sobrien
504236769Sobrien    Lst_ForEach(gn->commands, printCMD, &mf);
505236769Sobrien
506236769Sobrien    fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf)));
507236769Sobrien    fprintf(mf.fp, "TARGET %s\n", tname);
508236769Sobrien
509236769Sobrien    if (metaEnv) {
510236769Sobrien	for (ptr = environ; *ptr != NULL; ptr++)
511236769Sobrien	    fprintf(mf.fp, "ENV %s\n", *ptr);
512236769Sobrien    }
513236769Sobrien
514236769Sobrien    fprintf(mf.fp, "-- command output --\n");
515236769Sobrien    fflush(mf.fp);
516236769Sobrien
517236769Sobrien    Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
518236769Sobrien    Var_Append(".MAKE.META.CREATED", fname, VAR_GLOBAL);
519236769Sobrien
520236769Sobrien    gn->type |= OP_META;		/* in case anyone wants to know */
521236769Sobrien    if (metaSilent) {
522236769Sobrien	    gn->type |= OP_SILENT;
523236769Sobrien    }
524236769Sobrien out:
525236769Sobrien    for (i--; i >= 0; i--) {
526236769Sobrien	if (p[i])
527236769Sobrien	    free(p[i]);
528236769Sobrien    }
529236769Sobrien
530236769Sobrien    return (mf.fp);
531236769Sobrien}
532236769Sobrien
533236769Sobrienstatic Boolean
534236769SobrienboolValue(char *s)
535236769Sobrien{
536236769Sobrien    switch(*s) {
537236769Sobrien    case '0':
538236769Sobrien    case 'N':
539236769Sobrien    case 'n':
540236769Sobrien    case 'F':
541236769Sobrien    case 'f':
542236769Sobrien	return FALSE;
543236769Sobrien    }
544236769Sobrien    return TRUE;
545236769Sobrien}
546236769Sobrien
547249033Ssjg/*
548249033Ssjg * Initialization we need before reading makefiles.
549249033Ssjg */
550236769Sobrienvoid
551250837Ssjgmeta_init(void)
552236769Sobrien{
553249033Ssjg#ifdef USE_FILEMON
554249033Ssjg	/* this allows makefiles to test if we have filemon support */
555249033Ssjg	Var_Set(".MAKE.PATH_FILEMON", _PATH_FILEMON, VAR_GLOBAL, 0);
556249033Ssjg#endif
557249033Ssjg}
558249033Ssjg
559249033Ssjg
560249033Ssjg/*
561249033Ssjg * Initialization we need after reading makefiles.
562249033Ssjg */
563249033Ssjgvoid
564249033Ssjgmeta_mode_init(const char *make_mode)
565249033Ssjg{
566236769Sobrien    static int once = 0;
567236769Sobrien    char *cp;
568236769Sobrien
569236769Sobrien    useMeta = TRUE;
570236769Sobrien    useFilemon = TRUE;
571236769Sobrien    writeMeta = TRUE;
572236769Sobrien
573236769Sobrien    if (make_mode) {
574236769Sobrien	if (strstr(make_mode, "env"))
575236769Sobrien	    metaEnv = TRUE;
576236769Sobrien	if (strstr(make_mode, "verb"))
577236769Sobrien	    metaVerbose = TRUE;
578236769Sobrien	if (strstr(make_mode, "read"))
579236769Sobrien	    writeMeta = FALSE;
580236769Sobrien	if (strstr(make_mode, "nofilemon"))
581236769Sobrien	    useFilemon = FALSE;
582236769Sobrien	if ((cp = strstr(make_mode, "curdirok="))) {
583236769Sobrien	    metaCurdirOk = boolValue(&cp[9]);
584236769Sobrien	}
585236769Sobrien	if ((cp = strstr(make_mode, "silent="))) {
586236769Sobrien	    metaSilent = boolValue(&cp[7]);
587236769Sobrien	}
588236769Sobrien	if (strstr(make_mode, "ignore-cmd"))
589236769Sobrien	    metaIgnoreCMDs = TRUE;
590236769Sobrien	/* for backwards compatability */
591236769Sobrien	Var_Set(".MAKE.META_CREATED", "${.MAKE.META.CREATED}", VAR_GLOBAL, 0);
592236769Sobrien	Var_Set(".MAKE.META_FILES", "${.MAKE.META.FILES}", VAR_GLOBAL, 0);
593236769Sobrien    }
594236769Sobrien    if (metaVerbose && !Var_Exists(MAKE_META_PREFIX, VAR_GLOBAL)) {
595236769Sobrien	/*
596236769Sobrien	 * The default value for MAKE_META_PREFIX
597236769Sobrien	 * prints the absolute path of the target.
598236769Sobrien	 * This works be cause :H will generate '.' if there is no /
599236769Sobrien	 * and :tA will resolve that to cwd.
600236769Sobrien	 */
601236769Sobrien	Var_Set(MAKE_META_PREFIX, "Building ${.TARGET:H:tA}/${.TARGET:T}", VAR_GLOBAL, 0);
602236769Sobrien    }
603236769Sobrien    if (once)
604236769Sobrien	return;
605236769Sobrien    once = 1;
606236769Sobrien    memset(&Mybm, 0, sizeof(Mybm));
607236769Sobrien    /*
608236769Sobrien     * We consider ourselves master of all within ${.MAKE.META.BAILIWICK}
609236769Sobrien     */
610236769Sobrien    metaBailiwick = Lst_Init(FALSE);
611236769Sobrien    cp = Var_Subst(NULL, "${.MAKE.META.BAILIWICK:O:u:tA}", VAR_GLOBAL, 0);
612236769Sobrien    if (cp) {
613236769Sobrien	str2Lst_Append(metaBailiwick, cp, NULL);
614236769Sobrien    }
615253883Ssjg    /*
616253883Ssjg     * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS}
617253883Ssjg     */
618253883Ssjg    metaIgnorePaths = Lst_Init(FALSE);
619253883Ssjg    Var_Append(MAKE_META_IGNORE_PATHS,
620253883Ssjg	       "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}", VAR_GLOBAL);
621253883Ssjg    cp = Var_Subst(NULL,
622253883Ssjg		   "${" MAKE_META_IGNORE_PATHS ":O:u:tA}", VAR_GLOBAL, 0);
623253883Ssjg    if (cp) {
624253883Ssjg	str2Lst_Append(metaIgnorePaths, cp, NULL);
625253883Ssjg    }
626236769Sobrien}
627236769Sobrien
628236769Sobrien/*
629236769Sobrien * In each case below we allow for job==NULL
630236769Sobrien */
631236769Sobrienvoid
632236769Sobrienmeta_job_start(Job *job, GNode *gn)
633236769Sobrien{
634236769Sobrien    BuildMon *pbm;
635236769Sobrien
636236769Sobrien    if (job != NULL) {
637236769Sobrien	pbm = &job->bm;
638236769Sobrien    } else {
639236769Sobrien	pbm = &Mybm;
640236769Sobrien    }
641236769Sobrien    pbm->mfp = meta_create(pbm, gn);
642236769Sobrien#ifdef USE_FILEMON_ONCE
643236769Sobrien    /* compat mode we open the filemon dev once per command */
644236769Sobrien    if (job == NULL)
645236769Sobrien	return;
646236769Sobrien#endif
647236769Sobrien#ifdef USE_FILEMON
648236769Sobrien    if (pbm->mfp != NULL && useFilemon) {
649236769Sobrien	filemon_open(pbm);
650236769Sobrien    } else {
651236769Sobrien	pbm->mon_fd = pbm->filemon_fd = -1;
652236769Sobrien    }
653236769Sobrien#endif
654236769Sobrien}
655236769Sobrien
656236769Sobrien/*
657236769Sobrien * The child calls this before doing anything.
658236769Sobrien * It does not disturb our state.
659236769Sobrien */
660236769Sobrienvoid
661236769Sobrienmeta_job_child(Job *job)
662236769Sobrien{
663236769Sobrien#ifdef USE_FILEMON
664236769Sobrien    BuildMon *pbm;
665236769Sobrien    pid_t pid;
666236769Sobrien
667236769Sobrien    if (job != NULL) {
668236769Sobrien	pbm = &job->bm;
669236769Sobrien    } else {
670236769Sobrien	pbm = &Mybm;
671236769Sobrien    }
672236769Sobrien    pid = getpid();
673236769Sobrien    if (pbm->mfp != NULL && useFilemon) {
674236769Sobrien	if (ioctl(pbm->filemon_fd, FILEMON_SET_PID, &pid) < 0) {
675236769Sobrien	    err(1, "Could not set filemon pid!");
676236769Sobrien	}
677236769Sobrien    }
678236769Sobrien#endif
679236769Sobrien}
680236769Sobrien
681236769Sobrienvoid
682236769Sobrienmeta_job_error(Job *job, GNode *gn, int flags, int status)
683236769Sobrien{
684236769Sobrien    char cwd[MAXPATHLEN];
685236769Sobrien    BuildMon *pbm;
686236769Sobrien
687236769Sobrien    if (job != NULL) {
688236769Sobrien	pbm = &job->bm;
689236769Sobrien    } else {
690236769Sobrien	if (!gn)
691236769Sobrien	    gn = job->node;
692236769Sobrien	pbm = &Mybm;
693236769Sobrien    }
694236769Sobrien    if (pbm->mfp != NULL) {
695236769Sobrien	fprintf(pbm->mfp, "*** Error code %d%s\n",
696236769Sobrien		status,
697236769Sobrien		(flags & JOB_IGNERR) ?
698236769Sobrien		"(ignored)" : "");
699236769Sobrien    }
700236769Sobrien    if (gn) {
701236769Sobrien	Var_Set(".ERROR_TARGET", gn->path ? gn->path : gn->name, VAR_GLOBAL, 0);
702236769Sobrien    }
703236769Sobrien    getcwd(cwd, sizeof(cwd));
704236769Sobrien    Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL, 0);
705236769Sobrien    if (pbm && pbm->meta_fname[0]) {
706236769Sobrien	Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL, 0);
707236769Sobrien    }
708236769Sobrien    meta_job_finish(job);
709236769Sobrien}
710236769Sobrien
711236769Sobrienvoid
712236769Sobrienmeta_job_output(Job *job, char *cp, const char *nl)
713236769Sobrien{
714236769Sobrien    BuildMon *pbm;
715236769Sobrien
716236769Sobrien    if (job != NULL) {
717236769Sobrien	pbm = &job->bm;
718236769Sobrien    } else {
719236769Sobrien	pbm = &Mybm;
720236769Sobrien    }
721236769Sobrien    if (pbm->mfp != NULL) {
722236769Sobrien	if (metaVerbose) {
723236769Sobrien	    static char *meta_prefix = NULL;
724236769Sobrien	    static int meta_prefix_len;
725236769Sobrien
726236769Sobrien	    if (!meta_prefix) {
727236769Sobrien		char *cp2;
728236769Sobrien
729236769Sobrien		meta_prefix = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", VAR_GLOBAL, 0);
730236769Sobrien		if ((cp2 = strchr(meta_prefix, '$')))
731236769Sobrien		    meta_prefix_len = cp2 - meta_prefix;
732236769Sobrien		else
733236769Sobrien		    meta_prefix_len = strlen(meta_prefix);
734236769Sobrien	    }
735236769Sobrien	    if (strncmp(cp, meta_prefix, meta_prefix_len) == 0) {
736236769Sobrien		cp = strchr(cp+1, '\n');
737236769Sobrien		if (!cp++)
738236769Sobrien		    return;
739236769Sobrien	    }
740236769Sobrien	}
741236769Sobrien	fprintf(pbm->mfp, "%s%s", cp, nl);
742236769Sobrien    }
743236769Sobrien}
744236769Sobrien
745236769Sobrienvoid
746236769Sobrienmeta_cmd_finish(void *pbmp)
747236769Sobrien{
748236769Sobrien#ifdef USE_FILEMON
749236769Sobrien    BuildMon *pbm = pbmp;
750236769Sobrien
751236769Sobrien    if (!pbm)
752236769Sobrien	pbm = &Mybm;
753236769Sobrien
754236769Sobrien    if (pbm->filemon_fd >= 0) {
755236769Sobrien	close(pbm->filemon_fd);
756236769Sobrien	filemon_read(pbm->mfp, pbm->mon_fd);
757236769Sobrien	pbm->filemon_fd = pbm->mon_fd = -1;
758236769Sobrien    }
759236769Sobrien#endif
760236769Sobrien}
761236769Sobrien
762236769Sobrienvoid
763236769Sobrienmeta_job_finish(Job *job)
764236769Sobrien{
765236769Sobrien    BuildMon *pbm;
766236769Sobrien
767236769Sobrien    if (job != NULL) {
768236769Sobrien	pbm = &job->bm;
769236769Sobrien    } else {
770236769Sobrien	pbm = &Mybm;
771236769Sobrien    }
772236769Sobrien    if (pbm->mfp != NULL) {
773236769Sobrien	meta_cmd_finish(pbm);
774236769Sobrien	fclose(pbm->mfp);
775236769Sobrien	pbm->mfp = NULL;
776236769Sobrien	pbm->meta_fname[0] = '\0';
777236769Sobrien    }
778236769Sobrien}
779236769Sobrien
780236769Sobrien/*
781236769Sobrien * Fetch a full line from fp - growing bufp if needed
782236769Sobrien * Return length in bufp.
783236769Sobrien */
784236769Sobrienstatic int
785236769SobrienfgetLine(char **bufp, size_t *szp, int o, FILE *fp)
786236769Sobrien{
787236769Sobrien    char *buf = *bufp;
788236769Sobrien    size_t bufsz = *szp;
789236769Sobrien    struct stat fs;
790236769Sobrien    int x;
791236769Sobrien
792236769Sobrien    if (fgets(&buf[o], bufsz - o, fp) != NULL) {
793236769Sobrien    check_newline:
794236769Sobrien	x = o + strlen(&buf[o]);
795236769Sobrien	if (buf[x - 1] == '\n')
796236769Sobrien	    return x;
797236769Sobrien	/*
798236769Sobrien	 * We need to grow the buffer.
799236769Sobrien	 * The meta file can give us a clue.
800236769Sobrien	 */
801236769Sobrien	if (fstat(fileno(fp), &fs) == 0) {
802236769Sobrien	    size_t newsz;
803236769Sobrien	    char *p;
804236769Sobrien
805236769Sobrien	    newsz = ROUNDUP((fs.st_size / 2), BUFSIZ);
806236769Sobrien	    if (newsz <= bufsz)
807236769Sobrien		newsz = ROUNDUP(fs.st_size, BUFSIZ);
808236769Sobrien	    if (DEBUG(META))
809236769Sobrien		fprintf(debug_file, "growing buffer %u -> %u\n",
810236769Sobrien			(unsigned)bufsz, (unsigned)newsz);
811236769Sobrien	    p = bmake_realloc(buf, newsz);
812236769Sobrien	    if (p) {
813236769Sobrien		*bufp = buf = p;
814236769Sobrien		*szp = bufsz = newsz;
815236769Sobrien		/* fetch the rest */
816236769Sobrien		if (!fgets(&buf[x], bufsz - x, fp))
817236769Sobrien		    return x;		/* truncated! */
818236769Sobrien		goto check_newline;
819236769Sobrien	    }
820236769Sobrien	}
821236769Sobrien    }
822236769Sobrien    return 0;
823236769Sobrien}
824236769Sobrien
825236769Sobrienstatic int
826236769Sobrienprefix_match(void *p, void *q)
827236769Sobrien{
828236769Sobrien    const char *prefix = p;
829236769Sobrien    const char *path = q;
830236769Sobrien    size_t n = strlen(prefix);
831236769Sobrien
832236769Sobrien    return (0 == strncmp(path, prefix, n));
833236769Sobrien}
834236769Sobrien
835236769Sobrienstatic int
836236769Sobrienstring_match(const void *p, const void *q)
837236769Sobrien{
838236769Sobrien    const char *p1 = p;
839236769Sobrien    const char *p2 = q;
840236769Sobrien
841236769Sobrien    return strcmp(p1, p2);
842236769Sobrien}
843236769Sobrien
844236769Sobrien
845236769Sobrien/*
846236769Sobrien * When running with 'meta' functionality, a target can be out-of-date
847236769Sobrien * if any of the references in it's meta data file is more recent.
848236769Sobrien * We have to track the latestdir on a per-process basis.
849236769Sobrien */
850236769Sobrien#define LDIR_VNAME_FMT ".meta.%d.ldir"
851236769Sobrien
852236769Sobrien/*
853236769Sobrien * It is possible that a .meta file is corrupted,
854236769Sobrien * if we detect this we want to reproduce it.
855236769Sobrien * Setting oodate TRUE will have that effect.
856236769Sobrien */
857236769Sobrien#define CHECK_VALID_META(p) if (!(p && *p)) { \
858236769Sobrien    warnx("%s: %d: malformed", fname, lineno); \
859236769Sobrien    oodate = TRUE; \
860236769Sobrien    continue; \
861236769Sobrien    }
862236769Sobrien
863236769SobrienBoolean
864236769Sobrienmeta_oodate(GNode *gn, Boolean oodate)
865236769Sobrien{
866236769Sobrien    static char *tmpdir = NULL;
867236769Sobrien    static char cwd[MAXPATHLEN];
868236769Sobrien    char ldir_vname[64];
869236769Sobrien    char latestdir[MAXPATHLEN];
870236769Sobrien    char fname[MAXPATHLEN];
871236769Sobrien    char fname1[MAXPATHLEN];
872236769Sobrien    char fname2[MAXPATHLEN];
873236769Sobrien    char *p;
874236769Sobrien    char *cp;
875236769Sobrien    static size_t cwdlen = 0;
876236769Sobrien    static size_t tmplen = 0;
877236769Sobrien    FILE *fp;
878246223Ssjg    Boolean needOODATE = FALSE;
879236769Sobrien    Lst missingFiles;
880236769Sobrien
881236769Sobrien    if (oodate)
882236769Sobrien	return oodate;		/* we're done */
883236769Sobrien
884236769Sobrien    missingFiles = Lst_Init(FALSE);
885236769Sobrien
886236769Sobrien    /*
887236769Sobrien     * We need to check if the target is out-of-date. This includes
888236769Sobrien     * checking if the expanded command has changed. This in turn
889236769Sobrien     * requires that all variables are set in the same way that they
890236769Sobrien     * would be if the target needs to be re-built.
891236769Sobrien     */
892236769Sobrien    Make_DoAllVar(gn);
893236769Sobrien
894236769Sobrien    meta_name(gn, fname, sizeof(fname), NULL, NULL);
895236769Sobrien
896236769Sobrien#ifdef DEBUG_META_MODE
897236769Sobrien    if (DEBUG(META))
898236769Sobrien	fprintf(debug_file, "meta_oodate: %s\n", fname);
899236769Sobrien#endif
900236769Sobrien
901236769Sobrien    if ((fp = fopen(fname, "r")) != NULL) {
902236769Sobrien	static char *buf = NULL;
903236769Sobrien	static size_t bufsz;
904236769Sobrien	int lineno = 0;
905236769Sobrien	int lastpid = 0;
906236769Sobrien	int pid;
907236769Sobrien	int f = 0;
908236769Sobrien	int x;
909236769Sobrien	LstNode ln;
910236769Sobrien	struct stat fs;
911236769Sobrien
912236769Sobrien	if (!buf) {
913236769Sobrien	    bufsz = 8 * BUFSIZ;
914236769Sobrien	    buf = bmake_malloc(bufsz);
915236769Sobrien	}
916236769Sobrien
917236769Sobrien	if (!cwdlen) {
918236769Sobrien	    if (getcwd(cwd, sizeof(cwd)) == NULL)
919236769Sobrien		err(1, "Could not get current working directory");
920236769Sobrien	    cwdlen = strlen(cwd);
921236769Sobrien	}
922236769Sobrien
923236769Sobrien	if (!tmpdir) {
924236769Sobrien	    tmpdir = getTmpdir();
925236769Sobrien	    tmplen = strlen(tmpdir);
926236769Sobrien	}
927236769Sobrien
928236769Sobrien	/* we want to track all the .meta we read */
929236769Sobrien	Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
930236769Sobrien
931236769Sobrien	ln = Lst_First(gn->commands);
932236769Sobrien	while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) {
933236769Sobrien	    lineno++;
934236769Sobrien	    if (buf[x - 1] == '\n')
935236769Sobrien		buf[x - 1] = '\0';
936236769Sobrien	    else {
937236769Sobrien		warnx("%s: %d: line truncated at %u", fname, lineno, x);
938236769Sobrien		oodate = TRUE;
939236769Sobrien		break;
940236769Sobrien	    }
941236769Sobrien	    /* Find the start of the build monitor section. */
942236769Sobrien	    if (!f) {
943236769Sobrien		if (strncmp(buf, "-- filemon", 10) == 0) {
944236769Sobrien		    f = 1;
945236769Sobrien		    continue;
946236769Sobrien		}
947236769Sobrien		if (strncmp(buf, "# buildmon", 10) == 0) {
948236769Sobrien		    f = 1;
949236769Sobrien		    continue;
950236769Sobrien		}
951236769Sobrien	    }
952236769Sobrien
953236769Sobrien	    /* Delimit the record type. */
954236769Sobrien	    p = buf;
955236769Sobrien#ifdef DEBUG_META_MODE
956236769Sobrien	    if (DEBUG(META))
957236769Sobrien		fprintf(debug_file, "%s: %d: %s\n", fname, lineno, buf);
958236769Sobrien#endif
959236769Sobrien	    strsep(&p, " ");
960236769Sobrien	    if (f) {
961236769Sobrien		/*
962236769Sobrien		 * We are in the 'filemon' output section.
963236769Sobrien		 * Each record from filemon follows the general form:
964236769Sobrien		 *
965236769Sobrien		 * <key> <pid> <data>
966236769Sobrien		 *
967236769Sobrien		 * Where:
968236769Sobrien		 * <key> is a single letter, denoting the syscall.
969236769Sobrien		 * <pid> is the process that made the syscall.
970236769Sobrien		 * <data> is the arguments (of interest).
971236769Sobrien		 */
972236769Sobrien		switch(buf[0]) {
973236769Sobrien		case '#':		/* comment */
974236769Sobrien		case 'V':		/* version */
975236769Sobrien		    break;
976236769Sobrien		default:
977236769Sobrien		    /*
978236769Sobrien		     * We need to track pathnames per-process.
979236769Sobrien		     *
980236769Sobrien		     * Each process run by make, starts off in the 'CWD'
981236769Sobrien		     * recorded in the .meta file, if it chdirs ('C')
982236769Sobrien		     * elsewhere we need to track that - but only for
983236769Sobrien		     * that process.  If it forks ('F'), we initialize
984236769Sobrien		     * the child to have the same cwd as its parent.
985236769Sobrien		     *
986236769Sobrien		     * We also need to track the 'latestdir' of
987236769Sobrien		     * interest.  This is usually the same as cwd, but
988236769Sobrien		     * not if a process is reading directories.
989236769Sobrien		     *
990236769Sobrien		     * Each time we spot a different process ('pid')
991236769Sobrien		     * we save the current value of 'latestdir' in a
992236769Sobrien		     * variable qualified by 'lastpid', and
993236769Sobrien		     * re-initialize 'latestdir' to any pre-saved
994236769Sobrien		     * value for the current 'pid' and 'CWD' if none.
995236769Sobrien		     */
996236769Sobrien		    CHECK_VALID_META(p);
997236769Sobrien		    pid = atoi(p);
998236769Sobrien		    if (pid > 0 && pid != lastpid) {
999236769Sobrien			char *ldir;
1000236769Sobrien			char *tp;
1001236769Sobrien
1002236769Sobrien			if (lastpid > 0) {
1003236769Sobrien			    /* We need to remember this. */
1004236769Sobrien			    Var_Set(ldir_vname, latestdir, VAR_GLOBAL, 0);
1005236769Sobrien			}
1006236769Sobrien			snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid);
1007236769Sobrien			lastpid = pid;
1008236769Sobrien			ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp);
1009236769Sobrien			if (ldir) {
1010236769Sobrien			    strlcpy(latestdir, ldir, sizeof(latestdir));
1011236769Sobrien			    if (tp)
1012236769Sobrien				free(tp);
1013236769Sobrien			} else
1014236769Sobrien			    strlcpy(latestdir, cwd, sizeof(latestdir));
1015236769Sobrien		    }
1016236769Sobrien		    /* Skip past the pid. */
1017236769Sobrien		    if (strsep(&p, " ") == NULL)
1018236769Sobrien			continue;
1019236769Sobrien#ifdef DEBUG_META_MODE
1020236769Sobrien		    if (DEBUG(META))
1021236769Sobrien			fprintf(debug_file, "%s: %d: cwd=%s ldir=%s\n", fname, lineno, cwd, latestdir);
1022236769Sobrien#endif
1023236769Sobrien		    break;
1024236769Sobrien		}
1025236769Sobrien
1026236769Sobrien		CHECK_VALID_META(p);
1027236769Sobrien
1028236769Sobrien		/* Process according to record type. */
1029236769Sobrien		switch (buf[0]) {
1030236769Sobrien		case 'X':		/* eXit */
1031236769Sobrien		    Var_Delete(ldir_vname, VAR_GLOBAL);
1032236769Sobrien		    lastpid = 0;	/* no need to save ldir_vname */
1033236769Sobrien		    break;
1034236769Sobrien
1035236769Sobrien		case 'F':		/* [v]Fork */
1036236769Sobrien		    {
1037236769Sobrien			char cldir[64];
1038236769Sobrien			int child;
1039236769Sobrien
1040236769Sobrien			child = atoi(p);
1041236769Sobrien			if (child > 0) {
1042236769Sobrien			    snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child);
1043236769Sobrien			    Var_Set(cldir, latestdir, VAR_GLOBAL, 0);
1044236769Sobrien			}
1045236769Sobrien		    }
1046236769Sobrien		    break;
1047236769Sobrien
1048236769Sobrien		case 'C':		/* Chdir */
1049236769Sobrien		    /* Update the latest directory. */
1050236769Sobrien		    strlcpy(latestdir, p, sizeof(latestdir));
1051236769Sobrien		    break;
1052236769Sobrien
1053236769Sobrien		case 'M':		/* renaMe */
1054236769Sobrien		    if (Lst_IsEmpty(missingFiles))
1055236769Sobrien			break;
1056236769Sobrien		    /* 'L' and 'M' put single quotes around the args */
1057236769Sobrien		    if (*p == '\'') {
1058236769Sobrien			char *ep;
1059236769Sobrien
1060236769Sobrien			p++;
1061236769Sobrien			if ((ep = strchr(p, '\'')))
1062236769Sobrien			    *ep = '\0';
1063236769Sobrien		    }
1064236769Sobrien		    /* FALLTHROUGH */
1065236769Sobrien		case 'D':		/* unlink */
1066236769Sobrien		    if (*p == '/' && !Lst_IsEmpty(missingFiles)) {
1067236769Sobrien			/* remove p from the missingFiles list if present */
1068236769Sobrien			if ((ln = Lst_Find(missingFiles, p, string_match)) != NULL) {
1069236769Sobrien			    char *tp = Lst_Datum(ln);
1070236769Sobrien			    Lst_Remove(missingFiles, ln);
1071236769Sobrien			    free(tp);
1072249033Ssjg			    ln = NULL;	/* we're done with it */
1073236769Sobrien			}
1074236769Sobrien		    }
1075236769Sobrien		    break;
1076236769Sobrien		case 'L':		/* Link */
1077236769Sobrien		    /* we want the target */
1078236769Sobrien		    if (strsep(&p, " ") == NULL)
1079236769Sobrien			continue;
1080236769Sobrien		    CHECK_VALID_META(p);
1081236769Sobrien		    /* 'L' and 'M' put single quotes around the args */
1082236769Sobrien		    if (*p == '\'') {
1083236769Sobrien			char *ep;
1084236769Sobrien
1085236769Sobrien			p++;
1086236769Sobrien			if ((ep = strchr(p, '\'')))
1087236769Sobrien			    *ep = '\0';
1088236769Sobrien		    }
1089236769Sobrien		    /* FALLTHROUGH */
1090236769Sobrien		case 'W':		/* Write */
1091236769Sobrien		    /*
1092236769Sobrien		     * If a file we generated within our bailiwick
1093236769Sobrien		     * but outside of .OBJDIR is missing,
1094236769Sobrien		     * we need to do it again.
1095236769Sobrien		     */
1096236769Sobrien		    /* ignore non-absolute paths */
1097236769Sobrien		    if (*p != '/')
1098236769Sobrien			break;
1099236769Sobrien
1100236769Sobrien		    if (Lst_IsEmpty(metaBailiwick))
1101236769Sobrien			break;
1102236769Sobrien
1103236769Sobrien		    /* ignore cwd - normal dependencies handle those */
1104236769Sobrien		    if (strncmp(p, cwd, cwdlen) == 0)
1105236769Sobrien			break;
1106236769Sobrien
1107236769Sobrien		    if (!Lst_ForEach(metaBailiwick, prefix_match, p))
1108236769Sobrien			break;
1109236769Sobrien
1110236769Sobrien		    /* tmpdir might be within */
1111236769Sobrien		    if (tmplen > 0 && strncmp(p, tmpdir, tmplen) == 0)
1112236769Sobrien			break;
1113236769Sobrien
1114236769Sobrien		    /* ignore anything containing the string "tmp" */
1115236769Sobrien		    if ((strstr("tmp", p)))
1116236769Sobrien			break;
1117236769Sobrien
1118236769Sobrien		    if (stat(p, &fs) < 0) {
1119236769Sobrien			Lst_AtEnd(missingFiles, bmake_strdup(p));
1120236769Sobrien		    }
1121236769Sobrien		    break;
1122236769Sobrien		case 'R':		/* Read */
1123236769Sobrien		case 'E':		/* Exec */
1124236769Sobrien		    /*
1125236769Sobrien		     * Check for runtime files that can't
1126236769Sobrien		     * be part of the dependencies because
1127236769Sobrien		     * they are _expected_ to change.
1128236769Sobrien		     */
1129253883Ssjg		    if (*p == '/' &&
1130253883Ssjg			Lst_ForEach(metaIgnorePaths, prefix_match, p)) {
1131253883Ssjg#ifdef DEBUG_META_MODE
1132253883Ssjg			if (DEBUG(META))
1133253883Ssjg			    fprintf(debug_file, "meta_oodate: ignoring: %s\n",
1134253883Ssjg				    p);
1135253883Ssjg#endif
1136236769Sobrien			break;
1137253883Ssjg		    }
1138236769Sobrien
1139236769Sobrien		    if ((cp = strrchr(p, '/'))) {
1140236769Sobrien			cp++;
1141236769Sobrien			/*
1142236769Sobrien			 * We don't normally expect to see this,
1143236769Sobrien			 * but we do expect it to change.
1144236769Sobrien			 */
1145236769Sobrien			if (strcmp(cp, makeDependfile) == 0)
1146236769Sobrien			    break;
1147236769Sobrien		    }
1148236769Sobrien
1149236769Sobrien		    /*
1150236769Sobrien		     * The rest of the record is the file name.
1151236769Sobrien		     * Check if it's not an absolute path.
1152236769Sobrien		     */
1153236769Sobrien		    {
1154236769Sobrien			char *sdirs[4];
1155236769Sobrien			char **sdp;
1156236769Sobrien			int sdx = 0;
1157236769Sobrien			int found = 0;
1158236769Sobrien
1159236769Sobrien			if (*p == '/') {
1160236769Sobrien			    sdirs[sdx++] = p; /* done */
1161236769Sobrien			} else {
1162236769Sobrien			    if (strcmp(".", p) == 0)
1163236769Sobrien				continue;  /* no point */
1164236769Sobrien
1165236769Sobrien			    /* Check vs latestdir */
1166236769Sobrien			    snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p);
1167236769Sobrien			    sdirs[sdx++] = fname1;
1168236769Sobrien
1169236769Sobrien			    if (strcmp(latestdir, cwd) != 0) {
1170236769Sobrien				/* Check vs cwd */
1171236769Sobrien				snprintf(fname2, sizeof(fname2), "%s/%s", cwd, p);
1172236769Sobrien				sdirs[sdx++] = fname2;
1173236769Sobrien			    }
1174236769Sobrien			}
1175236769Sobrien			sdirs[sdx++] = NULL;
1176236769Sobrien
1177236769Sobrien			for (sdp = sdirs; *sdp && !found; sdp++) {
1178236769Sobrien#ifdef DEBUG_META_MODE
1179236769Sobrien			    if (DEBUG(META))
1180236769Sobrien				fprintf(debug_file, "%s: %d: looking for: %s\n", fname, lineno, *sdp);
1181236769Sobrien#endif
1182236769Sobrien			    if (stat(*sdp, &fs) == 0) {
1183236769Sobrien				found = 1;
1184236769Sobrien				p = *sdp;
1185236769Sobrien			    }
1186236769Sobrien			}
1187236769Sobrien			if (found) {
1188236769Sobrien#ifdef DEBUG_META_MODE
1189236769Sobrien			    if (DEBUG(META))
1190236769Sobrien				fprintf(debug_file, "%s: %d: found: %s\n", fname, lineno, p);
1191236769Sobrien#endif
1192236769Sobrien			    if (!S_ISDIR(fs.st_mode) &&
1193236769Sobrien				fs.st_mtime > gn->mtime) {
1194236769Sobrien				if (DEBUG(META))
1195236769Sobrien				    fprintf(debug_file, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p);
1196236769Sobrien				oodate = TRUE;
1197236769Sobrien			    } else if (S_ISDIR(fs.st_mode)) {
1198236769Sobrien				/* Update the latest directory. */
1199236769Sobrien				realpath(p, latestdir);
1200236769Sobrien			    }
1201236769Sobrien			} else if (errno == ENOENT && *p == '/' &&
1202236769Sobrien				   strncmp(p, cwd, cwdlen) != 0) {
1203236769Sobrien			    /*
1204236769Sobrien			     * A referenced file outside of CWD is missing.
1205236769Sobrien			     * We cannot catch every eventuality here...
1206236769Sobrien			     */
1207236769Sobrien			    if (DEBUG(META))
1208236769Sobrien				fprintf(debug_file, "%s: %d: file '%s' may have moved?...\n", fname, lineno, p);
1209236769Sobrien			    oodate = TRUE;
1210236769Sobrien			}
1211236769Sobrien		    }
1212236769Sobrien		    break;
1213236769Sobrien		default:
1214236769Sobrien		    break;
1215236769Sobrien		}
1216236769Sobrien	    } else if (strcmp(buf, "CMD") == 0) {
1217236769Sobrien		/*
1218236769Sobrien		 * Compare the current command with the one in the
1219236769Sobrien		 * meta data file.
1220236769Sobrien		 */
1221236769Sobrien		if (ln == NULL) {
1222236769Sobrien		    if (DEBUG(META))
1223236769Sobrien			fprintf(debug_file, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno);
1224236769Sobrien		    oodate = TRUE;
1225236769Sobrien		} else {
1226236769Sobrien		    char *cmd = (char *)Lst_Datum(ln);
1227249033Ssjg		    Boolean hasOODATE = FALSE;
1228236769Sobrien
1229249033Ssjg		    if (strstr(cmd, "$?"))
1230249033Ssjg			hasOODATE = TRUE;
1231249033Ssjg		    else if ((cp = strstr(cmd, ".OODATE"))) {
1232249033Ssjg			/* check for $[{(].OODATE[:)}] */
1233249033Ssjg			if (cp > cmd + 2 && cp[-2] == '$')
1234249033Ssjg			    hasOODATE = TRUE;
1235236769Sobrien		    }
1236249033Ssjg		    if (hasOODATE) {
1237249033Ssjg			needOODATE = TRUE;
1238249033Ssjg			if (DEBUG(META))
1239249033Ssjg			    fprintf(debug_file, "%s: %d: cannot compare command using .OODATE\n", fname, lineno);
1240249033Ssjg		    }
1241236769Sobrien		    cmd = Var_Subst(NULL, cmd, gn, TRUE);
1242236769Sobrien
1243236769Sobrien		    if ((cp = strchr(cmd, '\n'))) {
1244236769Sobrien			int n;
1245236769Sobrien
1246236769Sobrien			/*
1247236769Sobrien			 * This command contains newlines, we need to
1248236769Sobrien			 * fetch more from the .meta file before we
1249236769Sobrien			 * attempt a comparison.
1250236769Sobrien			 */
1251236769Sobrien			/* first put the newline back at buf[x - 1] */
1252236769Sobrien			buf[x - 1] = '\n';
1253236769Sobrien			do {
1254236769Sobrien			    /* now fetch the next line */
1255236769Sobrien			    if ((n = fgetLine(&buf, &bufsz, x, fp)) <= 0)
1256236769Sobrien				break;
1257236769Sobrien			    x = n;
1258236769Sobrien			    lineno++;
1259236769Sobrien			    if (buf[x - 1] != '\n') {
1260236769Sobrien				warnx("%s: %d: line truncated at %u", fname, lineno, x);
1261236769Sobrien				break;
1262236769Sobrien			    }
1263236769Sobrien			    cp = strchr(++cp, '\n');
1264236769Sobrien			} while (cp);
1265236769Sobrien			if (buf[x - 1] == '\n')
1266236769Sobrien			    buf[x - 1] = '\0';
1267236769Sobrien		    }
1268249033Ssjg		    if (!hasOODATE &&
1269236769Sobrien			!(gn->type & OP_NOMETA_CMP) &&
1270236769Sobrien			strcmp(p, cmd) != 0) {
1271236769Sobrien			if (DEBUG(META))
1272236769Sobrien			    fprintf(debug_file, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd);
1273236769Sobrien			if (!metaIgnoreCMDs)
1274236769Sobrien			    oodate = TRUE;
1275236769Sobrien		    }
1276236769Sobrien		    free(cmd);
1277236769Sobrien		    ln = Lst_Succ(ln);
1278236769Sobrien		}
1279236769Sobrien	    } else if (strcmp(buf, "CWD") == 0) {
1280236769Sobrien		/*
1281236769Sobrien		 * Check if there are extra commands now
1282236769Sobrien		 * that weren't in the meta data file.
1283236769Sobrien		 */
1284236769Sobrien		if (!oodate && ln != NULL) {
1285236769Sobrien		    if (DEBUG(META))
1286236769Sobrien			fprintf(debug_file, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno);
1287236769Sobrien		    oodate = TRUE;
1288236769Sobrien		}
1289236769Sobrien		if (strcmp(p, cwd) != 0) {
1290236769Sobrien		    if (DEBUG(META))
1291236769Sobrien			fprintf(debug_file, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir);
1292236769Sobrien		    oodate = TRUE;
1293236769Sobrien		}
1294236769Sobrien	    }
1295236769Sobrien	}
1296236769Sobrien
1297236769Sobrien	fclose(fp);
1298236769Sobrien	if (!Lst_IsEmpty(missingFiles)) {
1299236769Sobrien	    if (DEBUG(META))
1300236769Sobrien		fprintf(debug_file, "%s: missing files: %s...\n",
1301236769Sobrien			fname, (char *)Lst_Datum(Lst_First(missingFiles)));
1302236769Sobrien	    oodate = TRUE;
1303236769Sobrien	    Lst_Destroy(missingFiles, (FreeProc *)free);
1304236769Sobrien	}
1305236769Sobrien    } else {
1306236769Sobrien	if ((gn->type & OP_META)) {
1307236769Sobrien	    if (DEBUG(META))
1308236769Sobrien		fprintf(debug_file, "%s: required but missing\n", fname);
1309236769Sobrien	    oodate = TRUE;
1310236769Sobrien	}
1311236769Sobrien    }
1312246223Ssjg    if (oodate && needOODATE) {
1313236769Sobrien	/*
1314246223Ssjg	 * Target uses .OODATE which is empty; or we wouldn't be here.
1315246223Ssjg	 * We have decided it is oodate, so .OODATE needs to be set.
1316246223Ssjg	 * All we can sanely do is set it to .ALLSRC.
1317236769Sobrien	 */
1318236769Sobrien	Var_Delete(OODATE, gn);
1319246223Ssjg	Var_Set(OODATE, Var_Value(ALLSRC, gn, &cp), gn, 0);
1320246223Ssjg	if (cp)
1321246223Ssjg	    free(cp);
1322236769Sobrien    }
1323236769Sobrien    return oodate;
1324236769Sobrien}
1325236769Sobrien
1326236769Sobrien/* support for compat mode */
1327236769Sobrien
1328236769Sobrienstatic int childPipe[2];
1329236769Sobrien
1330236769Sobrienvoid
1331236769Sobrienmeta_compat_start(void)
1332236769Sobrien{
1333236769Sobrien#ifdef USE_FILEMON_ONCE
1334236769Sobrien    /*
1335236769Sobrien     * We need to re-open filemon for each cmd.
1336236769Sobrien     */
1337236769Sobrien    BuildMon *pbm = &Mybm;
1338236769Sobrien
1339236769Sobrien    if (pbm->mfp != NULL && useFilemon) {
1340236769Sobrien	filemon_open(pbm);
1341236769Sobrien    } else {
1342236769Sobrien	pbm->mon_fd = pbm->filemon_fd = -1;
1343236769Sobrien    }
1344236769Sobrien#endif
1345236769Sobrien    if (pipe(childPipe) < 0)
1346236769Sobrien	Punt("Cannot create pipe: %s", strerror(errno));
1347236769Sobrien    /* Set close-on-exec flag for both */
1348236769Sobrien    (void)fcntl(childPipe[0], F_SETFD, 1);
1349236769Sobrien    (void)fcntl(childPipe[1], F_SETFD, 1);
1350236769Sobrien}
1351236769Sobrien
1352236769Sobrienvoid
1353236769Sobrienmeta_compat_child(void)
1354236769Sobrien{
1355236769Sobrien    meta_job_child(NULL);
1356236769Sobrien    if (dup2(childPipe[1], 1) < 0 ||
1357236769Sobrien	dup2(1, 2) < 0) {
1358236769Sobrien	execError("dup2", "pipe");
1359236769Sobrien	_exit(1);
1360236769Sobrien    }
1361236769Sobrien}
1362236769Sobrien
1363236769Sobrienvoid
1364236769Sobrienmeta_compat_parent(void)
1365236769Sobrien{
1366236769Sobrien    FILE *fp;
1367236769Sobrien    char buf[BUFSIZ];
1368236769Sobrien
1369236769Sobrien    close(childPipe[1]);			/* child side */
1370236769Sobrien    fp = fdopen(childPipe[0], "r");
1371236769Sobrien    while (fgets(buf, sizeof(buf), fp)) {
1372236769Sobrien	meta_job_output(NULL, buf, "");
1373236769Sobrien	printf("%s", buf);
1374236769Sobrien    }
1375236769Sobrien    fclose(fp);
1376236769Sobrien}
1377236769Sobrien
1378236769Sobrien#endif	/* USE_META */
1379