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