diff options
Diffstat (limited to 'usr/src/lib/libproc/common/Pexecname.c')
-rw-r--r-- | usr/src/lib/libproc/common/Pexecname.c | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/usr/src/lib/libproc/common/Pexecname.c b/usr/src/lib/libproc/common/Pexecname.c new file mode 100644 index 0000000000..3c4fee08d1 --- /dev/null +++ b/usr/src/lib/libproc/common/Pexecname.c @@ -0,0 +1,286 @@ +/* + * 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 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#define __EXTENSIONS__ +#include <string.h> +#undef __EXTENSIONS__ + +#include <libgen.h> +#include <limits.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> + +#include "Pcontrol.h" + +/* + * Pexecname.c - Way too much code to attempt to derive the full pathname of + * the executable file from a process handle, be it dead or alive. + */ + +/* + * Once we've computed a cwd and a relative path, we use try_exec() to + * form an absolute path, call resolvepath() on it, and then let the + * caller's function do the final confirmation. + */ +static int +try_exec(const char *cwd, const char *path, char *buf, + int (*isexec)(const char *, void *), void *isdata) +{ + int i; + + if (path[0] != '/') + (void) snprintf(buf, PATH_MAX, "%s/%s", cwd, path); + else + (void) strcpy(buf, path); + + dprintf("try_exec \"%s\"\n", buf); + + if ((i = resolvepath(buf, buf, PATH_MAX)) > 0) { + buf[i] = '\0'; + return (isexec(buf, isdata)); + } + + return (0); /* resolvepath failed */ +} + +/* + * The Pfindexec function contains the logic for the executable name dance. + * The caller provides a possible executable name or likely directory (the + * aout parameter), and a function which is responsible for doing any + * final confirmation on the executable pathname once a possible full + * pathname has been chosen. + */ +char * +Pfindexec(struct ps_prochandle *P, const char *aout, + int (*isexec)(const char *, void *), void *isdata) +{ + char cwd[PATH_MAX * 2]; + char path[PATH_MAX]; + char buf[PATH_MAX]; + struct stat st; + uintptr_t addr; + char *p, *q; + + if (P->execname) + return (P->execname); /* Already found */ + + errno = 0; /* Set to zero so we can tell if stat() failed */ + + /* + * First try: use the provided default value, if it is not a directory. + * If the aout parameter turns out to be a directory, this is + * interpreted as the directory to use as an alternate cwd for + * our subsequent attempts to locate the executable. + */ + if (aout != NULL && stat(aout, &st) == 0 && !S_ISDIR(st.st_mode)) { + if (try_exec(".", aout, buf, isexec, isdata)) + goto found; + else + aout = "."; + + } else if (aout == NULL || errno != 0) + aout = "."; + + /* + * At this point 'aout' is either "." or an alternate cwd. We use + * realpath(3c) to turn this into a full pathname free of ".", "..", + * and symlinks. If this fails for some reason, fall back to "." + */ + if (realpath(aout, cwd) == NULL) + (void) strcpy(cwd, "."); + + /* + * Second try: read the string pointed to by the AT_SUN_EXECNAME + * auxv element, saved when the program was exec'd. If the full + * pathname try_exec() forms fails, try again using just the + * basename appended to our cwd. + */ + if ((addr = Pgetauxval(P, AT_SUN_EXECNAME)) != (uintptr_t)-1L && + Pread_string(P, path, sizeof (path), (off_t)addr) > 0) { + + if (try_exec(cwd, path, buf, isexec, isdata)) + goto found; + + if (strchr(path, '/') != NULL && basename(path) != NULL && + try_exec(cwd, path, buf, isexec, isdata)) + goto found; + } + + /* + * Third try: try using the first whitespace-separated token + * saved in the psinfo_t's pr_psargs (the initial value of argv[0]). + */ + if (Ppsinfo(P) != NULL) { + (void) strncpy(path, P->psinfo.pr_psargs, PRARGSZ); + path[PRARGSZ] = '\0'; + + if ((p = strchr(path, ' ')) != NULL) + *p = '\0'; + + if (try_exec(cwd, path, buf, isexec, isdata)) + goto found; + + if (strchr(path, '/') != NULL && basename(path) != NULL && + try_exec(cwd, path, buf, isexec, isdata)) + goto found; + } + + /* + * Fourth try: read the string pointed to by argv[0] out of the + * stack in the process's address space. + */ + if (P->psinfo.pr_argv != NULL && + Pread(P, &addr, sizeof (addr), P->psinfo.pr_argv) != -1 && + Pread_string(P, path, sizeof (path), (off_t)addr) > 0) { + + if (try_exec(cwd, path, buf, isexec, isdata)) + goto found; + + if (strchr(path, '/') != NULL && basename(path) != NULL && + try_exec(cwd, path, buf, isexec, isdata)) + goto found; + } + + /* + * Fifth try: read the process's $PATH environment variable and + * search each directory named there for the name matching pr_fname. + */ + if (Pgetenv(P, "PATH", cwd, sizeof (cwd)) != NULL) { + /* + * If the name from pr_psargs contains pr_fname as its + * leading string, then accept the name from pr_psargs + * because more bytes are saved there. Otherwise use + * pr_fname because this gives us new information. + */ + (void) strncpy(path, P->psinfo.pr_psargs, PRARGSZ); + path[PRARGSZ] = '\0'; + + if ((p = strchr(path, ' ')) != NULL) + *p = '\0'; + + if (strchr(path, '/') != NULL || strncmp(path, + P->psinfo.pr_fname, strlen(P->psinfo.pr_fname)) != 0) + (void) strcpy(path, P->psinfo.pr_fname); + + /* + * Now iterate over the $PATH elements, trying to form + * an executable pathname with each one. + */ + for (p = strtok_r(cwd, ":", &q); p != NULL; + p = strtok_r(NULL, ":", &q)) { + + if (*p != '/') + continue; /* Ignore anything relative */ + + if (try_exec(p, path, buf, isexec, isdata)) + goto found; + } + } + + errno = ENOENT; + return (NULL); + +found: + if ((P->execname = strdup(buf)) == NULL) + dprintf("failed to malloc; executable name is \"%s\"", buf); + + return (P->execname); +} + +/* + * Callback function for Pfindexec(). We return a match if we can stat the + * suggested pathname and confirm its device and inode number match our + * previous information about the /proc/<pid>/object/a.out file. + */ +static int +stat_exec(const char *path, struct stat64 *stp) +{ + struct stat64 st; + + return (stat64(path, &st) == 0 && S_ISREG(st.st_mode) && + stp->st_dev == st.st_dev && stp->st_ino == st.st_ino); +} + +/* + * Return the full pathname for the executable file. If the process handle is + * a core file, we've already tried our best to get the executable name. + * Otherwise, we make an attempt using Pfindexec(). + */ +char * +Pexecname(struct ps_prochandle *P, char *buf, size_t buflen) +{ + if (P->execname == NULL && P->state != PS_DEAD && P->state != PS_IDLE) { + char exec_name[PATH_MAX]; + char cwd[PATH_MAX]; + char proc_cwd[64]; + struct stat64 st; + int ret; + + /* + * Try to get the path information first. + */ + (void) snprintf(exec_name, sizeof (exec_name), + "/proc/%d/path/a.out", (int)P->pid); + if ((ret = readlink(exec_name, buf, buflen - 1)) > 0) { + buf[ret] = '\0'; + return (buf); + } + + /* + * Stat the executable file so we can compare Pfindexec's + * suggestions to the actual device and inode number. + */ + (void) snprintf(exec_name, sizeof (exec_name), + "/proc/%d/object/a.out", (int)P->pid); + + if (stat64(exec_name, &st) != 0 || !S_ISREG(st.st_mode)) + return (NULL); + + /* + * Attempt to figure out the current working directory of the + * target process. This only works if the target process has + * not changed its current directory since it was exec'd. + */ + (void) snprintf(proc_cwd, sizeof (proc_cwd), + "/proc/%d/path/cwd", (int)P->pid); + + if ((ret = readlink(proc_cwd, cwd, PATH_MAX - 1)) > 0) + cwd[ret] = '\0'; + + (void) Pfindexec(P, ret > 0 ? cwd : NULL, + (int (*)(const char *, void *))stat_exec, &st); + } + + if (P->execname != NULL) { + (void) strncpy(buf, P->execname, buflen); + return (buf); + } + + return (NULL); +} |