BSD 4_4_Lite2 release
[unix-history] / usr / src / sys / kern / vfs_lookup.c
index d369fbc..826fbfe 100644 (file)
@@ -1,48 +1,60 @@
 /*
 /*
- * Copyright (c) 1982, 1986, 1989 Regents of the University of California.
- * All rights reserved.
+ * Copyright (c) 1982, 1986, 1989, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ * (c) UNIX System Laboratories, Inc.
+ * All or some portions of this file are derived from material licensed
+ * to the University of California by American Telephone and Telegraph
+ * Co. or Unix System Laboratories, Inc. and are reproduced herein with
+ * the permission of UNIX System Laboratories, Inc.
  *
  *
- * Redistribution and use in source and binary forms are permitted
- * provided that the above copyright notice and this paragraph are
- * duplicated in all such forms and that any documentation,
- * advertising materials, and other materials related to such
- * distribution and use acknowledge that the software was developed
- * by the University of California, Berkeley.  The name of the
- * University may not be used to endorse or promote products derived
- * from this software without specific prior written permission.
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
  *
  *
- *     @(#)vfs_lookup.c        7.12 (Berkeley) %G%
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)vfs_lookup.c        8.10 (Berkeley) 5/27/95
  */
 
  */
 
-#include "param.h"
-#include "time.h"
-#include "namei.h"
-#include "vnode.h"
-#include "mount.h"
-#include "errno.h"
-#include "malloc.h"
+#include <sys/param.h>
+#include <sys/syslimits.h>
+#include <sys/time.h>
+#include <sys/namei.h>
+#include <sys/vnode.h>
+#include <sys/mount.h>
+#include <sys/errno.h>
+#include <sys/malloc.h>
+#include <sys/filedesc.h>
+#include <sys/proc.h>
 
 #ifdef KTRACE
 
 #ifdef KTRACE
-#include "user.h"
-#include "proc.h"
-#include "ktrace.h"
+#include <sys/ktrace.h>
 #endif
 
 /*
  * Convert a pathname into a pointer to a locked inode.
 #endif
 
 /*
  * Convert a pathname into a pointer to a locked inode.
- * This is a very central and rather complicated routine.
- *
- * The flag argument is LOOKUP, CREATE, RENAME, or DELETE depending on
- * whether the name is to be looked up, created, renamed, or deleted.
- * When CREATE, RENAME, or DELETE is specified, information usable in
- * creating, renaming, or deleting a directory entry may be calculated.
- * If flag has LOCKPARENT or'ed into it and the target of the pathname
- * exists, namei returns both the target and its parent directory locked.
- * When creating or renaming and LOCKPARENT is specified, the target may not
- * be ".".  When deleting and LOCKPARENT is specified, the target may be ".".
  *
  * The FOLLOW flag is set when symbolic links are to be followed
  * when they occur at the end of the name translation process.
  *
  * The FOLLOW flag is set when symbolic links are to be followed
  * when they occur at the end of the name translation process.
  *
  *     copy in name
  *     get starting directory
  *
  *     copy in name
  *     get starting directory
- * dirloop:
- *     copy next component of name to ndp->ni_dent
- *     handle degenerate case where name is null string
- *     if .. and on mounted filesys, find parent
- *     call lookup routine for next component name
- *       directory vnode returned in ni_dvp, unlocked unless LOCKPARENT set
- *       component vnode returned in ni_vp (if it exists), locked.
- *     if symbolic link, massage name in buffer and continue at dirloop
- *     if result inode is mounted on, find mounted on vnode
- *     if more components of name, do next level at dirloop
- *     return the answer in ni_vp as locked vnode;
- *       if LOCKPARENT set, return locked parent in ni_dvp
- *
- * NOTE: (LOOKUP | LOCKPARENT) currently returns the parent vnode unlocked.
+ *     while (!done && !error) {
+ *             call lookup to search path.
+ *             if symbolic link, massage name in buffer and continue
+ *     }
  */
  */
