/*
 * Copyright (c) 1994, by Sun Microsytems, Inc.
 */

#pragma ident	"@(#)util.c	1.33	99/01/05 SMI"

/*
 * Utility functions to initialize tnfctl handle, find functions that
 * can be plugged into probes, find trace file information, and create
 * a trace file for process tracing.
 */

#ifndef DEBUG
#define	NDEBUG	1
#endif

#include "tnfctl_int.h"
#include "dbg.h"

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/param.h>

#include "tnf_buf.h"
/*
 * Defines - Project private interfaces in libtnfprobe.so
 */

#define	TRACEFILE_NAME		"tnf_trace_file_name"
#define	TRACEFILE_SIZE		"tnf_trace_file_size"
#define	TRACEFILE_MIN		"tnf_trace_file_min"
#define	TRACE_ERROR		"_tnfw_b_control"

#define	TRACE_ALLOC		"tnf_trace_alloc"
#define	TRACE_COMMIT		"tnf_trace_commit"
#define	TRACE_ROLLBACK		"tnf_trace_rollback"
#define	DEBUG_ENTRY		"tnf_probe_debug"

#define	PROBE_LIST_HEAD		"__tnf_probe_list_head"
#define	PROBE_LIST_VALID	"__tnf_probe_list_valid"

#define	NONTHREAD_TEST		"tnf_non_threaded_test_addr"
#define	THREAD_TEST		"tnf_threaded_test_addr"
#define	PROBE_THR_SYNC		"__tnf_probe_thr_sync"

#define	MEMSEG_PTR		"__tnf_probe_memseg_p"

/* Project private interfaces in libthread.so */
#define	LIBTHREAD_PRESENT	"thr_probe_getfunc_addr"

/*
 * Local declarations
 */

static tnfctl_errcode_t find_test_func(tnfctl_handle_t *hndl);
static tnfctl_errcode_t find_target_syms(tnfctl_handle_t *hndl);
static tnfctl_errcode_t find_trace_file_info(tnfctl_handle_t *hndl);
static tnfctl_errcode_t check_trace_error(tnfctl_handle_t *hndl);

/*
 * _tnfctl_refresh_process() - search for new shared objects.  If any
 * found, discover probes in new shared objects.
 *	NOT to be called in kernel mode.
 */

tnfctl_errcode_t
_tnfctl_refresh_process(tnfctl_handle_t *hndl, boolean_t *lmap_ok,
			enum event_op_t *dl_evt)
{
	tnfctl_errcode_t	prexstat = TNFCTL_ERR_NONE;
	boolean_t	release_lock;

	assert(hndl->mode != KERNEL_MODE);

	/*LINTED statement has no consequent: else*/
	LOCK(hndl, prexstat, release_lock);

	prexstat = check_trace_error(hndl);
	if (prexstat)
		goto finish_func;

	/*
	 * update the link map. caller decides what to do on
	 * inconsistent link map
	 */
	prexstat = _tnfctl_lmap_update(hndl, lmap_ok, dl_evt);
	if (prexstat)
		goto finish_func;

	/* link map is ok now */
	prexstat = find_test_func(hndl);
	if (prexstat)
		goto finish_func;
	if (*dl_evt != EVT_NONE) {
		prexstat = _tnfctl_find_all_probes(hndl);
		if (prexstat)
			goto finish_func;
	}

finish_func:
	/*LINTED statement has no consequent: else*/
	UNLOCK(hndl, release_lock);

	return (prexstat);
}

/*
 * initialize tnfctl handle for a new target
 */
tnfctl_errcode_t
_tnfctl_set_state(tnfctl_handle_t *hndl)
{
	tnfctl_errcode_t	prexstat = TNFCTL_ERR_NONE;
	boolean_t	lmap_ok;
	enum event_op_t	dl_evt;
	boolean_t	release_lock;

	hndl->targ_pid = hndl->p_getpid(hndl->proc_p);

	/*LINTED statement has no consequent: else*/
	LOCK(hndl, prexstat, release_lock);

	/*
	 * initialize the link map table. If link map is not ok, it is an
	 * error.
	 */
	prexstat = _tnfctl_lmap_update(hndl, &lmap_ok, &dl_evt);
	if (prexstat)
		goto end_func;

	/* find the needed target symbols */
	prexstat = find_target_syms(hndl);
	if (prexstat) {
		/* is libtnfprobe.so loaded in target ? */
		goto end_func;
	}

	prexstat = find_trace_file_info(hndl);
	if (prexstat)
		goto end_func;

	prexstat = find_test_func(hndl);
	if (prexstat)
		goto end_func;

	prexstat = _tnfctl_find_all_probes(hndl);
	if (prexstat)
		goto end_func;

	prexstat = check_trace_error(hndl);
	/* fall into end_func */

end_func:
	/*LINTED statement has no consequent: else*/
	UNLOCK(hndl, release_lock);

	return (prexstat);
}

