From f95186b5d972cc3ab09e2c6c899cee80d4cdd2ec Mon Sep 17 00:00:00 2001
From: Taylor R Campbell <riastradh@NetBSD.org>
Date: Mon, 23 Dec 2019 05:41:16 +0000
Subject: [PATCH] WIP: Draft make meta mode without filemon(4).

---
 usr.bin/make/Makefile  |   5 +-
 usr.bin/make/compat.c  |   2 +-
 usr.bin/make/filemon.c | 882 +++++++++++++++++++++++++++++++++++++++++
 usr.bin/make/filemon.h |  50 +++
 usr.bin/make/job.c     |  46 ++-
 usr.bin/make/meta.c    | 168 ++++++--
 usr.bin/make/meta.h    |   7 +-
 7 files changed, 1111 insertions(+), 49 deletions(-)
 create mode 100644 usr.bin/make/filemon.c
 create mode 100644 usr.bin/make/filemon.h

diff --git a/usr.bin/make/Makefile b/usr.bin/make/Makefile
index d3b9df94dd8b..2986db20d334 100644
--- a/usr.bin/make/Makefile
+++ b/usr.bin/make/Makefile
@@ -15,12 +15,9 @@ SRCS += lstPrev.c
 
 USE_META ?= yes
 .if ${USE_META:tl} != "no"
+SRCS+=	filemon.c
 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
diff --git a/usr.bin/make/compat.c b/usr.bin/make/compat.c
index 538c45603fbd..74d7bb651105 100644
--- a/usr.bin/make/compat.c
+++ b/usr.bin/make/compat.c
@@ -404,7 +404,7 @@ again:
 
 #ifdef USE_META
     if (useMeta) {
-	meta_compat_parent();
+	meta_compat_parent(cpid);
     }
 #endif
 
