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