/*
 * Copyright (c) 1994,1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)internal.c	1.20	99/03/12 SMI"

/*
 * Implements the routines that are needed only for internal process
 * control.
 */

#ifndef DEBUG
#define	NDEBUG	1
#endif

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

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <link.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/procfs.h>
#include <assert.h>
#include <dlfcn.h>

static int inprocess_read(void *ignore,
    uintptr_t addr, void *buf, size_t size);
static int inprocess_write(void *ignore,
    uintptr_t addr, void *buf, size_t size);
static pid_t inprocess_getpid(void *ignore);
static tnfctl_errcode_t inprocess_get_dtdebug(void *hndl, uintptr_t *ret_val);
static int inprocess_loadobj_iter(void *opq, tnfctl_ind_obj_f *obj_func,
					void *cd);

/*
 * Cause interposition on _dlclose(), dlclose(), _dlopen(), and dlopen()
 */
#pragma weak _dlclose = _tnfctl_dlclose
#pragma weak dlclose = _tnfctl_dlclose

#pragma weak _dlopen = _tnfctl_dlopen
#pragma weak dlopen = _tnfctl_dlopen

/*
 * The lock used to protect the _tnfctl_internal_tracing_flag variable.
 *
 */
mutex_t		_tnfctl_internalguard_lock = DEFAULTMUTEX;
boolean_t	_tnfctl_internal_tracing_flag = 0;
pid_t		_tnfctl_externally_traced_pid = NOPID;

/*
 * Returns a pointer to a tnfctl handle that can do in process probe control.
 */
tnfctl_errcode_t
tnfctl_internal_open(tnfctl_handle_t **ret_val)
{
	tnfctl_handle_t	*hdl;
	tnfctl_errcode_t	prexstat;
	uintptr_t		dbgaddr;

	/* allocate hdl and zero fill */
	hdl = calloc(1, sizeof (*hdl));
	if (hdl == NULL) {
		return (TNFCTL_ERR_ALLOCFAIL);
	}

	hdl->mode = INTERNAL_MODE;
	hdl->called_exit = B_FALSE;

	/* plug in inprocess call back functions */
	hdl->p_read = inprocess_read;
	hdl->p_write = inprocess_write;
	hdl->p_obj_iter = inprocess_loadobj_iter;
	hdl->p_getpid = inprocess_getpid;

	/*
	 * get the address of DT_DEBUG and store it in proc_p
	 * (the handle on the same process is the dbg address)
	 */
	prexstat = inprocess_get_dtdebug(hdl, &dbgaddr);
	if (prexstat) {
		free(hdl);
		return (prexstat);
	}
	hdl->proc_p = (void *) dbgaddr;

	/* initialize state in handle */
	prexstat = _tnfctl_set_state(hdl);
	if (prexstat) {
		free(hdl);
		return (prexstat);
	}
	/* see if process is already being traced */
	prexstat = _tnfctl_internal_getlock();
	if (prexstat) {
		free(hdl);
		return (prexstat);
	}
	*ret_val = hdl;
	return (TNFCTL_ERR_NONE);
}

/*
 * reads a block of memory from the same address space.
 */
static int
inprocess_read(void *ignore, uintptr_t addr, void *buf, size_t size)
{

	DBG_TNF_PROBE_2(inprocess_read_1, "libtnfctl", "sunw%verbosity 3;",
		tnf_long, num_bytes, size,
		tnf_opaque, from_address, addr);

	(void) memcpy(buf, (void *) addr, size);
	return (0);
}

/*
 * writes a block of memory to the same address space.
 */
static int
inprocess_write(void *ignore, uintptr_t addr, void *buf, size_t size)
{

	DBG_TNF_PROBE_2(inprocess_write_1, "libtnfctl", "sunw%verbosity 3;",
		tnf_long, num_bytes, size,
		tnf_opaque, to_address, addr);

	(void) memcpy((void *)addr, buf, size);
	return (0);
}

/*
 * returns the pid of the process.
 */
static pid_t
inprocess_getpid(void *ignore)
{
	return (getpid());
}
extern Elf3264_Dyn _DYNAMIC;

/*
 * returns the address of the DT_DEBUG field in the _DYNAMIC array
 * of the same address space.
 */