/*
 * find the test function for a probe.  The test function could change
 * with time, so we have to repeatedly check for the test function to use
 */
static tnfctl_errcode_t
find_test_func(tnfctl_handle_t *hndl)
{
	long		thr_sync;
	int		miscstat;

	if (hndl->mt_target == B_FALSE) {
		/* no libthread linked in */
		hndl->testfunc = hndl->nonthread_test;
	} else {
		/*
		 * check whether libthread/libtnfw have synced up.
		 * If not yet synced up, use non-threaded test function
		 */

		/* assume we are going to use threaded test */
		hndl->testfunc = hndl->thread_test;
		miscstat = hndl->p_read(hndl->proc_p, hndl->thread_sync,
			&thr_sync, sizeof (thr_sync));
		if (miscstat != 0)
			return (TNFCTL_ERR_INTERNAL);
		/* if not yet synced up, change test func to non-threaded one */
		if (thr_sync == 0) {
			hndl->testfunc = hndl->nonthread_test;
		}
	}

	/*
	 * Note: the testfunc in the target can change underneath us because
	 * in an MT program the init section of libthread changes all the
	 * test functions from the non-threaded one to the threaded one.
	 * So, every time we write out a probe, we have to make sure that
	 * we are using the correct test function by not trusting the test
	 * function in our copy of the probe.  A more fool-proof solution
	 * which will allow other fields in the probe to change internally
	 * is to refresh every probe on a _tnfctl_refresh_process()
	 */
	return (TNFCTL_ERR_NONE);
}

/*
 * check_trace_error() - checks whether there was an error in tracing
 *	side effects trace_buf_state and trace_state in hndl
 *	note: call this function only after trace_file_name is set up
 *	in hndl
 */
tnfctl_errcode_t
check_trace_error(tnfctl_handle_t *hndl)
{
	int		miscstat;
	uintptr_t	trace_error_ptr;
	TNFW_B_CONTROL	trace_error_rec;

	/* read in the value of the control structure pointer */
	miscstat = hndl->p_read(hndl->proc_p, hndl->trace_error,
		&trace_error_ptr, sizeof (trace_error_ptr));
	if (miscstat != 0)
		return (TNFCTL_ERR_INTERNAL);

	/* read in the value of the control structure */
	miscstat = hndl->p_read(hndl->proc_p, trace_error_ptr, &trace_error_rec,
		sizeof (trace_error_rec));
	if (miscstat != 0)
		return (TNFCTL_ERR_INTERNAL);

	if (trace_error_rec.tnf_state == TNFW_B_NOBUFFER) {
		/*
		 * massage into correct state for caller - the target might
		 * not have hit the first probe and hence we got "no buffer".
		 * So, if the user had given a file name, return BUF_OK.
		 */
		if (hndl->trace_file_name == NULL)
			hndl->trace_buf_state = TNFCTL_BUF_NONE;
		else
			hndl->trace_buf_state = TNFCTL_BUF_OK;
	} else if (trace_error_rec.tnf_state == TNFW_B_BROKEN) {
		hndl->trace_buf_state = TNFCTL_BUF_BROKEN;
	} else {
		hndl->trace_buf_state = TNFCTL_BUF_OK;
	}

	if (TNFW_B_IS_STOPPED(trace_error_rec.tnf_state))
		hndl->trace_state = B_FALSE;
	else
		hndl->trace_state = B_TRUE;

	return (TNFCTL_ERR_NONE);

}				/* end find_alloc_func */

/*
 * find_target_syms() - finds needed target functions
 * 	sideffects allocfunc, commitfunc, endfunc, rollbackfunc in hndl
 */
