add union_revoke operator
[unix-history] / usr / src / sys / miscfs / union / union_vnops.c
index 7766a4a..2de83d4 100644 (file)
@@ -1,14 +1,14 @@
 /*
 /*
- * Copyright (c) 1992, 1993, 1994 The Regents of the University of California.
- * Copyright (c) 1992, 1993, 1994 Jan-Simon Pendry.
- * All rights reserved.
+ * Copyright (c) 1992, 1993, 1994, 1995 Jan-Simon Pendry.
+ * Copyright (c) 1992, 1993, 1994, 1995
+ *     The Regents of the University of California.  All rights reserved.
  *
  * This code is derived from software contributed to Berkeley by
  * Jan-Simon Pendry.
  *
  * %sccs.include.redist.c%
  *
  *
  * This code is derived from software contributed to Berkeley by
  * Jan-Simon Pendry.
  *
  * %sccs.include.redist.c%
  *
- *     @(#)union_vnops.c       8.6 (Berkeley) %G%
+ *     @(#)union_vnops.c       8.26 (Berkeley) %G%
  */
 
 #include <sys/param.h>
  */
 
 #include <sys/param.h>
@@ -16,6 +16,7 @@
 #include <sys/proc.h>
 #include <sys/file.h>
 #include <sys/time.h>
 #include <sys/proc.h>
 #include <sys/file.h>
 #include <sys/time.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/vnode.h>
 #include <sys/mount.h>
 #include <sys/types.h>
 #include <sys/vnode.h>
 #include <sys/mount.h>
@@ -41,16 +42,19 @@ union_fixup(un)
 }
 
 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
@@ -58,18 +62,15 @@ union_lookup1(udvp, dvp, vpp, cnp)
         * hierarchy.
         */
        if (cnp->cn_flags & ISDOTDOT) {
         * hierarchy.
         */
        if (cnp->cn_flags & ISDOTDOT) {
-               for (;;) {
+               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.
                         */
                        /*
                         * Don't do the NOCROSSMOUNT check
                         * at this level.  By definition,
                         * union fs deals with namespaces, not
                         * filesystems.
                         */
-                       if ((dvp->v_flag & VROOT) == 0)
-                               break;
-
                        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);
@@ -137,6 +138,24 @@ union_lookup(ap)
        int rdonly = cnp->cn_flags & RDONLY;
        struct union_mount *um = MOUNTTOUNIONMOUNT(dvp->v_mount);
        struct ucred *saved_cred;
        int rdonly = cnp->cn_flags & RDONLY;
        struct union_mount *um = MOUNTTOUNIONMOUNT(dvp->v_mount);
        struct ucred *saved_cred;
+       int iswhiteout;
+       struct vattr va;
+
+#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;
 
 
        cnp->cn_flags |= LOCKPARENT;
 
@@ -144,6 +163,7 @@ union_lookup(ap)
        lowerdvp = dun->un_lowervp;
        uppervp = NULLVP;
        lowervp = NULLVP;
        lowerdvp = dun->un_lowervp;
        uppervp = NULLVP;
        lowervp = NULLVP;
+       iswhiteout = 0;
 
        /*
         * do the lookup in the upper level.
 
        /*
         * do the lookup in the upper level.
@@ -151,9 +171,9 @@ 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) {
+       if (upperdvp != NULLVP) {
                FIXUP(dun);
                FIXUP(dun);
-               uerror = union_lookup1(um->um_uppervp, upperdvp,
+               uerror = union_lookup1(um->um_uppervp, &upperdvp,
                                        &uppervp, cnp);
                /*if (uppervp == upperdvp)
                        dun->un_flags |= UN_KLOCK;*/
                                        &uppervp, cnp);
                /*if (uppervp == upperdvp)
                        dun->un_flags |= UN_KLOCK;*/
@@ -164,6 +184,16 @@ union_lookup(ap)
                                cnp->cn_flags &= ~LOCKPARENT;
                        return (uerror);
                }
                                cnp->cn_flags &= ~LOCKPARENT;
                        return (uerror);
                }
+               if (uerror == ENOENT || uerror == EJUSTRETURN) {
+                       if (cnp->cn_flags & ISWHITEOUT) {
+                               iswhiteout = 1;
+                       } else if (lowerdvp != NULLVP) {
+                               lerror = VOP_GETATTR(upperdvp, &va,
+                                       cnp->cn_cred, cnp->cn_proc);
+                               if (lerror == 0 && (va.va_flags & OPAQUE))
+                                       iswhiteout = 1;
+                       }
+               }
        } else {
                uerror = ENOENT;
        }
        } else {
                uerror = ENOENT;
        }