diff --git a/usr.bin/make/filemon.c b/usr.bin/make/filemon.c
new file mode 100644
index 000000000000..ebd2a0c16fe7
--- /dev/null
+++ b/usr.bin/make/filemon.c
@@ -0,0 +1,882 @@
+/*	$NetBSD$	*/
+
+/*-
+ * Copyright (c) 2019 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Taylor R. Campbell.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#include "filemon.h"
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/rbtree.h>
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include <sys/ktrace.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "make.h"
+
+#ifndef AT_CWD
+#define AT_CWD -1
+#endif
+
+struct filemon;
+struct filemon_key;
+struct filemon_state;
+
+typedef struct filemon_state *filemon_syscall_t(struct filemon *,
+    const struct filemon_key *, const struct ktr_syscall *);
+
+static filemon_syscall_t filemon_sys_chdir;
+static filemon_syscall_t filemon_sys_execve;
+static filemon_syscall_t filemon_sys_exit;
+static filemon_syscall_t filemon_sys_fork;
+static filemon_syscall_t filemon_sys_link;
+static filemon_syscall_t filemon_sys_open;
+static filemon_syscall_t filemon_sys_openat;
+static filemon_syscall_t filemon_sys_symlink;
+static filemon_syscall_t filemon_sys_unlink;
+static filemon_syscall_t filemon_sys_rename;
+
+static filemon_syscall_t *const filemon_syscalls[] = {
+	[SYS_chdir] = &filemon_sys_chdir,
+	[SYS_execve] = &filemon_sys_execve,
+	[SYS_exit] = &filemon_sys_exit,
+	[SYS_fork] = &filemon_sys_fork,
+	[SYS_link] = &filemon_sys_link,
+	[SYS_open] = &filemon_sys_open,
+	[SYS_openat] = &filemon_sys_openat,
+	[SYS_symlink] = &filemon_sys_symlink,
+	[SYS_unlink] = &filemon_sys_unlink,
+	[SYS_rename] = &filemon_sys_rename,
+};
+
+struct filemon {
+	int			ktrfd; /* kernel writes ktrace events here */
+	FILE			*in;   /* we read ktrace events from here */
+	FILE			*out;  /* we write filemon events to here */
+	rb_tree_t		active;
+	pid_t			child;
+
+	/* I/O state machine.  */
+	enum {
+		FILEMON_START = 0,
+		FILEMON_HEADER,
+		FILEMON_PAYLOAD,
+		FILEMON_EOF,
+		FILEMON_ERROR,
+	}			state;
+	unsigned char		*p;
+	size_t			resid;
+
+	/* I/O buffer.  */
+	struct ktr_header	hdr;
+	union {
+		struct ktr_syscall	syscall;
+		struct ktr_sysret	sysret;
+		char			namei[PATH_MAX];
+		unsigned char		buf[4096];
+	}			payload;
+};
+
+struct filemon_state {
+	struct filemon_key {
+		pid_t pid;
+		lwpid_t lid;
+	} key;
+	struct rb_node node;
+	int syscode;
+	void (*show)(struct filemon *, const struct filemon_state *,
+	    const struct ktr_sysret *);
+	unsigned i, npath;
+	char *path[/*npath*/];
+};
+
+static int
+compare_filemon_states(void *cookie MAKE_ATTR_UNUSED, const void *na,
+    const void *nb)
+{
+	const struct filemon_state *Sa = na;
+	const struct filemon_state *Sb = nb;
+
+	if (Sa->key.pid < Sb->key.pid)
+		return -1;
+	if (Sa->key.pid > Sb->key.pid)
+		return +1;
+	if (Sa->key.lid < Sb->key.lid)
+		return -1;
+	if (Sa->key.lid > Sb->key.lid)
+		return +1;
+	return 0;
+}
+
+static int
+compare_filemon_key(void *cookie MAKE_ATTR_UNUSED, const void *n,
+    const void *k)
+{
+	const struct filemon_state *S = n;
+	const struct filemon_key *key = k;
+
+	if (S->key.pid < key->pid)
+		return -1;
+	if (S->key.pid > key->pid)
+		return +1;
+	if (S->key.lid < key->lid)
+		return -1;
+	if (S->key.lid > key->lid)
+		return +1;
+	return 0;
+}
+
+static const rb_tree_ops_t filemon_rb_ops = {
+	.rbto_compare_nodes = &compare_filemon_states,
+	.rbto_compare_key = &compare_filemon_key,
+	.rbto_node_offset = offsetof(struct filemon_state, node),
+	.rbto_context = NULL,
+};
+
+/*
+ * filemon_open()
+ *
+ *	Allocate a filemon descriptor.  Returns NULL and sets errno on
+ *	failure.
+ */
+struct filemon *
+filemon_open(void)
+{
+	struct filemon *F;
+	int ktrpipe[2];
+	int error;
+
+	/* Allocate and zero a struct filemon object.  */
+	F = calloc(1, sizeof(*F));
+	if (F == NULL)
+		return NULL;
+
+	/* Create a pipe for ktrace events.  */
+	if (pipe2(ktrpipe, O_CLOEXEC|O_NONBLOCK) == -1) {
+		error = errno;
+		goto fail0;
+	}
+
+	/* Create a file stream for reading the ktrace events.  */
+	if ((F->in = fdopen(ktrpipe[0], "r")) == NULL) {
+		error = errno;
+		goto fail1;
+	}
+	ktrpipe[0] = -1;	/* claimed by fdopen */
+
+	/*
+	 * Set the fd for writing ktrace events and initialize the
+	 * rbtree.  The rest can be safely initialized to zero.
+	 */
+	F->ktrfd = ktrpipe[1];
+	rb_tree_init(&F->active, &filemon_rb_ops);
+
+	/* Success!  */
+	return F;
+
+fail2: __unused
+	(void)fclose(F->in);
+fail1:	(void)close(ktrpipe[0]);
+	(void)close(ktrpipe[1]);
+fail0:	free(F);
+	errno = error;
+	return NULL;
+}
+
+/*
+ * filemon_closefd(F)
+ *
+ *	Internal subroutine to try to flush and close the output file.
+ *	If F is not open for output, do nothing.  Never leaves F open
+ *	for output even on failure.  Returns 0 on success; sets errno
+ *	and return -1 on failure.
+ */
+static int
+filemon_closefd(struct filemon *F)
+{
+	int error = 0;
+
+	/* If we're not open, nothing to do.  */
+	if (F->out == NULL)
+		return 0;
+
+	/*
+	 * Flush it, close it, and null it unconditionally, but be
+	 * careful to return the earliest error in errno.
+	 */
+	if (fflush(F->out) == EOF)
+		error = errno;
+	if (fclose(F->out) == EOF && error == 0)
+		error = errno;
+	F->out = NULL;
+
+	/* Set errno and return -1 if anything went wrong.  */
+	if (error) {
+		errno = error;
+		return -1;
+	}
+
+	/* Success!  */
+	return 0;
+}
+
+/*
+ * filemon_setfd(F, fd)
+ *
+ *	Cause filemon activity on F to be sent to fd.  Claims ownership
+ *	of fd; caller should not use fd afterward, and any duplicates
+ *	of fd may see their file positions changed.
+ */
+int
+filemon_setfd(struct filemon *F, int fd)
+{
+
+	/*
+	 * Close an existing output file if done.  Fail now if there's
+	 * an error closing.
+	 */
+	if ((filemon_closefd(F)) == -1)
+		return -1;
+	assert(F->out == NULL);
+
+	/* Open a file stream and claim ownership of the fd.  */
+	if ((F->out = fdopen(fd, "a")) == NULL)
+		return -1;
+
+	/*
+	 * Print the opening output.  Any failure will be deferred
+	 * until closing.  For hysterical raisins, we show the parent
+	 * pid, not the child pid.
+	 */
+	fprintf(F->out, "# filemon version 4\n");
+	fprintf(F->out, "# Target pid %jd\n", (intmax_t)getpid());
+	fprintf(F->out, "V 4\n");
+
+	/* Success!  */
+	return 0;
+}
+
+/*
+ * filemon_setpid_parent(F, pid)
+ *
+ *	Set the traced pid, from the parent.  Never fails.
+ */
+void
+filemon_setpid_parent(struct filemon *F, pid_t pid)
+{
+
+	F->child = pid;
+}
+
+/*
+ * filemon_setpid_child(F, pid)
+ *
+ *	Set the traced pid, from the child.  Returns 0 on success; sets
+ *	errno and returns -1 on failure.
+ */
+int
+filemon_setpid_child(const struct filemon *F, pid_t pid)
+{
+	int ops, trpoints;
+
+	ops = KTROP_SET|KTRFLAG_DESCEND;
+	trpoints = KTRFACv2;
+	trpoints |= KTRFAC_SYSCALL|KTRFAC_NAMEI|KTRFAC_SYSRET;
+	trpoints |= KTRFAC_INHERIT;
+	if (fktrace(F->ktrfd, ops, trpoints, pid) == -1)
+		return -1;
+
+	return 0;
+}
+
+/*
+ * filemon_close(F)
+ *
+ *	Close F for output if necessary, and free a filemon descriptor.
+ *	Returns 0 on success; sets errno and returns -1 on failure, but
+ *	frees the filemon descriptor either way;
+ */
+int
+filemon_close(struct filemon *F)
+{
+	struct filemon_state *S;
+	int error = 0;
+
+	/* Close for output.  */
+	if (filemon_closefd(F) == -1)
+		error = errno;
+
+	/* Close the ktrace pipe.  */
+	if (fclose(F->in) == EOF && error == 0)
+		error = errno;
+	if (close(F->ktrfd) == -1 && error == 0)
+		error = errno;
+
+	/* Free any active records.  */
+	while ((S = RB_TREE_MIN(&F->active)) != NULL) {
+		rb_tree_remove_node(&F->active, S);
+		free(S);
+	}
+
+	/* Free the filemon descriptor.  */
+	free(F);
+
+	/* Set errno and return -1 if anything went wrong.  */
+	if (error) {
+		errno = error;
+		return -1;
+	}
+
+	/* Success!  */
+	return 0;
+}
+
+/*
+ * filemon_readfd(F)
+ *
+ *	Returns a file descriptor which will select/poll ready for read
+ *	when there are filemon events to be processed by
+ *	filemon_process, or -1 if anything has gone wrong.
+ */
+int
+filemon_readfd(const struct filemon *F)
+{
+
+	if (F->state == FILEMON_ERROR)
+		return -1;
+	return fileno(F->in);
+}
+
+/*
+ * filemon_dispatch(F)
+ *
+ *	Internal subroutine to dispatch a filemon ktrace event.
+ *	Silently ignore events that we don't recognize.
+ */
+static void
+filemon_dispatch(struct filemon *F)
+{
+	const struct filemon_key key = {
+		.pid = F->hdr.ktr_pid,
+		.lid = F->hdr.ktr_lid,
+	};
+	struct filemon_state *S;
+
+	switch (F->hdr.ktr_type) {
+	case KTR_SYSCALL: {
+		struct ktr_syscall *call = &F->payload.syscall;
+		struct filemon_state *S1;
+
+		/* Validate the syscall code.  */
+		if (call->ktr_code < 0 ||
+		    (size_t)call->ktr_code >= __arraycount(filemon_syscalls) ||
+		    filemon_syscalls[call->ktr_code] == NULL)
+			break;
+
+		/*
+		 * Invoke the syscall-specific logic to create a new
+		 * active state.
+		 */
+		S = (*filemon_syscalls[call->ktr_code])(F, &key, call);
+		if (S == NULL)
+			break;
+
+		/*
+		 * Insert the active state, or ignore it if there
+		 * already is one.
+		 *
+		 * Collisions shouldn't happen because the states are
+		 * keyed by <pid,lid>, in which syscalls should happen
+		 * sequentially in CALL/RET pairs, but let's be
+		 * defensive.
+		 */
+		S1 = rb_tree_insert_node(&F->active, S);
+		if (S1 != S) {
+			/* XXX Which one to drop?  */
+			free(S);
+			break;
+		}
+		break;
+	}
+	case KTR_NAMEI:
+		/* Find an active syscall state, or drop it.  */
+		S = rb_tree_find_node(&F->active, &key);
+		if (S == NULL)
+			break;
+		/* Find the position of the next path, or drop it.  */
+		if (S->i >= S->npath)
+			break;
+		/* Record the path.  */
+		S->path[S->i++] = strndup(F->payload.namei,
+		    sizeof F->payload.namei);
+		break;
+	case KTR_SYSRET: {
+		struct ktr_sysret *ret = &F->payload.sysret;
+		unsigned i;
+
+		/* Find and remove an active syscall state, or drop it.  */
+		S = rb_tree_find_node(&F->active, &key);
+		if (S == NULL)
+			break;
+		rb_tree_remove_node(&F->active, S);
+
+		/*
+		 * If the active syscall state matches this return,
+		 * invoke the syscall-specific logic to show a filemon
+		 * event.
+		 */
+		/* XXX What to do if syscall code doesn't match?  */
+		if (S->i == S->npath && S->syscode == ret->ktr_code)
+			(*S->show)(F, S, ret);
+
+		/* Free the state now that it is no longer active.  */
+		for (i = 0; i < S->i; i++)
+			free(S->path[i]);
+		free(S);
+		break;
+	}
+	default:
+		/* Ignore all other ktrace events.  */
+		break;
+	}
+}
+
+/*
+ * filemon_process(F)
+ *
+ *	Process an event after filemon_readfd(F) has selected/polled
+ *	ready for read.
+ *
+ *	Returns -1 on failure, 0 on end of events, and anything else if
+ *	there may be more events.
+ */
+int
+filemon_process(struct filemon *F)
+{
+	size_t nread;
+
+top:	/* If the child has exited, nothing to do.  */
+	/* XXX What if one thread calls exit while another is running?  */
+	if (F->child == 0)
+		return 0;
+
+	/*
+	 * Run the I/O state machine to process as many events as are
+	 * available immediately.
+	 *
+	 * XXX What about fairness to other activities in the event
+	 * loop?  If we stop while there's events buffered in F->in,
+	 * then select/poll may not return ready even though there's
+	 * work to do, but if we don't stop then ktrace events may
+	 * overwhelm all other activity in the event loop.
+	 */
+	switch (F->state) {
+	case FILEMON_START:	/* just started filemon; read header */
+		F->state = FILEMON_HEADER;
+		F->p = (void *)&F->hdr;
+		F->resid = sizeof F->hdr;
+		/*FALLTHROUGH*/
+	case FILEMON_HEADER:	/* reading header */
+		/* Read what we can.  */
+		nread = fread(F->p, 1, F->resid, F->in);
+		if (nread == 0) {
+			if (feof(F->in))
+				return 0;
+			assert(ferror(F->in));
+			/*
+			 * If interrupted or would block, there may be
+			 * more events.  Otherwise fail.
+			 */
+			if (errno == EAGAIN || errno == EINTR)
+				return 1;
+			F->state = FILEMON_ERROR;
+			return -1;
+		}
+		assert(nread <= F->resid);
+		F->p += nread;
+		F->resid -= nread;
+		if (F->resid)	/* may be more events */
+			return 1;
+		if (F->hdr.ktr_len < 0 ||
+		    (size_t)F->hdr.ktr_len >= sizeof F->payload) {
+			F->state = FILEMON_ERROR;
+			errno = EIO;
+			return -1;
+		}
+		F->state = FILEMON_PAYLOAD;
+		F->p = (void *)&F->payload;
+		F->resid = (size_t)F->hdr.ktr_len;
+		/*FALLTHROUGH*/
+	case FILEMON_PAYLOAD:	/* have read header, now reading payload */
+		nread = fread(F->p, 1, F->resid, F->in);
+		if (nread == 0) {
+			if (feof(F->in))
+				return 0;
+			assert(ferror(F->in));
+			/*
+			 * If interrupted or would block, there may be
+			 * more events.  Otherwise fail.
+			 */
+			if (errno == EAGAIN || errno == EINTR)
+				return 1;
+			F->state = FILEMON_ERROR;
+			return -1;
+		}
+		assert(nread <= F->resid);
+		F->p += nread;
+		F->resid -= nread;
+		if (F->resid)	/* may be more events */
+			return 1;
+		/*
+		 * Dispatch ktrace event, transition to read header,
+		 * and return to event loop.
+		 */
+		filemon_dispatch(F);
+		F->state = FILEMON_HEADER;
+		F->p = (void *)&F->hdr;
+		F->resid = sizeof F->hdr;
+		goto top;
+	default:		/* paranoia */
+		F->state = FILEMON_ERROR;
+		/*FALLTHROUGH*/
+	case FILEMON_ERROR:	/* persistent error indicator */
+		errno = EIO;
+		return -1;
+	}
+	return 1;
+}
+
+static struct filemon_state *
+syscall_enter(struct filemon *F MAKE_ATTR_UNUSED,
+    const struct filemon_key *key, const struct ktr_syscall *call,
+    unsigned npath,
+    void (*show)(struct filemon *, const struct filemon_state *,
+	const struct ktr_sysret *))
+{
+	struct filemon_state *S;
+	unsigned i;
+
+	S = calloc(1, offsetof(struct filemon_state, path[npath]));
+	if (S == NULL)
+		return NULL;
+	S->key = *key;
+	S->show = show;
+	S->syscode = call->ktr_code;
+	S->i = 0;
+	S->npath = npath;
+	for (i = 0; i < npath; i++)
+		 S->path[i] = NULL; /* paranoia */
+
+	return S;
+}
+
+static void
+show_paths(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret, const char *prefix)
+{
+	unsigned i;
+
+	/* Caller must ensure all paths have been specified.  */
+	assert(S->i == S->npath);
+
+	/*
+	 * Ignore it if it failed or yielded EJUSTRETURN (-2), or if
+	 * we're not producing output.
+	 */
+	if (ret->ktr_error && ret->ktr_error != -2)
+		return;
+	if (F->out == NULL)
+		return;
+
+	/*
+	 * Print the prefix, pid, and paths -- with the paths quoted if
+	 * there's more than one.
+	 */
+	fprintf(F->out, "%s %jd", prefix, (intmax_t)S->key.pid);
+	for (i = 0; i < S->npath; i++) {
+		const char *q = S->npath > 1 ? "'" : "";
+		fprintf(F->out, " %s%s%s", q, S->path[i], q);
+	}
+	fprintf(F->out, "\n");
+}
+
+static void
+show_retval(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret, const char *prefix)
+{
+
+	/*
+	 * Ignore it if it failed or yielded EJUSTRETURN (-2), or if
+	 * we're not producing output.
+	 */
+	if (ret->ktr_error && ret->ktr_error != -2)
+		return;
+	if (F->out == NULL)
+		return;
+
+	fprintf(F->out, "%s %jd %jd\n", prefix, (intmax_t)S->key.pid,
+	    (intmax_t)ret->ktr_retval);
+}
+
+static void
+show_chdir(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret)
+{
+	show_paths(F, S, ret, "C");
+}
+
+static void
+show_execve(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret)
+{
+	return show_paths(F, S, ret, "E");
+}
+
+static void
+show_fork(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret)
+{
+	show_retval(F, S, ret, "F");
+}
+
+static void
+show_link(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret)
+{
+	show_paths(F, S, ret, "L"); /* XXX same as symlink */
+}
+
+static void
+show_open_read(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret)
+{
+	show_paths(F, S, ret, "R");
+}
+
+static void
+show_open_write(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret)
+{
+	show_paths(F, S, ret, "W");
+}
+
+static void
+show_open_readwrite(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret)
+{
+	show_paths(F, S, ret, "R");
+	show_paths(F, S, ret, "W");
+}
+
+static void
+show_openat_read(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret)
+{
+	if (S->path[0][0] != '/')
+		show_paths(F, S, ret, "A");
+	show_paths(F, S, ret, "R");
+}
+
+static void
+show_openat_write(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret)
+{
+	if (S->path[0][0] != '/')
+		show_paths(F, S, ret, "A");
+	show_paths(F, S, ret, "W");
+}
+
+static void
+show_openat_readwrite(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret)
+{
+	if (S->path[0][0] != '/')
+		show_paths(F, S, ret, "A");
+	show_paths(F, S, ret, "R");
+	show_paths(F, S, ret, "W");
+}
+
+static void
+show_symlink(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret)
+{
+	show_paths(F, S, ret, "L"); /* XXX same as link */
+}
+
+static void
+show_unlink(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret)
+{
+	show_paths(F, S, ret, "D");
+}
+
+static void
+show_rename(struct filemon *F, const struct filemon_state *S,
+    const struct ktr_sysret *ret)
+{
+	show_paths(F, S, ret, "M");
+}
+
+static struct filemon_state *
+filemon_sys_chdir(struct filemon *F, const struct filemon_key *key,
+    const struct ktr_syscall *call)
+{
+	return syscall_enter(F, key, call, 1, &show_chdir);
+}
+
+static struct filemon_state *
+filemon_sys_execve(struct filemon *F, const struct filemon_key *key,
+    const struct ktr_syscall *call)
+{
+	return syscall_enter(F, key, call, 1, &show_execve);
+}
+
+static struct filemon_state *
+filemon_sys_exit(struct filemon *F, const struct filemon_key *key,
+    const struct ktr_syscall *call)
+{
+	const register_t *args = (const void *)&call[1];
+	int status = args[0];
+
+	if (F->out) {
+		fprintf(F->out, "X %jd %d\n", (intmax_t)key->pid, status);
+		if (key->pid == F->child) {
+			fprintf(F->out, "# Bye bye\n");
+			F->child = 0;
+		}
+	}
+	return NULL;
+}
+
+static struct filemon_state *
+filemon_sys_fork(struct filemon *F, const struct filemon_key *key,
+    const struct ktr_syscall *call)
+{
+	return syscall_enter(F, key, call, 0, &show_fork);
+}
+
+static struct filemon_state *
+filemon_sys_link(struct filemon *F, const struct filemon_key *key,
+    const struct ktr_syscall *call)
+{
+	return syscall_enter(F, key, call, 2, &show_link);
+}
+
+static struct filemon_state *
+filemon_sys_open(struct filemon *F, const struct filemon_key *key,
+    const struct ktr_syscall *call)
+{
+	const register_t *args = (const void *)&call[1];
+	int flags;
+
+	if (call->ktr_argsize < 2)
+		return NULL;
+	flags = args[1];
+
+	if ((flags & O_RDWR) == O_RDWR)
+		return syscall_enter(F, key, call, 1, &show_open_readwrite);
+	else if ((flags & O_WRONLY) == O_WRONLY)
+		return syscall_enter(F, key, call, 1, &show_open_write);
+	else if ((flags & O_RDONLY) == O_RDONLY)
+		return syscall_enter(F, key, call, 1, &show_open_read);
+	else
+		return NULL;	/* XXX Do we care if no read or write?  */
+}
+
+static struct filemon_state *
+filemon_sys_openat(struct filemon *F, const struct filemon_key *key,
+    const struct ktr_syscall *call)
+{
+	const register_t *args = (const void *)&call[1];
+	int flags, fd;
+
+	if (call->ktr_argsize < 3)
+		return NULL;
+	fd = args[0];
+	flags = args[2];
+
+	if (fd == AT_CWD) {
+		if ((flags & O_RDWR) == O_RDWR)
+			return syscall_enter(F, key, call, 1,
+			    &show_open_readwrite);
+		else if ((flags & O_WRONLY) == O_WRONLY)
+			return syscall_enter(F, key, call, 1,
+			    &show_open_write);
+		else if ((flags & O_RDONLY) == O_RDONLY)
+			return syscall_enter(F, key, call, 1, &show_open_read);
+		else
+			return NULL;
+	} else {
+		if ((flags & O_RDWR) == O_RDWR)
+			return syscall_enter(F, key, call, 1,
+			    &show_openat_readwrite);
+		else if ((flags & O_WRONLY) == O_WRONLY)
+			return syscall_enter(F, key, call, 1,
+			    &show_openat_write);
+		else if ((flags & O_RDONLY) == O_RDONLY)
+			return syscall_enter(F, key, call, 1,
+			    &show_openat_read);
+		else
+			return NULL;
+	}
+}
+
+static struct filemon_state *
+filemon_sys_symlink(struct filemon *F, const struct filemon_key *key,
+    const struct ktr_syscall *call)
+{
+	return syscall_enter(F, key, call, 2, &show_symlink);
+}
+
+static struct filemon_state *
+filemon_sys_unlink(struct filemon *F, const struct filemon_key *key,
+    const struct ktr_syscall *call)
+{
+	return syscall_enter(F, key, call, 1, &show_unlink);
+}
+
+static struct filemon_state *
+filemon_sys_rename(struct filemon *F, const struct filemon_key *key,
+    const struct ktr_syscall *call)
+{
+	return syscall_enter(F, key, call, 2, &show_rename);
+}
diff --git a/usr.bin/make/filemon.h b/usr.bin/make/filemon.h
new file mode 100644
index 000000000000..82b7ca73c72f
--- /dev/null
+++ b/usr.bin/make/filemon.h
@@ -0,0 +1,50 @@
+/*	$NetBSD$	*/
+
+/*-
+ * Copyright (c) 2019 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Taylor R. Campbell.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifndef FILEMON_H
+#define	FILEMON_H
+
+#include <sys/types.h>
+
+struct filemon;
+
+struct filemon *
+	filemon_open(void);
+int	filemon_close(struct filemon *);
+
+int	filemon_setfd(struct filemon *, int);
+void	filemon_setpid_parent(struct filemon *, pid_t);
+int	filemon_setpid_child(const struct filemon *, pid_t);
+
+int	filemon_readfd(const struct filemon *);
+int	filemon_process(struct filemon *);
+
+#endif	/* FILEMON_H */
diff --git a/usr.bin/make/job.c b/usr.bin/make/job.c
index 48ec2840e8bf..3ec4ccd10847 100644
--- a/usr.bin/make/job.c
+++ b/usr.bin/make/job.c
@@ -1439,6 +1439,12 @@ JobExec(Job *job, char **argv)
 
     Trace_Log(JOBSTART, job);
 
