xref: /illumos-gate/usr/src/lib/libproc/common/Pexecname.c (revision 68dd05bf63748368ef592f58f0e893c23b45fe31)
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 
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(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 	if ((i = resolvepath(buf, buf, PATH_MAX)) > 0) {
64 		buf[i] = '\0';
65 		return (isexec(buf, isdata));
66 	}
67 
68 	return (0); /* resolvepath failed */
69 }
70 
71 /*
72  * The Pfindexec function contains the logic for the executable name dance.
73  * The caller provides a possible executable name or likely directory (the
74  * aout parameter), and a function which is responsible for doing any
75  * final confirmation on the executable pathname once a possible full
76  * pathname has been chosen.
77  */
78 char *
79 Pfindexec(struct ps_prochandle *P, const char *aout,
80     int (*isexec)(const char *, void *), void *isdata)
81 {
82 	char cwd[PATH_MAX * 2];
83 	char path[PATH_MAX];
84 	char buf[PATH_MAX];
85 	struct stat st;
86 	uintptr_t addr;
87 	char *p, *q;
88 
89 	if (P->execname)
90 		return (P->execname); /* Already found */
91 
92 	errno = 0; /* Set to zero so we can tell if stat() failed */
93 
94 	/*
95 	 * First try: use the provided default value, if it is not a directory.
96 	 * If the aout parameter turns out to be a directory, this is
97 	 * interpreted as the directory to use as an alternate cwd for
98 	 * our subsequent attempts to locate the executable.
99 	 */
100 	if (aout != NULL && stat(aout, &st) == 0 && !S_ISDIR(st.st_mode)) {
101 		if (try_exec(".", aout, buf, isexec, isdata))
102 			goto found;
103 		else
104 			aout = ".";
105 
106 	} else if (aout == NULL || errno != 0)
107 		aout = ".";
108 
109 	/*
110 	 * At this point 'aout' is either "." or an alternate cwd.  We use
111 	 * realpath(3c) to turn this into a full pathname free of ".", "..",
112 	 * and symlinks.  If this fails for some reason, fall back to "."
113 	 */
114 	if (realpath(aout, cwd) == NULL)
115 		(void) strcpy(cwd, ".");
116 
117 	/*
118 	 * Second try: read the string pointed to by the AT_SUN_EXECNAME
119 	 * auxv element, saved when the program was exec'd.  If the full
120 	 * pathname try_exec() forms fails, try again using just the
121 	 * basename appended to our cwd.
122 	 */
123 	if ((addr = Pgetauxval(P, AT_SUN_EXECNAME)) != (uintptr_t)-1L &&
124 	    Pread_string(P, path, sizeof (path), (off_t)addr) > 0) {
125 
126 		if (try_exec(cwd, path, buf, isexec, isdata))
127 			goto found;
128 
129 		if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
130 		    try_exec(cwd, p, buf, isexec, isdata))
131 			goto found;
132 	}
133 
134 	/*
135 	 * Third try: try using the first whitespace-separated token
136 	 * saved in the psinfo_t's pr_psargs (the initial value of argv[0]).
137 	 */
138 	if (Ppsinfo(P) != NULL) {
139 		(void) strncpy(path, P->psinfo.pr_psargs, PRARGSZ);
140 		path[PRARGSZ] = '\0';
141 
142 		if ((p = strchr(path, ' ')) != NULL)
143 			*p = '\0';
144 
145 		if (try_exec(cwd, path, buf, isexec, isdata))
146 			goto found;
147 
148 		if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
149 		    try_exec(cwd, p, buf, isexec, isdata))
150 			goto found;
151 	}
152 
153 	/*
154 	 * Fourth try: read the string pointed to by argv[0] out of the
155 	 * stack in the process's address space.
156 	 */
157 	if (P->psinfo.pr_argv != NULL &&
158 	    Pread(P, &addr, sizeof (addr), P->psinfo.pr_argv) != -1 &&
159 	    Pread_string(P, path, sizeof (path), (off_t)addr) > 0) {
160 
161 		if (try_exec(cwd, path, buf, isexec, isdata))
162 			goto found;
163 
164 		if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
165 		    try_exec(cwd, p, buf, isexec, isdata))
166 			goto found;
167 	}
168 
169 	/*
170 	 * Fifth try: read the process's $PATH environment variable and
171 	 * search each directory named there for the name matching pr_fname.
172 	 */
173 	if (Pgetenv(P, "PATH", cwd, sizeof (cwd)) != NULL) {
174 		/*
175 		 * If the name from pr_psargs contains pr_fname as its
176 		 * leading string, then accept the name from pr_psargs
177 		 * because more bytes are saved there.  Otherwise use
178 		 * pr_fname because this gives us new information.
179 		 */
180 		(void) strncpy(path, P->psinfo.pr_psargs, PRARGSZ);
181 		path[PRARGSZ] = '\0';
182 
183 		if ((p = strchr(path, ' ')) != NULL)
184 			*p = '\0';
185 
186 		if (strchr(path, '/') != NULL || strncmp(path,
187 		    P->psinfo.pr_fname, strlen(P->psinfo.pr_fname)) != 0)
188 			(void) strcpy(path, P->psinfo.pr_fname);
189 
190 		/*
191 		 * Now iterate over the $PATH elements, trying to form
192 		 * an executable pathname with each one.
193 		 */
194 		for (p = strtok_r(cwd, ":", &q); p != NULL;
195 		    p = strtok_r(NULL, ":", &q)) {
196 
197 			if (*p != '/')
198 				continue; /* Ignore anything relative */
199 
200 			if (try_exec(p, path, buf, isexec, isdata))
201 				goto found;
202 		}
203 	}
204 
205 	errno = ENOENT;
206 	return (NULL);
207 
208 found:
209 	if ((P->execname = strdup(buf)) == NULL)
210 		dprintf("failed to malloc; executable name is \"%s\"", buf);
211 
212 	return (P->execname);
213 }
214 
215 /*
216  * Callback function for Pfindexec().  We return a match if we can stat the
217  * suggested pathname and confirm its device and inode number match our
218  * previous information about the /proc/<pid>/object/a.out file.
219  */
220 static int
221 stat_exec(const char *path, struct stat64 *stp)
222 {
223 	struct stat64 st;
224 
225 	return (stat64(path, &st) == 0 && S_ISREG(st.st_mode) &&
226 	    stp->st_dev == st.st_dev && stp->st_ino == st.st_ino);
227 }
228 
229 /*
230  * Return the full pathname for the executable file.  If the process handle is
231  * a core file, we've already tried our best to get the executable name.
232  * Otherwise, we make an attempt using Pfindexec().
233  */
234 char *
235 Pexecname(struct ps_prochandle *P, char *buf, size_t buflen)
236 {
237 	if (P->execname == NULL && P->state != PS_DEAD && P->state != PS_IDLE) {
238 		char exec_name[PATH_MAX];
239 		char cwd[PATH_MAX];
240 		char proc_cwd[64];
241 		struct stat64 st;
242 		int ret;
243 
244 		/*
245 		 * Try to get the path information first.
246 		 */
247 		(void) snprintf(exec_name, sizeof (exec_name),
248 		    "/proc/%d/path/a.out", (int)P->pid);
249 		if ((ret = readlink(exec_name, buf, buflen - 1)) > 0) {
250 			buf[ret] = '\0';
251 			return (buf);
252 		}
253 
254 		/*
255 		 * Stat the executable file so we can compare Pfindexec's
256 		 * suggestions to the actual device and inode number.
257 		 */
258 		(void) snprintf(exec_name, sizeof (exec_name),
259 		    "/proc/%d/object/a.out", (int)P->pid);
260 
261 		if (stat64(exec_name, &st) != 0 || !S_ISREG(st.st_mode))
262 			return (NULL);
263 
264 		/*
265 		 * Attempt to figure out the current working directory of the
266 		 * target process.  This only works if the target process has
267 		 * not changed its current directory since it was exec'd.
268 		 */
269 		(void) snprintf(proc_cwd, sizeof (proc_cwd),
270 		    "/proc/%d/path/cwd", (int)P->pid);
271 
272 		if ((ret = readlink(proc_cwd, cwd, PATH_MAX - 1)) > 0)
273 			cwd[ret] = '\0';
274 
275 		(void) Pfindexec(P, ret > 0 ? cwd : NULL,
276 		    (int (*)(const char *, void *))stat_exec, &st);
277 	}
278 
279 	if (P->execname != NULL) {
280 		(void) strncpy(buf, P->execname, buflen);
281 		return (buf);
282 	}
283 
284 	return (NULL);
285 }
286