OpenSolaris_b135/lib/libtnfctl/kernel_int.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1994, by Sun Microsytems, Inc.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Interfaces to control kernel tracing and kernel probes
 */

#ifndef DEBUG
#define	NDEBUG	1
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>	/* for strerror() */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/tnf.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <assert.h>

#include "tnfctl_int.h"
#include "kernel_int.h"

/* The TNF pseudo-device */
#define	TNFDRIVER	"/dev/tnfctl"

/* Dummy "test" function  -- just used to flag enabled probes */
#define	PRBK_DUMMY_TEST	((tnf_probe_test_func_t) 4)

/* Dummy "commit" function -- just used to flag trace enabled */
#define	PRBK_DUMMY_COMMIT ((tnf_probe_func_t) 8)

/* Dummy "rollback" function -- just used to flag trace disabled */
#define	PRBK_DUMMY_ROLLBACK ((tnf_probe_func_t) 12)

/* Dummy "end" function */
#define	PRBK_DUMMY_END ((uintptr_t) 16)

/* Dummy "alloc" function */
#define	PRBK_DUMMY_ALLOC ((uintptr_t) 20)

/* Minimum and maximum allowed buffer sizes. */
/* XXX -- maximum should be some function of physmem. */
#define	KERNEL_MINBUF_SIZE	(128 * 1024)
#define	KERNEL_MAXBUF_SIZE	(128 * 1024 * 1024)

static tnfctl_errcode_t prbk_get_buf_attrs(tnfctl_handle_t *hdl);
static tnfctl_errcode_t alloc_probe_space(tnfctl_handle_t *hndl, int maxprobe);

/*
 * Initialize the kernel interface:  Open the TNF control device,
 * and determine the current kernel probes state, including the
 * current pidfilter list.
 */
tnfctl_errcode_t
_tnfctl_prbk_init(tnfctl_handle_t *hdl)
{
	tnfctl_errcode_t prexstat;
	tifiocstate_t kstate;
	int kfd;

	kfd = open(TNFDRIVER, O_RDWR);
	if (kfd < 0) {
		return (tnfctl_status_map(errno));
	}
	if (ioctl(kfd, TIFIOCGSTATE, &kstate) < 0)
		return (tnfctl_status_map(errno));

	hdl->kfd = kfd;
	hdl->kpidfilter_state = kstate.pidfilter_mode;
	hdl->trace_state = !kstate.trace_stopped;
	hdl->trace_min_size = KERNEL_MINBUF_SIZE;
	prexstat = prbk_get_buf_attrs(hdl);
	if (prexstat)
		return (prexstat);

	return (TNFCTL_ERR_NONE);
}

/*
 * Close the TNF control device.
 */
tnfctl_errcode_t
_tnfctl_prbk_close(tnfctl_handle_t *hdl)
{
	if (hdl == NULL)
		return (TNFCTL_ERR_NONE);

	if (close(hdl->kfd) == -1) {
		return (tnfctl_status_map(errno));
	}
	return (TNFCTL_ERR_NONE);
}

/*
 * Returns function addresses that can be plugged into function pointers
 * in kernel probes.  These are actually dummy values that get
 * interpreted by a routine in this file when a probe is flushed.
 */
void
_tnfctl_prbk_get_other_funcs(uintptr_t *allocp, uintptr_t *commitp,
	uintptr_t *rollbackp, uintptr_t *endp)
{
	*allocp = PRBK_DUMMY_ALLOC;
	*commitp = (uintptr_t) PRBK_DUMMY_COMMIT;
	*rollbackp = (uintptr_t) PRBK_DUMMY_ROLLBACK;
	*endp = PRBK_DUMMY_END;
}


/*
 * Returns test function address
 */
void
_tnfctl_prbk_test_func(uintptr_t *outp)
{
	*outp = (uintptr_t) PRBK_DUMMY_TEST;
}

/*
 * Allocate a trace buffer.  Check for reasonable size; reject if there's
 * already a buffer.
 */
tnfctl_errcode_t
_tnfctl_prbk_buffer_alloc(tnfctl_handle_t *hdl, int size)
{
	tifiocstate_t bufstat;
	tnfctl_errcode_t prexstat;
	int saved_val;

	if (ioctl(hdl->kfd, TIFIOCGSTATE, &bufstat) < 0) {
		return (tnfctl_status_map(errno));
	}
	if (bufstat.buffer_state != TIFIOCBUF_NONE) {
		return (TNFCTL_ERR_BUFEXISTS);
	}
	if (size < KERNEL_MINBUF_SIZE) {
		return (TNFCTL_ERR_SIZETOOSMALL);
	} else if (size > KERNEL_MAXBUF_SIZE) {
		/* REMIND: make this an error ? */
		size = KERNEL_MAXBUF_SIZE;
	}
	if (ioctl(hdl->kfd, TIFIOCALLOCBUF, size) < 0) {
		saved_val = errno;
		(void) prbk_get_buf_attrs(hdl);
		return (tnfctl_status_map(saved_val));
	}

	prexstat = prbk_get_buf_attrs(hdl);
	if (prexstat)
		return (prexstat);

	return (TNFCTL_ERR_NONE);
}

