Pexecname.c revision 2712:f74a135872bc
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 2006 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28#define	__EXTENSIONS__
29#include <string.h>
30#undef	__EXTENSIONS__
31
32#include <libgen.h>
33#include <limits.h>
34#include <stdio.h>
35#include <errno.h>
36#include <unistd.h>
37#include <libzonecfg.h>
38
39#include "Pcontrol.h"
40
41/*
42 * Pexecname.c - Way too much code to attempt to derive the full pathname of
43 * the executable file from a process handle, be it dead or alive.
44 */
45
46/*
47 * Once we've computed a cwd and a relative path, we use try_exec() to
48 * form an absolute path, call resolvepath() on it, and then let the
49 * caller's function do the final confirmation.
50 */
51static int
52try_exec(const char *cwd, const char *path, char *buf,
53    int (*isexec)(const char *, void *), void *isdata)
54{
55	int i;
56
57	if (path[0] != '/')
58		(void) snprintf(buf, PATH_MAX, "%s/%s", cwd, path);
59	else
60		(void) strcpy(buf, path);
61
62	dprintf("try_exec \"%s\"\n", buf);
63
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 */
79char *
80Pfindexec(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	if (P->execname)
91		return (P->execname); /* Already found */
92
93	errno = 0; /* Set to zero so we can tell if stat() failed */
94
95	/*
96	 * First try: use the provided default value, if it is not a directory.
97	 * If the aout parameter turns out to be a directory, this is
98	 * interpreted as the directory to use as an alternate cwd for
99	 * our subsequent attempts to locate the executable.
100	 */
101	if (aout != NULL && stat(aout, &st) == 0 && !S_ISDIR(st.st_mode)) {
102		if (try_exec(".", aout, buf, isexec, isdata))
103			goto found;
104		else
105			aout = ".";
106
107	} else if (aout == NULL || errno != 0)
108		aout = ".";
109
110	/*
111	 * At this point 'aout' is either "." or an alternate cwd.  We use
112	 * realpath(3c) to turn this into a full pathname free of ".", "..",
113	 * and symlinks.  If this fails for some reason, fall back to "."
114	 */
115	if (realpath(aout, cwd) == NULL)
116		(void) strcpy(cwd, ".");
117
118	/*
119	 * Second try: read the string pointed to by the AT_SUN_EXECNAME
120	 * auxv element, saved when the program was exec'd.  If the full
121	 * pathname try_exec() forms fails, try again using just the
122	 * basename appended to our cwd.  If that also fails, and the process
123	 * is in a zone, try again with the zone path instead of our cwd.
124	 */
125	if ((addr = Pgetauxval(P, AT_SUN_EXECNAME)) != (uintptr_t)-1L &&
126	    Pread_string(P, path, sizeof (path), (off_t)addr) > 0) {
127		char		zname[ZONENAME_MAX];
128		char		zpath[PATH_MAX];
129		const psinfo_t	*pi = Ppsinfo(P);
130
131		if (try_exec(cwd, path, buf, isexec, isdata))
132			goto found;
133
134		if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
135		    try_exec(cwd, p, buf, isexec, isdata))
136			goto found;
137
138		if (getzonenamebyid(pi->pr_zoneid, zname,
139		    sizeof (zname)) != -1 && strcmp(zname, "global") != 0 &&
140		    zone_get_zonepath(zname, zpath, sizeof (zpath)) == Z_OK) {
141			(void) strcat(zpath, "/root");
142			if (try_exec(zpath, p, buf, isexec, isdata))
143				goto found;
144		}
145	}
146
147	/*
148	 * Third try: try using the first whitespace-separated token
149	 * saved in the psinfo_t's pr_psargs (the initial value of argv[0]).
150	 */
151	if (Ppsinfo(P) != NULL) {
152		(void) strncpy(path, P->psinfo.pr_psargs, PRARGSZ);
153		path[PRARGSZ] = '\0';
154
155		if ((p = strchr(path, ' ')) != NULL)
156			*p = '\0';
157
158		if (try_exec(cwd, path, buf, isexec, isdata))
159			goto found;
160
161		if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
162		    try_exec(cwd, p, buf, isexec, isdata))
163			goto found;
164	}
165
166	/*
167	 * Fourth try: read the string pointed to by argv[0] out of the
168	 * stack in the process's address space.
169	 */
170	if (P->psinfo.pr_argv != NULL &&
171	    Pread(P, &addr, sizeof (addr), P->psinfo.pr_argv) != -1 &&
172	    Pread_string(P, path, sizeof (path), (off_t)addr) > 0) {
173
174		if (try_exec(cwd, path, buf, isexec, isdata))
175			goto found;
176
177		if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
178		    try_exec(cwd, p, buf, isexec, isdata))
179			goto found;
180	}
181
182	/*
183	 * Fifth try: read the process's $PATH environment variable and
184	 * search each directory named there for the name matching pr_fname.
185	 */
186	if (Pgetenv(P, "PATH", cwd, sizeof (cwd)) != NULL) {
187		/*
188		 * If the name from pr_psargs contains pr_fname as its
189		 * leading string, then accept the name from pr_psargs
190		 * because more bytes are saved there.  Otherwise use
191		 * pr_fname because this gives us new information.
192		 */
193		(void) strncpy(path, P->psinfo.pr_psargs, PRARGSZ);
194		path[PRARGSZ] = '\0';
195
196		if ((p = strchr(path, ' ')) != NULL)
197			*p = '\0';
198
199		if (strchr(path, '/') != NULL || strncmp(path,
200		    P->psinfo.pr_fname, strlen(P->psinfo.pr_fname)) != 0)
201			(void) strcpy(path, P->psinfo.pr_fname);
202
203		/*
204		 * Now iterate over the $PATH elements, trying to form
205		 * an executable pathname with each one.
206		 */
207		for (p = strtok_r(cwd, ":", &q); p != NULL;
208		    p = strtok_r(NULL, ":", &q)) {
209
210			if (*p != '/')
211				continue; /* Ignore anything relative */
212
213			if (try_exec(p, path, buf, isexec, isdata))
214				goto found;
215		}
216	}
217
218	errno = ENOENT;
219	return (NULL);
220
221found:
222	if ((P->execname = strdup(buf)) == NULL)
223		dprintf("failed to malloc; executable name is \"%s\"", buf);
224
225	return (P->execname);
226}
227
228/*
229 * Callback function for Pfindexec().  We return a match if we can stat the
230 * suggested pathname and confirm its device and inode number match our
231 * previous information about the /proc/<pid>/object/a.out file.
232 */
233static int
234stat_exec(const char *path, struct stat64 *stp)
235{
236	struct stat64 st;
237
238	return (stat64(path, &st) == 0 && S_ISREG(st.st_mode) &&
239	    stp->st_dev == st.st_dev && stp->st_ino == st.st_ino);
240}
241
242/*
243 * Return the full pathname for the executable file.  If the process handle is
244 * a core file, we've already tried our best to get the executable name.
245 * Otherwise, we make an attempt using Pfindexec().
246 */
247char *
248Pexecname(struct ps_prochandle *P, char *buf, size_t buflen)
249{
250	if (P->execname == NULL && P->state != PS_DEAD && P->state != PS_IDLE) {
251		char exec_name[PATH_MAX];
252		char cwd[PATH_MAX];
253		char proc_cwd[64];
254		struct stat64 st;
255		int ret;
256
257		/*
258		 * Try to get the path information first.
259		 */
260		(void) snprintf(exec_name, sizeof (exec_name),
261		    "%s/%d/path/a.out", procfs_path, (int)P->pid);
262		if ((ret = readlink(exec_name, buf, buflen - 1)) > 0) {
263			buf[ret] = '\0';
264			return (buf);
265		}
266
267		/*
268		 * Stat the executable file so we can compare Pfindexec's
269		 * suggestions to the actual device and inode number.
270		 */
271		(void) snprintf(exec_name, sizeof (exec_name),
272		    "%s/%d/object/a.out", procfs_path, (int)P->pid);
273
274		if (stat64(exec_name, &st) != 0 || !S_ISREG(st.st_mode))
275			return (NULL);
276
277		/*
278		 * Attempt to figure out the current working directory of the
279		 * target process.  This only works if the target process has
280		 * not changed its current directory since it was exec'd.
281		 */
282		(void) snprintf(proc_cwd, sizeof (proc_cwd),
283		    "%s/%d/path/cwd", procfs_path, (int)P->pid);
284
285		if ((ret = readlink(proc_cwd, cwd, PATH_MAX - 1)) > 0)
286			cwd[ret] = '\0';
287
288		(void) Pfindexec(P, ret > 0 ? cwd : NULL,
289		    (int (*)(const char *, void *))stat_exec, &st);
290	}
291
292	if (P->execname != NULL) {
293		(void) strncpy(buf, P->execname, buflen);
294		return (buf);
295	}
296
297	return (NULL);
298}
299