static tnfctl_errcode_t
find_target_syms(tnfctl_handle_t *hndl)
{
	tnfctl_errcode_t	prexstat;
	uintptr_t		temp_addr;
	int			miscstat;

	prexstat = _tnfctl_sym_find(hndl, TRACE_ALLOC, &hndl->allocfunc);
	if (prexstat)
		goto end_of_func;

	prexstat = _tnfctl_sym_find(hndl, TRACE_COMMIT, &hndl->commitfunc);
	if (prexstat)
		goto end_of_func;

	prexstat = _tnfctl_sym_find(hndl, TRACE_END_FUNC, &hndl->endfunc);
	if (prexstat)
		goto end_of_func;

	prexstat = _tnfctl_sym_find(hndl, TRACE_ROLLBACK, &hndl->rollbackfunc);
	if (prexstat)
		goto end_of_func;

	prexstat = _tnfctl_sym_find(hndl, PROBE_LIST_HEAD,
					&hndl->probelist_head);
	if (prexstat)
		goto end_of_func;

	prexstat = _tnfctl_sym_find(hndl, TRACE_ERROR, &hndl->trace_error);
	if (prexstat)
		goto end_of_func;

	prexstat = _tnfctl_sym_find(hndl, MEMSEG_PTR, &temp_addr);
	if (prexstat)
		goto end_of_func;

	/* dereference to get the actual address of structure */
	miscstat = hndl->p_read(hndl->proc_p, temp_addr, &hndl->memseg_p,
			sizeof (hndl->memseg_p));
	if (miscstat != 0)
		return (TNFCTL_ERR_INTERNAL);

	prexstat = _tnfctl_sym_find(hndl, PROBE_LIST_VALID,
					&hndl->probelist_valid);
	if (prexstat)
		goto end_of_func;

	prexstat = _tnfctl_sym_find(hndl, NONTHREAD_TEST, &temp_addr);
	if (prexstat)
		goto end_of_func;

	/* dereference to get the actual function address */
	miscstat = hndl->p_read(hndl->proc_p, temp_addr, &hndl->nonthread_test,
			sizeof (hndl->nonthread_test));
	if (miscstat != 0)
		return (TNFCTL_ERR_INTERNAL);

	prexstat = _tnfctl_sym_find(hndl, THREAD_TEST, &temp_addr);
	if (prexstat)
		goto end_of_func;

	/* dereference to get the actual function address */
	miscstat = hndl->p_read(hndl->proc_p, temp_addr, &hndl->thread_test,
			sizeof (hndl->thread_test));
	if (miscstat != 0)
		return (TNFCTL_ERR_INTERNAL);

	prexstat = _tnfctl_sym_find(hndl, PROBE_THR_SYNC, &hndl->thread_sync);
	if (prexstat)
		goto end_of_func;

	prexstat = _tnfctl_sym_find(hndl, LIBTHREAD_PRESENT, &temp_addr);
	if (prexstat) {
		if (prexstat == TNFCTL_ERR_BADARG) {
			/* no libthread linked in */
			hndl->mt_target = B_FALSE;
			/* this is not an error condition */
			prexstat = TNFCTL_ERR_NONE;
		} else {
			return (prexstat);
		}
	} else {
		hndl->mt_target = B_TRUE;
	}

end_of_func:
	if (prexstat == TNFCTL_ERR_BADARG)
		prexstat = TNFCTL_ERR_NOLIBTNFPROBE;

	return (prexstat);
}

/*
 * _tnfctl_create_tracefile() - initializes tracefile, sets the tracefile name
 *	and size
 *	side effects trace_file_name and trace_buf_size in hndl
 */

#define	ZBUFSZ		(64 * 1024)