/*
 * Deallocate the kernel's trace buffer.
 */
tnfctl_errcode_t
_tnfctl_prbk_buffer_dealloc(tnfctl_handle_t *hdl)
{
	tifiocstate_t bufstat;
	tnfctl_errcode_t prexstat;
	int saved_val;

	if (ioctl(hdl->kfd, TIFIOCGSTATE, &bufstat) < 0) {
		return (tnfctl_status_map(errno));
	}
	if (bufstat.buffer_state == TIFIOCBUF_NONE) {
		return (TNFCTL_ERR_NOBUF);
	}

	if (bufstat.buffer_state == TIFIOCBUF_OK && !bufstat.trace_stopped) {
		return (TNFCTL_ERR_BADDEALLOC);
	}
	if (ioctl(hdl->kfd, TIFIOCDEALLOCBUF) < 0) {
		saved_val = errno;
		(void) prbk_get_buf_attrs(hdl);
		return (tnfctl_status_map(saved_val));
	}

	prexstat = prbk_get_buf_attrs(hdl);
	if (prexstat)
		return (prexstat);

	return (TNFCTL_ERR_NONE);
}

/*
 * Turns kernel global tracing on or off.
 */
tnfctl_errcode_t
_tnfctl_prbk_set_tracing(tnfctl_handle_t *hdl, boolean_t onoff)
{
	if (hdl->trace_state != onoff &&
	    ioctl(hdl->kfd, TIFIOCSTRACING, onoff) < 0) {
		if (errno == ENOMEM && onoff)
			return (TNFCTL_ERR_NOBUF);
		else
			return (tnfctl_status_map(errno));
	}
	hdl->trace_state = onoff;
	return (TNFCTL_ERR_NONE);
}

/*
 * Turn process filter mode on or off.  The process filter is maintained
 * even when process filtering is off, but has no effect:  all processes
 * are traced.
 */
tnfctl_errcode_t
_tnfctl_prbk_set_pfilter_mode(tnfctl_handle_t *hdl, boolean_t onoff)
{
	if (hdl->kpidfilter_state != onoff &&
	    ioctl(hdl->kfd, TIFIOCSPIDFILTER, onoff) < 0) {
		return (tnfctl_status_map(errno));
	}
	hdl->kpidfilter_state = onoff;
	return (TNFCTL_ERR_NONE);
}

/*
 * Return the process filter list.
 */
tnfctl_errcode_t
_tnfctl_prbk_get_pfilter_list(tnfctl_handle_t *hdl, pid_t **ret_list_p,
				int *ret_count)
{
	tifiocstate_t kstate;
	int *filterset;
	int i;
	pid_t *ret_list;

	if (ioctl(hdl->kfd, TIFIOCGSTATE, &kstate) < 0)
		return (tnfctl_status_map(errno));

	if (kstate.pidfilter_size == 0) {
		*ret_count = 0;
		*ret_list_p = NULL;
		return (TNFCTL_ERR_NONE);
	}

	filterset = (int *) malloc((kstate.pidfilter_size + 1) *
					sizeof (pid_t));
	if (filterset == NULL)
		return (TNFCTL_ERR_ALLOCFAIL);
	if (ioctl(hdl->kfd, TIFIOCPIDFILTERGET, filterset) < 0)
		return (tnfctl_status_map(errno));

	/* filterset[0] contains size of array */
	ret_list = malloc(filterset[0] * sizeof (pid_t));
	if (ret_list == NULL)
		return (TNFCTL_ERR_ALLOCFAIL);

	for (i = 1; i <= filterset[0]; ++i)
		ret_list[i - 1] = filterset[i];

	*ret_count = filterset[0];
	(void) free(filterset);
	*ret_list_p = ret_list;
	return (TNFCTL_ERR_NONE);
}

/*
 * Add the pid to the process filter list.
 * check whether it's already in the filter list,
 * and whether the process exists.
 */
