#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: vfs_lookup.c,v 1.239 2025/09/14 14:24:13 andvar Exp $");
#ifdef _KERNEL_OPT
#include "opt_magiclinks.h"
#endif
#include <sys/param.h>
#include <sys/types.h>
#include <sys/dirent.h>
#include <sys/errno.h>
#include <sys/filedesc.h>
#include <sys/fstrans.h>
#include <sys/hash.h>
#include <sys/kauth.h>
#include <sys/kernel.h>
#include <sys/ktrace.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/proc.h>
#include <sys/sdt.h>
#include <sys/syslimits.h>
#include <sys/syslog.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/vnode.h>
#include <sys/vnode_impl.h>
#ifndef MAGICLINKS
#define MAGICLINKS 0
#endif
int vfs_magiclinks = MAGICLINKS;
__CTASSERT(MAXNAMLEN == NAME_MAX);
#define VNL(x) \
(sizeof(x) - 1)
#define VO '{'
#define VC '}'
#define MATCH(str) \
((termchar == '/' && i + VNL(str) == *len) || \
(i + VNL(str) < *len && \
cp[i + VNL(str)] == termchar)) && \
!strncmp((str), &cp[i], VNL(str))
#define SUBSTITUTE(m, s, sl) \
if ((newlen + (sl)) >= MAXPATHLEN) \
return 1; \
i += VNL(m); \
if (termchar != '/') \
i++; \
(void)memcpy(&tmp[newlen], (s), (sl)); \
newlen += (sl); \
change = 1; \
termchar = '/';
static int
symlink_magic(struct proc *p, char *cp, size_t *len)
{
char *tmp;
size_t change, i, newlen, slen;
char termchar = '/';
char idtmp[11];
tmp = PNBUF_GET();
for (change = i = newlen = 0; i < *len; ) {
if (cp[i] != '@') {
tmp[newlen++] = cp[i++];
continue;
}
i++;
if (cp[i] == VO) {
termchar = VC;
i++;
}
if (MATCH("machine_arch")) {
slen = strlen(PROC_MACHINE_ARCH(p));
SUBSTITUTE("machine_arch", PROC_MACHINE_ARCH(p), slen);
} else if (MATCH("machine")) {
slen = VNL(MACHINE);
SUBSTITUTE("machine", MACHINE, slen);
} else if (MATCH("hostname")) {
SUBSTITUTE("hostname", hostname, hostnamelen);
} else if (MATCH("osrelease")) {
slen = strlen(osrelease);
SUBSTITUTE("osrelease", osrelease, slen);
} else if (MATCH("emul")) {
slen = strlen(p->p_emul->e_name);
SUBSTITUTE("emul", p->p_emul->e_name, slen);
} else if (MATCH("kernel_ident")) {
slen = strlen(kernel_ident);
SUBSTITUTE("kernel_ident", kernel_ident, slen);
} else if (MATCH("domainname")) {
SUBSTITUTE("domainname", domainname, domainnamelen);
} else if (MATCH("ostype")) {
slen = strlen(ostype);
SUBSTITUTE("ostype", ostype, slen);
} else if (MATCH("uid")) {
slen = snprintf(idtmp, sizeof(idtmp), "%u",
kauth_cred_geteuid(kauth_cred_get()));
SUBSTITUTE("uid", idtmp, slen);
} else if (MATCH("ruid")) {
slen = snprintf(idtmp, sizeof(idtmp), "%u",
kauth_cred_getuid(kauth_cred_get()));
SUBSTITUTE("ruid", idtmp, slen);
} else if (MATCH("gid")) {
slen = snprintf(idtmp, sizeof(idtmp), "%u",
kauth_cred_getegid(kauth_cred_get()));
SUBSTITUTE("gid", idtmp, slen);
} else if (MATCH("rgid")) {
slen = snprintf(idtmp, sizeof(idtmp), "%u",
kauth_cred_getgid(kauth_cred_get()));
SUBSTITUTE("rgid", idtmp, slen);
} else {
tmp[newlen++] = '@';
if (termchar == VC)
tmp[newlen++] = VO;
}
}
if (change) {
(void)memcpy(cp, tmp, newlen);
*len = newlen;
}
PNBUF_PUT(tmp);
return 0;
}
#undef VNL
#undef VO
#undef VC
#undef MATCH
#undef SUBSTITUTE
uint32_t
namei_hash(const char *name, const char **ep)
{
uint32_t hash;
hash = HASH32_STR_INIT;
if (*ep != NULL) {
for (; name < *ep; name++)
hash = hash * 33 + *(const uint8_t *)name;
} else {
for (; *name != '\0' && *name != '/'; name++)
hash = hash * 33 + *(const uint8_t *)name;
*ep = name;
}
return (hash + (hash >> 5));
}
struct pathbuf {
char *pb_path;
char *pb_pathcopy;
unsigned pb_pathcopyuses;
};
static struct pathbuf *
pathbuf_create_raw(void)
{
struct pathbuf *pb;
pb = kmem_alloc(sizeof(*pb), KM_SLEEP);
pb->pb_path = PNBUF_GET();
if (pb->pb_path == NULL) {
kmem_free(pb, sizeof(*pb));
return NULL;
}
pb->pb_pathcopy = NULL;
pb->pb_pathcopyuses = 0;
return pb;
}
void
pathbuf_destroy(struct pathbuf *pb)
{
KASSERT(pb->pb_pathcopyuses == 0);
KASSERT(pb->pb_pathcopy == NULL);
PNBUF_PUT(pb->pb_path);
kmem_free(pb, sizeof(*pb));
}
struct pathbuf *
pathbuf_assimilate(char *pnbuf)
{
struct pathbuf *pb;
pb = kmem_alloc(sizeof(*pb), KM_SLEEP);
pb->pb_path = pnbuf;
pb->pb_pathcopy = NULL;
pb->pb_pathcopyuses = 0;
return pb;
}
struct pathbuf *
pathbuf_create(const char *path)
{
struct pathbuf *pb;
int error;
pb = pathbuf_create_raw();
if (pb == NULL) {
return NULL;
}
error = copystr(path, pb->pb_path, PATH_MAX, NULL);
if (error != 0) {
KASSERT(!"kernel path too long in pathbuf_create");
pb->pb_path[PATH_MAX-1] = '\0';
}
return pb;
}
int
pathbuf_copyin(const char *userpath, struct pathbuf **ret)
{
struct pathbuf *pb;
int error;
pb = pathbuf_create_raw();
if (pb == NULL) {
return SET_ERROR(ENOMEM);
}
error = copyinstr(userpath, pb->pb_path, PATH_MAX, NULL);
if (error) {
pathbuf_destroy(pb);
return error;
}
*ret = pb;
return 0;
}
int
pathbuf_maybe_copyin(const char *path, enum uio_seg seg, struct pathbuf **ret)
{
if (seg == UIO_USERSPACE) {
return pathbuf_copyin(path, ret);
} else {
*ret = pathbuf_create(path);
if (*ret == NULL) {
return SET_ERROR(ENOMEM);
}
return 0;
}
}
void
pathbuf_copystring(const struct pathbuf *pb, char *buf, size_t maxlen)
{
strlcpy(buf, pb->pb_path, maxlen);
}
const char *
pathbuf_stringcopy_get(struct pathbuf *pb)
{
if (pb->pb_pathcopyuses == 0) {
pb->pb_pathcopy = PNBUF_GET();
strcpy(pb->pb_pathcopy, pb->pb_path);
}
pb->pb_pathcopyuses++;
return pb->pb_pathcopy;
}
void
pathbuf_stringcopy_put(struct pathbuf *pb, const char *str)
{
KASSERT(str == pb->pb_pathcopy);
KASSERT(pb->pb_pathcopyuses > 0);
pb->pb_pathcopyuses--;
if (pb->pb_pathcopyuses == 0) {
PNBUF_PUT(pb->pb_pathcopy);
pb->pb_pathcopy = NULL;
}
}
struct namei_state {
struct nameidata *ndp;
struct componentname *cnp;
int docache;
int rdonly;
int slashes;
unsigned attempt_retry:1;
unsigned root_referenced:1;
};
static void
namei_init(struct namei_state *state, struct nameidata *ndp)
{
state->ndp = ndp;
state->cnp = &ndp->ni_cnd;
state->docache = 0;
state->rdonly = 0;
state->slashes = 0;
state->root_referenced = 0;
KASSERTMSG((state->cnp->cn_cred != NULL), "namei: bad cred/proc");
KASSERTMSG(((state->cnp->cn_nameiop & (~OPMASK)) == 0),
"namei: nameiop contaminated with flags: %08"PRIx32,
state->cnp->cn_nameiop);
KASSERTMSG(((state->cnp->cn_flags & OPMASK) == 0),
"name: flags contaminated with nameiops: %08"PRIx32,
state->cnp->cn_flags);
state->ndp->ni_pnbuf = state->ndp->ni_pathbuf->pb_path;
}
static void
namei_cleanup(struct namei_state *state)
{
KASSERT(state->cnp == &state->ndp->ni_cnd);
if (state->root_referenced) {
if (state->ndp->ni_rootdir != NULL)
vrele(state->ndp->ni_rootdir);
if (state->ndp->ni_erootdir != NULL)
vrele(state->ndp->ni_erootdir);
}
}
static struct vnode *
namei_getstartdir(struct namei_state *state)
{
struct nameidata *ndp = state->ndp;
struct componentname *cnp = state->cnp;
struct cwdinfo *cwdi;
struct lwp *self = curlwp;
struct vnode *rootdir, *erootdir, *curdir, *startdir;
if (state->root_referenced) {
if (state->ndp->ni_rootdir != NULL)
vrele(state->ndp->ni_rootdir);
if (state->ndp->ni_erootdir != NULL)
vrele(state->ndp->ni_erootdir);
state->root_referenced = 0;
}
cwdi = self->l_proc->p_cwdi;
rw_enter(&cwdi->cwdi_lock, RW_READER);
if (cwdi->cwdi_rdir == NULL || (cnp->cn_flags & NOCHROOT)) {
rootdir = rootvnode;
} else {
rootdir = cwdi->cwdi_rdir;
}
if ((cnp->cn_flags & TRYEMULROOT) == 0) {
erootdir = NULL;
} else if (cnp->cn_flags & EMULROOTSET) {
erootdir = ndp->ni_erootdir;
} else if (!strncmp(ndp->ni_pnbuf, "/../", 4)) {
erootdir = NULL;
} else {
erootdir = cwdi->cwdi_edir;
}
curdir = cwdi->cwdi_cdir;
if (ndp->ni_pnbuf[0] != '/') {
if (ndp->ni_atdir != NULL) {
startdir = ndp->ni_atdir;
} else {
startdir = curdir;
}
erootdir = NULL;
} else if (cnp->cn_flags & TRYEMULROOT && erootdir != NULL) {
startdir = erootdir;
} else {
startdir = rootdir;
erootdir = NULL;
}
state->ndp->ni_rootdir = rootdir;
state->ndp->ni_erootdir = erootdir;
if (startdir != NULL)
vref(startdir);
if (state->ndp->ni_rootdir != NULL)
vref(state->ndp->ni_rootdir);
if (state->ndp->ni_erootdir != NULL)
vref(state->ndp->ni_erootdir);
state->root_referenced = 1;
rw_exit(&cwdi->cwdi_lock);
return startdir;
}
static struct vnode *
namei_getstartdir_for_nfsd(struct namei_state *state)
{
KASSERT(state->ndp->ni_atdir != NULL);
if (rootvnode == NULL) {
return NULL;
}
state->ndp->ni_rootdir = rootvnode;
state->ndp->ni_erootdir = NULL;
vref(state->ndp->ni_atdir);
KASSERT(! state->root_referenced);
vref(state->ndp->ni_rootdir);
state->root_referenced = 1;
return state->ndp->ni_atdir;
}
static void
namei_ktrace(struct namei_state *state)
{
struct nameidata *ndp = state->ndp;
struct componentname *cnp = state->cnp;
struct lwp *self = curlwp;
const char *emul_path;
if (ktrpoint(KTR_NAMEI)) {
if (ndp->ni_erootdir != NULL) {
if (cnp->cn_flags & EMULROOTSET)
emul_path = ndp->ni_next;
else
emul_path = self->l_proc->p_emul->e_path;
ktrnamei2(emul_path, strlen(emul_path),
ndp->ni_pnbuf, ndp->ni_pathlen);
} else
ktrnamei(ndp->ni_pnbuf, ndp->ni_pathlen);
}
}
static int
namei_start(struct namei_state *state, int isnfsd,
struct vnode **startdir_ret)
{
struct nameidata *ndp = state->ndp;
struct vnode *startdir;
ndp->ni_pathlen = strlen(ndp->ni_pnbuf) + 1;
if (ndp->ni_pathlen == 1) {
ndp->ni_erootdir = NULL;
return SET_ERROR(ENOENT);
}
ndp->ni_loopcnt = 0;
if (isnfsd) {
startdir = namei_getstartdir_for_nfsd(state);
} else {
startdir = namei_getstartdir(state);
namei_ktrace(state);
}
if (startdir == NULL) {
return SET_ERROR(ENOENT);
}
if (startdir->v_type != VDIR) {
vrele(startdir);
return SET_ERROR(ENOTDIR);
}
*startdir_ret = startdir;
return 0;
}
static inline int
namei_atsymlink(struct namei_state *state, struct vnode *foundobj)
{
return (foundobj->v_type == VLNK) &&
(state->cnp->cn_flags & (FOLLOW|REQUIREDIR));
}
static inline int
namei_follow(struct namei_state *state, int inhibitmagic,
struct vnode *searchdir, struct vnode *foundobj,
struct vnode **newsearchdir_ret)
{
struct nameidata *ndp = state->ndp;
struct componentname *cnp = state->cnp;
struct lwp *self = curlwp;
struct iovec aiov;
struct uio auio;
char *cp;
size_t linklen;
int error;
if (ndp->ni_loopcnt++ >= MAXSYMLINKS) {
return SET_ERROR(ELOOP);
}
vn_lock(foundobj, LK_EXCLUSIVE | LK_RETRY);
if (foundobj->v_mount->mnt_flag & MNT_SYMPERM) {
error = VOP_ACCESS(foundobj, VEXEC, cnp->cn_cred);
if (error != 0) {
VOP_UNLOCK(foundobj);
return error;
}
}
cp = PNBUF_GET();
aiov.iov_base = cp;
aiov.iov_len = MAXPATHLEN;
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_offset = 0;
auio.uio_rw = UIO_READ;
auio.uio_resid = MAXPATHLEN;
UIO_SETUP_SYSSPACE(&auio);
error = VOP_READLINK(foundobj, &auio, cnp->cn_cred);
VOP_UNLOCK(foundobj);
if (error) {
PNBUF_PUT(cp);
return error;
}
linklen = MAXPATHLEN - auio.uio_resid;
if (linklen == 0) {
PNBUF_PUT(cp);
return SET_ERROR(ENOENT);
}
if ((!inhibitmagic && vfs_magiclinks &&
symlink_magic(self->l_proc, cp, &linklen)) ||
(linklen + ndp->ni_pathlen >= MAXPATHLEN)) {
PNBUF_PUT(cp);
return SET_ERROR(ENAMETOOLONG);
}
if (ndp->ni_pathlen > 1) {
memcpy(cp + linklen, ndp->ni_next, ndp->ni_pathlen);
} else {
cp[linklen] = '\0';
}
ndp->ni_pathlen += linklen;
memcpy(ndp->ni_pnbuf, cp, ndp->ni_pathlen);
PNBUF_PUT(cp);
cnp->cn_nameptr = ndp->ni_pnbuf;
if (ndp->ni_pnbuf[0] == '/') {
vrele(searchdir);
searchdir = ndp->ni_erootdir;
if (searchdir == NULL ||
(ndp->ni_pnbuf[1] == '.'
&& ndp->ni_pnbuf[2] == '.'
&& ndp->ni_pnbuf[3] == '/')) {
ndp->ni_erootdir = NULL;
searchdir = ndp->ni_rootdir;
}
vref(searchdir);
while (cnp->cn_nameptr[0] == '/') {
cnp->cn_nameptr++;
ndp->ni_pathlen--;
}
}
*newsearchdir_ret = searchdir;
return 0;
}
static int
lookup_parsepath(struct namei_state *state, struct vnode *searchdir)
{
const char *cp;
int error;
struct componentname *cnp = state->cnp;
struct nameidata *ndp = state->ndp;
KASSERT(cnp == &ndp->ni_cnd);
error = VOP_PARSEPATH(searchdir, cnp->cn_nameptr, &cnp->cn_namelen);
if (error) {
return error;
}
cp = cnp->cn_nameptr + cnp->cn_namelen;
if (cnp->cn_namelen > KERNEL_NAME_MAX) {
return SET_ERROR(ENAMETOOLONG);
}
#ifdef NAMEI_DIAGNOSTIC
{ char c = *cp;
*(char *)cp = '\0';
printf("{%s}: ", cnp->cn_nameptr);
*(char *)cp = c; }
#endif
ndp->ni_pathlen -= cnp->cn_namelen;
ndp->ni_next = cp;
if (*cp == '/') {
do {
cp++;
} while (*cp == '/');
state->slashes = cp - ndp->ni_next;
ndp->ni_pathlen -= state->slashes;
ndp->ni_next = cp;
cnp->cn_flags |= REQUIREDIR;
} else {
state->slashes = 0;
cnp->cn_flags &= ~REQUIREDIR;
}
if (*cp == '\0') {
if (state->docache)
cnp->cn_flags |= MAKEENTRY;
else
cnp->cn_flags &= ~MAKEENTRY;
cnp->cn_flags |= ISLASTCN;
} else {
cnp->cn_flags |= MAKEENTRY;
cnp->cn_flags &= ~ISLASTCN;
}
if (cnp->cn_namelen == 2 &&
cnp->cn_nameptr[1] == '.' && cnp->cn_nameptr[0] == '.')
cnp->cn_flags |= ISDOTDOT;
else
cnp->cn_flags &= ~ISDOTDOT;
return 0;
}
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, *vp;
struct vnode *searchdir;
struct mount *mp;
int error, lktype;
searchdir = *searchdir_ret;
foundobj = *foundobj_ret;
error = 0;
KASSERT((cnp->cn_flags & NOCROSSMOUNT) == 0);
if (*searchdir_locked) {
KASSERT(searchdir != NULL);
lktype = VOP_ISLOCKED(searchdir);
VOP_UNLOCK(searchdir);
*searchdir_locked = false;
} else {
lktype = LK_NONE;
}
while (foundobj->v_type == VDIR &&
(mp = foundobj->v_mountedhere) != NULL &&
(cnp->cn_flags & NOCROSSMOUNT) == 0) {
if (cache_lookup_mount(foundobj, &vp)) {
vrele(foundobj);
foundobj = vp;
} else {
while ((mp = foundobj->v_mountedhere) != NULL) {
fstrans_start(mp);
if (fstrans_held(mp) &&
mp == foundobj->v_mountedhere) {
break;
}
fstrans_done(mp);
}
if (mp == NULL) {
break;
}
error = VFS_ROOT(mp, LK_NONE, &vp);
if (error == 0) {
cache_enter_mount(foundobj, vp);
}
vrele(foundobj);
fstrans_done(mp);
if (error) {
foundobj = NULL;
break;
}
foundobj = vp;
}
if (searchdir == NULL) {
} else if (foundobj->v_type == VDIR) {
vrele(searchdir);
*searchdir_ret = searchdir = NULL;
lktype = LK_NONE;
}
}
if (error == 0 && lktype != LK_NONE) {
vn_lock(searchdir, lktype | LK_RETRY);
*searchdir_locked = true;
}
*foundobj_ret = foundobj;
return error;
}
static int
lookup_lktype(struct vnode *searchdir, struct componentname *cnp)
{
if ((searchdir->v_mount->mnt_iflag & IMNT_SHRLOOKUP) != 0 &&
(cnp->cn_nameiop == LOOKUP || (cnp->cn_flags & ISLASTCN) == 0)) {
return LK_SHARED;
} else {
return LK_EXCLUSIVE;
}
}
static int
lookup_once(struct namei_state *state,
struct vnode *searchdir,
struct vnode **newsearchdir_ret,
struct vnode **foundobj_ret,
bool *newsearchdir_locked_ret)
{
struct vnode *tmpvn;
struct vnode *foundobj;
struct lwp *l = curlwp;
bool searchdir_locked = false;
int error, lktype;
struct componentname *cnp = state->cnp;
struct nameidata *ndp = state->ndp;
KASSERT(cnp == &ndp->ni_cnd);
*newsearchdir_ret = searchdir;
if (cnp->cn_flags & ISDOTDOT) {
struct proc *p = l->l_proc;
for (;;) {
if (searchdir == ndp->ni_rootdir ||
searchdir == rootvnode) {
foundobj = searchdir;
vref(foundobj);
*foundobj_ret = foundobj;
if (cnp->cn_flags & LOCKPARENT) {
lktype = lookup_lktype(searchdir, cnp);
vn_lock(searchdir, lktype | LK_RETRY);
searchdir_locked = true;
}
error = 0;
goto done;
}
if (ndp->ni_rootdir != rootvnode) {
int retval;
retval = vn_isunder(searchdir, ndp->ni_rootdir,
l);
if (!retval) {
log(LOG_WARNING,
"chrooted pid %d uid %d (%s) "
"detected outside of its chroot\n",
p->p_pid,
kauth_cred_geteuid(l->l_cred),
p->p_comm);
vrele(searchdir);
searchdir = NULL;
foundobj = ndp->ni_rootdir;
vref(foundobj);
vref(foundobj);
*newsearchdir_ret = foundobj;
*foundobj_ret = foundobj;
error = 0;
goto done;
}
}
if ((searchdir->v_vflag & VV_ROOT) == 0 ||
(cnp->cn_flags & NOCROSSMOUNT))
break;
tmpvn = searchdir;
searchdir = searchdir->v_mount->mnt_vnodecovered;
vref(searchdir);
vrele(tmpvn);
*newsearchdir_ret = searchdir;
}
}
lktype = lookup_lktype(searchdir, cnp);
unionlookup:
foundobj = NULL;
if (!searchdir_locked) {
vn_lock(searchdir, lktype | LK_RETRY);
searchdir_locked = true;
}
error = VOP_LOOKUP(searchdir, &foundobj, cnp);
if (error != 0) {
KASSERTMSG((foundobj == NULL),
"leaf `%s' should be empty but is %p",
cnp->cn_nameptr, foundobj);
#ifdef NAMEI_DIAGNOSTIC
printf("not found\n");
#endif
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) &&
(searchdir->v_vflag & VV_ROOT) &&
(searchdir->v_mount->mnt_flag & MNT_UNION)) {
tmpvn = searchdir;
searchdir = searchdir->v_mount->mnt_vnodecovered;
vref(searchdir);
vput(tmpvn);
searchdir_locked = false;
*newsearchdir_ret = searchdir;
goto unionlookup;
}
if (error != EJUSTRETURN)
goto done;
if ((cnp->cn_flags & (REQUIREDIR | CREATEDIR)) == REQUIREDIR) {
error = SET_ERROR(ENOENT);
goto done;
}
if (state->rdonly) {
error = SET_ERROR(EROFS);
goto done;
}
*foundobj_ret = NULL;
error = 0;
goto done;
}
#ifdef NAMEI_DIAGNOSTIC
printf("found\n");
#endif
if (searchdir != NULL) {
KASSERT(searchdir_locked);
if ((cnp->cn_flags & (ISLASTCN | LOCKPARENT)) !=
(ISLASTCN | LOCKPARENT)) {
VOP_UNLOCK(searchdir);
searchdir_locked = false;
}
} else {
KASSERT(!searchdir_locked);
}
*foundobj_ret = foundobj;
error = 0;
done:
*newsearchdir_locked_ret = searchdir_locked;
return error;
}
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;
bool terminal;
plock = NULL;
searchdir = *searchdir_ret;
oldnameptr = cnp->cn_nameptr;
oldpathlen = ndp->ni_pathlen;
terminal = false;
for (;;) {
foundobj = NULL;
KASSERT(cnp->cn_nameptr[0] != '/');
KASSERT(cnp->cn_nameptr[0] != '\0');
if ((error = lookup_parsepath(state, searchdir)) != 0) {
break;
}
if ((cnp->cn_flags & ISDOTDOT) != 0) {
if ((searchdir->v_vflag & VV_ROOT) != 0 &&
(cnp->cn_flags & NOCROSSMOUNT)) {
error = SET_ERROR(EOPNOTSUPP);
break;
}
if (ndp->ni_rootdir != rootvnode) {
error = SET_ERROR(EOPNOTSUPP);
break;
}
}
if ((cnp->cn_flags & ISLASTCN) != 0) {
if (cnp->cn_nameiop != LOOKUP ||
(cnp->cn_flags & LOCKPARENT) != 0) {
error = SET_ERROR(EOPNOTSUPP);
break;
}
}
if (!cache_lookup_linked(searchdir, cnp->cn_nameptr,
cnp->cn_namelen, &foundobj, &plock, cnp->cn_cred)) {
error = SET_ERROR(EOPNOTSUPP);
break;
}
KASSERT(plock != NULL);
KASSERT(rw_lock_held(plock));
if (foundobj == NULL) {
if ((searchdir->v_vflag & VV_ROOT) != 0 &&
(searchdir->v_mount->mnt_flag & MNT_UNION) != 0) {
error = SET_ERROR(EOPNOTSUPP);
} else {
error = SET_ERROR(ENOENT);
terminal = ((cnp->cn_flags & ISLASTCN) != 0);
}
break;
}
if (foundobj->v_type != VDIR) {
error = vcache_tryvget(foundobj);
if (error != 0) {
foundobj = NULL;
error = SET_ERROR(EOPNOTSUPP);
} else {
terminal = (foundobj->v_type != VLNK &&
(cnp->cn_flags & ISLASTCN) != 0);
}
break;
}
if (foundobj->v_mountedhere != NULL) {
while (foundobj->v_mountedhere != NULL &&
(cnp->cn_flags & NOCROSSMOUNT) == 0 &&
cache_cross_mount(&foundobj, &plock)) {
KASSERT(foundobj != NULL);
KASSERT(foundobj->v_type == VDIR);
}
if (foundobj->v_mountedhere != NULL) {
error = vcache_tryvget(foundobj);
if (error != 0) {
foundobj = NULL;
error = SET_ERROR(EOPNOTSUPP);
}
break;
} else {
searchdir = NULL;
}
}
if ((cnp->cn_flags & ISLASTCN) != 0) {
error = vcache_tryvget(foundobj);
if (error != 0) {
foundobj = NULL;
error = SET_ERROR(EOPNOTSUPP);
} else {
terminal = (foundobj->v_type != VLNK);
}
break;
}
cnp->cn_nameptr = ndp->ni_next;
searchdir = foundobj;
}
if (terminal) {
KASSERT(plock != NULL);
rw_exit(plock);
vrele(*searchdir_ret);
*searchdir_ret = NULL;
} else if (searchdir != *searchdir_ret) {
if (searchdir == NULL) {
error2 = SET_ERROR(EOPNOTSUPP);
} else {
error2 = vcache_tryvget(searchdir);
}
KASSERT(plock != NULL);
rw_exit(plock);
if (__predict_true(error2 == 0)) {
vrele(*searchdir_ret);
*searchdir_ret = searchdir;
} else {
if (foundobj != NULL) {
vrele(foundobj);
foundobj = NULL;
}
cnp->cn_nameptr = oldnameptr;
ndp->ni_pathlen = oldpathlen;
error = lookup_parsepath(state, *searchdir_ret);
if (error == 0) {
error = SET_ERROR(EOPNOTSUPP);
}
}
} else if (plock != NULL) {
rw_exit(plock);
}
KASSERT(error == 0 ? foundobj != NULL : foundobj == NULL);
*foundobj_ret = foundobj;
return error;
}
static int
namei_oneroot(struct namei_state *state,
int neverfollow, int inhibitmagic, int isnfsd)
{
struct nameidata *ndp = state->ndp;
struct componentname *cnp = state->cnp;
struct vnode *searchdir, *foundobj;
bool searchdir_locked = false;
int error;
error = namei_start(state, isnfsd, &searchdir);
if (error) {
ndp->ni_dvp = NULL;
ndp->ni_vp = NULL;
return error;
}
KASSERT(searchdir->v_type == VDIR);
state->docache = (cnp->cn_flags & NOCACHE) ^ NOCACHE;
if (cnp->cn_nameiop == DELETE)
state->docache = 0;
state->rdonly = cnp->cn_flags & RDONLY;
cnp->cn_nameptr = ndp->ni_pnbuf;
while (cnp->cn_nameptr[0] == '/') {
cnp->cn_nameptr++;
ndp->ni_pathlen--;
}
if (cnp->cn_nameptr[0] == '\0') {
foundobj = searchdir;
searchdir = NULL;
cnp->cn_flags |= ISLASTCN;
goto skiploop;
}
for (;;) {
KASSERT(searchdir != NULL);
KASSERT(!searchdir_locked);
error = lookup_fastforward(state, &searchdir, &foundobj);
if (error == EOPNOTSUPP) {
error = lookup_once(state, searchdir, &searchdir,
&foundobj, &searchdir_locked);
}
if (error == 0 && foundobj != NULL &&
foundobj->v_type == VDIR &&
foundobj->v_mountedhere != NULL &&
(cnp->cn_flags & NOCROSSMOUNT) == 0) {
error = lookup_crossmount(state, &searchdir,
&foundobj, &searchdir_locked);
}
if (error) {
if (searchdir != NULL) {
if (searchdir_locked) {
searchdir_locked = false;
vput(searchdir);
} else {
vrele(searchdir);
}
}
ndp->ni_dvp = NULL;
ndp->ni_vp = NULL;
state->attempt_retry = 1;
return error;
}
if (foundobj == NULL) {
KASSERT(searchdir != NULL ||
(cnp->cn_flags & ISLASTCN) != 0);
break;
}
if (namei_atsymlink(state, foundobj)) {
if (searchdir_locked) {
searchdir_locked = false;
VOP_UNLOCK(searchdir);
}
ndp->ni_pathlen += state->slashes;
ndp->ni_next -= state->slashes;
if (neverfollow) {
error = SET_ERROR(EINVAL);
} else if (searchdir == NULL) {
error = SET_ERROR(ENOTDIR);
} else {
error = namei_follow(state, inhibitmagic,
searchdir, foundobj,
&searchdir);
}
if (error) {
KASSERT(searchdir != foundobj);
if (searchdir != NULL) {
vrele(searchdir);
}
vrele(foundobj);
ndp->ni_dvp = NULL;
ndp->ni_vp = NULL;
return error;
}
vrele(foundobj);
foundobj = NULL;
if (cnp->cn_nameptr[0] == '\0') {
KASSERT(searchdir != NULL);
foundobj = searchdir;
searchdir = NULL;
cnp->cn_flags |= ISLASTCN;
break;
}
continue;
}
if ((foundobj->v_type != VDIR) &&
(cnp->cn_flags & REQUIREDIR)) {
KASSERT(foundobj != searchdir);
if (searchdir) {
if (searchdir_locked) {
searchdir_locked = false;
vput(searchdir);
} else {
vrele(searchdir);
}
} else {
KASSERT(!searchdir_locked);
}
vrele(foundobj);
ndp->ni_dvp = NULL;
ndp->ni_vp = NULL;
state->attempt_retry = 1;
return SET_ERROR(ENOTDIR);
}
if (cnp->cn_flags & ISLASTCN) {
break;
}
cnp->cn_nameptr = ndp->ni_next;
if (searchdir != NULL) {
if (searchdir_locked) {
searchdir_locked = false;
vput(searchdir);
} else {
vrele(searchdir);
}
}
searchdir = foundobj;
foundobj = NULL;
}
KASSERT((cnp->cn_flags & LOCKPARENT) == 0 || searchdir == NULL ||
VOP_ISLOCKED(searchdir) == LK_EXCLUSIVE);
skiploop:
if (foundobj != NULL) {
if (foundobj == ndp->ni_erootdir) {
if (searchdir != NULL) {
if (searchdir_locked) {
vput(searchdir);
searchdir_locked = false;
} else {
vrele(searchdir);
}
searchdir = NULL;
}
vrele(foundobj);
foundobj = ndp->ni_rootdir;
vref(foundobj);
}
if (cnp->cn_nameiop != LOOKUP &&
(searchdir == NULL ||
searchdir->v_mount != foundobj->v_mount) &&
(cnp->cn_flags & NONEXCLHACK) == 0) {
if (searchdir) {
if (searchdir_locked) {
vput(searchdir);
searchdir_locked = false;
} else {
vrele(searchdir);
}
searchdir = NULL;
}
vrele(foundobj);
foundobj = NULL;
ndp->ni_dvp = NULL;
ndp->ni_vp = NULL;
state->attempt_retry = 1;
switch (cnp->cn_nameiop) {
case CREATE:
return SET_ERROR(EEXIST);
case DELETE:
case RENAME:
return SET_ERROR(EBUSY);
default:
break;
}
panic("Invalid nameiop\n");
}
if (state->rdonly &&
(cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) {
if (searchdir) {
if (searchdir_locked) {
vput(searchdir);
searchdir_locked = false;
} else {
vrele(searchdir);
}
searchdir = NULL;
}
vrele(foundobj);
foundobj = NULL;
ndp->ni_dvp = NULL;
ndp->ni_vp = NULL;
state->attempt_retry = 1;
return SET_ERROR(EROFS);
}
if ((cnp->cn_flags & (LOCKLEAF | LOCKPARENT)) == LOCKPARENT &&
searchdir == 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);
}
}
if ((cnp->cn_flags & LOCKPARENT) == 0 && searchdir != NULL) {
vrele(searchdir);
searchdir = NULL;
}
ndp->ni_dvp = searchdir;
ndp->ni_vp = foundobj;
return 0;
}
static int
namei_tryemulroot(struct namei_state *state,
int neverfollow, int inhibitmagic, int isnfsd)
{
int error;
struct nameidata *ndp = state->ndp;
struct componentname *cnp = state->cnp;
const char *savepath = NULL;
KASSERT(cnp == &ndp->ni_cnd);
if (cnp->cn_flags & TRYEMULROOT) {
savepath = pathbuf_stringcopy_get(ndp->ni_pathbuf);
}
emul_retry:
state->attempt_retry = 0;
error = namei_oneroot(state, neverfollow, inhibitmagic, isnfsd);
if (error) {
if (ndp->ni_erootdir != NULL && state->attempt_retry) {
cnp->cn_flags &= ~TRYEMULROOT;
state->attempt_retry = 0;
strcpy(ndp->ni_pathbuf->pb_path, savepath);
pathbuf_stringcopy_put(ndp->ni_pathbuf, savepath);
savepath = NULL;
goto emul_retry;
}
}
if (savepath != NULL) {
pathbuf_stringcopy_put(ndp->ni_pathbuf, savepath);
}
return error;
}
int
namei(struct nameidata *ndp)
{
struct namei_state state;
int error;
namei_init(&state, ndp);
error = namei_tryemulroot(&state,
0, 0, 0);
namei_cleanup(&state);
if (error) {
KASSERT(ndp->ni_dvp == NULL);
KASSERT(ndp->ni_vp == NULL);
}
return error;
}
int
lookup_for_nfsd(struct nameidata *ndp, struct vnode *forcecwd, int neverfollow)
{
struct namei_state state;
int error;
KASSERT(ndp->ni_atdir == NULL);
ndp->ni_atdir = forcecwd;
namei_init(&state, ndp);
error = namei_tryemulroot(&state,
neverfollow, 1, 1);
namei_cleanup(&state);
if (error) {
KASSERT(ndp->ni_dvp == NULL);
KASSERT(ndp->ni_vp == NULL);
}
return error;
}
static int
do_lookup_for_nfsd_index(struct namei_state *state)
{
int error;
struct componentname *cnp = state->cnp;
struct nameidata *ndp = state->ndp;
struct vnode *startdir;
struct vnode *foundobj;
bool startdir_locked;
const char *cp;
KASSERT(cnp == &ndp->ni_cnd);
startdir = state->ndp->ni_atdir;
cnp->cn_nameptr = ndp->ni_pnbuf;
state->docache = 1;
state->rdonly = cnp->cn_flags & RDONLY;
ndp->ni_dvp = NULL;
error = VOP_PARSEPATH(startdir, cnp->cn_nameptr, &cnp->cn_namelen);
if (error) {
return error;
}
cp = cnp->cn_nameptr + cnp->cn_namelen;
KASSERT(cnp->cn_namelen <= KERNEL_NAME_MAX);
ndp->ni_pathlen -= cnp->cn_namelen;
ndp->ni_next = cp;
state->slashes = 0;
cnp->cn_flags &= ~REQUIREDIR;
cnp->cn_flags |= MAKEENTRY|ISLASTCN;
if (cnp->cn_namelen == 2 &&
cnp->cn_nameptr[1] == '.' && cnp->cn_nameptr[0] == '.')
cnp->cn_flags |= ISDOTDOT;
else
cnp->cn_flags &= ~ISDOTDOT;
vref(startdir);
error = lookup_once(state, startdir, &startdir, &foundobj,
&startdir_locked);
KASSERT((cnp->cn_flags & LOCKPARENT) == 0);
if (startdir_locked) {
VOP_UNLOCK(startdir);
startdir_locked = false;
}
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);
}
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;
}
int
lookup_for_nfsd_index(struct nameidata *ndp, struct vnode *startdir)
{
struct namei_state state;
int error;
KASSERT(ndp->ni_atdir == NULL);
ndp->ni_atdir = startdir;
if (strlen(ndp->ni_pathbuf->pb_path) > KERNEL_NAME_MAX) {
return SET_ERROR(ENAMETOOLONG);
}
if (strchr(ndp->ni_pathbuf->pb_path, '/')) {
return SET_ERROR(EINVAL);
}
ndp->ni_pathlen = strlen(ndp->ni_pathbuf->pb_path) + 1;
ndp->ni_pnbuf = NULL;
ndp->ni_cnd.cn_nameptr = NULL;
namei_init(&state, ndp);
error = do_lookup_for_nfsd_index(&state);
namei_cleanup(&state);
return error;
}
int
relookup(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp,
int dummy)
{
int rdonly;
int error = 0;
#ifdef DEBUG
size_t newlen;
const char *cp;
#endif
(void)dummy;
rdonly = cnp->cn_flags & RDONLY;
#ifdef DEBUG
#if 0
cp = NULL;
newhash = namei_hash(cnp->cn_nameptr, &cp);
if ((uint32_t)newhash != (uint32_t)cnp->cn_hash)
panic("relookup: bad hash");
#endif
error = VOP_PARSEPATH(dvp, cnp->cn_nameptr, &newlen);
if (error) {
panic("relookup: parsepath failed with error %d", error);
}
if (cnp->cn_namelen != newlen)
panic("relookup: bad len");
cp = cnp->cn_nameptr + cnp->cn_namelen;
while (*cp == '/')
cp++;
if (*cp != 0)
panic("relookup: not last component");
#endif
if (cnp->cn_nameptr[0] == '\0')
panic("relookup: null name");
if (cnp->cn_flags & ISDOTDOT)
panic("relookup: lookup on dot-dot");
*vpp = NULL;
error = VOP_LOOKUP(dvp, vpp, cnp);
if ((error) != 0) {
KASSERTMSG((*vpp == NULL),
"leaf `%s' should be empty but is %p",
cnp->cn_nameptr, *vpp);
if (error != EJUSTRETURN)
goto bad;
}
KASSERTMSG((*vpp == NULL || (*vpp)->v_type != VLNK ||
(cnp->cn_flags & FOLLOW) == 0),
"relookup: symlink found");
if (rdonly && cnp->cn_nameiop != LOOKUP) {
error = SET_ERROR(EROFS);
if (*vpp) {
vrele(*vpp);
}
goto bad;
}
if (*vpp && *vpp != dvp) {
error = vn_lock(*vpp, LK_EXCLUSIVE);
if (error != 0) {
vrele(*vpp);
goto bad;
}
}
return 0;
bad:
*vpp = NULL;
return error;
}
struct namei_simple_flags_type {
int dummy;
};
static const struct namei_simple_flags_type ns_nn, ns_nt, ns_fn, ns_ft;
const namei_simple_flags_t NSM_NOFOLLOW_NOEMULROOT = &ns_nn;
const namei_simple_flags_t NSM_NOFOLLOW_TRYEMULROOT = &ns_nt;
const namei_simple_flags_t NSM_FOLLOW_NOEMULROOT = &ns_fn;
const namei_simple_flags_t NSM_FOLLOW_TRYEMULROOT = &ns_ft;
static int
namei_simple_convert_flags(namei_simple_flags_t sflags)
{
if (sflags == NSM_NOFOLLOW_NOEMULROOT)
return NOFOLLOW | 0;
if (sflags == NSM_NOFOLLOW_TRYEMULROOT)
return NOFOLLOW | TRYEMULROOT;
if (sflags == NSM_FOLLOW_NOEMULROOT)
return FOLLOW | 0;
if (sflags == NSM_FOLLOW_TRYEMULROOT)
return FOLLOW | TRYEMULROOT;
panic("namei_simple_convert_flags: bogus sflags\n");
return 0;
}
int
namei_simple_kernel(const char *path, namei_simple_flags_t sflags,
struct vnode **vp_ret)
{
return nameiat_simple_kernel(NULL, path, sflags, vp_ret);
}
int
nameiat_simple(struct vnode *dvp, struct pathbuf *pb,
namei_simple_flags_t sflags, struct vnode **vp_ret)
{
struct nameidata nd;
int error;
NDINIT(&nd, LOOKUP, namei_simple_convert_flags(sflags), pb);
if (dvp != NULL)
NDAT(&nd, dvp);
error = namei(&nd);
if (error != 0)
return error;
*vp_ret = nd.ni_vp;
return 0;
}
int
nameiat_simple_kernel(struct vnode *dvp, const char *path,
namei_simple_flags_t sflags, struct vnode **vp_ret)
{
struct pathbuf *pb;
int error;
pb = pathbuf_create(path);
if (pb == NULL)
return SET_ERROR(ENOMEM);
error = nameiat_simple(dvp, pb, sflags, vp_ret);
pathbuf_destroy(pb);
return error;
}
int
namei_simple_user(const char *path, namei_simple_flags_t sflags,
struct vnode **vp_ret)
{
return nameiat_simple_user(NULL, path, sflags, vp_ret);
}
int
nameiat_simple_user(struct vnode *dvp, const char *path,
namei_simple_flags_t sflags, struct vnode **vp_ret)
{
struct pathbuf *pb;
int error;
error = pathbuf_copyin(path, &pb);
if (error)
return error;
error = nameiat_simple(dvp, pb, sflags, vp_ret);
pathbuf_destroy(pb);
return error;
}