Module Name:    src
Committed By:   sjg
Date:           Mon Sep 13 15:36:58 UTC 2010

Modified Files:
        src/usr.bin/make: Makefile compat.c job.c job.h main.c make.1 make.c
            make.h parse.c
Added Files:
        src/usr.bin/make: meta.c meta.h

Log Message:
Add meta.c which implements "meta" mode for make.
In this mode, a .meta file is created for each target, capturing
the expanded commands used, any command output, and if filemon(9)
is available, a record of system calls which are of interest.
Not enabled unless USE_META=yes is set when building make.
Also, if FILEMON_H exists, meta.c will be compiled to use filemon(9).


To generate a diff of this commit:
cvs rdiff -u -r1.50 -r1.51 src/usr.bin/make/Makefile
cvs rdiff -u -r1.80 -r1.81 src/usr.bin/make/compat.c
cvs rdiff -u -r1.154 -r1.155 src/usr.bin/make/job.c
cvs rdiff -u -r1.39 -r1.40 src/usr.bin/make/job.h
cvs rdiff -u -r1.189 -r1.190 src/usr.bin/make/main.c
cvs rdiff -u -r1.179 -r1.180 src/usr.bin/make/make.1
cvs rdiff -u -r1.81 -r1.82 src/usr.bin/make/make.c
cvs rdiff -u -r1.82 -r1.83 src/usr.bin/make/make.h
cvs rdiff -u -r0 -r1.1 src/usr.bin/make/meta.c src/usr.bin/make/meta.h
cvs rdiff -u -r1.164 -r1.165 src/usr.bin/make/parse.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/usr.bin/make/Makefile
diff -u src/usr.bin/make/Makefile:1.50 src/usr.bin/make/Makefile:1.51
--- src/usr.bin/make/Makefile:1.50	Thu Apr 22 19:15:23 2010
+++ src/usr.bin/make/Makefile	Mon Sep 13 15:36:57 2010
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.50 2010/04/22 19:15:23 sjg Exp $
+#	$NetBSD: Makefile,v 1.51 2010/09/13 15:36:57 sjg Exp $
 #	@(#)Makefile	5.2 (Berkeley) 12/28/90
 
 PROG=	make
@@ -13,6 +13,17 @@
 	lstMember.c lstNext.c lstOpen.c lstRemove.c lstReplace.c lstSucc.c
 SRCS += lstPrev.c
 
+# let people experiment for a bit
+USE_META ?= no
+.if ${USE_META:tl} != "no"
+SRCS+=	meta.c
+CPPFLAGS+= -DUSE_META
+FILEMON_H ?= ${.CURDIR:H:H}/sys/dev/filemon/filemon.h
+.if exists(${FILEMON_H}) && ${FILEMON_H:T} == "filemon.h"
+COPTS.meta.c += -DHAVE_FILEMON_H -I${FILEMON_H:H}
+.endif
+.endif
+
 .PATH:	${.CURDIR}/lst.lib
 .if make(install)
 SUBDIR=	PSD.doc

Index: src/usr.bin/make/compat.c
diff -u src/usr.bin/make/compat.c:1.80 src/usr.bin/make/compat.c:1.81
--- src/usr.bin/make/compat.c:1.80	Sat Aug  7 06:44:08 2010
+++ src/usr.bin/make/compat.c	Mon Sep 13 15:36:57 2010
@@ -1,4 +1,4 @@
-/*	$NetBSD: compat.c,v 1.80 2010/08/07 06:44:08 sjg Exp $	*/
+/*	$NetBSD: compat.c,v 1.81 2010/09/13 15:36:57 sjg Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -70,14 +70,14 @@
  */
 
 #ifndef MAKE_NATIVE
-static char rcsid[] = "$NetBSD: compat.c,v 1.80 2010/08/07 06:44:08 sjg Exp $";
+static char rcsid[] = "$NetBSD: compat.c,v 1.81 2010/09/13 15:36:57 sjg Exp $";
 #else
 #include <sys/cdefs.h>
 #ifndef lint
 #if 0
 static char sccsid[] = "@(#)compat.c	8.2 (Berkeley) 3/19/94";
 #else
-__RCSID("$NetBSD: compat.c,v 1.80 2010/08/07 06:44:08 sjg Exp $");
+__RCSID("$NetBSD: compat.c,v 1.81 2010/09/13 15:36:57 sjg Exp $");
 #endif
 #endif /* not lint */
 #endif
@@ -352,6 +352,12 @@
 
     local = TRUE;
 
+#ifdef USE_META
+    if (useMeta) {
+	meta_compat_start();
+    }
+#endif
+    
     /*
      * Fork and execute the single command. If the fork fails, we abort.
      */
