union_statfs should only return free space information
[unix-history] / usr / src / sys / miscfs / union / union_vfsops.c
index 5b06696..8bb8cb0 100644 (file)
@@ -8,7 +8,7 @@
  *
  * %sccs.include.redist.c%
  *
  *
  * %sccs.include.redist.c%
  *
- *     @(#)union_vfsops.c      8.3 (Berkeley) %G%
+ *     @(#)union_vfsops.c      8.14 (Berkeley) %G%
  */
 
 /*
  */
 
 /*
@@ -43,8 +43,10 @@ union_mount(mp, path, data, ndp, p)
        struct union_args args;
        struct vnode *lowerrootvp = NULLVP;
        struct vnode *upperrootvp = NULLVP;
        struct union_args args;
        struct vnode *lowerrootvp = NULLVP;
        struct vnode *upperrootvp = NULLVP;
-       struct union_mount *um;
+       struct union_mount *um = 0;
        struct ucred *cred = 0;
        struct ucred *cred = 0;
+       struct ucred *scred;
+       struct vattr va;
        char *cp;
        int len;
        u_int size;
        char *cp;
        int len;
        u_int size;
@@ -66,26 +68,6 @@ union_mount(mp, path, data, ndp, p)
                goto bad;
        }
 
                goto bad;
        }
 
-       /*
-        * Take a copy of the process's credentials.  This isn't
-        * quite right since the euid will always be zero and we
-        * want to get the "real" users credentials.  So fix up
-        * the uid field after taking the copy.
-        */
-       cred = crdup(p->p_ucred);
-       cred->cr_uid = p->p_cred->p_ruid;
-
-       /*
-        * Ensure the *real* user has write permission on the
-        * mounted-on directory.  This allows the mount_union
-        * command to be made setuid root so allowing anyone
-        * to do union mounts onto any directory on which they
-        * have write permission.
-        */
-       error = VOP_ACCESS(mp->mnt_vnodecovered, VWRITE, cred, p);
-       if (error)
-               goto bad;
-
        /*
         * Get argument
         */
        /*
         * Get argument
         */
@@ -96,10 +78,11 @@ union_mount(mp, path, data, ndp, p)
        VREF(lowerrootvp);
 
        /*
        VREF(lowerrootvp);
 
        /*
-        * Find upper node
+        * Find upper node.
         */
        NDINIT(ndp, LOOKUP, FOLLOW|WANTPARENT,
               UIO_USERSPACE, args.target, p);
         */
        NDINIT(ndp, LOOKUP, FOLLOW|WANTPARENT,
               UIO_USERSPACE, args.target, p);
+
        if (error = namei(ndp))
                goto bad;
 
        if (error = namei(ndp))
                goto bad;
 
@@ -124,7 +107,8 @@ union_mount(mp, path, data, ndp, p)
         * same as providing a mount under option to the mount syscall.
         */
 
         * same as providing a mount under option to the mount syscall.
         */
 
-       switch (args.mntflags & UNMNT_OPMASK) {
+       um->um_op = args.mntflags & UNMNT_OPMASK;
+       switch (um->um_op) {
        case UNMNT_ABOVE:
                um->um_lowervp = lowerrootvp;
                um->um_uppervp = upperrootvp;
        case UNMNT_ABOVE:
                um->um_lowervp = lowerrootvp;
                um->um_uppervp = upperrootvp;
@@ -147,7 +131,18 @@ union_mount(mp, path, data, ndp, p)
                goto bad;
        }
 
                goto bad;
        }
 
-       um->um_cred = cred;
+       /*
+        * Unless the mount is readonly, ensure that the top layer
+        * supports whiteout operations
+        */
+       if ((mp->mnt_flag & MNT_RDONLY) == 0) {
+               error = VOP_WHITEOUT(um->um_uppervp, (struct componentname *) 0, LOOKUP);
+               if (error)
+                       goto bad;
+       }
+
+       um->um_cred = p->p_ucred;
+       crhold(um->um_cred);
        um->um_cmode = UN_DIRMODE &~ p->p_fd->fd_cmask;
 
        /*
        um->um_cmode = UN_DIRMODE &~ p->p_fd->fd_cmask;
 
        /*
@@ -159,10 +154,12 @@ union_mount(mp, path, data, ndp, p)
         * flag implies that some of the files might be stored locally
         * then you will want to change the conditional.
         */
         * flag implies that some of the files might be stored locally
         * then you will want to change the conditional.
         */
-       if (((um->um_lowervp == NULLVP) ||
-            (um->um_lowervp->v_mount->mnt_flag & MNT_LOCAL)) &&
-           (um->um_uppervp->v_mount->mnt_flag & MNT_LOCAL))
-               mp->mnt_flag |= MNT_LOCAL;
+       if (um->um_op == UNMNT_ABOVE) {
+               if (((um->um_lowervp == NULLVP) ||
+                    (um->um_lowervp->v_mount->mnt_flag & MNT_LOCAL)) &&
+                   (um->um_uppervp->v_mount->mnt_flag & MNT_LOCAL))
+                       mp->mnt_flag |= MNT_LOCAL;
+       }
 
        /*
         * Copy in the upper layer's RDONLY flag.  This is for the benefit
 
        /*
         * Copy in the upper layer's RDONLY flag.  This is for the benefit
@@ -172,21 +169,22 @@ union_mount(mp, path, data, ndp, p)
         * will leave the unioned view as read-only.
         */
        mp->mnt_flag |= (um->um_uppervp->v_mount->mnt_flag & MNT_RDONLY);
         * will leave the unioned view as read-only.
         */
        mp->mnt_flag |= (um->um_uppervp->v_mount->mnt_flag & MNT_RDONLY);
+
        mp->mnt_data = (qaddr_t) um;
        getnewfsid(mp, MOUNT_UNION);
 
        (void) copyinstr(path, mp->mnt_stat.f_mntonname, MNAMELEN - 1, &size);
        bzero(mp->mnt_stat.f_mntonname + size, MNAMELEN - size);
 
        mp->mnt_data = (qaddr_t) um;
        getnewfsid(mp, MOUNT_UNION);
 
        (void) copyinstr(path, mp->mnt_stat.f_mntonname, MNAMELEN - 1, &size);
        bzero(mp->mnt_stat.f_mntonname + size, MNAMELEN - size);
 
-       switch (args.mntflags & UNMNT_OPMASK) {
+       switch (um->um_op) {
        case UNMNT_ABOVE:
        case UNMNT_ABOVE:
-               cp = "un-above:";
+               cp = "<above>:";
                break;
        case UNMNT_BELOW:
                break;
        case UNMNT_BELOW:
-               cp = "un-below:";
+               cp = "<below>:";
                break;
        case UNMNT_REPLACE:
                break;
        case UNMNT_REPLACE:
-               cp = "replace:";
+               cp = "";
                break;
        }
        len = strlen(cp);
                break;
        }
        len = strlen(cp);
@@ -205,6 +203,8 @@ union_mount(mp, path, data, ndp, p)
        return (0);
 
 bad:
        return (0);
 
 bad:
+       if (um)
+               free(um, M_UFSMNT);
        if (cred)
                crfree(cred);
        if (upperrootvp)
        if (cred)
                crfree(cred);
        if (upperrootvp)
@@ -241,6 +241,7 @@ union_unmount(mp, mntflags, p)
        struct union_mount *um = MOUNTTOUNIONMOUNT(mp);
        struct vnode *um_rootvp;
        int error;
        struct union_mount *um = MOUNTTOUNIONMOUNT(mp);
        struct vnode *um_rootvp;
        int error;
+       int freeing;
        int flags = 0;
        extern int doforce;
 
        int flags = 0;
        extern int doforce;
 
@@ -257,17 +258,42 @@ union_unmount(mp, mntflags, p)
 
        if (error = union_root(mp, &um_rootvp))
                return (error);
 
        if (error = union_root(mp, &um_rootvp))
                return (error);
+
+       /*
+        * Keep flushing vnodes from the mount list.
+        * This is needed because of the un_pvp held
+        * reference to the parent vnode.
+        * If more vnodes have been freed on a given pass,
+        * the try again.  The loop will iterate at most
+        * (d) times, where (d) is the maximum tree depth
+        * in the filesystem.
+        */
+       for (freeing = 0; vflush(mp, um_rootvp, flags) != 0;) {
+               struct vnode *vp;
+               int n;
+
+               /* count #vnodes held on mount list */
+               for (n = 0, vp = mp->mnt_vnodelist.lh_first;
+                               vp != NULLVP;
+                               vp = vp->v_mntvnodes.le_next)
+                       n++;
+
+               /* if this is unchanged then stop */
+               if (n == freeing)
+                       break;
+
+               /* otherwise try once more time */
+               freeing = n;
+       }
+
+       /* At this point the root vnode should have a single reference */
        if (um_rootvp->v_usecount > 1) {
                vput(um_rootvp);
                return (EBUSY);
        }
        if (um_rootvp->v_usecount > 1) {
                vput(um_rootvp);
                return (EBUSY);
        }
-       if (error = vflush(mp, um_rootvp, flags)) {
-               vput(um_rootvp);
-               return (error);
-       }
 
 #ifdef UNION_DIAGNOSTIC
 
 #ifdef UNION_DIAGNOSTIC
-       vprint("alias root of lower", um_rootvp);
+       vprint("union root", um_rootvp);
 #endif  
        /*
         * Discard references to upper and lower target vnodes.
 #endif  
        /*
         * Discard references to upper and lower target vnodes.
@@ -299,18 +325,19 @@ union_root(mp, vpp)
 {
        struct union_mount *um = MOUNTTOUNIONMOUNT(mp);
        int error;
 {
        struct union_mount *um = MOUNTTOUNIONMOUNT(mp);
        int error;
-
-#ifdef UNION_DIAGNOSTIC
-       printf("union_root(mp = %x, lvp = %x, uvp = %x)\n", mp,
-                       um->um_lowervp,
-                       um->um_uppervp);
-#endif
+       int loselock;
 
        /*
         * Return locked reference to root.
         */
        VREF(um->um_uppervp);
 
        /*
         * Return locked reference to root.
         */
        VREF(um->um_uppervp);
-       VOP_LOCK(um->um_uppervp);
+       if ((um->um_op == UNMNT_BELOW) &&
+            VOP_ISLOCKED(um->um_uppervp)) {
+               loselock = 1;
+       } else {
+               VOP_LOCK(um->um_uppervp);
+               loselock = 0;
+       }
        if (um->um_lowervp)
                VREF(um->um_lowervp);
        error = union_allocvp(vpp, mp,
        if (um->um_lowervp)
                VREF(um->um_lowervp);
        error = union_allocvp(vpp, mp,
@@ -318,14 +345,18 @@ union_root(mp, vpp)
                              (struct vnode *) 0,
                              (struct componentname *) 0,
                              um->um_uppervp,
                              (struct vnode *) 0,
                              (struct componentname *) 0,
                              um->um_uppervp,
-                             um->um_lowervp);
+                             um->um_lowervp,
+                             1);
 
        if (error) {
 
        if (error) {
+               if (!loselock)
+                       VOP_UNLOCK(um->um_uppervp);
                vrele(um->um_uppervp);
                if (um->um_lowervp)
                        vrele(um->um_lowervp);
        } else {
                vrele(um->um_uppervp);
                if (um->um_lowervp)
                        vrele(um->um_lowervp);
        } else {
-               (*vpp)->v_flag |= VROOT;
+               if (loselock)
+                       VTOUNION(*vpp)->un_flags &= ~UN_ULOCK;
        }
 
        return (error);
        }
 
        return (error);
@@ -397,16 +428,20 @@ union_statfs(mp, sbp, p)
         * kind of sense.  none of this makes sense though.
         */
 
         * kind of sense.  none of this makes sense though.
         */
 
-       if (mstat.f_bsize != lbsize) {
+       if (mstat.f_bsize != lbsize)
                sbp->f_blocks = sbp->f_blocks * lbsize / mstat.f_bsize;
                sbp->f_blocks = sbp->f_blocks * lbsize / mstat.f_bsize;
-               sbp->f_bfree = sbp->f_bfree * lbsize / mstat.f_bsize;
-               sbp->f_bavail = sbp->f_bavail * lbsize / mstat.f_bsize;
-       }
+
+       /*
+        * The "total" fields count total resources in all layers,
+        * the "free" fields count only those resources which are
+        * free in the upper layer (since only the upper layer
+        * is writeable).
+        */
        sbp->f_blocks += mstat.f_blocks;
        sbp->f_blocks += mstat.f_blocks;
-       sbp->f_bfree += mstat.f_bfree;
-       sbp->f_bavail += mstat.f_bavail;
+       sbp->f_bfree = mstat.f_bfree;
+       sbp->f_bavail = mstat.f_bavail;
        sbp->f_files += mstat.f_files;
        sbp->f_files += mstat.f_files;
-       sbp->f_ffree += mstat.f_ffree;
+       sbp->f_ffree = mstat.f_ffree;
 
        if (sbp != &mp->mnt_stat) {
                bcopy(&mp->mnt_stat.f_fsid, &sbp->f_fsid, sizeof(sbp->f_fsid));
 
        if (sbp != &mp->mnt_stat) {
                bcopy(&mp->mnt_stat.f_fsid, &sbp->f_fsid, sizeof(sbp->f_fsid));