+#ifdef USE_META
+    if (useMeta) {
+	meta_job_parent(job, cpid);
+    }
+#endif
+
     /*
      * Set the current position in the buffer to the beginning
      * and mark another stream to watch in the outputs mask
@@ -2121,12 +2127,24 @@ Job_CatchOutput(void)
     if (nready == 0)
 	    return;
 
-    for (i = 2; i < nfds; i++) {
+    i = 2;
+#ifdef USE_META
+    if (useMeta)
+	i *= 2;
+#endif
+    for (; i < nfds; i++) {
 	if (!fds[i].revents)
 	    continue;
 	job = jobfds[i];
 	if (job->job_state == JOB_ST_RUNNING)
 	    JobDoOutput(job, FALSE);
+#ifdef USE_META
+	if (useMeta && job->inPollfd != &fds[i]) {
+	    if (meta_job_event(job) <= 0) {
+		fds[i].events = 0; /* never mind */
+	    }
+	}
+#endif
 	if (--nready == 0)
 		return;
     }
@@ -2272,8 +2290,9 @@ Job_Init(void)
     JobCreatePipe(&childExitJob, 3);
 
     /* We can only need to wait for tokens, children and output from each job */
-    fds = bmake_malloc(sizeof (*fds) * (2 + maxJobs));
-    jobfds = bmake_malloc(sizeof (*jobfds) * (2 + maxJobs));
+    /* XXX plus filemon for make meta */
+    fds = bmake_malloc(sizeof (*fds) * (2 + maxJobs) * 2);
+    jobfds = bmake_malloc(sizeof (*jobfds) * (2 + maxJobs) * 2);
 
     /* These are permanent entries and take slots 0 and 1 */
     watchfd(&tokenWaitJob);