@@ -175,7 +205,7 @@ 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 && !iswhiteout) {
                int nameiop;
 
                VOP_LOCK(lowerdvp);
                int nameiop;
 
                VOP_LOCK(lowerdvp);
@@ -190,7 +220,7 @@ union_lookup(ap)
                        saved_cred = cnp->cn_cred;
                        cnp->cn_cred = um->um_cred;
                }
                        saved_cred = cnp->cn_cred;
                        cnp->cn_cred = um->um_cred;
                }
-               lerror = union_lookup1(um->um_lowervp, lowerdvp,
+               lerror = union_lookup1(um->um_lowervp, &lowerdvp,
                                &lowervp, cnp);
                if (um->um_op == UNMNT_BELOW)
                        cnp->cn_cred = saved_cred;
                                &lowervp, cnp);
                if (um->um_op == UNMNT_BELOW)
                        cnp->cn_cred = saved_cred;
@@ -200,7 +230,7 @@ union_lookup(ap)
                        VOP_UNLOCK(lowerdvp);
 
                if (cnp->cn_consume != 0) {
                        VOP_UNLOCK(lowerdvp);
 
                if (cnp->cn_consume != 0) {
-                       if (uppervp) {
+                       if (uppervp != NULLVP) {
                                if (uppervp == upperdvp)
                                        vrele(uppervp);
                                else
                                if (uppervp == upperdvp)
                                        vrele(uppervp);
                                else
@@ -214,6 +244,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)
@@ -260,7 +298,7 @@ union_lookup(ap)
                        dun->un_flags |= UN_ULOCK;
 
                        if (uerror) {
                        dun->un_flags |= UN_ULOCK;
 
                        if (uerror) {
-                               if (lowervp) {
+                               if (lowervp != NULLVP) {
                                        vput(lowervp);
                                        lowervp = NULLVP;
                                }
                                        vput(lowervp);
                                        lowervp = NULLVP;
                                }
@@ -269,16 +307,16 @@ union_lookup(ap)
                }
        }
 
                }
        }
 
-       if (lowervp)
+       if (lowervp != NULLVP)
                VOP_UNLOCK(lowervp);
 
        error = union_allocvp(ap->a_vpp, dvp->v_mount, dvp, upperdvp, cnp,
                VOP_UNLOCK(lowervp);
 
        error = union_allocvp(ap->a_vpp, dvp->v_mount, dvp, upperdvp, cnp,
-                             uppervp, lowervp);
+                             uppervp, lowervp, 1);
 
        if (error) {
 
        if (error) {
-               if (uppervp)
+               if (uppervp != NULLVP)
                        vput(uppervp);
                        vput(uppervp);
-               if (lowervp)
+               if (lowervp != NULLVP)
                        vrele(lowervp);
        } else {
                if (*ap->a_vpp != dvp)
                        vrele(lowervp);
        } else {
                if (*ap->a_vpp != dvp)
@@ -301,14 +339,16 @@ 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;
+               struct mount *mp;
 
                FIXUP(un);
 
                VREF(dvp);
                un->un_flags |= UN_KLOCK;
 
                FIXUP(un);
 
                VREF(dvp);
                un->un_flags |= UN_KLOCK;
+               mp = ap->a_dvp->v_mount;
                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)
@@ -316,12 +356,13 @@ union_create(ap)
 
                error = union_allocvp(
                                ap->a_vpp,
 
                error = union_allocvp(
                                ap->a_vpp,
-                               ap->a_dvp->v_mount,
-                               ap->a_dvp,
+                               mp,
+                               NULLVP,
                                NULLVP,
                                ap->a_cnp,
                                vp,
                                NULLVP,
                                ap->a_cnp,
                                vp,
-                               NULLVP);
+                               NULLVP,
+                               1);
                if (error)
                        vput(vp);
                return (error);
                if (error)
                        vput(vp);
                return (error);
@@ -331,6 +372,23 @@ union_create(ap)
        return (EROFS);
 }
 
        return (EROFS);
 }
 
