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

#pragma ident	"@(#)continue.c	1.25	96/08/27 SMI"

/*
 * interface to continue a target process (DIRECT_MODE) and helper
 * functions needed by this routine.
 */

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


#include <stdlib.h>
#include <errno.h>

static tnfctl_errcode_t _tnfctl_continue(tnfctl_handle_t *hndl,
    tnfctl_event_t *evt, sigset_t *oldmask, boolean_t watch_forks);
static tnfctl_errcode_t enable_target_state(tnfctl_handle_t *hndl,
    boolean_t watch_forks);
static tnfctl_errcode_t disable_target_state(tnfctl_handle_t *hndl);

/*
 * continue the target process and return the evt it stopped on.
 * If child_hndl is set and we see a fork, return a handle on child
 * process.
 */
tnfctl_errcode_t
tnfctl_continue(tnfctl_handle_t *hndl, tnfctl_event_t *evt,
		tnfctl_handle_t **child_hndl)
{
	tnfctl_errcode_t	prexstat;
	prb_status_t		prbstat;
	boolean_t		lmapok = B_FALSE;
	boolean_t		watch_forks;
	/* set my_evt to something other than TNFCTL_EVENT_TARGGONE */
	tnfctl_event_t		my_evt = TNFCTL_EVENT_EINTR;
	enum event_op_t		dl_evt;
	sigset_t		newmask, oldmask;
	prb_proc_ctl_t		*proc_p;
	prgreg_t		reg0, reg1;

	/* this interface only works for DIRECT_MODE clients */
	if (hndl->mode != DIRECT_MODE)
		return (TNFCTL_ERR_BADARG);

	proc_p = hndl->proc_p;

	if (sigfillset(&newmask) == -1)
		return (tnfctl_status_map(errno));

	watch_forks = (child_hndl != NULL);

	/*
	 * XXXX block all signals.  Synchronous signals like SEGV that
	 * the user could catch and handle will now result in a core dump.
	 * But, this is very unlikely for 2 reasons - most users don't try
	 * to handle synchronous signals - it usually just aborts the process.
	 * And, secondly, the code until we return the original mask is the
	 * place where this synchronous signal would be generated - and, it
	 * is not very much code.
	 */
	if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) == -1)
		return (tnfctl_status_map(errno));

	/*
	 * Target is stopped on entry because tnfctl_continue()
	 * only returns with a stopped target.
	 */

	/* target process shouldn't be stopped when link maps are incosistent */
	while (lmapok == B_FALSE) {
		prexstat = _tnfctl_continue(hndl, &my_evt, &oldmask,
		    watch_forks);
		if (prexstat) {
			if (my_evt == TNFCTL_EVENT_TARGGONE ||
			    my_evt == TNFCTL_EVENT_EXIT) {
				/*
				 * target exited - free obj list and probe
				 * list so that we keep our internal state
				 * correct, else probe control interfaces will
				 * have wrong information.
				 */
			    DBG(fprintf(stderr, "target is gone\n"));
				_tnfctl_free_objs_and_probes(hndl);
				*evt = my_evt;
				return (TNFCTL_ERR_NONE);
			} else if (my_evt == TNFCTL_EVENT_EXEC) {
				*evt = my_evt;
				return (TNFCTL_ERR_NONE);
			} else if (prexstat == TNFCTL_ERR_FILENOTFOUND) {
				return (TNFCTL_ERR_NOPROCESS);
			} else {
				return (prexstat);
			}
		}
		if (my_evt == TNFCTL_EVENT_FORK) {
	/*
	* sanity check.  we should only get here if child_hndl is set
	*/
		    if (child_hndl) {
			    *evt = my_evt;
			    prbstat = prb_proc_get_r0_r1(proc_p,
				&reg0, &reg1);
			    if (prbstat) {
				prexstat = _tnfctl_map_to_errcode(prbstat);
				return (prexstat);
			    }
			    prexstat = tnfctl_pid_open((pid_t)reg0,
						child_hndl);
			    disable_target_state(*child_hndl);
			    return (prexstat);
			}
			return (TNFCTL_ERR_NONE);
		}

		/*
		 * update state in handle
		 * REMIND: Only need to call _tnfctl_refresh_process on
		 * dlopen or dlclose.  Need to take out other functionality
		 * of refresh_process into a separate function that should
		 * be called here.
		 */
		prexstat = _tnfctl_refresh_process(hndl, &lmapok, &dl_evt);
		if (prexstat && (lmapok == B_TRUE))
			return (prexstat);
		prexstat = TNFCTL_ERR_NONE;
	}
	*evt = my_evt;
	/* see if we have more detail about the event */
	if (dl_evt == EVT_OPEN)
		*evt = TNFCTL_EVENT_DLOPEN;
	else if (dl_evt == EVT_CLOSE)
		*evt = TNFCTL_EVENT_DLCLOSE;

	return (TNFCTL_ERR_NONE);
}