tnfctl_errcode_t
_tnfctl_prbk_pfilter_add(tnfctl_handle_t *hdl, pid_t pid_to_add)
{
	if (ioctl(hdl->kfd, TIFIOCSPIDON, pid_to_add) < 0) {
		return (tnfctl_status_map(errno));
	}
	return (TNFCTL_ERR_NONE);
}

/*
 * Drop the pid from the process filter list.
 */
tnfctl_errcode_t
_tnfctl_prbk_pfilter_delete(tnfctl_handle_t *hdl, pid_t pid_to_del)
{
	if (ioctl(hdl->kfd, TIFIOCSPIDOFF, pid_to_del) < 0) {
		if (errno == ESRCH) {
			return (TNFCTL_ERR_NOPROCESS);
		} else {
			return (tnfctl_status_map(errno));
		}
	}
	return (TNFCTL_ERR_NONE);
}

/*
 * get the buffer attributes - side effect tnfctl handle
 */
static tnfctl_errcode_t
prbk_get_buf_attrs(tnfctl_handle_t *hdl)
{
	tifiocstate_t bufstat;

	if (ioctl(hdl->kfd, TIFIOCGSTATE, &bufstat) < 0) {
		return (tnfctl_status_map(errno));
	}

	hdl->trace_file_name = NULL;
	hdl->trace_buf_size = bufstat.buffer_size;
	if (bufstat.buffer_state == TIFIOCBUF_NONE)
		hdl->trace_buf_state = TNFCTL_BUF_NONE;
	else if (bufstat.buffer_state == TIFIOCBUF_BROKEN)
		hdl->trace_buf_state = TNFCTL_BUF_BROKEN;
	else
		hdl->trace_buf_state = TNFCTL_BUF_OK;
	return (TNFCTL_ERR_NONE);
}

/*
 * "Flush" a probe:  i.e., sync up the kernel state with the
 * (desired) state stored in our data structure.
 */
tnfctl_errcode_t
_tnfctl_prbk_flush(tnfctl_handle_t *hndl, prbctlref_t *p)
{
	tnf_probevals_t probebuf;

	probebuf.probenum = p->probe_id;
	probebuf.enabled = (p->wrkprbctl.test_func != NULL);
	probebuf.traced = (p->wrkprbctl.commit_func == PRBK_DUMMY_COMMIT);
	if (ioctl(hndl->kfd, TIFIOCSPROBEVALS, &probebuf) < 0)
		return (tnfctl_status_map(errno));
	return (TNFCTL_ERR_NONE);
}

/*
 * Refresh our understanding of the existing probes in the kernel.
 */
tnfctl_errcode_t
_tnfctl_refresh_kernel(tnfctl_handle_t *hndl)
{
	int maxprobe, i;
	int pos;
	tnfctl_errcode_t prexstat;
	tnf_probevals_t probebuf;
	objlist_t *obj_p;
	prbctlref_t *p = NULL;

	prexstat = prbk_get_buf_attrs(hndl);
	if (prexstat)
		return (prexstat);
	/*
	 * Here is where you'd set obj_p->new to B_FALSE and obj_p->old to
	 * B_TRUE for all existing objects.  We currently don't need
	 * it until we get modload/unload working correctly with probes
	 */
	if (ioctl(hndl->kfd, TIFIOCGMAXPROBE, &maxprobe) < 0)
		return (tnfctl_status_map(errno));
	if (maxprobe == hndl->num_probes) {
		/* XXX Inadequate in the presence of module unloading */
		return (TNFCTL_ERR_NONE);
	}

	prexstat = alloc_probe_space(hndl, maxprobe);
	if (prexstat)
		return (prexstat);

	NOTE(NO_COMPETING_THREADS_NOW)
	obj_p = hndl->objlist;
	NOTE(COMPETING_THREADS_NOW)
	assert((obj_p != NULL) && (obj_p->probes != NULL));

	for (i = 1; i <= maxprobe; ++i) {

		if (i >= (obj_p->min_probe_num + obj_p->probecnt)) {
			obj_p = obj_p->next;
		}

		/* make sure we are in the correct object */
		assert(obj_p != NULL);
		assert((i >= obj_p->min_probe_num) &&
			(i < (obj_p->min_probe_num + obj_p->probecnt)));

		/* get a pointer to correct probe */
		pos = i - obj_p->min_probe_num;
		p = &(obj_p->probes[pos]);
		assert((p != NULL) && (p->probe_id == i) && (p->probe_handle));

		probebuf.probenum = i;
		if (ioctl(hndl->kfd, TIFIOCGPROBEVALS, &probebuf) < 0) {
			if (errno == ENOENT) {
				/*
				 * This probe has vanished due to a module
				 * unload.
				 */
				p->probe_handle->valid = B_FALSE;
			} else {
				return (tnfctl_status_map(errno));
			}
		} else {
			if (p->probe_handle->valid == B_FALSE) {
				/*
				 * seeing this probe for the first time
				 * (alloc_probe_space() initialized this
				 * "valid" field to B_FALSE)
				 */
				/* Update our info about this probe */
				p->wrkprbctl.test_func = (probebuf.enabled) ?
					PRBK_DUMMY_TEST : NULL;
				p->wrkprbctl.commit_func = (probebuf.traced) ?
					PRBK_DUMMY_COMMIT : PRBK_DUMMY_ROLLBACK;
				p->probe_handle->valid = B_TRUE;
				if (probebuf.attrsize < sizeof (probebuf))
					probebuf.attrsize = sizeof (probebuf);
				p->attr_string = malloc(probebuf.attrsize);
				if (p->attr_string == NULL)
					return (TNFCTL_ERR_ALLOCFAIL);
				/*
				 * NOTE: the next statement is a structure
				 * copy and *not* a pointer assignment
				 */
/* LINTED pointer cast may result in improper alignment */
				*(tnf_probevals_t *) p->attr_string = probebuf;
				if (ioctl(hndl->kfd, TIFIOCGPROBESTRING,
						p->attr_string) < 0)
					return (tnfctl_status_map(errno));
				if (hndl->create_func) {
				    p->probe_handle->client_registered_data =
					hndl->create_func(hndl,
						p->probe_handle);
				}
			}
		}
	}
	hndl->num_probes = maxprobe;
	return (TNFCTL_ERR_NONE);
}