+int
+union_whiteout(ap)
+       struct vop_whiteout_args /* {
+               struct vnode *a_dvp;
+               struct componentname *a_cnp;
+               int a_flags;
+       } */ *ap;
+{
+       struct union_node *un = VTOUNION(ap->a_dvp);
+
+       if (un->un_uppervp == NULLVP)
+               return (EOPNOTSUPP);
+
+       FIXUP(un);
+       return (VOP_WHITEOUT(un->un_uppervp, ap->a_cnp, ap->a_flags));
+}
+
 int
 union_mknod(ap)
        struct vop_mknod_args /* {
 int
 union_mknod(ap)
        struct vop_mknod_args /* {
@@ -343,28 +401,31 @@ 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;
+               struct mount *mp;
 
                FIXUP(un);
 
                VREF(dvp);
                un->un_flags |= UN_KLOCK;
 
                FIXUP(un);
 
                VREF(dvp);
                un->un_flags |= UN_KLOCK;
+               mp = ap->a_dvp->v_mount;
                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,
                        error = union_allocvp(
                                        ap->a_vpp,
-                                       ap->a_dvp->v_mount,
-                                       ap->a_dvp,
+                                       mp,
+                                       NULLVP,
                                        NULLVP,
                                        ap->a_cnp,
                                        vp,
                                        NULLVP,
                                        ap->a_cnp,
                                        vp,
-                                       NULLVP);
+                                       NULLVP,
+                                       1);
                        if (error)
                                vput(vp);
                }
                        if (error)
                                vput(vp);
                }
@@ -404,77 +465,7 @@ 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 vnode *vp;
-                       int i;
-
-                       /*
-                        * 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 should re-lookup the node (once more
-                        * with feeling) and simply open that.  Who knows.
-                        */
-                       error = union_vn_create(&vp, un, p);
-                       if (error)
-                               return (error);
-
-                       /* at this point, uppervp is locked */
-                       union_newupper(un, vp);
-                       un->un_flags |= UN_ULOCK;
-
-                       /*
-                        * 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);
-                               }
-
-#ifdef UNION_DIAGNOSTIC
-                               if (!error)
-                                       uprintf("union: copied up %s\n",
-                                                               un->un_path);
-#endif
-                       }
-
-                       un->un_flags &= ~UN_ULOCK;
-                       VOP_UNLOCK(un->un_uppervp);
-                       union_vn_close(un->un_uppervp, FWRITE, cred, p);
-                       VOP_LOCK(un->un_uppervp);
-                       un->un_flags |= UN_ULOCK;
-
-                       /*
-                        * Subsequent IOs will go to the top layer, so
-                        * call close on the lower vnode and open on the
-                        * upper vnode to ensure that the filesystem keeps
-                        * its references counts right.  This doesn't do
-                        * the right thing with (cred) and (FREAD) though.
-                        * Ignoring error returns is not righ, either.
-                        */
-                       for (i = 0; i < un->un_openl; i++) {
-                               (void) VOP_CLOSE(tvp, FREAD);
-                               (void) VOP_OPEN(un->un_uppervp, FREAD, cred, p);
-                       }
-                       un->un_openl = 0;
-
+                       error = union_copyup(un, (mode&O_TRUNC) == 0, cred, p);
                        if (error == 0)
                                error = VOP_OPEN(un->un_uppervp, mode, cred, p);
                        return (error);
                        if (error == 0)
                                error = VOP_OPEN(un->un_uppervp, mode, cred, p);
                        return (error);
@@ -510,7 +501,7 @@ union_close(ap)
        struct union_node *un = VTOUNION(ap->a_vp);
        struct vnode *vp;
 
        struct union_node *un = VTOUNION(ap->a_vp);
        struct vnode *vp;
 