/*
 * Continues target and waits for it to stop.
 *	warning: This routine returns TNFCTL_EVENT_DLOPEN for any kind of
 *	dl activity.  Up to the caller to determine the actual DL event.
 */
static tnfctl_errcode_t
_tnfctl_continue(tnfctl_handle_t *hndl, tnfctl_event_t *evt, sigset_t *oldmask,
    boolean_t watch_forks)
{
	tnfctl_errcode_t	prexstat;
	tnfctl_errcode_t	ret_prexstat = TNFCTL_ERR_NONE;
	prb_status_t		prbstat, prbstat2;
	prb_proc_ctl_t		*proc_p;
	prb_proc_state_t 	state;

	proc_p = hndl->proc_p;

	/* set up state before we run process */
	prexstat = enable_target_state(hndl, watch_forks);
	if (prexstat)
		return (prexstat);

again:

	/* resume target */
	prbstat = prb_proc_cont(proc_p);
	if (prbstat) {
		ret_prexstat = _tnfctl_map_to_errcode(prbstat);
		goto end_of_func;
	}

	/* wait on target to stop (standby) */
	prbstat = prb_proc_wait(proc_p, B_TRUE, oldmask);
	if (prbstat) {
		if (prbstat == EINTR) {
			*evt = TNFCTL_EVENT_EINTR;
			prbstat2 = prb_proc_stop(proc_p);
			if (prbstat2) {
				ret_prexstat = _tnfctl_map_to_errcode(prbstat2);
				goto end_of_func;
			}
		} else if (prbstat == ENOENT) {
			/* target process finished */
			if (hndl->called_exit)
				*evt = TNFCTL_EVENT_EXIT;
			else
				*evt = TNFCTL_EVENT_TARGGONE;
			/* return directly - process no longer around */
			return (TNFCTL_ERR_INTERNAL);
		} else {
			ret_prexstat = _tnfctl_map_to_errcode(prbstat);
			goto end_of_func;
		}
	}

	prbstat = prb_proc_state(proc_p, &state);
	if (prbstat) {
		ret_prexstat = _tnfctl_map_to_errcode(prbstat);
		goto end_of_func;
	}
	if (state.ps_isbptfault) {
		/* dlopen or dlclose */
		prbstat = prb_rtld_advance(proc_p);
		if (prbstat) {
			ret_prexstat = _tnfctl_map_to_errcode(prbstat);
			goto end_of_func;
		}
		/*
		 * actually don't know if it is a dlopen or dlclose yet.
		 * But, we return dlopen here.  Up to the caller to determine
		 * which one it actually is.
		 */
		*evt = TNFCTL_EVENT_DLOPEN;
	} else
	    if (state.ps_issysentry) {
		switch (state.ps_syscallnum) {
		case SYS_exec:
		case SYS_execve:
		    *evt = TNFCTL_EVENT_EXEC;
		    ret_prexstat = TNFCTL_ERR_INTERNAL;
		    break;
		case SYS_exit:
		    hndl->called_exit = B_TRUE;
		    goto again;
		default:
		    break;
		}
	    } else if (state.ps_issysexit) {
		    switch (state.ps_syscallnum) {
		    case SYS_fork:
		    case SYS_vfork:
		    case SYS_fork1:
			*evt = TNFCTL_EVENT_FORK;
			break;
		    default:
			break;
		    }
		}
end_of_func:
	/*
	 * disable all our sycall tracing and bpt setup in process when it
	 * is stopped, so that even if the controlling process aborts,
	 * the target could continue running
	 */
	prexstat = disable_target_state(hndl);
	if (prexstat)
		return (prexstat);
	return (ret_prexstat);
}