/*
 * check if there are any new probes in the kernel that we aren't aware of.
 * If so, allocate space for those probes in our data structure.
 */
static tnfctl_errcode_t
alloc_probe_space(tnfctl_handle_t *hndl, int maxprobe)
{
	objlist_t **o_pp;
	objlist_t *obj_p, *nobj_p;
	int min_probe_num, i;
	prbctlref_t *probe_p;

	/* we know that: hndl->maxprobe != maxprobe */
	NOTE(NO_COMPETING_THREADS_NOW)
	obj_p = hndl->objlist;
	NOTE(COMPETING_THREADS_NOW)
	if (obj_p == NULL) {
		/* no objects allocated */
		o_pp = &(hndl->objlist);
		min_probe_num = 1;
	} else {
		/* find last object */
		while (obj_p->next != NULL) {
			/* reset new_probe field on modload/unload */
			obj_p->new_probe = B_FALSE;
			obj_p = obj_p->next;
		}
		o_pp = &(obj_p->next);
		min_probe_num = obj_p->min_probe_num + obj_p->probecnt;
	}

	nobj_p = calloc(1, sizeof (objlist_t));
	if (nobj_p == NULL)
		return (TNFCTL_ERR_ALLOCFAIL);
	/* add to the linked list */
	*o_pp = nobj_p;
	/* NULL, B_FALSE, or 0's not explicitly initialized */
	nobj_p->new_probe = B_TRUE;
	nobj_p->new = B_TRUE;
	nobj_p->objfd = -1;
	nobj_p->min_probe_num = min_probe_num;
	nobj_p->probecnt = maxprobe - min_probe_num + 1;
	nobj_p->probes = calloc(nobj_p->probecnt,  sizeof (prbctlref_t));
	if (nobj_p->probes == NULL) {
		free(nobj_p);
		return (TNFCTL_ERR_ALLOCFAIL);
	}

	probe_p = &(nobj_p->probes[0]);
	for (i = min_probe_num; i <= maxprobe; i++) {
		NOTE(NO_COMPETING_THREADS_NOW)
		probe_p->obj = nobj_p;
		NOTE(COMPETING_THREADS_NOW)
		probe_p->probe_id = i;
		probe_p->probe_handle = calloc(1, sizeof (tnfctl_probe_t));
		if (probe_p->probe_handle == NULL) {
			if (nobj_p->probes)
				free(nobj_p->probes);
			free(nobj_p);
			return (TNFCTL_ERR_ALLOCFAIL);
		}
		probe_p->probe_handle->valid = B_FALSE;
		probe_p->probe_handle->probe_p = probe_p;
		/* link in probe handle into chain off tnfctl_handle_t */
		probe_p->probe_handle->next = hndl->probe_handle_list_head;
		hndl->probe_handle_list_head = probe_p->probe_handle;

		probe_p++;
	}

	hndl->num_probes = maxprobe;
	return (TNFCTL_ERR_NONE);
}