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