Module Name:    src
Committed By:   riastradh
Date:           Sun Jan 19 19:49:37 UTC 2020

Modified Files:
        src/usr.bin/make: Makefile meta.c
Added Files:
        src/usr.bin/make/filemon: filemon.h filemon_dev.c filemon_ktrace.c
Removed Files:
        src/usr.bin/make: filemon.c filemon.h

Log Message:
Per sjg's suggestion, split filemon API into separate back ends.

By default we use the ktrace back end, but the /dev/filemon back end
is available as a compile-time option, by setting USE_FILEMON=dev in
make.  sjg raised concerns about ktrace performance and would like to
continue using /dev/filemon on FreeBSD (which has seen more
maintenance kernel-side) without forking make.


To generate a diff of this commit:
cvs rdiff -u -r1.66 -r1.67 src/usr.bin/make/Makefile
cvs rdiff -u -r1.1 -r0 src/usr.bin/make/filemon.c src/usr.bin/make/filemon.h
cvs rdiff -u -r1.74 -r1.75 src/usr.bin/make/meta.c
cvs rdiff -u -r0 -r1.1 src/usr.bin/make/filemon/filemon.h \
    src/usr.bin/make/filemon/filemon_dev.c \
    src/usr.bin/make/filemon/filemon_ktrace.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.66 src/usr.bin/make/Makefile:1.67
--- src/usr.bin/make/Makefile:1.66	Sun Jan 19 19:42:32 2020
+++ src/usr.bin/make/Makefile	Sun Jan 19 19:49:36 2020
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.66 2020/01/19 19:42:32 riastradh Exp $
+#	$NetBSD: Makefile,v 1.67 2020/01/19 19:49:36 riastradh Exp $
 #	@(#)Makefile	5.2 (Berkeley) 12/28/90
 
 PROG=	make
@@ -17,9 +17,10 @@ USE_META ?= yes
 .if ${USE_META:tl} != "no"
 SRCS+=	meta.c
 CPPFLAGS+= -DUSE_META
-USE_FILEMON ?= yes
+USE_FILEMON ?= ktrace
 .if ${USE_FILEMON:tl} != "no"
-SRCS+=	filemon.c
+.PATH:	${.CURDIR}/filemon
+SRCS+=	filemon_${USE_FILEMON}.c
 CPPFLAGS+= -DUSE_FILEMON
 .endif
 .endif

Index: src/usr.bin/make/meta.c
diff -u src/usr.bin/make/meta.c:1.74 src/usr.bin/make/meta.c:1.75
--- src/usr.bin/make/meta.c:1.74	Sun Jan 19 19:42:32 2020
+++ src/usr.bin/make/meta.c	Sun Jan 19 19:49:37 2020
@@ -1,4 +1,4 @@
-/*      $NetBSD: meta.c,v 1.74 2020/01/19 19:42:32 riastradh Exp $ */
+/*      $NetBSD: meta.c,v 1.75 2020/01/19 19:49:37 riastradh Exp $ */
 
 /*
  * Implement 'meta' mode.
@@ -46,7 +46,7 @@
 #include "job.h"
 
 #ifdef USE_FILEMON
-#include "filemon.h"
+#include "filemon/filemon.h"
 #endif
 
 static BuildMon Mybm;			/* for compat */
@@ -130,7 +130,7 @@ meta_open_filemon(BuildMon *pbm)
     pbm->filemon = filemon_open();
     if (pbm->filemon == NULL) {
 	useFilemon = FALSE;
-	warn("Could not open filemon");
+	warn("Could not open filemon %s", filemon_path());
 	return;
     }
 
@@ -563,7 +563,7 @@ meta_init(void)
 {
 #ifdef USE_FILEMON
 	/* this allows makefiles to test if we have filemon support */
-	Var_Set(".MAKE.PATH_FILEMON", "ktrace", VAR_GLOBAL, 0); /* XXX */
+	Var_Set(".MAKE.PATH_FILEMON", filemon_path(), VAR_GLOBAL, 0);
 #endif
 }
 

Added files:

Index: src/usr.bin/make/filemon/filemon.h
diff -u /dev/null src/usr.bin/make/filemon/filemon.h:1.1
--- /dev/null	Sun Jan 19 19:49:37 2020
+++ src/usr.bin/make/filemon/filemon.h	Sun Jan 19 19:49:37 2020
@@ -0,0 +1,53 @@
+/*	$NetBSD: filemon.h,v 1.1 2020/01/19 19:49:37 riastradh Exp $	*/
+
+/*-
+ * 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;
+
+const char *
+	filemon_path(void);
+
+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 */
Index: src/usr.bin/make/filemon/filemon_dev.c
diff -u /dev/null src/usr.bin/make/filemon/filemon_dev.c:1.1
--- /dev/null	Sun Jan 19 19:49:37 2020
+++ src/usr.bin/make/filemon/filemon_dev.c	Sun Jan 19 19:49:37 2020
@@ -0,0 +1,151 @@
+/*	$NetBSD: filemon_dev.c,v 1.1 2020/01/19 19:49:37 riastradh Exp $	*/
+
+/*-
+ * Copyright (c) 2020 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/ioctl.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef HAVE_FILEMON_H
+#  include <filemon.h>
+#endif
+
+#ifndef _PATH_FILEMON
+#define	_PATH_FILEMON	"/dev/filemon"
+#endif
+
+struct filemon {
+	int	fd;
+};
+
+const char *
+filemon_path(void)
+{
+
+	return _PATH_FILEMON;
+}
+
+struct filemon *
+filemon_open(void)
+{
+	struct filemon *F;
+	unsigned i;
+	int error;
+
+	/* Allocate and zero a struct filemon object.  */
+	F = calloc(1, sizeof(*F));
+	if (F == NULL)
+		return NULL;
+
+	/* Try opening /dev/filemon, up to six times (cargo cult!).  */
+	for (i = 0; (F->fd = open(_PATH_FILEMON, O_RDWR)) == -1; i++) {
+		if (i == 5) {
+			error = errno;
+			goto fail0;
+		}
+	}
+
+	/* Success!  */
+	return F;
+
+fail0:	free(F);
+	errno = error;
+	return NULL;
+}
+
+int
+filemon_setfd(struct filemon *F, int fd)
+{
+
+	/* Point the kernel at this file descriptor.  */
+	if (ioctl(F->fd, FILEMON_SET_FD, &fd) == -1)
+		return -1;
+
+	/* No need for it in userland any more; close it.  */
+	(void)close(fd);
+
+	/* Success!  */
+	return 0;
+}
+
+void
+filemon_setpid_parent(struct filemon *F, pid_t pid)
+{
+	/* Nothing to do!  */
+}
+
+int
+filemon_setpid_child(const struct filemon *F, pid_t pid)
+{
+
+	/* Just pass it on to the kernel.  */
+	return ioctl(F->fd, FILEMON_SET_PID, &pid);
+}
+
+int
+filemon_close(struct filemon *F)
+{
+	int error = 0;
+
+	/* Close the filemon device fd.  */
+	if (close(F->fd) == -1 && error == 0)
+		error = errno;
+
+	/* Free the filemon descriptor.  */
+	free(F);
+
+	/* Set errno and return -1 if anything went wrong.  */
+	if (error) {
+		errno = error;
+		return -1;
+	}
+
+	/* Success!  */
+	return 0;
+}
+
+int
+filemon_readfd(const struct filemon *F)
+{
+
+	return -1;
+}
+
+int
+filemon_process(struct filemon *F)
+{
+
+	return 0;
+}
Index: src/usr.bin/make/filemon/filemon_ktrace.c
diff -u /dev/null src/usr.bin/make/filemon/filemon_ktrace.c:1.1
--- /dev/null	Sun Jan 19 19:49:37 2020
+++ src/usr.bin/make/filemon/filemon_ktrace.c	Sun Jan 19 19:49:37 2020
@@ -0,0 +1,876 @@
+/*	$NetBSD: filemon_ktrace.c,v 1.1 2020/01/19 19:49:37 riastradh Exp $	*/
+
+/*-
+ * 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 <unistd.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_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;
+	unsigned	npath;
+	char		*path[/*npath*/];
+};
+
+static int
+compare_filemon_states(void *cookie, 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, 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_path()
+ *
+ *	Return a pointer to a constant string denoting the `path' of
+ *	the filemon.
+ */
+const char *
+filemon_path(void)
+{
+
+	return "ktrace";
+}
+
+/*
+ * 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 == 0)
+		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 == 0)
+		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 all pending events 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.
+ *
+ *	XXX What about fairness to other activities in the event loop?
+ *	If we stop while there's events buffered in F->in, then select
+ *	or poll may not return ready even though there's work queued up
+ *	in the buffer of F->in, but if we don't stop then ktrace events
+ *	may overwhelm all other activity in the event loop.
+ */
+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;
+
+	/* If we're waiting for input, read some.  */
+	if (F->resid) {
+		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;
+			F->p = NULL;
+			F->resid = 0;
+			return -1;
+		}
+		assert(nread <= F->resid);
+		F->p += nread;
+		F->resid -= nread;
+		if (F->resid)	/* may be more events */
+			return 1;
+	}
+
+	/* Process a state transition now that we've read a buffer.  */
+	switch (F->state) {
+	case FILEMON_START:	/* just started filemon; read header next */
+		F->state = FILEMON_HEADER;
+		F->p = (void *)&F->hdr;
+		F->resid = sizeof F->hdr;
+		goto top;
+	case FILEMON_HEADER:	/* read header */
+		/* Sanity-check ktrace header; then read payload.  */
+		if (F->hdr.ktr_len < 0 ||
+		    (size_t)F->hdr.ktr_len > sizeof F->payload) {
+			F->state = FILEMON_ERROR;
+			F->p = NULL;
+			F->resid = 0;
+			errno = EIO;
+			return -1;
+		}
+		F->state = FILEMON_PAYLOAD;
+		F->p = (void *)&F->payload;
+		F->resid = (size_t)F->hdr.ktr_len;
+		goto top;
+	case FILEMON_PAYLOAD:	/* read header and payload */
+		/* Dispatch ktrace event; then read next header.  */
+		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 */
+		F->p = NULL;
+		F->resid = 0;
+		errno = EIO;
+		return -1;
+	}
+}
+
+static struct filemon_state *
+syscall_enter(struct filemon *F,
+    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);
+}

Reply via email to