static tnfctl_errcode_t
inprocess_get_dtdebug(void *hndl, uintptr_t *ret_val)
{
	Elf3264_Dyn 	*dyn = &_DYNAMIC;
	Elf3264_Dyn	*dp;

	for (dp = dyn; dp->d_tag != DT_NULL; dp++) {
		if (dp->d_tag == DT_DEBUG) {
			*ret_val = (uintptr_t) dp;
			return (TNFCTL_ERR_NONE);
		}
	}
	return (TNFCTL_ERR_INTERNAL);
}

#define	PROCFORMAT	"/proc/%d"

/*
 * iterate over all loadobjects in the same address space calling the
 * callback function "obj_func".
 */
static int
inprocess_loadobj_iter(void *opq, tnfctl_ind_obj_f *obj_func, void *cd)
{
	Elf3264_Dyn	*dtdebug = opq;
	struct r_debug	*r_dbg;
	struct link_map *lmap;
	char		path[MAXPATHLEN];
	int		procfd;
	tnfctl_ind_obj_info_t	loadobj;
	int		retval = 0;	/* sucessful return */

	DBG_TNF_PROBE_0(inprocess_loadobj_iter_start, "libtnfctl",
			"start inprocess_loadobj_iter; sunw%verbosity 1");

	r_dbg = (struct r_debug *)dtdebug->d_un.d_ptr;

	DBG_TNF_PROBE_1(inprocess_loadobj_iter_1, "libtnfctl",
		"sunw%verbosity 1",
		tnf_string, link_map_state,
		(r_dbg->r_state == RT_CONSISTENT) ? "RT_CONSISTENT" :
			(r_dbg->r_state == RT_ADD) ? "RT_ADD" : "RT_DELETE");

	/* bail if link map is not consistent */
	if (r_dbg->r_state != RT_CONSISTENT)
		return (1);

	(void) sprintf(path, PROCFORMAT, (int) getpid());

	/*
	 * opening /proc readonly, so debuggers can still run
	 * We use /proc in order to get fd on the object.
	 */
	procfd = open(path, O_RDONLY);
	if (procfd == -1)
		return (1);

	for (lmap = r_dbg->r_map; lmap; lmap = lmap->l_next) {
		loadobj.text_base = lmap->l_addr;
		loadobj.data_base = lmap->l_addr;
		loadobj.objname = lmap->l_name;
		/*
		 * client of this interface should deal with -1 for objfd,
		 * so no error checking is needed on this ioctl
		 */
		loadobj.objfd = ioctl(procfd, PIOCOPENM, &(lmap->l_addr));

		retval = obj_func(opq, &loadobj, cd);

		/* close the fd */
		if (loadobj.objfd != -1)
			close(loadobj.objfd);

		/* check for error */
		if (retval == 1)
			goto end_of_func;
	}

end_of_func:
	close(procfd);

	DBG_TNF_PROBE_0(inprocess_loadobj_iter_end, "libtnfctl",
			"end inprocess_loadobj_iter; sunw%verbosity 1");
	return (retval);
}

/*
 * The lock that prevents a thread from accessing our cached library list
 * and a dlopen or dlclose happening at the same time in another thread.
 */
mutex_t		_tnfctl_lmap_lock = DEFAULTMUTEX;

/*
 * The flag that indicates that the library list has changed via a
 * dlopen or dlclose.
 */
boolean_t	_tnfctl_libs_changed = B_FALSE;

/*
 * Thread id of the owner of the lock in order to implement a
 * recursive lock i.e. no deadlock if the same thread tries to lock
 * a lock it already holds.
 */
static thread_t	lock_holder = 0;	/* XXX - no tid with 0 */
NOTE(MUTEX_PROTECTS_DATA(warlock::lmap_lock, lock_holder))
NOTE(DATA_READABLE_WITHOUT_LOCK(lock_holder))

/*
 * In the routines below, we will appear to use a different lock if we
 * are running lock_lint/warlock.  We define a macro to represent whichever
 * lock is appropriate.
 */
#if defined(__lock_lint)
#define	LMAP_LOCK	(&warlock_kludge->lmap_lock)
#else
#define	LMAP_LOCK	(&_tnfctl_lmap_lock)
#endif