-       if (un->un_uppervp) {
+       if (un->un_uppervp != NULLVP) {
                vp = un->un_uppervp;
        } else {
 #ifdef UNION_DIAGNOSTIC
                vp = un->un_uppervp;
        } else {
 #ifdef UNION_DIAGNOSTIC
@@ -546,12 +537,12 @@ union_access(ap)
        int error = EACCES;
        struct vnode *vp;
 
        int error = EACCES;
        struct vnode *vp;
 
-       if (vp = un->un_uppervp) {
+       if ((vp = un->un_uppervp) != NULLVP) {
                FIXUP(un);
                return (VOP_ACCESS(vp, ap->a_mode, ap->a_cred, ap->a_p));
        }
 
                FIXUP(un);
                return (VOP_ACCESS(vp, ap->a_mode, ap->a_cred, ap->a_p));
        }
 
-       if (vp = un->un_lowervp) {
+       if ((vp = un->un_lowervp) != NULLVP) {
                VOP_LOCK(vp);
                error = VOP_ACCESS(vp, ap->a_mode, ap->a_cred, ap->a_p);
                if (error == 0) {
                VOP_LOCK(vp);
                error = VOP_ACCESS(vp, ap->a_mode, ap->a_cred, ap->a_p);
                if (error == 0) {
@@ -570,7 +561,8 @@ union_access(ap)
 }
 
 /*
 }
 
 /*
- *  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)
@@ -601,10 +593,21 @@ union_getattr(ap)
 
        vp = un->un_uppervp;
        if (vp != NULLVP) {
 
        vp = un->un_uppervp;
        if (vp != NULLVP) {
-               FIXUP(un);
+               /*
+                * 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);
                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) {
        }
 
        if (vp == NULLVP) {
@@ -617,17 +620,16 @@ union_getattr(ap)
        }
 
        if (vp != NULLVP) {
        }
 
        if (vp != NULLVP) {
-               VOP_LOCK(vp);
                error = VOP_GETATTR(vp, vap, ap->a_cred, ap->a_p);
                error = VOP_GETATTR(vp, vap, ap->a_cred, ap->a_p);
-               VOP_UNLOCK(vp);
                if (error)
                        return (error);
                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;
 
        }
 
        if ((vap != ap->a_vap) && (vap->va_type == VDIR))
                ap->a_vap->va_nlink += vap->va_nlink;
 
-       vap->va_fsid = ap->a_vp->v_mount->mnt_stat.f_fsid.val[0];
+       ap->a_vap->va_fsid = ap->a_vp->v_mount->mnt_stat.f_fsid.val[0];
        return (0);
 }
 
        return (0);
 }
 
@@ -650,21 +652,11 @@ union_setattr(ap)
         */
        if ((un->un_uppervp == NULLVP) &&
            /* assert(un->un_lowervp != NULLVP) */
         */
        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);
+           (un->un_lowervp->v_type == VREG)) {
+               error = union_copyup(un, (ap->a_vap->va_size != 0),
+                                               ap->a_cred, ap->a_p);
                if (error)
                        return (error);
                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;
        }
 
        /*
        }
 
        /*
@@ -675,6 +667,8 @@ union_setattr(ap)
                FIXUP(un);
                error = VOP_SETATTR(un->un_uppervp, ap->a_vap,
                                        ap->a_cred, ap->a_p);
                FIXUP(un);
                error = VOP_SETATTR(un->un_uppervp, ap->a_vap,
                                        ap->a_cred, ap->a_p);
+               if ((error == 0) && (ap->a_vap->va_size != VNOVAL))
+                       union_newsize(ap->a_vp, ap->a_vap->va_size, VNOVAL);
        } else {
                error = EROFS;
        }
        } else {
                error = EROFS;
        }
@@ -703,6 +697,25 @@ union_read(ap)
        if (dolock)
                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);
 }
 
@@ -716,20 +729,42 @@ union_write(ap)
        } */ *ap;
 {
        int error;
        } */ *ap;
 {
        int error;
-       struct vnode *vp = OTHERVP(ap->a_vp);
-       int dolock = (vp == LOWERVP(ap->a_vp));
+       struct vnode *vp;
+       struct union_node *un = VTOUNION(ap->a_vp);
 
 
-       if (dolock)
-               VOP_LOCK(vp);
-       else
-               FIXUP(VTOUNION(ap->a_vp));
+       vp = UPPERVP(ap->a_vp);
+       if (vp == NULLVP)
+               panic("union: missing upper layer in write");
+
+       FIXUP(un);
        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);
-       if (dolock)
-               VOP_UNLOCK(vp);
+
+       /*
+        * the size of the underlying object may be changed by the
+        * write.
+        */
+       if (error == 0) {
+               off_t cur = ap->a_uio->uio_offset;
+
+               if (cur > un->un_uppersz)
+                       union_newsize(ap->a_vp, cur, VNOVAL);
+       }
 
        return (error);
 }
 
 
        return (error);
 }
 
+union_lease(ap)
+       struct vop_lease_args /* {
+               struct vnode *a_vp;
+               struct proc *a_p;
+               struct ucred *a_cred;
+               int a_flag;
+       } */ *ap;
+{
+
+       return (VOP_LEASE(OTHERVP(ap->a_vp), ap->a_p, ap->a_cred, ap->a_flag));
+}
+
 int
 union_ioctl(ap)
        struct vop_ioctl_args /* {
 int
 union_ioctl(ap)
        struct vop_ioctl_args /* {
@@ -761,6 +796,22 @@ union_select(ap)
                                ap->a_cred, ap->a_p));
 }
 
                                ap->a_cred, ap->a_p));
 }
 
+int
+union_revoke(ap)
+       struct vop_revoke_args /* {
+               struct vnode *a_vp;
+               int a_flags;
+       } */ *ap;
+{
+       struct vnode *vp = ap->a_vp;
+
+       if (UPPERVP(vp))
+               VOP_REVOKE(UPPERVP(vp), ap->a_flags);
+       if (LOWERVP(vp))
+               VOP_REVOKE(UPPERVP(vp), ap->a_flags);
+       vgone(vp);
+}
+
 int
 union_mmap(ap)
        struct vop_mmap_args /* {
 int
 union_mmap(ap)
        struct vop_mmap_args /* {
@@ -787,7 +838,7 @@ 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) {
+       if (targetvp != NULLVP) {
                int dolock = (targetvp == LOWERVP(ap->a_vp));
 
                if (dolock)
                int dolock = (targetvp == LOWERVP(ap->a_vp));
 
                if (dolock)
@@ -828,9 +879,13 @@ 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)
+               panic("union remove: null upper vnode");
+
+       if (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;
+               struct componentname *cnp = ap->a_cnp;
 
                FIXUP(dun);
                VREF(dvp);
 
                FIXUP(dun);
                VREF(dvp);
@@ -841,20 +896,18 @@ union_remove(ap)
                un->un_flags |= UN_KLOCK;
                vput(ap->a_vp);
 
                un->un_flags |= UN_KLOCK;
                vput(ap->a_vp);
 
-               error = VOP_REMOVE(dvp, vp, ap->a_cnp);
+               if (union_dowhiteout(un, cnp->cn_cred, cnp->cn_proc))
+                       cnp->cn_flags |= DOWHITEOUT;
+               error = VOP_REMOVE(dvp, vp, cnp);
                if (!error)
                        union_removed_upper(un);
                if (!error)
                        union_removed_upper(un);
-
-               /*
-                * XXX: should create a whiteout here
-                */
        } else {
        } else {
-               /*
-                * XXX: should create a whiteout here
-                */
+               FIXUP(dun);
+               error = union_mkwhiteout(
+                       MOUNTTOUNIONMOUNT(UNIONTOV(dun)->v_mount),
+                       dun->un_uppervp, ap->a_cnp, un->un_path);
                vput(ap->a_dvp);
                vput(ap->a_vp);
                vput(ap->a_dvp);
                vput(ap->a_vp);
-               error = EROFS;
        }
 
        return (error);
        }
 
        return (error);
