diff --git a/sys/vfs/autofs/autofs.h b/sys/vfs/autofs/autofs.h index d4458c7..dd6aeb6 100644 --- a/sys/vfs/autofs/autofs.h +++ b/sys/vfs/autofs/autofs.h @@ -113,6 +113,8 @@ struct autofs_node { struct autofs_mount { struct autofs_node *am_root; mtx_t am_lock; + bool am_dying; + int am_triggers; char am_from[MAXPATHLEN]; char am_on[MAXPATHLEN]; char am_options[MAXPATHLEN]; diff --git a/sys/vfs/autofs/autofs_vfsops.c b/sys/vfs/autofs/autofs_vfsops.c index cd00711..f9c6945 100644 --- a/sys/vfs/autofs/autofs_vfsops.c +++ b/sys/vfs/autofs/autofs_vfsops.c @@ -171,6 +171,8 @@ autofs_mount(struct mount *mp, char *mntpt, caddr_t data, struct ucred *cred) * Initialize the autofs mount. */ mtx_init(&->am_lock, "autofsmnlk"); + amp->am_dying = false; + amp->am_triggers = 0; amp->am_last_ino = AUTOFS_ROOTINO; mtx_lock_ex_quick(&->am_lock); @@ -206,6 +208,18 @@ autofs_unmount(struct mount *mp, int mntflags) return (error); } + /* + * DF-3169: Mark the mount dying so new triggers (and new vop + * entries) refuse to start, then fail all outstanding requests. + * NOTE: the old assumption that no new triggerings can appear + * after vflush() is wrong under MNT_FORCE -- force-closed vnodes + * can still have in-flight vops -- hence the am_triggers wait + * below before any node is freed. + */ + mtx_lock_ex_quick(&->am_lock); + amp->am_dying = true; + mtx_unlock_ex(&->am_lock); + /* * All vnodes are gone, and new one will not appear - so, * no new triggerings. @@ -236,6 +250,36 @@ autofs_unmount(struct mount *mp, int mntflags) tsleep(&dummy, 0, "autofs_umount", hz); } + /* + * DF-3169: wait for in-flight trigger paths (nresolve/readdir/ + * nmkdir hold an am_triggers reference across their whole body) + * before freeing anything. Bounded: a trigger stuck inside an + * inner vfs_busy() waiting for this very unmount must not + * deadlock it -- abort with EBUSY and let the caller retry. + */ + { + int waits; + + for (waits = 0; ; waits++) { + int triggers; + + mtx_lock_ex_quick(&->am_lock); + triggers = amp->am_triggers; + mtx_unlock_ex(&->am_lock); + if (triggers == 0) + break; + if (waits > 120) { + AUTOFS_WARN("autofs_unmount: in-flight " + "trigger did not finish, retry later"); + mtx_lock_ex_quick(&->am_lock); + amp->am_dying = false; + mtx_unlock_ex(&->am_lock); + return (EBUSY); + } + tsleep(amp, 0, "autofs_umnt2", hz); + } + } + mtx_lock_ex_quick(&->am_lock); while (!RB_EMPTY(&->am_root->an_children)) { struct autofs_node *anp; diff --git a/sys/vfs/autofs/autofs_vnops.c b/sys/vfs/autofs/autofs_vnops.c index 2a94cf7..7624aef 100644 --- a/sys/vfs/autofs/autofs_vnops.c +++ b/sys/vfs/autofs/autofs_vnops.c @@ -41,6 +41,47 @@ #include "autofs.h" + +/* + * DF-3169: mount-lifetime references for the trigger paths. + * autofs_unmount() marks the mount dying and waits for am_triggers + * to drain before freeing any autofs_node or the autofs_mount, so an + * in-flight trigger (daemon dispatch + retry loop can sleep for many + * seconds) can never run on freed memory. + */ +static bool +autofs_mount_acquire(struct autofs_mount *amp) +{ + bool ok; + + mtx_lock_ex_quick(&->am_lock); + ok = !amp->am_dying; + if (ok) + amp->am_triggers++; + mtx_unlock_ex(&->am_lock); + + return (ok); +} + +static void +autofs_mount_release(struct autofs_mount *amp) +{ + mtx_lock_ex_quick(&->am_lock); + KKASSERT(amp->am_triggers > 0); + if (--amp->am_triggers == 0) + wakeup(amp); + mtx_unlock_ex(&->am_lock); +} + +static __inline struct autofs_mount * +autofs_vnode_mount(struct vnode *vp) +{ + + if (vp->v_flag & VRECLAIMED) + return (NULL); + return (VFSTOAUTOFS(vp->v_mount)); +} + static bool test_fs_root(struct vnode *vp) { @@ -195,11 +236,14 @@ autofs_nresolve(struct vop_nresolve_args *ap) struct vnode *dvp = ap->a_dvp; struct nchandle *nch = ap->a_nch; struct namecache *ncp = nch->ncp; - struct autofs_mount *amp = VFSTOAUTOFS(dvp->v_mount); + struct autofs_mount *amp = autofs_vnode_mount(dvp); struct autofs_node *anp = VTOI(dvp); struct autofs_node *child = NULL; int error; + if (amp == NULL || autofs_mount_acquire(amp) == false) + return (ENXIO); + if (autofs_cached(anp, ncp->nc_name, ncp->nc_nlen) == false && autofs_ignore_thread() == false) { struct vnode *newvp = NULL; @@ -210,13 +254,17 @@ autofs_nresolve(struct vop_nresolve_args *ap) &newvp); cache_lock(nch); cache_drop(nch); - if (error) + if (error) { + autofs_mount_release(amp); return (error); + } if (newvp != NULL) { KKASSERT(newvp->v_tag != VT_AUTOFS); vput(newvp); + autofs_mount_release(amp); return (ESTALE); } + autofs_mount_release(amp); return (0); } @@ -226,6 +274,7 @@ autofs_nresolve(struct vop_nresolve_args *ap) if (error) { cache_setvp(nch, NULL); + autofs_mount_release(amp); return (0); } @@ -235,9 +284,11 @@ autofs_nresolve(struct vop_nresolve_args *ap) vn_unlock(vp); cache_setvp(nch, vp); vrele(vp); + autofs_mount_release(amp); return (0); } + autofs_mount_release(amp); return (error); } @@ -248,17 +299,22 @@ autofs_nmkdir(struct vop_nmkdir_args *ap) struct vnode *dvp = ap->a_dvp; struct nchandle *nch = ap->a_nch; struct namecache *ncp = nch->ncp; - struct autofs_mount *amp = VFSTOAUTOFS(dvp->v_mount); + struct autofs_mount *amp = autofs_vnode_mount(dvp); struct autofs_node *anp = VTOI(dvp); struct autofs_node *child = NULL; int error; + if (amp == NULL || autofs_mount_acquire(amp) == false) + return (ENXIO); + /* * Do not allow mkdir() if the calling thread is not * automountd(8) descendant. */ - if (autofs_ignore_thread() == false) + if (autofs_ignore_thread() == false) { + autofs_mount_release(amp); return (EPERM); + } mtx_lock_ex_quick(&->am_lock); error = autofs_node_new(anp, amp, ncp->nc_name, ncp->nc_nlen, &child); @@ -271,9 +327,11 @@ autofs_nmkdir(struct vop_nmkdir_args *ap) cache_setunresolved(nch); cache_setvp(nch, vp); *(ap->a_vpp) = vp; + autofs_mount_release(amp); return (0); } + autofs_mount_release(amp); return (error); } @@ -298,7 +356,7 @@ static int autofs_readdir(struct vop_readdir_args *ap) { struct vnode *vp = ap->a_vp; - struct autofs_mount *amp = VFSTOAUTOFS(vp->v_mount); + struct autofs_mount *amp = autofs_vnode_mount(vp); struct autofs_node *anp = VTOI(vp); struct autofs_node *child; struct uio *uio = ap->a_uio; @@ -306,26 +364,34 @@ autofs_readdir(struct vop_readdir_args *ap) size_t reclens; int error; + if (amp == NULL || autofs_mount_acquire(amp) == false) + return (ENXIO); + KASSERT(vp->v_type == VDIR, ("!VDIR")); if (autofs_cached(anp, NULL, 0) == false && autofs_ignore_thread() == false) { struct vnode *newvp = NULL; error = autofs_trigger_vn(vp, "", 0, &newvp); - if (error) + if (error) { + autofs_mount_release(amp); return (error); + } if (newvp != NULL) { KKASSERT(newvp->v_tag != VT_AUTOFS); vn_unlock(newvp); error = VOP_READDIR(newvp, ap->a_uio, ap->a_cred, ap->a_eofflag, ap->a_ncookies, ap->a_cookies); vrele(newvp); + autofs_mount_release(amp); return (error); } } - if (uio->uio_offset < 0) + if (uio->uio_offset < 0) { + autofs_mount_release(amp); return (EINVAL); + } if (ap->a_eofflag != NULL) *ap->a_eofflag = FALSE; @@ -347,8 +413,10 @@ autofs_readdir(struct vop_readdir_args *ap) * Write out the directory entry for "..". */ if (uio->uio_offset <= reclens) { - if (uio->uio_offset != reclens) + if (uio->uio_offset != reclens) { + autofs_mount_release(amp); return (EINVAL); + } error = autofs_write_dirent(uio, "..", anp->an_parent ? anp->an_parent->an_ino : anp->an_ino); if (error) { @@ -378,6 +446,7 @@ autofs_readdir(struct vop_readdir_args *ap) */ if (uio->uio_offset != reclens) { mtx_unlock_sh(&->am_lock); + autofs_mount_release(amp); return (EINVAL); } @@ -395,20 +464,26 @@ autofs_readdir(struct vop_readdir_args *ap) if (ap->a_eofflag != NULL) *ap->a_eofflag = TRUE; + autofs_mount_release(amp); return (0); out: /* * Return error if the initial buffer was too small to do anything. */ - if (uio->uio_resid == initial_resid) + if (uio->uio_resid == initial_resid) { + autofs_mount_release(amp); return (error); + } /* * Don't return an error if we managed to copy out some entries. */ - if (uio->uio_resid < initial_resid) + if (uio->uio_resid < initial_resid) { + autofs_mount_release(amp); return (0); + } + autofs_mount_release(amp); return (error); }