tnfctl_errcode_t
_tnfctl_create_tracefile(tnfctl_handle_t *hndl, const char *trace_file_name,
			uint_t trace_file_size)
{
	char		*preexisting;
	tnfctl_errcode_t	prexstat;
	int		miscstat;
	char		path[MAXPATHLEN];
	uintptr_t	name_addr, size_addr;
	uint_t		outsize;
	char		zerobuf[ZBUFSZ];
	int		fd, sz, i;

	/* find the neccessary symbols in the target */
	prexstat = _tnfctl_sym_find(hndl, TRACEFILE_NAME, &name_addr);
	if (prexstat) {
		if (prexstat == TNFCTL_ERR_BADARG)
			prexstat = TNFCTL_ERR_INTERNAL;
		return (prexstat);
	}
	prexstat = _tnfctl_sym_find(hndl, TRACEFILE_SIZE, &size_addr);
	if (prexstat) {
		if (prexstat == TNFCTL_ERR_BADARG)
			prexstat = TNFCTL_ERR_INTERNAL;
		return (prexstat);
	}

	/* Double check that a file name doesn't already exist */
	preexisting = NULL;
	prexstat = _tnfctl_readstr_targ(hndl, name_addr, &preexisting);
	if (prexstat) {
		if (preexisting)
			free(preexisting);
		return (prexstat);
	}

	/* There better not be a file name there yet */
	assert(preexisting[0] == '\0');

	/* paranoia - for optimized compilation */
	if (preexisting[0] != '\0')
		return (TNFCTL_ERR_BUFEXISTS);

	/* free memory in preexisting string */
	if (preexisting)
		free(preexisting);

	if (trace_file_size < hndl->trace_min_size) {
		return (TNFCTL_ERR_SIZETOOSMALL);
	}

	/* do we have an absolute, relative or no pathname specified? */
	if (trace_file_name == NULL) {
		return (TNFCTL_ERR_BADARG);
	}
	if (trace_file_name[0] == '/') {
		/* absolute path to tracefile specified */
		if ((strlen(trace_file_name) + 1) > (size_t) MAXPATHLEN) {
			/* directory specification too long */
			return (TNFCTL_ERR_BADARG);
		}
		(void) strcpy(path, trace_file_name);
	} else {
		char		   *cwd;

		/* relative path to tracefile specified */
		cwd = getcwd(NULL, MAXPATHLEN);
		if (!cwd) {
			return (tnfctl_status_map(errno));
		}
		if ((strlen(cwd) + 1 + strlen(trace_file_name) + 1) >
			(size_t) MAXPATHLEN) {
			/* path name too long */
			return (TNFCTL_ERR_BADARG);
		}
		(void) sprintf(path, "%s/%s", cwd, trace_file_name);

		free(cwd);
	}

	outsize = trace_file_size;

	DBG_TNF_PROBE_2(_tnfctl_create_tracefile_1, "libtnfctl",
		"sunw%verbosity 1; sunw%debug 'setting trace file name'",
		tnf_string, tracefile_name, path,
		tnf_long, tracefile_size, outsize);

	/* unlink a previous tracefile (if one exists) */
	(void) unlink(path);

	/* create the new tracefile */
	fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644);
	if (fd < 0)	{
		return (tnfctl_status_map(errno));
	}

	/* zero fill the file */
	(void) memset(zerobuf, 0, ZBUFSZ);
	sz = ZBUFSZ;
	for (i = 0; i < outsize; i += sz) {
		ulong_t		retval;

		sz = ((outsize - i) > ZBUFSZ) ? ZBUFSZ : (outsize - i);
		retval = write(fd, zerobuf, sz);
		if (retval != sz) {
			/* trouble zeroing tracefile */
			return (tnfctl_status_map(errno));
		}
	}

	/* close the file */
	(void) close(fd);

	/* write the tracefile name and size into the target process */
	miscstat = hndl->p_write(hndl->proc_p, name_addr, path,
					strlen(path) + 1);
	if (miscstat != 0)
		return (TNFCTL_ERR_INTERNAL);
	miscstat = hndl->p_write(hndl->proc_p, size_addr, &outsize,
					sizeof (outsize));
	if (miscstat != 0)
		return (TNFCTL_ERR_INTERNAL);

	hndl->trace_file_name = strdup(path);
	if (hndl->trace_file_name == NULL)
		return (TNFCTL_ERR_ALLOCFAIL);
	hndl->trace_buf_size = outsize;
	hndl->trace_buf_state = TNFCTL_BUF_OK;
	return (TNFCTL_ERR_NONE);
}				/* end _tnfctl_create_tracefile */

/*
 * find_trace_file_info()
 *	finds out information about the trace file.
 *	side effects trace_buf_size, trace_min_size, trace_file_name in hndl
 */

