/*
* 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);
}