@@ -868,34 +921,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;
 
 
-               FIXUP(dun);
-               VREF(dvp);
-               dun->un_flags |= UN_KLOCK;
-               vput(ap->a_vp);
-               FIXUP(un);
-               VREF(vp);
-               vrele(ap->a_tdvp);
+       un = VTOUNION(ap->a_tdvp);
 
 
-               error = VOP_LINK(dvp, vp, ap->a_cnp);
+       if (ap->a_tdvp->v_op != ap->a_vp->v_op) {
+               vp = ap->a_vp;
        } 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 *tun = VTOUNION(ap->a_vp);
+               if (tun->un_uppervp == NULLVP) {
+                       VOP_LOCK(ap->a_vp);
+                       if (un->un_uppervp == tun->un_dirvp) {
+                               un->un_flags &= ~UN_ULOCK;
+                               VOP_UNLOCK(un->un_uppervp);
+                       }
+                       error = union_copyup(tun, 1, ap->a_cnp->cn_cred,
+                                               ap->a_cnp->cn_proc);
+                       if (un->un_uppervp == tun->un_dirvp) {
+                               VOP_LOCK(un->un_uppervp);
+                               un->un_flags |= UN_ULOCK;
+                       }
+                       VOP_UNLOCK(ap->a_vp);
+               }
+               vp = tun->un_uppervp;
+       }
+
+       tdvp = un->un_uppervp;
+       if (tdvp == NULLVP)
                error = EROFS;
                error = EROFS;