static tnfctl_errcode_t
find_trace_file_info(tnfctl_handle_t *hndl)
{
	tnfctl_errcode_t	prexstat;
	int		miscstat;
	char		*preexisting;
	uintptr_t	name_addr, size_addr, min_addr;
	uint_t		outsize, minoutsize;

	/* find the neccessary symbols in the target */
	prexstat = _tnfctl_sym_find(hndl, TRACEFILE_NAME, &name_addr);
	if (prexstat) {
		if (prexstat == TNFCTL_ERR_BADARG)
			prexstat = TNFCTL_ERR_INTERNAL;
		return (prexstat);
	}
	prexstat = _tnfctl_sym_find(hndl, TRACEFILE_SIZE, &size_addr);
	if (prexstat) {
		if (prexstat == TNFCTL_ERR_BADARG)
			prexstat = TNFCTL_ERR_INTERNAL;
		return (prexstat);
	}
	prexstat = _tnfctl_sym_find(hndl, TRACEFILE_MIN, &min_addr);
	if (prexstat) {
		if (prexstat == TNFCTL_ERR_BADARG)
			prexstat = TNFCTL_ERR_INTERNAL;
		return (prexstat);
	}

	/* read file name */
	preexisting = NULL;
	prexstat = _tnfctl_readstr_targ(hndl, name_addr, &preexisting);
	if (prexstat) {
		if (preexisting)
			free(preexisting);
		return (prexstat);
	}

	/* read the minimum file size from the target */
	miscstat = hndl->p_read(hndl->proc_p, min_addr, &minoutsize,
							sizeof (minoutsize));
	if (miscstat != 0)
		return (TNFCTL_ERR_INTERNAL);
	hndl->trace_min_size = minoutsize;

	/* if there is no filename, we are done */
	if (preexisting[0] == '\0') {
		hndl->trace_file_name = NULL;
		hndl->trace_buf_size = 0;
	} else {
		hndl->trace_file_name = preexisting;
		/* read size of file */
		miscstat = hndl->p_read(hndl->proc_p, size_addr,
				&outsize, sizeof (outsize));
		if (miscstat != 0)
			return (TNFCTL_ERR_INTERNAL);
		hndl->trace_buf_size = outsize;
	}

	return (TNFCTL_ERR_NONE);
}				/* end find_trace_file_info */

/*
 * wrapper functions over native /proc functions implemented by proc
 * layer
 */
int
_tnfctl_read_targ(void *proc_p, uintptr_t addr, void *buf, size_t size)
{
	return (prb_proc_read(proc_p, addr, buf, size));
}

int
_tnfctl_write_targ(void *proc_p, uintptr_t addr, void *buf, size_t size)
{
	return (prb_proc_write(proc_p, addr, buf, size));
}

int
_tnfctl_loadobj_iter(void *proc_p, tnfctl_ind_obj_f *func, void *client_data)
{
	prb_loadobj_f *same_func = (prb_loadobj_f *) func;

	return (prb_loadobj_iter(proc_p, same_func, client_data));
}

pid_t
_tnfctl_pid_get(void *proc_p)
{
	return (prb_proc_pid_get(proc_p));
}

/*
 * _tnfctl_readstr_targ() - dereferences a string in the target
 * 	NOTE: There is a similar routine called prb_proc_readstr()
 *	      used by proc layer.  It would be better if there was only
 *	      one of these functions defined.
 */

#define	BUFSZ	256

tnfctl_errcode_t
_tnfctl_readstr_targ(tnfctl_handle_t *hndl, uintptr_t addr, char **outstr_pp)
{
	int		retstat;
	int		bufsz = BUFSZ;
	char		buffer[BUFSZ + 1];
	offset_t	offset;
	char		*ptr, *orig_ptr;

	*outstr_pp = NULL;
	offset = 0;

	/* allocate an inital return buffer */
	ptr = (char *) malloc(BUFSZ);
	if (!ptr) {
		DBG((void) fprintf(stderr,
			"_tnfctl_readstr_targ: malloc failed\n"));
		return (TNFCTL_ERR_ALLOCFAIL);
	}
	/*LINTED constant in conditional context*/
	while (1) {
		int			 i;

		/* read a chunk into our buffer */
		retstat = hndl->p_read(hndl->proc_p, addr + offset, buffer,
								bufsz);
		if (retstat != 0) {

			/*
			 * if we get into trouble with a large read, try again
			 * with a single byte.  Subsequent failiure is real ...
			 */
			if (bufsz > 1) {
				bufsz = 1;
				continue;
			}

			DBG((void) fprintf(stderr,
			    "_tnfctl_readstr_targ: target read failed: \n"));
			free(ptr);
			return (TNFCTL_ERR_INTERNAL);
		}
		/* copy the chracters into the return buffer */
		for (i = 0; i < bufsz; i++) {
			char			c = buffer[i];

			ptr[offset + i] = c;
			if (c == '\0') {
				/* hooray! we saw the end of the string */
				*outstr_pp = ptr;
				return (TNFCTL_ERR_NONE);
			}
		}

		/* bummer, need to grab another bufsz characters */
		offset += bufsz;
		orig_ptr = ptr;
		ptr = (char *) realloc(ptr, offset + bufsz);
		if (!ptr) {
			free(orig_ptr);
			DBG((void) fprintf(stderr,
				"_tnfctl_readstr_targ: realloc failed\n"));
			return (TNFCTL_ERR_ALLOCFAIL);
		}
	}

#if defined(lint)
	return (TNFCTL_ERR_NONE);
#endif

}
