xref: /illumos-gate/usr/src/lib/libproc/common/Pexecname.c (revision 24f5a37652e188ebdcdd6da454511686935025df)
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  * Copyright (c) 2013 by Delphix. All rights reserved.
27  */
28 
29 #define	__EXTENSIONS__
30 #include <string.h>
31 #undef	__EXTENSIONS__
32 
33 #include <libgen.h>
34 #include <limits.h>
35 #include <stdio.h>
36 #include <errno.h>
37 #include <unistd.h>
38 #include <zone.h>
39 
40 #include "libproc.h"
41 #include "Pcontrol.h"
42 
43 /*
44  * Pexecname.c - Way too much code to attempt to derive the full pathname of
45  * the executable file from a process handle, be it dead or alive.
46  */
47 
48 /*
49  * Once we've computed a cwd and a relative path, we use try_exec() to
50  * form an absolute path, call resolvepath() on it, and then let the
51  * caller's function do the final confirmation.
52  */
53 static int
54 try_exec(struct ps_prochandle *P, const char *cwd, const char *path, char *buf,
55     int (*isexec)(const char *, void *), void *isdata)
56 {
57 	int i;
58 
59 	if (path[0] != '/')
60 		(void) snprintf(buf, PATH_MAX, "%s/%s", cwd, path);
61 	else
62 		(void) strcpy(buf, path);
63 
64 	dprintf("try_exec \"%s\"\n", buf);
65 
66 	(void) Pfindobj(P, buf, buf, PATH_MAX);
67 	if ((i = resolvepath(buf, buf, PATH_MAX)) > 0) {
68 		buf[i] = '\0';
69 		return (isexec(buf, isdata));
70 	}
71 
72 	return (0); /* resolvepath failed */
73 }
74 
75 /*
76  * The Pfindexec function contains the logic for the executable name dance.
77  * The caller provides a possible executable name or likely directory (the
78  * aout parameter), and a function which is responsible for doing any
79  * final confirmation on the executable pathname once a possible full
80  * pathname has been chosen.
81  */
82 char *
83 Pfindexec(struct ps_prochandle *P, const char *aout,
84     int (*isexec)(const char *, void *), void *isdata)
85 {
86 	char cwd[PATH_MAX * 2];
87 	char path[PATH_MAX];
88 	char buf[PATH_MAX];
89 	struct stat st;
90 	uintptr_t addr;
91 	char *p = path, *q;
92 
93 	dprintf("Pfindexec '%s'\n", aout);
94 
95 	if (P->execname)
96 		return (P->execname); /* Already found */
97 
98 	errno = 0; /* Set to zero so we can tell if stat() failed */
99 
100 	/*
101 	 * First try: use the provided default value, if it is not a directory.
102 	 * If the aout parameter turns out to be a directory, this is
103 	 * interpreted as the directory to use as an alternate cwd for
104 	 * our subsequent attempts to locate the executable.
105 	 */
106 	if (aout != NULL && stat(aout, &st) == 0 && !S_ISDIR(st.st_mode)) {
107 		if (try_exec(P, ".", aout, buf, isexec, isdata))
108 			goto found;
109 		else
110 			aout = ".";
111 
112 	} else if (aout == NULL || errno != 0)
113 		aout = ".";
114 
115 	/*
116 	 * At this point 'aout' is either "." or an alternate cwd.  We use
117 	 * realpath(3c) to turn this into a full pathname free of ".", "..",
118 	 * and symlinks.  If this fails for some reason, fall back to "."
119 	 */
120 	if (realpath(aout, cwd) == NULL)
121 		(void) strcpy(cwd, ".");
122 
123 	/*
124 	 * Second try: read the string pointed to by the AT_SUN_EXECNAME
125 	 * auxv element, saved when the program was exec'd.  If the full
126 	 * pathname try_exec() forms fails, try again using just the
127 	 * basename appended to our cwd.  If that also fails, and the process
128 	 * is in a zone, try again with the zone path instead of our cwd.
129 	 */
130 	if ((addr = Pgetauxval(P, AT_SUN_EXECNAME)) != (uintptr_t)-1L &&
131 	    Pread_string(P, path, sizeof (path), (off_t)addr) > 0) {
132 		char		zpath[PATH_MAX];
133 		const psinfo_t	*pi = Ppsinfo(P);
134 
135 		if (try_exec(P, cwd, path, buf, isexec, isdata))
136 			goto found;
137 
138 		if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
139 		    try_exec(P, cwd, p, buf, isexec, isdata))
140 			goto found;
141 
142 		if (getzoneid() == GLOBAL_ZONEID &&
143 		    pi->pr_zoneid != GLOBAL_ZONEID &&
144 		    zone_getattr(pi->pr_zoneid, ZONE_ATTR_ROOT, zpath,
145 		    sizeof (zpath)) != -1) {
146 			/*
147 			 * try_exec() only combines its cwd and path arguments
148 			 * if path is relative; but in our case even an absolute
149 			 * path inside a zone is a relative path from the global
150 			 * zone perspective. So we turn a non-global zone's
151 			 * absolute path into a relative path here before
152 			 * calling try_exec().
153 			 */
154 			p = (path[0] == '/') ? path + 1 : path;
155 			if (try_exec(P, zpath, p, buf, isexec, isdata))
156 				goto found;
157 		}
158 	}
159 
160 	/*
161 	 * Third try: try using the first whitespace-separated token
162 	 * saved in the psinfo_t's pr_psargs (the initial value of argv[0]).
163 	 */
164 	if (Ppsinfo(P) != NULL) {
165 		(void) strncpy(path, P->psinfo.pr_psargs, PRARGSZ);
166 		path[PRARGSZ] = '\0';
167 
168 		if ((p = strchr(path, ' ')) != NULL)
169 			*p = '\0';
170 
171 		if (try_exec(P, cwd, path, buf, isexec, isdata))
172 			goto found;
173 
174 		if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
175 		    try_exec(P, cwd, p, buf, isexec, isdata))
176 			goto found;
177 	}
178 
179 	/*
180 	 * Fourth try: read the string pointed to by argv[0] out of the
181 	 * stack in the process's address space.
182 	 */
183 	if (P->psinfo.pr_argv != NULL &&
184 	    Pread(P, &addr, sizeof (addr), P->psinfo.pr_argv) != -1 &&
185 	    Pread_string(P, path, sizeof (path), (off_t)addr) > 0) {
186 
187 		if (try_exec(P, cwd, path, buf, isexec, isdata))
188 			goto found;
189 
190 		if (strchr(path, '/') != NULL && (p = basename(path)) != NULL &&
191 		    try_exec(P, cwd, p, buf, isexec, isdata))
192 			goto found;
193 	}
194 
195 	/*
196 	 * Fifth try: read the process's $PATH environment variable and
197 	 * search each directory named there for the name matching pr_fname.
198 	 */
199 	if (Pgetenv(P, "PATH", cwd, sizeof (cwd)) != NULL) {
200 		/*
201 		 * If the name from pr_psargs contains pr_fname as its
202 		 * leading string, then accept the name from pr_psargs
203 		 * because more bytes are saved there.  Otherwise use
204 		 * pr_fname because this gives us new information.
205 		 */
206 		(void) strncpy(path, P->psinfo.pr_psargs, PRARGSZ);
207 		path[PRARGSZ] = '\0';
208 
209 		if ((p = strchr(path, ' ')) != NULL)
210 			*p = '\0';
211 
212 		if (strchr(path, '/') != NULL || strncmp(path,
213 		    P->psinfo.pr_fname, strlen(P->psinfo.pr_fname)) != 0)
214 			(void) strcpy(path, P->psinfo.pr_fname);
215 
216 		/*
217 		 * Now iterate over the $PATH elements, trying to form
218 		 * an executable pathname with each one.
219 		 */
220 		for (p = strtok_r(cwd, ":", &q); p != NULL;
221 		    p = strtok_r(NULL, ":", &q)) {
222 
223 			if (*p != '/')
224 				continue; /* Ignore anything relative */
225 
226 			if (try_exec(P, p, path, buf, isexec, isdata))
227 				goto found;
228 		}
229 	}
230 
231 	errno = ENOENT;
232 	return (NULL);
233 
234 found:
235 	if ((P->execname = strdup(buf)) == NULL)
236 		dprintf("failed to malloc; executable name is \"%s\"", buf);
237 
238 	return (P->execname);
239 }
240 
241 /*
242  * Return the full pathname for the executable file.
243  */
244 char *
245 Pexecname(struct ps_prochandle *P, char *buf, size_t buflen)
246 {
247 	if (P->execname != NULL) {
248 		(void) strncpy(buf, P->execname, buflen);
249 		return (buf);
250 	}
251 
252 	return (P->ops.pop_execname(P, buf, buflen, P->data));
253 }
254