/*
 * enable the system call tracing and dl activity tracing
 */
static tnfctl_errcode_t
enable_target_state(tnfctl_handle_t *hndl, boolean_t watch_forks)
{
	prb_status_t	prbstat;
	prb_proc_ctl_t	*proc_p;

	proc_p = hndl->proc_p;

	/* trace exec */
	prbstat = prb_proc_entry(proc_p, SYS_execve, PRB_SYS_ADD);
	if (prbstat)
		return (_tnfctl_map_to_errcode(prbstat));
	prbstat = prb_proc_entry(proc_p, SYS_exec, PRB_SYS_ADD);
	if (prbstat)
		return (_tnfctl_map_to_errcode(prbstat));
	/* trace exit */
	prbstat = prb_proc_entry(proc_p, SYS_exit, PRB_SYS_ADD);
	if (prbstat)
		return (_tnfctl_map_to_errcode(prbstat));
	/* trace fork if the caller requests */
	if (watch_forks) {
		prbstat = prb_proc_exit(proc_p, SYS_fork, PRB_SYS_ADD);
		if (prbstat)
			return (_tnfctl_map_to_errcode(prbstat));

		prbstat = prb_proc_exit(proc_p, SYS_vfork, PRB_SYS_ADD);
		if (prbstat)
			return (_tnfctl_map_to_errcode(prbstat));

		prbstat = prb_proc_exit(proc_p, SYS_fork1, PRB_SYS_ADD);
		if (prbstat)
			return (_tnfctl_map_to_errcode(prbstat));
		prbstat = prb_proc_setfork(proc_p, B_TRUE);
		if (prbstat)
			return (_tnfctl_map_to_errcode(prbstat));
	}
	/*
	 * tracing flags for fork and exec will get unset when
	 * process stops. see disable_target_state()
	 */

	/* setup process to stop during dlopen() or dlclose() */
	prbstat = prb_rtld_stalk(proc_p);
	return (_tnfctl_map_to_errcode(prbstat));
}

/*
 * disable the system call tracing and dl activity tracing
 */
static tnfctl_errcode_t
disable_target_state(tnfctl_handle_t *hndl)
{
	prb_status_t	prbstat;
	prb_proc_ctl_t	*proc_p;

	proc_p = hndl->proc_p;

	/* remove the stalking breakpoint while the process is stopped */
	prbstat = prb_rtld_unstalk(proc_p);
	if (prbstat)
		return (_tnfctl_map_to_errcode(prbstat));

	/* remove the exec, exit and fork tracing while stopped */
	prbstat = prb_proc_entry(proc_p, SYS_execve, PRB_SYS_DEL);
	if (prbstat)
		return (_tnfctl_map_to_errcode(prbstat));
	prbstat = prb_proc_entry(proc_p, SYS_exec, PRB_SYS_DEL);
	if (prbstat)
		return (_tnfctl_map_to_errcode(prbstat));
	prbstat = prb_proc_entry(proc_p, SYS_exit, PRB_SYS_DEL);
	if (prbstat)
		return (_tnfctl_map_to_errcode(prbstat));
	prbstat = prb_proc_exit(proc_p, SYS_fork, PRB_SYS_DEL);
	if (prbstat)
	    return (_tnfctl_map_to_errcode(prbstat));
	prbstat = prb_proc_exit(proc_p, SYS_vfork, PRB_SYS_DEL);
	if (prbstat)
	    return (_tnfctl_map_to_errcode(prbstat));
	prbstat = prb_proc_exit(proc_p, SYS_fork1, PRB_SYS_DEL);
	if (prbstat)
	    return (_tnfctl_map_to_errcode(prbstat));
	prbstat = prb_proc_setfork(proc_p, B_FALSE);
	if (prbstat)
	    return (_tnfctl_map_to_errcode(prbstat));

	return (TNFCTL_ERR_NONE);
}