+
+       if (error) {
+               vput(ap->a_tdvp);
+               return (error);
        }
 
        }
 
-       return (error);
+       FIXUP(un);
+       VREF(tdvp);
+       un->un_flags |= UN_KLOCK;
+       vput(ap->a_tdvp);
+
+       return (VOP_LINK(vp, tdvp, ap->a_cnp));
 }
 
 int
 }
 
 int
@@ -919,11 +987,16 @@ union_rename(ap)
        if (fdvp->v_op == union_vnodeop_p) {    /* always true */
                struct union_node *un = VTOUNION(fdvp);
                if (un->un_uppervp == NULLVP) {
        if (fdvp->v_op == union_vnodeop_p) {    /* always true */
                struct union_node *un = VTOUNION(fdvp);
                if (un->un_uppervp == NULLVP) {
-                       error = EROFS;
+                       /*
+                        * this should never happen in normal
+                        * operation but might if there was
+                        * a problem creating the top-level shadow
+                        * directory.
+                        */
+                       error = EXDEV;
                        goto bad;
                }
 
                        goto bad;
                }
 
-               FIXUP(un);
                fdvp = un->un_uppervp;
                VREF(fdvp);
                vrele(ap->a_fdvp);
                fdvp = un->un_uppervp;
                VREF(fdvp);
                vrele(ap->a_fdvp);
@@ -932,11 +1005,14 @@ union_rename(ap)
        if (fvp->v_op == union_vnodeop_p) {     /* always true */
                struct union_node *un = VTOUNION(fvp);
                if (un->un_uppervp == NULLVP) {
        if (fvp->v_op == union_vnodeop_p) {     /* always true */
                struct union_node *un = VTOUNION(fvp);
                if (un->un_uppervp == NULLVP) {
-                       error = EROFS;
+                       /* XXX: should do a copyup */
+                       error = EXDEV;
                        goto bad;
                }
 
                        goto bad;
                }
 
-               FIXUP(un);
+               if (un->un_lowervp != NULLVP)
+                       ap->a_fcnp->cn_flags |= DOWHITEOUT;
+
                fvp = un->un_uppervp;
                VREF(fvp);
                vrele(ap->a_fvp);
                fvp = un->un_uppervp;
                VREF(fvp);
                vrele(ap->a_fvp);
@@ -945,7 +1021,13 @@ union_rename(ap)
        if (tdvp->v_op == union_vnodeop_p) {
                struct union_node *un = VTOUNION(tdvp);
                if (un->un_uppervp == NULLVP) {
        if (tdvp->v_op == union_vnodeop_p) {
                struct union_node *un = VTOUNION(tdvp);
                if (un->un_uppervp == NULLVP) {
-                       error = EROFS;
+                       /*
+                        * this should never happen in normal
+                        * operation but might if there was
+                        * a problem creating the top-level shadow
+                        * directory.
+                        */
+                       error = EXDEV;
                        goto bad;
                }
 
                        goto bad;
                }
 
@@ -955,16 +1037,14 @@ union_rename(ap)
                vput(ap->a_tdvp);
        }
 
                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 == NULLVP) {
-                       error = EROFS;
-                       goto bad;
-               }
 
                tvp = un->un_uppervp;
 
                tvp = un->un_uppervp;
-               VREF(tvp);
-               un->un_flags |= UN_KLOCK;
+               if (tvp != NULLVP) {
+                       VREF(tvp);
+                       un->un_flags |= UN_KLOCK;
+               }
                vput(ap->a_tvp);
        }
 
                vput(ap->a_tvp);
        }
 
@@ -974,7 +1054,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);
@@ -992,17 +1072,19 @@ 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;
 
                FIXUP(un);
                VREF(dvp);
                un->un_flags |= UN_KLOCK;
                int error;
                struct vnode *vp;
 
                FIXUP(un);
                VREF(dvp);
                un->un_flags |= UN_KLOCK;
-               vput(ap->a_dvp);
+               VOP_UNLOCK(ap->a_dvp);
                error = VOP_MKDIR(dvp, &vp, ap->a_cnp, ap->a_vap);
                error = VOP_MKDIR(dvp, &vp, ap->a_cnp, ap->a_vap);
-               if (error)
+               if (error) {
+                       vrele(ap->a_dvp);
                        return (error);
                        return (error);
+               }
 
                error = union_allocvp(
                                ap->a_vpp,
 
                error = union_allocvp(
                                ap->a_vpp,
@@ -1011,7 +1093,9 @@ union_mkdir(ap)
                                NULLVP,
                                ap->a_cnp,
                                vp,
                                NULLVP,
                                ap->a_cnp,
                                vp,
-                               NULLVP);
+                               NULLVP,
+                               1);
+               vrele(ap->a_dvp);
                if (error)
                        vput(vp);
                return (error);
                if (error)
                        vput(vp);
                return (error);
@@ -1033,9 +1117,13 @@ 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)
+               panic("union rmdir: null upper vnode");
+
+       if (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;
+               struct componentname *cnp = ap->a_cnp;
 
                FIXUP(dun);
                VREF(dvp);
 
                FIXUP(dun);
                VREF(dvp);
@@ -1046,20 +1134,18 @@ union_rmdir(ap)
                un->un_flags |= UN_KLOCK;
                vput(ap->a_vp);
 
                un->un_flags |= UN_KLOCK;
                vput(ap->a_vp);
 
+               if (union_dowhiteout(un, cnp->cn_cred, cnp->cn_proc))
+                       cnp->cn_flags |= DOWHITEOUT;
                error = VOP_RMDIR(dvp, vp, ap->a_cnp);
                if (!error)
                        union_removed_upper(un);
                error = VOP_RMDIR(dvp, vp, ap->a_cnp);
                if (!error)
                        union_removed_upper(un);
-
-               /*
-                * XXX: should create a whiteout here
-                */
        } else {
        } else {
-               /*
-                * XXX: should create a whiteout here
-                */
+               FIXUP(dun);
+               error = union_mkwhiteout(
+                       MOUNTTOUNIONMOUNT(UNIONTOV(dun)->v_mount),
+                       dun->un_uppervp, ap->a_cnp, un->un_path);
                vput(ap->a_dvp);
                vput(ap->a_vp);
                vput(ap->a_dvp);
                vput(ap->a_vp);
-               error = EROFS;
        }
 
        return (error);
        }
 
        return (error);