/*
 * dlclose interposition with a recursive lock so that a .fini section
 * can recursively call dlopen or dlclose while holding _tnfctl_lmap_lock
 * This interposition serializes access to rtld's loadobject list and
 * also updates the flag _tnfctl_libs_changed to indicate a change in
 * the library list.  This flag is checked by operations that update
 * probes so that it can sync up with the new library list and potential
 * new/deleted probes.
 */
int
_tnfctl_dlclose(void *handle)
{
	static int (*real_dlclose)(void *handle) = NULL;
	int retval;
	thread_t tid;

	if (real_dlclose == NULL) {
		real_dlclose = (int (*)(void *)) dlsym(RTLD_NEXT, "dlclose");
	}
	assert(real_dlclose);

	if (mutex_trylock(LMAP_LOCK) != 0) {
		/* don't have lock */
		tid = thr_self();
		if (tid == lock_holder) {
			/* recursive dlopen/dlclose by same thread */
			return ((*real_dlclose)(handle));
		}
		/* not a recursive dlopen/dlclose - wait on lock */
		mutex_lock(LMAP_LOCK);
	}

	/* lock is held now */
	lock_holder = thr_self();
	retval = (*real_dlclose)(handle);

	/*
	 * reset lock_holder so that if _tnfctl_lmap_lock is held by some
	 * other part of the code, we don't assume it is a recursive
	 * dlopen/dlclose
	 */
	lock_holder = 0;
	_tnfctl_libs_changed = B_TRUE;
	mutex_unlock(LMAP_LOCK);

	return (retval);
}

/*
 * dlopen interposition with a recursive lock so that a .init section
 * can recursively call dlopen or dlclose while holding _tnfctl_lmap_lock
 * This interposition serializes access to rtld's loadobject list and
 * also updates the flag _tnfctl_libs_changed to indicate a change in
 * the library list.  This flag is checked by operations that update
 * probes so that it can sync up with the new library list and potential
 * new/deleted probes.
 */
void *
_tnfctl_dlopen(const char *pathname, int mode)
{
	static void * (*real_dlopen)(const char *, int) = NULL;
	void *retval;
	thread_t tid;

	if (real_dlopen == NULL) {
		real_dlopen = (void * (*)(const char *, int))
					dlsym(RTLD_NEXT, "dlopen");
	}
	assert(real_dlopen);

	if (mutex_trylock(LMAP_LOCK) != 0) {
		/* don't have lock */
		tid = thr_self();
		if (tid == lock_holder) {
			/* recursive dlopen/dlclose by same thread */
			return ((*real_dlopen)(pathname, mode));
		}
		/* not a recursive dlopen/dlclose - wait on lock */
		mutex_lock(LMAP_LOCK);
	}

	/* lock is held now */
	lock_holder = thr_self();
	retval = (*real_dlopen)(pathname, mode);

	/*
	 * reset lock_holder so that if _tnfctl_lmap_lock is held by some
	 * other part of the code, we don't assume it is a recursive
	 * dlopen/dlclose
	 */
	lock_holder = 0;
	_tnfctl_libs_changed = B_TRUE;
	mutex_unlock(LMAP_LOCK);

	return (retval);
}

tnfctl_errcode_t
_tnfctl_internal_getlock()
{
	mutex_lock(&_tnfctl_internalguard_lock);
	if (_tnfctl_internal_tracing_flag == 1) {
	/* internal trace control active */
	mutex_unlock(&_tnfctl_internalguard_lock);
	return (TNFCTL_ERR_BUSY);
	}
	_tnfctl_internal_tracing_flag = 1;
	if (_tnfctl_externally_traced_pid == getpid()) {
	/* external trace control is active */
	_tnfctl_internal_tracing_flag = 0;
	mutex_unlock(&_tnfctl_internalguard_lock);
	return (TNFCTL_ERR_BUSY);
	}
	DBG((void) fprintf(stderr, "_tnfctl_internal_getlock: ok to trace %d\n",
	getpid()));
	mutex_unlock(&_tnfctl_internalguard_lock);
	return (TNFCTL_ERR_NONE);
}


#ifdef __lock_lint

/*
 * dummy function for lock_lint (warlock) static lock analysis.
 */
int
warlock_dummy()
{
	int (*fp)();

	return ((*fp)());
}

#endif
