Home | History | Annotate | Download | only in common
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 #define	__EXTENSIONS__
     27 #include <string.h>
     28 #undef	__EXTENSIONS__
     29 
     30 #include <libgen.h>
     31 #include <limits.h>
     32 #include <stdio.h>
     33 #include <errno.h>
     34 #include <unistd.h>
     35 #include <zone.h>
     36 
     37 #include "libproc.h"
     38 #include "Pcontrol.h"
     39 
     40 /*
     41  * Pexecname.c - Way too much code to attempt to derive the full pathname of
     42  * the executable file from a process handle, be it dead or alive.
     43  */
     44 
     45 /*
     46  * Once we've computed a cwd and a relative path, we use try_exec() to
     47  * form an absolute path, call resolvepath() on it, and then let the
     48  * caller's function do the final confirmation.
     49  */
     50 static int
     51 try_exec(struct ps_prochandle *P, const char *cwd, const char *path, char *buf,
     52     int (*isexec)(const char *, void *), void *isdata)
     53 {
     54 	int i;
     55 
     56 	if (path[0] != '/')
     57 		(void) snprintf(buf, PATH_MAX, "%s/%s", cwd, path);
     58 	else
     59 		(void) strcpy(buf, path);
     60 
     61 	dprintf("try_exec \"%s\"\n", buf);
     62 
     63 	(void) Pfindobj(P, buf, buf, PATH_MAX);
     64 	if ((i = resolvepath(buf, buf, PATH_MAX)) > 0) {
     65 		buf[i] = '\0';
     66 		return (isexec(buf, isdata));
     67 	}
     68 
     69 	return (0); /* resolvepath failed */
     70 }
     71 
     72 /*
     73  * The Pfindexec function contains the logic for the executable name dance.
     74  * The caller provides a possible executable name or likely directory (the
     75  * aout parameter), and a function which is responsible for doing any
     76  * final confirmation on the executable pathname once a possible full
     77  * pathname has been chosen.
     78  */
     79 char *
     80 Pfindexec(struct ps_prochandle *P, const char *aout,
     81     int (*isexec)(const char *, void *), void *isdata)
     82 {
     83 	char cwd[PATH_MAX * 2];
     84 	char path[PATH_MAX];
     85 	char buf[PATH_MAX];
     86 	struct stat st;
     87 	uintptr_t addr;
     88 	char *p = path, *q;
     89 
     90 	dprintf("Pfindexec '%s'\n", aout);
     91 
     92 	if (P->execname)
     93 		return (P->execname); /* Already found */
     94 
     95 	errno = 0; /* Set to zero so we can tell if stat() failed */
     96 
     97 	/*
     98 	 * First try: use the provided default value, if it is not a directory.
     99 	 * If the aout parameter turns out to be a directory, this is
    100 	 * interpreted as the directory to use as an alternate cwd for
    101 	 * our subsequent attempts to locate the executable.
    102 	 */
    103 	if (aout != NULL && stat(aout, &st) == 0 && !S_ISDIR(st.st_mode)) {
    104 		if (try_exec(P, ".", aout, buf, isexec, isdata))
    105 			goto found;
    106 		else
    107 			aout = ".";
    108 
    109 	} else if (aout == NULL || errno != 0)
    110 		aout = ".";
    111 
    112 	/*
    113 	 * At this point 'aout' is either "." or an alternate cwd.  We use
    114 	 * realpath(3c) to turn this into a full pathname free of ".", "..",
    115 	 * and symlinks.  If this fails for some reason, fall back to "."
    116 	 */
    117 	if (realpath(aout, cwd) == NULL)
    118 		(void) strcpy(cwd, ".");
    119 
    120 	/*
    121 	 * Second try: read the string pointed to by the AT_SUN_EXECNAME
    122 	 * auxv element, saved when the program was exec'd.  If the full
    123 	 * pathname try_exec() forms fails, try again using just the
    124 	 * basename appended to our cwd.  If that also fails, and the process
    125 	 * is in a zone, try again with the zone path instead of our cwd.
    126 	 */
    127 	if ((addr = Pgetauxval(P, AT_SUN_EXECNAME)) != (uintptr_t)-1L &&
    128 	    Pread_string(P, path, sizeof (path), (off_t)addr) > 0) {
    129 		char		zpath[PATH_MAX];
    130 		const psinfo_t	*pi = Ppsinfo(P);
    131 
    132 		if (try_exec(P, cwd, path, buf, isexec, isdata))
    133 			goto found;
    134 
    135 		if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
    136 		    try_exec(P, cwd, p, buf, isexec, isdata))
    137 			goto found;
    138 
    139 		if (getzoneid() == GLOBAL_ZONEID &&
    140 		    pi->pr_zoneid != GLOBAL_ZONEID &&
    141 		    zone_getattr(pi->pr_zoneid, ZONE_ATTR_ROOT, zpath,
    142 		    sizeof (zpath)) != -1) {
    143 			/*
    144 			 * try_exec() only combines its cwd and path arguments
    145 			 * if path is relative; but in our case even an absolute
    146 			 * path inside a zone is a relative path from the global
    147 			 * zone perspective. So we turn a non-global zone's
    148 			 * absolute path into a relative path here before
    149 			 * calling try_exec().
    150 			 */
    151 			p = (path[0] == '/') ? path + 1 : path;
    152 			if (try_exec(P, zpath, p, buf, isexec, isdata))
    153 				goto found;
    154 		}
    155 	}
    156 
    157 	/*
    158 	 * Third try: try using the first whitespace-separated token
    159 	 * saved in the psinfo_t's pr_psargs (the initial value of argv[0]).
    160 	 */
    161 	if (Ppsinfo(P) != NULL) {
    162 		(void) strncpy(path, P->psinfo.pr_psargs, PRARGSZ);
    163 		path[PRARGSZ] = '\0';
    164 
    165 		if ((p = strchr(path, ' ')) != NULL)
    166 			*p = '\0';
    167 
    168 		if (try_exec(P, cwd, path, buf, isexec, isdata))
    169 			goto found;
    170 
    171 		if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
    172 		    try_exec(P, cwd, p, buf, isexec, isdata))
    173 			goto found;
    174 	}
    175 
    176 	/*
    177 	 * Fourth try: read the string pointed to by argv[0] out of the
    178 	 * stack in the process's address space.
    179 	 */
    180 	if (P->psinfo.pr_argv != NULL &&
    181 	    Pread(P, &addr, sizeof (addr), P->psinfo.pr_argv) != -1 &&
    182 	    Pread_string(P, path, sizeof (path), (off_t)addr) > 0) {
    183 
    184 		if (try_exec(P, cwd, path, buf, isexec, isdata))
    185 			goto found;
    186 
    187 		if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
    188 		    try_exec(P, cwd, p, buf, isexec, isdata))
    189 			goto found;
    190 	}
    191 
    192 	/*
    193 	 * Fifth try: read the process's $PATH environment variable and
    194 	 * search each directory named there for the name matching pr_fname.
    195 	 */
    196 	if (Pgetenv(P, "PATH", cwd, sizeof (cwd)) != NULL) {
    197 		/*
    198 		 * If the name from pr_psargs contains pr_fname as its
    199 		 * leading string, then accept the name from pr_psargs
    200 		 * because more bytes are saved there.  Otherwise use
    201 		 * pr_fname because this gives us new information.
    202 		 */
    203 		(void) strncpy(path, P->psinfo.pr_psargs, PRARGSZ);
    204 		path[PRARGSZ] = '\0';
    205 
    206 		if ((p = strchr(path, ' ')) != NULL)
    207 			*p = '\0';
    208 
    209 		if (strchr(path, '/') != NULL || strncmp(path,
    210 		    P->psinfo.pr_fname, strlen(P->psinfo.pr_fname)) != 0)
    211 			(void) strcpy(path, P->psinfo.pr_fname);
    212 
    213 		/*
    214 		 * Now iterate over the $PATH elements, trying to form
    215 		 * an executable pathname with each one.
    216 		 */
    217 		for (p = strtok_r(cwd, ":", &q); p != NULL;
    218 		    p = strtok_r(NULL, ":", &q)) {
    219 
    220 			if (*p != '/')
    221 				continue; /* Ignore anything relative */
    222 
    223 			if (try_exec(P, p, path, buf, isexec, isdata))
    224 				goto found;
    225 		}
    226 	}
    227 
    228 	errno = ENOENT;
    229 	return (NULL);
    230 
    231 found:
    232 	if ((P->execname = strdup(buf)) == NULL)
    233 		dprintf("failed to malloc; executable name is \"%s\"", buf);
    234 
    235 	return (P->execname);
    236 }
    237 
    238 /*
    239  * Callback function for Pfindexec().  We return a match if we can stat the
    240  * suggested pathname and confirm its device and inode number match our
    241  * previous information about the /proc/<pid>/object/a.out file.
    242  */
    243 static int
    244 stat_exec(const char *path, struct stat64 *stp)
    245 {
    246 	struct stat64 st;
    247 
    248 	return (stat64(path, &st) == 0 && S_ISREG(st.st_mode) &&
    249 	    stp->st_dev == st.st_dev && stp->st_ino == st.st_ino);
    250 }
    251 
    252 /*
    253  * Return the full pathname for the executable file.  If the process handle is
    254  * a core file, we've already tried our best to get the executable name.
    255  * Otherwise, we make an attempt using Pfindexec().
    256  */
    257 char *
    258 Pexecname(struct ps_prochandle *P, char *buf, size_t buflen)
    259 {
    260 	if (P->execname != NULL) {
    261 		(void) strncpy(buf, P->execname, buflen);
    262 		return (buf);
    263 	}
    264 
    265 	if (P->state != PS_DEAD && P->state != PS_IDLE) {
    266 		char exec_name[PATH_MAX];
    267 		char cwd[PATH_MAX];
    268 		char proc_cwd[64];
    269 		struct stat64 st;
    270 		int ret;
    271 
    272 		/*
    273 		 * Try to get the path information first.
    274 		 */
    275 		(void) snprintf(exec_name, sizeof (exec_name),
    276 		    "%s/%d/path/a.out", procfs_path, (int)P->pid);
    277 		if ((ret = readlink(exec_name, buf, buflen - 1)) > 0) {
    278 			buf[ret] = '\0';
    279 			(void) Pfindobj(P, buf, buf, buflen);
    280 			return (buf);
    281 		}
    282 
    283 		/*
    284 		 * Stat the executable file so we can compare Pfindexec's
    285 		 * suggestions to the actual device and inode number.
    286 		 */
    287 		(void) snprintf(exec_name, sizeof (exec_name),
    288 		    "%s/%d/object/a.out", procfs_path, (int)P->pid);
    289 
    290 		if (stat64(exec_name, &st) != 0 || !S_ISREG(st.st_mode))
    291 			return (NULL);
    292 
    293 		/*
    294 		 * Attempt to figure out the current working directory of the
    295 		 * target process.  This only works if the target process has
    296 		 * not changed its current directory since it was exec'd.
    297 		 */
    298 		(void) snprintf(proc_cwd, sizeof (proc_cwd),
    299 		    "%s/%d/path/cwd", procfs_path, (int)P->pid);
    300 
    301 		if ((ret = readlink(proc_cwd, cwd, PATH_MAX - 1)) > 0)
    302 			cwd[ret] = '\0';
    303 
    304 		(void) Pfindexec(P, ret > 0 ? cwd : NULL,
    305 		    (int (*)(const char *, void *))stat_exec, &st);
    306 	}
    307 
    308 	return (NULL);
    309 }
    310