diff options
author | Simon J. Gerraty <sjg@FreeBSD.org> | 2020-05-20 22:25:46 +0000 |
---|---|---|
committer | Simon J. Gerraty <sjg@FreeBSD.org> | 2020-05-20 22:25:46 +0000 |
commit | 49caa483b3fafffd9cf5197eb30e8bb235aa7410 (patch) | |
tree | 9ee636872e19ac361f51f9f97990c7fa142c2fb6 /contrib/bmake/filemon | |
parent | 4f98ffdd1da28744fbcf31d3d818fc17ea30cb5e (diff) | |
parent | b897d72a5adf5cfad8c79f90e155bae99bdde48c (diff) |
Merge bmake-20200517
Changes since 20181221 are mostly portability related
hence the large gap in versions imported.
There are however some bug fixes, and a rework of filemon handling.
In NetBSD make/filemon/filemon_ktrace.c allows use of fktrace
and elimination of filemon(4) which has not had the TLC it needs.
FreeBSD filemon(4) is in much better shape, so bmake/filemon/filemon_dev.c
allows use of that, with a bit less overhead than the ktrace model.
Summary of changes from ChangeLog
o str.c: empty string does not match % pattern
plus unit-test changes
o var.c: import handling of old sysV style modifier using '%'
o str.c: refactor brk_string
o meta.c: meta_oodate, CHECK_VALID_META is too aggressive for CMD
a blank command is perfectly valid.
o meta.c: meta_oodate, check for corrupted meta file
earlier and more often.
* meta.c: meta_compat_parent check for USE_FILEMON
patch from Soeren Tempel
o meta.c: fix compat mode, need to call meta_job_output()
o job.c: extra fds for meta mode not needed if using filemon_dev
o meta.c: avoid passing NULL to filemon_*() when meta_needed()
returns FALSE.
o filemon/filemon_{dev,ktrace}.c: allow selection of
filemon implementation. filemon_dev.c uses the kernel module
while filemon_ktrace.c leverages the fktrace api available in
NetBSD. filemon_ktrace.c can hopefully form the basis for
adding support for other tracing mechanisms such as strace on
Linux.
o meta.c: when target is out-of-date per normal make rules
record value of .OODATE in meta file.
o parse.c: don't pass NULL to realpath(3)
some versions cannot handle it.
o parse.c: ParseDoDependency: free paths rather than assert
plus more unit-tests
Notes
Notes:
svn path=/head/; revision=361307
Diffstat (limited to 'contrib/bmake/filemon')
-rw-r--r-- | contrib/bmake/filemon/filemon.h | 53 | ||||
-rw-r--r-- | contrib/bmake/filemon/filemon_dev.c | 151 | ||||
-rw-r--r-- | contrib/bmake/filemon/filemon_ktrace.c | 878 |
3 files changed, 1082 insertions, 0 deletions
diff --git a/contrib/bmake/filemon/filemon.h b/contrib/bmake/filemon/filemon.h new file mode 100644 index 000000000000..5a1231d935ec --- /dev/null +++ b/contrib/bmake/filemon/filemon.h @@ -0,0 +1,53 @@ +/* $NetBSD: filemon.h,v 1.2 2020/01/22 22:10:36 sjg 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_ */ diff --git a/contrib/bmake/filemon/filemon_dev.c b/contrib/bmake/filemon/filemon_dev.c new file mode 100644 index 000000000000..1d2aaa98584d --- /dev/null +++ b/contrib/bmake/filemon/filemon_dev.c @@ -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; +} diff --git a/contrib/bmake/filemon/filemon_ktrace.c b/contrib/bmake/filemon/filemon_ktrace.c new file mode 100644 index 000000000000..0a532d68f4f2 --- /dev/null +++ b/contrib/bmake/filemon/filemon_ktrace.c @@ -0,0 +1,878 @@ +/* $NetBSD: filemon_ktrace.c,v 1.2 2020/01/19 20:22:57 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. + */ + +#define _KERNTYPES /* register_t */ + +#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); +} |