2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
2N/A * Use is subject to license terms.
2N/A */
2N/A
2N/A#define __EXTENSIONS__
2N/A#include <string.h>
2N/A#undef __EXTENSIONS__
2N/A
2N/A#include <libgen.h>
2N/A#include <limits.h>
2N/A#include <stdio.h>
2N/A#include <errno.h>
2N/A#include <unistd.h>
2N/A#include <zone.h>
2N/A
2N/A#include "libproc.h"
2N/A#include "Pcontrol.h"
2N/A
2N/A/*
2N/A * Pexecname.c - Way too much code to attempt to derive the full pathname of
2N/A * the executable file from a process handle, be it dead or alive.
2N/A */
2N/A
2N/A/*
2N/A * Once we've computed a cwd and a relative path, we use try_exec() to
2N/A * form an absolute path, call resolvepath() on it, and then let the
2N/A * caller's function do the final confirmation.
2N/A */
2N/Astatic int
2N/Atry_exec(struct ps_prochandle *P, const char *cwd, const char *path, char *buf,
2N/A int (*isexec)(const char *, void *), void *isdata)
2N/A{
2N/A int i;
2N/A
2N/A if (path[0] != '/')
2N/A (void) snprintf(buf, PATH_MAX, "%s/%s", cwd, path);
2N/A else
2N/A (void) strcpy(buf, path);
2N/A
2N/A dprintf("try_exec \"%s\"\n", buf);
2N/A
2N/A (void) Pfindobj(P, buf, buf, PATH_MAX);
2N/A if ((i = resolvepath(buf, buf, PATH_MAX)) > 0) {
2N/A buf[i] = '\0';
2N/A return (isexec(buf, isdata));
2N/A }
2N/A
2N/A return (0); /* resolvepath failed */
2N/A}
2N/A
2N/A/*
2N/A * The Pfindexec function contains the logic for the executable name dance.
2N/A * The caller provides a possible executable name or likely directory (the
2N/A * aout parameter), and a function which is responsible for doing any
2N/A * final confirmation on the executable pathname once a possible full
2N/A * pathname has been chosen.
2N/A */
2N/Achar *
2N/APfindexec(struct ps_prochandle *P, const char *aout,
2N/A int (*isexec)(const char *, void *), void *isdata)
2N/A{
2N/A char cwd[PATH_MAX * 2];
2N/A char path[PATH_MAX];
2N/A char buf[PATH_MAX];
2N/A struct stat st;
2N/A uintptr_t addr;
2N/A char *p = path, *q;
2N/A
2N/A dprintf("Pfindexec '%s'\n", aout);
2N/A
2N/A if (P->execname)
2N/A return (P->execname); /* Already found */
2N/A
2N/A errno = 0; /* Set to zero so we can tell if stat() failed */
2N/A
2N/A /*
2N/A * First try: use the provided default value, if it is not a directory.
2N/A * If the aout parameter turns out to be a directory, this is
2N/A * interpreted as the directory to use as an alternate cwd for
2N/A * our subsequent attempts to locate the executable.
2N/A */
2N/A if (aout != NULL && stat(aout, &st) == 0 && !S_ISDIR(st.st_mode)) {
2N/A if (try_exec(P, ".", aout, buf, isexec, isdata))
2N/A goto found;
2N/A else
2N/A aout = ".";
2N/A
2N/A } else if (aout == NULL || errno != 0)
2N/A aout = ".";
2N/A
2N/A /*
2N/A * At this point 'aout' is either "." or an alternate cwd. We use
2N/A * realpath(3c) to turn this into a full pathname free of ".", "..",
2N/A * and symlinks. If this fails for some reason, fall back to "."
2N/A */
2N/A if (realpath(aout, cwd) == NULL)
2N/A (void) strcpy(cwd, ".");
2N/A
2N/A /*
2N/A * Second try: read the string pointed to by the AT_SUN_EXECNAME
2N/A * auxv element, saved when the program was exec'd. If the full
2N/A * pathname try_exec() forms fails, try again using just the
2N/A * basename appended to our cwd. If that also fails, and the process
2N/A * is in a zone, try again with the zone path instead of our cwd.
2N/A */
2N/A if ((addr = Pgetauxval(P, AT_SUN_EXECNAME)) != (uintptr_t)-1L &&
2N/A Pread_string(P, path, sizeof (path), (off_t)addr) > 0) {
2N/A char zpath[PATH_MAX];
2N/A const psinfo_t *pi = Ppsinfo(P);
2N/A
2N/A if (try_exec(P, cwd, path, buf, isexec, isdata))
2N/A goto found;
2N/A
2N/A if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
2N/A try_exec(P, cwd, p, buf, isexec, isdata))
2N/A goto found;
2N/A
2N/A if (getzoneid() == GLOBAL_ZONEID &&
2N/A pi->pr_zoneid != GLOBAL_ZONEID &&
2N/A zone_getattr(pi->pr_zoneid, ZONE_ATTR_ROOT, zpath,
2N/A sizeof (zpath)) != -1) {
2N/A /*
2N/A * try_exec() only combines its cwd and path arguments
2N/A * if path is relative; but in our case even an absolute
2N/A * path inside a zone is a relative path from the global
2N/A * zone perspective. So we turn a non-global zone's
2N/A * absolute path into a relative path here before
2N/A * calling try_exec().
2N/A */
2N/A p = (path[0] == '/') ? path + 1 : path;
2N/A if (try_exec(P, zpath, p, buf, isexec, isdata))
2N/A goto found;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * Third try: try using the first whitespace-separated token
2N/A * saved in the psinfo_t's pr_psargs (the initial value of argv[0]).
2N/A */
2N/A if (Ppsinfo(P) != NULL) {
2N/A (void) strncpy(path, P->psinfo.pr_psargs, PRARGSZ);
2N/A path[PRARGSZ] = '\0';
2N/A
2N/A if ((p = strchr(path, ' ')) != NULL)
2N/A *p = '\0';
2N/A
2N/A if (try_exec(P, cwd, path, buf, isexec, isdata))
2N/A goto found;
2N/A
2N/A if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
2N/A try_exec(P, cwd, p, buf, isexec, isdata))
2N/A goto found;
2N/A }
2N/A
2N/A /*
2N/A * Fourth try: read the string pointed to by argv[0] out of the
2N/A * stack in the process's address space.
2N/A */
2N/A if (P->psinfo.pr_argv != NULL &&
2N/A Pread(P, &addr, sizeof (addr), P->psinfo.pr_argv) != -1 &&
2N/A Pread_string(P, path, sizeof (path), (off_t)addr) > 0) {
2N/A
2N/A if (try_exec(P, cwd, path, buf, isexec, isdata))
2N/A goto found;
2N/A
2N/A if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
2N/A try_exec(P, cwd, p, buf, isexec, isdata))
2N/A goto found;
2N/A }
2N/A
2N/A /*
2N/A * Fifth try: read the process's $PATH environment variable and
2N/A * search each directory named there for the name matching pr_fname.
2N/A */
2N/A if (Pgetenv(P, "PATH", cwd, sizeof (cwd)) != NULL) {
2N/A /*
2N/A * If the name from pr_psargs contains pr_fname as its
2N/A * leading string, then accept the name from pr_psargs
2N/A * because more bytes are saved there. Otherwise use
2N/A * pr_fname because this gives us new information.
2N/A */
2N/A (void) strncpy(path, P->psinfo.pr_psargs, PRARGSZ);
2N/A path[PRARGSZ] = '\0';
2N/A
2N/A if ((p = strchr(path, ' ')) != NULL)
2N/A *p = '\0';
2N/A
2N/A if (strchr(path, '/') != NULL || strncmp(path,
2N/A P->psinfo.pr_fname, strlen(P->psinfo.pr_fname)) != 0)
2N/A (void) strcpy(path, P->psinfo.pr_fname);
2N/A
2N/A /*
2N/A * Now iterate over the $PATH elements, trying to form
2N/A * an executable pathname with each one.
2N/A */
2N/A for (p = strtok_r(cwd, ":", &q); p != NULL;
2N/A p = strtok_r(NULL, ":", &q)) {
2N/A
2N/A if (*p != '/')
2N/A continue; /* Ignore anything relative */
2N/A
2N/A if (try_exec(P, p, path, buf, isexec, isdata))
2N/A goto found;
2N/A }
2N/A }
2N/A
2N/A errno = ENOENT;
2N/A return (NULL);
2N/A
2N/Afound:
2N/A if ((P->execname = strdup(buf)) == NULL)
2N/A dprintf("failed to malloc; executable name is \"%s\"", buf);
2N/A
2N/A return (P->execname);
2N/A}
2N/A
2N/A/*
2N/A * Callback function for Pfindexec(). We return a match if we can stat the
2N/A * suggested pathname and confirm its device and inode number match our
2N/A * previous information about the /proc/<pid>/object/a.out file.
2N/A */
2N/Astatic int
2N/Astat_exec(const char *path, struct stat64 *stp)
2N/A{
2N/A struct stat64 st;
2N/A
2N/A return (stat64(path, &st) == 0 && S_ISREG(st.st_mode) &&
2N/A stp->st_dev == st.st_dev && stp->st_ino == st.st_ino);
2N/A}
2N/A
2N/A/*
2N/A * Return the full pathname for the executable file. If the process handle is
2N/A * a core file, we've already tried our best to get the executable name.
2N/A * Otherwise, we make an attempt using Pfindexec().
2N/A */
2N/Achar *
2N/APexecname(struct ps_prochandle *P, char *buf, size_t buflen)
2N/A{
2N/A if (P->execname != NULL) {
2N/A (void) strncpy(buf, P->execname, buflen);
2N/A return (buf);
2N/A }
2N/A
2N/A if (P->state != PS_DEAD && P->state != PS_IDLE) {
2N/A char exec_name[PATH_MAX];
2N/A char cwd[PATH_MAX];
2N/A char proc_cwd[64];
2N/A struct stat64 st;
2N/A int ret;
2N/A
2N/A /*
2N/A * Try to get the path information first.
2N/A */
2N/A (void) snprintf(exec_name, sizeof (exec_name),
2N/A "%s/%d/path/a.out", procfs_path, (int)P->pid);
2N/A if ((ret = readlink(exec_name, buf, buflen - 1)) > 0) {
2N/A buf[ret] = '\0';
2N/A (void) Pfindobj(P, buf, buf, buflen);
2N/A return (buf);
2N/A }
2N/A
2N/A /*
2N/A * Stat the executable file so we can compare Pfindexec's
2N/A * suggestions to the actual device and inode number.
2N/A */
2N/A (void) snprintf(exec_name, sizeof (exec_name),
2N/A "%s/%d/object/a.out", procfs_path, (int)P->pid);
2N/A
2N/A if (stat64(exec_name, &st) != 0 || !S_ISREG(st.st_mode))
2N/A return (NULL);
2N/A
2N/A /*
2N/A * Attempt to figure out the current working directory of the
2N/A * target process. This only works if the target process has
2N/A * not changed its current directory since it was exec'd.
2N/A */
2N/A (void) snprintf(proc_cwd, sizeof (proc_cwd),
2N/A "%s/%d/path/cwd", procfs_path, (int)P->pid);
2N/A
2N/A if ((ret = readlink(proc_cwd, cwd, PATH_MAX - 1)) > 0)
2N/A cwd[ret] = '\0';
2N/A
2N/A (void) Pfindexec(P, ret > 0 ? cwd : NULL,
2N/A (int (*)(const char *, void *))stat_exec, &st);
2N/A }
2N/A
2N/A return (NULL);
2N/A}