@@ -1078,7 +1164,7 @@ 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;
@@ -1111,17 +1197,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);
+       register struct union_node *un = VTOUNION(ap->a_vp);
+       register struct vnode *uvp = un->un_uppervp;
 
 
-       if (un->un_uppervp) {
-               FIXUP(un);
-               error = VOP_READDIR(un->un_uppervp, ap->a_uio, ap->a_cred);
-       }
+       if (uvp == NULLVP)
+               return (0);
 
 
-       return (error);
+       FIXUP(un);
+       ap->a_vp = uvp;
+       return (VOCALL(uvp->v_op, VOFFSET(vop_readdir), ap));
 }
 
 int
 }
 
 int
@@ -1179,6 +1268,8 @@ union_inactive(ap)
                struct vnode *a_vp;
        } */ *ap;
 {
                struct vnode *a_vp;
        } */ *ap;
 {
+       struct union_node *un = VTOUNION(ap->a_vp);
+       struct vnode **vpp;
 
        /*
         * Do nothing (and _don't_ bypass).
 
        /*
         * Do nothing (and _don't_ bypass).
@@ -1194,12 +1285,22 @@ union_inactive(ap)
         */
 
 #ifdef UNION_DIAGNOSTIC
         */
 
 #ifdef UNION_DIAGNOSTIC
-       struct union_node *un = VTOUNION(ap->a_vp);
-
        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_dircache != 0) {
+               for (vpp = un->un_dircache; *vpp != NULLVP; vpp++)
+                       vrele(*vpp);
+               free(un->un_dircache, M_TEMP);
+               un->un_dircache = 0;
+       }
+
+       if ((un->un_flags & UN_CACHED) == 0)
+               vgone(ap->a_vp);
+
        return (0);
 }
 
        return (0);
 }
 
@@ -1230,14 +1331,17 @@ start:
 
        un = VTOUNION(vp);
 
 
        un = VTOUNION(vp);
 
