version 1.212, 2019/07/18 09:39:40 |
version 1.212.4.9, 2020/01/25 15:54:03 |
Line 50 __KERNEL_RCSID(0, "$NetBSD$"); |
|
Line 50 __KERNEL_RCSID(0, "$NetBSD$"); |
|
#include <sys/time.h> |
#include <sys/time.h> |
#include <sys/namei.h> |
#include <sys/namei.h> |
#include <sys/vnode.h> |
#include <sys/vnode.h> |
|
#include <sys/vnode_impl.h> |
#include <sys/mount.h> |
#include <sys/mount.h> |
#include <sys/errno.h> |
#include <sys/errno.h> |
#include <sys/filedesc.h> |
#include <sys/filedesc.h> |
Line 535 namei_getstartdir(struct namei_state *st |
|
Line 536 namei_getstartdir(struct namei_state *st |
|
struct nameidata *ndp = state->ndp; |
struct nameidata *ndp = state->ndp; |
struct componentname *cnp = state->cnp; |
struct componentname *cnp = state->cnp; |
struct cwdinfo *cwdi; /* pointer to cwd state */ |
struct cwdinfo *cwdi; /* pointer to cwd state */ |
struct lwp *self = curlwp; /* thread doing namei() */ |
|
struct vnode *rootdir, *erootdir, *curdir, *startdir; |
struct vnode *rootdir, *erootdir, *curdir, *startdir; |
|
|
if (state->root_referenced) { |
if (state->root_referenced) { |
Line 546 namei_getstartdir(struct namei_state *st |
|
Line 546 namei_getstartdir(struct namei_state *st |
|
state->root_referenced = 0; |
state->root_referenced = 0; |
} |
} |
|
|
cwdi = self->l_proc->p_cwdi; |
/* NB: we must not block while holding the cwdi read locked. */ |
rw_enter(&cwdi->cwdi_lock, RW_READER); |
cwdi = cwdenter(RW_READER); |
|
|
/* root dir */ |
/* root dir */ |
if (cwdi->cwdi_rdir == NULL || (cnp->cn_flags & NOCHROOT)) { |
if (cwdi->cwdi_rdir == NULL || (cnp->cn_flags & NOCHROOT)) { |
Line 605 namei_getstartdir(struct namei_state *st |
|
Line 605 namei_getstartdir(struct namei_state *st |
|
vref(state->ndp->ni_erootdir); |
vref(state->ndp->ni_erootdir); |
state->root_referenced = 1; |
state->root_referenced = 1; |
|
|
rw_exit(&cwdi->cwdi_lock); |
cwdexit(cwdi); |
return startdir; |
return startdir; |
} |
} |
|
|
Line 710 namei_start(struct namei_state *state, i |
|
Line 710 namei_start(struct namei_state *state, i |
|
return ENOTDIR; |
return ENOTDIR; |
} |
} |
|
|
vn_lock(startdir, LK_EXCLUSIVE | LK_RETRY); |
|
|
|
*startdir_ret = startdir; |
*startdir_ret = startdir; |
return 0; |
return 0; |
} |
} |
Line 749 namei_follow(struct namei_state *state, |
|
Line 747 namei_follow(struct namei_state *state, |
|
size_t linklen; |
size_t linklen; |
int error; |
int error; |
|
|
KASSERT(VOP_ISLOCKED(searchdir) == LK_EXCLUSIVE); |
|
KASSERT(VOP_ISLOCKED(foundobj) == LK_EXCLUSIVE); |
|
if (ndp->ni_loopcnt++ >= MAXSYMLINKS) { |
if (ndp->ni_loopcnt++ >= MAXSYMLINKS) { |
return ELOOP; |
return ELOOP; |
} |
} |
|
|
|
vn_lock(foundobj, LK_EXCLUSIVE | LK_RETRY); |
if (foundobj->v_mount->mnt_flag & MNT_SYMPERM) { |
if (foundobj->v_mount->mnt_flag & MNT_SYMPERM) { |
error = VOP_ACCESS(foundobj, VEXEC, cnp->cn_cred); |
error = VOP_ACCESS(foundobj, VEXEC, cnp->cn_cred); |
if (error != 0) |
if (error != 0) { |
|
VOP_UNLOCK(foundobj); |
return error; |
return error; |
|
} |
} |
} |
|
|
/* FUTURE: fix this to not use a second buffer */ |
/* FUTURE: fix this to not use a second buffer */ |
Line 771 namei_follow(struct namei_state *state, |
|
Line 771 namei_follow(struct namei_state *state, |
|
auio.uio_resid = MAXPATHLEN; |
auio.uio_resid = MAXPATHLEN; |
UIO_SETUP_SYSSPACE(&auio); |
UIO_SETUP_SYSSPACE(&auio); |
error = VOP_READLINK(foundobj, &auio, cnp->cn_cred); |
error = VOP_READLINK(foundobj, &auio, cnp->cn_cred); |
|
VOP_UNLOCK(foundobj); |
if (error) { |
if (error) { |
PNBUF_PUT(cp); |
PNBUF_PUT(cp); |
return error; |
return error; |
Line 807 namei_follow(struct namei_state *state, |
|
Line 808 namei_follow(struct namei_state *state, |
|
/* we're now starting from the beginning of the buffer again */ |
/* we're now starting from the beginning of the buffer again */ |
cnp->cn_nameptr = ndp->ni_pnbuf; |
cnp->cn_nameptr = ndp->ni_pnbuf; |
|
|
/* must unlock this before relocking searchdir */ |
|
VOP_UNLOCK(foundobj); |
|
|
|
/* |
/* |
* Check if root directory should replace current directory. |
* Check if root directory should replace current directory. |
*/ |
*/ |
if (ndp->ni_pnbuf[0] == '/') { |
if (ndp->ni_pnbuf[0] == '/') { |
vput(searchdir); |
vrele(searchdir); |
/* Keep absolute symbolic links inside emulation root */ |
/* Keep absolute symbolic links inside emulation root */ |
searchdir = ndp->ni_erootdir; |
searchdir = ndp->ni_erootdir; |
if (searchdir == NULL || |
if (searchdir == NULL || |
Line 825 namei_follow(struct namei_state *state, |
|
Line 823 namei_follow(struct namei_state *state, |
|
searchdir = ndp->ni_rootdir; |
searchdir = ndp->ni_rootdir; |
} |
} |
vref(searchdir); |
vref(searchdir); |
vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY); |
|
while (cnp->cn_nameptr[0] == '/') { |
while (cnp->cn_nameptr[0] == '/') { |
cnp->cn_nameptr++; |
cnp->cn_nameptr++; |
ndp->ni_pathlen--; |
ndp->ni_pathlen--; |
Line 833 namei_follow(struct namei_state *state, |
|
Line 830 namei_follow(struct namei_state *state, |
|
} |
} |
|
|
*newsearchdir_ret = searchdir; |
*newsearchdir_ret = searchdir; |
KASSERT(VOP_ISLOCKED(searchdir) == LK_EXCLUSIVE); |
|
return 0; |
return 0; |
} |
} |
|
|
Line 861 lookup_parsepath(struct namei_state *sta |
|
Line 857 lookup_parsepath(struct namei_state *sta |
|
* responsibility for freeing the pathname buffer. |
* responsibility for freeing the pathname buffer. |
* |
* |
* At this point, our only vnode state is that the search dir |
* At this point, our only vnode state is that the search dir |
* is held and locked. |
* is held. |
*/ |
*/ |
cnp->cn_consume = 0; |
cnp->cn_consume = 0; |
cnp->cn_namelen = namei_getcomponent(cnp->cn_nameptr); |
cnp->cn_namelen = namei_getcomponent(cnp->cn_nameptr); |
Line 918 lookup_parsepath(struct namei_state *sta |
|
Line 914 lookup_parsepath(struct namei_state *sta |
|
} |
} |
|
|
/* |
/* |
|
* Take care of crossing a mounted-on vnode. On error, foundobj_ret will be |
|
* vrele'd, but searchdir is left alone. |
|
*/ |
|
static int |
|
lookup_crossmount(struct namei_state *state, |
|
struct vnode **searchdir_ret, |
|
struct vnode **foundobj_ret, |
|
bool *searchdir_locked) |
|
{ |
|
struct componentname *cnp = state->cnp; |
|
struct vnode *foundobj; |
|
struct vnode *searchdir; |
|
struct mount *mp; |
|
int error, lktype; |
|
|
|
searchdir = *searchdir_ret; |
|
foundobj = *foundobj_ret; |
|
error = 0; |
|
|
|
KASSERT((cnp->cn_flags & NOCROSSMOUNT) == 0); |
|
KASSERT(searchdir != NULL); |
|
|
|
/* First, unlock searchdir (oof). */ |
|
if (*searchdir_locked) { |
|
lktype = VOP_ISLOCKED(searchdir); |
|
VOP_UNLOCK(searchdir); |
|
*searchdir_locked = false; |
|
} else { |
|
lktype = LK_NONE; |
|
} |
|
|
|
/* |
|
* Do an unlocked check to see if the vnode has been mounted on; if |
|
* so find the root of the mounted file system. |
|
*/ |
|
while (foundobj->v_type == VDIR && |
|
(mp = foundobj->v_mountedhere) != NULL && |
|
(cnp->cn_flags & NOCROSSMOUNT) == 0) { |
|
KASSERTMSG(searchdir != foundobj, "same vn %p", searchdir); |
|
/* |
|
* First get the vnode stable. LK_SHARED works brilliantly |
|
* here because almost nothing else wants to lock the |
|
* covered vnode. |
|
*/ |
|
error = vn_lock(foundobj, LK_SHARED); |
|
if (error != 0) { |
|
vrele(foundobj); |
|
*foundobj_ret = NULL; |
|
break; |
|
} |
|
|
|
/* Then check to see if something is still mounted on it. */ |
|
if ((mp = foundobj->v_mountedhere) == NULL) { |
|
VOP_UNLOCK(foundobj); |
|
break; |
|
} |
|
|
|
/* Get a reference to the mountpoint, and ditch foundobj. */ |
|
error = vfs_busy(mp); |
|
vput(foundobj); |
|
if (error != 0) { |
|
*foundobj_ret = NULL; |
|
break; |
|
} |
|
|
|
/* Now get a reference on the root vnode, and drop mount. */ |
|
error = VFS_ROOT(mp, LK_NONE, &foundobj); |
|
vfs_unbusy(mp); |
|
if (error) { |
|
*foundobj_ret = NULL; |
|
break; |
|
} |
|
|
|
/* |
|
* Avoid locking vnodes from two filesystems because |
|
* it's prone to deadlock, e.g. when using puffs. |
|
* Also, it isn't a good idea to propagate slowness of |
|
* a filesystem up to the root directory. For now, |
|
* only handle the common case, where foundobj is |
|
* VDIR. |
|
* |
|
* In this case set searchdir to null to avoid using |
|
* it again. It is not correct to set searchdir == |
|
* foundobj here as that will confuse the caller. |
|
* (See PR 40740.) |
|
*/ |
|
if (searchdir == NULL) { |
|
/* already been here once; do nothing further */ |
|
} else if (foundobj->v_type == VDIR) { |
|
vrele(searchdir); |
|
*searchdir_ret = searchdir = NULL; |
|
*foundobj_ret = foundobj; |
|
lktype = LK_NONE; |
|
} |
|
} |
|
|
|
/* If searchdir is still around, re-lock it. */ |
|
if (error == 0 && lktype != LK_NONE) { |
|
vn_lock(searchdir, lktype | LK_RETRY); |
|
*searchdir_locked = true; |
|
} |
|
return error; |
|
} |
|
|
|
/* |
* Call VOP_LOOKUP for a single lookup; return a new search directory |
* Call VOP_LOOKUP for a single lookup; return a new search directory |
* (used when crossing mountpoints up or searching union mounts down) and |
* (used when crossing mountpoints up or searching union mounts down) and |
* the found object, which for create operations may be NULL on success. |
* the found object, which for create operations may be NULL on success. |
|
|
lookup_once(struct namei_state *state, |
lookup_once(struct namei_state *state, |
struct vnode *searchdir, |
struct vnode *searchdir, |
struct vnode **newsearchdir_ret, |
struct vnode **newsearchdir_ret, |
struct vnode **foundobj_ret) |
struct vnode **foundobj_ret, |
|
bool *newsearchdir_locked_ret) |
{ |
{ |
struct vnode *tmpvn; /* scratch vnode */ |
struct vnode *tmpvn; /* scratch vnode */ |
struct vnode *foundobj; /* result */ |
struct vnode *foundobj; /* result */ |
struct mount *mp; /* mount table entry */ |
|
struct lwp *l = curlwp; |
struct lwp *l = curlwp; |
int error; |
bool searchdir_locked = false; |
|
int error, lktype; |
|
|
struct componentname *cnp = state->cnp; |
struct componentname *cnp = state->cnp; |
struct nameidata *ndp = state->ndp; |
struct nameidata *ndp = state->ndp; |
|
|
KASSERT(cnp == &ndp->ni_cnd); |
KASSERT(cnp == &ndp->ni_cnd); |
KASSERT(VOP_ISLOCKED(searchdir) == LK_EXCLUSIVE); |
|
*newsearchdir_ret = searchdir; |
*newsearchdir_ret = searchdir; |
|
|
/* |
/* |
Line 977 lookup_once(struct namei_state *state, |
|
Line 1078 lookup_once(struct namei_state *state, |
|
if (ndp->ni_rootdir != rootvnode) { |
if (ndp->ni_rootdir != rootvnode) { |
int retval; |
int retval; |
|
|
VOP_UNLOCK(searchdir); |
|
retval = vn_isunder(searchdir, ndp->ni_rootdir, l); |
retval = vn_isunder(searchdir, ndp->ni_rootdir, l); |
vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY); |
|
if (!retval) { |
if (!retval) { |
/* Oops! We got out of jail! */ |
/* Oops! We got out of jail! */ |
log(LOG_WARNING, |
log(LOG_WARNING, |
Line 988 lookup_once(struct namei_state *state, |
|
Line 1087 lookup_once(struct namei_state *state, |
|
p->p_pid, kauth_cred_geteuid(l->l_cred), |
p->p_pid, kauth_cred_geteuid(l->l_cred), |
p->p_comm); |
p->p_comm); |
/* Put us at the jail root. */ |
/* Put us at the jail root. */ |
vput(searchdir); |
vrele(searchdir); |
searchdir = NULL; |
searchdir = NULL; |
foundobj = ndp->ni_rootdir; |
foundobj = ndp->ni_rootdir; |
vref(foundobj); |
vref(foundobj); |
vref(foundobj); |
vref(foundobj); |
vn_lock(foundobj, LK_EXCLUSIVE | LK_RETRY); |
|
*newsearchdir_ret = foundobj; |
*newsearchdir_ret = foundobj; |
*foundobj_ret = foundobj; |
*foundobj_ret = foundobj; |
error = 0; |
error = 0; |
Line 1006 lookup_once(struct namei_state *state, |
|
Line 1104 lookup_once(struct namei_state *state, |
|
tmpvn = searchdir; |
tmpvn = searchdir; |
searchdir = searchdir->v_mount->mnt_vnodecovered; |
searchdir = searchdir->v_mount->mnt_vnodecovered; |
vref(searchdir); |
vref(searchdir); |
vput(tmpvn); |
vrele(tmpvn); |
vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY); |
|
*newsearchdir_ret = searchdir; |
*newsearchdir_ret = searchdir; |
} |
} |
} |
} |
|
|
/* |
/* |
|
* If the file system supports VOP_LOOKUP() with a shared lock, and |
|
* we are not making any modifications (nameiop LOOKUP) or this is |
|
* not the last component then get a shared lock. Where we can't do |
|
* fast-forwarded lookups (for example with layered file systems) |
|
* then this is the fallback for reducing lock contention. |
|
*/ |
|
if ((searchdir->v_mount->mnt_iflag & IMNT_SHRLOOKUP) != 0 && |
|
(cnp->cn_nameiop == LOOKUP || (cnp->cn_flags & ISLASTCN) == 0)) { |
|
lktype = LK_SHARED; |
|
} else { |
|
lktype = LK_EXCLUSIVE; |
|
} |
|
|
|
/* |
* We now have a segment name to search for, and a directory to search. |
* We now have a segment name to search for, and a directory to search. |
* Our vnode state here is that "searchdir" is held and locked. |
* Our vnode state here is that "searchdir" is held. |
*/ |
*/ |
unionlookup: |
unionlookup: |
foundobj = NULL; |
foundobj = NULL; |
|
if (!searchdir_locked) { |
|
vn_lock(searchdir, lktype | LK_RETRY); |
|
searchdir_locked = true; |
|
} |
error = VOP_LOOKUP(searchdir, &foundobj, cnp); |
error = VOP_LOOKUP(searchdir, &foundobj, cnp); |
|
|
if (error != 0) { |
if (error != 0) { |
|
|
#ifdef NAMEI_DIAGNOSTIC |
#ifdef NAMEI_DIAGNOSTIC |
printf("not found\n"); |
printf("not found\n"); |
#endif /* NAMEI_DIAGNOSTIC */ |
#endif /* NAMEI_DIAGNOSTIC */ |
|
|
|
/* |
|
* If ENOLCK, the file system needs us to retry the lookup |
|
* with an exclusive lock. It's likely nothing was found in |
|
* cache and/or modifications need to be made. |
|
*/ |
|
if (error == ENOLCK) { |
|
KASSERT(VOP_ISLOCKED(searchdir) == LK_SHARED); |
|
KASSERT(searchdir_locked); |
|
if (vn_lock(searchdir, LK_UPGRADE | LK_NOWAIT)) { |
|
VOP_UNLOCK(searchdir); |
|
searchdir_locked = false; |
|
} |
|
lktype = LK_EXCLUSIVE; |
|
goto unionlookup; |
|
} |
|
|
if ((error == ENOENT) && |
if ((error == ENOENT) && |
(searchdir->v_vflag & VV_ROOT) && |
(searchdir->v_vflag & VV_ROOT) && |
(searchdir->v_mount->mnt_flag & MNT_UNION)) { |
(searchdir->v_mount->mnt_flag & MNT_UNION)) { |
|
|
searchdir = searchdir->v_mount->mnt_vnodecovered; |
searchdir = searchdir->v_mount->mnt_vnodecovered; |
vref(searchdir); |
vref(searchdir); |
vput(tmpvn); |
vput(tmpvn); |
vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY); |
searchdir_locked = false; |
*newsearchdir_ret = searchdir; |
*newsearchdir_ret = searchdir; |
goto unionlookup; |
goto unionlookup; |
} |
} |
|
|
cnp->cn_flags |= ISLASTCN; |
cnp->cn_flags |= ISLASTCN; |
} |
} |
|
|
/* |
/* Unlock, unless the caller needs the parent locked. */ |
* "searchdir" is locked and held, "foundobj" is held, |
if (searchdir != NULL) { |
* they may be the same vnode. |
KASSERT(searchdir_locked); |
*/ |
if ((cnp->cn_flags & (ISLASTCN | LOCKPARENT)) != |
if (searchdir != foundobj) { |
(ISLASTCN | LOCKPARENT)) { |
if (cnp->cn_flags & ISDOTDOT) |
VOP_UNLOCK(searchdir); |
VOP_UNLOCK(searchdir); |
searchdir_locked = false; |
error = vn_lock(foundobj, LK_EXCLUSIVE); |
|
if (cnp->cn_flags & ISDOTDOT) |
|
vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY); |
|
if (error != 0) { |
|
vrele(foundobj); |
|
goto done; |
|
} |
} |
|
} else { |
|
KASSERT(!searchdir_locked); |
} |
} |
|
|
/* |
*foundobj_ret = foundobj; |
* Check to see if the vnode has been mounted on; |
error = 0; |
* if so find the root of the mounted file system. |
done: |
*/ |
*newsearchdir_locked_ret = searchdir_locked; |
KASSERT(searchdir != NULL); |
return error; |
while (foundobj->v_type == VDIR && |
} |
(mp = foundobj->v_mountedhere) != NULL && |
|
(cnp->cn_flags & NOCROSSMOUNT) == 0) { |
|
|
|
KASSERT(searchdir != foundobj); |
/* |
|
* Parse out the first path name component that we need to to consider. |
|
* |
|
* While doing this, attempt to use the name cache to fast-forward through |
|
* as many "easy" to find components of the path as possible. |
|
* |
|
* We use the namecache's node locks to form a chain, and avoid as many |
|
* vnode references and locks as possible. In the ideal case, only the |
|
* final vnode will have its reference count adjusted and lock taken. |
|
*/ |
|
static int |
|
lookup_fastforward(struct namei_state *state, struct vnode **searchdir_ret, |
|
struct vnode **foundobj_ret) |
|
{ |
|
struct componentname *cnp = state->cnp; |
|
struct nameidata *ndp = state->ndp; |
|
krwlock_t *plock; |
|
struct vnode *foundobj, *searchdir; |
|
int error, error2; |
|
size_t oldpathlen; |
|
const char *oldnameptr; |
|
|
error = vfs_busy(mp); |
/* |
if (error != 0) { |
* Eat as many path name components as possible before giving up and |
vput(foundobj); |
* letting lookup_once() handle it. Remember the starting point in |
goto done; |
* case we can't get vnode references and need to roll back. |
|
*/ |
|
plock = NULL; |
|
searchdir = *searchdir_ret; |
|
oldnameptr = cnp->cn_nameptr; |
|
oldpathlen = ndp->ni_pathlen; |
|
for (;;) { |
|
foundobj = NULL; |
|
|
|
/* |
|
* Get the next component name. There should be no slashes |
|
* here, and we shouldn't have looped around if we were |
|
* done. |
|
*/ |
|
KASSERT(cnp->cn_nameptr[0] != '/'); |
|
KASSERT(cnp->cn_nameptr[0] != '\0'); |
|
if ((error = lookup_parsepath(state)) != 0) { |
|
break; |
} |
} |
if (searchdir != NULL) { |
|
VOP_UNLOCK(searchdir); |
/* |
|
* Can't deal with dotdot lookups, because it means lock |
|
* order reversal, and there are checks in lookup_once() |
|
* that need to be made. Also check for missing mountpoints. |
|
*/ |
|
if ((cnp->cn_flags & ISDOTDOT) != 0 || |
|
searchdir->v_mount == NULL) { |
|
error = EOPNOTSUPP; |
|
break; |
} |
} |
vput(foundobj); |
|
error = VFS_ROOT(mp, &foundobj); |
/* |
vfs_unbusy(mp); |
* Can't deal with last component when modifying; this needs |
if (error) { |
* searchdir locked and VOP_LOOKUP() called (which can and |
if (searchdir != NULL) { |
* does modify state, despite the name). |
vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY); |
*/ |
|
if ((cnp->cn_flags & ISLASTCN) != 0) { |
|
if (cnp->cn_nameiop != LOOKUP || |
|
(cnp->cn_flags & LOCKPARENT) != 0) { |
|
error = EOPNOTSUPP; |
|
break; |
} |
} |
goto done; |
|
} |
} |
|
|
/* |
/* |
* Avoid locking vnodes from two filesystems because |
* Good, now look for it in cache. cache_lookup_linked() |
* it's prone to deadlock, e.g. when using puffs. |
* will fail if there's nothing there, or if there's no |
* Also, it isn't a good idea to propagate slowness of |
* ownership info for the directory, or if the user doesn't |
* a filesystem up to the root directory. For now, |
* have permission to look up files in this directory. |
* only handle the common case, where foundobj is |
*/ |
* VDIR. |
if (!cache_lookup_linked(searchdir, cnp->cn_nameptr, |
|
cnp->cn_namelen, &foundobj, &plock, cnp->cn_cred)) { |
|
error = EOPNOTSUPP; |
|
break; |
|
} |
|
KASSERT(plock != NULL && rw_lock_held(plock)); |
|
|
|
/* Scored a hit. Negative is good too (ENOENT). */ |
|
if (foundobj == NULL) { |
|
/* XXXAD need to handle -o union mount. */ |
|
error = ENOENT; |
|
break; |
|
} |
|
|
|
/* |
|
* Stop and get a hold on the vnode if there's something |
|
* that can't be handled here: |
* |
* |
* In this case set searchdir to null to avoid using |
* - we've reached the last component. |
* it again. It is not correct to set searchdir == |
* - or encountered a mount point that needs to be crossed. |
* foundobj here as that will confuse the caller. |
* - or encountered something other than a directory. |
* (See PR 40740.) |
|
*/ |
*/ |
if (searchdir == NULL) { |
if ((cnp->cn_flags & ISLASTCN) != 0 || |
/* already been here once; do nothing further */ |
foundobj->v_type != VDIR || |
} else if (foundobj->v_type == VDIR) { |
(foundobj->v_type == VDIR && |
vrele(searchdir); |
foundobj->v_mountedhere != NULL)) { |
*newsearchdir_ret = searchdir = NULL; |
mutex_enter(foundobj->v_interlock); |
|
error = vcache_tryvget(foundobj); |
|
/* v_interlock now released */ |
|
if (error != 0) { |
|
foundobj = NULL; |
|
} |
|
break; |
|
} |
|
|
|
/* |
|
* Otherwise, we're still in business. Set the found VDIR |
|
* vnode as the search dir for the next component and |
|
* continue on to it. |
|
*/ |
|
cnp->cn_nameptr = ndp->ni_next; |
|
searchdir = foundobj; |
|
} |
|
|
|
/* |
|
* If we ended up with a new search dir, ref it before dropping the |
|
* namecache's lock. The lock prevents both searchdir and foundobj |
|
* from disappearing. If we can't ref the new searchdir, we have a |
|
* bit of a problem. Roll back the fastforward to the beginning and |
|
* let lookup_once() take care of it. |
|
*/ |
|
if (searchdir != *searchdir_ret) { |
|
mutex_enter(searchdir->v_interlock); |
|
error2 = vcache_tryvget(searchdir); |
|
/* v_interlock now unheld */ |
|
KASSERT(plock != NULL); |
|
rw_exit(plock); |
|
if (__predict_true(error2 == 0)) { |
|
/* Returning new searchdir, and maybe new foundobj. */ |
|
vrele(*searchdir_ret); |
|
*searchdir_ret = searchdir; |
} else { |
} else { |
VOP_UNLOCK(foundobj); |
/* Returning nothing. */ |
vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY); |
if (foundobj != NULL) { |
vn_lock(foundobj, LK_EXCLUSIVE | LK_RETRY); |
vrele(foundobj); |
|
foundobj = NULL; |
|
} |
|
cnp->cn_nameptr = oldnameptr; |
|
ndp->ni_pathlen = oldpathlen; |
|
error = lookup_parsepath(state); |
} |
} |
|
} else if (plock != NULL) { |
|
/* Drop any namecache lock still held. */ |
|
rw_exit(plock); |
} |
} |
|
|
|
KASSERT(error == 0 ? foundobj != NULL : foundobj == NULL); |
*foundobj_ret = foundobj; |
*foundobj_ret = foundobj; |
error = 0; |
|
done: |
|
KASSERT(*newsearchdir_ret == NULL || |
|
VOP_ISLOCKED(*newsearchdir_ret) == LK_EXCLUSIVE); |
|
/* |
|
* *foundobj_ret is valid only if error == 0. |
|
*/ |
|
KASSERT(error != 0 || *foundobj_ret == NULL || |
|
VOP_ISLOCKED(*foundobj_ret) == LK_EXCLUSIVE); |
|
return error; |
return error; |
} |
} |
|
|
Line 1183 namei_oneroot(struct namei_state *state, |
|
Line 1408 namei_oneroot(struct namei_state *state, |
|
struct nameidata *ndp = state->ndp; |
struct nameidata *ndp = state->ndp; |
struct componentname *cnp = state->cnp; |
struct componentname *cnp = state->cnp; |
struct vnode *searchdir, *foundobj; |
struct vnode *searchdir, *foundobj; |
|
bool searchdir_locked = false; |
int error; |
int error; |
|
|
error = namei_start(state, isnfsd, &searchdir); |
error = namei_start(state, isnfsd, &searchdir); |
Line 1223 namei_oneroot(struct namei_state *state, |
|
Line 1449 namei_oneroot(struct namei_state *state, |
|
|
|
for (;;) { |
for (;;) { |
KASSERT(searchdir != NULL); |
KASSERT(searchdir != NULL); |
KASSERT(VOP_ISLOCKED(searchdir) == LK_EXCLUSIVE); |
KASSERT(!searchdir_locked); |
|
|
/* |
/* |
* If the directory we're on is unmounted, bail out. |
* Parse out the first path name component that we need to |
* XXX: should this also check if it's unlinked? |
* to consider. While doing this, attempt to use the name |
* XXX: yes it should... but how? |
* cache to fast-forward through as many "easy" to find |
|
* components of the path as possible. |
*/ |
*/ |
if (searchdir->v_mount == NULL) { |
error = lookup_fastforward(state, &searchdir, &foundobj); |
vput(searchdir); |
|
ndp->ni_dvp = NULL; |
|
ndp->ni_vp = NULL; |
|
return (ENOENT); |
|
} |
|
|
|
/* |
/* |
* Look up the next path component. |
* If we didn't get a good answer from the namecache, then |
* (currently, this may consume more than one) |
* go directly to the file system. |
*/ |
*/ |
|
if (error != 0 && error != ENOENT) { |
|
error = lookup_once(state, searchdir, &searchdir, |
|
&foundobj, &searchdir_locked); |
|
} |
|
|
/* There should be no slashes here. */ |
/* |
KASSERT(cnp->cn_nameptr[0] != '/'); |
* If the vnode we found is mounted on, then cross the mount |
|
* and get the root vnode in foundobj. If this encounters |
/* and we shouldn't have looped around if we were done */ |
* an error, it will dispose of foundobj, but searchdir is |
KASSERT(cnp->cn_nameptr[0] != '\0'); |
* untouched. |
|
*/ |
error = lookup_parsepath(state); |
if (error == 0 && foundobj != NULL && |
if (error) { |
foundobj->v_type == VDIR && |
vput(searchdir); |
foundobj->v_mountedhere != NULL && |
ndp->ni_dvp = NULL; |
(cnp->cn_flags & NOCROSSMOUNT) == 0) { |
ndp->ni_vp = NULL; |
error = lookup_crossmount(state, &searchdir, |
state->attempt_retry = 1; |
&foundobj, &searchdir_locked); |
return (error); |
|
} |
} |
|
|
error = lookup_once(state, searchdir, &searchdir, &foundobj); |
|
if (error) { |
if (error) { |
if (searchdir != NULL) { |
if (searchdir != NULL) { |
vput(searchdir); |
if (searchdir_locked) { |
|
searchdir_locked = false; |
|
vput(searchdir); |
|
} else { |
|
vrele(searchdir); |
|
} |
} |
} |
ndp->ni_dvp = NULL; |
ndp->ni_dvp = NULL; |
ndp->ni_vp = NULL; |
ndp->ni_vp = NULL; |
Line 1297 namei_oneroot(struct namei_state *state, |
|
Line 1526 namei_oneroot(struct namei_state *state, |
|
* them again. |
* them again. |
*/ |
*/ |
if (namei_atsymlink(state, foundobj)) { |
if (namei_atsymlink(state, foundobj)) { |
|
/* Don't need searchdir locked any more. */ |
|
if (searchdir_locked) { |
|
searchdir_locked = false; |
|
VOP_UNLOCK(searchdir); |
|
} |
ndp->ni_pathlen += state->slashes; |
ndp->ni_pathlen += state->slashes; |
ndp->ni_next -= state->slashes; |
ndp->ni_next -= state->slashes; |
if (neverfollow) { |
if (neverfollow) { |
Line 1338 namei_oneroot(struct namei_state *state, |
|
Line 1572 namei_oneroot(struct namei_state *state, |
|
if (error) { |
if (error) { |
KASSERT(searchdir != foundobj); |
KASSERT(searchdir != foundobj); |
if (searchdir != NULL) { |
if (searchdir != NULL) { |
vput(searchdir); |
vrele(searchdir); |
} |
} |
vput(foundobj); |
vrele(foundobj); |
ndp->ni_dvp = NULL; |
ndp->ni_dvp = NULL; |
ndp->ni_vp = NULL; |
ndp->ni_vp = NULL; |
return error; |
return error; |
} |
} |
/* namei_follow unlocks it (ugh) so rele, not put */ |
|
vrele(foundobj); |
vrele(foundobj); |
foundobj = NULL; |
foundobj = NULL; |
|
|
Line 1376 namei_oneroot(struct namei_state *state, |
|
Line 1609 namei_oneroot(struct namei_state *state, |
|
(cnp->cn_flags & REQUIREDIR)) { |
(cnp->cn_flags & REQUIREDIR)) { |
KASSERT(foundobj != searchdir); |
KASSERT(foundobj != searchdir); |
if (searchdir) { |
if (searchdir) { |
vput(searchdir); |
if (searchdir_locked) { |
|
searchdir_locked = false; |
|
vput(searchdir); |
|
} else { |
|
vrele(searchdir); |
|
} |
|
} else { |
|
KASSERT(!searchdir_locked); |
} |
} |
vput(foundobj); |
vrele(foundobj); |
ndp->ni_dvp = NULL; |
ndp->ni_dvp = NULL; |
ndp->ni_vp = NULL; |
ndp->ni_vp = NULL; |
state->attempt_retry = 1; |
state->attempt_retry = 1; |
Line 1396 namei_oneroot(struct namei_state *state, |
|
Line 1636 namei_oneroot(struct namei_state *state, |
|
* Continue with the next component. |
* Continue with the next component. |
*/ |
*/ |
cnp->cn_nameptr = ndp->ni_next; |
cnp->cn_nameptr = ndp->ni_next; |
if (searchdir == foundobj) { |
if (searchdir != NULL) { |
vrele(searchdir); |
if (searchdir_locked) { |
} else if (searchdir != NULL) { |
searchdir_locked = false; |
vput(searchdir); |
vput(searchdir); |
|
} else { |
|
vrele(searchdir); |
|
} |
} |
} |
searchdir = foundobj; |
searchdir = foundobj; |
foundobj = NULL; |
foundobj = NULL; |
} |
} |
|
|
|
KASSERT((cnp->cn_flags & LOCKPARENT) == 0 || searchdir == NULL || |
|
VOP_ISLOCKED(searchdir) == LK_EXCLUSIVE); |
|
|
skiploop: |
skiploop: |
|
|
if (foundobj != NULL) { |
if (foundobj != NULL) { |
Line 1417 namei_oneroot(struct namei_state *state, |
|
Line 1663 namei_oneroot(struct namei_state *state, |
|
* forever. So convert it to the real root. |
* forever. So convert it to the real root. |
*/ |
*/ |
if (searchdir != NULL) { |
if (searchdir != NULL) { |
if (searchdir == foundobj) |
if (searchdir_locked) { |
vrele(searchdir); |
|
else |
|
vput(searchdir); |
vput(searchdir); |
|
searchdir_locked = false; |
|
} else { |
|
vrele(searchdir); |
|
} |
searchdir = NULL; |
searchdir = NULL; |
} |
} |
vput(foundobj); |
vrele(foundobj); |
foundobj = ndp->ni_rootdir; |
foundobj = ndp->ni_rootdir; |
vref(foundobj); |
vref(foundobj); |
vn_lock(foundobj, LK_EXCLUSIVE | LK_RETRY); |
|
} |
} |
|
|
/* |
/* |
Line 1439 namei_oneroot(struct namei_state *state, |
|
Line 1686 namei_oneroot(struct namei_state *state, |
|
(searchdir == NULL || |
(searchdir == NULL || |
searchdir->v_mount != foundobj->v_mount)) { |
searchdir->v_mount != foundobj->v_mount)) { |
if (searchdir) { |
if (searchdir) { |
vput(searchdir); |
if (searchdir_locked) { |
|
vput(searchdir); |
|
searchdir_locked = false; |
|
} else { |
|
vrele(searchdir); |
|
} |
|
searchdir = NULL; |
} |
} |
vput(foundobj); |
vrele(foundobj); |
foundobj = NULL; |
foundobj = NULL; |
ndp->ni_dvp = NULL; |
ndp->ni_dvp = NULL; |
ndp->ni_vp = NULL; |
ndp->ni_vp = NULL; |
Line 1466 namei_oneroot(struct namei_state *state, |
|
Line 1719 namei_oneroot(struct namei_state *state, |
|
if (state->rdonly && |
if (state->rdonly && |
(cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) { |
(cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) { |
if (searchdir) { |
if (searchdir) { |
if (foundobj != searchdir) { |
if (searchdir_locked) { |
vput(searchdir); |
vput(searchdir); |
|
searchdir_locked = false; |
} else { |
} else { |
vrele(searchdir); |
vrele(searchdir); |
} |
} |
searchdir = NULL; |
searchdir = NULL; |
} |
} |
vput(foundobj); |
vrele(foundobj); |
foundobj = NULL; |
foundobj = NULL; |
ndp->ni_dvp = NULL; |
ndp->ni_dvp = NULL; |
ndp->ni_vp = NULL; |
ndp->ni_vp = NULL; |
state->attempt_retry = 1; |
state->attempt_retry = 1; |
return EROFS; |
return EROFS; |
} |
} |
if ((cnp->cn_flags & LOCKLEAF) == 0) { |
|
|
/* Lock the leaf node if requested. */ |
|
if ((cnp->cn_flags & (LOCKLEAF | LOCKPARENT)) == LOCKPARENT && |
|
searchdir == foundobj) { |
/* |
/* |
* Note: if LOCKPARENT but not LOCKLEAF is |
* Note: if LOCKPARENT but not LOCKLEAF is |
* set, and searchdir == foundobj, this code |
* set, and searchdir == foundobj, this code |
Line 1492 namei_oneroot(struct namei_state *state, |
|
Line 1749 namei_oneroot(struct namei_state *state, |
|
* that uses this combination "knows" this, so |
* that uses this combination "knows" this, so |
* it can't be safely changed. Feh. XXX |
* it can't be safely changed. Feh. XXX |
*/ |
*/ |
VOP_UNLOCK(foundobj); |
KASSERT(searchdir_locked); |
|
VOP_UNLOCK(searchdir); |
|
searchdir_locked = false; |
|
} else if ((cnp->cn_flags & LOCKLEAF) != 0 && |
|
(searchdir != foundobj || |
|
(cnp->cn_flags & LOCKPARENT) == 0)) { |
|
const int lktype = (cnp->cn_flags & LOCKSHARED) != 0 ? |
|
LK_SHARED : LK_EXCLUSIVE; |
|
vn_lock(foundobj, lktype | LK_RETRY); |
} |
} |
} |
} |
|
|
Line 1504 namei_oneroot(struct namei_state *state, |
|
Line 1769 namei_oneroot(struct namei_state *state, |
|
* If LOCKPARENT is not set, the parent directory isn't returned. |
* If LOCKPARENT is not set, the parent directory isn't returned. |
*/ |
*/ |
if ((cnp->cn_flags & LOCKPARENT) == 0 && searchdir != NULL) { |
if ((cnp->cn_flags & LOCKPARENT) == 0 && searchdir != NULL) { |
if (searchdir == foundobj) { |
vrele(searchdir); |
vrele(searchdir); |
|
} else { |
|
vput(searchdir); |
|
} |
|
searchdir = NULL; |
searchdir = NULL; |
} |
} |
|
|
Line 1650 do_lookup_for_nfsd_index(struct namei_st |
|
Line 1911 do_lookup_for_nfsd_index(struct namei_st |
|
struct nameidata *ndp = state->ndp; |
struct nameidata *ndp = state->ndp; |
struct vnode *startdir; |
struct vnode *startdir; |
struct vnode *foundobj; |
struct vnode *foundobj; |
|
bool startdir_locked; |
const char *cp; /* pointer into pathname argument */ |
const char *cp; /* pointer into pathname argument */ |
|
|
KASSERT(cnp == &ndp->ni_cnd); |
KASSERT(cnp == &ndp->ni_cnd); |
Line 1682 do_lookup_for_nfsd_index(struct namei_st |
|
Line 1944 do_lookup_for_nfsd_index(struct namei_st |
|
* own reference to it to avoid consuming the caller's. |
* own reference to it to avoid consuming the caller's. |
*/ |
*/ |
vref(startdir); |
vref(startdir); |
vn_lock(startdir, LK_EXCLUSIVE | LK_RETRY); |
error = lookup_once(state, startdir, &startdir, &foundobj, |
error = lookup_once(state, startdir, &startdir, &foundobj); |
&startdir_locked); |
if (error == 0 && startdir == foundobj) { |
|
vrele(startdir); |
|
} else if (startdir != NULL) { |
|
vput(startdir); |
|
} |
|
if (error) { |
|
goto bad; |
|
} |
|
ndp->ni_vp = foundobj; |
|
|
|
if (foundobj == NULL) { |
KASSERT((cnp->cn_flags & LOCKPARENT) == 0); |
return 0; |
if (startdir_locked) { |
|
VOP_UNLOCK(startdir); |
|
startdir_locked = false; |
} |
} |
|
|
KASSERT((cnp->cn_flags & LOCKPARENT) == 0); |
/* |
if ((cnp->cn_flags & LOCKLEAF) == 0) { |
* If the vnode we found is mounted on, then cross the mount and get |
VOP_UNLOCK(foundobj); |
* the root vnode in foundobj. If this encounters an error, it will |
|
* dispose of foundobj, but searchdir is untouched. |
|
*/ |
|
if (error == 0 && foundobj != NULL && |
|
foundobj->v_type == VDIR && |
|
foundobj->v_mountedhere != NULL && |
|
(cnp->cn_flags & NOCROSSMOUNT) == 0) { |
|
error = lookup_crossmount(state, &startdir, &foundobj, |
|
&startdir_locked); |
} |
} |
return (0); |
|
|
|
bad: |
/* Now toss startdir and see if we have an error. */ |
ndp->ni_vp = NULL; |
if (startdir != NULL) |
|
vrele(startdir); |
|
if (error) |
|
foundobj = NULL; |
|
else if (foundobj != NULL && (cnp->cn_flags & LOCKLEAF) != 0) |
|
vn_lock(foundobj, LK_EXCLUSIVE | LK_RETRY); |
|
|
|
ndp->ni_vp = foundobj; |
return (error); |
return (error); |
} |
} |
|
|