Print this page
        
@@ -19,11 +19,11 @@
  * CDDL HEADER END
  */
 
 /*
  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
- * Copyright 2016 Joyent, Inc.
+ * Copyright (c) 2015, Joyent, Inc. All rights reserved.
  * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
  */
 
 /*      Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T     */
 /*        All Rights Reserved   */
@@ -977,129 +977,28 @@
 
         return (ret);
 }
 
 /*
- * Clean a stale v_path from a vnode.  This is only performed if the v_path has
- * not been altered since it was found to be stale
- */
-static void
-vnode_clear_vpath(vnode_t *vp, char *vpath_old)
-{
-        mutex_enter(&vp->v_lock);
-        if (vp->v_path != vn_vpath_empty && vp->v_path == vpath_old) {
-                vp->v_path = vn_vpath_empty;
-                mutex_exit(&vp->v_lock);
-                kmem_free(vpath_old, strlen(vpath_old) + 1);
-        } else {
-                mutex_exit(&vp->v_lock);
-        }
-}
-
-/*
- * Validate that a pathname refers to a given vnode.
- */
-static int
-vnode_valid_pn(vnode_t *vp, vnode_t *vrootp, pathname_t *pn, pathname_t *rpn,
-    int flags, cred_t *cr)
-{
-        vnode_t *compvp;
-        /*
-         * If we are in a zone or a chroot environment, then we have to
-         * take additional steps, since the path to the root might not
-         * be readable with the current credentials, even though the
-         * process can legitmately access the file.  In this case, we
-         * do the following:
-         *
-         * lookuppnvp() with all privileges to get the resolved path.
-         * call localpath() to get the local portion of the path, and
-         * continue as normal.
-         *
-         * If the the conversion to a local path fails, then we continue
-         * as normal.  This is a heuristic to make process object file
-         * paths available from within a zone.  Because lofs doesn't
-         * support page operations, the vnode stored in the seg_t is
-         * actually the underlying real vnode, not the lofs node itself.
-         * Most of the time, the lofs path is the same as the underlying
-         * vnode (for example, /usr/lib/libc.so.1).
-         */
-        if (vrootp != rootdir) {
-                char *local = NULL;
-
-                VN_HOLD(rootdir);
-                if (lookuppnvp(pn, rpn, FOLLOW, NULL, &compvp, rootdir,
-                    rootdir, kcred) == 0) {
-                        local = localpath(rpn->pn_path, vrootp, kcred);
-                        VN_RELE(compvp);
-                }
-
-                /*
-                 * The original pn was changed through lookuppnvp().
-                 * Set it to local for next validation attempt.
-                 */
-                if (local) {
-                        (void) pn_set(pn, local);
-                } else {
-                        return (1);
-                }
-        }
-
-        /*
-         * We should have a local path at this point, so start the search from
-         * the root of the current process.
-         */
-        VN_HOLD(vrootp);
-        if (vrootp != rootdir)
-                VN_HOLD(vrootp);
-        if (lookuppnvp(pn, rpn, FOLLOW | flags, NULL, &compvp, vrootp, vrootp,
-            cr) == 0) {
-                /*
-                 * Check to see if the returned vnode is the same as the one we
-                 * expect.
-                 */
-                if (vn_compare(vp, compvp) ||
-                    vnode_match(vp, compvp, cr)) {
-                        VN_RELE(compvp);
-                        return (0);
-                } else {
-                        VN_RELE(compvp);
-                }
-        }
-
-        return (1);
-}
-
-/*
- * Struct for tracking vnodes with invalidated v_path entries during a
- * dirtopath reverse lookup.  By keepeing adequate state, those vnode can be
- * revisted to populate v_path.
- */
-struct dirpath_walk {
-        struct dirpath_walk     *dw_next;
-        vnode_t                 *dw_vnode;
-        vnode_t                 *dw_pvnode;
-        size_t                  dw_len;
-        char                    *dw_name;
-};
-
-/*
  * Given a directory, return the full, resolved path.  This looks up "..",
  * searches for the given vnode in the parent, appends the component, etc.  It
  * is used to implement vnodetopath() and getcwd() when the cached path fails.
  */
 static int
 dirtopath(vnode_t *vrootp, vnode_t *vp, char *buf, size_t buflen, int flags,
     cred_t *cr)
 {
         pathname_t      pn, rpn, emptypn;
-        vnode_t         *pvp = NULL, *startvp = vp;
-        int             err = 0;
+        vnode_t *cmpvp, *pvp = NULL;
+        vnode_t *startvp = vp;
+        int err = 0, vprivs;
         size_t          complen;
+        char *dbuf;
         dirent64_t      *dp;
-        char            *bufloc, *dbuf;
-        const size_t    dlen = DIRENT64_RECLEN(MAXPATHLEN);
-        struct dirpath_walk *dw_chain = NULL, *dw_entry;
+        char            *bufloc;
+        size_t          dlen = DIRENT64_RECLEN(MAXPATHLEN);
+        refstr_t        *mntpt;
 
         /* Operation only allowed on directories */
         ASSERT(vp->v_type == VDIR);
 
         /* We must have at least enough space for "/" */
@@ -1120,13 +1019,10 @@
          * during the loop.
          */
         VN_HOLD(vp);
 
         for (;;) {
-                int vprivs;
-                hrtime_t cached_stamp;
-
                 /*
                  * Return if we've reached the root.  If the buffer is empty,
                  * return '/'.  We explicitly don't use vn_compare(), since it
                  * compares the real vnodes.  A lofs mount of '/' would produce
                  * incorrect results otherwise.
@@ -1147,41 +1043,93 @@
                         err = EPERM;
                         goto out;
                 }
 
                 /*
+                 * Shortcut: see if this vnode is a mountpoint.  If so,
+                 * grab the path information from the vfs_t.
+                 */
+                if (vp->v_flag & VROOT) {
+
+                        mntpt = vfs_getmntpoint(vp->v_vfsp);
+                        if ((err = pn_set(&pn, (char *)refstr_value(mntpt)))
+                            == 0) {
+                                refstr_rele(mntpt);
+                                rpn.pn_path = rpn.pn_buf;
+
+                                /*
+                                 * Ensure the mountpoint still exists.
+                                 */
+                                VN_HOLD(vrootp);
+                                if (vrootp != rootdir)
+                                        VN_HOLD(vrootp);
+                                if (lookuppnvp(&pn, &rpn, flags, NULL,
+                                    &cmpvp, vrootp, vrootp, cr) == 0) {
+
+                                        if (VN_CMP(vp, cmpvp)) {
+                                                VN_RELE(cmpvp);
+
+                                                complen = strlen(rpn.pn_path);
+                                                bufloc -= complen;
+                                                if (bufloc < buf) {
+                                                        err = ERANGE;
+                                                        goto out;
+                                                }
+                                                bcopy(rpn.pn_path, bufloc,
+                                                    complen);
+                                                break;
+                                        } else {
+                                                VN_RELE(cmpvp);
+                                        }
+                                }
+                        } else {
+                                refstr_rele(mntpt);
+                        }
+                }
+
+                /*
                  * Shortcut: see if this vnode has correct v_path. If so,
                  * we have the work done.
                  */
                 mutex_enter(&vp->v_lock);
-                if (vp->v_path != vn_vpath_empty &&
-                    pn_set(&pn, vp->v_path) == 0) {
-                        cached_stamp = vp->v_path_stamp;
+                if (vp->v_path != NULL) {
+
+                        if ((err = pn_set(&pn, vp->v_path)) == 0) {
                         mutex_exit(&vp->v_lock);
                         rpn.pn_path = rpn.pn_buf;
 
-                        /* Ensure the v_path pointing to correct vnode */
-                        if (vnode_valid_pn(vp, vrootp, &pn, &rpn, flags,
-                            cr) == 0) {
+                                /*
+                                 * Ensure the v_path pointing to correct vnode
+                                 */
+                                VN_HOLD(vrootp);
+                                if (vrootp != rootdir)
+                                        VN_HOLD(vrootp);
+                                if (lookuppnvp(&pn, &rpn, flags, NULL,
+                                    &cmpvp, vrootp, vrootp, cr) == 0) {
+
+                                        if (VN_CMP(vp, cmpvp)) {
+                                                VN_RELE(cmpvp);
+
                                 complen = strlen(rpn.pn_path);
                                 bufloc -= complen;
                                 if (bufloc < buf) {
                                         err = ERANGE;
                                         goto out;
                                 }
-                                bcopy(rpn.pn_path, bufloc, complen);
+                                                bcopy(rpn.pn_path, bufloc,
+                                                    complen);
                                 break;
                         } else {
-                                /*
-                                 * Immediately nuke cached v_path entries known
-                                 * to be invalid.
-                                 */
-                                vn_clearpath(vp, cached_stamp);
+                                                VN_RELE(cmpvp);
                         }
+                                }
                 } else {
                         mutex_exit(&vp->v_lock);
                 }
+                } else {
+                        mutex_exit(&vp->v_lock);
+                }
 
                 /*
                  * Shortcuts failed, search for this vnode in its parent.  If
                  * this is a mountpoint, then get the vnode underneath.
                  */
@@ -1218,10 +1166,42 @@
                 if ((err = VOP_ACCESS(pvp, vprivs, 0, cr, NULL)) != 0) {
                         goto out;
                 }
 
                 /*
+                 * Try to obtain the path component from dnlc cache
+                 * before searching through the directory.
+                 */
+                if ((cmpvp = dnlc_reverse_lookup(vp, dbuf, dlen)) != NULL) {
+                        /*
+                         * If we got parent vnode as a result,
+                         * then the answered path is correct.
+                         */
+                        if (VN_CMP(cmpvp, pvp)) {
+                                VN_RELE(cmpvp);
+                                complen = strlen(dbuf);
+                                bufloc -= complen;
+                                if (bufloc <= buf) {
+                                        err = ENAMETOOLONG;
+                                        goto out;
+                                }
+                                bcopy(dbuf, bufloc, complen);
+
+                                /* Prepend a slash to the current path */
+                                *--bufloc = '/';
+
+                                /* And continue with the next component */
+                                VN_RELE(vp);
+                                vp = pvp;
+                                pvp = NULL;
+                                continue;
+                        } else {
+                                VN_RELE(cmpvp);
+                        }
+                }
+
+                /*
                  * Search the parent directory for the entry corresponding to
                  * this vnode.
                  */
                 if ((err = dirfindvp(vrootp, pvp, vp, cr, dbuf, dlen, &dp))
                     != 0)
@@ -1235,23 +1215,10 @@
                 bcopy(dp->d_name, bufloc, complen);
 
                 /* Prepend a slash to the current path.  */
                 *--bufloc = '/';
 
-                /*
-                 * Record the name and directory for later reconstruction and
-                 * link it up with the others.
-                 */
-                dw_entry = kmem_alloc(sizeof (*dw_entry), KM_SLEEP);
-                dw_entry->dw_name = kmem_alloc(complen + 1, KM_SLEEP);
-                VN_HOLD(dw_entry->dw_vnode = vp);
-                VN_HOLD(dw_entry->dw_pvnode = pvp);
-                bcopy(dp->d_name, dw_entry->dw_name, complen + 1);
-                dw_entry->dw_len = complen;
-                dw_entry->dw_next = dw_chain;
-                dw_chain = dw_entry;
-
                 /* And continue with the next component */
                 VN_RELE(vp);
                 vp = pvp;
                 pvp = NULL;
         }
@@ -1262,41 +1229,10 @@
         if (bufloc != buf)
                 ovbcopy(bufloc, buf, buflen - (bufloc - buf));
 
 out:
         /*
-         * Walk over encountered directory entries which were afflicted with a
-         * stale or absent v_path.  If the dirtopath was successful, we should
-         * possess the necessary information to populate all of them with a
-         * valid v_path.
-         *
-         * While processing this list, it is safe to call vn_setpath despite
-         * the fact that racing vnode actions may have altered v_path entries
-         * while the above loopwas still executing.  Any updated entries will
-         * have a newer v_path_stamp value which prevents an invalid overwrite.
-         *
-         * If an error was encountered during the search, freeing the chain is
-         * still required.
-         */
-        dw_entry = dw_chain;
-        while (dw_entry != NULL) {
-                struct dirpath_walk *next = dw_entry->dw_next;
-
-                if (err == 0) {
-                        vn_setpath(NULL, dw_entry->dw_pvnode,
-                            dw_entry->dw_vnode, dw_entry->dw_name,
-                            dw_entry->dw_len);
-                }
-
-                VN_RELE(dw_entry->dw_vnode);
-                VN_RELE(dw_entry->dw_pvnode);
-                kmem_free(dw_entry->dw_name, dw_entry->dw_len + 1);
-                kmem_free(dw_entry, sizeof (*dw_entry));
-                dw_entry = next;
-        }
-
-        /*
          * If the error was ESTALE and the current directory to look in
          * was the root for this lookup, the root for a mounted file
          * system, or the starting directory for lookups, then
          * return ENOENT instead of ESTALE.  In this case, no recovery
          * is possible by the higher level.  If ESTALE was returned for
@@ -1332,22 +1268,22 @@
  */
 static int
 vnodetopath_common(vnode_t *vrootp, vnode_t *vp, char *buf, size_t buflen,
     cred_t *cr, int flags)
 {
-        pathname_t pn;
-        int ret = 0;
-        vnode_t *realvp;
-        boolean_t doclose = B_FALSE;
+        pathname_t pn, rpn;
+        int ret, len;
+        vnode_t *compvp, *pvp, *realvp;
+        proc_t *p = curproc;
+        char path[MAXNAMELEN];
+        int doclose = 0;
 
         /*
          * If vrootp is NULL, get the root for curproc.  Callers with any other
          * requirements should pass in a different vrootp.
          */
         if (vrootp == NULL) {
-                proc_t *p = curproc;
-
                 mutex_enter(&p->p_lock);
                 if ((vrootp = PTOU(p)->u_rdir) == NULL)
                         vrootp = rootdir;
                 VN_HOLD(vrootp);
                 mutex_exit(&p->p_lock);
@@ -1365,63 +1301,167 @@
          */
         if (vp->v_type == VDIR && VOP_REALVP(vp, &realvp, NULL) == 0 &&
             realvp != vp) {
                 VN_HOLD(vp);
                 if (VOP_OPEN(&vp, FREAD, cr, NULL) == 0)
-                        doclose = B_TRUE;
+                        doclose = 1;
                 else
                         VN_RELE(vp);
         }
 
+        pn_alloc(&pn);
+
         /*
-         * Check to see if we have a valid cached path in the vnode.
+         * Check to see if we have a cached path in the vnode.
          */
-        pn_alloc(&pn);
         mutex_enter(&vp->v_lock);
-        if (vp->v_path != vn_vpath_empty) {
-                hrtime_t cached_stamp;
-                pathname_t rpn;
-
-                cached_stamp = vp->v_path_stamp;
+        if (vp->v_path != NULL) {
                 (void) pn_set(&pn, vp->v_path);
                 mutex_exit(&vp->v_lock);
 
+                pn_alloc(&rpn);
+
                 /* We should only cache absolute paths */
                 ASSERT(pn.pn_buf[0] == '/');
 
-                pn_alloc(&rpn);
-                if (vnode_valid_pn(vp, vrootp, &pn, &rpn, flags, cr) == 0) {
-                        /* Return the result, if we're able. */
-                        if (buflen > rpn.pn_pathlen) {
-                                bcopy(rpn.pn_path, buf, rpn.pn_pathlen + 1);
+                /*
+                 * If we are in a zone or a chroot environment, then we have to
+                 * take additional steps, since the path to the root might not
+                 * be readable with the current credentials, even though the
+                 * process can legitmately access the file.  In this case, we
+                 * do the following:
+                 *
+                 * lookuppnvp() with all privileges to get the resolved path.
+                 * call localpath() to get the local portion of the path, and
+                 * continue as normal.
+                 *
+                 * If the the conversion to a local path fails, then we continue
+                 * as normal.  This is a heuristic to make process object file
+                 * paths available from within a zone.  Because lofs doesn't
+                 * support page operations, the vnode stored in the seg_t is
+                 * actually the underlying real vnode, not the lofs node itself.
+                 * Most of the time, the lofs path is the same as the underlying
+                 * vnode (for example, /usr/lib/libc.so.1).
+                 */
+                if (vrootp != rootdir) {
+                        char *local = NULL;
+                        VN_HOLD(rootdir);
+                        if (lookuppnvp(&pn, &rpn, FOLLOW,
+                            NULL, &compvp, rootdir, rootdir, kcred) == 0) {
+                                local = localpath(rpn.pn_path, vrootp,
+                                    kcred);
+                                VN_RELE(compvp);
+                        }
+
+                        /*
+                         * The original pn was changed through lookuppnvp().
+                         * Set it to local for next validation attempt.
+                         */
+                        if (local) {
+                                (void) pn_set(&pn, local);
                         } else {
-                                ret = ENAMETOOLONG;
+                                goto notcached;
                         }
+                }
+
+                /*
+                 * We should have a local path at this point, so start the
+                 * search from the root of the current process.
+                 */
+                VN_HOLD(vrootp);
+                if (vrootp != rootdir)
+                        VN_HOLD(vrootp);
+                ret = lookuppnvp(&pn, &rpn, FOLLOW | flags, NULL,
+                    &compvp, vrootp, vrootp, cr);
+                if (ret == 0) {
+                        /*
+                         * Check to see if the returned vnode is the same as
+                         * the one we expect.  If not, give up.
+                         */
+                        if (!vn_compare(vp, compvp) &&
+                            !vnode_match(vp, compvp, cr)) {
+                                VN_RELE(compvp);
+                                goto notcached;
+                        }
+
+                        VN_RELE(compvp);
+
+                        /*
+                         * Return the result.
+                         */
+                        if (buflen <= rpn.pn_pathlen)
+                                goto notcached;
+
+                        bcopy(rpn.pn_path, buf, rpn.pn_pathlen + 1);
                         pn_free(&pn);
                         pn_free(&rpn);
-                        goto out;
+                        VN_RELE(vrootp);
+                        if (doclose) {
+                                (void) VOP_CLOSE(vp, FREAD, 1, 0, cr, NULL);
+                                VN_RELE(vp);
                 }
+                        return (0);
+                }
+
+notcached:
                 pn_free(&rpn);
-                vn_clearpath(vp, cached_stamp);
         } else {
                 mutex_exit(&vp->v_lock);
         }
+
         pn_free(&pn);
 
-        if (vp->v_type != VDIR) {
+        if (PROC_IS_BRANDED(curproc)) {
                 /*
-                 * The reverse lookup tricks used by dirtopath aren't possible
-                 * for non-directory entries.  The best which can be done is
-                 * clearing any stale v_path so later lookups can potentially
-                 * repopulate it with a valid path.
+                 * If v_path doesn't work out and we're in a branded zone,
+                 * we're not going to bother doing more work here:  because
+                 * directories from the global can be lofs mounted into odd
+                 * locations (e.g., /native in an lx zone), it is likely that
+                 * the DNLC reverse lookup will yield nothing.  Indeed, the
+                 * only certainty is that the DNLC reverse lookup will be
+                 * exceedingly painful; we save ourselves the substantial
+                 * grief of scanning the entire DNLC and kick out with ENOENT
+                 * in this case.
                  */
                 ret = ENOENT;
+        } else if (vp->v_type != VDIR) {
+                /*
+                 * If we don't have a directory, try to find it in the dnlc via
+                 * reverse lookup.  Once this is found, we can use the regular
+                 * directory search to find the full path.
+                 */
+                if ((pvp = dnlc_reverse_lookup(vp, path, MAXNAMELEN)) != NULL) {
+                        /*
+                         * Check if we have read privilege so, that
+                         * we can lookup the path in the directory
+                         */
+                        ret = 0;
+                        if ((flags & LOOKUP_CHECKREAD)) {
+                                ret = VOP_ACCESS(pvp, VREAD, 0, cr, NULL);
+                        }
+                        if (ret == 0) {
+                                ret = dirtopath(vrootp, pvp, buf, buflen,
+                                    flags, cr);
+                        }
+                        if (ret == 0) {
+                                len = strlen(buf);
+                                if (len + strlen(path) + 1 >= buflen) {
+                                        ret = ENAMETOOLONG;
         } else {
-                ret = dirtopath(vrootp, vp, buf, buflen, flags, cr);
+                                        if (buf[len - 1] != '/')
+                                                buf[len++] = '/';
+                                        bcopy(path, buf + len,
+                                            strlen(path) + 1);
         }
+                        }
 
-out:
+                        VN_RELE(pvp);
+                } else
+                        ret = ENOENT;
+        } else
+                ret = dirtopath(vrootp, vp, buf, buflen, flags, cr);
+
         VN_RELE(vrootp);
         if (doclose) {
                 (void) VOP_CLOSE(vp, FREAD, 1, 0, cr, NULL);
                 VN_RELE(vp);
         }