@@ -362,6 +368,11 @@
     if (cpid == 0) {
 	Check_Cwd(av);
 	Var_ExportVars();
+#ifdef USE_META
+	if (useMeta) {
+	    meta_compat_child();
+	}
+#endif
 	if (local)
 	    (void)execvp(av[0], (char *const *)UNCONST(av));
 	else
@@ -375,6 +386,12 @@
 	free(bp);
     Lst_Replace(cmdNode, NULL);
 
+#ifdef USE_META
+    if (useMeta) {
+	meta_compat_parent();
+    }
+#endif
+
     /*
      * The child is off and running. Now all we can do is wait...
      */
@@ -393,6 +410,11 @@
 		status = WSTOPSIG(reason);		/* stopped */
 	    } else if (WIFEXITED(reason)) {
 		status = WEXITSTATUS(reason);		/* exited */
+#if defined(USE_META) && defined(USE_FILEMON_ONCE)
+		if (useMeta) {
+		    meta_cmd_finish(NULL);
+		}
+#endif
 		if (status != 0) {
 		    if (DEBUG(ERROR)) {
 		        fprintf(debug_file, "\n*** Failed target:  %s\n*** Failed command: ",
@@ -419,6 +441,11 @@
 
 	    if (!WIFEXITED(reason) || (status != 0)) {
 		if (errCheck) {
+#ifdef USE_META
+		    if (useMeta) {
+			meta_job_error(NULL, gn, 0, status);
+		    }
+#endif
 		    gn->made = ERROR;
 		    if (keepgoing) {
 			/*
@@ -551,6 +578,11 @@
 	     */
 	    if (!touchFlag || (gn->type & OP_MAKE)) {
 		curTarg = gn;
+#ifdef USE_META
+		if (useMeta && !NoExecute(gn)) {
+		    meta_job_start(NULL, gn);
+		}
+#endif
 		Lst_ForEach(gn->commands, CompatRunCommand, gn);
 		curTarg = NULL;
 	    } else {
@@ -559,6 +591,11 @@
 	} else {
 	    gn->made = ERROR;
 	}
+#ifdef USE_META
+	if (useMeta && !NoExecute(gn)) {
+	    meta_job_finish(NULL);
+	}
+#endif
 
 	if (gn->made != ERROR) {
 	    /*

Index: src/usr.bin/make/job.c
diff -u src/usr.bin/make/job.c:1.154 src/usr.bin/make/job.c:1.155
--- src/usr.bin/make/job.c:1.154	Sat Aug  7 21:28:40 2010
+++ src/usr.bin/make/job.c	Mon Sep 13 15:36:57 2010
@@ -1,4 +1,4 @@
-/*	$NetBSD: job.c,v 1.154 2010/08/07 21:28:40 sjg Exp $	*/
+/*	$NetBSD: job.c,v 1.155 2010/09/13 15:36:57 sjg Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -70,14 +70,14 @@
  */
 
 #ifndef MAKE_NATIVE
-static char rcsid[] = "$NetBSD: job.c,v 1.154 2010/08/07 21:28:40 sjg Exp $";
+static char rcsid[] = "$NetBSD: job.c,v 1.155 2010/09/13 15:36:57 sjg Exp $";
 #else
 #include <sys/cdefs.h>
 #ifndef lint
 #if 0
 static char sccsid[] = "@(#)job.c	8.2 (Berkeley) 3/19/94";
 #else
-__RCSID("$NetBSD: job.c,v 1.154 2010/08/07 21:28:40 sjg Exp $");
+__RCSID("$NetBSD: job.c,v 1.155 2010/09/13 15:36:57 sjg Exp $");
 #endif
 #endif /* not lint */
 #endif
@@ -960,6 +960,12 @@
 {
     Boolean 	 done, return_job_token;
 
+#ifdef USE_META
+    if (useMeta) {
+	meta_job_finish(job);
+    }
+#endif
+    
     if (DEBUG(JOB)) {
 	fprintf(debug_file, "Jobfinish: %d [%s], status %d\n",
 				job->pid, job->node->name, status);
@@ -1017,6 +1023,11 @@
 		    MESSAGE(stdout, job->node);
 		    lastNode = job->node;
 		}
+#ifdef USE_META
+		if (useMeta) {
+		    meta_job_error(job, job->node, job->flags, WEXITSTATUS(status));
+		}
+#endif
 		(void)printf("*** [%s] Error code %d%s\n",
 				job->node->name,
 			       WEXITSTATUS(status),
@@ -1311,6 +1322,11 @@
 	/* Child */
 	sigset_t tmask;
 
+#ifdef USE_META
+	if (useMeta) {
+	    meta_job_child(job);
+	}
+#endif
 	/*
 	 * Reset all signal handlers; this is necessary because we also
 	 * need to unblock signals before we exec(2).
@@ -1574,6 +1590,11 @@
 	 */
 	noExec = FALSE;
 
+#ifdef USE_META
+	if (useMeta) {
+	    meta_job_start(job, gn);
+	}
+#endif
 	/*
 	 * We can do all the commands at once. hooray for sanity
 	 */
@@ -1846,6 +1867,11 @@
 		    MESSAGE(stdout, job->node);
 		    lastNode = job->node;
 		}
+#ifdef USE_META
+		if (useMeta) {
+		    meta_job_output(job, cp, gotNL ? "\n" : "");
+		}
+#endif
 		(void)fprintf(stdout, "%s%s", cp, gotNL ? "\n" : "");
 		(void)fflush(stdout);
 	    }

Index: src/usr.bin/make/job.h
diff -u src/usr.bin/make/job.h:1.39 src/usr.bin/make/job.h:1.40
--- src/usr.bin/make/job.h:1.39	Sat Apr 11 09:41:18 2009
+++ src/usr.bin/make/job.h	Mon Sep 13 15:36:57 2010
@@ -1,4 +1,4 @@
-/*	$NetBSD: job.h,v 1.39 2009/04/11 09:41:18 apb Exp $	*/
+/*	$NetBSD: job.h,v 1.40 2010/09/13 15:36:57 sjg Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -135,6 +135,11 @@
  */
 struct pollfd;
 
+
+#ifdef USE_META
+# include "meta.h"
+#endif
+
 #define JOB_BUFSIZE	1024
 typedef struct Job {
     int       	pid;	    /* The child's process ID */
@@ -165,6 +170,10 @@
 				/* Buffer for storing the output of the
 				 * job, line by line */
     int   	curPos;	/* Current position in op_outBuf */
+
+#ifdef USE_META
+    struct BuildMon	bm;
+#endif
 } Job;
 
 #define inPipe jobPipe[0]

Index: src/usr.bin/make/main.c
diff -u src/usr.bin/make/main.c:1.189 src/usr.bin/make/main.c:1.190
--- src/usr.bin/make/main.c:1.189	Sat Aug  7 06:44:08 2010
+++ src/usr.bin/make/main.c	Mon Sep 13 15:36:57 2010
@@ -1,4 +1,4 @@
-/*	$NetBSD: main.c,v 1.189 2010/08/07 06:44:08 sjg Exp $	*/
+/*	$NetBSD: main.c,v 1.190 2010/09/13 15:36:57 sjg Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -69,7 +69,7 @@
  */
 
 #ifndef MAKE_NATIVE
-static char rcsid[] = "$NetBSD: main.c,v 1.189 2010/08/07 06:44:08 sjg Exp $";
+static char rcsid[] = "$NetBSD: main.c,v 1.190 2010/09/13 15:36:57 sjg Exp $";
 #else
 #include <sys/cdefs.h>
 #ifndef lint
@@ -81,7 +81,7 @@
 #if 0
 static char sccsid[] = "@(#)main.c	8.3 (Berkeley) 3/19/94";
 #else
-__RCSID("$NetBSD: main.c,v 1.189 2010/08/07 06:44:08 sjg Exp $");
+__RCSID("$NetBSD: main.c,v 1.190 2010/09/13 15:36:57 sjg Exp $");
 #endif
 #endif /* not lint */
 #endif
@@ -242,6 +242,9 @@
 		case 'l':
 			debug |= DEBUG_LOUD;
 			break;
+		case 'M':
+			debug |= DEBUG_META;
+			break;
 		case 'm':
 			debug |= DEBUG_MAKE;
 			break;
@@ -732,6 +735,10 @@
 	    compatMake = TRUE;
 	    forceJobs = FALSE;
 	}
+#if USE_META
+	if (strstr(mode, "meta"))
+	    meta_init(mode);
+#endif
     }
     if (mp)
 	free(mp);

Index: src/usr.bin/make/make.1
diff -u src/usr.bin/make/make.1:1.179 src/usr.bin/make/make.1:1.180
--- src/usr.bin/make/make.1:1.179	Wed Jun 30 01:03:54 2010
+++ src/usr.bin/make/make.1	Mon Sep 13 15:36:57 2010
@@ -1,4 +1,4 @@
-.\"	$NetBSD: make.1,v 1.179 2010/06/30 01:03:54 dholland Exp $
+.\"	$NetBSD: make.1,v 1.180 2010/09/13 15:36:57 sjg Exp $
 .\"
 .\" Copyright (c) 1990, 1993
 .\"	The Regents of the University of California.  All rights reserved.
@@ -194,6 +194,8 @@
 .Ql @
 or other "quiet" flags.
 Also known as "loud" behavior.
+.It Ar M
+Print debugging information about "meta" mode decisions about targets.
 .It Ar m
 Print debugging information about making targets, including modification
 dates.
@@ -266,6 +268,8 @@
 Specify the maximum number of jobs that
 .Nm
 may have running at any one time.
+The value is saved in
+.Va .MAKE.JOBS .
 Turns compatibility mode off, unless the
 .Ar B
 flag is also specified.
@@ -759,9 +763,49 @@
 Can affect the mode that
 .Nm
 runs in.
-Currently just
-.Ql Pa compat
-mode.
+It can contain a number of keywords:
+.Bl -hang -width ignore-cmd
+.It Pa compat
+Like 
+.Fl B
+puts 
+.Nm
+into "compat" mode.
+.It Pa meta
+Puts
+.Nm
+into "meta" mode, where meta files are created for each target 
+to capture the command run, the output generated and if
+.Xr filemon 4
+is available, the system calls which are of interest to
+.Nm .
+The captured output can be very useful when diagnosing errors.
+.It Pa verbose
+If in "meta" mode, print a clue about the target being built.
+This is useful if the build is otherwise running silently.
+The message printed the value of:
+.Va .MAKE.META.PREFIX .
+.It Pa ignore-cmd
+Some makefiles have commands which are simply not stable.
+This keyword causes them to be ignored for 
+determining whether a target is out of date in "meta" mode.
+See also
+.Ic .NOMETA_CMP .
+.El
+.It Va .MAKE.META.CREATED
+In "meta" mode, this variable contains a list of all the meta files
+updated.
+If not empty, it can be used to trigger processing of
+.Va .MAKE.META.FILES .
+.It Va .MAKE.META.FILES
+In "meta" mode, this variable contains a list of all the meta files
+used (updated or not).
+This list can be used to process the meta files to extract dependency 
+information.
+.It Va .MAKE.META.PREFIX
+Defines the message printed for each meta file updated in "meta verbose" mode.
+The default value is:
+.Dl Building ${.TARGET:H:tA}/${.TARGET:T}
 .It Va .MAKEOVERRIDES
 This variable is used to record the names of variables assigned to
 on the command line, so that they may be exported as part of
@@ -1612,6 +1656,27 @@
 options were specified.
 Normally used to mark recursive
 .Nm Ns 's .
+.It Ic .META
+Create a meta file for the target, even if it is flagged as
+.Ic .PHONY ,
+.Ic .MAKE 
+or
+.Ic .SPECIAL .
+Usage in conjunction with
+.Ic .MAKE
+is the most likely case.
+.It Ic .NOMETA
+Do not create a meta file for the target.
+Meta files are also not created for
+.Ic .PHONY ,
+.Ic .MAKE
+or
+.Ic .SPECIAL
+targets.
+.It Ic .NOMETA_CMP
+Ignore differences in commands when deciding if target is out of date.
+This is useful if the command contains a value which always changes.
+If the number of commands change, though the target will still be out of date.
 .It Ic .NOPATH
 Do not search for the target in the directories specified by
 .Ic .PATH .

Index: src/usr.bin/make/make.c
diff -u src/usr.bin/make/make.c:1.81 src/usr.bin/make/make.c:1.82
--- src/usr.bin/make/make.c:1.81	Tue Jul  6 03:56:59 2010
+++ src/usr.bin/make/make.c	Mon Sep 13 15:36:57 2010
@@ -1,4 +1,4 @@
-/*	$NetBSD: make.c,v 1.81 2010/07/06 03:56:59 dholland Exp $	*/
+/*	$NetBSD: make.c,v 1.82 2010/09/13 15:36:57 sjg Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -69,14 +69,14 @@
  */
 
 #ifndef MAKE_NATIVE
-static char rcsid[] = "$NetBSD: make.c,v 1.81 2010/07/06 03:56:59 dholland Exp $";
+static char rcsid[] = "$NetBSD: make.c,v 1.82 2010/09/13 15:36:57 sjg Exp $";
 #else
 #include <sys/cdefs.h>
 #ifndef lint
 #if 0
 static char sccsid[] = "@(#)make.c	8.1 (Berkeley) 6/6/93";
 #else
-__RCSID("$NetBSD: make.c,v 1.81 2010/07/06 03:56:59 dholland Exp $");
+__RCSID("$NetBSD: make.c,v 1.82 2010/09/13 15:36:57 sjg Exp $");
 #endif
 #endif /* not lint */
 #endif
@@ -330,6 +330,12 @@
 	oodate = (gn->flags & FORCE) ? TRUE : FALSE;
     }
 
+#ifdef USE_META
+    if (useMeta) {
+	oodate = meta_oodate(gn, oodate);
+    }
+#endif
+
     /*
      * If the target isn't out-of-date, the parents need to know its
      * modification time. Note that targets that appear to be out-of-date

Index: src/usr.bin/make/make.h
diff -u src/usr.bin/make/make.h:1.82 src/usr.bin/make/make.h:1.83
--- src/usr.bin/make/make.h:1.82	Fri Apr 23 00:18:50 2010
+++ src/usr.bin/make/make.h	Mon Sep 13 15:36:57 2010
@@ -1,4 +1,4 @@
-/*	$NetBSD: make.h,v 1.82 2010/04/23 00:18:50 sjg Exp $	*/
+/*	$NetBSD: make.h,v 1.83 2010/09/13 15:36:57 sjg Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -260,6 +260,9 @@
 #define OP_PHONY	0x00010000  /* Not a file target; run always */
 #define OP_NOPATH	0x00020000  /* Don't search for file in the path */
 #define OP_WAIT 	0x00040000  /* .WAIT phony node */
+#define OP_NOMETA	0x00080000  /* .NOMETA do not create a .meta file */
+#define OP_META		0x00100000  /* .META we _do_ want a .meta file */
+#define OP_NOMETA_CMP	0x00200000  /* Do not compare commands in .meta file */
 /* Attributes applied by PMake */
 #define OP_TRANSFORM	0x80000000  /* The node is a transformation rule */
 #define OP_MEMBER 	0x40000000  /* Target is a member of an archive */
@@ -433,6 +436,8 @@
 #define DEBUG_SHELL	0x00800
 #define DEBUG_ERROR	0x01000
 #define DEBUG_LOUD	0x02000
+#define DEBUG_META	0x04000
+
 #define DEBUG_GRAPH3	0x10000
 #define DEBUG_SCRIPT	0x20000
 #define DEBUG_PARSE	0x40000

Index: src/usr.bin/make/parse.c
diff -u src/usr.bin/make/parse.c:1.164 src/usr.bin/make/parse.c:1.165
--- src/usr.bin/make/parse.c:1.164	Mon May 24 21:04:49 2010
+++ src/usr.bin/make/parse.c	Mon Sep 13 15:36:57 2010
@@ -1,4 +1,4 @@
-/*	$NetBSD: parse.c,v 1.164 2010/05/24 21:04:49 sjg Exp $	*/
+/*	$NetBSD: parse.c,v 1.165 2010/09/13 15:36:57 sjg Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -69,14 +69,14 @@
  */
 
 #ifndef MAKE_NATIVE
-static char rcsid[] = "$NetBSD: parse.c,v 1.164 2010/05/24 21:04:49 sjg Exp $";
+static char rcsid[] = "$NetBSD: parse.c,v 1.165 2010/09/13 15:36:57 sjg Exp $";
 #else
 #include <sys/cdefs.h>
 #ifndef lint
 #if 0
 static char sccsid[] = "@(#)parse.c	8.3 (Berkeley) 3/19/94";
 #else
-__RCSID("$NetBSD: parse.c,v 1.164 2010/05/24 21:04:49 sjg Exp $");
+__RCSID("$NetBSD: parse.c,v 1.165 2010/09/13 15:36:57 sjg Exp $");
 #endif
 #endif /* not lint */
 #endif
@@ -196,10 +196,13 @@
     Includes,	    /* .INCLUDES */
     Interrupt,	    /* .INTERRUPT */
     Libs,	    /* .LIBS */
+    Meta,	    /* .META */
     MFlags,	    /* .MFLAGS or .MAKEFLAGS */
     Main,	    /* .MAIN and we don't have anything user-specified to
 		     * make */
     NoExport,	    /* .NOEXPORT */
+    NoMeta,	    /* .NOMETA */
+    NoMetaCmp,	    /* .NOMETA_CMP */
     NoPath,	    /* .NOPATH */
     Not,	    /* Not special */
     NotParallel,    /* .NOTPARALLEL */
@@ -258,7 +261,10 @@
 { ".MAIN",	  Main,		0 },
 { ".MAKE",  	  Attribute,   	OP_MAKE },
 { ".MAKEFLAGS",	  MFlags,   	0 },
+{ ".META",	  Meta,		OP_META },
 { ".MFLAGS",	  MFlags,   	0 },
+{ ".NOMETA",	  NoMeta,	OP_NOMETA },
+{ ".NOMETA_CMP",  NoMetaCmp,	OP_NOMETA_CMP },
 { ".NOPATH",	  NoPath,	OP_NOPATH },
 { ".NOTMAIN",	  Attribute,   	OP_NOTMAIN },
 { ".NOTPARALLEL", NotParallel,	0 },

Added files:

Index: src/usr.bin/make/meta.c
diff -u /dev/null src/usr.bin/make/meta.c:1.1
--- /dev/null	Mon Sep 13 15:36:58 2010
+++ src/usr.bin/make/meta.c	Mon Sep 13 15:36:57 2010
@@ -0,0 +1,955 @@
+/*
+ * Implement 'meta' mode.
+ * Adapted from John Birrell's patches to FreeBSD make.
+ * --sjg
+ */
+/*
+ * Copyright (c) 2009-2010, Juniper Networks, Inc.
+ * Portions Copyright (c) 2009, John Birrell.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions 
+ * are met: 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer. 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.  
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <errno.h>
+#include <err.h>
+
+#include "make.h"
+#include "job.h"
+
+#ifdef HAVE_FILEMON_H
+# include <filemon.h>
+#endif
+#if !defined(USE_FILEMON) && defined(FILEMON_SET_FD)
+# define USE_FILEMON
+#endif
+
+static BuildMon Mybm;			/* for compat */
+
+Boolean useMeta = FALSE;
+static Boolean useFilemon = FALSE;
+static Boolean writeMeta = FALSE;
+static Boolean metaEnv = FALSE;		/* don't save env unless asked */
+static Boolean metaVerbose = FALSE;
+static Boolean metaIgnoreCMDs = FALSE;	/* ignore CMDs in .meta files */
+
+extern Boolean forceJobs;
+extern Boolean comatMake;
+
+#define	MAKE_META_PREFIX	".MAKE.META.PREFIX"
+
+#ifndef N2U
+# define N2U(n, u)   (((n) + ((u) - 1)) / (u))
+#endif
+#ifndef ROUNDUP
+# define ROUNDUP(n, u)   (N2U((n), (u)) * (u))
+#endif
+
+/*
+ * Filemon is a kernel module which snoops certain syscalls.
+ *
+ * C chdir
+ * E exec
+ * F [v]fork
+ * L [sym]link
+ * M rename
+ * R read
+ * W write
+ * S stat
+ *
+ * See meta_oodate below - we mainly care about 'E' and 'R'.
+ *
+ * We can still use meta mode without filemon, but 
+ * the benefits are more limited.
+ */
+#ifdef USE_FILEMON
+# ifndef _PATH_FILEMON
+#   define _PATH_FILEMON "/dev/filemon"
+# endif
+
+/*
+ * Open the filemon device.
+ */
+static void
+filemon_open(BuildMon *pbm)
+{
+    int retry;
+    
+    pbm->mon_fd = pbm->filemon_fd = -1;
+    if (!useFilemon)
+	return;
+
+    for (retry = 5; retry >= 0; retry--) {
+	if ((pbm->filemon_fd = open(_PATH_FILEMON, O_RDWR)) >= 0)
+	    break;
+    }
+
+    if (pbm->filemon_fd < 0) {
+	useFilemon = FALSE;
+	warn("Could not open %s", _PATH_FILEMON);
+	return;
+    }
+
+    /*
+     * We use a file outside of '.'
+     * to avoid a FreeBSD kernel bug where unlink invalidates
+     * cwd causing getcwd to do a lot more work.
+     * We only care about the descriptor.
+     */
+    pbm->mon_fd = mkTempFile("filemon.XXXXXX", NULL);
+    if (ioctl(pbm->filemon_fd, FILEMON_SET_FD, &pbm->mon_fd) < 0) {
+	err(1, "Could not set filemon file descriptor!");
+    }
+    /* we don't need these once we exec */
+    (void)fcntl(pbm->mon_fd, F_SETFD, 1);
+    (void)fcntl(pbm->filemon_fd, F_SETFD, 1);
+}
+
+/*
+ * Read the build monitor output file and write records to the target's
+ * metadata file.
+ */
+static void
+filemon_read(FILE *mfp, int fd)
+{
+    FILE *fp;
+    char buf[BUFSIZ];
+
+    /* Check if we're not writing to a meta data file.*/
+    if (mfp == NULL) {
+	if (fd >= 0)
+	    close(fd);			/* not interested */
+	return;
+    }
+    /* rewind */
+    lseek(fd, SEEK_SET, 0);
+    if ((fp = fdopen(fd, "r")) == NULL)
+	err(1, "Could not read build monitor file '%d'", fd);
+
+    fprintf(mfp, "-- filemon acquired metadata --\n");
+
+    while (fgets(buf, sizeof(buf), fp)) {
+	fprintf(mfp, "%s", buf);
+    }
+    fflush(mfp);
+    clearerr(fp);
+    fclose(fp);
+}
+#endif
+
+static char *
+meta_name(struct GNode *gn, char *mname, size_t mnamelen,
+	  const char *dname,
+	  const char *tname)
+{
+    char buf[MAXPATHLEN];
+    char cwd[MAXPATHLEN];
+    char *rp;
+    char *cp;
+    char *tp;
+    char *p[4];				/* >= number of possible uses */
+    int i;
+
+    i = 0;
+    if (!dname)
+	dname = Var_Value(".OBJDIR", gn, &p[i++]);
+    if (!tname)
+	tname = Var_Value(TARGET, gn, &p[i++]);
+
+    /*
+     * Weed out relative paths from the target file name.
+     * We have to be careful though since if target is a
+     * symlink, the result will be unstable.
+     * So we use realpath() just to get the dirname, and leave the
+     * basename as given to us.
+     */
+    if ((cp = strrchr(tname, '/'))) {
+	if (realpath(tname, buf)) {
+	    if ((rp = strrchr(buf, '/'))) {
+		rp++;
+		cp++;
+		if (strcmp(cp, rp) != 0)
+		    strlcpy(rp, cp, sizeof(buf) - (rp - buf));
+	    }
+	    tname = buf;
+	}
+    }
+    if (realpath(dname, cwd))
+	dname = cwd;
+    /* on some systems dirname may modify its arg */
+    tp = bmake_strdup(tname);
+    if (strcmp(dname, dirname(tp)) == 0)
+	snprintf(mname, mnamelen, "%s.meta", tname);
+    else {
+	snprintf(mname, mnamelen, "%s/%s.meta", dname, tname);
+
+	/*
+	 * Replace path separators in the file name after the
+	 * current object directory path.
+	 */
+	cp = mname + strlen(dname) + 1;
+
+	while (*cp != '\0') {
+	    if (*cp == '/')
+		*cp = '_';
+	    cp++;
+	}
+    }
+    free(tp);
+    for (i--; i >= 0; i--) {
+	if (p[i])
+	    free(p[i]);
+    }
+    return (mname);
+}
+
+/*
+ * Return true if running ${.MAKE}
+ * Bypassed if target is flagged .MAKE
+ */
+static int
+is_submake(void *cmdp, void *gnp)
+{
+    static char *p_make = NULL;
+    static int p_len;
+    char  *cmd = cmdp;
+    GNode *gn = gnp;
+    char *mp = NULL;
+    char *cp;
+    char *cp2;
+    int rc = 0;				/* keep looking */
+
+    if (!p_make) {
+	p_make = Var_Value(".MAKE", gn, &cp);
+	p_len = strlen(p_make);
+    }
+    cp = strchr(cmd, '$');
+    if ((cp)) {
+	mp = Var_Subst(NULL, cmd, gn, FALSE);
+	cmd = mp;
+    }
+    cp2 = strstr(cmd, p_make);
+    if ((cp2)) {
+	switch (cp2[p_len]) {
+	case '\0':
+	case ' ':
+	case '\t':
+	case '\n':
+	    rc = 1;
+	    break;
+	}
+	if (cp2 > cmd && rc > 0) {
+	    switch (cp2[-1]) {
+	    case ' ':
+	    case '\t':
+	    case '\n':
+		break;
+	    default:
+		rc = 0;			/* no match */
+		break;
+	    }
+	}
+    }
+    if (mp)
+	free(mp);
+    return (rc);
+}
+
+typedef struct meta_file_s {
+    FILE *fp;
+    GNode *gn;
+} meta_file_t;
+
+static int
+printCMD(void *cmdp, void *mfpp)
+{
+    meta_file_t *mfp = mfpp;
+    char *cmd = cmdp;
+    char *cp = NULL;
+
+    if (strchr(cmd, '$')) {
+	cmd = cp = Var_Subst(NULL, cmd, mfp->gn, FALSE);
+    }
+    fprintf(mfp->fp, "CMD %s\n", cmd);
+    if (cp)
+	free(cp);
+    return 0;
+}
+
+/*
+ * Certain node types never get a .meta file
+ */
+#define SKIP_META_TYPE(_type) do { \
+    if ((gn->type & __CONCAT(OP_, _type))) {	\
+	if (DEBUG(META)) { \
+	    fprintf(debug_file, "Skipping meta for %s: .%s\n", \
+		    gn->name, __STRING(_type));		       \
+	} \
+	return (NULL); \
+    } \
+} while (0)
+
+static FILE *
+meta_create(BuildMon *pbm, GNode *gn)
+{
+    extern char **environ;
+    meta_file_t mf;
+    char buf[MAXPATHLEN];
+    char curdir[MAXPATHLEN];
+    char objdir[MAXPATHLEN];
+    char **ptr;
+    const char *cname;
+    const char *dname;
+    const char *tname;
+    char *fname;
+    const char *cp;
+    char *p[4];				/* >= possible uses */
+    int i;
+    struct stat fs;
+
+    
+    /* This may be a phony node which we don't want meta data for... */
+    /* Skip .meta for .BEGIN, .END, .ERROR etc as well. */
+    /* Or it may be explicitly flagged as .NOMETA */
+    SKIP_META_TYPE(NOMETA);
+    /* Unless it is explicitly flagged as .META */
+    if (!(gn->type & OP_META)) {
+	SKIP_META_TYPE(PHONY);
+	SKIP_META_TYPE(SPECIAL);
+	SKIP_META_TYPE(MAKE);
+    }
+
+    mf.fp = NULL;
+    
+    i = 0;
+    
+    dname = Var_Value(".OBJDIR", gn, &p[i++]);
+    cname = Var_Value(".CURDIR", gn, &p[i++]);
+    tname = Var_Value(TARGET, gn, &p[i++]);
+    
+    /* The object directory may not exist. Check it.. */
+    if (stat(dname, &fs) != 0) {
+	if (DEBUG(META))
+	    fprintf(debug_file, "Skipping meta for %s: no .OBJDIR\n",
+		    gn->name);
+	goto out;
+    }
+    /* Check if there are no commands to execute. */
+    if (Lst_IsEmpty(gn->commands)) {
+	if (DEBUG(META))
+	    fprintf(debug_file, "Skipping meta for %s: no commands\n",
+		    gn->name);
+	goto out;
+    }
+
+    /* make sure these are canonical */
+    if (realpath(dname, objdir))
+	dname = objdir;
+    if (realpath(cname, curdir))
+	cname = curdir;
+
+    /* If we aren't in the object directory, don't create a meta file. */
+    if (strcmp(cname, dname) == 0) {
+	if (DEBUG(META))
+	    fprintf(debug_file, "Skipping meta for %s: .OBJDIR == .CURDIR\n",
+		    gn->name);
+	goto out;
+    }
+    if (!(gn->type & OP_META)) {
+	/* We do not generate .meta files for sub-makes */
+	if (Lst_ForEach(gn->commands, is_submake, gn)) {
+	    if (DEBUG(META))
+		fprintf(debug_file, "Skipping meta for %s: .MAKE\n",
+			gn->name);
+	    goto out;
+	}
+    }
+
+    if (metaVerbose) {
+	char *mp;
+
+	/* Describe the target we are building */
+	mp = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", gn, 0);
+	if (*mp)
+	    fprintf(stdout, "%s\n", mp);
+	free(mp);
+    }
+    /* Get the basename of the target */
+    if ((cp = strrchr(tname, '/')) == NULL) {
+	cp = tname;
+    } else {
+	cp++;
+    }
+
+    fflush(stdout);
+
+    if (strcmp(cp, makeDependfile) == 0)
+	goto out;
+
+    if (!writeMeta)
+	/* Don't create meta data. */
+	goto out;
+
+    fname = meta_name(gn, pbm->meta_fname, sizeof(pbm->meta_fname),
+		      dname, tname);
+
+    if ((mf.fp = fopen(fname, "w")) == NULL)
+	err(1, "Could not open meta file '%s'", fname);
+
+    fprintf(mf.fp, "# Meta data file %s\n", fname);
+
+    mf.gn = gn;
+
+    Lst_ForEach(gn->commands, printCMD, &mf);
+
+    fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf)));
+    fprintf(mf.fp, "TARGET %s\n", tname);
+
+    if (metaEnv) {
+	for (ptr = environ; *ptr != NULL; ptr++)
+	    fprintf(mf.fp, "ENV %s\n", *ptr);
+    }
+
+    fprintf(mf.fp, "-- command output --\n");
+    fflush(mf.fp);
+
+    Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
+    Var_Append(".MAKE.META.CREATED", fname, VAR_GLOBAL);
+    
+ out:
+    for (i--; i >= 0; i--) {
+	if (p[i])
+	    free(p[i]);
+    }
+
+    return (mf.fp);
+}
+
+
+void
+meta_init(const char *make_mode)
+{
+    static int once = 0;
+
+    useMeta = TRUE;
+    useFilemon = TRUE;
+    writeMeta = TRUE;
+
+    if (make_mode) {
+	if (strstr(make_mode, "env"))
+	    metaEnv = TRUE;
+	if (strstr(make_mode, "verb"))
+	    metaVerbose = TRUE;
+	if (strstr(make_mode, "read"))
+	    writeMeta = FALSE;
+	if (strstr(make_mode, "nofilemon"))
+	    useFilemon = FALSE;
+	if (strstr(make_mode, "ignore-cmd"))
+	    metaIgnoreCMDs = TRUE;
+	/* for backwards compatability */
+	Var_Set(".MAKE.META_CREATED", "${.MAKE.META.CREATED}", VAR_GLOBAL, 0);
+	Var_Set(".MAKE.META_FILES", "${.MAKE.META.FILES}", VAR_GLOBAL, 0);
+    }
+    if (metaVerbose && !Var_Exists(MAKE_META_PREFIX, VAR_GLOBAL)) {
+	/*
+	 * The default value for MAKE_META_PREFIX
+	 * prints the absolute path of the target.
+	 * This works be cause :H will generate '.' if there is no /
+	 * and :tA will resolve that to cwd.
+	 */
+	Var_Set(MAKE_META_PREFIX, "Building ${.TARGET:H:tA}/${.TARGET:T}", VAR_GLOBAL, 0);
+    }
+    if (once)
+	return;
+    once = 1;
+    memset(&Mybm, 0, sizeof(Mybm));
+}
+
+/*
+ * In each case below we allow for job==NULL
+ */
+void
+meta_job_start(Job *job, GNode *gn)
+{
+    BuildMon *pbm;
+
+    if (job != NULL) {
+	pbm = &job->bm;
+    } else {
+	pbm = &Mybm;
+    }
+    pbm->mfp = meta_create(pbm, gn);
+#ifdef USE_FILEMON_ONCE
+    /* compat mode we open the filemon dev once per command */
+    if (job == NULL)
+	return;
+#endif
+#ifdef USE_FILEMON
+    if (pbm->mfp != NULL && useFilemon) {
+	filemon_open(pbm);
+    } else {
+	pbm->mon_fd = pbm->filemon_fd = -1;
+    }
+#endif
+}
+
+/*
+ * The child calls this before doing anything.
+ * It does not disturb our state.
+ */
+void
+meta_job_child(Job *job)
+{
+#ifdef USE_FILEMON
+    BuildMon *pbm;
+    pid_t pid;
+
+    if (job != NULL) {
+	pbm = &job->bm;
+    } else {
+	pbm = &Mybm;
+    }
+    pid = getpid();
+    if (pbm->mfp != NULL && useFilemon) {
+	if (ioctl(pbm->filemon_fd, FILEMON_SET_PID, &pid) < 0) {
+	    err(1, "Could not set filemon pid!");
+	}
+    }
+#endif
+}
+
+void
+meta_job_error(Job *job, GNode *gn, int flags, int status)
+{
+    char cwd[MAXPATHLEN];
+    BuildMon *pbm;
+
+    if (job != NULL) {
+	pbm = &job->bm;
+    } else {
+	if (!gn)
+	    gn = job->node;
+	pbm = &Mybm;
+    }
+    if (pbm->mfp != NULL) {
+	fprintf(pbm->mfp, "*** Error code %d%s\n",
+		status,
+		(flags & JOB_IGNERR) ?
+		"(ignored)" : "");
+    }
+    if (gn) {
+	Var_Set(".ERROR_TARGET", gn->path ? gn->path : gn->name, VAR_GLOBAL, 0);
+    }
+    getcwd(cwd, sizeof(cwd));
+    Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL, 0);
+#ifdef USE_FILEMON
+    if (pbm) {
+	Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL, 0);
+    }
+#endif
+}
+
+void
+meta_job_output(Job *job, char *cp, const char *nl)
+{
+    BuildMon *pbm;
+    
+    if (job != NULL) {
+	pbm = &job->bm;
+    } else {
+	pbm = &Mybm;
+    }
+    if (pbm->mfp != NULL) {
+	if (metaVerbose) {
+	    static char *meta_prefix = NULL;
+	    static int meta_prefix_len;
+
+	    if (!meta_prefix) {
+		char *cp2;
+
+		meta_prefix = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", VAR_GLOBAL, 0);
+		if ((cp2 = strchr(meta_prefix, '$')))
+		    meta_prefix_len = cp2 - meta_prefix;
+		else
+		    meta_prefix_len = strlen(meta_prefix);
+	    }
+	    if (strncmp(cp, meta_prefix, meta_prefix_len) == 0) {
+		cp = strchr(cp+1, '\n');
+		if (!cp++)
+		    return;
+	    }
+	}
+	fprintf(pbm->mfp, "%s%s", cp, nl);
+    }
+}
+
+void
+meta_cmd_finish(void *pbmp)
+{
+#ifdef USE_FILEMON
+    BuildMon *pbm = pbmp;
+
+    if (!pbm)
+	pbm = &Mybm;
+
+    if (pbm->filemon_fd >= 0) {
+	close(pbm->filemon_fd);
+	filemon_read(pbm->mfp, pbm->mon_fd);
+	pbm->filemon_fd = pbm->mon_fd = -1;
+    }
+#endif
+}
+
+void
+meta_job_finish(Job *job)
+{
+    BuildMon *pbm;
+
+    if (job != NULL) {
+	pbm = &job->bm;
+    } else {
+	pbm = &Mybm;
+    }
+    if (pbm->mfp != NULL) {
+	meta_cmd_finish(pbm);
+	fclose(pbm->mfp);
+	pbm->mfp = NULL;
+    }
+}
+
+/*
+ * Fetch a full line from fp - growing bufp if needed
+ * Return length in bufp.
+ */
+static int 
+fgetLine(char **bufp, size_t *szp, int o, FILE *fp)
+{
+    char *buf = *bufp;
+    size_t bufsz = *szp;
+    struct stat fs;
+    int x;
+
+    if (fgets(&buf[o], bufsz - o, fp) != NULL) {
+    check_newline:
+	x = o + strlen(&buf[o]);
+	if (buf[x - 1] == '\n')
+	    return x;
+	/*
+	 * We need to grow the buffer.
+	 * The meta file can give us a clue.
+	 */
+	if (fstat(fileno(fp), &fs) == 0) {
+	    size_t newsz;
+	    char *p;
+
+	    newsz = ROUNDUP((fs.st_size / 2), BUFSIZ);
+	    if (newsz <= bufsz)
+		newsz = ROUNDUP(fs.st_size, BUFSIZ);
+	    if (DEBUG(META)) 
+		fprintf(debug_file, "growing buffer %u -> %u\n",
+			bufsz, newsz);
+	    p = bmake_realloc(buf, newsz);
+	    if (p) {
+		*bufp = buf = p;
+		*szp = bufsz = newsz;
+		/* fetch the rest */
+		if (!fgets(&buf[x], bufsz - x, fp))
+		    return x;		/* truncated! */
+		goto check_newline;
+	    }
+	}
+    }
+    return 0;
+}
+
+/*
+ * When running with 'meta' functionality, a target can be out-of-date
+ * if any of the references in it's meta data file is more recent.
+ */
+Boolean
+meta_oodate(GNode *gn, Boolean oodate)
+{
+    char latestdir[MAXPATHLEN];
+    char fname[MAXPATHLEN];
+    char fname1[MAXPATHLEN];
+    char *p;
+    char *cp;
+    FILE *fp;
+    Boolean ignoreOODATE = FALSE;
+
+    /*
+     * We need to check if the target is out-of-date. This includes
+     * checking if the expanded command has changed. This in turn
+     * requires that all variables are set in the same way that they
+     * would be if the target needs to be re-built.
+     */
+    Make_DoAllVar(gn);
+
+    if (oodate)
+	return oodate;		/* we're done */
+
+    if (getcwd(latestdir, sizeof(latestdir)) == NULL)
+	err(1, "Could not get current working directory");
+
+    meta_name(gn, fname, sizeof(fname), NULL, NULL);
+
+    if ((fp = fopen(fname, "r")) != NULL) {
+	static char *buf = NULL;
+	static size_t bufsz;
+	int lineno = 0;
+	int f = 0;
+	int x;
+	LstNode ln;
+	struct stat fs;
+
+	if (!buf) {
+	    bufsz = 8 * BUFSIZ;
+	    buf = bmake_malloc(bufsz);
+	}
+	
+	/* we want to track all the .meta we read */
+	Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL);
+
+	ln = Lst_First(gn->commands);
+	while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) {
+	    lineno++;
+	    if (buf[x - 1] == '\n')
+		buf[x - 1] = '\0';
+	    else
+		warnx("%s: %d: line truncated at %u", fname, lineno, x);
+
+	    /* Find the start of the build monitor section. */
+	    if (!f) {
+		if (strncmp(buf, "-- filemon", 10) == 0) {
+		    f = 1;
+		    continue;
+		}
+		if (strncmp(buf, "# buildmon", 10) == 0) {
+		    f = 1;
+		    continue;
+		}
+	    }		    
+
+	    /* Delimit the record type. */
+	    p = buf;
+	    strsep(&p, " ");
+	    if (f) {
+		/* Process according to record type. */
+		switch (buf[0]) {
+		case 'C':
+		    /* Skip the pid. */
+		    if (strsep(&p, " ") == NULL)
+			break;
+
+		    /* Update the latest directory. */
+		    strlcpy(latestdir, p, sizeof(latestdir));
+		    break;
+
+		case 'R':
+		case 'E':
+		    /* Skip the pid. */
+		    if (strsep(&p, " ") == NULL)
+			break;
+
+		    /*
+		     * Check for runtime files that can't
+		     * be part of the dependencies because
+		     * they are _expected_ to change.
+		     */
+		    if (strncmp(p, "/var/", 5) == 0)
+			break;
+
+		    /* Ignore device files. */
+		    if (strncmp(p, "/dev/", 5) == 0)
+			break;
+
+		    /* Ignore /etc/ files. */
+		    if (strncmp(p, "/etc/", 5) == 0)
+			break;
+
+		    /*
+		     * The rest of the record is the
+		     * file name.
+		     * Check if it's not an absolute
+		     * path.
+		     */
+		    if (*p != '/') {
+			/* Use the latest path seen. */
+			snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p);
+			p = fname1;
+		    }
+
+		    if (stat(p, &fs) == 0 &&
+			!S_ISDIR(fs.st_mode) &&
+			fs.st_mtime > gn->mtime) {
+			if (DEBUG(META))
+			    fprintf(debug_file, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p);
+			oodate = TRUE;
+		    }
+		    break;
+		default:
+		    break;
+		}
+
+		/*
+		 * Compare the current command with the one in the
+		 * meta data file.
+		 */
+	    } else if (strcmp(buf, "CMD") == 0) {
+		if (ln == NULL) {
+		    if (DEBUG(META))
+			fprintf(debug_file, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno);
+		    oodate = TRUE;
+		} else {
+		    char *cmd = (char *)Lst_Datum(ln);
+
+		    if (!ignoreOODATE) {
+			if (strstr(cmd, "$?"))
+			    ignoreOODATE = TRUE;
+			else if ((cp = strstr(cmd, ".OODATE"))) {
+			    /* check for $[{(].OODATE[)}] */
+			    if (cp > cmd + 2 && cp[-2] == '$')
+				ignoreOODATE = TRUE;
+			}
+			if (ignoreOODATE && DEBUG(META))
+			    fprintf(debug_file, "%s: %d: cannot compare commands using .OODATE\n", fname, lineno);
+		    }
+		    cmd = Var_Subst(NULL, cmd, gn, TRUE);
+
+		    if ((cp = strchr(cmd, '\n'))) {
+			int n;
+
+			/*
+			 * This command contains newlines, we need to
+			 * fetch more from the .meta file before we
+			 * attempt a comparison.
+			 */
+			/* first put the newline back at buf[x - 1] */
+			buf[x - 1] = '\n';
+			do {
+			    /* now fetch the next line */
+			    if ((n = fgetLine(&buf, &bufsz, x, fp)) <= 0)
+				break;
+			    x = n;
+			    lineno++;
+			    if (buf[x - 1] != '\n') {
+				warnx("%s: %d: line truncated at %u", fname, lineno, x);
+				break;
+			    }
+			    cp = strchr(++cp, '\n');
+			} while (cp);
+			if (buf[x - 1] == '\n')
+			    buf[x - 1] = '\0';
+		    }
+		    if (!ignoreOODATE &&
+			!(gn->type & OP_NOMETA_CMP) &&
+			strcmp(p, cmd) != 0) {
+			if (DEBUG(META))
+			    fprintf(debug_file, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd);
+			if (!metaIgnoreCMDs)
+			    oodate = TRUE;
+		    }
+		    free(cmd);
+		    ln = Lst_Succ(ln);
+		}
+	    } else if (strcmp(buf, "CWD") == 0) {
+		char curdir[MAXPATHLEN];
+		if (strcmp(p, getcwd(curdir, sizeof(curdir))) != 0) {
+		    if (DEBUG(META))
+			fprintf(debug_file, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir);
+		    oodate = TRUE;
+		}
+	    }
+	}
+
+	/*
+	 * Check if there are extra commands now
+	 * that weren't in the meta data file.
+	 */
+	if (!oodate && ln != NULL) {
+	    if (DEBUG(META))
+		fprintf(debug_file, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno);
+	    oodate = TRUE;
+	}
+
+	fclose(fp);
+    }
+    return oodate;
+}
+
+/* support for compat mode */
+
+static int childPipe[2];
+
+void
+meta_compat_start(void)
+{
+#ifdef USE_FILEMON_ONCE
+    /*
+     * We need to re-open filemon for each cmd.
+     */
+    BuildMon *pbm = &Mybm;
+    
+    if (pbm->mfp != NULL && useFilemon) {
+	filemon_open(pbm);
+    } else {
+	pbm->mon_fd = pbm->filemon_fd = -1;
+    }
+#endif
+    if (pipe(childPipe) < 0)
+	Punt("Cannot create pipe: %s", strerror(errno));
+    /* Set close-on-exec flag for both */
+    (void)fcntl(childPipe[0], F_SETFD, 1);
+    (void)fcntl(childPipe[1], F_SETFD, 1);
+}
+
+void
+meta_compat_child(void)
+{
+    meta_job_child(NULL);
+    if (dup2(childPipe[1], 1) < 0 ||
+	dup2(1, 2) < 0) {
+	execError("dup2", "pipe");
+	_exit(1);
+    }
+}
+
+void
+meta_compat_parent(void)
+{
+    FILE *fp;
+    char buf[BUFSIZ];
+    
+    close(childPipe[1]);			/* child side */
+    fp = fdopen(childPipe[0], "r");
+    while (fgets(buf, sizeof(buf), fp)) {
+	meta_job_output(NULL, buf, "");
+	printf("%s", buf);
+    }
+    fclose(fp);
+}
Index: src/usr.bin/make/meta.h
diff -u /dev/null src/usr.bin/make/meta.h:1.1
--- /dev/null	Mon Sep 13 15:36:58 2010
+++ src/usr.bin/make/meta.h	Mon Sep 13 15:36:57 2010
@@ -0,0 +1,52 @@
+/*
+ * Things needed for 'meta' mode.
+ */
+/*
+ * Copyright (c) 2009-2010, Juniper Networks, Inc.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions 
+ * are met: 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer. 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.  
+ * 3. Neither the name of the copyright holders nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission. 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+typedef struct BuildMon {
+    char	meta_fname[MAXPATHLEN];
+    int		filemon_fd;
+    int		mon_fd;
+    FILE	*mfp;
+} BuildMon;
+
+extern Boolean useMeta;
+
+struct Job;				/* not defined yet */
+void meta_init(const char *);
+void meta_job_start(struct Job *, GNode *);
+void meta_job_child(struct Job *);
+void meta_job_error(struct Job *, GNode *, int, int);
+void meta_job_output(struct Job *, char *, const char *);
+void meta_cmd_finish(void *);
+void meta_job_finish(struct Job *);
+Boolean meta_oodate(GNode *, Boolean);
+void meta_compat_start(void);
+void meta_compat_child(void);
+void meta_compat_parent(void);

Reply via email to