-       if (un->un_uppervp) {
-               if ((un->un_flags & UN_ULOCK) == 0) {
-                       un->un_flags |= UN_ULOCK;
+       if (un->un_uppervp != NULLVP) {
+               if (((un->un_flags & UN_ULOCK) == 0) &&
+                   (vp->v_usecount != 0)) {
                        VOP_LOCK(un->un_uppervp);
                        VOP_LOCK(un->un_uppervp);
+                       un->un_flags |= UN_ULOCK;
                }
 #ifdef DIAGNOSTIC
                }
 #ifdef DIAGNOSTIC
-               if (un->un_flags & UN_KLOCK)
-                       panic("union: dangling upper lock");
+               if (un->un_flags & UN_KLOCK) {
+                       vprint("union: dangling klock", vp);
+                       panic("union: dangling upper lock (%lx)", vp);
+               }
 #endif
        }
 
 #endif
        }
 
@@ -1263,6 +1367,17 @@ start:
        return (0);
 }
 
        return (0);
 }
 
+/*
+ * When operations want to vput() a union node yet retain a lock on
+ * the upper vnode (say, to do some further operations like link(),
+ * mkdir(), ...), they set UN_KLOCK on the union node, then call
+ * vput() which calls VOP_UNLOCK() and comes here.  union_unlock()
+ * unlocks the union node (leaving the upper vnode alone), clears the
+ * KLOCK flag, and then returns to vput().  The caller then does whatever
+ * is left to do with the upper vnode, and ensures that it gets unlocked.
+ *
+ * If UN_KLOCK isn't set, then the upper vnode is unlocked here.
+ */
 int
 union_unlock(ap)
        struct vop_lock_args *ap;
 int
 union_unlock(ap)
        struct vop_lock_args *ap;
@@ -1331,6 +1446,11 @@ union_print(ap)
 
        printf("\ttag VT_UNION, vp=%x, uppervp=%x, lowervp=%x\n",
                        vp, UPPERVP(vp), LOWERVP(vp));
 
        printf("\ttag VT_UNION, vp=%x, uppervp=%x, lowervp=%x\n",
                        vp, UPPERVP(vp), LOWERVP(vp));
+       if (UPPERVP(vp) != NULLVP)
+               vprint("union: upper", UPPERVP(vp));
+       if (LOWERVP(vp) != NULLVP)
+               vprint("union: lower", LOWERVP(vp));
+
        return (0);
 }
 
        return (0);
 }
 
@@ -1423,6 +1543,7 @@ struct vnodeopv_entry_desc union_vnodeop_entries[] = {
        { &vop_default_desc, vn_default_error },
        { &vop_lookup_desc, union_lookup },             /* lookup */
        { &vop_create_desc, union_create },             /* create */
        { &vop_default_desc, vn_default_error },
        { &vop_lookup_desc, union_lookup },             /* lookup */
        { &vop_create_desc, union_create },             /* create */
+       { &vop_whiteout_desc, union_whiteout },         /* whiteout */
        { &vop_mknod_desc, union_mknod },               /* mknod */
        { &vop_open_desc, union_open },                 /* open */
        { &vop_close_desc, union_close },               /* close */
        { &vop_mknod_desc, union_mknod },               /* mknod */
        { &vop_open_desc, union_open },                 /* open */
        { &vop_close_desc, union_close },               /* close */
@@ -1431,8 +1552,10 @@ struct vnodeopv_entry_desc union_vnodeop_entries[] = {
        { &vop_setattr_desc, union_setattr },           /* setattr */
        { &vop_read_desc, union_read },                 /* read */
        { &vop_write_desc, union_write },               /* write */
        { &vop_setattr_desc, union_setattr },           /* setattr */
        { &vop_read_desc, union_read },                 /* read */
        { &vop_write_desc, union_write },               /* write */
+       { &vop_lease_desc, union_lease },               /* lease */
        { &vop_ioctl_desc, union_ioctl },               /* ioctl */
        { &vop_select_desc, union_select },             /* select */
        { &vop_ioctl_desc, union_ioctl },               /* ioctl */
        { &vop_select_desc, union_select },             /* select */
+       { &vop_revoke_desc, union_revoke },             /* revoke */
        { &vop_mmap_desc, union_mmap },                 /* mmap */
        { &vop_fsync_desc, union_fsync },               /* fsync */
        { &vop_seek_desc, union_seek },                 /* seek */
        { &vop_mmap_desc, union_mmap },                 /* mmap */
        { &vop_fsync_desc, union_fsync },               /* fsync */
        { &vop_seek_desc, union_seek },                 /* seek */