checkpoint (first pass at "...")
[unix-history] / usr / src / sys / miscfs / union / union_vnops.c
index 469537e..75c237c 100644 (file)
@@ -8,14 +8,13 @@
  *
  * %sccs.include.redist.c%
  *
  *
  * %sccs.include.redist.c%
  *
- *     @(#)union_vnops.c       1.5 (Berkeley) %G%
+ *     @(#)union_vnops.c       8.18 (Berkeley) %G%
  */
 
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/proc.h>
 #include <sys/file.h>
  */
 
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/proc.h>
 #include <sys/file.h>
-#include <sys/filedesc.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/vnode.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/vnode.h>
 #include <sys/namei.h>
 #include <sys/malloc.h>
 #include <sys/buf.h>
 #include <sys/namei.h>
 #include <sys/malloc.h>
 #include <sys/buf.h>
-#include "union.h"
+#include <sys/queue.h>
+#include <miscfs/union/union.h>
 
 
-/*
- * Create a shadow directory in the upper layer.
- * The new vnode is returned locked.
- */
-static int
-union_mkshadow(dvp, cnp, vpp)
-       struct vnode *dvp;
-       struct componentname *cnp;
-       struct vnode *vpp;
-{
-       int error;
-       struct vattr va;
-       struct proc *p = cnp->cn_proc;
-       struct componentname cn;
+#define FIXUP(un) { \
+       if (((un)->un_flags & UN_ULOCK) == 0) { \
+               union_fixup(un); \
+       } \
+}
 
 
-       /*
-        * policy: when creating the shadow directory in the
-        * upper layer, create it owned by the current user,
-        * group from parent directory, and mode 777 modified
-        * by umask (ie mostly identical to the mkdir syscall).
-        * (jsp, kb)
-        * TODO: create the directory owned by the user who
-        * did the mount (um->um_cred).
-        */
+static void
+union_fixup(un)
+       struct union_node *un;
+{
 
 
-       /*
-        * A new componentname structure must be faked up because
-        * there is no way to know where the upper level cnp came
-        * from or what it is being used for.  This must duplicate
-        * some of the work done by NDINIT, some of the work done
-        * by namei, some of the work done by lookup and some of
-        * the work done by VOP_LOOKUP when given a CREATE flag.
-        * Conclusion: Horrible.
-        *
-        * The pathname buffer will be FREEed by VOP_MKDIR.
-        */
-       cn.cn_pnbuf = malloc(cnp->cn_namelen+1, M_NAMEI, M_WAITOK);
-       bcopy(cnp->cn_nameptr, cn.cn_pnbuf, cnp->cn_namelen+1);
-
-       cn.cn_nameiop = CREATE;
-       cn.cn_flags = HASBUF | SAVENAME | ISLASTCN;
-       cn.cn_proc = cnp->cn_proc;
-       cn.cn_cred = cnp->cn_cred;
-       cn.cn_nameptr = cn.cn_pnbuf;
-       cn.cn_namelen = cnp->cn_namelen;
-       cn.cn_hash = cnp->cn_hash;
-       cn.cn_consume = cnp->cn_consume;
-
-       VATTR_NULL(&va);
-       va.va_type = VDIR;
-       va.va_mode = UN_DIRMODE &~ p->p_fd->fd_cmask;
-
-       /* LEASE_CHECK: dvp is locked */
-       LEASE_CHECK(dvp, p, p->p_ucred, LEASE_WRITE);
-
-       VREF(dvp);
-       error = VOP_MKDIR(dvp, vpp, &cn, &va);
-       VOP_LOCK(dvp);
-       return (error);
+       VOP_LOCK(un->un_uppervp);
+       un->un_flags |= UN_ULOCK;
 }
 
 static int
 }
 
 static int
-union_lookup1(udvp, dvp, vpp, cnp)
+union_lookup1(udvp, dvpp, vpp, cnp)
        struct vnode *udvp;
        struct vnode *udvp;
-       struct vnode *dvp;
+       struct vnode **dvpp;
        struct vnode **vpp;
        struct componentname *cnp;
 {
        int error;
        struct vnode *tdvp;
        struct vnode **vpp;
        struct componentname *cnp;
 {
        int error;
        struct vnode *tdvp;
+       struct vnode *dvp;
        struct mount *mp;
 
        struct mount *mp;
 
+       dvp = *dvpp;
+
        /*
         * If stepping up the directory tree, check for going
         * back across the mount point, in which case do what
        /*
         * If stepping up the directory tree, check for going
         * back across the mount point, in which case do what
@@ -104,42 +61,42 @@ union_lookup1(udvp, dvp, vpp, cnp)
         * hierarchy.
         */
        if (cnp->cn_flags & ISDOTDOT) {
         * hierarchy.
         */
        if (cnp->cn_flags & ISDOTDOT) {
-               for (;;) {
-                       if ((dvp->v_flag & VROOT) == 0 ||
-                           (cnp->cn_flags & NOCROSSMOUNT))
-                               break;
-
+               while ((dvp != udvp) && (dvp->v_flag & VROOT)) {
+                       /*
+                        * Don't do the NOCROSSMOUNT check
+                        * at this level.  By definition,
+                        * union fs deals with namespaces, not
+                        * filesystems.
+                        */
                        tdvp = dvp;
                        tdvp = dvp;
-                       dvp = dvp->v_mount->mnt_vnodecovered;
+                       *dvpp = dvp = dvp->v_mount->mnt_vnodecovered;
                        vput(tdvp);
                        VREF(dvp);
                        VOP_LOCK(dvp);
                }
        }
                        vput(tdvp);
                        VREF(dvp);
                        VOP_LOCK(dvp);
                }
        }
-       
+
         error = VOP_LOOKUP(dvp, &tdvp, cnp);
        if (error)
                return (error);
 
        /*
         error = VOP_LOOKUP(dvp, &tdvp, cnp);
        if (error)
                return (error);
 
        /*
-        * If going back up the directory tree, then the parent directory
-        * will have been unlocked, unless lookup found the last
-        * component.  In which case, re-lock the node here to allow
-        * it to be unlocked again (phew) in union_lookup.
+        * The parent directory will have been unlocked, unless lookup
+        * found the last component.  In which case, re-lock the node
+        * here to allow it to be unlocked again (phew) in union_lookup.
         */
         */
-       if ((cnp->cn_flags & ISDOTDOT) && !(cnp->cn_flags & ISLASTCN))
+       if (dvp != tdvp && !(cnp->cn_flags & ISLASTCN))
                VOP_LOCK(dvp);
 
        dvp = tdvp;
 
        /*
         * Lastly check if the current node is a mount point in
                VOP_LOCK(dvp);
 
        dvp = tdvp;
 
        /*
         * Lastly check if the current node is a mount point in
-        * which cse walk up the mount hierarchy making sure not to
+        * which case walk up the mount hierarchy making sure not to
         * bump into the root of the mount tree (ie. dvp != udvp).
         */
        while (dvp != udvp && (dvp->v_type == VDIR) &&
         * bump into the root of the mount tree (ie. dvp != udvp).
         */
        while (dvp != udvp && (dvp->v_type == VDIR) &&
-              (mp = dvp->v_mountedhere) &&
-              (cnp->cn_flags & NOCROSSMOUNT) == 0) {
+              (mp = dvp->v_mountedhere)) {
 
                if (mp->mnt_flag & MNT_MLOCK) {
                        mp->mnt_flag |= MNT_MWAIT;
 
                if (mp->mnt_flag & MNT_MLOCK) {
                        mp->mnt_flag |= MNT_MWAIT;
@@ -178,13 +135,31 @@ union_lookup(ap)
        struct componentname *cnp = ap->a_cnp;
        int lockparent = cnp->cn_flags & LOCKPARENT;
        int rdonly = cnp->cn_flags & RDONLY;
        struct componentname *cnp = ap->a_cnp;
        int lockparent = cnp->cn_flags & LOCKPARENT;
        int rdonly = cnp->cn_flags & RDONLY;
+       struct union_mount *um = MOUNTTOUNIONMOUNT(dvp->v_mount);
+       struct ucred *saved_cred;
+
+#ifdef notyet
+       if (cnp->cn_namelen == 3 &&
+                       cnp->cn_nameptr[2] == '.' &&
+                       cnp->cn_nameptr[1] == '.' &&
+                       cnp->cn_nameptr[0] == '.') {
+               dvp = *ap->a_vpp = LOWERVP(ap->a_dvp);
+               if (dvp == NULLVP)
+                       return (ENOENT);
+               VREF(dvp);
+               VOP_LOCK(dvp);
+               if (!lockparent || !(cnp->cn_flags & ISLASTCN))
+                       VOP_UNLOCK(ap->a_dvp);
+               return (0);
+       }
+#endif
 
        cnp->cn_flags |= LOCKPARENT;
 
        upperdvp = dun->un_uppervp;
        lowerdvp = dun->un_lowervp;
 
        cnp->cn_flags |= LOCKPARENT;
 
        upperdvp = dun->un_uppervp;
        lowerdvp = dun->un_lowervp;
-       uppervp = 0;
-       lowervp = 0;
+       uppervp = NULLVP;
+       lowervp = NULLVP;
 
        /*
         * do the lookup in the upper level.
 
        /*
         * do the lookup in the upper level.
@@ -192,13 +167,12 @@ union_lookup(ap)
         * then assume that something special is going
         * on and just return that vnode.
         */
         * then assume that something special is going
         * on and just return that vnode.
         */
-       if (upperdvp) {
-               VOP_LOCK(upperdvp);
-               uerror = union_lookup1(
-                       MOUNTTOUNIONMOUNT(dvp->v_mount)->um_uppervp,
-                       upperdvp, &uppervp, cnp);
-               if (uppervp != upperdvp)
-                       VOP_UNLOCK(upperdvp);
+       if (upperdvp != NULLVP) {
+               FIXUP(dun);
+               uerror = union_lookup1(um->um_uppervp, &upperdvp,
+                                       &uppervp, cnp);
+               /*if (uppervp == upperdvp)
+                       dun->un_flags |= UN_KLOCK;*/
 
                if (cnp->cn_consume != 0) {
                        *ap->a_vpp = uppervp;
 
                if (cnp->cn_consume != 0) {
                        *ap->a_vpp = uppervp;
@@ -217,18 +191,37 @@ union_lookup(ap)
         * back from the upper layer and return the lower vnode
         * instead.
         */
         * back from the upper layer and return the lower vnode
         * instead.
         */
-       if (lowerdvp) {
+       if (lowerdvp != NULLVP) {
+               int nameiop;
+
                VOP_LOCK(lowerdvp);
                VOP_LOCK(lowerdvp);
-               lerror = union_lookup1(
-                       MOUNTTOUNIONMOUNT(dvp->v_mount)->um_lowervp,
-                       lowerdvp, &lowervp, cnp);
+
+               /*
+                * Only do a LOOKUP on the bottom node, since
+                * we won't be making changes to it anyway.
+                */
+               nameiop = cnp->cn_nameiop;
+               cnp->cn_nameiop = LOOKUP;
+               if (um->um_op == UNMNT_BELOW) {
+                       saved_cred = cnp->cn_cred;
+                       cnp->cn_cred = um->um_cred;
+               }
+               lerror = union_lookup1(um->um_lowervp, &lowerdvp,
+                               &lowervp, cnp);
+               if (um->um_op == UNMNT_BELOW)
+                       cnp->cn_cred = saved_cred;
+               cnp->cn_nameiop = nameiop;
+
                if (lowervp != lowerdvp)
                        VOP_UNLOCK(lowerdvp);
 
                if (cnp->cn_consume != 0) {
                if (lowervp != lowerdvp)
                        VOP_UNLOCK(lowerdvp);
 
                if (cnp->cn_consume != 0) {
-                       if (uppervp) {
-                               vput(uppervp);
-                               uppervp = 0;
+                       if (uppervp != NULLVP) {
+                               if (uppervp == upperdvp)
+                                       vrele(uppervp);
+                               else
+                                       vput(uppervp);
+                               uppervp = NULLVP;
                        }
                        *ap->a_vpp = lowervp;
                        if (!lockparent)
                        }
                        *ap->a_vpp = lowervp;
                        if (!lockparent)
@@ -237,6 +230,14 @@ union_lookup(ap)
                }
        } else {
                lerror = ENOENT;
                }
        } else {
                lerror = ENOENT;
+               if ((cnp->cn_flags & ISDOTDOT) && dun->un_pvp != NULLVP) {
+                       lowervp = LOWERVP(dun->un_pvp);
+                       if (lowervp != NULLVP) {
+                               VREF(lowervp);
+                               VOP_LOCK(lowervp);
+                               lerror = 0;
+                       }
+               }
        }
 
        if (!lockparent)
        }
 
        if (!lockparent)
@@ -266,41 +267,42 @@ union_lookup(ap)
         *    whatever the bottom layer returned.
         */
 
         *    whatever the bottom layer returned.
         */
 
+       *ap->a_vpp = NULLVP;
+
        /* case 1. */
        if ((uerror != 0) && (lerror != 0)) {
        /* case 1. */
        if ((uerror != 0) && (lerror != 0)) {
-               *ap->a_vpp = 0;
                return (uerror);
        }
 
        /* case 2. */
        if (uerror != 0 /* && (lerror == 0) */ ) {
                if (lowervp->v_type == VDIR) { /* case 2b. */
                return (uerror);
        }
 
        /* case 2. */
        if (uerror != 0 /* && (lerror == 0) */ ) {
                if (lowervp->v_type == VDIR) { /* case 2b. */
+                       dun->un_flags &= ~UN_ULOCK;
+                       VOP_UNLOCK(upperdvp);
+                       uerror = union_mkshadow(um, upperdvp, cnp, &uppervp);
                        VOP_LOCK(upperdvp);
                        VOP_LOCK(upperdvp);
-                       uerror = union_mkshadow(upperdvp, cnp, &uppervp);
-                       if (uppervp != upperdvp)
-                               VOP_UNLOCK(upperdvp);
+                       dun->un_flags |= UN_ULOCK;
+
                        if (uerror) {
                        if (uerror) {
-                               if (lowervp) {
+                               if (lowervp != NULLVP) {
                                        vput(lowervp);
                                        vput(lowervp);
-                                       lowervp = 0;
+                                       lowervp = NULLVP;
                                }
                                return (uerror);
                        }
                }
        }
 
                                }
                                return (uerror);
                        }
                }
        }
 
+       if (lowervp != NULLVP)
+               VOP_UNLOCK(lowervp);
+
        error = union_allocvp(ap->a_vpp, dvp->v_mount, dvp, upperdvp, cnp,
                              uppervp, lowervp);
 
        error = union_allocvp(ap->a_vpp, dvp->v_mount, dvp, upperdvp, cnp,
                              uppervp, lowervp);
 
-       if (uppervp)
-               VOP_UNLOCK(uppervp);
-       if (lowervp)
-               VOP_UNLOCK(lowervp);
-
        if (error) {
        if (error) {
-               if (uppervp)
-                       vrele(uppervp);
-               if (lowervp)
+               if (uppervp != NULLVP)
+                       vput(uppervp);
+               if (lowervp != NULLVP)
                        vrele(lowervp);
        } else {
                if (*ap->a_vpp != dvp)
                        vrele(lowervp);
        } else {
                if (*ap->a_vpp != dvp)
@@ -323,12 +325,14 @@ union_create(ap)
        struct union_node *un = VTOUNION(ap->a_dvp);
        struct vnode *dvp = un->un_uppervp;
 
        struct union_node *un = VTOUNION(ap->a_dvp);
        struct vnode *dvp = un->un_uppervp;
 
-       if (dvp) {
+       if (dvp != NULLVP) {
                int error;
                struct vnode *vp;
 
                int error;
                struct vnode *vp;
 
+               FIXUP(un);
+
                VREF(dvp);
                VREF(dvp);
-               VOP_LOCK(dvp);
+               un->un_flags |= UN_KLOCK;
                vput(ap->a_dvp);
                error = VOP_CREATE(dvp, &vp, ap->a_cnp, ap->a_vap);
                if (error)
                vput(ap->a_dvp);
                error = VOP_CREATE(dvp, &vp, ap->a_cnp, ap->a_vap);
                if (error)
@@ -342,9 +346,8 @@ union_create(ap)
                                ap->a_cnp,
                                vp,
                                NULLVP);
                                ap->a_cnp,
                                vp,
                                NULLVP);
-               VOP_UNLOCK(vp);
                if (error)
                if (error)
-                       vrele(vp);
+                       vput(vp);
                return (error);
        }
 
                return (error);
        }
 
@@ -364,18 +367,20 @@ union_mknod(ap)
        struct union_node *un = VTOUNION(ap->a_dvp);
        struct vnode *dvp = un->un_uppervp;
 
        struct union_node *un = VTOUNION(ap->a_dvp);
        struct vnode *dvp = un->un_uppervp;
 
-       if (dvp) {
+       if (dvp != NULLVP) {
                int error;
                struct vnode *vp;
 
                int error;
                struct vnode *vp;
 
+               FIXUP(un);
+
                VREF(dvp);
                VREF(dvp);
-               VOP_LOCK(dvp);
+               un->un_flags |= UN_KLOCK;
                vput(ap->a_dvp);
                error = VOP_MKNOD(dvp, &vp, ap->a_cnp, ap->a_vap);
                if (error)
                        return (error);
 
                vput(ap->a_dvp);
                error = VOP_MKNOD(dvp, &vp, ap->a_cnp, ap->a_vap);
                if (error)
                        return (error);
 
-               if (vp) {
+               if (vp != NULLVP) {
                        error = union_allocvp(
                                        ap->a_vpp,
                                        ap->a_dvp->v_mount,
                        error = union_allocvp(
                                        ap->a_vpp,
                                        ap->a_dvp->v_mount,
@@ -384,9 +389,8 @@ union_mknod(ap)
                                        ap->a_cnp,
                                        vp,
                                        NULLVP);
                                        ap->a_cnp,
                                        vp,
                                        NULLVP);
-                       VOP_UNLOCK(vp);
                        if (error)
                        if (error)
-                               vrele(vp);
+                               vput(vp);
                }
                return (error);
        }
                }
                return (error);
        }
@@ -424,69 +428,26 @@ union_open(ap)
                 */
                tvp = un->un_lowervp;
                if ((ap->a_mode & FWRITE) && (tvp->v_type == VREG)) {
                 */
                tvp = un->un_lowervp;
                if ((ap->a_mode & FWRITE) && (tvp->v_type == VREG)) {
-                       struct nameidata nd;
-                       struct filedesc *fdp = p->p_fd;
-                       struct vnode *vp;
-                       /*int fmode;*/
-                       int cmode;
-
-                       /*
-                        * Open the named file in the upper layer.  Note that
-                        * the file may have come into existence *since* the
-                        * lookup was done, since the upper layer may really
-                        * be a loopback mount of some other filesystem...
-                        * so open the file with exclusive create and barf if
-                        * it already exists.
-                        * XXX - perhaps shoudl re-lookup the node (once more
-                        * with feeling) and simply open that.  Who knows.
-                        */
-                       /*
-                       NDINIT(&nd, CREATE, 0, UIO_SYSSPACE, un->un_path, p);
-                       fmode = (O_CREAT|O_TRUNC|O_EXCL);
-                       */
-                       cmode = UN_FILEMODE & ~fdp->fd_cmask;
-                       error = union_vn_create(&vp, un, cmode, p);
-                       if (error)
-                               return (error);
-                       un->un_uppervp = vp;    /* XXX */
-                       /* at this point, uppervp is locked */
-
-                       /*
-                        * Now, if the file is being opened with truncation,
-                        * then the (new) upper vnode is ready to fly,
-                        * otherwise the data from the lower vnode must be
-                        * copied to the upper layer first.  This only works
-                        * for regular files (check is made above).
-                        */
-                       if ((mode & O_TRUNC) == 0) {
-                               /*
-                                * XXX - should not ignore errors
-                                * from VOP_CLOSE
-                                */
-                               VOP_LOCK(tvp);
-                               error = VOP_OPEN(tvp, FREAD, cred, p);
-                               if (error == 0) {
-                                       error = union_copyfile(p, cred,
-                                                      tvp, un->un_uppervp);
-                                       VOP_UNLOCK(tvp);
-                                       (void) VOP_CLOSE(tvp, FREAD);
-                               } else {
-                                       VOP_UNLOCK(tvp);
-                               }
-                               VOP_UNLOCK(un->un_uppervp);
-                               (void) VOP_CLOSE(un->un_uppervp, FWRITE);
-                               VOP_LOCK(un->un_uppervp);
-                       }
+                       error = union_copyup(un, (mode&O_TRUNC) == 0, cred, p);
                        if (error == 0)
                                error = VOP_OPEN(un->un_uppervp, mode, cred, p);
                        if (error == 0)
                                error = VOP_OPEN(un->un_uppervp, mode, cred, p);
-                       VOP_UNLOCK(un->un_uppervp);
                        return (error);
                }
                        return (error);
                }
+
+               /*
+                * Just open the lower vnode
+                */
+               un->un_openl++;
+               VOP_LOCK(tvp);
+               error = VOP_OPEN(tvp, mode, cred, p);
+               VOP_UNLOCK(tvp);
+
+               return (error);
        }
 
        }
 
-       VOP_LOCK(tvp);
+       FIXUP(un);
+
        error = VOP_OPEN(tvp, mode, cred, p);
        error = VOP_OPEN(tvp, mode, cred, p);
-       VOP_UNLOCK(tvp);
 
        return (error);
 }
 
        return (error);
 }
@@ -500,8 +461,21 @@ union_close(ap)
                struct proc *a_p;
        } */ *ap;
 {
                struct proc *a_p;
        } */ *ap;
 {
+       struct union_node *un = VTOUNION(ap->a_vp);
+       struct vnode *vp;
 
 
-       return (VOP_CLOSE(OTHERVP(ap->a_vp), ap->a_fflag, ap->a_cred, ap->a_p));
+       if (un->un_uppervp != NULLVP) {
+               vp = un->un_uppervp;
+       } else {
+#ifdef UNION_DIAGNOSTIC
+               if (un->un_openl <= 0)
+                       panic("union: un_openl cnt");
+#endif
+               --un->un_openl;
+               vp = un->un_lowervp;
+       }
+
+       return (VOP_CLOSE(vp, ap->a_fflag, ap->a_cred, ap->a_p));
 }
 
 /*
 }
 
 /*
@@ -523,28 +497,35 @@ union_access(ap)
        } */ *ap;
 {
        struct union_node *un = VTOUNION(ap->a_vp);
        } */ *ap;
 {
        struct union_node *un = VTOUNION(ap->a_vp);
-       int error = 0;
+       int error = EACCES;
        struct vnode *vp;
 
        struct vnode *vp;
 
-       if (vp = un->un_lowervp) {
-               VOP_LOCK(vp);
-               error = VOP_ACCESS(vp, ap->a_mode, ap->a_cred, ap->a_p);
-               VOP_UNLOCK(vp);
-               if (error)
-                       return (error);
+       if ((vp = un->un_uppervp) != NULLVP) {
+               FIXUP(un);
+               return (VOP_ACCESS(vp, ap->a_mode, ap->a_cred, ap->a_p));
        }
 
        }
 
-       if (vp = un->un_uppervp) {
+       if ((vp = un->un_lowervp) != NULLVP) {
                VOP_LOCK(vp);
                error = VOP_ACCESS(vp, ap->a_mode, ap->a_cred, ap->a_p);
                VOP_LOCK(vp);
                error = VOP_ACCESS(vp, ap->a_mode, ap->a_cred, ap->a_p);
+               if (error == 0) {
+                       struct union_mount *um = MOUNTTOUNIONMOUNT(vp->v_mount);
+
+                       if (um->um_op == UNMNT_BELOW)
+                               error = VOP_ACCESS(vp, ap->a_mode,
+                                               um->um_cred, ap->a_p);
+               }
                VOP_UNLOCK(vp);
                VOP_UNLOCK(vp);
+               if (error)
+                       return (error);
        }
 
        return (error);
 }
 
 /*
        }
 
        return (error);
 }
 
 /*
- *  We handle getattr only to change the fsid.
+ * We handle getattr only to change the fsid and
+ * track object sizes
  */
 int
 union_getattr(ap)
  */
 int
 union_getattr(ap)
@@ -556,13 +537,63 @@ union_getattr(ap)
        } */ *ap;
 {
        int error;
        } */ *ap;
 {
        int error;
-       struct vnode *vp = OTHERVP(ap->a_vp);
+       struct union_node *un = VTOUNION(ap->a_vp);
+       struct vnode *vp = un->un_uppervp;
+       struct vattr *vap;
+       struct vattr va;
+
+
+       /*
+        * Some programs walk the filesystem hierarchy by counting
+        * links to directories to avoid stat'ing all the time.
+        * This means the link count on directories needs to be "correct".
+        * The only way to do that is to call getattr on both layers
+        * and fix up the link count.  The link count will not necessarily
+        * be accurate but will be large enough to defeat the tree walkers.
+        */
 
 
-       VOP_LOCK(vp);
-       error = VOP_GETATTR(vp, ap->a_vap, ap->a_cred, ap->a_p);
-       VOP_UNLOCK(vp);
+       vap = ap->a_vap;
+
+       vp = un->un_uppervp;
+       if (vp != NULLVP) {
+               /*
+                * It's not clear whether VOP_GETATTR is to be
+                * called with the vnode locked or not.  stat() calls
+                * it with (vp) locked, and fstat calls it with
+                * (vp) unlocked.
+                * In the mean time, compensate here by checking
+                * the union_node's lock flag.
+                */
+               if (un->un_flags & UN_LOCKED)
+                       FIXUP(un);
+
+               error = VOP_GETATTR(vp, vap, ap->a_cred, ap->a_p);
+               if (error)
+                       return (error);
+               union_newsize(ap->a_vp, vap->va_size, VNOVAL);
+       }
+
+       if (vp == NULLVP) {
+               vp = un->un_lowervp;
+       } else if (vp->v_type == VDIR) {
+               vp = un->un_lowervp;
+               vap = &va;
+       } else {
+               vp = NULLVP;
+       }
+
+       if (vp != NULLVP) {
+               VOP_LOCK(vp);
+               error = VOP_GETATTR(vp, vap, ap->a_cred, ap->a_p);
+               VOP_UNLOCK(vp);
+               if (error)
+                       return (error);
+               union_newsize(ap->a_vp, VNOVAL, vap->va_size);
+       }
+
+       if ((vap != ap->a_vap) && (vap->va_type == VDIR))
+               ap->a_vap->va_nlink += vap->va_nlink;
 
 
-       /* Requires that arguments be restored. */
        ap->a_vap->va_fsid = ap->a_vp->v_mount->mnt_stat.f_fsid.val[0];
        return (0);
 }
        ap->a_vap->va_fsid = ap->a_vp->v_mount->mnt_stat.f_fsid.val[0];
        return (0);
 }
@@ -579,17 +610,41 @@ union_setattr(ap)
        struct union_node *un = VTOUNION(ap->a_vp);
        int error;
 
        struct union_node *un = VTOUNION(ap->a_vp);
        int error;
 
-       if (un->un_uppervp) {
-               VOP_LOCK(un->un_uppervp);
+       /*
+        * Handle case of truncating lower object to zero size,
+        * by creating a zero length upper object.  This is to
+        * handle the case of open with O_TRUNC and O_CREAT.
+        */
+       if ((un->un_uppervp == NULLVP) &&
+           /* assert(un->un_lowervp != NULLVP) */
+           (un->un_lowervp->v_type == VREG) &&
+           (ap->a_vap->va_size == 0)) {
+               struct vnode *vp;
+
+               error = union_vn_create(&vp, un, ap->a_p);
+               if (error)
+                       return (error);
+
+               /* at this point, uppervp is locked */
+               union_newupper(un, vp);
+
+               VOP_UNLOCK(vp);
+               union_vn_close(un->un_uppervp, FWRITE, ap->a_cred, ap->a_p);
+               VOP_LOCK(vp);
+               un->un_flags |= UN_ULOCK;
+       }
+
+       /*
+        * Try to set attributes in upper layer,
+        * otherwise return read-only filesystem error.
+        */
+       if (un->un_uppervp != NULLVP) {
+               FIXUP(un);
                error = VOP_SETATTR(un->un_uppervp, ap->a_vap,
                                        ap->a_cred, ap->a_p);
                error = VOP_SETATTR(un->un_uppervp, ap->a_vap,
                                        ap->a_cred, ap->a_p);
-               VOP_UNLOCK(un->un_uppervp);
+               if ((error == 0) && (ap->a_vap->va_size != VNOVAL))
+                       union_newsize(ap->a_vp, ap->a_vap->va_size, VNOVAL);
        } else {
        } else {
-               /*
-                * XXX should do a copyfile (perhaps only if
-                * the file permission change, which would not
-                * track va_ctime correctly).
-                */
                error = EROFS;
        }
 
                error = EROFS;
        }
 
@@ -607,10 +662,34 @@ union_read(ap)
 {
        int error;
        struct vnode *vp = OTHERVP(ap->a_vp);
 {
        int error;
        struct vnode *vp = OTHERVP(ap->a_vp);
+       int dolock = (vp == LOWERVP(ap->a_vp));
 
 
-       VOP_LOCK(vp);
+       if (dolock)
+               VOP_LOCK(vp);
+       else
+               FIXUP(VTOUNION(ap->a_vp));
        error = VOP_READ(vp, ap->a_uio, ap->a_ioflag, ap->a_cred);
        error = VOP_READ(vp, ap->a_uio, ap->a_ioflag, ap->a_cred);
-       VOP_UNLOCK(vp);
+       if (dolock)
+               VOP_UNLOCK(vp);
+
+       /*
+        * XXX
+        * perhaps the size of the underlying object has changed under
+        * our feet.  take advantage of the offset information present
+        * in the uio structure.
+        */
+       if (error == 0) {
+               struct union_node *un = VTOUNION(ap->a_vp);
+               off_t cur = ap->a_uio->uio_offset;
+
+               if (vp == un->un_uppervp) {
+                       if (cur > un->un_uppersz)
+                               union_newsize(ap->a_vp, cur, VNOVAL);
+               } else {
+                       if (cur > un->un_lowersz)
+                               union_newsize(ap->a_vp, VNOVAL, cur);
+               }
+       }
 
        return (error);
 }
 
        return (error);
 }
@@ -626,10 +705,32 @@ union_write(ap)
 {
        int error;
        struct vnode *vp = OTHERVP(ap->a_vp);
 {
        int error;
        struct vnode *vp = OTHERVP(ap->a_vp);
+       int dolock = (vp == LOWERVP(ap->a_vp));
 
 
-       VOP_LOCK(vp);
+       if (dolock)
+               VOP_LOCK(vp);
+       else
+               FIXUP(VTOUNION(ap->a_vp));
        error = VOP_WRITE(vp, ap->a_uio, ap->a_ioflag, ap->a_cred);
        error = VOP_WRITE(vp, ap->a_uio, ap->a_ioflag, ap->a_cred);
-       VOP_UNLOCK(vp);
+       if (dolock)
+               VOP_UNLOCK(vp);
+
+       /*
+        * the size of the underlying object may be changed by the
+        * write.
+        */
+       if (error == 0) {
+               struct union_node *un = VTOUNION(ap->a_vp);
+               off_t cur = ap->a_uio->uio_offset;
+
+               if (vp == un->un_uppervp) {
+                       if (cur > un->un_uppersz)
+                               union_newsize(ap->a_vp, cur, VNOVAL);
+               } else {
+                       if (cur > un->un_lowersz)
+                               union_newsize(ap->a_vp, VNOVAL, cur);
+               }
+       }
 
        return (error);
 }
 
        return (error);
 }
@@ -691,11 +792,17 @@ union_fsync(ap)
        int error = 0;
        struct vnode *targetvp = OTHERVP(ap->a_vp);
 
        int error = 0;
        struct vnode *targetvp = OTHERVP(ap->a_vp);
 
-       if (targetvp) {
-               VOP_LOCK(targetvp);
+       if (targetvp != NULLVP) {
+               int dolock = (targetvp == LOWERVP(ap->a_vp));
+
+               if (dolock)
+                       VOP_LOCK(targetvp);
+               else
+                       FIXUP(VTOUNION(ap->a_vp));
                error = VOP_FSYNC(targetvp, ap->a_cred,
                                        ap->a_waitfor, ap->a_p);
                error = VOP_FSYNC(targetvp, ap->a_cred,
                                        ap->a_waitfor, ap->a_p);
-               VOP_UNLOCK(targetvp);
+               if (dolock)
+                       VOP_UNLOCK(targetvp);
        }
 
        return (error);
        }
 
        return (error);
@@ -726,18 +833,26 @@ union_remove(ap)
        struct union_node *dun = VTOUNION(ap->a_dvp);
        struct union_node *un = VTOUNION(ap->a_vp);
 
        struct union_node *dun = VTOUNION(ap->a_dvp);
        struct union_node *un = VTOUNION(ap->a_vp);
 
-       if (dun->un_uppervp && un->un_uppervp) {
+       if (dun->un_uppervp != NULLVP && un->un_uppervp != NULLVP) {
                struct vnode *dvp = dun->un_uppervp;
                struct vnode *vp = un->un_uppervp;
 
                struct vnode *dvp = dun->un_uppervp;
                struct vnode *vp = un->un_uppervp;
 
+               FIXUP(dun);
                VREF(dvp);
                VREF(dvp);
-               VOP_LOCK(dvp);
+               dun->un_flags |= UN_KLOCK;
                vput(ap->a_dvp);
                vput(ap->a_dvp);
+               FIXUP(un);
                VREF(vp);
                VREF(vp);
-               VOP_LOCK(vp);
+               un->un_flags |= UN_KLOCK;
                vput(ap->a_vp);
 
                error = VOP_REMOVE(dvp, vp, ap->a_cnp);
                vput(ap->a_vp);
 
                error = VOP_REMOVE(dvp, vp, ap->a_cnp);
+               if (!error)
+                       union_removed_upper(un);
+
+               /*
+                * XXX: should create a whiteout here
+                */
        } else {
                /*
                 * XXX: should create a whiteout here
        } else {
                /*
                 * XXX: should create a whiteout here
@@ -758,32 +873,49 @@ union_link(ap)
                struct componentname *a_cnp;
        } */ *ap;
 {
                struct componentname *a_cnp;
        } */ *ap;
 {
-       int error;
-       struct union_node *dun = VTOUNION(ap->a_vp);
-       struct union_node *un = VTOUNION(ap->a_tdvp);
-
-       if (dun->un_uppervp && un->un_uppervp) {
-               struct vnode *dvp = dun->un_uppervp;
-               struct vnode *vp = un->un_uppervp;
+       int error = 0;
+       struct union_node *un;
+       struct vnode *vp;
+       struct vnode *tdvp;
 
 
-               VREF(dvp);
-               VOP_LOCK(dvp);
-               vput(ap->a_vp);
-               VREF(vp);
-               vrele(ap->a_tdvp);
+       un = VTOUNION(ap->a_vp);
 
 
-               error = VOP_LINK(dvp, vp, ap->a_cnp);
+       if (ap->a_vp->v_op != ap->a_tdvp->v_op) {
+               tdvp = ap->a_tdvp;
        } else {
        } else {
-               /*
-                * XXX: need to copy to upper layer
-                * and do the link there.
-                */
-               vput(ap->a_vp);
-               vrele(ap->a_tdvp);
+               struct union_node *tdun = VTOUNION(ap->a_tdvp);
+               if (tdun->un_uppervp == NULLVP) {
+                       VOP_LOCK(ap->a_tdvp);
+                       if (un->un_uppervp == tdun->un_dirvp) {
+                               un->un_flags &= ~UN_ULOCK;
+                               VOP_UNLOCK(un->un_uppervp);
+                       }
+                       error = union_copyup(tdun, 1, ap->a_cnp->cn_cred,
+                                               ap->a_cnp->cn_proc);
+                       if (un->un_uppervp == tdun->un_dirvp) {
+                               VOP_LOCK(un->un_uppervp);
+                               un->un_flags |= UN_ULOCK;
+                       }
+                       VOP_UNLOCK(ap->a_tdvp);
+               }
+               tdvp = tdun->un_uppervp;
+       }
+
+       vp = un->un_uppervp;
+       if (vp == NULLVP)
                error = EROFS;
                error = EROFS;
+
+       if (error) {
+               vput(ap->a_vp);
+               return (error);
        }
 
        }
 
-       return (error);
+       FIXUP(un);
+       VREF(vp);
+       un->un_flags |= UN_KLOCK;
+       vput(ap->a_vp);
+
+       return (VOP_LINK(vp, tdvp, ap->a_cnp));
 }
 
 int
 }
 
 int
@@ -806,7 +938,7 @@ union_rename(ap)
 
        if (fdvp->v_op == union_vnodeop_p) {    /* always true */
                struct union_node *un = VTOUNION(fdvp);
 
        if (fdvp->v_op == union_vnodeop_p) {    /* always true */
                struct union_node *un = VTOUNION(fdvp);
-               if (un->un_uppervp == 0) {
+               if (un->un_uppervp == NULLVP) {
                        error = EROFS;
                        goto bad;
                }
                        error = EROFS;
                        goto bad;
                }
@@ -818,7 +950,7 @@ union_rename(ap)
 
        if (fvp->v_op == union_vnodeop_p) {     /* always true */
                struct union_node *un = VTOUNION(fvp);
 
        if (fvp->v_op == union_vnodeop_p) {     /* always true */
                struct union_node *un = VTOUNION(fvp);
-               if (un->un_uppervp == 0) {
+               if (un->un_uppervp == NULLVP) {
                        error = EROFS;
                        goto bad;
                }
                        error = EROFS;
                        goto bad;
                }
@@ -830,27 +962,31 @@ union_rename(ap)
 
        if (tdvp->v_op == union_vnodeop_p) {
                struct union_node *un = VTOUNION(tdvp);
 
        if (tdvp->v_op == union_vnodeop_p) {
                struct union_node *un = VTOUNION(tdvp);
-               if (un->un_uppervp == 0) {
+               if (un->un_uppervp == NULLVP) {
+                       /*
+                        * this should never happen in normal
+                        * operation but might if there was
+                        * a problem creating the top-level shadow
+                        * directory.
+                        */
                        error = EROFS;
                        goto bad;
                }
 
                tdvp = un->un_uppervp;
                VREF(tdvp);
                        error = EROFS;
                        goto bad;
                }
 
                tdvp = un->un_uppervp;
                VREF(tdvp);
-               VOP_LOCK(tdvp);
-               vput(ap->a_fdvp);
+               un->un_flags |= UN_KLOCK;
+               vput(ap->a_tdvp);
        }
 
        }
 
-       if (tvp && tvp->v_op == union_vnodeop_p) {
+       if (tvp != NULLVP && tvp->v_op == union_vnodeop_p) {
                struct union_node *un = VTOUNION(tvp);
                struct union_node *un = VTOUNION(tvp);
-               if (un->un_uppervp == 0) {
-                       error = EROFS;
-                       goto bad;
-               }
 
                tvp = un->un_uppervp;
 
                tvp = un->un_uppervp;
-               VREF(tvp);
-               VOP_LOCK(tvp);
+               if (tvp != NULLVP) {
+                       VREF(tvp);
+                       un->un_flags |= UN_KLOCK;
+               }
                vput(ap->a_tvp);
        }
 
                vput(ap->a_tvp);
        }
 
@@ -860,7 +996,7 @@ bad:
        vrele(fdvp);
        vrele(fvp);
        vput(tdvp);
        vrele(fdvp);
        vrele(fvp);
        vput(tdvp);
-       if (tvp)
+       if (tvp != NULLVP)
                vput(tvp);
 
        return (error);
                vput(tvp);
 
        return (error);
@@ -878,12 +1014,13 @@ union_mkdir(ap)
        struct union_node *un = VTOUNION(ap->a_dvp);
        struct vnode *dvp = un->un_uppervp;
 
        struct union_node *un = VTOUNION(ap->a_dvp);
        struct vnode *dvp = un->un_uppervp;
 
-       if (dvp) {
+       if (dvp != NULLVP) {
                int error;
                struct vnode *vp;
 
                int error;
                struct vnode *vp;
 
+               FIXUP(un);
                VREF(dvp);
                VREF(dvp);
-               VOP_LOCK(dvp);
+               un->un_flags |= UN_KLOCK;
                vput(ap->a_dvp);
                error = VOP_MKDIR(dvp, &vp, ap->a_cnp, ap->a_vap);
                if (error)
                vput(ap->a_dvp);
                error = VOP_MKDIR(dvp, &vp, ap->a_cnp, ap->a_vap);
                if (error)
@@ -897,9 +1034,8 @@ union_mkdir(ap)
                                ap->a_cnp,
                                vp,
                                NULLVP);
                                ap->a_cnp,
                                vp,
                                NULLVP);
-               VOP_UNLOCK(vp);
                if (error)
                if (error)
-                       vrele(vp);
+                       vput(vp);
                return (error);
        }
 
                return (error);
        }
 
@@ -919,18 +1055,26 @@ union_rmdir(ap)
        struct union_node *dun = VTOUNION(ap->a_dvp);
        struct union_node *un = VTOUNION(ap->a_vp);
 
        struct union_node *dun = VTOUNION(ap->a_dvp);
        struct union_node *un = VTOUNION(ap->a_vp);
 
-       if (dun->un_uppervp && un->un_uppervp) {
+       if (dun->un_uppervp != NULLVP && un->un_uppervp != NULLVP) {
                struct vnode *dvp = dun->un_uppervp;
                struct vnode *vp = un->un_uppervp;
 
                struct vnode *dvp = dun->un_uppervp;
                struct vnode *vp = un->un_uppervp;
 
+               FIXUP(dun);
                VREF(dvp);
                VREF(dvp);
-               VOP_LOCK(dvp);
+               dun->un_flags |= UN_KLOCK;
                vput(ap->a_dvp);
                vput(ap->a_dvp);
+               FIXUP(un);
                VREF(vp);
                VREF(vp);
-               VOP_LOCK(vp);
+               un->un_flags |= UN_KLOCK;
                vput(ap->a_vp);
 
                vput(ap->a_vp);
 
-               error = VOP_REMOVE(dvp, vp, ap->a_cnp);
+               error = VOP_RMDIR(dvp, vp, ap->a_cnp);
+               if (!error)
+                       union_removed_upper(un);
+
+               /*
+                * XXX: should create a whiteout here
+                */
        } else {
                /*
                 * XXX: should create a whiteout here
        } else {
                /*
                 * XXX: should create a whiteout here
@@ -956,17 +1100,18 @@ union_symlink(ap)
        struct union_node *un = VTOUNION(ap->a_dvp);
        struct vnode *dvp = un->un_uppervp;
 
        struct union_node *un = VTOUNION(ap->a_dvp);
        struct vnode *dvp = un->un_uppervp;
 
-       if (dvp) {
+       if (dvp != NULLVP) {
                int error;
                struct vnode *vp;
                struct mount *mp = ap->a_dvp->v_mount;
 
                int error;
                struct vnode *vp;
                struct mount *mp = ap->a_dvp->v_mount;
 
+               FIXUP(un);
                VREF(dvp);
                VREF(dvp);
-               VOP_LOCK(dvp);
+               un->un_flags |= UN_KLOCK;
                vput(ap->a_dvp);
                error = VOP_SYMLINK(dvp, &vp, ap->a_cnp,
                                        ap->a_vap, ap->a_target);
                vput(ap->a_dvp);
                error = VOP_SYMLINK(dvp, &vp, ap->a_cnp,
                                        ap->a_vap, ap->a_target);
-               *ap->a_vpp = 0;
+               *ap->a_vpp = NULLVP;
                return (error);
        }
 
                return (error);
        }
 
@@ -988,20 +1133,20 @@ union_readdir(ap)
                struct vnode *a_vp;
                struct uio *a_uio;
                struct ucred *a_cred;
                struct vnode *a_vp;
                struct uio *a_uio;
                struct ucred *a_cred;
+               int *a_eofflag;
+               u_long *a_cookies;
+               int a_ncookies;
        } */ *ap;
 {
        } */ *ap;
 {
-       int error = 0;
-       struct union_node *un = VTOUNION(ap->a_vp);
-
-       if (un->un_uppervp) {
-               struct vnode *vp = OTHERVP(ap->a_vp);
+       register struct union_node *un = VTOUNION(ap->a_vp);
+       register struct vnode *uvp = un->un_uppervp;
 
 
-               VOP_LOCK(vp);
-               error = VOP_READLINK(vp, ap->a_uio, ap->a_cred);
-               VOP_UNLOCK(vp);
-       }
+       if (uvp == NULLVP)
+               return (0);
 
 
-       return (error);
+       FIXUP(un);
+       ap->a_vp = uvp;
+       return (VOCALL(uvp->v_op, VOFFSET(vop_readdir), ap));
 }
 
 int
 }
 
 int
@@ -1014,10 +1159,15 @@ union_readlink(ap)
 {
        int error;
        struct vnode *vp = OTHERVP(ap->a_vp);
 {
        int error;
        struct vnode *vp = OTHERVP(ap->a_vp);
+       int dolock = (vp == LOWERVP(ap->a_vp));
 
 
-       VOP_LOCK(vp);
+       if (dolock)
+               VOP_LOCK(vp);
+       else
+               FIXUP(VTOUNION(ap->a_vp));
        error = VOP_READLINK(vp, ap->a_uio, ap->a_cred);
        error = VOP_READLINK(vp, ap->a_uio, ap->a_cred);
-       VOP_UNLOCK(vp);
+       if (dolock)
+               VOP_UNLOCK(vp);
 
        return (error);
 }
 
        return (error);
 }
@@ -1033,11 +1183,16 @@ union_abortop(ap)
        struct vnode *vp = OTHERVP(ap->a_dvp);
        struct union_node *un = VTOUNION(ap->a_dvp);
        int islocked = un->un_flags & UN_LOCKED;
        struct vnode *vp = OTHERVP(ap->a_dvp);
        struct union_node *un = VTOUNION(ap->a_dvp);
        int islocked = un->un_flags & UN_LOCKED;
+       int dolock = (vp == LOWERVP(ap->a_dvp));
 
 
-       if (islocked)
-               VOP_LOCK(vp);
+       if (islocked) {
+               if (dolock)
+                       VOP_LOCK(vp);
+               else
+                       FIXUP(VTOUNION(ap->a_dvp));
+       }
        error = VOP_ABORTOP(vp, ap->a_cnp);
        error = VOP_ABORTOP(vp, ap->a_cnp);
-       if (islocked)
+       if (islocked && dolock)
                VOP_UNLOCK(vp);
 
        return (error);
                VOP_UNLOCK(vp);
 
        return (error);
@@ -1049,6 +1204,7 @@ union_inactive(ap)
                struct vnode *a_vp;
        } */ *ap;
 {
                struct vnode *a_vp;
        } */ *ap;
 {
+       struct union_node *un = VTOUNION(ap->a_vp);
 
        /*
         * Do nothing (and _don't_ bypass).
 
        /*
         * Do nothing (and _don't_ bypass).
@@ -1063,13 +1219,16 @@ union_inactive(ap)
         * That's too much work for now.
         */
 
         * That's too much work for now.
         */
 
-#ifdef DIAGNOSTIC
-       struct union_node *un = VTOUNION(ap->a_vp);
-
+#ifdef UNION_DIAGNOSTIC
        if (un->un_flags & UN_LOCKED)
                panic("union: inactivating locked node");
        if (un->un_flags & UN_LOCKED)
                panic("union: inactivating locked node");
+       if (un->un_flags & UN_ULOCK)
+               panic("union: inactivating w/locked upper node");
 #endif
 
 #endif
 
+       if ((un->un_flags & UN_CACHED) == 0)
+               vgone(ap->a_vp);
+
        return (0);
 }
 
        return (0);
 }
 
@@ -1079,31 +1238,9 @@ union_reclaim(ap)
                struct vnode *a_vp;
        } */ *ap;
 {
                struct vnode *a_vp;
        } */ *ap;
 {
-       struct vnode *vp = ap->a_vp;
-       struct union_node *un = VTOUNION(vp);
-       struct vnode *uppervp = un->un_uppervp;
-       struct vnode *lowervp = un->un_lowervp;
-       struct vnode *dirvp = un->un_dirvp;
-       char *path = un->un_path;
 
 
-       /*
-        * Note: in vop_reclaim, vp->v_op == dead_vnodeop_p,
-        * so we can't call VOPs on ourself.
-        */
-       /* After this assignment, this node will not be re-used. */
-       un->un_uppervp = 0;
-       un->un_lowervp = 0;
-       un->un_dirvp = 0;
-       un->un_path = NULL;
-       union_freevp(vp);
-       if (uppervp)
-               vrele(uppervp);
-       if (lowervp)
-               vrele(lowervp);
-       if (dirvp)
-               vrele(dirvp);
-       if (path)
-               free(path, M_TEMP);
+       union_freevp(ap->a_vp);
+
        return (0);
 }
 
        return (0);
 }
 
@@ -1111,9 +1248,30 @@ int
 union_lock(ap)
        struct vop_lock_args *ap;
 {
 union_lock(ap)
        struct vop_lock_args *ap;
 {
-       struct union_node *un = VTOUNION(ap->a_vp);
+       struct vnode *vp = ap->a_vp;
+       struct union_node *un;
+
+start:
+       while (vp->v_flag & VXLOCK) {
+               vp->v_flag |= VXWANT;
+               sleep((caddr_t)vp, PINOD);
+       }
+
+       un = VTOUNION(vp);
 
 
-       while (un->un_flags & UN_LOCKED) {
+       if (un->un_uppervp != NULLVP) {
+               if (((un->un_flags & UN_ULOCK) == 0) &&
+                   (vp->v_usecount != 0)) {
+                       un->un_flags |= UN_ULOCK;
+                       VOP_LOCK(un->un_uppervp);
+               }
+#ifdef DIAGNOSTIC
+               if (un->un_flags & UN_KLOCK)
+                       panic("union: dangling upper lock");
+#endif
+       }
+
+       if (un->un_flags & UN_LOCKED) {
 #ifdef DIAGNOSTIC
                if (curproc && un->un_pid == curproc->p_pid &&
                            un->un_pid > -1 && curproc->p_pid > -1)
 #ifdef DIAGNOSTIC
                if (curproc && un->un_pid == curproc->p_pid &&
                            un->un_pid > -1 && curproc->p_pid > -1)
@@ -1121,8 +1279,8 @@ union_lock(ap)
 #endif
                un->un_flags |= UN_WANT;
                sleep((caddr_t) &un->un_flags, PINOD);
 #endif
                un->un_flags |= UN_WANT;
                sleep((caddr_t) &un->un_flags, PINOD);
+               goto start;
        }
        }
-       un->un_flags |= UN_LOCKED;
 
 #ifdef DIAGNOSTIC
        if (curproc)
 
 #ifdef DIAGNOSTIC
        if (curproc)
@@ -1130,6 +1288,9 @@ union_lock(ap)
        else
                un->un_pid = -1;
 #endif
        else
                un->un_pid = -1;
 #endif
+
+       un->un_flags |= UN_LOCKED;
+       return (0);
 }
 
 int
 }
 
 int
@@ -1147,6 +1308,12 @@ union_unlock(ap)
 #endif
 
        un->un_flags &= ~UN_LOCKED;
 #endif
 
        un->un_flags &= ~UN_LOCKED;
+
+       if ((un->un_flags & (UN_ULOCK|UN_KLOCK)) == UN_ULOCK)
+               VOP_UNLOCK(un->un_uppervp);
+
+       un->un_flags &= ~(UN_ULOCK|UN_KLOCK);
+
        if (un->un_flags & UN_WANT) {
                un->un_flags &= ~UN_WANT;
                wakeup((caddr_t) &un->un_flags);
        if (un->un_flags & UN_WANT) {
                un->un_flags &= ~UN_WANT;
                wakeup((caddr_t) &un->un_flags);
@@ -1155,6 +1322,8 @@ union_unlock(ap)
 #ifdef DIAGNOSTIC
        un->un_pid = 0;
 #endif
 #ifdef DIAGNOSTIC
        un->un_pid = 0;
 #endif
+
+       return (0);
 }
 
 int
 }
 
 int
@@ -1169,10 +1338,15 @@ union_bmap(ap)
 {
        int error;
        struct vnode *vp = OTHERVP(ap->a_vp);
 {
        int error;
        struct vnode *vp = OTHERVP(ap->a_vp);
+       int dolock = (vp == LOWERVP(ap->a_vp));
 
 
-       VOP_LOCK(vp);
+       if (dolock)
+               VOP_LOCK(vp);
+       else
+               FIXUP(VTOUNION(ap->a_vp));
        error = VOP_BMAP(vp, ap->a_bn, ap->a_vpp, ap->a_bnp, ap->a_runp);
        error = VOP_BMAP(vp, ap->a_bn, ap->a_vpp, ap->a_bnp, ap->a_runp);
-       VOP_UNLOCK(vp);
+       if (dolock)
+               VOP_UNLOCK(vp);
 
        return (error);
 }
 
        return (error);
 }
@@ -1210,10 +1384,15 @@ union_pathconf(ap)
 {
        int error;
        struct vnode *vp = OTHERVP(ap->a_vp);
 {
        int error;
        struct vnode *vp = OTHERVP(ap->a_vp);
+       int dolock = (vp == LOWERVP(ap->a_vp));
 
 
-       VOP_LOCK(vp);
+       if (dolock)
+               VOP_LOCK(vp);
+       else
+               FIXUP(VTOUNION(ap->a_vp));
        error = VOP_PATHCONF(vp, ap->a_name, ap->a_retval);
        error = VOP_PATHCONF(vp, ap->a_name, ap->a_retval);
-       VOP_UNLOCK(vp);
+       if (dolock)
+               VOP_UNLOCK(vp);
 
        return (error);
 }
 
        return (error);
 }
@@ -1253,7 +1432,7 @@ union_strategy(ap)
        bp->b_vp = OTHERVP(bp->b_vp);
 
 #ifdef DIAGNOSTIC
        bp->b_vp = OTHERVP(bp->b_vp);
 
 #ifdef DIAGNOSTIC
-       if (bp->b_vp == 0)
+       if (bp->b_vp == NULLVP)
                panic("union_strategy: nil vp");
        if (((bp->b_flags & B_READ) == 0) &&
            (bp->b_vp == LOWERVP(savedvp)))
                panic("union_strategy: nil vp");
        if (((bp->b_flags & B_READ) == 0) &&
            (bp->b_vp == LOWERVP(savedvp)))