Path: blob/main/contrib/bmake/filemon/filemon_ktrace.c
39507 views
/* $NetBSD: filemon_ktrace.c,v 1.15 2021/07/31 09:30:17 rillig Exp $ */12/*3* Copyright (c) 2019 The NetBSD Foundation, Inc.4* All rights reserved.5*6* This code is derived from software contributed to The NetBSD Foundation7* by Taylor R. Campbell.8*9* Redistribution and use in source and binary forms, with or without10* modification, are permitted provided that the following conditions11* are met:12* 1. Redistributions of source code must retain the above copyright13* notice, this list of conditions and the following disclaimer.14* 2. Redistributions in binary form must reproduce the above copyright15* notice, this list of conditions and the following disclaimer in the16* documentation and/or other materials provided with the distribution.17*18* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS19* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED20* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR21* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS22* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR23* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF24* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS25* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN26* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)27* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE28* POSSIBILITY OF SUCH DAMAGE.29*/3031#define _KERNTYPES /* register_t */3233#include "filemon.h"3435#include <sys/param.h>36#include <sys/types.h>37#include <sys/rbtree.h>38#include <sys/syscall.h>39#include <sys/time.h>40#include <sys/uio.h>41#include <sys/wait.h>4243#include <sys/ktrace.h>4445#include <assert.h>46#include <err.h>47#include <errno.h>48#include <fcntl.h>49#include <stdbool.h>50#include <stddef.h>51#include <stdio.h>52#include <stdlib.h>53#include <string.h>54#include <unistd.h>5556#ifndef AT_CWD57#define AT_CWD -158#endif5960struct filemon;61struct filemon_key;62struct filemon_state;6364typedef struct filemon_state *filemon_syscall_t(struct filemon *,65const struct filemon_key *, const struct ktr_syscall *);6667static filemon_syscall_t filemon_sys_chdir;68static filemon_syscall_t filemon_sys_execve;69static filemon_syscall_t filemon_sys_exit;70static filemon_syscall_t filemon_sys_fork;71static filemon_syscall_t filemon_sys_link;72static filemon_syscall_t filemon_sys_open;73static filemon_syscall_t filemon_sys_openat;74static filemon_syscall_t filemon_sys_symlink;75static filemon_syscall_t filemon_sys_unlink;76static filemon_syscall_t filemon_sys_rename;7778static filemon_syscall_t *const filemon_syscalls[] = {79[SYS_chdir] = &filemon_sys_chdir,80[SYS_execve] = &filemon_sys_execve,81[SYS_exit] = &filemon_sys_exit,82[SYS_fork] = &filemon_sys_fork,83[SYS_link] = &filemon_sys_link,84[SYS_open] = &filemon_sys_open,85[SYS_openat] = &filemon_sys_openat,86[SYS_symlink] = &filemon_sys_symlink,87[SYS_unlink] = &filemon_sys_unlink,88[SYS_rename] = &filemon_sys_rename,89};9091struct filemon {92int ktrfd; /* kernel writes ktrace events here */93FILE *in; /* we read ktrace events from here */94FILE *out; /* we write filemon events to here */95rb_tree_t active;96pid_t child;9798/* I/O state machine. */99enum {100FILEMON_START = 0,101FILEMON_HEADER,102FILEMON_PAYLOAD,103FILEMON_ERROR,104} state;105unsigned char *p;106size_t resid;107108/* I/O buffer. */109struct ktr_header hdr;110union {111struct ktr_syscall syscall;112struct ktr_sysret sysret;113char namei[PATH_MAX];114unsigned char buf[4096];115} payload;116};117118struct filemon_state {119struct filemon_key {120pid_t pid;121lwpid_t lid;122} key;123struct rb_node node;124int syscode;125void (*show)(struct filemon *, const struct filemon_state *,126const struct ktr_sysret *);127unsigned i;128unsigned npath;129char *path[/*npath*/];130};131132/*ARGSUSED*/133static int134compare_filemon_states(void *cookie, const void *na, const void *nb)135{136const struct filemon_state *Sa = na;137const struct filemon_state *Sb = nb;138139if (Sa->key.pid < Sb->key.pid)140return -1;141if (Sa->key.pid > Sb->key.pid)142return +1;143if (Sa->key.lid < Sb->key.lid)144return -1;145if (Sa->key.lid > Sb->key.lid)146return +1;147return 0;148}149150/*ARGSUSED*/151static int152compare_filemon_key(void *cookie, const void *n, const void *k)153{154const struct filemon_state *S = n;155const struct filemon_key *key = k;156157if (S->key.pid < key->pid)158return -1;159if (S->key.pid > key->pid)160return +1;161if (S->key.lid < key->lid)162return -1;163if (S->key.lid > key->lid)164return +1;165return 0;166}167168static const rb_tree_ops_t filemon_rb_ops = {169.rbto_compare_nodes = &compare_filemon_states,170.rbto_compare_key = &compare_filemon_key,171.rbto_node_offset = offsetof(struct filemon_state, node),172.rbto_context = NULL,173};174175/*176* filemon_path()177*178* Return a pointer to a constant string denoting the `path' of179* the filemon.180*/181const char *182filemon_path(void)183{184185return "ktrace";186}187188/*189* filemon_open()190*191* Allocate a filemon descriptor. Returns NULL and sets errno on192* failure.193*/194struct filemon *195filemon_open(void)196{197struct filemon *F;198int ktrpipe[2];199int error;200201/* Allocate and zero a struct filemon object. */202F = calloc(1, sizeof *F);203if (F == NULL)204return NULL;205206/* Create a pipe for ktrace events. */207if (pipe2(ktrpipe, O_CLOEXEC|O_NONBLOCK) == -1) {208error = errno;209goto fail0;210}211212/* Create a file stream for reading the ktrace events. */213if ((F->in = fdopen(ktrpipe[0], "r")) == NULL) {214error = errno;215goto fail1;216}217ktrpipe[0] = -1; /* claimed by fdopen */218219/*220* Set the fd for writing ktrace events and initialize the221* rbtree. The rest can be safely initialized to zero.222*/223F->ktrfd = ktrpipe[1];224rb_tree_init(&F->active, &filemon_rb_ops);225226/* Success! */227return F;228229fail1: (void)close(ktrpipe[0]);230(void)close(ktrpipe[1]);231fail0: free(F);232errno = error;233return NULL;234}235236/*237* filemon_closefd(F)238*239* Internal subroutine to try to flush and close the output file.240* If F is not open for output, do nothing. Never leaves F open241* for output even on failure. Returns 0 on success; sets errno242* and return -1 on failure.243*/244static int245filemon_closefd(struct filemon *F)246{247int error = 0;248249/* If we're not open, nothing to do. */250if (F->out == NULL)251return 0;252253/*254* Flush it, close it, and null it unconditionally, but be255* careful to return the earliest error in errno.256*/257if (fflush(F->out) == EOF && error == 0)258error = errno;259if (fclose(F->out) == EOF && error == 0)260error = errno;261F->out = NULL;262263/* Set errno and return -1 if anything went wrong. */264if (error != 0) {265errno = error;266return -1;267}268269/* Success! */270return 0;271}272273/*274* filemon_setfd(F, fd)275*276* Cause filemon activity on F to be sent to fd. Claims ownership277* of fd; caller should not use fd afterward, and any duplicates278* of fd may see their file positions changed.279*/280int281filemon_setfd(struct filemon *F, int fd)282{283284/*285* Close an existing output file if done. Fail now if there's286* an error closing.287*/288if ((filemon_closefd(F)) == -1)289return -1;290assert(F->out == NULL);291292/* Open a file stream and claim ownership of the fd. */293if ((F->out = fdopen(fd, "a")) == NULL)294return -1;295296/*297* Print the opening output. Any failure will be deferred298* until closing. For hysterical raisins, we show the parent299* pid, not the child pid.300*/301fprintf(F->out, "# filemon version 4\n");302fprintf(F->out, "# Target pid %jd\n", (intmax_t)getpid());303fprintf(F->out, "V 4\n");304305/* Success! */306return 0;307}308309/*310* filemon_setpid_parent(F, pid)311*312* Set the traced pid, from the parent. Never fails.313*/314void315filemon_setpid_parent(struct filemon *F, pid_t pid)316{317318F->child = pid;319}320321/*322* filemon_setpid_child(F, pid)323*324* Set the traced pid, from the child. Returns 0 on success; sets325* errno and returns -1 on failure.326*/327int328filemon_setpid_child(const struct filemon *F, pid_t pid)329{330int ops, trpoints;331332ops = KTROP_SET|KTRFLAG_DESCEND;333trpoints = KTRFACv2;334trpoints |= KTRFAC_SYSCALL|KTRFAC_NAMEI|KTRFAC_SYSRET;335trpoints |= KTRFAC_INHERIT;336if (fktrace(F->ktrfd, ops, trpoints, pid) == -1)337return -1;338339return 0;340}341342/*343* filemon_close(F)344*345* Close F for output if necessary, and free a filemon descriptor.346* Returns 0 on success; sets errno and returns -1 on failure, but347* frees the filemon descriptor either way;348*/349int350filemon_close(struct filemon *F)351{352struct filemon_state *S;353int error = 0;354355/* Close for output. */356if (filemon_closefd(F) == -1 && error == 0)357error = errno;358359/* Close the ktrace pipe. */360if (fclose(F->in) == EOF && error == 0)361error = errno;362if (close(F->ktrfd) == -1 && error == 0)363error = errno;364365/* Free any active records. */366while ((S = RB_TREE_MIN(&F->active)) != NULL) {367rb_tree_remove_node(&F->active, S);368free(S);369}370371/* Free the filemon descriptor. */372free(F);373374/* Set errno and return -1 if anything went wrong. */375if (error != 0) {376errno = error;377return -1;378}379380/* Success! */381return 0;382}383384/*385* filemon_readfd(F)386*387* Returns a file descriptor which will select/poll ready for read388* when there are filemon events to be processed by389* filemon_process, or -1 if anything has gone wrong.390*/391int392filemon_readfd(const struct filemon *F)393{394395if (F->state == FILEMON_ERROR)396return -1;397return fileno(F->in);398}399400/*401* filemon_dispatch(F)402*403* Internal subroutine to dispatch a filemon ktrace event.404* Silently ignore events that we don't recognize.405*/406static void407filemon_dispatch(struct filemon *F)408{409const struct filemon_key key = {410.pid = F->hdr.ktr_pid,411.lid = F->hdr.ktr_lid,412};413struct filemon_state *S;414415switch (F->hdr.ktr_type) {416case KTR_SYSCALL: {417struct ktr_syscall *call = &F->payload.syscall;418struct filemon_state *S1;419420/* Validate the syscall code. */421if (call->ktr_code < 0 ||422(size_t)call->ktr_code >= __arraycount(filemon_syscalls) ||423filemon_syscalls[call->ktr_code] == NULL)424break;425426/*427* Invoke the syscall-specific logic to create a new428* active state.429*/430S = (*filemon_syscalls[call->ktr_code])(F, &key, call);431if (S == NULL)432break;433434/*435* Insert the active state, or ignore it if there436* already is one.437*438* Collisions shouldn't happen because the states are439* keyed by <pid,lid>, in which syscalls should happen440* sequentially in CALL/RET pairs, but let's be441* defensive.442*/443S1 = rb_tree_insert_node(&F->active, S);444if (S1 != S) {445/* XXX Which one to drop? */446free(S);447break;448}449break;450}451case KTR_NAMEI:452/* Find an active syscall state, or drop it. */453S = rb_tree_find_node(&F->active, &key);454if (S == NULL)455break;456/* Find the position of the next path, or drop it. */457if (S->i >= S->npath)458break;459/* Record the path. */460S->path[S->i++] = strndup(F->payload.namei,461sizeof F->payload.namei);462break;463case KTR_SYSRET: {464struct ktr_sysret *ret = &F->payload.sysret;465unsigned i;466467/* Find and remove an active syscall state, or drop it. */468S = rb_tree_find_node(&F->active, &key);469if (S == NULL)470break;471rb_tree_remove_node(&F->active, S);472473/*474* If the active syscall state matches this return,475* invoke the syscall-specific logic to show a filemon476* event.477*/478/* XXX What to do if syscall code doesn't match? */479if (S->i == S->npath && S->syscode == ret->ktr_code)480S->show(F, S, ret);481482/* Free the state now that it is no longer active. */483for (i = 0; i < S->i; i++)484free(S->path[i]);485free(S);486break;487}488default:489/* Ignore all other ktrace events. */490break;491}492}493494/*495* filemon_process(F)496*497* Process all pending events after filemon_readfd(F) has498* selected/polled ready for read.499*500* Returns -1 on failure, 0 on end of events, and anything else if501* there may be more events.502*503* XXX What about fairness to other activities in the event loop?504* If we stop while there's events buffered in F->in, then select505* or poll may not return ready even though there's work queued up506* in the buffer of F->in, but if we don't stop then ktrace events507* may overwhelm all other activity in the event loop.508*/509int510filemon_process(struct filemon *F)511{512size_t nread;513514top: /* If the child has exited, nothing to do. */515/* XXX What if one thread calls exit while another is running? */516if (F->child == 0)517return 0;518519/* If we're waiting for input, read some. */520if (F->resid > 0) {521nread = fread(F->p, 1, F->resid, F->in);522if (nread == 0) {523if (feof(F->in) != 0)524return 0;525assert(ferror(F->in) != 0);526/*527* If interrupted or would block, there may be528* more events. Otherwise fail.529*/530if (errno == EAGAIN || errno == EINTR)531return 1;532F->state = FILEMON_ERROR;533F->p = NULL;534F->resid = 0;535return -1;536}537assert(nread <= F->resid);538F->p += nread;539F->resid -= nread;540if (F->resid > 0) /* may be more events */541return 1;542}543544/* Process a state transition now that we've read a buffer. */545switch (F->state) {546case FILEMON_START: /* just started filemon; read header next */547F->state = FILEMON_HEADER;548F->p = (void *)&F->hdr;549F->resid = sizeof F->hdr;550goto top;551case FILEMON_HEADER: /* read header */552/* Sanity-check ktrace header; then read payload. */553if (F->hdr.ktr_len < 0 ||554(size_t)F->hdr.ktr_len > sizeof F->payload) {555F->state = FILEMON_ERROR;556F->p = NULL;557F->resid = 0;558errno = EIO;559return -1;560}561F->state = FILEMON_PAYLOAD;562F->p = (void *)&F->payload;563F->resid = (size_t)F->hdr.ktr_len;564goto top;565case FILEMON_PAYLOAD: /* read header and payload */566/* Dispatch ktrace event; then read next header. */567filemon_dispatch(F);568F->state = FILEMON_HEADER;569F->p = (void *)&F->hdr;570F->resid = sizeof F->hdr;571goto top;572default: /* paranoia */573F->state = FILEMON_ERROR;574/*FALLTHROUGH*/575case FILEMON_ERROR: /* persistent error indicator */576F->p = NULL;577F->resid = 0;578errno = EIO;579return -1;580}581}582583static struct filemon_state *584syscall_enter(585const struct filemon_key *key, const struct ktr_syscall *call,586unsigned npath,587void (*show)(struct filemon *, const struct filemon_state *,588const struct ktr_sysret *))589{590struct filemon_state *S;591unsigned i;592593S = calloc(1, offsetof(struct filemon_state, path[npath]));594if (S == NULL)595return NULL;596S->key = *key;597S->show = show;598S->syscode = call->ktr_code;599S->i = 0;600S->npath = npath;601for (i = 0; i < npath; i++)602S->path[i] = NULL; /* paranoia */603604return S;605}606607static void608show_paths(struct filemon *F, const struct filemon_state *S,609const struct ktr_sysret *ret, const char *prefix)610{611unsigned i;612613/* Caller must ensure all paths have been specified. */614assert(S->i == S->npath);615616/*617* Ignore it if it failed or yielded EJUSTRETURN (-2), or if618* we're not producing output.619*/620if (ret->ktr_error != 0 && ret->ktr_error != -2)621return;622if (F->out == NULL)623return;624625/*626* Print the prefix, pid, and paths -- with the paths quoted if627* there's more than one.628*/629fprintf(F->out, "%s %jd", prefix, (intmax_t)S->key.pid);630for (i = 0; i < S->npath; i++) {631const char *q = S->npath > 1 ? "'" : "";632fprintf(F->out, " %s%s%s", q, S->path[i], q);633}634fprintf(F->out, "\n");635}636637static void638show_retval(struct filemon *F, const struct filemon_state *S,639const struct ktr_sysret *ret, const char *prefix)640{641642/*643* Ignore it if it failed or yielded EJUSTRETURN (-2), or if644* we're not producing output.645*/646if (ret->ktr_error != 0 && ret->ktr_error != -2)647return;648if (F->out == NULL)649return;650651fprintf(F->out, "%s %jd %jd\n", prefix, (intmax_t)S->key.pid,652(intmax_t)ret->ktr_retval);653}654655static void656show_chdir(struct filemon *F, const struct filemon_state *S,657const struct ktr_sysret *ret)658{659show_paths(F, S, ret, "C");660}661662static void663show_execve(struct filemon *F, const struct filemon_state *S,664const struct ktr_sysret *ret)665{666show_paths(F, S, ret, "E");667}668669static void670show_fork(struct filemon *F, const struct filemon_state *S,671const struct ktr_sysret *ret)672{673show_retval(F, S, ret, "F");674}675676static void677show_link(struct filemon *F, const struct filemon_state *S,678const struct ktr_sysret *ret)679{680show_paths(F, S, ret, "L"); /* XXX same as symlink */681}682683static void684show_open_read(struct filemon *F, const struct filemon_state *S,685const struct ktr_sysret *ret)686{687show_paths(F, S, ret, "R");688}689690static void691show_open_write(struct filemon *F, const struct filemon_state *S,692const struct ktr_sysret *ret)693{694show_paths(F, S, ret, "W");695}696697static void698show_open_readwrite(struct filemon *F, const struct filemon_state *S,699const struct ktr_sysret *ret)700{701show_paths(F, S, ret, "R");702show_paths(F, S, ret, "W");703}704705static void706show_openat_read(struct filemon *F, const struct filemon_state *S,707const struct ktr_sysret *ret)708{709if (S->path[0][0] != '/')710show_paths(F, S, ret, "A");711show_paths(F, S, ret, "R");712}713714static void715show_openat_write(struct filemon *F, const struct filemon_state *S,716const struct ktr_sysret *ret)717{718if (S->path[0][0] != '/')719show_paths(F, S, ret, "A");720show_paths(F, S, ret, "W");721}722723static void724show_openat_readwrite(struct filemon *F, const struct filemon_state *S,725const struct ktr_sysret *ret)726{727if (S->path[0][0] != '/')728show_paths(F, S, ret, "A");729show_paths(F, S, ret, "R");730show_paths(F, S, ret, "W");731}732733static void734show_symlink(struct filemon *F, const struct filemon_state *S,735const struct ktr_sysret *ret)736{737show_paths(F, S, ret, "L"); /* XXX same as link */738}739740static void741show_unlink(struct filemon *F, const struct filemon_state *S,742const struct ktr_sysret *ret)743{744show_paths(F, S, ret, "D");745}746747static void748show_rename(struct filemon *F, const struct filemon_state *S,749const struct ktr_sysret *ret)750{751show_paths(F, S, ret, "M");752}753754/*ARGSUSED*/755static struct filemon_state *756filemon_sys_chdir(struct filemon *F, const struct filemon_key *key,757const struct ktr_syscall *call)758{759return syscall_enter(key, call, 1, &show_chdir);760}761762/* TODO: monitor fchdir as well */763764/*ARGSUSED*/765static struct filemon_state *766filemon_sys_execve(struct filemon *F, const struct filemon_key *key,767const struct ktr_syscall *call)768{769return syscall_enter(key, call, 1, &show_execve);770}771772static struct filemon_state *773filemon_sys_exit(struct filemon *F, const struct filemon_key *key,774const struct ktr_syscall *call)775{776const register_t *args = (const void *)&call[1];777int status = (int)args[0];778779if (F->out != NULL) {780fprintf(F->out, "X %jd %d\n", (intmax_t)key->pid, status);781if (key->pid == F->child) {782fprintf(F->out, "# Bye bye\n");783F->child = 0;784}785}786return NULL;787}788789/*ARGSUSED*/790static struct filemon_state *791filemon_sys_fork(struct filemon *F, const struct filemon_key *key,792const struct ktr_syscall *call)793{794return syscall_enter(key, call, 0, &show_fork);795}796797/*ARGSUSED*/798static struct filemon_state *799filemon_sys_link(struct filemon *F, const struct filemon_key *key,800const struct ktr_syscall *call)801{802return syscall_enter(key, call, 2, &show_link);803}804805/*ARGSUSED*/806static struct filemon_state *807filemon_sys_open(struct filemon *F, const struct filemon_key *key,808const struct ktr_syscall *call)809{810const register_t *args = (const void *)&call[1];811int flags;812813if (call->ktr_argsize < 2)814return NULL;815flags = (int)args[1];816817if ((flags & O_RDWR) == O_RDWR)818return syscall_enter(key, call, 1, &show_open_readwrite);819else if ((flags & O_WRONLY) == O_WRONLY)820return syscall_enter(key, call, 1, &show_open_write);821else if ((flags & O_RDONLY) == O_RDONLY)822return syscall_enter(key, call, 1, &show_open_read);823else824return NULL; /* XXX Do we care if no read or write? */825}826827/*ARGSUSED*/828static struct filemon_state *829filemon_sys_openat(struct filemon *F, const struct filemon_key *key,830const struct ktr_syscall *call)831{832const register_t *args = (const void *)&call[1];833int flags, fd;834835/*836* XXX: In the .meta log, the base directory is missing, which makes837* all references to relative pathnames useless.838*/839840if (call->ktr_argsize < 3)841return NULL;842fd = (int)args[0];843flags = (int)args[2];844845if (fd == AT_CWD) {846if ((flags & O_RDWR) == O_RDWR)847return syscall_enter(key, call, 1,848&show_open_readwrite);849else if ((flags & O_WRONLY) == O_WRONLY)850return syscall_enter(key, call, 1, &show_open_write);851else if ((flags & O_RDONLY) == O_RDONLY)852return syscall_enter(key, call, 1, &show_open_read);853else854return NULL;855} else {856if ((flags & O_RDWR) == O_RDWR)857return syscall_enter(key, call, 1,858&show_openat_readwrite);859else if ((flags & O_WRONLY) == O_WRONLY)860return syscall_enter(key, call, 1, &show_openat_write);861else if ((flags & O_RDONLY) == O_RDONLY)862return syscall_enter(key, call, 1, &show_openat_read);863else864return NULL;865}866}867868/* TODO: monitor the other *at syscalls as well, not only openat. */869870/*ARGSUSED*/871static struct filemon_state *872filemon_sys_symlink(struct filemon *F, const struct filemon_key *key,873const struct ktr_syscall *call)874{875return syscall_enter(key, call, 2, &show_symlink);876}877878/*ARGSUSED*/879static struct filemon_state *880filemon_sys_unlink(struct filemon *F, const struct filemon_key *key,881const struct ktr_syscall *call)882{883return syscall_enter(key, call, 1, &show_unlink);884}885886/*ARGSUSED*/887static struct filemon_state *888filemon_sys_rename(struct filemon *F, const struct filemon_key *key,889const struct ktr_syscall *call)890{891return syscall_enter(key, call, 2, &show_rename);892}893894895