Index: sys/fs/unionfs/union.h =================================================================== --- sys/fs/unionfs/union.h +++ sys/fs/unionfs/union.h @@ -134,6 +134,9 @@ #define UNIONFSVPTOUPPERVP(vp) (VTOUNIONFS(vp)->un_uppervp) #endif +struct mount *unionfs_mount_trybusy(struct vnode *); +void unionfs_mount_unbusy(struct mount *); + extern struct vop_vector unionfs_vnodeops; #ifdef MALLOC_DECLARE Index: sys/fs/unionfs/union_subr.c =================================================================== --- sys/fs/unionfs/union_subr.c +++ sys/fs/unionfs/union_subr.c @@ -55,6 +55,8 @@ #include #include +#include + #include #include @@ -1239,7 +1241,7 @@ #ifdef DIAGNOSTIC -struct vnode * +struct vnode * unionfs_checkuppervp(struct vnode *vp, char *fil, int lno) { struct unionfs_node *unp; @@ -1259,7 +1261,7 @@ return (unp->un_uppervp); } -struct vnode * +struct vnode * unionfs_checklowervp(struct vnode *vp, char *fil, int lno) { struct unionfs_node *unp; @@ -1279,3 +1281,31 @@ return (unp->un_lowervp); } #endif + +/* + * Attempt to mark the underlying mount for a vnode busy so that unionfs + * can safely access it or issue VFS_* operations against it. + * Returns NULL on failure. + */ +struct mount * +unionfs_mount_trybusy(struct vnode *vp) +{ + struct mount *mp; + int error; + + mp = atomic_load_ptr(&vp->v_mount); + if (mp == NULL) + return (NULL); + error = vfs_busy(mp, MBF_NOWAIT); + if (error != 0) + return (NULL); + + return (mp); +} + +void +unionfs_mount_unbusy(struct mount *mp) +{ + if (mp != NULL) + vfs_unbusy(mp); +} Index: sys/fs/unionfs/union_vfsops.c =================================================================== --- sys/fs/unionfs/union_vfsops.c +++ sys/fs/unionfs/union_vfsops.c @@ -75,10 +75,11 @@ unionfs_domount(struct mount *mp) { int error; + struct mount *lowermp, *uppermp; struct vnode *lowerrootvp; struct vnode *upperrootvp; struct unionfs_mount *ump; - struct thread *td; + struct thread *td; char *target; char *tmp; char *ep; @@ -285,18 +286,27 @@ error = unionfs_nodeget(mp, ump->um_uppervp, ump->um_lowervp, NULLVP, &(ump->um_rootvp), NULL, td); vrele(upperrootvp); - if (error) { - free(ump, M_UNIONFSMNT); - mp->mnt_data = NULL; - return (error); + if (error != 0) + goto unionfs_domount_cleanup; + + lowermp = unionfs_mount_trybusy(ump->um_lowervp); + uppermp = unionfs_mount_trybusy(ump->um_uppervp); + if (lowermp == NULL || uppermp == NULL) + error = ENOENT; + else { + MNT_ILOCK(mp); + if ((lowermp->mnt_flag & MNT_LOCAL) != 0 && + (uppermp->mnt_flag & MNT_LOCAL) != 0) + mp->mnt_flag |= MNT_LOCAL; + mp->mnt_kern_flag |= MNTK_NOMSYNC | MNTK_UNIONFS; + MNT_IUNLOCK(mp); } - MNT_ILOCK(mp); - if ((ump->um_lowervp->v_mount->mnt_flag & MNT_LOCAL) && - (ump->um_uppervp->v_mount->mnt_flag & MNT_LOCAL)) - mp->mnt_flag |= MNT_LOCAL; - mp->mnt_kern_flag |= MNTK_NOMSYNC | MNTK_UNIONFS; - MNT_IUNLOCK(mp); + unionfs_mount_unbusy(uppermp); + unionfs_mount_unbusy(lowermp); + + if (error != 0) + goto unionfs_domount_cleanup; /* * Get new fsid @@ -310,6 +320,12 @@ mp->mnt_stat.f_mntfromname, mp->mnt_stat.f_mntonname); return (0); + +unionfs_domount_cleanup: + + free(ump, M_UNIONFSMNT); + mp->mnt_data = NULL; + return (error); } /* @@ -371,21 +387,37 @@ } static int -unionfs_quotactl(struct mount *mp, int cmd, uid_t uid, void *arg) +unionfs_quotactl(struct mount *mp, int cmds, uid_t uid, void *arg, + bool *mp_busy) { + struct mount *uppermp; struct unionfs_mount *ump; + int error; + bool unbusy; + + unbusy = true; ump = MOUNTTOUNIONFSMOUNT(mp); + uppermp = unionfs_mount_trybusy(ump->um_uppervp); /* * Writing is always performed to upper vnode. */ - return (VFS_QUOTACTL(ump->um_uppervp->v_mount, cmd, uid, arg)); + if (uppermp == NULL) + error = ENOENT; + else + error = VFS_QUOTACTL(uppermp, cmds, uid, arg, &unbusy); + + if (unbusy) + unionfs_mount_unbusy(uppermp); + + return (error); } static int unionfs_statfs(struct mount *mp, struct statfs *sbp) { + struct mount *lowermp, *uppermp; struct unionfs_mount *ump; int error; struct statfs *mstat; @@ -398,7 +430,13 @@ mstat = malloc(sizeof(struct statfs), M_STATFS, M_WAITOK | M_ZERO); - error = VFS_STATFS(ump->um_lowervp->v_mount, mstat); + lowermp = unionfs_mount_trybusy(ump->um_lowervp); + if (lowermp != NULL) + error = VFS_STATFS(lowermp, mstat); + else + error = ENOENT; + + unionfs_mount_unbusy(lowermp); if (error) { free(mstat, M_STATFS); return (error); @@ -410,7 +448,13 @@ lbsize = mstat->f_bsize; - error = VFS_STATFS(ump->um_uppervp->v_mount, mstat); + uppermp = unionfs_mount_trybusy(ump->um_uppervp); + if (uppermp != NULL) + error = VFS_STATFS(uppermp, mstat); + else + error = ENOENT; + + unionfs_mount_unbusy(uppermp); if (error) { free(mstat, M_STATFS); return (error); @@ -470,19 +514,32 @@ unionfs_extattrctl(struct mount *mp, int cmd, struct vnode *filename_vp, int namespace, const char *attrname) { + struct mount *cmp; struct unionfs_mount *ump; struct unionfs_node *unp; + struct vnode *vp, *mvp; + int error; ump = MOUNTTOUNIONFSMOUNT(mp); unp = VTOUNIONFS(filename_vp); if (unp->un_uppervp != NULLVP) { - return (VFS_EXTATTRCTL(ump->um_uppervp->v_mount, cmd, - unp->un_uppervp, namespace, attrname)); + vp = unp->un_uppervp; + mvp = ump->um_uppervp; } else { - return (VFS_EXTATTRCTL(ump->um_lowervp->v_mount, cmd, - unp->un_lowervp, namespace, attrname)); + vp = unp->un_lowervp; + mvp = ump->um_lowervp; } + cmp = unionfs_mount_trybusy(mvp); + + if (cmp == NULL) + error = ENOENT; + else + error = VFS_EXTATTRCTL(cmp, cmd, vp, namespace, attrname); + + unionfs_mount_unbusy(cmp); + + return (error); } static struct vfsops unionfs_vfsops = { Index: sys/fs/unionfs/union_vnops.c =================================================================== --- sys/fs/unionfs/union_vnops.c +++ sys/fs/unionfs/union_vnops.c @@ -61,6 +61,8 @@ #include +#include + #include #include #include @@ -671,6 +673,7 @@ static int unionfs_access(struct vop_access_args *ap) { + struct mount *uppermp; struct unionfs_mount *ump; struct unionfs_node *unp; struct vnode *uvp; @@ -714,7 +717,15 @@ if (lvp != NULLVP) { if (accmode & VWRITE) { - if (ump->um_uppervp->v_mount->mnt_flag & MNT_RDONLY) { + uppermp = atomic_load_ptr(&ump->um_uppervp->v_mount); + if (uppermp == NULL) + return (ENOENT); + /* + * struct mount* is type-stable, so we should be able + * to safely access uppermp->mnt_flag even if a + * concurrent unmount recycles the object. + */ + if (uppermp->mnt_flag & MNT_RDONLY) { switch (ap->a_vp->v_type) { case VREG: case VDIR: @@ -751,13 +762,14 @@ static int unionfs_getattr(struct vop_getattr_args *ap) { - int error; + struct mount *uppermp; struct unionfs_node *unp; struct unionfs_mount *ump; struct vnode *uvp; struct vnode *lvp; struct thread *td; struct vattr va; + int error; UNIONFS_INTERNAL_DEBUG("unionfs_getattr: enter\n"); @@ -782,7 +794,15 @@ error = VOP_GETATTR(lvp, ap->a_vap, ap->a_cred); - if (error == 0 && !(ump->um_uppervp->v_mount->mnt_flag & MNT_RDONLY)) { + uppermp = atomic_load_ptr(&ump->um_uppervp->v_mount); + if (uppermp == NULL) + error = ENOENT; + /* + * struct mount* is type-stable, so we should be able to safely + * access uppermp->mnt_flag even if a concurrent unmount recycles + * the object. + */ + if (error == 0 && !(uppermp->mnt_flag & MNT_RDONLY)) { /* correct the attr toward shadow file/dir. */ if (ap->a_vp->v_type == VREG || ap->a_vp->v_type == VDIR) { unionfs_create_uppervattr_core(ump, ap->a_vap, &va, td);