+int
 namei(ndp)
        register struct nameidata *ndp;
 {
 namei(ndp)
        register struct nameidata *ndp;
 {
+       register struct filedesc *fdp;  /* pointer to file descriptor state */
        register char *cp;              /* pointer into pathname argument */
        register char *cp;              /* pointer into pathname argument */
-       register struct vnode *dp = 0;  /* the directory we are searching */
-       register int i;                 /* Temp counter */
-       struct vnode *tdp;              /* saved dp */
-       struct mount *mp;               /* mount table entry */
-       int docache;                    /* == 0 do not cache last component */
-       int flag;                       /* LOOKUP, CREATE, RENAME or DELETE */
-       int wantparent;                 /* 1 => wantparent or lockparent flag */
-       int lockparent;                 /* 1 => lockparent flag */
-       int error = 0;
+       register struct vnode *dp;      /* the directory we are searching */
+       struct iovec aiov;              /* uio for reading symbolic links */
+       struct uio auio;
+       int error, linklen;
+       struct componentname *cnp = &ndp->ni_cnd;
+       struct proc *p = cnp->cn_proc;
+
+       ndp->ni_cnd.cn_cred = ndp->ni_cnd.cn_proc->p_ucred;
+#ifdef DIAGNOSTIC
+       if (!cnp->cn_cred || !cnp->cn_proc)
+               panic ("namei: bad cred/proc");
+       if (cnp->cn_nameiop & (~OPMASK))
+               panic ("namei: nameiop contaminated with flags");
+       if (cnp->cn_flags & OPMASK)
+               panic ("namei: flags contaminated with nameiops");
+#endif
+       fdp = cnp->cn_proc->p_fd;
 
 
-       flag = ndp->ni_nameiop & OPFLAG;
-       wantparent = ndp->ni_nameiop & (LOCKPARENT|WANTPARENT);
-       lockparent = ndp->ni_nameiop & LOCKPARENT;
-       docache = (ndp->ni_nameiop & NOCACHE) ^ NOCACHE;
-       if (flag == DELETE || wantparent)
-               docache = 0;
        /*
         * Get a buffer for the name to be translated, and copy the
         * name into the buffer.
         */
        /*
         * Get a buffer for the name to be translated, and copy the
         * name into the buffer.
         */
-       MALLOC(ndp->ni_pnbuf, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK);
+       if ((cnp->cn_flags & HASBUF) == 0)
+               MALLOC(cnp->cn_pnbuf, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK);
        if (ndp->ni_segflg == UIO_SYSSPACE)
        if (ndp->ni_segflg == UIO_SYSSPACE)
-               error = copystr(ndp->ni_dirp, ndp->ni_pnbuf, MAXPATHLEN,
-                   &ndp->ni_pathlen);
+               error = copystr(ndp->ni_dirp, cnp->cn_pnbuf,
+                           MAXPATHLEN, &ndp->ni_pathlen);
        else
        else
-               error = copyinstr(ndp->ni_dirp, ndp->ni_pnbuf, MAXPATHLEN,
-                   &ndp->ni_pathlen);
+               error = copyinstr(ndp->ni_dirp, cnp->cn_pnbuf,
+                           MAXPATHLEN, &ndp->ni_pathlen);
        if (error) {
        if (error) {
-               free(ndp->ni_pnbuf, M_NAMEI);
+               free(cnp->cn_pnbuf, M_NAMEI);
                ndp->ni_vp = NULL;
                return (error);
        }
                ndp->ni_vp = NULL;
                return (error);
        }
-       ndp->ni_ptr = ndp->ni_pnbuf;
        ndp->ni_loopcnt = 0;
        ndp->ni_loopcnt = 0;
-       dp = ndp->ni_cdir;
-       dp->v_count++;
 #ifdef KTRACE
 #ifdef KTRACE
-       if (KTRPOINT(u.u_procp, KTR_NAMEI))
-               ktrnamei(u.u_procp->p_tracep, ndp->ni_pnbuf);
+       if (KTRPOINT(cnp->cn_proc, KTR_NAMEI))
+               ktrnamei(cnp->cn_proc->p_tracep, cnp->cn_pnbuf);
 #endif
 
 #endif
 
-start:
        /*
        /*
-        * Get starting directory.
-        * Done at start of translation and after symbolic link.
+        * Get starting point for the translation.
         */
         */
-       if (*ndp->ni_ptr == '/') {
-               vrele(dp);
-               while (*ndp->ni_ptr == '/') {
-                       ndp->ni_ptr++;
-                       ndp->ni_pathlen--;
+       if ((ndp->ni_rootdir = fdp->fd_rdir) == NULL)
+               ndp->ni_rootdir = rootvnode;
+       dp = fdp->fd_cdir;
+       VREF(dp);
+       for (;;) {
+               /*
+                * Check if root directory should replace current directory.
+                * Done at start of translation and after symbolic link.
+                */
+               cnp->cn_nameptr = cnp->cn_pnbuf;
+               if (*(cnp->cn_nameptr) == '/') {
+                       vrele(dp);
+                       while (*(cnp->cn_nameptr) == '/') {
+                               cnp->cn_nameptr++;
+                               ndp->ni_pathlen--;
+                       }
+                       dp = ndp->ni_rootdir;
+                       VREF(dp);
+               }
+               ndp->ni_startdir = dp;
+               if (error = lookup(ndp)) {
+                       FREE(cnp->cn_pnbuf, M_NAMEI);
+                       return (error);
+               }
+               /*
+                * Check for symbolic link
+                */
+               if ((cnp->cn_flags & ISSYMLINK) == 0) {
+                       if ((cnp->cn_flags & (SAVENAME | SAVESTART)) == 0)
+                               FREE(cnp->cn_pnbuf, M_NAMEI);
+                       else
+                               cnp->cn_flags |= HASBUF;
+                       return (0);
                }
                }
-               if ((dp = ndp->ni_rdir) == NULL)
-                       dp = rootdir;
-               dp->v_count++;
+               if ((cnp->cn_flags & LOCKPARENT) && ndp->ni_pathlen == 1)
+                       VOP_UNLOCK(ndp->ni_dvp, 0, p);
+               if (ndp->ni_loopcnt++ >= MAXSYMLINKS) {
+                       error = ELOOP;
+                       break;
+               }
+               if (ndp->ni_pathlen > 1)
+                       MALLOC(cp, char *, MAXPATHLEN, M_NAMEI, M_WAITOK);
+               else
+                       cp = cnp->cn_pnbuf;
+               aiov.iov_base = cp;
+               aiov.iov_len = MAXPATHLEN;
+               auio.uio_iov = &aiov;
+               auio.uio_iovcnt = 1;
+               auio.uio_offset = 0;
+               auio.uio_rw = UIO_READ;
+               auio.uio_segflg = UIO_SYSSPACE;
+               auio.uio_procp = (struct proc *)0;
+               auio.uio_resid = MAXPATHLEN;
+               if (error = VOP_READLINK(ndp->ni_vp, &auio, cnp->cn_cred)) {
+                       if (ndp->ni_pathlen > 1)
+                               free(cp, M_NAMEI);
+                       break;
+               }
+               linklen = MAXPATHLEN - auio.uio_resid;
+               if (linklen + ndp->ni_pathlen >= MAXPATHLEN) {
+                       if (ndp->ni_pathlen > 1)
+                               free(cp, M_NAMEI);
+                       error = ENAMETOOLONG;
+                       break;
+               }
+               if (ndp->ni_pathlen > 1) {
+                       bcopy(ndp->ni_next, cp + linklen, ndp->ni_pathlen);
+                       FREE(cnp->cn_pnbuf, M_NAMEI);
+                       cnp->cn_pnbuf = cp;
+               } else
+                       cnp->cn_pnbuf[linklen] = '\0';
+               ndp->ni_pathlen += linklen;
+               vput(ndp->ni_vp);
+               dp = ndp->ni_dvp;
        }
        }
-       VOP_LOCK(dp);
-       ndp->ni_endoff = 0;
+       FREE(cnp->cn_pnbuf, M_NAMEI);
+       vrele(ndp->ni_dvp);
+       vput(ndp->ni_vp);
+       ndp->ni_vp = NULL;
+       return (error);
+}
+
+/*
+ * Search a pathname.
+ * This is a very central and rather complicated routine.
+ *
+ * The pathname is pointed to by ni_ptr and is of length ni_pathlen.
+ * The starting directory is taken from ni_startdir. The pathname is
+ * descended until done, or a symbolic link is encountered. The variable
+ * ni_more is clear if the path is completed; it is set to one if a
+ * symbolic link needing interpretation is encountered.
+ *
+ * The flag argument is LOOKUP, CREATE, RENAME, or DELETE depending on
+ * whether the name is to be looked up, created, renamed, or deleted.
+ * When CREATE, RENAME, or DELETE is specified, information usable in
+ * creating, renaming, or deleting a directory entry may be calculated.
+ * If flag has LOCKPARENT or'ed into it, the parent directory is returned
+ * locked. If flag has WANTPARENT or'ed into it, the parent directory is
+ * returned unlocked. Otherwise the parent directory is not returned. If
+ * the target of the pathname exists and LOCKLEAF is or'ed into the flag
+ * the target is returned locked, otherwise it is returned unlocked.
+ * When creating or renaming and LOCKPARENT is specified, the target may not
+ * be ".".  When deleting and LOCKPARENT is specified, the target may be ".".
+ * 
+ * Overall outline of lookup:
+ *
+ * dirloop:
+ *     identify next component of name at ndp->ni_ptr
+ *     handle degenerate case where name is null string
+ *     if .. and crossing mount points and on mounted filesys, find parent
+ *     call VOP_LOOKUP routine for next component name
+ *         directory vnode returned in ni_dvp, unlocked unless LOCKPARENT set
+ *         component vnode returned in ni_vp (if it exists), locked.
+ *     if result vnode is mounted on and crossing mount points,
+ *         find mounted on vnode
+ *     if more components of name, do next level at dirloop
+ *     return the answer in ni_vp, locked if LOCKLEAF set
+ *         if LOCKPARENT set, return locked parent in ni_dvp
+ *         if WANTPARENT set, return unlocked parent in ni_dvp
+ */
+int
+lookup(ndp)
+       register struct nameidata *ndp;
+{
+       register char *cp;              /* pointer into pathname argument */
+       register struct vnode *dp = 0;  /* the directory we are searching */
+       struct vnode *tdp;              /* saved dp */
+       struct mount *mp;               /* mount table entry */
+       int docache;                    /* == 0 do not cache last component */
+       int wantparent;                 /* 1 => wantparent or lockparent flag */
+       int rdonly;                     /* lookup read-only flag bit */
+       int error = 0;
+       struct componentname *cnp = &ndp->ni_cnd;
+       struct proc *p = cnp->cn_proc;
 
        /*
 
        /*
-        * We come to dirloop to search a new directory.
+        * Setup: break out flag bits into variables.
         */
         */
+       wantparent = cnp->cn_flags & (LOCKPARENT | WANTPARENT);
+       docache = (cnp->cn_flags & NOCACHE) ^ NOCACHE;
+       if (cnp->cn_nameiop == DELETE ||
+           (wantparent && cnp->cn_nameiop != CREATE))
+               docache = 0;
+       rdonly = cnp->cn_flags & RDONLY;
+       ndp->ni_dvp = NULL;
+       cnp->cn_flags &= ~ISSYMLINK;
+       dp = ndp->ni_startdir;
+       ndp->ni_startdir = NULLVP;
+       vn_lock(dp, LK_EXCLUSIVE | LK_RETRY, p);
+
 dirloop:
        /*
 dirloop:
        /*
-        * Copy next component of name to ndp->ni_dent.
-        * XXX kern_exec looks at d_name
-        * ??? The ni_hash value may be useful for vfs_cache
-        * XXX There must be the last component of the filename left
-        * somewhere accessible via. ndp for NFS (and any other stateless file
-        * systems) in case they are doing a CREATE. The "Towards a..." noted
-        * that ni_ptr would be left pointing to the last component, but since
-        * the ni_pnbuf gets free'd, that is not a good idea.
+        * Search a new directory.
+        *
+        * The cn_hash value is for use by vfs_cache.
+        * The last component of the filename is left accessible via
+        * cnp->cn_nameptr for callers that need the name. Callers needing
+        * the name set the SAVENAME flag. When done, they assume
+        * responsibility for freeing the pathname buffer.
         */
         */
-#ifdef notdef
-       for (cp = ndp->ni_ptr; *cp != 0 && *cp != '/'; cp++) {
-               if ((*cp & 0200) == 0)
-                       continue;
-               if ((*cp&0377) == ('/'|0200) || flag != DELETE) {
-                       error = EINVAL;
-                       goto bad;
-               }
-       }
-       ndp->ni_namelen = cp - ndp->ni_ptr;
-       if (ndp->ni_namelen >= MAXNAMLEN) {
+       cnp->cn_consume = 0;
+       cnp->cn_hash = 0;
+       for (cp = cnp->cn_nameptr; *cp != 0 && *cp != '/'; cp++)
+               cnp->cn_hash += (unsigned char)*cp;
+       cnp->cn_namelen = cp - cnp->cn_nameptr;
+       if (cnp->cn_namelen > NAME_MAX) {
                error = ENAMETOOLONG;
                goto bad;
        }
                error = ENAMETOOLONG;
                goto bad;
        }
-       ndp->ni_pathlen -= ndp->ni_namelen;
 #ifdef NAMEI_DIAGNOSTIC
        { char c = *cp;
        *cp = '\0';
 #ifdef NAMEI_DIAGNOSTIC
        { char c = *cp;
        *cp = '\0';
-       printf("{%s}: ", ndp->ni_ptr);
+       printf("{%s}: ", cnp->cn_nameptr);
        *cp = c; }
 #endif
        *cp = c; }
 #endif
-#else fornow
-       ndp->ni_hash = 0;
-       for (cp = ndp->ni_ptr, i = 0; *cp != 0 && *cp != '/'; cp++) {
-               if (i >= MAXNAMLEN) {
-                       error = ENAMETOOLONG;
-                       goto bad;
-               }
-               if (*cp & 0200)
-                       if ((*cp&0377) == ('/'|0200) || flag != DELETE) {
-                               error = EINVAL;
-                               goto bad;
-                       }
-               ndp->ni_dent.d_name[i++] = *cp;
-               ndp->ni_hash += (unsigned char)*cp * i;
-       }
-       ndp->ni_namelen = i;
-       ndp->ni_dent.d_namlen = i;
-       ndp->ni_dent.d_name[i] = '\0';
-       ndp->ni_pathlen -= i;
-#ifdef NAMEI_DIAGNOSTIC
-       printf("{%s}: ", ndp->ni_dent.d_name);
-#endif
-#endif fornow
+       ndp->ni_pathlen -= cnp->cn_namelen;
        ndp->ni_next = cp;
        ndp->ni_next = cp;
-       ndp->ni_makeentry = 1;
+       cnp->cn_flags |= MAKEENTRY;
        if (*cp == '\0' && docache == 0)
        if (*cp == '\0' && docache == 0)
-               ndp->ni_makeentry = 0;
-       ndp->ni_isdotdot = (ndp->ni_namelen == 2 &&
-               ndp->ni_dent.d_name[1] == '.' && ndp->ni_dent.d_name[0] == '.');
+               cnp->cn_flags &= ~MAKEENTRY;
+       if (cnp->cn_namelen == 2 &&
+           cnp->cn_nameptr[1] == '.' && cnp->cn_nameptr[0] == '.')
+               cnp->cn_flags |= ISDOTDOT;
+       else
+               cnp->cn_flags &= ~ISDOTDOT;
+       if (*ndp->ni_next == 0)
+               cnp->cn_flags |= ISLASTCN;
+       else
+               cnp->cn_flags &= ~ISLASTCN;
+
 
        /*
         * Check for degenerate name (e.g. / or "")
         * which is a way of talking about a directory,
         * e.g. like "/." or ".".
         */
 
        /*
         * Check for degenerate name (e.g. / or "")
         * which is a way of talking about a directory,
         * e.g. like "/." or ".".
         */
-       if (ndp->ni_ptr[0] == '\0') {
-               if (flag != LOOKUP || wantparent) {
+       if (cnp->cn_nameptr[0] == '\0') {
+               if (dp->v_type != VDIR) {
+                       error = ENOTDIR;
+                       goto bad;
+               }
+               if (cnp->cn_nameiop != LOOKUP) {
                        error = EISDIR;
                        goto bad;
                }
                        error = EISDIR;
                        goto bad;
                }
-               free(ndp->ni_pnbuf, M_NAMEI);
-               if (!(ndp->ni_nameiop & LOCKLEAF))
-                       VOP_UNLOCK(dp);
+               if (wantparent) {
+                       ndp->ni_dvp = dp;
+                       VREF(dp);
+               }
                ndp->ni_vp = dp;
                ndp->ni_vp = dp;
+               if (!(cnp->cn_flags & (LOCKPARENT | LOCKLEAF)))
+                       VOP_UNLOCK(dp, 0, p);
+               if (cnp->cn_flags & SAVESTART)
+                       panic("lookup: SAVESTART");
                return (0);
        }
 
        /*
         * Handle "..": two special cases.
         * 1. If at root directory (e.g. after chroot)
                return (0);
        }
 
        /*
         * Handle "..": two special cases.
         * 1. If at root directory (e.g. after chroot)
+        *    or at absolute root directory
         *    then ignore it so can't get out.
         * 2. If this vnode is the root of a mounted
         *    then ignore it so can't get out.
         * 2. If this vnode is the root of a mounted
-        *    file system, then replace it with the
+        *    filesystem, then replace it with the
         *    vnode which was mounted on so we take the
         *    .. in the other file system.
         */
         *    vnode which was mounted on so we take the
         *    .. in the other file system.
         */
-       if (ndp->ni_isdotdot) {
+       if (cnp->cn_flags & ISDOTDOT) {
                for (;;) {
                for (;;) {
-                       if (dp == ndp->ni_rdir || dp == rootdir) {
+                       if (dp == ndp->ni_rootdir || dp == rootvnode) {
                                ndp->ni_dvp = dp;
                                ndp->ni_dvp = dp;
-                               dp->v_count++;
+                               ndp->ni_vp = dp;
+                               VREF(dp);
                                goto nextname;
                        }
                                goto nextname;
                        }
-                       if ((dp->v_flag & VROOT) == 0)
+                       if ((dp->v_flag & VROOT) == 0 ||
+                           (cnp->cn_flags & NOCROSSMOUNT))
                                break;
                        tdp = dp;
                                break;
                        tdp = dp;
-                       dp = dp->v_mount->m_vnodecovered;
+                       dp = dp->v_mount->mnt_vnodecovered;
                        vput(tdp);
                        vput(tdp);
-                       VOP_LOCK(dp);
-                       dp->v_count++;
+                       VREF(dp);
+                       vn_lock(dp, LK_EXCLUSIVE | LK_RETRY, p);
                }
        }
 
        /*
         * We now have a segment name to search for, and a directory to search.
         */
                }
        }
 
        /*
         * We now have a segment name to search for, and a directory to search.
         */
-       if (error = VOP_LOOKUP(dp, ndp)) {
+unionlookup:
+       ndp->ni_dvp = dp;
+       ndp->ni_vp = NULL;
+       if (error = VOP_LOOKUP(dp, &ndp->ni_vp, cnp)) {
+#ifdef DIAGNOSTIC
                if (ndp->ni_vp != NULL)
                        panic("leaf should be empty");
                if (ndp->ni_vp != NULL)
                        panic("leaf should be empty");
+#endif
 #ifdef NAMEI_DIAGNOSTIC
                printf("not found\n");
 #endif
 #ifdef NAMEI_DIAGNOSTIC
                printf("not found\n");
 #endif
+               if ((error == ENOENT) &&
+                   (dp->v_flag & VROOT) &&
+                   (dp->v_mount->mnt_flag & MNT_UNION)) {
+                       tdp = dp;
+                       dp = dp->v_mount->mnt_vnodecovered;
+                       vput(tdp);
+                       VREF(dp);
+                       vn_lock(dp, LK_EXCLUSIVE | LK_RETRY, p);
+                       goto unionlookup;
+               }
+
+               if (error != EJUSTRETURN)
+                       goto bad;
                /*
                 * If creating and at end of pathname, then can consider
                 * allowing file to be created.
                 */
                /*
                 * If creating and at end of pathname, then can consider
                 * allowing file to be created.
                 */
-               if (ndp->ni_dvp->v_mount->m_flag & M_RDONLY)
+               if (rdonly) {
                        error = EROFS;
                        error = EROFS;
-               if (flag == LOOKUP || flag == DELETE ||
-                   error != ENOENT || *cp != 0)
                        goto bad;
                        goto bad;
+               }
                /*
                 * We return with ni_vp NULL to indicate that the entry
                 * doesn't currently exist, leaving a pointer to the
                 * (possibly locked) directory inode in ndp->ni_dvp.
                 */
                /*
                 * We return with ni_vp NULL to indicate that the entry
                 * doesn't currently exist, leaving a pointer to the
                 * (possibly locked) directory inode in ndp->ni_dvp.
                 */
-               FREE(ndp->ni_pnbuf, M_NAMEI);
-               return (0);     /* should this be ENOENT? */
+               if (cnp->cn_flags & SAVESTART) {
+                       ndp->ni_startdir = ndp->ni_dvp;
+                       VREF(ndp->ni_startdir);
+               }
+               return (0);
        }
 #ifdef NAMEI_DIAGNOSTIC
        printf("found\n");
 #endif
 
        /*
        }
 #ifdef NAMEI_DIAGNOSTIC
        printf("found\n");
 #endif
 
        /*
-        * Check for symbolic link
+        * Take into account any additional components consumed by
+        * the underlying filesystem.
         */
         */
-       dp = ndp->ni_vp;
-       if ((dp->v_type == VLNK) &&
-           ((ndp->ni_nameiop & FOLLOW) || *ndp->ni_next == '/')) {
-               struct iovec aiov;
-               struct uio auio;
-               int linklen;
-
-               if (++ndp->ni_loopcnt > MAXSYMLINKS) {
-                       error = ELOOP;
-                       goto bad2;
-               }
-               if (ndp->ni_pathlen > 1)
-                       MALLOC(cp, char *, MAXPATHLEN, M_NAMEI, M_WAITOK);
-               else
-                       cp = ndp->ni_pnbuf;
-               aiov.iov_base = cp;
-               aiov.iov_len = MAXPATHLEN;
-               auio.uio_iov = &aiov;
-               auio.uio_iovcnt = 1;
-               auio.uio_offset = 0;
-               auio.uio_rw = UIO_READ;
-               auio.uio_segflg = UIO_SYSSPACE;
-               auio.uio_resid = MAXPATHLEN;
-               if (error = VOP_READLINK(dp, &auio, ndp->ni_cred)) {
-                       if (ndp->ni_pathlen > 1)
-                               free(cp, M_NAMEI);
-                       goto bad2;
-               }
-               linklen = MAXPATHLEN - auio.uio_resid;
-               if (linklen + ndp->ni_pathlen >= MAXPATHLEN) {
-                       if (ndp->ni_pathlen > 1)
-                               free(cp, M_NAMEI);
-                       error = ENAMETOOLONG;
-                       goto bad2;
-               }
-               if (ndp->ni_pathlen > 1) {
-                       bcopy(ndp->ni_next, cp + linklen, ndp->ni_pathlen);
-                       FREE(ndp->ni_pnbuf, M_NAMEI);
-                       ndp->ni_pnbuf = cp;
-               } else
-                       ndp->ni_pnbuf[linklen] = '\0';
-               ndp->ni_ptr = cp;
-               ndp->ni_pathlen += linklen;
-               vput(dp);
-               dp = ndp->ni_dvp;
-               if (lockparent && *ndp->ni_next == '\0')
-                       VOP_UNLOCK(dp);
-               goto start;
+       if (cnp->cn_consume > 0) {
+               cnp->cn_nameptr += cnp->cn_consume;
+               ndp->ni_next += cnp->cn_consume;
+               ndp->ni_pathlen -= cnp->cn_consume;
+               cnp->cn_consume = 0;
        }
 
        }
 
+       dp = ndp->ni_vp;
        /*
         * Check to see if the vnode has been mounted on;
         * if so find the root of the mounted file system.
         */
        /*
         * Check to see if the vnode has been mounted on;
         * if so find the root of the mounted file system.
         */
-mntloop:
-       while (dp->v_type == VDIR && (mp = dp->v_mountedhere)) {
-               while(mp->m_flag & M_MLOCK) {
-                       mp->m_flag |= M_MWAIT;
-                       sleep((caddr_t)mp, PVFS);
-                       goto mntloop;
-               }
-               error = VFS_ROOT(dp->v_mountedhere, &tdp);
+       while (dp->v_type == VDIR && (mp = dp->v_mountedhere) &&
+              (cnp->cn_flags & NOCROSSMOUNT) == 0) {
+               if (vfs_busy(mp, 0, 0, p))
+                       continue;
+               error = VFS_ROOT(mp, &tdp);
+               vfs_unbusy(mp, p);
                if (error)
                        goto bad2;
                vput(dp);
                ndp->ni_vp = dp = tdp;
        }
 
                if (error)
                        goto bad2;
                vput(dp);
                ndp->ni_vp = dp = tdp;
        }
 
+       /*
+        * Check for symbolic link
+        */
+       if ((dp->v_type == VLNK) &&
+           ((cnp->cn_flags & FOLLOW) || *ndp->ni_next == '/')) {
+               cnp->cn_flags |= ISSYMLINK;
+               return (0);
+       }
+
 nextname:
        /*
         * Not a symbolic link.  If more pathname,
         * continue at next component, else return.
         */
 nextname:
        /*
         * Not a symbolic link.  If more pathname,
         * continue at next component, else return.
         */
-       ndp->ni_ptr = ndp->ni_next;
-       if (*ndp->ni_ptr == '/') {
-               while (*ndp->ni_ptr == '/') {
-                       ndp->ni_ptr++;
+       if (*ndp->ni_next == '/') {
+               cnp->cn_nameptr = ndp->ni_next;
+               while (*cnp->cn_nameptr == '/') {
+                       cnp->cn_nameptr++;
                        ndp->ni_pathlen--;
                }
                vrele(ndp->ni_dvp);
                goto dirloop;
        }
        /*
                        ndp->ni_pathlen--;
                }
                vrele(ndp->ni_dvp);
                goto dirloop;
        }
        /*
-        * Check for read-only file systems and executing texts
+        * Disallow directory write attempts on read-only file systems.
         */
         */
-       if (flag != LOOKUP && (error = vn_access(dp, VWRITE, ndp->ni_cred)))
+       if (rdonly &&
+           (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) {
+               error = EROFS;
                goto bad2;
                goto bad2;
+       }
+       if (cnp->cn_flags & SAVESTART) {
+               ndp->ni_startdir = ndp->ni_dvp;
+               VREF(ndp->ni_startdir);
+       }
        if (!wantparent)
                vrele(ndp->ni_dvp);
        if (!wantparent)
                vrele(ndp->ni_dvp);
-       if ((ndp->ni_nameiop & LOCKLEAF) == 0)
-               VOP_UNLOCK(dp);
-       FREE(ndp->ni_pnbuf, M_NAMEI);
+       if ((cnp->cn_flags & LOCKLEAF) == 0)
+               VOP_UNLOCK(dp, 0, p);
        return (0);
 
 bad2:
        return (0);
 
 bad2:
-       if (lockparent && *ndp->ni_next == '\0')
-               VOP_UNLOCK(ndp->ni_dvp);
+       if ((cnp->cn_flags & LOCKPARENT) && *ndp->ni_next == '\0')
+               VOP_UNLOCK(ndp->ni_dvp, 0, p);
        vrele(ndp->ni_dvp);
 bad:
        vput(dp);
        ndp->ni_vp = NULL;
        vrele(ndp->ni_dvp);
 bad:
        vput(dp);
        ndp->ni_vp = NULL;
-       FREE(ndp->ni_pnbuf, M_NAMEI);
+       return (error);
+}
+
+/*
+ * relookup - lookup a path name component
+ *    Used by lookup to re-aquire things.
+ */
+int
+relookup(dvp, vpp, cnp)
+       struct vnode *dvp, **vpp;
+       struct componentname *cnp;
+{
+       struct proc *p = cnp->cn_proc;
+       struct vnode *dp = 0;           /* the directory we are searching */
+       int docache;                    /* == 0 do not cache last component */
+       int wantparent;                 /* 1 => wantparent or lockparent flag */
+       int rdonly;                     /* lookup read-only flag bit */
+       int error = 0;
+#ifdef NAMEI_DIAGNOSTIC
+       int newhash;                    /* DEBUG: check name hash */
+       char *cp;                       /* DEBUG: check name ptr/len */
+#endif
+
+       /*
+        * Setup: break out flag bits into variables.
+        */
+       wantparent = cnp->cn_flags & (LOCKPARENT|WANTPARENT);
+       docache = (cnp->cn_flags & NOCACHE) ^ NOCACHE;
+       if (cnp->cn_nameiop == DELETE ||
+           (wantparent && cnp->cn_nameiop != CREATE))
+               docache = 0;
+       rdonly = cnp->cn_flags & RDONLY;
+       cnp->cn_flags &= ~ISSYMLINK;
+       dp = dvp;
+       vn_lock(dp, LK_EXCLUSIVE | LK_RETRY, p);
+
+/* dirloop: */
+       /*
+        * Search a new directory.
+        *
+        * The cn_hash value is for use by vfs_cache.
+        * The last component of the filename is left accessible via
+        * cnp->cn_nameptr for callers that need the name. Callers needing
+        * the name set the SAVENAME flag. When done, they assume
+        * responsibility for freeing the pathname buffer.
+        */
+#ifdef NAMEI_DIAGNOSTIC
+       for (newhash = 0, cp = cnp->cn_nameptr; *cp != 0 && *cp != '/'; cp++)
+               newhash += (unsigned char)*cp;
+       if (newhash != cnp->cn_hash)
+               panic("relookup: bad hash");
+       if (cnp->cn_namelen != cp - cnp->cn_nameptr)
+               panic ("relookup: bad len");
+       if (*cp != 0)
+               panic("relookup: not last component");
+       printf("{%s}: ", cnp->cn_nameptr);
+#endif
+
+       /*
+        * Check for degenerate name (e.g. / or "")
+        * which is a way of talking about a directory,
+        * e.g. like "/." or ".".
+        */
+       if (cnp->cn_nameptr[0] == '\0') {
+               if (cnp->cn_nameiop != LOOKUP || wantparent) {
+                       error = EISDIR;
+                       goto bad;
+               }
+               if (dp->v_type != VDIR) {
+                       error = ENOTDIR;
+                       goto bad;
+               }
+               if (!(cnp->cn_flags & LOCKLEAF))
+                       VOP_UNLOCK(dp, 0, p);
+               *vpp = dp;
+               if (cnp->cn_flags & SAVESTART)
+                       panic("lookup: SAVESTART");
+               return (0);
+       }
+
+       if (cnp->cn_flags & ISDOTDOT)
+               panic ("relookup: lookup on dot-dot");
+
+       /*
+        * We now have a segment name to search for, and a directory to search.
+        */
+       if (error = VOP_LOOKUP(dp, vpp, cnp)) {
+#ifdef DIAGNOSTIC
+               if (*vpp != NULL)
+                       panic("leaf should be empty");
+#endif
+               if (error != EJUSTRETURN)
+                       goto bad;
+               /*
+                * If creating and at end of pathname, then can consider
+                * allowing file to be created.
+                */
+               if (rdonly) {
+                       error = EROFS;
+                       goto bad;
+               }
+               /* ASSERT(dvp == ndp->ni_startdir) */
+               if (cnp->cn_flags & SAVESTART)
+                       VREF(dvp);
+               /*
+                * We return with ni_vp NULL to indicate that the entry
+                * doesn't currently exist, leaving a pointer to the
+                * (possibly locked) directory inode in ndp->ni_dvp.
+                */
+               return (0);
+       }
+       dp = *vpp;
+
+#ifdef DIAGNOSTIC
+       /*
+        * Check for symbolic link
+        */
+       if (dp->v_type == VLNK && (cnp->cn_flags & FOLLOW))
+               panic ("relookup: symlink found.\n");
+#endif
+
+       /*
+        * Disallow directory write attempts on read-only file systems.
+        */
+       if (rdonly &&
+           (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) {
+               error = EROFS;
+               goto bad2;
+       }
+       /* ASSERT(dvp == ndp->ni_startdir) */
+       if (cnp->cn_flags & SAVESTART)
+               VREF(dvp);
+       
+       if (!wantparent)
+               vrele(dvp);
+       if ((cnp->cn_flags & LOCKLEAF) == 0)
+               VOP_UNLOCK(dp, 0, p);
+       return (0);
+
+bad2:
+       if ((cnp->cn_flags & LOCKPARENT) && (cnp->cn_flags & ISLASTCN))
+               VOP_UNLOCK(dvp, 0, p);
+       vrele(dvp);
+bad:
+       vput(dp);
+       *vpp = NULL;
        return (error);
 }
        return (error);
 }