OpenSolaris_b135/lib/libtnfctl/prb_child.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 exec a command and run it till all loadobjects have
 * been loaded (rtld sync point).
 */

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "prb_proc_int.h"
#include "dbg.h"

/*
 * Defines
 */

#define	PRELOAD		"LD_PRELOAD"
#define	LIBPROBE	"libtnfprobe.so.1"

/*
 * Local declarations
 */

static prb_status_t sync_child(int pid, volatile shmem_msg_t *smp,
					prb_proc_ctl_t **proc_pp);

/*
 * prb_child_create()  - this routine instantiates and rendevous with the
 * target child process.  This routine returns an opaque handle for the
 * childs /proc entry.
 */
prb_status_t
prb_child_create(const char *cmdname, char * const *cmdargs,
		const char *loption, const char *libtnfprobe_path,
		char * const *envp, prb_proc_ctl_t **ret_val)
{
	prb_status_t	prbstat;
	pid_t		childpid;
	char		executable_name[PATH_MAX + 2];
	extern char 	**environ;
	char * const *	env_to_use;
	size_t		loptlen, probepathlen;
	volatile shmem_msg_t *smp;

	/* initialize shmem communication buffer to cause child to wait */
	prbstat = prb_shmem_init(&smp);
	if (prbstat)
		return (prbstat);

	/* fork to create the child process */
	childpid = fork();
	if (childpid == (pid_t) - 1) {
		DBG(perror("prb_child_create: fork failed"));
		return (prb_status_map(errno));
	}
	if (childpid == 0) {
		char		   *oldenv;
		char		   *newenv;

		/* ---- CHILD PROCESS ---- */

		DBG_TNF_PROBE_1(prb_child_create_1, "libtnfctl",
			"sunw%verbosity 1; sunw%debug 'child process created'",
			tnf_long, pid, getpid());

		if (envp) {
			env_to_use = envp;
			goto ContChild;
		}

		/* append libtnfprobe.so to the LD_PRELOAD environment */
		loptlen = (loption) ? strlen(loption) : 0;
		/* probepathlen has a "/" added in ("+ 1") */
		probepathlen = (libtnfprobe_path) ?
				(strlen(libtnfprobe_path) + 1) : 0;
		oldenv = getenv(PRELOAD);
		if (oldenv) {
			newenv = (char *) malloc(strlen(PRELOAD) +
				1 +	/* "=" */
				strlen(oldenv) +
				1 +	/* " " */
				probepathlen +
				strlen(LIBPROBE) +
				1 +	/* " " */
				loptlen +
				1);	/* NULL */

			if (!newenv)
				goto ContChild;
			(void) strcpy(newenv, PRELOAD);
			(void) strcat(newenv, "=");
			(void) strcat(newenv, oldenv);
			(void) strcat(newenv, " ");
			if (probepathlen) {
				(void) strcat(newenv, libtnfprobe_path);
				(void) strcat(newenv, "/");
			}
			(void) strcat(newenv, LIBPROBE);
			if (loptlen) {
				(void) strcat(newenv, " ");
				(void) strcat(newenv, loption);
			}
		} else {
			newenv = (char *) malloc(strlen(PRELOAD) +
				1 +	/* "=" */
				probepathlen +
				strlen(LIBPROBE) +
				1 +	/* " " */
				loptlen +
				1);	/* NULL */
			if (!newenv)
				goto ContChild;
			(void) strcpy(newenv, PRELOAD);
			(void) strcat(newenv, "=");
			if (probepathlen) {
				(void) strcat(newenv, libtnfprobe_path);
				(void) strcat(newenv, "/");
			}
			(void) strcat(newenv, LIBPROBE);
			if (loptlen) {
				(void) strcat(newenv, " ");
				(void) strcat(newenv, loption);
			}
		}
		(void) putenv((char *) newenv);
		env_to_use = environ;
		/*
		 * We don't check the return value of putenv because the
		 * desired libraries might already be in the target, even
		 * if our effort to change the environment fails.  We
		 * should continue either way ...
		 */
ContChild:
		/* wait until the parent releases us */
		(void) prb_shmem_wait(smp);

		DBG_TNF_PROBE_1(prb_child_create_2, "libtnfctl",
			"sunw%verbosity 2; "
			"sunw%debug 'child process about to exec'",
			tnf_string, cmdname, cmdname);

		/*
		 * make the child it's own process group.
		 * This is so that signals delivered to parent are not
		 * also delivered to child.
		 */
		(void) setpgrp();
		prbstat = find_executable(cmdname, executable_name);
		if (prbstat) {
			DBG((void) fprintf(stderr, "prb_child_create: %s\n",
					prb_status_str(prbstat)));
			/* parent waits for exit */
			_exit(1);
		}
		if (execve(executable_name, cmdargs, env_to_use) == -1) {
			DBG(perror("prb_child_create: exec failed"));
			_exit(1);
		}

		/* Never reached */
		_exit(1);
	}
	/* ---- PARENT PROCESS ---- */
	/* child is waiting for us */

	prbstat = sync_child(childpid, smp, ret_val);
	if (prbstat) {
		return (prbstat);
	}

	return (PRB_STATUS_OK);

}