@@ -2792,6 +2811,14 @@ watchfd(Job *job)
     jobfds[nfds] = job;
     job->inPollfd = &fds[nfds];
     nfds++;
+#ifdef USE_META
+    if (useMeta) {
+	fds[nfds].fd = meta_job_fd(job);
+	fds[nfds].events = fds[nfds].fd == -1 ? 0 : POLLIN;
+	jobfds[nfds] = job;
+	nfds++;
+    }
+#endif
 }
 
 static void
@@ -2802,6 +2829,13 @@ clearfd(Job *job)
 	Punt("Unwatching unwatched job");
     i = job->inPollfd - fds;
     nfds--;
+#ifdef USE_META
+    if (useMeta) {
+	if (i % 2)
+	    Punt("odd-numbered job with meta");
+	nfds--;
+    }
+#endif
     /*
      * Move last job in table into hole made by dead job.
      */
@@ -2809,6 +2843,12 @@ clearfd(Job *job)
 	fds[i] = fds[nfds];
 	jobfds[i] = jobfds[nfds];
 	jobfds[i]->inPollfd = &fds[i];
+#ifdef USE_META
+	if (useMeta) {
+	    fds[i + 1] = fds[nfds + 1];
+	    jobfds[i + 1] = jobfds[nfds + 1];
+	}
+#endif
     }
     job->inPollfd = NULL;
 }
