diff options
author | Edward Pilatowicz <Edward.Pilatowicz@Sun.COM> | 2008-09-23 22:32:10 -0700 |
---|---|---|
committer | Edward Pilatowicz <Edward.Pilatowicz@Sun.COM> | 2008-09-23 22:32:10 -0700 |
commit | 186f7fbf5e07d046b50e4e15c32b21f109b76c80 (patch) | |
tree | 7bb0d0b3f1656c4959df4535c7adc296422b5863 /usr/src/lib/libproc/common/Pzone.c | |
parent | feccaf6df4d61f3e7e0723a768ce407f9eb6a1dd (diff) | |
download | illumos-joyent-186f7fbf5e07d046b50e4e15c32b21f109b76c80.tar.gz |
PSARC/2008/490 pmadvise/pldd unresolved link map flag
6599704 libproc should look inside zones for objects
--HG--
rename : usr/src/lib/libproc/common/Pbrand.c => usr/src/lib/libproc/common/Pzone.c
Diffstat (limited to 'usr/src/lib/libproc/common/Pzone.c')
-rw-r--r-- | usr/src/lib/libproc/common/Pzone.c | 748 |
1 files changed, 748 insertions, 0 deletions
diff --git a/usr/src/lib/libproc/common/Pzone.c b/usr/src/lib/libproc/common/Pzone.c new file mode 100644 index 0000000000..c4023c2301 --- /dev/null +++ b/usr/src/lib/libproc/common/Pzone.c @@ -0,0 +1,748 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include <assert.h> +#include <dlfcn.h> +#include <errno.h> +#include <libzonecfg.h> +#include <link.h> +#include <string.h> +#include <strings.h> +#include <sys/list.h> +#include <sys/types.h> +#include <sys/mkdev.h> +#include <sys/mman.h> +#include <sys/mnttab.h> + +#include "Pcontrol.h" + +struct path_node { + struct path_node *pn_next; + char *pn_path; +}; +typedef struct path_node path_node_t; + +static path_node_t * +pn_push(path_node_t **pnp, char *path) +{ + path_node_t *pn; + + if ((pn = calloc(sizeof (path_node_t), 1)) == NULL) + return (NULL); + + if ((pn->pn_path = strdup(path)) == NULL) { + free(pn); + return (NULL); + } + pn->pn_next = *pnp; + return (*pnp = pn); +} + +static void +pn_free(path_node_t **pnp) +{ + path_node_t *pn; + + while (*pnp != NULL) { + pn = *pnp; + *pnp = pn->pn_next; + free(pn->pn_path); + free(pn); + } +} + +static void +pn_free2(path_node_t **pn1, path_node_t **pn2) +{ + pn_free(pn1); + pn_free(pn2); +} + +static char * +pn_pop(path_node_t **pnp, char *path) +{ + path_node_t *pn; + + if (*pnp == NULL) + return (NULL); + + pn = *pnp; + *pnp = pn->pn_next; + pn->pn_next = NULL; + + if (path == NULL) { + pn_free(&pn); + return (NULL); + } + (void) strlcpy(path, pn->pn_path, PATH_MAX); + pn_free(&pn); + return (path); +} + + +/* + * Libzonecfg.so links against libproc, so libproc can't link against + * libzonecfg.so. Also, libzonecfg.so is optional and might not be + * installed. Hence instead of relying on linking to access libzonecfg.so, + * we'll try dlopening it here. This trick is borrowed from + * libc`zone_get_id(), see that function for more detailed comments. + */ +static int +i_zone_get_zonepath(char *zone_name, char *zonepath, size_t rp_sz) +{ + typedef int (*zone_get_zonepath_t)(char *, char *, size_t); + static zone_get_zonepath_t zone_get_zonepath_fp = NULL; + + if (zone_get_zonepath_fp == NULL) { + /* There's no harm in doing this multiple times. */ + void *dlhandle = dlopen("libzonecfg.so.1", RTLD_LAZY); + void *sym = (void *)(-1); + if (dlhandle != NULL && + (sym = dlsym(dlhandle, "zone_get_zonepath")) == NULL) { + sym = (void *)(-1); + (void) dlclose(dlhandle); + } + zone_get_zonepath_fp = (zone_get_zonepath_t)sym; + } + + /* If we've successfully loaded it, call the real function */ + if (zone_get_zonepath_fp != (zone_get_zonepath_t)(-1)) + return (zone_get_zonepath_fp(zone_name, zonepath, rp_sz)); + return (Z_NO_ZONE); +} + +char * +Pbrandname(struct ps_prochandle *P, char *buf, size_t buflen) +{ + long addr; + + if ((addr = Pgetauxval(P, AT_SUN_BRANDNAME)) == -1) + return (NULL); + + if (Pread_string(P, buf, buflen, addr) == -1) + return (NULL); + + return (buf); +} + +/* + * Get the zone name from the core file if we have it; look up the + * name based on the zone id if this is a live process. + */ +char * +Pzonename(struct ps_prochandle *P, char *s, size_t n) +{ + if (P->state == PS_IDLE) { + errno = ENODATA; + return (NULL); + } + + if (P->state == PS_DEAD) { + if (P->core->core_zonename == NULL) { + errno = ENODATA; + return (NULL); + } + (void) strlcpy(s, P->core->core_zonename, n); + } else { + if (getzonenamebyid(P->status.pr_zoneid, s, n) < 0) + return (NULL); + s[n - 1] = '\0'; + } + return (s); +} + +char * +Pzoneroot(struct ps_prochandle *P, char *s, size_t n) +{ + char zname[ZONENAME_MAX], zpath[PATH_MAX], tmp[PATH_MAX]; + int rv; + + if (P->zoneroot != NULL) { + (void) strlcpy(s, P->zoneroot, n); + return (s); + } + + if ((Pzonename(P, zname, sizeof (zname)) == NULL) || + (strcmp(zname, GLOBAL_ZONENAME) == 0)) { + if ((P->zoneroot = strdup("")) == NULL) { + errno = ENOMEM; + return (NULL); + } + dprintf("Pzoneroot defaulting to '%s'\n", GLOBAL_ZONENAME); + (void) strlcpy(s, P->zoneroot, n); + return (s); + } + + if (i_zone_get_zonepath(zname, zpath, sizeof (zpath)) != Z_OK) { + if ((P->zoneroot = strdup("")) == NULL) { + errno = ENOMEM; + return (NULL); + } + dprintf( + "Pzoneroot zone not found '%s', defaulting to '%s'\n", + zname, GLOBAL_ZONENAME); + (void) strlcpy(s, P->zoneroot, n); + return (s); + } + (void) strlcat(zpath, "/root", sizeof (zpath)); + + if ((rv = resolvepath(zpath, tmp, sizeof (tmp) - 1)) < 0) { + if ((P->zoneroot = strdup("")) == NULL) { + errno = ENOMEM; + return (NULL); + } + dprintf( + "Pzoneroot can't access '%s:%s', defaulting to '%s'\n", + zname, zpath, GLOBAL_ZONENAME); + (void) strlcpy(s, P->zoneroot, n); + return (s); + } + tmp[rv] = '\0'; + (void) strlcpy(zpath, tmp, sizeof (zpath)); + + if ((P->zoneroot = strdup(zpath)) == NULL) { + errno = ENOMEM; + return (NULL); + } + dprintf("Pzoneroot found zone root '%s:%s'\n", zname, zpath); + (void) strlcpy(s, P->zoneroot, n); + return (s); +} + +/* + * Plofspath() takes a path, "path", and removes any lofs components from + * that path. The resultant path (if different from the starting path) + * is placed in "s", which is limited to "n" characters, and the return + * value is the pointer s. If there are no lofs components in the path + * the NULL is returned and s is not modified. It's ok for "path" and + * "s" to be the same pointer. (ie, the results can be stored directly + * in the input buffer.) The path that is passed in must be an absolute + * path. + * + * Example: + * if "path" == "/foo/bar", and "/candy/" is lofs mounted on "/foo/" + * then "/candy/bar/" will be written into "s" and "s" will be returned. + */ +char * +Plofspath(const char *path, char *s, size_t n) +{ + char tmp[PATH_MAX + 1]; + struct mnttab mt, mt_find; + FILE *fp; + char *p, *p2; + int rv; + + dprintf("Plofspath path '%s'\n", path); + + /* We only deal with absolute paths */ + if (path[0] != '/') + return (NULL); + + /* Open /etc/mnttab */ + if ((fp = fopen(MNTTAB, "r")) == NULL) + return (NULL); + + /* Make a copy of the path so that we can muck with it */ + (void) strlcpy(tmp, path, sizeof (tmp) - 1); + + /* + * Use resolvepath() to make sure there are no consecutive or + * trailing '/'s in the path. + */ + if ((rv = resolvepath(tmp, tmp, sizeof (tmp) - 1)) >= 0) + tmp[rv] = '\0'; + + /* + * So now we're going to search the path for any components that + * might be lofs mounts. We'll start out search from the full + * path and then step back through each parent directly till + * we reach the root. If we find a lofs mount point in the path + * then we'll replace the initial portion of the path (up + * to that mount point) with the source of that mount point + * and then start our search over again. + * + * Here's some of the variables we're going to use: + * + * tmp - A pointer to our working copy of the path. Sometimes + * this path will be divided into two strings by a + * '\0' (NUL) character. The first string is the + * component we're currently checking and the second + * string is the path components we've already checked. + * + * p - A pointer to the last '/' seen in the string. + * + * p[1] - A pointer to the component of the string we've already + * checked. + * + * Initially, p will point to the end of our path and p[1] will point + * to an extra '\0' (NUL) that we'll append to the end of the string. + * (This is why we declared tmp with a size of PATH_MAX + 1). + */ + p = &tmp[strlen(tmp)]; + p[1] = '\0'; + for (;;) { + /* Check if tmp is a mount point */ + rewind(fp); + bzero(&mt_find, sizeof (mt_find)); + mt_find.mnt_mountp = tmp; + rv = getmntany(fp, &mt, &mt_find); + + /* We only care about lofs mount points */ + if ((rv == 0) && (strcmp(mt.mnt_fstype, "lofs") == 0)) { + char tmp2[PATH_MAX + 1]; + + /* + * We found a lofs mount. Update the path that we're + * checking and start over. This means append the + * portion of the path we've already checked to the + * source of the lofs mount and re-start this entire + * lofs resolution loop. Use resolvepath() to make + * sure there are no consecutive or trailing '/'s + * in the path. + */ + (void) strlcpy(tmp2, mt.mnt_special, sizeof (tmp2) - 1); + (void) strlcat(tmp2, "/", sizeof (tmp2) - 1); + (void) strlcat(tmp2, &p[1], sizeof (tmp2) - 1); + (void) strlcpy(tmp, tmp2, sizeof (tmp) - 1); + if ((rv = resolvepath(tmp, tmp, sizeof (tmp) - 1)) >= 0) + tmp[rv] = '\0'; + p = &tmp[strlen(tmp)]; + p[1] = '\0'; + continue; + } + + /* No lofs mount found */ + if ((p2 = strrchr(tmp, '/')) == NULL) { + char tmp2[PATH_MAX]; + + /* + * We know that tmp was an absolute path, so if we + * made it here we know that (p == tmp) and that + * (*p == '\0'). This means that we've managed + * to check the whole path and so we're done. + */ + assert(p == tmp); + assert(p[0] == '\0'); + (void) fclose(fp); + + /* Restore the leading '/' in the path */ + p[0] = '/'; + + if (strcmp(tmp, path) == 0) { + /* The path didn't change */ + return (NULL); + } + + /* + * It's possible that lofs source path we just + * obtained contains a symbolic link. Use + * resolvepath() to clean it up. + */ + (void) strlcpy(tmp2, tmp, sizeof (tmp2)); + if ((rv = resolvepath(tmp, tmp, sizeof (tmp) - 1)) >= 0) + tmp[rv] = '\0'; + + /* + * It's always possible that our lofs source path is + * actually another lofs mount. So call ourselves + * recursively to resolve that path. + */ + (void) Plofspath(tmp, tmp, PATH_MAX); + + /* Copy out our final resolved lofs source path */ + (void) strlcpy(s, tmp, n); + dprintf("Plofspath path result '%s'\n", s); + return (s); + } + + /* + * So the path we just checked is not a lofs mount. Next we + * want to check the parent path component for a lofs mount. + * + * First, restore any '/' that we replaced with a '\0' (NUL). + * We can determine if we should do this by looking at p[1]. + * If p[1] points to a '\0' (NUL) then we know that p points + * to the end of the string and there is no '/' to restore. + * if p[1] doesn't point to a '\0' (NUL) then it points to + * the part of the path that we've already verified so there + * is a '/' to restore. + */ + if (p[1] != '\0') + p[0] = '/'; + + /* + * Second, replace the last '/' in the part of the path + * that we've already checked with a '\0' (NUL) so that + * when we loop around we check the parent component of the + * path. + */ + p2[0] = '\0'; + p = p2; + } + /*NOTREACHED*/ +} + +/* + * Pzonepath() - Way too much code to attempt to derive the full path of + * an object within a zone. + * + * Pzonepath() takes a path and attempts to resolve it relative to the + * root associated with the current process handle. If it fails it will + * not update the results string. It is safe to specify the same pointer + * for the file string and the results string. + * + * Doing this resolution is more difficult than it initially sounds. + * We can't simply append the file path to the zone root, because in + * a root directory, '..' is treated the same as '.'. Also, symbolic + * links that specify an absolute path need to be interpreted relative + * to the zone root. + * + * It seems like perhaps we could do a chroot(<zone root>) followed by a + * resolvepath(). But we can't do this because chroot requires special + * privileges and affects the entire process. Perhaps if there was a + * special version of resolvepath() which took an addition root path + * we could use that, but this isn't ideal either. The reason is + * that we want to have special handling for native paths. (A native path + * is a path that begins with "/native/" or "/.SUNWnative/".) Native + * paths could be passed explicity to this function or could be embedded + * in a symlink that is part of the path passed into this function. + * These paths are always lofs mounts of global zone paths, but lofs + * mounts only exist when a zone is booted. So if we were to try to do + * a resolvepath() on a native path when the zone wasn't booted the + * resolvepath() would fail even though we know that the components + * exists in the global zone. + * + * Given all these constraints, we just implement a path walking function + * that resolves a file path relative to a zone root by manually inspecting + * each of the path components and verifying its existence. This means that + * we must have access to the zone and that all the components of the + * path must exist for this operation to succeed. + */ +char * +Pzonepath(struct ps_prochandle *P, const char *path, char *s, size_t n) +{ + char zroot[PATH_MAX], zpath[PATH_MAX], tmp[PATH_MAX], link[PATH_MAX]; + path_node_t *pn_stack = NULL, *pn_links = NULL, *pn; + struct stat64 sb; + char *p; + int i, rv; + + dprintf("Pzonepath lookup '%s'\n", path); + + /* First lookup the zone root */ + if (Pzoneroot(P, zroot, sizeof (zroot)) == NULL) + return (NULL); + + /* + * Make a temporary copy of the path specified. + * If it's a relative path then make it into an absolute path. + */ + tmp[0] = '\0'; + if (path[0] != '/') + (void) strlcat(tmp, "/", sizeof (tmp)); + (void) strlcat(tmp, path, sizeof (tmp)); + + /* + * If the path that was passed in is the zone root, we're done. + * If the path that was passed in already contains the zone root + * then strip the zone root out and verify the rest of the path. + */ + if (strcmp(tmp, zroot) == 0) { + (void) Plofspath(zroot, zroot, sizeof (zroot)); + dprintf("Pzonepath found zone path (1) '%s'\n", zroot); + (void) strlcpy(s, zroot, n); + return (s); + } + i = strlen(zroot); + if ((strncmp(tmp, zroot, i) == 0) && (tmp[i] == '/')) + (void) memmove(tmp, tmp + i, strlen(tmp + i) + 1); + + /* If no path is passed in, then it maps to the zone root */ + if (strlen(tmp) == 0) { + (void) Plofspath(zroot, zroot, sizeof (zroot)); + dprintf("Pzonepath found zone path (2) '%s'\n", zroot); + (void) strlcpy(s, zroot, n); + return (s); + } + + /* + * Push each path component that we plan to verify onto a stack of + * path components, with parent components at the top of the stack. + * So for example, if we're going to verify the path /foo/bar/bang + * then our stack will look like: + * foo (top) + * bar + * bang (bottom) + */ + while ((p = strrchr(tmp, '/')) != NULL) { + *p = '\0'; + if (pn_push(&pn_stack, &p[1]) != NULL) + continue; + pn_free(&pn_stack); + return (NULL); + } + + /* We're going to store the final zone relative path in zpath */ + *zpath = '\0'; + + while (pn_pop(&pn_stack, tmp) != NULL) { + /* + * Drop zero length path components (which come from + * consecutive '/'s) and '.' path components. + */ + if ((strlen(tmp) == 0) || (strcmp(tmp, ".") == 0)) + continue; + + /* + * Check the current path component for '..', if found + * drop any previous path component. + */ + if (strcmp(tmp, "..") == 0) { + if ((p = strrchr(zpath, '/')) != NULL) + *p = '\0'; + continue; + } + + /* The path we want to verify now is zpath + / + tmp. */ + (void) strlcat(zpath, "/", sizeof (zpath)); + (void) strlcat(zpath, tmp, sizeof (zpath)); + + /* + * Check if this is a native object. A native object is an + * object from the global zone that is running in a branded + * zone. These objects are lofs mounted into a zone. So if a + * branded zone is not booted then lofs mounts won't be setup + * so we won't be able to find these objects. Luckily, we know + * that they exist in the global zone with the same path sans + * the initial native component, so we'll just strip out the + * native component here. + */ + if ((strncmp(zpath, "/native", sizeof ("/native")) == 0) || + (strncmp(zpath, "/.SUNWnative", + sizeof ("/.SUNWnative")) == 0)) { + + /* Free any cached symlink paths */ + pn_free(&pn_links); + + /* Reconstruct the path from our path component stack */ + *zpath = '\0'; + while (pn_pop(&pn_stack, tmp) != NULL) { + (void) strlcat(zpath, "/", sizeof (zpath)); + (void) strlcat(zpath, tmp, sizeof (zpath)); + } + + /* Verify that the path actually exists */ + rv = resolvepath(zpath, tmp, sizeof (tmp) - 1); + if (rv < 0) { + dprintf("Pzonepath invalid native path '%s'\n", + zpath); + return (NULL); + } + tmp[rv] = '\0'; + + /* Return the path */ + dprintf("Pzonepath found native path '%s'\n", tmp); + (void) Plofspath(tmp, tmp, sizeof (tmp)); + (void) strlcpy(s, tmp, n); + return (s); + } + + /* + * Check if the path points to a symlink. We do this + * explicitly since any absolute symlink needs to be + * interpreted relativly to the zone root and not "/". + */ + (void) strlcpy(tmp, zroot, sizeof (tmp)); + (void) strlcat(tmp, zpath, sizeof (tmp)); + if (lstat64(tmp, &sb) != 0) { + pn_free2(&pn_stack, &pn_links); + return (NULL); + } + if (!S_ISLNK(sb.st_mode)) { + /* + * Since the lstat64() above succeeded we know that + * zpath exists, since this is not a symlink loop + * around and check the next path component. + */ + continue; + } + + /* + * Symlink allow for paths with loops. Make sure + * we're not stuck in a loop. + */ + for (pn = pn_links; pn != NULL; pn = pn->pn_next) { + if (strcmp(zpath, pn->pn_path) != 0) + continue; + + /* We have a loop. Fail. */ + dprintf("Pzonepath symlink loop '%s'\n", zpath); + pn_free2(&pn_stack, &pn_links); + return (NULL); + } + + /* Save this symlink path for future loop checks */ + if (pn_push(&pn_links, zpath) == NULL) { + /* Out of memory */ + pn_free2(&pn_stack, &pn_links); + return (NULL); + } + + /* Now follow the contents of the symlink */ + bzero(link, sizeof (link)); + if (readlink(tmp, link, sizeof (link)) == -1) { + pn_free2(&pn_stack, &pn_links); + return (NULL); + } + + dprintf("Pzonepath following symlink '%s' -> '%s'\n", + zpath, link); + + /* + * Push each path component of the symlink target onto our + * path components stack since we need to verify each one. + */ + while ((p = strrchr(link, '/')) != NULL) { + *p = '\0'; + if (pn_push(&pn_stack, &p[1]) != NULL) + continue; + pn_free2(&pn_stack, &pn_links); + return (NULL); + } + + /* absolute or relative symlink? */ + if (*link == '\0') { + /* Absolute symlink, nuke existing zpath. */ + *zpath = '\0'; + continue; + } + + /* + * Relative symlink. Push the first path component of the + * symlink target onto our stack for verification and then + * remove the current path component from zpath. + */ + if (pn_push(&pn_stack, link) == NULL) { + pn_free2(&pn_stack, &pn_links); + return (NULL); + } + p = strrchr(zpath, '/'); + assert(p != NULL); + *p = '\0'; + continue; + } + pn_free(&pn_links); + + /* Place the final result in zpath */ + (void) strlcpy(tmp, zroot, sizeof (tmp)); + (void) strlcat(tmp, zpath, sizeof (tmp)); + (void) strlcpy(zpath, tmp, sizeof (zpath)); + + (void) Plofspath(zpath, zpath, sizeof (zpath)); + dprintf("Pzonepath found zone path (3) '%s'\n", zpath); + + (void) strlcpy(s, zpath, n); + return (s); +} + +char * +Pfindobj(struct ps_prochandle *P, const char *path, char *s, size_t n) +{ + int len; + + dprintf("Pfindobj '%s'\n", path); + + /* We only deal with absolute paths */ + if (path[0] != '/') + return (NULL); + + /* First try to resolve the path to some zone */ + if (Pzonepath(P, path, s, n) != NULL) + return (s); + + /* If that fails resolve any lofs links in the path */ + if (Plofspath(path, s, n) != NULL) + return (s); + + /* If that fails then just see if the path exists */ + if ((len = resolvepath(path, s, n)) > 0) { + s[len] = '\0'; + return (s); + } + + return (NULL); +} + +char * +Pfindmap(struct ps_prochandle *P, map_info_t *mptr, char *s, size_t n) +{ + file_info_t *fptr = mptr->map_file; + char buf[PATH_MAX]; + int len; + + /* If it's already been explicity set return that */ + if ((fptr != NULL) && (fptr->file_rname != NULL)) { + (void) strlcpy(s, fptr->file_rname, n); + return (s); + } + + /* If it's the a.out segment, defer to the magical Pexecname() */ + if ((P->map_exec == mptr) || + (strcmp(mptr->map_pmap.pr_mapname, "a.out") == 0) || + ((fptr != NULL) && (fptr->file_lname != NULL) && + (strcmp(fptr->file_lname, "a.out") == 0))) { + (void) Pexecname(P, buf, sizeof (buf)); + (void) strlcpy(s, buf, n); + return (s); + } + + /* Try /proc first to get the real object name */ + if ((Pstate(P) != PS_DEAD) && (mptr->map_pmap.pr_mapname[0] != '\0')) { + (void) snprintf(buf, sizeof (buf), "%s/%d/path/%s", + procfs_path, (int)P->pid, mptr->map_pmap.pr_mapname); + if ((len = readlink(buf, buf, sizeof (buf))) > 0) { + buf[len] = '\0'; + (void) Plofspath(buf, buf, sizeof (buf)); + (void) strlcpy(s, buf, n); + return (s); + } + } + + /* + * If we couldn't get the name from /proc, take the lname and + * try to expand it on the current system to a real object path. + */ + fptr = mptr->map_file; + if ((fptr != NULL) && (fptr->file_lname != NULL)) { + (void) strlcpy(buf, fptr->file_lname, sizeof (buf)); + if (Pfindobj(P, buf, buf, sizeof (buf)) == NULL) + return (NULL); + (void) strlcpy(s, buf, n); + return (s); + } + + return (NULL); +} |