/*
* 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.
*/
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;
/*
* 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);
/*
* 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 != 0) {
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;
/*
* 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 != 0) {
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);
}
/* 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 > 0) {
nread = fread(F->p, 1, F->resid, F->in);
if (nread == 0) {
if (feof(F->in) != 0)
return 0;
assert(ferror(F->in) != 0);
/*
* 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 > 0) /* 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;
}
}
/* 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 != 0 && 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");
}
/*
* Ignore it if it failed or yielded EJUSTRETURN (-2), or if
* we're not producing output.
*/
if (ret->ktr_error != 0 && ret->ktr_error != -2)
return;
if (F->out == NULL)
return;