diff --git a/sys/kern/vfs_lookup.c b/sys/kern/vfs_lookup.c --- a/sys/kern/vfs_lookup.c +++ b/sys/kern/vfs_lookup.c @@ -1166,20 +1166,17 @@ char *lastchar; /* location of the last character */ struct vnode *dp = NULL; /* the directory we are searching */ struct vnode *tdp; /* saved dp */ - struct mount *mp; /* mount table entry */ struct prison *pr; size_t prev_ni_pathlen; /* saved ndp->ni_pathlen */ int docache; /* == 0 do not cache last component */ int wantparent; /* 1 => wantparent or lockparent flag */ int rdonly; /* lookup read-only flag bit */ int error = 0; - int dpunlocked = 0; /* dp has already been unlocked */ + bool dp_put = false; /* vput() already called on dp */ int relookup = 0; /* do not consume the path component */ struct componentname *cnp = &ndp->ni_cnd; int lkflags_save; int ni_dvp_unlocked; - int crosslkflags; - bool crosslock; /* * Setup: break out flag bits into variables. @@ -1531,70 +1528,49 @@ ni_dvp_unlocked = 1; } goto success; - } else if ((vn_irflag_read(dp) & VIRF_MOUNTPOINT) != 0) { - if ((cnp->cn_flags & NOCROSSMOUNT) != 0) - goto nextname; - } else + } + + /* From here: Not a symlink we have to follow. */ + + if ((cnp->cn_flags & NOCROSSMOUNT) != 0) goto nextname; /* - * Check to see if the vnode has been mounted on; - * if so find the root of the mounted filesystem. + * Cross the mounts, if any. */ - do { - mp = dp->v_mountedhere; - KASSERT(mp != NULL, - ("%s: NULL mountpoint for VIRF_MOUNTPOINT vnode", __func__)); - crosslock = (dp->v_vflag & VV_CROSSLOCK) != 0; - crosslkflags = enforce_lkflags(mp, cnp->cn_lkflags); - if (__predict_false(crosslock)) { - /* - * We are going to be holding the vnode lock, which - * in this case is shared by the root vnode of the - * filesystem mounted at mp, across the call to - * VFS_ROOT(). Make the situation clear to the - * filesystem by passing LK_CANRECURSE if the - * lock is held exclusive, or by clearinng - * LK_NODDLKTREAT to allow recursion on the shared - * lock in the presence of an exclusive waiter. - */ - if (VOP_ISLOCKED(dp) == LK_EXCLUSIVE) { - crosslkflags &= ~LK_SHARED; - crosslkflags |= LK_EXCLUSIVE | LK_CANRECURSE; - } else if ((crosslkflags & LK_EXCLUSIVE) != 0) { - vn_lock(dp, LK_UPGRADE | LK_RETRY); - if (VN_IS_DOOMED(dp)) { - error = ENOENT; - goto bad2; - } - if (dp->v_mountedhere != mp) { - continue; - } - } else - crosslkflags &= ~LK_NODDLKTREAT; - } - if (vfs_busy(mp, 0) != 0) - continue; - if (__predict_true(!crosslock)) - vput(dp); - if (dp != ndp->ni_dvp) + error = vn_cross_mounts(dp, cnp->cn_lkflags, &dp); + if (__predict_false(error != 0)) { + /* + * If 'error' is ENOENT, during crossing, 'dp' was unlocked and + * is now doomed, which, given the pre-existing active + * reference, can only happen on a forced unmount. We could + * climb to the "parent" directory (in the resolution, i.e., + * 'ni_dvp'), but this is likely to be useless (if on the same + * mount it can have been doomed already, and if not, it + * currently is equal to 'vp_crossmp' anyway) and problematic + * semantically if done standalone. The most sensible + * alternative would be to restart the whole resolution, but we + * are not there yet. + */ + dp_put = true; + goto bad2; + } + + if (dp != ndp->ni_vp) { + /* We effectively crossed some mounts. Release 'ni_dvp', which + * is going to be replaced by 'vp_crossmp'. */ + if (ndp->ni_dvp != ndp->ni_vp) vput(ndp->ni_dvp); else vrele(ndp->ni_dvp); + + ndp->ni_vp = dp; + vrefact(vp_crossmp); - ndp->ni_dvp = vp_crossmp; - error = VFS_ROOT(mp, crosslkflags, &tdp); - vfs_unbusy(mp); - if (__predict_false(crosslock)) - vput(dp); if (vn_lock(vp_crossmp, LK_SHARED | LK_NOWAIT)) panic("vp_crossmp exclusively locked or reclaimed"); - if (error != 0) { - dpunlocked = 1; - goto bad2; - } - ndp->ni_vp = dp = tdp; - } while ((vn_irflag_read(dp) & VIRF_MOUNTPOINT) != 0); + ndp->ni_dvp = vp_crossmp; + } nextname: /* @@ -1704,7 +1680,7 @@ vrele(ndp->ni_dvp); } bad: - if (!dpunlocked) + if (!dp_put) vput(dp); bad_unlocked: ndp->ni_vp = NULL;