/*
 * interface that registers the address of the debug structure
 * in the target process.  This is where the linker maintains all
 * the information about the loadobjects
 */
void
prb_dbgaddr(prb_proc_ctl_t *proc_p, uintptr_t dbgaddr)
{
	proc_p->dbgaddr = dbgaddr;
}

/*
 * continue the child until the run time linker has loaded in all
 * the loadobjects (rtld sync point)
 */
static prb_status_t
sync_child(int childpid, volatile shmem_msg_t *smp, prb_proc_ctl_t **proc_pp)
{
	prb_proc_ctl_t		*proc_p, *oldproc_p;
	prb_status_t		prbstat = PRB_STATUS_OK;
	prb_status_t		tempstat;
	prb_proc_state_t	pstate;

	prbstat = prb_proc_open(childpid, proc_pp);
	if (prbstat)
		return (prbstat);

	proc_p = *proc_pp;

	prbstat = prb_proc_stop(proc_p);
	if (prbstat)
		goto ret_failure;

	/*
	 * default is to kill-on-last-close.  In case we cannot sync with
	 * target, we don't want the target to continue.
	 */
	prbstat = prb_proc_setrlc(proc_p, B_FALSE);
	if (prbstat)
		goto ret_failure;

	prbstat = prb_proc_setklc(proc_p, B_TRUE);
	if (prbstat)
		goto ret_failure;

	/* REMIND: do we have to wait on SYS_exec also ? */
	prbstat = prb_proc_exit(proc_p, SYS_execve, PRB_SYS_ADD);
	if (prbstat)
		goto ret_failure;

	prbstat = prb_proc_entry(proc_p, SYS_exit, PRB_SYS_ADD);
	if (prbstat)
		goto ret_failure;

	prbstat = prb_shmem_clear(smp);
	if (prbstat)
		goto ret_failure;

	prbstat = prb_proc_cont(proc_p);
	if (prbstat)
		goto ret_failure;

	prbstat = prb_proc_wait(proc_p, B_FALSE, NULL);
	switch (prbstat) {
	case PRB_STATUS_OK:
		break;
	case EAGAIN:
		/*
		 * If we had exec'ed a setuid/setgid program PIOCWSTOP
		 * will return EAGAIN.  Reopen the 'fd' and try again.
		 * Read the last section of /proc man page - we reopen first
		 * and then close the old fd.
		 */
		oldproc_p = proc_p;
		tempstat = prb_proc_reopen(childpid, proc_pp);
		proc_p = *proc_pp;
		if (tempstat) {
			/* here EACCES means exec'ed a setuid/setgid program */
			(void) prb_proc_close(oldproc_p);
			return (tempstat);
		}

		(void) prb_proc_close(oldproc_p);
		break;
	default:
		goto ret_failure;
	}

	prbstat = prb_shmem_free(smp);
	if (prbstat)
		goto ret_failure;

	prbstat = prb_proc_state(proc_p, &pstate);
	if (prbstat)
		goto ret_failure;

	if (pstate.ps_issysexit && (pstate.ps_syscallnum == SYS_execve)) {
		/* expected condition */
		prbstat = PRB_STATUS_OK;
	} else {
		prbstat = prb_status_map(ENOENT);
		goto ret_failure;
	}

	/* clear old interest mask */
	prbstat = prb_proc_exit(proc_p, 0, PRB_SYS_NONE);
	if (prbstat)
		goto ret_failure;

	prbstat = prb_proc_entry(proc_p, 0, PRB_SYS_NONE);
	if (prbstat)
		goto ret_failure;

	/* Successful return */
	return (PRB_STATUS_OK);

ret_failure:
	(void) prb_proc_close(proc_p);
	return (prbstat);
}