diff --git a/usr.bin/make/meta.c b/usr.bin/make/meta.c
index 4a3f41df14b1..14bdc60c8788 100644
--- a/usr.bin/make/meta.c
+++ b/usr.bin/make/meta.c
@@ -37,6 +37,7 @@
 #endif
 #include <sys/stat.h>
 #include <sys/ioctl.h>
+#include <sys/wait.h>
 #include <libgen.h>
 #include <errno.h>
 #if !defined(HAVE_CONFIG_H) || defined(HAVE_ERR_H)
@@ -46,12 +47,8 @@
 #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
+#include "filemon.h"
+#define USE_FILEMON
 
 static BuildMon Mybm;			/* for compat */
 static Lst metaBailiwick;		/* our scope of control */
@@ -117,30 +114,24 @@ extern char    **environ;
  * 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)
+meta_open_filemon(BuildMon *pbm)
 {
-    int retry;
-    
-    pbm->mon_fd = pbm->filemon_fd = -1;
+    int dupfd;
+
+    pbm->mon_fd = -1;
+    pbm->filemon = NULL;
     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) {
+    pbm->filemon = filemon_open();
+    if (pbm->filemon == NULL) {
 	useFilemon = FALSE;
-	warn("Could not open %s", _PATH_FILEMON);
+	warn("Could not open filemon");
 	return;
     }
 
@@ -151,12 +142,15 @@ filemon_open(BuildMon *pbm)
      * 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) {
+    if ((dupfd = dup(pbm->mon_fd)) == -1) {
+	err(1, "Could not dup filemon output!");
+    }
+    (void)fcntl(dupfd, F_SETFD, FD_CLOEXEC);
+    if (filemon_setfd(pbm->filemon, dupfd) == -1) {
 	err(1, "Could not set filemon file descriptor!");
     }
     /* we don't need these once we exec */
     (void)fcntl(pbm->mon_fd, F_SETFD, FD_CLOEXEC);
-    (void)fcntl(pbm->filemon_fd, F_SETFD, FD_CLOEXEC);
 }
 
 /*
@@ -570,6 +564,7 @@ meta_init(void)
 {
 #ifdef USE_FILEMON
 	/* this allows makefiles to test if we have filemon support */
