NetBSD/sys/dev/filemon/filemon.c
2010-09-09 00:10:16 +00:00

367 lines
7.8 KiB
C

/*
* Copyright (c) 2010, Juniper Networks, Inc.
*
* 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 COPYRIGHT HOLDERS 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 COPYRIGHT
* OWNER 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: filemon.c,v 1.1 2010/09/09 00:10:16 sjg Exp $");
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/fcntl.h>
#include <sys/rwlock.h>
#include <sys/condvar.h>
#include <sys/lwp.h>
#include <sys/proc.h>
#include <sys/kmem.h>
#include <sys/syslog.h>
#include "filemon.h"
MODULE(MODULE_CLASS_DRIVER, filemon, NULL);
static dev_type_open(filemon_open);
static struct cdevsw filemon_cdevsw = {
.d_open = filemon_open,
.d_flag = D_MPSAFE,
.d_close = noclose,
.d_read = noread,
.d_write = nowrite,
.d_ioctl = noioctl,
.d_stop = nostop,
.d_tty = notty,
.d_poll = nopoll,
.d_mmap = nommap,
.d_kqfilter = nokqfilter,
};
static int filemon_ioctl(struct file *, u_long, void *);
static int filemon_close(struct file *);
static const struct fileops filemon_fileops = {
.fo_ioctl = filemon_ioctl,
.fo_close = filemon_close,
.fo_read = fbadop_read,
.fo_write = fbadop_write,
.fo_fcntl = fnullop_fcntl,
.fo_poll = fnullop_poll,
.fo_stat = fbadop_stat,
.fo_kqfilter = fnullop_kqfilter,
};
static krwlock_t filemon_mtx;
static TAILQ_HEAD(, filemon) filemons_inuse =
TAILQ_HEAD_INITIALIZER(filemons_inuse);
#ifdef DEBUG
static int logLevel = LOG_DEBUG;
#endif
void
filemon_output(struct filemon * filemon, char *msg, size_t len)
{
struct uio auio;
struct iovec aiov;
if (filemon->fm_fp == NULL)
return;
aiov.iov_base = msg;
aiov.iov_len = len;
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_resid = len;
auio.uio_rw = UIO_WRITE;
auio.uio_offset = (off_t) - 1;
uio_setup_sysspace(&auio);
#ifdef DEBUG
{
char *cp;
int x = 16;
cp = strchr(msg, '\n');
if (cp && cp - msg <= 16)
x = (cp - msg) - 2;
log(logLevel, "filemont_output:('%.*s%s'", x,
(x < 16) ? "..." : "", msg);
}
#endif
(*filemon->fm_fp->f_ops->fo_write) (filemon->fm_fp,
&(filemon->fm_fp->f_offset),
&auio, curlwp->l_cred, FOF_UPDATE_OFFSET);
}
static void
filemon_comment(struct filemon * filemon)
{
int len;
len = snprintf(filemon->fm_msgbufr, sizeof(filemon->fm_msgbufr),
"# filemon version 2\n# Target pid %d\nV 2\n",
curproc->p_pid);
filemon_output(filemon, filemon->fm_msgbufr, len);
}
static struct filemon *
filemon_pid_check(struct proc * p)
{
struct filemon *filemon;
TAILQ_FOREACH(filemon, &filemons_inuse, fm_link) {
if (p->p_pid == filemon->fm_pid)
return (filemon);
}
if (p->p_pptr == NULL)
return (NULL);
return (filemon_pid_check(p->p_pptr));
}
/*
* return exclusive access to a filemon struct
*/
struct filemon *
filemon_lookup(struct proc * p)
{
struct filemon *filemon;
rw_enter(&filemon_mtx, RW_READER);
filemon = filemon_pid_check(p);
if (filemon) {
rw_enter(&filemon->fm_mtx, RW_WRITER);
}
rw_exit(&filemon_mtx);
return filemon;
}
static struct filemon *
filemon_fp_data(struct file * fp, int lck)
{
struct filemon *filemon;
rw_enter(&filemon_mtx, RW_READER);
filemon = fp->f_data;
if (filemon && lck) {
rw_enter(&filemon->fm_mtx, lck);
}
rw_exit(&filemon_mtx);
return filemon;
}
static int n_open = 0;
static int
filemon_open(dev_t dev, int oflags __unused, int mode __unused,
struct lwp * l __unused)
{
struct filemon *filemon;
struct file *fp;
int error, fd;
/* falloc() will use the descriptor for us. */
if ((error = fd_allocfile(&fp, &fd)) != 0)
return error;
filemon = kmem_alloc(sizeof(struct filemon), KM_SLEEP);
if (!filemon)
return ENOMEM;
rw_init(&filemon->fm_mtx);
filemon->fm_fd = -1;
filemon->fm_fp = NULL;
filemon->fm_pid = curproc->p_pid;
rw_enter(&filemon_mtx, RW_WRITER);
n_open++;
TAILQ_INSERT_TAIL(&filemons_inuse, filemon, fm_link);
rw_exit(&filemon_mtx);
return fd_clone(fp, fd, oflags, &filemon_fileops, filemon);
}
static int
filemon_close(struct file * fp)
{
struct filemon *filemon;
#ifdef DEBUG
log(logLevel, "filemon_close()");
#endif
/*
* Follow the same lock order as filemon_lookup()
* and filemon_fp_data() but hold exclusive access to
* filemon_mtx until we are done.
*/
rw_enter(&filemon_mtx, RW_WRITER);
filemon = fp->f_data;
if (!filemon) {
rw_exit(&filemon_mtx);
return EBADF;
}
/* ensure that filemon_lookup() will now fail */
TAILQ_REMOVE(&filemons_inuse, filemon, fm_link);
n_open--;
/* ensure that filemon_fp_data() will now fail */
fp->f_data = NULL;
/*
* once we have exclusive access, it should never be used again
*/
rw_enter(&filemon->fm_mtx, RW_WRITER);
if (filemon->fm_fp) {
fd_putfile(filemon->fm_fd); /* release our reference */
filemon->fm_fp = NULL;
}
rw_exit(&filemon->fm_mtx);
rw_destroy(&filemon->fm_mtx);
kmem_free(filemon, sizeof(struct filemon));
rw_exit(&filemon_mtx);
return (0);
}
static int
filemon_ioctl(struct file * fp, u_long cmd, void *data)
{
int error = 0;
struct filemon *filemon;
#ifdef DEBUG
log(logLevel, "filemon_ioctl(%lu)", cmd);;
#endif
/*
* this ensures we cannot get filemon if it is closing.
*/
filemon = filemon_fp_data(fp, RW_WRITER);
if (!filemon)
return EBADF;
switch (cmd) {
case FILEMON_SET_FD:
/* Set the output file descriptor. */
filemon->fm_fd = *((int *) data);
if ((filemon->fm_fp = fd_getfile(filemon->fm_fd)) == NULL) {
rw_exit(&filemon->fm_mtx);
return EBADF;
}
/* Write the file header. */
filemon_comment(filemon);
break;
case FILEMON_SET_PID:
/* Set the monitored process ID. */
filemon->fm_pid = *((pid_t *) data);
break;
default:
error = EINVAL;
break;
}
rw_exit(&filemon->fm_mtx);
return (error);
}
static void
filemon_load(void *dummy __unused)
{
rw_init(&filemon_mtx);
/* Install the syscall wrappers. */
filemon_wrapper_install();
}
static int
filemon_unload(void)
{
int error = 0;
rw_enter(&filemon_mtx, RW_WRITER);
if (TAILQ_FIRST(&filemons_inuse) != NULL)
error = EBUSY;
else {
/* Deinstall the syscall wrappers. */
filemon_wrapper_deinstall();
}
rw_exit(&filemon_mtx);
if (error == 0) {
rw_destroy(&filemon_mtx);
}
return (error);
}
static int
filemon_modcmd(modcmd_t cmd, void *data)
{
int error = 0;
int bmajor = -1;
int cmajor = -1;
switch (cmd) {
case MODULE_CMD_INIT:
#ifdef DEBUG
logLevel = LOG_INFO;
#endif
filemon_load(data);
error = devsw_attach("filemon", NULL, &bmajor,
&filemon_cdevsw, &cmajor);
break;
case MODULE_CMD_FINI:
error = filemon_unload();
if (!error)
error = devsw_detach(NULL, &filemon_cdevsw);
break;
case MODULE_CMD_STAT:
log(LOG_INFO, "filemon: open=%d", n_open);
break;
default:
error = EOPNOTSUPP;
break;
}
return (error);
}