#include "opt_ktrace.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/vnode.h>
#include <sys/mount.h>
#include <sys/filedesc.h>
#include <sys/proc.h>
#include <sys/namei.h>
#include <sys/nlookup.h>
#include <sys/malloc.h>
#include <sys/stat.h>
#include <sys/objcache.h>
#include <sys/file.h>
#include <sys/kcollect.h>
#include <sys/sysctl.h>
#ifdef KTRACE
#include <sys/ktrace.h>
#endif
__read_mostly static int nlookup_max_retries = 4;
SYSCTL_INT(_debug, OID_AUTO, nlookup_max_retries, CTLFLAG_RW,
&nlookup_max_retries, 0,
"retries on generation mismatch");
__read_mostly static int nlookup_debug;
SYSCTL_INT(_debug, OID_AUTO, nlookup_debug, CTLFLAG_RW,
&nlookup_debug, 0,
"Force retry test");
static int naccess(struct nlookupdata *nd, struct nchandle *nch,
u_int *genp, int vmode,
struct ucred *cred, int *stickyp, int nchislocked);
static __inline
int
keeperror(struct nlookupdata *nd, int error)
{
if (error) {
if ((nd->nl_flags & NLC_IGNBADDIR) == 0 ||
(error != EIO && error != EBADRPC && error != ESTALE)) {
return 1;
}
}
return 0;
}
int
nlookup_init(struct nlookupdata *nd,
const char *path, enum uio_seg seg, int flags)
{
size_t pathlen;
struct proc *p;
thread_t td;
int error;
td = curthread;
p = td->td_proc;
bzero(nd, sizeof(struct nlookupdata));
nd->nl_path = objcache_get(namei_oc, M_WAITOK);
nd->nl_flags |= NLC_HASBUF;
if (seg == UIO_SYSSPACE)
error = copystr(path, nd->nl_path, MAXPATHLEN, &pathlen);
else
error = copyinstr(path, nd->nl_path, MAXPATHLEN, &pathlen);
if (error == 0 && pathlen <= 1)
error = ENOENT;
if (error == 0) {
if (p && p->p_fd) {
if (nd->nl_path[0] == '/') {
if ((flags & NLC_NLNCH_NOINIT) == 0) {
nd->nl_basench = &p->p_fd->fd_nrdir;
cache_copy(nd->nl_basench, &nd->nl_nch);
}
cache_copy(&p->p_fd->fd_nrdir, &nd->nl_rootnch);
if (p->p_fd->fd_njdir.ncp)
cache_copy(&p->p_fd->fd_njdir, &nd->nl_jailnch);
nd->nl_cred = td->td_ucred;
nd->nl_flags |= NLC_BORROWCRED;
} else {
if ((flags & NLC_NLNCH_NOINIT) == 0) {
nd->nl_basench = &p->p_fd->fd_ncdir;
cache_copy(nd->nl_basench, &nd->nl_nch);
}
cache_copy(&p->p_fd->fd_nrdir, &nd->nl_rootnch);
if (p->p_fd->fd_njdir.ncp)
cache_copy(&p->p_fd->fd_njdir, &nd->nl_jailnch);
nd->nl_cred = td->td_ucred;
nd->nl_flags |= NLC_BORROWCRED;
}
} else {
if ((flags & NLC_NLNCH_NOINIT) == 0) {
nd->nl_basench = &rootnch;
cache_copy(nd->nl_basench, &nd->nl_nch);
}
cache_copy(&rootnch, &nd->nl_rootnch);
cache_copy(&rootnch, &nd->nl_jailnch);
nd->nl_cred = proc0.p_ucred;
nd->nl_flags |= NLC_BORROWCRED;
}
nd->nl_td = td;
nd->nl_flags |= flags & ~NLC_NLNCH_NOINIT;
} else {
nlookup_done(nd);
}
return(error);
}
int
nlookup_init_at(struct nlookupdata *nd, struct file **fpp, int fd,
const char *path, enum uio_seg seg, int flags)
{
struct thread *td = curthread;
struct file* fp;
struct vnode *vp;
int error;
*fpp = NULL;
error = nlookup_init(nd, path, seg, flags | NLC_NLNCH_NOINIT);
if (__predict_false(error))
return (error);
if (nd->nl_path[0] == '/') {
struct proc *p = curproc;
nd->nl_basench = &p->p_fd->fd_nrdir;
} else if (fd == AT_FDCWD) {
struct proc *p = curproc;
nd->nl_basench = &p->p_fd->fd_ncdir;
} else {
if ((error = holdvnode(td, fd, &fp)) != 0)
goto done;
vp = (struct vnode*)fp->f_data;
if (vp->v_type != VDIR || fp->f_nchandle.ncp == NULL) {
fdrop(fp);
fp = NULL;
error = ENOTDIR;
goto done;
}
nd->nl_basench = &fp->f_nchandle;
*fpp = fp;
}
cache_copy(nd->nl_basench, &nd->nl_nch);
done:
if (error)
nlookup_done(nd);
return (error);
}
int
nlookup_init_raw(struct nlookupdata *nd,
const char *path, enum uio_seg seg, int flags,
struct ucred *cred, struct nchandle *ncstart)
{
size_t pathlen;
thread_t td;
int error;
td = curthread;
bzero(nd, sizeof(struct nlookupdata));
nd->nl_path = objcache_get(namei_oc, M_WAITOK);
nd->nl_flags |= NLC_HASBUF;
if (seg == UIO_SYSSPACE)
error = copystr(path, nd->nl_path, MAXPATHLEN, &pathlen);
else
error = copyinstr(path, nd->nl_path, MAXPATHLEN, &pathlen);
if (error == 0 && pathlen <= 1)
error = ENOENT;
if (error == 0) {
cache_copy(ncstart, &nd->nl_nch);
cache_copy(&rootnch, &nd->nl_rootnch);
cache_copy(&rootnch, &nd->nl_jailnch);
nd->nl_cred = crhold(cred);
nd->nl_td = td;
nd->nl_flags |= flags;
} else {
nlookup_done(nd);
}
return(error);
}
int
nlookup_init_root(struct nlookupdata *nd,
const char *path, enum uio_seg seg, int flags,
struct ucred *cred, struct nchandle *ncstart,
struct nchandle *ncroot)
{
size_t pathlen;
thread_t td;
int error;
td = curthread;
bzero(nd, sizeof(struct nlookupdata));
nd->nl_path = objcache_get(namei_oc, M_WAITOK);
nd->nl_flags |= NLC_HASBUF;
if (seg == UIO_SYSSPACE)
error = copystr(path, nd->nl_path, MAXPATHLEN, &pathlen);
else
error = copyinstr(path, nd->nl_path, MAXPATHLEN, &pathlen);
if (error == 0 && pathlen <= 1)
error = ENOENT;
if (error == 0) {
cache_copy(ncstart, &nd->nl_nch);
cache_copy(ncroot, &nd->nl_rootnch);
cache_copy(ncroot, &nd->nl_jailnch);
nd->nl_cred = crhold(cred);
nd->nl_td = td;
nd->nl_flags |= flags;
} else {
nlookup_done(nd);
}
return(error);
}
#if 0
void
nlookup_set_cred(struct nlookupdata *nd, struct ucred *cred)
{
KKASSERT(nd->nl_cred != NULL);
if (nd->nl_cred != cred) {
cred = crhold(cred);
if ((nd->nl_flags & NLC_BORROWCRED) == 0)
crfree(nd->nl_cred);
nd->nl_flags &= ~NLC_BORROWCRED;
nd->nl_cred = cred;
}
}
#endif
void
nlookup_done(struct nlookupdata *nd)
{
if (nd->nl_nch.ncp) {
if (nd->nl_flags & NLC_NCPISLOCKED)
cache_unlock(&nd->nl_nch);
cache_drop_and_cache(&nd->nl_nch, nd->nl_elmno);
}
nd->nl_flags &= ~NLC_NCPISLOCKED;
if (nd->nl_rootnch.ncp)
cache_drop_and_cache(&nd->nl_rootnch, 0);
if (nd->nl_jailnch.ncp)
cache_drop_and_cache(&nd->nl_jailnch, 0);
if ((nd->nl_flags & NLC_HASBUF) && nd->nl_path) {
objcache_put(namei_oc, nd->nl_path);
nd->nl_path = NULL;
}
if (nd->nl_cred) {
if ((nd->nl_flags & NLC_BORROWCRED) == 0)
crfree(nd->nl_cred);
nd->nl_cred = NULL;
nd->nl_flags &= ~NLC_BORROWCRED;
}
if (nd->nl_open_vp) {
if (nd->nl_flags & NLC_LOCKVP) {
vn_unlock(nd->nl_open_vp);
nd->nl_flags &= ~NLC_LOCKVP;
}
vn_close(nd->nl_open_vp, nd->nl_vp_fmode, NULL);
nd->nl_open_vp = NULL;
}
if (nd->nl_dvp) {
vrele(nd->nl_dvp);
nd->nl_dvp = NULL;
}
nd->nl_flags = 0;
nd->nl_basench = NULL;
}
void
nlookup_done_at(struct nlookupdata *nd, struct file *fp)
{
nlookup_done(nd);
if (fp != NULL)
fdrop(fp);
}
void
nlookup_zero(struct nlookupdata *nd)
{
bzero(nd, sizeof(struct nlookupdata));
}
struct nchandle
nlookup_simple(const char *str, enum uio_seg seg,
int niflags, int *error)
{
struct nlookupdata nd;
struct nchandle nch;
*error = nlookup_init(&nd, str, seg, niflags);
if (*error == 0) {
if ((*error = nlookup(&nd)) == 0) {
nch = nd.nl_nch;
cache_zero(&nd.nl_nch);
} else {
cache_zero(&nch);
}
nlookup_done(&nd);
} else {
cache_zero(&nch);
}
return(nch);
}
static
int
islastelement(const char *ptr)
{
while (*ptr == '/')
++ptr;
return (*ptr == 0);
}
static __inline
int
wantsexcllock(struct nlookupdata *nd, int last_element)
{
if ((nd->nl_flags & NLC_SHAREDLOCK) == 0)
return(last_element);
return 0;
}
int
nlookup(struct nlookupdata *nd)
{
globaldata_t gd = mycpu;
struct nlcomponent nlc;
struct nchandle nch;
struct nchandle nctmp;
struct mount *mp;
int wasdotordotdot;
char *path_reset;
char *ptr;
char *nptr;
int error;
int len;
int dflags;
int hit = 1;
int saveflag = nd->nl_flags;
int max_retries = nlookup_max_retries;
u_int nl_gen;
u_int nch_gen;
int gen_changed;
boolean_t doretry = FALSE;
boolean_t inretry = FALSE;
path_reset = NULL;
nlookup_start:
#ifdef KTRACE
if (KTRPOINT(nd->nl_td, KTR_NAMEI))
ktrnamei(nd->nl_td->td_lwp, nd->nl_path);
#endif
bzero(&nlc, sizeof(nlc));
nd->nl_loopcnt = 0;
nd->nl_dir_error = 0;
if (nd->nl_dvp) {
vrele(nd->nl_dvp);
nd->nl_dvp = NULL;
}
ptr = nd->nl_path;
nl_gen = nd->nl_nch.ncp ? nd->nl_nch.ncp->nc_generation : 0;
nl_gen &= ~3;
gen_changed = 0;
for (;;) {
int last_element;
++nd->nl_elmno;
KKASSERT((nd->nl_flags & NLC_NCPISLOCKED) == 0);
if (*ptr == '/') {
do {
++ptr;
} while (*ptr == '/');
if (nd->nl_nch.mount != nd->nl_rootnch.mount ||
nd->nl_nch.ncp != nd->nl_rootnch.ncp) {
cache_drop_and_cache(&nd->nl_nch, 0);
cache_copy(&nd->nl_rootnch, &nd->nl_nch);
nl_gen = nd->nl_nch.ncp->nc_generation & ~3;
}
if (*ptr == 0) {
if (nd->nl_flags & NLC_REFDVP)
error = (nd->nl_flags & NLC_CREATE) ? EEXIST : EACCES;
else
error = 0;
nd->nl_flags |= NLC_NCPISLOCKED;
cache_lock_maybe_shared(&nd->nl_nch,
wantsexcllock(nd, islastelement(ptr)));
break;
}
continue;
}
for (nptr = ptr; *nptr && *nptr != '/'; ++nptr)
;
dflags = 0;
if (*nptr == '/' || (saveflag & NLC_MODIFYING_MASK) == 0) {
error = naccess(nd, &nd->nl_nch, &nl_gen, NLC_EXEC,
nd->nl_cred, NULL, 0);
} else {
error = naccess(nd, &nd->nl_nch, &nl_gen, NLC_EXEC,
nd->nl_cred, &dflags, 0);
}
if (error) {
if (keeperror(nd, error))
break;
error = 0;
}
nlc.nlc_nameptr = ptr;
nlc.nlc_namelen = nptr - ptr;
ptr = nptr;
if (nlc.nlc_namelen >= 256) {
error = ENAMETOOLONG;
break;
}
last_element = islastelement(nptr);
KKASSERT((nd->nl_flags & NLC_NCPISLOCKED) == 0);
if (nlc.nlc_namelen == 1 && nlc.nlc_nameptr[0] == '.') {
if (last_element) {
cache_get_maybe_shared(&nd->nl_nch, &nch,
wantsexcllock(nd, 1));
} else {
cache_copy(&nd->nl_nch, &nch);
}
nch_gen = nch.ncp->nc_generation & ~3;
wasdotordotdot = 1;
} else if (nlc.nlc_namelen == 2 &&
nlc.nlc_nameptr[0] == '.' && nlc.nlc_nameptr[1] == '.') {
if (nd->nl_nch.mount == nd->nl_rootnch.mount &&
nd->nl_nch.ncp == nd->nl_rootnch.ncp
) {
if (last_element) {
cache_get_maybe_shared(&nd->nl_nch, &nch,
wantsexcllock(nd, 1));
} else {
cache_copy(&nd->nl_nch, &nch);
}
} else {
nctmp = nd->nl_nch;
while (nctmp.ncp == nctmp.mount->mnt_ncmountpt.ncp) {
nctmp = nctmp.mount->mnt_ncmounton;
if (nctmp.ncp == NULL)
break;
}
if (nctmp.ncp == NULL) {
if (curthread->td_proc) {
kprintf("vfs_nlookup: '..' traverse broke "
"jail: pid %d (%s)\n",
curthread->td_proc->p_pid,
curthread->td_comm);
}
nctmp = nd->nl_rootnch;
} else {
nctmp.ncp = nctmp.ncp->nc_parent;
}
if (last_element) {
cache_get_maybe_shared(&nctmp, &nch,
wantsexcllock(nd, 1));
} else {
cache_copy(&nctmp, &nch);
}
}
nch_gen = nch.ncp->nc_generation & ~3;
wasdotordotdot = 2;
} else {
if (last_element) {
error = cache_nlookup_maybe_shared(&nd->nl_nch, &nlc,
wantsexcllock(nd, 1),
&nch);
} else {
nch = cache_nlookup_nonlocked(&nd->nl_nch, &nlc);
if (nch.ncp == NULL)
error = EWOULDBLOCK;
}
if (error == EWOULDBLOCK) {
nch = cache_nlookup(&nd->nl_nch, &nlc);
if (nch.ncp->nc_flag & NCF_UNRESOLVED)
hit = 0;
for (;;) {
error = cache_resolve(&nch, &nch_gen, nd->nl_cred);
if (error != EAGAIN &&
(nch.ncp->nc_flag & NCF_DESTROYED) == 0) {
if (error == ESTALE) {
if (!inretry)
error = ENOENT;
doretry = TRUE;
}
if (last_element == 0)
cache_unlock(&nch);
break;
}
kprintf("[diagnostic] nlookup: relookup %*.*s\n",
nch.ncp->nc_nlen, nch.ncp->nc_nlen,
nch.ncp->nc_name);
cache_put(&nch);
nch = cache_nlookup(&nd->nl_nch, &nlc);
}
}
nch_gen = nch.ncp->nc_generation & ~3;
wasdotordotdot = 0;
}
if (wasdotordotdot && error == 0) {
struct nchandle par;
dflags = 0;
if (last_element == 0)
cache_lock_maybe_shared(&nch, wantsexcllock(nd, 0));
if ((par.ncp = nch.ncp->nc_parent) != NULL) {
u_int dummy_gen = 0;
par.mount = nch.mount;
cache_hold(&par);
error = naccess(nd, &par, &dummy_gen, 0, nd->nl_cred, &dflags, 0);
cache_drop_and_cache(&par, nd->nl_elmno - 1);
if (error) {
if (!keeperror(nd, error))
error = 0;
if (error == EINVAL) {
kprintf("nlookup (%s): trailing . or .. retry on %s\n",
curthread->td_comm, nd->nl_path);
doretry = TRUE;
}
}
}
if (last_element == 0)
cache_unlock(&nch);
}
KKASSERT((nd->nl_flags & NLC_NCPISLOCKED) == 0);
if (nch.ncp->nc_flag & NCF_UNRESOLVED) {
if (last_element == 0)
cache_lock(&nch);
hit = 0;
error = cache_resolve(&nch, &nch_gen, nd->nl_cred);
if (error == ESTALE) {
if (!inretry)
error = ENOENT;
doretry = TRUE;
}
if (last_element == 0)
cache_unlock(&nch);
KKASSERT(error != EAGAIN);
} else {
error = nch.ncp->nc_error;
}
if (last_element) {
if (error == ENOENT &&
(nd->nl_flags & (NLC_CREATE | NLC_RENAME_DST)))
{
if (nd->nl_flags & NLC_NFS_RDONLY) {
error = EROFS;
} else {
error = naccess(nd, &nch, &nch_gen,
nd->nl_flags | dflags,
nd->nl_cred, NULL, last_element);
}
}
if (error == 0 && wasdotordotdot &&
(nd->nl_flags & (NLC_CREATE | NLC_DELETE |
NLC_RENAME_SRC | NLC_RENAME_DST)))
{
if (nd->nl_flags & NLC_CREATE)
error = EEXIST;
else if (nd->nl_flags & NLC_DELETE)
error = (wasdotordotdot == 1) ? EINVAL : ENOTEMPTY;
else
error = EINVAL;
}
}
if (error) {
if (last_element)
cache_unlock(&nch);
cache_drop_and_cache(&nch, nd->nl_elmno);
break;
}
if ((nch.ncp->nc_flag & NCF_ISSYMLINK) &&
(*ptr || (nd->nl_flags & NLC_FOLLOW))
) {
if (nd->nl_loopcnt++ >= MAXSYMLINKS) {
error = ELOOP;
if (last_element)
cache_unlock(&nch);
cache_drop_and_cache(&nch, nd->nl_elmno);
break;
}
if (last_element == 0)
cache_lock_maybe_shared(&nch, 1);
if ((nch.ncp->nc_generation - nch_gen) & ~1) {
if (nlookup_debug & 1) {
kprintf("nlookup: symlink: GEN CHANGE %d\n",
(nch.ncp->nc_generation - nch_gen));
}
gen_changed = 1;
}
error = nreadsymlink(nd, &nch, &nlc);
cache_put(&nch);
if (error)
break;
len = strlen(ptr);
if (nlc.nlc_namelen == 0 || nlc.nlc_namelen + len >= MAXPATHLEN) {
error = nlc.nlc_namelen ? ENAMETOOLONG : ENOENT;
objcache_put(namei_oc, nlc.nlc_nameptr);
break;
}
bcopy(ptr, nlc.nlc_nameptr + nlc.nlc_namelen, len + 1);
if (path_reset) {
if (nd->nl_flags & NLC_HASBUF)
objcache_put(namei_oc, nd->nl_path);
} else {
path_reset = nd->nl_path;
}
nd->nl_path = nlc.nlc_nameptr;
nd->nl_flags |= NLC_HASBUF;
ptr = nd->nl_path;
continue;
}
while ((nch.ncp->nc_flag & NCF_ISMOUNTPT) &&
(nd->nl_flags & NLC_NOCROSSMOUNT) == 0 &&
(mp = cache_findmount(&nch)) != NULL
) {
struct vnode *tdp;
int vfs_do_busy = 0;
again:
if ((nch.ncp->nc_generation - nch_gen) & ~1) {
if (last_element == 0) {
cache_lock_maybe_shared(&nch, 1);
cache_unlock(&nch);
}
if (nlookup_debug & 1) {
kprintf("nlookup: mountpt: GEN CHANGE %d\n",
(nch.ncp->nc_generation - nch_gen));
}
gen_changed = 1;
}
if (last_element)
cache_unlock(&nch);
cache_drop_and_cache(&nch, nd->nl_elmno);
if (vfs_do_busy) {
while (vfs_busy(mp, 0)) {
if (mp->mnt_kern_flag & MNTK_UNMOUNT) {
kprintf("nlookup: warning umount race avoided\n");
cache_dropmount(mp);
error = EBUSY;
vfs_do_busy = 0;
goto double_break;
}
}
}
if (last_element)
cache_get_maybe_shared(&mp->mnt_ncmountpt, &nch,
wantsexcllock(nd, 1));
else
cache_copy(&mp->mnt_ncmountpt, &nch);
nch_gen = nch.ncp->nc_generation & ~3;
if (nch.ncp->nc_flag & NCF_UNRESOLVED) {
if (last_element == 0)
cache_lock(&nch);
if (nch.ncp->nc_flag & NCF_UNRESOLVED) {
if (vfs_do_busy == 0) {
vfs_do_busy = 1;
if (last_element == 0)
cache_unlock(&nch);
goto again;
}
error = VFS_ROOT(mp, &tdp);
vfs_unbusy(mp);
vfs_do_busy = 0;
if (keeperror(nd, error)) {
cache_dropmount(mp);
if (last_element == 0)
cache_unlock(&nch);
break;
}
if (error == 0) {
cache_setvp(&nch, tdp);
nch_gen = nch.ncp->nc_generation & ~3;
vput(tdp);
}
}
if (last_element == 0)
cache_unlock(&nch);
}
if (vfs_do_busy)
vfs_unbusy(mp);
cache_dropmount(mp);
}
if (keeperror(nd, error)) {
if (last_element)
cache_unlock(&nch);
cache_drop_and_cache(&nch, nd->nl_elmno);
double_break:
break;
}
while (*ptr == '/') {
if ((nch.ncp->nc_flag & NCF_ISDIR) == 0 &&
!(nd->nl_flags & NLC_WILLBEDIR)
) {
break;
}
++ptr;
}
if (*ptr && (nch.ncp->nc_flag & NCF_ISDIR)) {
if ((nch.ncp->nc_generation - nch_gen) & ~1) {
if (last_element == 0) {
cache_lock_maybe_shared(&nch, 1);
cache_unlock(&nch);
}
if (nlookup_debug & 1) {
kprintf("nlookup: next: GEN CHANGE %d\n",
(nch.ncp->nc_generation - nch_gen));
}
gen_changed = 1;
}
cache_drop_and_cache(&nd->nl_nch, nd->nl_elmno);
if (last_element)
cache_unlock(&nch);
KKASSERT((nd->nl_flags & NLC_NCPISLOCKED) == 0);
nd->nl_nch = nch;
nl_gen = nch_gen;
continue;
}
if ((nch.ncp->nc_generation - nch_gen) & ~1) {
if (nlookup_debug & 1) {
if (last_element == 0) {
cache_lock_maybe_shared(&nch, 1);
cache_unlock(&nch);
}
kprintf("nlookup: final: GEN CHANGE %d\n",
(nch.ncp->nc_generation - nch_gen));
gen_changed = 1;
}
}
if (*ptr) {
if (last_element)
cache_unlock(&nch);
cache_drop_and_cache(&nch, nd->nl_elmno);
error = ENOTDIR;
break;
}
KKASSERT(last_element);
if (nch.ncp->nc_vp && (nd->nl_flags & NLC_ALLCHKS)) {
error = naccess(nd, &nch, &nch_gen, nd->nl_flags | dflags,
nd->nl_cred, NULL, 1);
if (keeperror(nd, error)) {
cache_put(&nch);
break;
}
}
if (nch.ncp->nc_parent &&
(nch.ncp->nc_parent->nc_flag & NCF_DESTROYED)) {
doretry = TRUE;
}
if ((nd->nl_flags & NLC_REFDVP) &&
(doretry == FALSE || inretry == TRUE)) {
if (nch.ncp->nc_parent) {
error = cache_resolve_dvp(&nch, nd->nl_cred,
&nd->nl_dvp);
if (error) {
if (nlookup_debug & 1) {
kprintf("Parent directory lost during "
"nlookup: %s/%s (%08x/%08x)\n",
nch.ncp->nc_parent->nc_name,
nch.ncp->nc_name,
nch.ncp->nc_parent->nc_flag,
nch.ncp->nc_flag);
}
cache_put(&nch);
error = EINVAL;
break;
}
if (nd->nl_dvp == NULL) {
cache_put(&nch);
break;
}
} else {
error = 0;
cache_put(&nch);
break;
}
}
cache_drop_and_cache(&nd->nl_nch, nd->nl_elmno);
nd->nl_nch = nch;
nd->nl_flags |= NLC_NCPISLOCKED;
nl_gen = nch_gen;
error = 0;
break;
}
if (nd->nl_nch.ncp && (nd->nl_nch.ncp->nc_generation - nl_gen) & ~1) {
if (nlookup_debug & 1) {
kprintf("nlookup: DONE error %d: GEN CHANGE ON \"%s\" "
"%d (retries %d)\n",
error,
nd->nl_nch.ncp->nc_name,
(nd->nl_nch.ncp->nc_generation - nl_gen),
max_retries);
}
gen_changed = 1;
}
if (gen_changed) {
if (max_retries) {
--max_retries;
doretry = TRUE;
inretry = FALSE;
} else {
error = EINVAL;
}
}
if (hit)
++gd->gd_nchstats->ncs_longhits;
else
++gd->gd_nchstats->ncs_longmiss;
if (nd->nl_flags & NLC_NCPISLOCKED)
KKASSERT(cache_lockstatus(&nd->nl_nch) > 0);
if (path_reset) {
if (nd->nl_flags & NLC_HASBUF) {
objcache_put(namei_oc, nd->nl_path);
nd->nl_flags &= ~NLC_HASBUF;
}
nd->nl_path = path_reset;
nd->nl_flags |= saveflag & NLC_HASBUF;
path_reset = NULL;
}
if (doretry && !inretry) {
if (nlookup_debug & 2)
kprintf("nlookup: errno %d retry %s\n", error, nd->nl_path);
inretry = TRUE;
if (nd->nl_flags & NLC_NCPISLOCKED) {
cache_unlock(&nd->nl_nch);
nd->nl_flags &= ~NLC_NCPISLOCKED;
}
cache_drop(&nd->nl_nch);
cache_copy(nd->nl_basench, &nd->nl_nch);
nd->nl_elmno = 0;
nd->nl_flags |= saveflag;
goto nlookup_start;
}
return(error);
}
int
nlookup_mp(struct mount *mp, struct nchandle *nch)
{
struct vnode *vp;
int error;
error = 0;
cache_get(&mp->mnt_ncmountpt, nch);
if (nch->ncp->nc_flag & NCF_UNRESOLVED) {
while (vfs_busy(mp, 0))
;
error = VFS_ROOT(mp, &vp);
vfs_unbusy(mp);
if (error) {
cache_put(nch);
} else {
cache_setvp(nch, vp);
vput(vp);
}
}
return(error);
}
int
nreadsymlink(struct nlookupdata *nd, struct nchandle *nch,
struct nlcomponent *nlc)
{
struct vnode *vp;
struct iovec aiov;
struct uio auio;
int linklen;
int error;
char *cp;
nlc->nlc_nameptr = NULL;
nlc->nlc_namelen = 0;
if (nch->ncp->nc_vp == NULL)
return(ENOENT);
if ((error = cache_vget(nch, nd->nl_cred, LK_SHARED, &vp)) != 0)
return(error);
cp = objcache_get(namei_oc, M_WAITOK);
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_segflg = UIO_SYSSPACE;
auio.uio_td = nd->nl_td;
auio.uio_resid = MAXPATHLEN - 1;
error = VOP_READLINK(vp, &auio, nd->nl_cred);
if (error)
goto fail;
linklen = MAXPATHLEN - 1 - auio.uio_resid;
if (varsym_enable) {
linklen = varsymreplace(cp, linklen, MAXPATHLEN - 1);
if (linklen < 0) {
error = ENAMETOOLONG;
goto fail;
}
}
cp[linklen] = 0;
nlc->nlc_nameptr = cp;
nlc->nlc_namelen = linklen;
vput(vp);
return(0);
fail:
objcache_put(namei_oc, cp);
vput(vp);
return(error);
}
#define S_WXOK_MASK (S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
#define S_XOK_MASK (S_IXUSR|S_IXGRP|S_IXOTH)
static int
naccess(struct nlookupdata *nd, struct nchandle *nch, u_int *genp, int nflags,
struct ucred *cred, int *nflagsp, int nchislocked)
{
struct vnode *vp;
struct vattr_lite lva;
struct namecache *ncp;
int error;
int cflags;
ncp = nch->ncp;
again:
if (ncp->nc_flag & NCF_UNRESOLVED) {
if (nchislocked == 0) {
cache_lock(nch);
nchislocked = 2;
}
cache_resolve(nch, genp, cred);
ncp = nch->ncp;
}
error = ncp->nc_error;
if (error == ENOTCONN) {
if (nchislocked == 0) {
if (nlookup_debug & 4) {
kprintf("ncp %p %08x %d %s: Warning, unexpected state, "
"forcing lock\n",
ncp, ncp->nc_flag, ncp->nc_error, ncp->nc_name);
print_backtrace(-1);
}
cache_lock(nch);
nchislocked = 2;
goto again;
}
if (nlookup_debug & 4) {
kprintf("ncp %p %08x %d %s: Warning, unexpected state\n",
ncp, ncp->nc_flag, ncp->nc_error, ncp->nc_name);
print_backtrace(-1);
}
}
if (nflags & (NLC_CREATE | NLC_DELETE | NLC_RENAME_SRC | NLC_RENAME_DST)) {
if (((nflags & NLC_CREATE) && ncp->nc_vp == NULL) ||
((nflags & NLC_DELETE) && ncp->nc_vp != NULL) ||
((nflags & NLC_RENAME_SRC) && ncp->nc_vp != NULL) ||
(nflags & NLC_RENAME_DST)
) {
struct nchandle par;
if (nchislocked == 0) {
cache_lock_maybe_shared(nch, 0);
nchislocked = 2;
goto again;
}
if ((par.ncp = ncp->nc_parent) == NULL) {
if (error != EAGAIN) {
error = EINVAL;
++nd->nl_dir_error;
}
} else if (error == 0 || error == ENOENT) {
u_int dummy_gen = 0;
par.mount = nch->mount;
cache_hold(&par);
cache_lock_maybe_shared(&par, 0);
error = naccess(nd, &par, &dummy_gen, NLC_WRITE, cred, NULL, 1);
cache_put(&par);
if (error)
++nd->nl_dir_error;
}
}
}
if (error == 0 && (nflags & NLC_EXCL) && ncp->nc_vp != NULL)
error = EEXIST;
if (error == 0 && nflagsp == NULL &&
nflags == NLC_EXEC && (ncp->nc_flag & NCF_WXOK)) {
if (nchislocked == 2)
cache_unlock(nch);
return 0;
}
if (error == 0) {
if (nchislocked == 0) {
cache_lock_maybe_shared(nch, 0);
nchislocked = 2;
}
#if 0
error = cache_vget(nch, cred, LK_SHARED, &vp);
#else
error = cache_vref(nch, cred, &vp);
#endif
if (error == ENOENT) {
if (nflags & (NLC_CREATE | NLC_RENAME_DST))
error = 0;
} else if (error == 0) {
error = VOP_GETATTR_LITE(vp, &lva);
if (error == 0 && (nflags & NLC_TRUNCATE)) {
switch(lva.va_type) {
case VREG:
case VDATABASE:
case VCHR:
case VBLK:
case VFIFO:
break;
case VDIR:
error = EISDIR;
break;
default:
error = EINVAL;
break;
}
}
if (error == 0 && (nflags & NLC_WRITE) && vp->v_mount &&
(vp->v_mount->mnt_flag & MNT_RDONLY)
) {
switch(lva.va_type) {
case VDIR:
case VLNK:
case VREG:
case VDATABASE:
error = EROFS;
break;
default:
break;
}
}
#if 0
vput(vp);
#else
vrele(vp);
#endif
if (error == 0) {
if (nflagsp) {
if ((lva.va_mode & VSVTX) && lva.va_uid != cred->cr_uid)
*nflagsp |= NLC_STICKY;
if (lva.va_flags & APPEND)
*nflagsp |= NLC_APPENDONLY;
if (lva.va_flags & IMMUTABLE)
*nflagsp |= NLC_IMMUTABLE;
}
cflags = 0;
if (lva.va_type == VDIR &&
(lva.va_mode & S_WXOK_MASK) == S_WXOK_MASK) {
cflags |= NCF_WXOK;
}
if ((lva.va_mode & S_XOK_MASK) == 0)
cflags |= NCF_NOTX;
if (lva.va_flags & SF_NOCACHE)
cflags |= NCF_SF_NOCACHE;
if (lva.va_flags & UF_CACHE)
cflags |= NCF_UF_CACHE;
if (ncp->nc_parent) {
if (ncp->nc_parent->nc_flag &
(NCF_SF_NOCACHE | NCF_SF_PNOCACHE)) {
cflags |= NCF_SF_PNOCACHE;
}
if (ncp->nc_parent->nc_flag &
(NCF_UF_CACHE | NCF_UF_PCACHE)) {
cflags |= NCF_UF_PCACHE;
}
}
atomic_clear_short(&ncp->nc_flag,
(NCF_SF_NOCACHE | NCF_UF_CACHE |
NCF_SF_PNOCACHE | NCF_UF_PCACHE |
NCF_WXOK | NCF_NOTX) & ~cflags);
atomic_set_short(&ncp->nc_flag, cflags);
error = naccess_lva(&lva, nflags, cred);
}
}
}
if (nchislocked == 2)
cache_unlock(nch);
return(error);
}
int
naccess_lva(struct vattr_lite *lvap, int nflags, struct ucred *cred)
{
int i;
int vmode;
if ((lvap->va_flags & IMMUTABLE) || (nflags & NLC_IMMUTABLE)) {
if ((nflags & NLC_IMMUTABLE) && (nflags & NLC_HLINK))
return (EPERM);
if (nflags & (NLC_CREATE | NLC_DELETE |
NLC_RENAME_SRC | NLC_RENAME_DST)) {
return (EPERM);
}
if (nflags & (NLC_WRITE | NLC_TRUNCATE)) {
switch(lvap->va_type) {
case VDIR:
return (EISDIR);
case VLNK:
case VREG:
case VDATABASE:
return (EPERM);
default:
break;
}
}
}
if (((lvap->va_flags & NOUNLINK) || (nflags & NLC_APPENDONLY)) &&
(nflags & (NLC_DELETE | NLC_RENAME_SRC | NLC_RENAME_DST))
) {
return (EPERM);
}
if ((lvap->va_flags & APPEND) &&
(nflags & (NLC_DELETE | NLC_RENAME_DST))
) {
return (EPERM);
}
if ((lvap->va_flags & APPEND) && (nflags & (NLC_OPEN | NLC_TRUNCATE))) {
if (nflags & NLC_TRUNCATE)
return (EPERM);
if ((nflags & (NLC_OPEN | NLC_WRITE)) == (NLC_OPEN | NLC_WRITE)) {
if ((nflags & NLC_APPEND) == 0)
return (EPERM);
}
}
if (cred->cr_uid == 0)
return(0);
vmode = 0;
if (nflags & NLC_READ)
vmode |= S_IRUSR;
if (nflags & NLC_WRITE)
vmode |= S_IWUSR;
if (nflags & NLC_EXEC)
vmode |= S_IXUSR;
if (cred->cr_uid == lvap->va_uid) {
if ((nflags & NLC_OWN) == 0) {
if ((vmode & lvap->va_mode) != vmode)
return(EACCES);
}
return(0);
}
if ((nflags & NLC_STICKY) &&
(nflags & (NLC_RENAME_SRC | NLC_RENAME_DST | NLC_DELETE))) {
return(EACCES);
}
vmode >>= 3;
for (i = 0; i < cred->cr_ngroups; ++i) {
if (lvap->va_gid == cred->cr_groups[i]) {
if ((vmode & lvap->va_mode) != vmode)
return(EACCES);
return(0);
}
}
vmode >>= 3;
if ((vmode & lvap->va_mode) != vmode)
return(EACCES);
return(0);
}
static
uint64_t
collect_nlookup_callback(int n)
{
static uint64_t last_total;
uint64_t save;
uint64_t total;
total = 0;
for (n = 0; n < ncpus; ++n) {
globaldata_t gd = globaldata_find(n);
struct nchstats *sp;
if ((sp = gd->gd_nchstats) != NULL)
total += sp->ncs_longhits + sp->ncs_longmiss;
}
save = total;
total = total - last_total;
last_total = save;
return total;
}
static
void
nlookup_collect_init(void *dummy __unused)
{
kcollect_register(KCOLLECT_NLOOKUP, "nlookup", collect_nlookup_callback,
KCOLLECT_SCALE(KCOLLECT_NLOOKUP_FORMAT, 0));
}
SYSINIT(collect_nlookup, SI_SUB_PROP, SI_ORDER_ANY, nlookup_collect_init, 0);