+#define	_PATH_FILEMON	"/dev/filemon" /* XXX */
 	Var_Set(".MAKE.PATH_FILEMON", _PATH_FILEMON, VAR_GLOBAL, 0);
 #endif
 }
@@ -680,9 +675,10 @@ meta_job_start(Job *job, GNode *gn)
 #endif
 #ifdef USE_FILEMON
     if (pbm->mfp != NULL && useFilemon) {
-	filemon_open(pbm);
+	meta_open_filemon(pbm);
     } else {
-	pbm->mon_fd = pbm->filemon_fd = -1;
+	pbm->mon_fd = -1;
+	pbm->filemon = NULL;
     }
 #endif
 }
@@ -708,7 +704,7 @@ meta_job_child(Job *job)
 	    pid_t pid;
 
 	    pid = getpid();
-	    if (ioctl(pbm->filemon_fd, FILEMON_SET_PID, &pid) < 0) {
+	    if (filemon_setpid_child(pbm->filemon, pid) == -1) {
 		err(1, "Could not set filemon pid!");
 	    }
 	}
@@ -716,6 +712,59 @@ meta_job_child(Job *job)
 #endif
 }
 
+void
+meta_job_parent(Job *job, pid_t pid)
+{
+#ifdef USE_FILEMON
+    BuildMon *pbm;
+
+    if (job != NULL) {
+	pbm = &job->bm;
+    } else {
+	pbm = &Mybm;
+    }
+    if (useFilemon) {
+	filemon_setpid_parent(pbm->filemon, pid);
+    }
+#endif
+}
+
+int
+meta_job_fd(Job *job)
+{
+#ifdef USE_FILEMON
+    BuildMon *pbm;
+
+    if (job != NULL) {
+	pbm = &job->bm;
+    } else {
+	pbm = &Mybm;
+    }
+    if (useFilemon && pbm->filemon) {
+	return filemon_readfd(pbm->filemon);
+    }
+#endif
+    return -1;
+}
+
+int
+meta_job_event(Job *job)
+{
+#ifdef USE_FILEMON
+    BuildMon *pbm;
+
+    if (job != NULL) {
+	pbm = &job->bm;
+    } else {
+	pbm = &Mybm;
+    }
+    if (useFilemon && pbm->filemon) {
+	return filemon_process(pbm->filemon);
+    }
+#endif
+    return 0;
+}
+
 void
 meta_job_error(Job *job, GNode *gn, int flags, int status)
 {
@@ -794,13 +843,16 @@ meta_cmd_finish(void *pbmp)
 	pbm = &Mybm;
 
 #ifdef USE_FILEMON
-    if (pbm->filemon_fd >= 0) {
-	if (close(pbm->filemon_fd) < 0)
+    if (pbm->filemon) {
+	while (filemon_process(pbm->filemon) > 0)
+	    continue;
+	if (filemon_close(pbm->filemon) == -1)
 	    error = errno;
 	x = filemon_read(pbm->mfp, pbm->mon_fd);
 	if (error == 0 && x != 0)
 	    error = x;
-	pbm->filemon_fd = pbm->mon_fd = -1;
+	pbm->mon_fd = -1;
+	pbm->filemon = NULL;
     } else
 #endif
 	fprintf(pbm->mfp, "\n");	/* ensure end with newline */
@@ -1599,9 +1651,10 @@ meta_compat_start(void)
     BuildMon *pbm = &Mybm;
     
     if (pbm->mfp != NULL && useFilemon) {
-	filemon_open(pbm);
+	meta_open_filemon(pbm);
     } else {
-	pbm->mon_fd = pbm->filemon_fd = -1;
+	pbm->mon_fd = -1;
+	pbm->filemon = NULL;
     }
 #endif
     if (pipe(childPipe) < 0)
@@ -1623,19 +1676,56 @@ meta_compat_child(void)
 }
 
 void
-meta_compat_parent(void)
+meta_compat_parent(pid_t child)
 {
-    FILE *fp;
+    int outfd, metafd, maxfd, nfds;
     char buf[BUFSIZ];
-    
+    fd_set readfds;
+
+    meta_job_parent(NULL, child);
     close(childPipe[1]);			/* child side */
-    fp = fdopen(childPipe[0], "r");
-    while (fgets(buf, sizeof(buf), fp)) {
-	meta_job_output(NULL, buf, "");
-	printf("%s", buf);
-	fflush(stdout);
+    outfd = childPipe[0];
+    metafd = filemon_readfd(Mybm.filemon);
+
+    maxfd = -1;
+    if (outfd > maxfd)
+	    maxfd = outfd;
+    if (metafd > maxfd)
+	    maxfd = metafd;
+
+    while (outfd != -1 || metafd != -1) {
+	FD_ZERO(&readfds);
+	if (outfd != -1) {
+	    FD_SET(outfd, &readfds);
+	}
+	if (metafd != -1) {
+	    FD_SET(metafd, &readfds);
+	}
+	nfds = select(maxfd + 1, &readfds, NULL, NULL, NULL);
+	if (nfds == -1) {
+	    if (errno == EINTR)
+		continue;
+	    err(1, "select");
+	}
+
+	if (outfd != -1 && FD_ISSET(outfd, &readfds)) do {
+	    /* XXX this is not line-buffered */
+	    ssize_t nread = read(outfd, buf, sizeof buf);
+	    if (nread == -1)
+		err(1, "read");
+	    if (nread == 0) {
+		close(outfd);
+		outfd = -1;
+		break;
+	    }
+	    fwrite(buf, 1, (size_t)nread, stdout);
+	    fflush(stdout);
+	} while (0);
+	if (metafd != -1 && FD_ISSET(metafd, &readfds)) {
+	    if (meta_job_event(NULL) <= 0)
+		metafd = -1;
+	}
     }
-    fclose(fp);
 }
 
 #endif	/* USE_META */
diff --git a/usr.bin/make/meta.h b/usr.bin/make/meta.h
index 8f1018e89a7d..fc9c9fd07276 100644
--- a/usr.bin/make/meta.h
+++ b/usr.bin/make/meta.h
@@ -33,7 +33,7 @@
 
 typedef struct BuildMon {
     char	meta_fname[MAXPATHLEN];
-    int		filemon_fd;
+    struct filemon *filemon;
     int		mon_fd;
     FILE	*mfp;
 } BuildMon;
@@ -46,6 +46,9 @@ void meta_finish(void);
 void meta_mode_init(const char *);
 void meta_job_start(struct Job *, GNode *);
 void meta_job_child(struct Job *);
+void meta_job_parent(struct Job *, pid_t);
+int  meta_job_fd(struct Job *);
+int  meta_job_event(struct Job *);
 void meta_job_error(struct Job *, GNode *, int, int);
 void meta_job_output(struct Job *, char *, const char *);
 int  meta_cmd_finish(void *);
@@ -53,4 +56,4 @@ int  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);
+void meta_compat_parent(pid_t);