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 }