#include <sys/param.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/sysmsg.h>
#include <sys/spinlock.h>
#include <sys/proc.h>
#include <sys/nlookup.h>
#include <sys/filedesc.h>
#include <sys/fnv_hash.h>
#include <sys/globaldata.h>
#include <sys/kern_syscall.h>
#include <sys/dirent.h>
#include <ddb/ddb.h>
#include <sys/spinlock2.h>
#define MAX_RECURSION_DEPTH 64
#define NCHHASH(hash) (&nchashtbl[(hash) & nchash])
#define MINNEG 1024
#define MINPOS 1024
#define NCMOUNT_NUMCACHE (16384)
#define NCMOUNT_SET (8)
MALLOC_DEFINE_OBJ(M_VFSCACHE, sizeof(struct namecache),
"namecache", "namecache entries");
MALLOC_DEFINE(M_VFSCACHEAUX, "namecachestr", "namecache strings");
TAILQ_HEAD(nchash_list, namecache);
struct nchash_head {
struct nchash_list list;
struct spinlock spin;
long pad01;
};
struct ncmount_cache {
struct spinlock spin;
struct namecache *ncp;
struct mount *mp;
struct mount *mp_target;
int isneg;
int ticks;
int updating;
int unused01;
};
struct pcpu_ncache {
struct spinlock umount_spin;
struct spinlock neg_spin;
struct namecache_list neg_list;
long neg_count;
long vfscache_negs;
long vfscache_count;
long vfscache_leafs;
long vfscache_unres;
long numdefered;
long inv_kid_quick_count;
long inv_ncp_quick_count;
long clean_pos_count;
long clean_neg_count;
} __cachealign;
__read_mostly static struct nchash_head *nchashtbl;
__read_mostly static struct pcpu_ncache *pcpu_ncache;
static struct ncmount_cache ncmount_cache[NCMOUNT_NUMCACHE];
__read_mostly int ncvp_debug;
SYSCTL_INT(_debug, OID_AUTO, ncvp_debug, CTLFLAG_RW, &ncvp_debug, 0,
"Namecache debug level (0-3)");
__read_mostly static u_long nchash;
SYSCTL_ULONG(_debug, OID_AUTO, nchash, CTLFLAG_RD, &nchash, 0,
"Size of namecache hash table");
__read_mostly static int ncnegflush = 10;
SYSCTL_INT(_debug, OID_AUTO, ncnegflush, CTLFLAG_RW, &ncnegflush, 0,
"Batch flush negative entries");
__read_mostly static int ncposflush = 10;
SYSCTL_INT(_debug, OID_AUTO, ncposflush, CTLFLAG_RW, &ncposflush, 0,
"Batch flush positive entries");
__read_mostly static int ncnegfactor = 16;
SYSCTL_INT(_debug, OID_AUTO, ncnegfactor, CTLFLAG_RW, &ncnegfactor, 0,
"Ratio of negative namecache entries");
__read_mostly static int ncposfactor = 16;
SYSCTL_INT(_debug, OID_AUTO, ncposfactor, CTLFLAG_RW, &ncposfactor, 0,
"Ratio of unresolved leaf namecache entries");
__read_mostly static int nclockwarn;
SYSCTL_INT(_debug, OID_AUTO, nclockwarn, CTLFLAG_RW, &nclockwarn, 0,
"Warn on locked namecache entries in ticks");
__read_mostly static int ncposlimit;
SYSCTL_INT(_debug, OID_AUTO, ncposlimit, CTLFLAG_RW, &ncposlimit, 0,
"Number of cache entries allocated");
__read_mostly static int ncp_shared_lock_disable = 0;
SYSCTL_INT(_debug, OID_AUTO, ncp_shared_lock_disable, CTLFLAG_RW,
&ncp_shared_lock_disable, 0, "Disable shared namecache locks");
SYSCTL_INT(_debug, OID_AUTO, vnsize, CTLFLAG_RD, 0, sizeof(struct vnode),
"sizeof(struct vnode)");
SYSCTL_INT(_debug, OID_AUTO, ncsize, CTLFLAG_RD, 0, sizeof(struct namecache),
"sizeof(struct namecache)");
__read_mostly static int ncmount_cache_enable = 1;
SYSCTL_INT(_debug, OID_AUTO, ncmount_cache_enable, CTLFLAG_RW,
&ncmount_cache_enable, 0, "mount point cache");
static void _cache_drop(struct namecache *ncp);
static int cache_resolve_mp(struct mount *mp, int adjgen);
static int cache_findmount_callback(struct mount *mp, void *data);
static void _cache_setunresolved(struct namecache *ncp, int adjgen);
static void _cache_cleanneg(long count);
static void _cache_cleanpos(long ucount, long xcount);
static void _cache_cleandefered(void);
static void _cache_unlink(struct namecache *ncp);
SYSCTL_NODE(_vfs, OID_AUTO, cache, CTLFLAG_RW, 0, "Name cache statistics");
static long vfscache_negs;
SYSCTL_LONG(_vfs_cache, OID_AUTO, numneg, CTLFLAG_RD, &vfscache_negs, 0,
"Number of negative namecache entries");
static long vfscache_count;
SYSCTL_LONG(_vfs_cache, OID_AUTO, numcache, CTLFLAG_RD, &vfscache_count, 0,
"Number of namecaches entries");
static long vfscache_leafs;
SYSCTL_LONG(_vfs_cache, OID_AUTO, numleafs, CTLFLAG_RD, &vfscache_leafs, 0,
"Number of leaf namecaches entries");
static long vfscache_unres;
SYSCTL_LONG(_vfs_cache, OID_AUTO, numunres, CTLFLAG_RD, &vfscache_unres, 0,
"Number of unresolved leaf namecaches entries");
static long inv_kid_quick_count;
SYSCTL_LONG(_vfs_cache, OID_AUTO, inv_kid_quick_count, CTLFLAG_RD,
&inv_kid_quick_count, 0,
"quick kid invalidations");
static long inv_ncp_quick_count;
SYSCTL_LONG(_vfs_cache, OID_AUTO, inv_ncp_quick_count, CTLFLAG_RD,
&inv_ncp_quick_count, 0,
"quick ncp invalidations");
static long clean_pos_count;
SYSCTL_LONG(_vfs_cache, OID_AUTO, clean_pos_count, CTLFLAG_RD,
&clean_pos_count, 0,
"positive ncp cleanings");
static long clean_neg_count;
SYSCTL_LONG(_vfs_cache, OID_AUTO, clean_neg_count, CTLFLAG_RD,
&clean_neg_count, 0,
"negative ncp cleanings");
static long numdefered;
SYSCTL_LONG(_debug, OID_AUTO, numdefered, CTLFLAG_RD, &numdefered, 0,
"Number of cache entries allocated");
static __inline int
ncpbaserefs(struct namecache *ncp)
{
return (1 + ((ncp->nc_flag & NCF_UNRESOLVED) == 0));
}
struct nchstats nchstats[SMP_MAXCPU];
static int
sysctl_nchstats(SYSCTL_HANDLER_ARGS)
{
struct globaldata *gd;
int i, error;
error = 0;
for (i = 0; i < ncpus; ++i) {
gd = globaldata_find(i);
if ((error = SYSCTL_OUT(req, (void *)&(*gd->gd_nchstats),
sizeof(struct nchstats))))
break;
}
return (error);
}
SYSCTL_PROC(_vfs_cache, OID_AUTO, nchstats, CTLTYPE_OPAQUE|CTLFLAG_RD,
0, 0, sysctl_nchstats, "S,nchstats", "VFS cache effectiveness statistics");
static int cache_zap(struct namecache *ncp);
#define MNTCACHE_COUNT 32
#define MNTCACHE_SET 8
struct mntcache_elm {
struct namecache *ncp;
struct mount *mp;
int ticks;
int unused01;
};
struct mntcache {
struct mntcache_elm array[MNTCACHE_COUNT];
} __cachealign;
static struct mntcache pcpu_mntcache[MAXCPU];
static __inline
void
_cache_ncp_gen_enter(struct namecache *ncp)
{
ncp->nc_generation += 2;
cpu_sfence();
}
static __inline
void
_cache_ncp_gen_exit(struct namecache *ncp)
{
cpu_sfence();
ncp->nc_generation += 2;
cpu_sfence();
}
static __inline
struct mntcache_elm *
_cache_mntcache_hash(void *ptr)
{
struct mntcache_elm *elm;
int hv;
hv = iscsi_crc32(&ptr, sizeof(ptr)) & (MNTCACHE_COUNT - 1);
elm = &pcpu_mntcache[mycpu->gd_cpuid].array[hv & ~(MNTCACHE_SET - 1)];
return elm;
}
static
void
_cache_mntref(struct mount *mp)
{
struct mntcache_elm *elm;
struct mount *mpr;
int i;
elm = _cache_mntcache_hash(mp);
for (i = 0; i < MNTCACHE_SET; ++i) {
if (elm->mp == mp) {
mpr = atomic_swap_ptr((void *)&elm->mp, NULL);
if (__predict_true(mpr == mp))
return;
if (mpr)
atomic_add_int(&mpr->mnt_refs, -1);
}
++elm;
}
atomic_add_int(&mp->mnt_refs, 1);
}
static
void
_cache_mntrel(struct mount *mp)
{
struct mntcache_elm *elm;
struct mntcache_elm *best;
struct mount *mpr;
int delta1;
int delta2;
int i;
elm = _cache_mntcache_hash(mp);
best = elm;
for (i = 0; i < MNTCACHE_SET; ++i) {
if (elm->mp == NULL) {
mpr = atomic_swap_ptr((void *)&elm->mp, mp);
if (__predict_false(mpr != NULL)) {
atomic_add_int(&mpr->mnt_refs, -1);
}
elm->ticks = ticks;
return;
}
delta1 = ticks - best->ticks;
delta2 = ticks - elm->ticks;
if (delta2 > delta1 || delta1 < -1 || delta2 < -1)
best = elm;
++elm;
}
mpr = atomic_swap_ptr((void *)&best->mp, mp);
best->ticks = ticks;
if (mpr)
atomic_add_int(&mpr->mnt_refs, -1);
}
void
cache_clearmntcache(struct mount *target __unused)
{
int n;
for (n = 0; n < ncpus; ++n) {
struct mntcache *cache = &pcpu_mntcache[n];
struct mntcache_elm *elm;
struct namecache *ncp;
struct mount *mp;
int i;
for (i = 0; i < MNTCACHE_COUNT; ++i) {
elm = &cache->array[i];
if (elm->mp) {
mp = atomic_swap_ptr((void *)&elm->mp, NULL);
if (mp)
atomic_add_int(&mp->mnt_refs, -1);
}
if (elm->ncp) {
ncp = atomic_swap_ptr((void *)&elm->ncp, NULL);
if (ncp)
_cache_drop(ncp);
}
}
}
}
static __always_inline
void
_cache_lock(struct namecache *ncp)
{
int didwarn = 0;
int error;
error = lockmgr(&ncp->nc_lock, LK_EXCLUSIVE);
while (__predict_false(error == EWOULDBLOCK)) {
if (didwarn == 0) {
didwarn = ticks - nclockwarn;
kprintf("[diagnostic] cache_lock: "
"%s blocked on %p "
"\"%*.*s\"\n",
curthread->td_comm, ncp,
ncp->nc_nlen, ncp->nc_nlen,
ncp->nc_name);
}
error = lockmgr(&ncp->nc_lock, LK_EXCLUSIVE | LK_TIMELOCK);
}
if (__predict_false(didwarn)) {
kprintf("[diagnostic] cache_lock: "
"%s unblocked %*.*s after %d secs\n",
curthread->td_comm,
ncp->nc_nlen, ncp->nc_nlen, ncp->nc_name,
(int)(ticks - didwarn) / hz);
}
}
static __always_inline
void
_cache_unlock(struct namecache *ncp)
{
lockmgr(&ncp->nc_lock, LK_RELEASE);
}
static __always_inline
int
_cache_lock_nonblock(struct namecache *ncp)
{
int error;
error = lockmgr(&ncp->nc_lock, LK_EXCLUSIVE | LK_NOWAIT);
if (__predict_false(error != 0)) {
return(EWOULDBLOCK);
}
return 0;
}
static __always_inline
int
_cache_lock_special(struct namecache *ncp)
{
if (_cache_lock_nonblock(ncp) == 0) {
if (lockmgr_oneexcl(&ncp->nc_lock)) {
if (ncp->nc_vp && (ncp->nc_vp->v_flag & VRECLAIMED))
_cache_setunresolved(ncp, 1);
return 0;
}
_cache_unlock(ncp);
}
return EWOULDBLOCK;
}
static __always_inline
void
_cache_lock_shared(struct namecache *ncp)
{
int didwarn = 0;
int error;
error = lockmgr(&ncp->nc_lock, LK_SHARED | LK_TIMELOCK);
while (__predict_false(error == EWOULDBLOCK)) {
if (didwarn == 0) {
didwarn = ticks - nclockwarn;
kprintf("[diagnostic] cache_lock_shared: "
"%s blocked on %p "
"\"%*.*s\"\n",
curthread->td_comm, ncp,
ncp->nc_nlen, ncp->nc_nlen,
ncp->nc_name);
}
error = lockmgr(&ncp->nc_lock, LK_SHARED | LK_TIMELOCK);
}
if (__predict_false(didwarn)) {
kprintf("[diagnostic] cache_lock_shared: "
"%s unblocked %*.*s after %d secs\n",
curthread->td_comm,
ncp->nc_nlen, ncp->nc_nlen, ncp->nc_name,
(int)(ticks - didwarn) / hz);
}
}
static __always_inline
int
_cache_lock_shared_nonblock(struct namecache *ncp)
{
int error;
error = lockmgr(&ncp->nc_lock, LK_SHARED | LK_NOWAIT);
if (__predict_false(error != 0)) {
return(EWOULDBLOCK);
}
return 0;
}
static __always_inline
int
_cache_lock_shared_special(struct namecache *ncp)
{
if (_cache_lock_shared_nonblock(ncp) == 0) {
if (__predict_true(!lockmgr_exclpending(&ncp->nc_lock))) {
if (ncp->nc_vp == NULL ||
(ncp->nc_vp->v_flag & VRECLAIMED) == 0) {
return(0);
}
}
_cache_unlock(ncp);
return(EWOULDBLOCK);
}
if (lockstatus(&ncp->nc_lock, curthread) == LK_EXCLUSIVE) {
_cache_lock(ncp);
return(0);
}
_cache_lock_shared(ncp);
return(0);
}
static __always_inline
int
_cache_lockstatus(struct namecache *ncp)
{
int status;
status = lockstatus(&ncp->nc_lock, curthread);
if (status == LK_EXCLOTHER)
status = -1;
return status;
}
static __always_inline
struct namecache *
_cache_hold(struct namecache *ncp)
{
KKASSERT(ncp->nc_refs > 0);
atomic_add_int(&ncp->nc_refs, 1);
return(ncp);
}
static __always_inline
void
_cache_drop(struct namecache *ncp)
{
if (atomic_fetchadd_int(&ncp->nc_refs, -1) == 1) {
KKASSERT(ncp->nc_flag & NCF_UNRESOLVED);
ncp->nc_refs = -1;
if (ncp->nc_name)
kfree(ncp->nc_name, M_VFSCACHEAUX);
kfree_obj(ncp, M_VFSCACHE);
}
}
static void
_cache_link_parent(struct namecache *ncp, struct namecache *par,
struct nchash_head *nchpp)
{
struct pcpu_ncache *pn = &pcpu_ncache[mycpu->gd_cpuid];
KKASSERT(ncp->nc_parent == NULL);
_cache_ncp_gen_enter(ncp);
ncp->nc_parent = par;
ncp->nc_head = nchpp;
ncp->nc_flag &= ~(NCF_SF_PNOCACHE | NCF_UF_PCACHE);
if (par->nc_flag & (NCF_SF_NOCACHE | NCF_SF_PNOCACHE))
ncp->nc_flag |= NCF_SF_PNOCACHE;
if (par->nc_flag & (NCF_UF_CACHE | NCF_UF_PCACHE))
ncp->nc_flag |= NCF_UF_PCACHE;
TAILQ_INSERT_HEAD(&nchpp->list, ncp, nc_hash);
atomic_add_long(&pn->vfscache_count, 1);
if (TAILQ_EMPTY(&ncp->nc_list)) {
atomic_add_long(&pn->vfscache_leafs, 1);
if (ncp->nc_flag & NCF_UNRESOLVED)
atomic_add_long(&pn->vfscache_unres, 1);
}
if (TAILQ_EMPTY(&par->nc_list)) {
TAILQ_INSERT_HEAD(&par->nc_list, ncp, nc_entry);
if (par->nc_parent) {
if (par->nc_flag & NCF_UNRESOLVED)
atomic_add_long(&pn->vfscache_unres, -1);
atomic_add_long(&pn->vfscache_leafs, -1);
}
if (par->nc_vp)
vhold(par->nc_vp);
} else {
TAILQ_INSERT_HEAD(&par->nc_list, ncp, nc_entry);
}
_cache_hold(par);
_cache_ncp_gen_exit(ncp);
}
static void
_cache_unlink_parent(struct namecache *par, struct namecache *ncp,
struct nchash_head *nchpp)
{
struct pcpu_ncache *pn = &pcpu_ncache[mycpu->gd_cpuid];
struct vnode *dropvp;
KKASSERT(ncp->nc_parent == par);
cpu_ccfence();
_cache_ncp_gen_enter(ncp);
TAILQ_REMOVE(&ncp->nc_head->list, ncp, nc_hash);
TAILQ_REMOVE(&par->nc_list, ncp, nc_entry);
atomic_add_long(&pn->vfscache_count, -1);
if (TAILQ_EMPTY(&ncp->nc_list)) {
if (ncp->nc_flag & NCF_UNRESOLVED)
atomic_add_long(&pn->vfscache_unres, -1);
atomic_add_long(&pn->vfscache_leafs, -1);
}
dropvp = NULL;
if (TAILQ_EMPTY(&par->nc_list)) {
if (par->nc_parent) {
if (par->nc_flag & NCF_UNRESOLVED)
atomic_add_long(&pn->vfscache_unres, 1);
atomic_add_long(&pn->vfscache_leafs, 1);
}
if (par->nc_vp)
dropvp = par->nc_vp;
}
ncp->nc_parent = NULL;
ncp->nc_head = NULL;
spin_unlock(&nchpp->spin);
_cache_drop(par);
if (dropvp)
vdrop(dropvp);
_cache_ncp_gen_exit(ncp);
}
static struct namecache *
cache_alloc(int nlen)
{
struct namecache *ncp;
ncp = kmalloc_obj(sizeof(*ncp), M_VFSCACHE, M_WAITOK|M_ZERO);
if (nlen)
ncp->nc_name = kmalloc(nlen + 1, M_VFSCACHEAUX, M_WAITOK);
ncp->nc_nlen = nlen;
ncp->nc_flag = NCF_UNRESOLVED;
ncp->nc_error = ENOTCONN;
ncp->nc_refs = 1;
ncp->nc_generation = 0;
TAILQ_INIT(&ncp->nc_list);
lockinit(&ncp->nc_lock, "ncplk", hz, LK_CANRECURSE);
lockmgr(&ncp->nc_lock, LK_EXCLUSIVE);
return(ncp);
}
static void
_cache_free(struct namecache *ncp)
{
KKASSERT(ncp->nc_refs == 1);
if (ncp->nc_name)
kfree(ncp->nc_name, M_VFSCACHEAUX);
kfree_obj(ncp, M_VFSCACHE);
}
void
cache_zero(struct nchandle *nch)
{
nch->ncp = NULL;
nch->mount = NULL;
}
struct nchandle *
cache_hold(struct nchandle *nch)
{
_cache_hold(nch->ncp);
_cache_mntref(nch->mount);
return(nch);
}
void
cache_copy(struct nchandle *nch, struct nchandle *target)
{
struct namecache *ncp;
struct mount *mp;
struct mntcache_elm *elm;
struct namecache *ncpr;
int i;
ncp = nch->ncp;
mp = nch->mount;
target->ncp = ncp;
target->mount = mp;
elm = _cache_mntcache_hash(ncp);
for (i = 0; i < MNTCACHE_SET; ++i) {
if (elm->ncp == ncp) {
ncpr = atomic_swap_ptr((void *)&elm->ncp, NULL);
if (ncpr == ncp) {
_cache_mntref(mp);
return;
}
if (ncpr)
_cache_drop(ncpr);
}
++elm;
}
if (ncp)
_cache_hold(ncp);
_cache_mntref(mp);
}
void
cache_drop_and_cache(struct nchandle *nch, int elmno)
{
struct mntcache_elm *elm;
struct mntcache_elm *best;
struct namecache *ncpr;
int delta1;
int delta2;
int i;
if (elmno > 4) {
if (nch->ncp) {
_cache_drop(nch->ncp);
nch->ncp = NULL;
}
if (nch->mount) {
_cache_mntrel(nch->mount);
nch->mount = NULL;
}
return;
}
elm = _cache_mntcache_hash(nch->ncp);
best = elm;
for (i = 0; i < MNTCACHE_SET; ++i) {
if (elm->ncp == NULL) {
ncpr = atomic_swap_ptr((void *)&elm->ncp, nch->ncp);
_cache_mntrel(nch->mount);
elm->ticks = ticks;
nch->mount = NULL;
nch->ncp = NULL;
if (ncpr)
_cache_drop(ncpr);
return;
}
delta1 = ticks - best->ticks;
delta2 = ticks - elm->ticks;
if (delta2 > delta1 || delta1 < -1 || delta2 < -1)
best = elm;
++elm;
}
ncpr = atomic_swap_ptr((void *)&best->ncp, nch->ncp);
_cache_mntrel(nch->mount);
best->ticks = ticks;
nch->mount = NULL;
nch->ncp = NULL;
if (ncpr)
_cache_drop(ncpr);
}
void
cache_changemount(struct nchandle *nch, struct mount *mp)
{
_cache_mntref(mp);
_cache_mntrel(nch->mount);
nch->mount = mp;
}
void
cache_drop(struct nchandle *nch)
{
_cache_mntrel(nch->mount);
_cache_drop(nch->ncp);
nch->ncp = NULL;
nch->mount = NULL;
}
int
cache_lockstatus(struct nchandle *nch)
{
return(_cache_lockstatus(nch->ncp));
}
void
cache_lock(struct nchandle *nch)
{
_cache_lock(nch->ncp);
}
void
cache_lock_maybe_shared(struct nchandle *nch, int excl)
{
struct namecache *ncp = nch->ncp;
if (ncp_shared_lock_disable || excl ||
(ncp->nc_flag & NCF_UNRESOLVED)) {
_cache_lock(ncp);
} else {
_cache_lock_shared(ncp);
if ((ncp->nc_flag & NCF_UNRESOLVED) == 0) {
if (ncp->nc_vp && (ncp->nc_vp->v_flag & VRECLAIMED)) {
_cache_unlock(ncp);
_cache_lock(ncp);
}
} else {
_cache_unlock(ncp);
_cache_lock(ncp);
}
}
}
void
cache_lock4_tondlocked(struct nchandle *fncpd, struct nchandle *fncp,
struct nchandle *tncpd, struct nchandle *tncp,
struct ucred *fcred, struct ucred *tcred)
{
int tlocked = 1;
u_int dummy_gen = 0;
again:
if (__predict_false(tlocked == 0)) {
cache_lock(tncp);
}
if (__predict_false(cache_lock_nonblock(tncpd) != 0)) {
cache_unlock(tncp);
cache_lock(tncpd);
cache_unlock(tncpd);
tlocked = 0;
goto again;
}
if (__predict_false(cache_lock_nonblock(fncp) != 0)) {
cache_unlock(tncpd);
cache_unlock(tncp);
cache_lock(fncp);
cache_unlock(fncp);
tlocked = 0;
goto again;
}
if (__predict_false(cache_lock_nonblock(fncpd) != 0)) {
cache_unlock(fncp);
cache_unlock(tncpd);
cache_unlock(tncp);
cache_lock(fncpd);
cache_unlock(fncpd);
tlocked = 0;
goto again;
}
if (__predict_true((fncpd->ncp->nc_flag & NCF_DESTROYED) == 0))
cache_resolve(fncpd, &dummy_gen, fcred);
if (__predict_true((tncpd->ncp->nc_flag & NCF_DESTROYED) == 0))
cache_resolve(tncpd, &dummy_gen, tcred);
if (__predict_true((fncp->ncp->nc_flag & NCF_DESTROYED) == 0))
cache_resolve(fncp, &dummy_gen, fcred);
if (__predict_true((tncp->ncp->nc_flag & NCF_DESTROYED) == 0))
cache_resolve(tncp, &dummy_gen, tcred);
}
int
cache_lock_nonblock(struct nchandle *nch)
{
return(_cache_lock_nonblock(nch->ncp));
}
void
cache_unlock(struct nchandle *nch)
{
_cache_unlock(nch->ncp);
}
static
struct namecache *
_cache_get(struct namecache *ncp)
{
_cache_hold(ncp);
_cache_lock(ncp);
if (ncp->nc_vp && (ncp->nc_vp->v_flag & VRECLAIMED))
_cache_setunresolved(ncp, 1);
return(ncp);
}
static
struct namecache *
_cache_get_maybe_shared(struct namecache *ncp, int excl)
{
if (ncp_shared_lock_disable || excl ||
(ncp->nc_flag & NCF_UNRESOLVED))
{
return(_cache_get(ncp));
}
_cache_hold(ncp);
_cache_lock_shared(ncp);
if ((ncp->nc_flag & NCF_UNRESOLVED) == 0) {
if (ncp->nc_vp && (ncp->nc_vp->v_flag & VRECLAIMED)) {
_cache_unlock(ncp);
ncp = _cache_get(ncp);
_cache_drop(ncp);
}
} else {
_cache_unlock(ncp);
ncp = _cache_get(ncp);
_cache_drop(ncp);
}
return(ncp);
}
void
cache_get(struct nchandle *nch, struct nchandle *target)
{
KKASSERT(nch->ncp->nc_refs > 0);
target->mount = nch->mount;
target->ncp = _cache_get(nch->ncp);
_cache_mntref(target->mount);
}
void
cache_get_maybe_shared(struct nchandle *nch, struct nchandle *target, int excl)
{
KKASSERT(nch->ncp->nc_refs > 0);
target->mount = nch->mount;
target->ncp = _cache_get_maybe_shared(nch->ncp, excl);
_cache_mntref(target->mount);
}
static __always_inline
void
_cache_put(struct namecache *ncp)
{
_cache_unlock(ncp);
_cache_drop(ncp);
}
void
cache_put(struct nchandle *nch)
{
_cache_mntrel(nch->mount);
_cache_put(nch->ncp);
nch->ncp = NULL;
nch->mount = NULL;
}
static
void
_cache_setvp(struct mount *mp, struct namecache *ncp, struct vnode *vp,
int adjgen)
{
struct pcpu_ncache *pn = &pcpu_ncache[mycpu->gd_cpuid];
KKASSERT((ncp->nc_flag & NCF_UNRESOLVED) &&
(_cache_lockstatus(ncp) == LK_EXCLUSIVE) &&
ncp->nc_vp == NULL);
if (adjgen)
_cache_ncp_gen_enter(ncp);
if (vp) {
if (!TAILQ_EMPTY(&ncp->nc_list))
vhold(vp);
spin_lock(&vp->v_spin);
ncp->nc_vp = vp;
TAILQ_INSERT_HEAD(&vp->v_namecache, ncp, nc_vnode);
++vp->v_namecache_count;
_cache_hold(ncp);
spin_unlock(&vp->v_spin);
vhold(vp);
switch(vp->v_type) {
case VDIR:
ncp->nc_flag |= NCF_ISDIR;
break;
case VLNK:
ncp->nc_flag |= NCF_ISSYMLINK;
break;
default:
break;
}
ncp->nc_error = 0;
if (mp) {
if (strncmp(mp->mnt_stat.f_fstypename, "null", 5) == 0)
vp->v_pfsmp = mp;
}
} else {
ncp->nc_vp = NULL;
ncp->nc_negcpu = mycpu->gd_cpuid;
spin_lock(&pn->neg_spin);
TAILQ_INSERT_TAIL(&pn->neg_list, ncp, nc_vnode);
_cache_hold(ncp);
++pn->neg_count;
spin_unlock(&pn->neg_spin);
atomic_add_long(&pn->vfscache_negs, 1);
ncp->nc_error = ENOENT;
if (mp)
VFS_NCPGEN_SET(mp, ncp);
}
if (TAILQ_EMPTY(&ncp->nc_list) && ncp->nc_parent)
atomic_add_long(&pn->vfscache_unres, -1);
ncp->nc_flag &= ~(NCF_UNRESOLVED | NCF_DEFEREDZAP);
if (adjgen)
_cache_ncp_gen_exit(ncp);
}
void
cache_setvp(struct nchandle *nch, struct vnode *vp)
{
_cache_setvp(nch->mount, nch->ncp, vp, 1);
}
void
cache_settimeout(struct nchandle *nch, int nticks)
{
struct namecache *ncp = nch->ncp;
if ((ncp->nc_timeout = ticks + nticks) == 0)
ncp->nc_timeout = 1;
}
static
void
_cache_setunresolved(struct namecache *ncp, int adjgen)
{
struct vnode *vp;
if ((ncp->nc_flag & NCF_UNRESOLVED) == 0) {
struct pcpu_ncache *pn;
if (adjgen)
_cache_ncp_gen_enter(ncp);
if (TAILQ_EMPTY(&ncp->nc_list) && ncp->nc_parent) {
pn = &pcpu_ncache[mycpu->gd_cpuid];
atomic_add_long(&pn->vfscache_unres, 1);
}
ncp->nc_flag |= NCF_UNRESOLVED;
ncp->nc_timeout = 0;
ncp->nc_error = ENOTCONN;
if ((vp = ncp->nc_vp) != NULL) {
spin_lock(&vp->v_spin);
ncp->nc_vp = NULL;
TAILQ_REMOVE(&vp->v_namecache, ncp, nc_vnode);
--vp->v_namecache_count;
spin_unlock(&vp->v_spin);
if (!TAILQ_EMPTY(&ncp->nc_list))
vdrop(vp);
vdrop(vp);
} else {
pn = &pcpu_ncache[ncp->nc_negcpu];
atomic_add_long(&pn->vfscache_negs, -1);
spin_lock(&pn->neg_spin);
TAILQ_REMOVE(&pn->neg_list, ncp, nc_vnode);
--pn->neg_count;
spin_unlock(&pn->neg_spin);
}
ncp->nc_flag &= ~(NCF_WHITEOUT|NCF_ISDIR|NCF_ISSYMLINK);
if (adjgen)
_cache_ncp_gen_exit(ncp);
_cache_drop(ncp);
}
}
static __inline int
_cache_auto_unresolve_test(struct mount *mp, struct namecache *ncp)
{
if (ncp->nc_timeout && (int)(ncp->nc_timeout - ticks) < 0 &&
TAILQ_EMPTY(&ncp->nc_list)) {
return 1;
}
if (ncp->nc_vp == NULL && VFS_NCPGEN_TEST(mp, ncp)) {
return 1;
}
return 0;
}
static __inline void
_cache_auto_unresolve(struct mount *mp, struct namecache *ncp)
{
if ((ncp->nc_flag & NCF_UNRESOLVED) == 0) {
if (_cache_auto_unresolve_test(mp, ncp))
_cache_setunresolved(ncp, 1);
}
}
void
cache_setunresolved(struct nchandle *nch)
{
_cache_setunresolved(nch->ncp, 1);
}
static
int
cache_clrmountpt_callback(struct mount *mp, void *data)
{
struct nchandle *nch = data;
if (mp->mnt_ncmounton.ncp == nch->ncp)
return(1);
if (mp->mnt_ncmountpt.ncp == nch->ncp)
return(1);
return(0);
}
void
cache_clrmountpt(struct nchandle *nch)
{
int count;
count = mountlist_scan(cache_clrmountpt_callback, nch,
MNTSCAN_FORWARD | MNTSCAN_NOBUSY |
MNTSCAN_NOUNLOCK);
if (count == 0)
nch->ncp->nc_flag &= ~NCF_ISMOUNTPT;
}
struct cinvtrack {
struct namecache *resume_ncp;
int depth;
};
static int _cache_inval_internal(struct namecache *, int, struct cinvtrack *);
static
int
_cache_inval(struct namecache *ncp, int flags)
{
struct cinvtrack track;
struct namecache *ncp2;
int r;
track.depth = 0;
track.resume_ncp = NULL;
for (;;) {
r = _cache_inval_internal(ncp, flags, &track);
if (track.resume_ncp == NULL)
break;
_cache_unlock(ncp);
while ((ncp2 = track.resume_ncp) != NULL) {
track.resume_ncp = NULL;
_cache_lock(ncp2);
_cache_inval_internal(ncp2, flags & ~CINV_DESTROY,
&track);
cache_zap(ncp2);
}
_cache_lock(ncp);
}
return(r);
}
int
cache_inval(struct nchandle *nch, int flags)
{
return(_cache_inval(nch->ncp, flags));
}
static int
_cache_inval_internal(struct namecache *ncp, int flags, struct cinvtrack *track)
{
struct namecache *nextkid;
int rcnt = 0;
KKASSERT(_cache_lockstatus(ncp) == LK_EXCLUSIVE);
_cache_ncp_gen_enter(ncp);
_cache_setunresolved(ncp, 0);
if (flags & CINV_DESTROY) {
ncp->nc_flag |= NCF_DESTROYED;
cpu_sfence();
}
while ((flags & CINV_CHILDREN) &&
(nextkid = TAILQ_FIRST(&ncp->nc_list)) != NULL
) {
struct namecache *kid;
int restart;
restart = 0;
_cache_hold(nextkid);
if (++track->depth > MAX_RECURSION_DEPTH) {
track->resume_ncp = ncp;
_cache_hold(ncp);
++rcnt;
}
while ((kid = nextkid) != NULL) {
nextkid = NULL;
if (kid->nc_parent != ncp) {
_cache_drop(kid);
kprintf("cache_inval_internal restartA %s\n",
ncp->nc_name);
restart = 1;
break;
}
if ((nextkid = TAILQ_NEXT(kid, nc_entry)) != NULL)
_cache_hold(nextkid);
_cache_unlock(ncp);
if (track->resume_ncp) {
_cache_drop(kid);
_cache_lock(ncp);
break;
}
_cache_lock(kid);
if (kid->nc_parent != ncp) {
kprintf("cache_inval_internal "
"restartB %s\n",
ncp->nc_name);
restart = 1;
_cache_unlock(kid);
_cache_drop(kid);
_cache_lock(ncp);
break;
}
if ((kid->nc_flag & NCF_UNRESOLVED) == 0 ||
TAILQ_FIRST(&kid->nc_list)
) {
rcnt += _cache_inval_internal(kid,
flags & ~CINV_DESTROY, track);
cache_zap(kid);
} else {
cache_zap(kid);
}
_cache_lock(ncp);
}
if (nextkid)
_cache_drop(nextkid);
--track->depth;
if (restart == 0)
break;
}
if ((ncp->nc_flag & NCF_UNRESOLVED) == 0)
++rcnt;
_cache_ncp_gen_exit(ncp);
return (rcnt);
}
int
cache_inval_vp(struct vnode *vp, int flags)
{
struct namecache *ncp;
struct namecache *next;
restart:
spin_lock(&vp->v_spin);
ncp = TAILQ_FIRST(&vp->v_namecache);
if (ncp)
_cache_hold(ncp);
while (ncp) {
if ((next = TAILQ_NEXT(ncp, nc_vnode)) != NULL)
_cache_hold(next);
spin_unlock(&vp->v_spin);
_cache_lock(ncp);
if (ncp->nc_vp != vp) {
kprintf("Warning: cache_inval_vp: race-A detected on "
"%s\n", ncp->nc_name);
_cache_put(ncp);
if (next)
_cache_drop(next);
goto restart;
}
_cache_inval(ncp, flags);
_cache_put(ncp);
ncp = next;
spin_lock(&vp->v_spin);
if (ncp && ncp->nc_vp != vp) {
spin_unlock(&vp->v_spin);
kprintf("Warning: cache_inval_vp: race-B detected on "
"%s\n", ncp->nc_name);
_cache_drop(ncp);
goto restart;
}
}
spin_unlock(&vp->v_spin);
return(TAILQ_FIRST(&vp->v_namecache) != NULL);
}
int
cache_inval_vp_nonblock(struct vnode *vp)
{
struct namecache *ncp;
struct namecache *next;
spin_lock(&vp->v_spin);
ncp = TAILQ_FIRST(&vp->v_namecache);
if (ncp)
_cache_hold(ncp);
while (ncp) {
if ((next = TAILQ_NEXT(ncp, nc_vnode)) != NULL)
_cache_hold(next);
spin_unlock(&vp->v_spin);
if (_cache_lock_nonblock(ncp)) {
_cache_drop(ncp);
if (next)
_cache_drop(next);
goto done;
}
if (ncp->nc_vp != vp) {
kprintf("Warning: cache_inval_vp: race-A detected on "
"%s\n", ncp->nc_name);
_cache_put(ncp);
if (next)
_cache_drop(next);
goto done;
}
_cache_inval(ncp, 0);
_cache_put(ncp);
ncp = next;
spin_lock(&vp->v_spin);
if (ncp && ncp->nc_vp != vp) {
spin_unlock(&vp->v_spin);
kprintf("Warning: cache_inval_vp: race-B detected on "
"%s\n", ncp->nc_name);
_cache_drop(ncp);
goto done;
}
}
spin_unlock(&vp->v_spin);
done:
return(TAILQ_FIRST(&vp->v_namecache) != NULL);
}
void
cache_inval_vp_quick(struct vnode *vp)
{
struct pcpu_ncache *pn = &pcpu_ncache[mycpu->gd_cpuid];
struct namecache *ncp;
struct namecache *kid;
spin_lock(&vp->v_spin);
while ((ncp = TAILQ_FIRST(&vp->v_namecache)) != NULL) {
_cache_hold(ncp);
spin_unlock(&vp->v_spin);
if (_cache_lock_nonblock(ncp)) {
_cache_drop(ncp);
return;
}
while ((kid = TAILQ_FIRST(&ncp->nc_list)) != NULL) {
struct nchash_head *nchpp;
if (TAILQ_FIRST(&kid->nc_list) ||
kid->nc_vp ||
kid->nc_refs != ncpbaserefs(kid))
{
_cache_put(ncp);
return;
}
_cache_hold(kid);
if (_cache_lock_nonblock(kid)) {
_cache_drop(kid);
_cache_put(ncp);
return;
}
nchpp = kid->nc_head;
spin_lock(&nchpp->spin);
if (kid->nc_parent != ncp ||
kid->nc_vp ||
TAILQ_FIRST(&kid->nc_list) ||
kid->nc_refs != 1 + ncpbaserefs(kid))
{
spin_unlock(&nchpp->spin);
_cache_put(kid);
_cache_put(ncp);
return;
}
++pn->inv_kid_quick_count;
_cache_unlink_parent(ncp, kid, nchpp);
_cache_setunresolved(kid, 1);
if (kid->nc_refs != 2) {
kprintf("Warning: kid %p unexpected refs=%d "
"%08x %s\n",
kid, kid->nc_refs,
kid->nc_flag, kid->nc_name);
}
_cache_put(kid);
_cache_drop(kid);
}
if (ncp->nc_refs != 1 + ncpbaserefs(ncp)) {
_cache_put(ncp);
spin_lock(&vp->v_spin);
break;
}
++pn->inv_ncp_quick_count;
_cache_setunresolved(ncp, 1);
_cache_put(ncp);
spin_lock(&vp->v_spin);
}
spin_unlock(&vp->v_spin);
}
void
cache_inval_wxok(struct vnode *vp)
{
struct namecache *ncp;
spin_lock(&vp->v_spin);
TAILQ_FOREACH(ncp, &vp->v_namecache, nc_vnode) {
if (ncp->nc_flag & (NCF_WXOK | NCF_NOTX))
atomic_clear_short(&ncp->nc_flag, NCF_WXOK | NCF_NOTX);
}
spin_unlock(&vp->v_spin);
}
void
cache_rename(struct nchandle *fnch, struct nchandle *tnch)
{
struct namecache *fncp = fnch->ncp;
struct namecache *tncp = tnch->ncp;
struct namecache *par;
struct nchash_head *nchpp;
u_int32_t hash;
char *oname;
char *nname;
if (tncp->nc_nlen) {
nname = kmalloc(tncp->nc_nlen + 1, M_VFSCACHEAUX, M_WAITOK);
bcopy(tncp->nc_name, nname, tncp->nc_nlen);
nname[tncp->nc_nlen] = 0;
} else {
nname = NULL;
}
if (fncp->nc_parent) {
par = fncp->nc_parent;
_cache_hold(par);
_cache_lock(par);
nchpp = fncp->nc_head;
spin_lock(&nchpp->spin);
_cache_unlink_parent(par, fncp, nchpp);
_cache_put(par);
} else {
par = NULL;
nchpp = NULL;
}
oname = fncp->nc_name;
fncp->nc_name = nname;
fncp->nc_nlen = tncp->nc_nlen;
if (oname)
kfree(oname, M_VFSCACHEAUX);
par = tncp->nc_parent;
KKASSERT(par->nc_lock.lk_lockholder == curthread);
hash = fnv_32_buf(fncp->nc_name, fncp->nc_nlen, FNV1_32_INIT);
hash = fnv_32_buf(&par, sizeof(par), hash);
nchpp = NCHHASH(hash);
spin_lock(&nchpp->spin);
_cache_link_parent(fncp, par, nchpp);
spin_unlock(&nchpp->spin);
_cache_unlink(tncp);
}
void
cache_unlink(struct nchandle *nch)
{
_cache_unlink(nch->ncp);
}
static void
_cache_unlink(struct namecache *ncp)
{
struct vnode *vp;
_cache_ncp_gen_enter(ncp);
ncp->nc_flag |= NCF_DESTROYED;
if ((ncp->nc_flag & NCF_UNRESOLVED) == 0) {
vp = ncp->nc_vp;
if (vp) {
atomic_set_int(&vp->v_refcnt, VREF_FINALIZE);
if (VREFCNT(vp) <= 0) {
if (vget(vp, LK_SHARED) == 0)
vput(vp);
}
} else {
_cache_setunresolved(ncp, 0);
}
}
_cache_ncp_gen_exit(ncp);
}
int
cache_isopen(struct nchandle *nch)
{
struct vnode *vp;
struct namecache *ncp = nch->ncp;
if ((ncp->nc_flag & NCF_UNRESOLVED) == 0 &&
(vp = ncp->nc_vp) != NULL &&
VREFCNT(vp)) {
return 1;
}
return 0;
}
int
cache_vget(struct nchandle *nch, struct ucred *cred,
int lk_type, struct vnode **vpp)
{
struct namecache *ncp;
struct vnode *vp;
int error;
u_int dummy_gen = 0;
ncp = nch->ncp;
again:
vp = NULL;
if (ncp->nc_flag & NCF_UNRESOLVED)
error = cache_resolve(nch, &dummy_gen, cred);
else
error = 0;
if (error == 0 && (vp = ncp->nc_vp) != NULL) {
error = vget(vp, lk_type);
if (error) {
if (error == ENOENT) {
kprintf("Warning: vnode reclaim race detected "
"in cache_vget on %p (%s)\n",
vp, ncp->nc_name);
_cache_unlock(ncp);
_cache_lock(ncp);
_cache_setunresolved(ncp, 1);
goto again;
}
KKASSERT(ncp->nc_vp == vp);
vp = NULL;
} else {
KKASSERT(ncp->nc_vp == vp);
KKASSERT((vp->v_flag & VRECLAIMED) == 0);
}
}
if (error == 0 && vp == NULL)
error = ENOENT;
*vpp = vp;
return(error);
}
int
cache_vref(struct nchandle *nch, struct ucred *cred, struct vnode **vpp)
{
struct namecache *ncp;
struct vnode *vp;
int error;
int v;
u_int dummy_gen = 0;
ncp = nch->ncp;
again:
vp = NULL;
if (ncp->nc_flag & NCF_UNRESOLVED)
error = cache_resolve(nch, &dummy_gen, cred);
else
error = 0;
while (error == 0 && (vp = ncp->nc_vp) != NULL) {
v = spin_access_start_only(&vp->v_spin);
if (__predict_true(spin_access_check_inprog(v) == 0)) {
vref_special(vp);
if (__predict_false(
spin_access_end_only(&vp->v_spin, v))) {
vrele(vp);
continue;
}
if (__predict_true((vp->v_flag & VRECLAIMED) == 0)) {
break;
}
vrele(vp);
kprintf("CACHE_VREF: IN-RECLAIM\n");
}
error = vget(vp, LK_SHARED);
if (error) {
if (error == ENOENT) {
kprintf("Warning: vnode reclaim race detected "
"in cache_vget on %p (%s)\n",
vp, ncp->nc_name);
_cache_unlock(ncp);
_cache_lock(ncp);
_cache_setunresolved(ncp, 1);
goto again;
}
KKASSERT(ncp->nc_vp == vp);
vp = NULL;
} else {
KKASSERT(ncp->nc_vp == vp);
KKASSERT((vp->v_flag & VRECLAIMED) == 0);
vn_unlock(vp);
}
break;
}
if (error == 0 && vp == NULL)
error = ENOENT;
*vpp = vp;
return(error);
}
struct vnode *
cache_dvpref(struct namecache *ncp)
{
struct namecache *par;
struct vnode *dvp;
dvp = NULL;
if ((par = ncp->nc_parent) != NULL) {
_cache_hold(par);
_cache_lock(par);
if ((par->nc_flag & NCF_UNRESOLVED) == 0) {
if ((dvp = par->nc_vp) != NULL)
vhold(dvp);
}
_cache_unlock(par);
if (dvp) {
if (vget(dvp, LK_SHARED) == 0) {
vn_unlock(dvp);
vdrop(dvp);
} else {
vdrop(dvp);
dvp = NULL;
}
}
_cache_drop(par);
}
return(dvp);
}
static int cache_inefficient_scan(struct nchandle *nch, struct ucred *cred,
struct vnode *dvp, char *fakename);
static int cache_fromdvp_try(struct vnode *dvp, struct ucred *cred,
struct vnode **saved_dvp);
int
cache_fromdvp(struct vnode *dvp, struct ucred *cred, int makeit,
struct nchandle *nch)
{
struct vnode *saved_dvp;
struct vnode *pvp;
char *fakename;
int error;
nch->ncp = NULL;
nch->mount = dvp->v_mount;
saved_dvp = NULL;
fakename = NULL;
if (makeit == 0) {
spin_lock_shared(&dvp->v_spin);
nch->ncp = TAILQ_FIRST(&dvp->v_namecache);
if (nch->ncp)
cache_hold(nch);
spin_unlock_shared(&dvp->v_spin);
}
while (makeit) {
spin_lock_shared(&dvp->v_spin);
nch->ncp = TAILQ_FIRST(&dvp->v_namecache);
if (nch->ncp) {
cache_hold(nch);
spin_unlock_shared(&dvp->v_spin);
break;
}
spin_unlock_shared(&dvp->v_spin);
if (dvp->v_flag & VROOT) {
nch->ncp = _cache_get(nch->mount->mnt_ncmountpt.ncp);
error = cache_resolve_mp(nch->mount, 1);
_cache_put(nch->ncp);
if (ncvp_debug & 1) {
kprintf("cache_fromdvp: resolve root of "
"mount %p error %d",
dvp->v_mount, error);
}
if (error) {
if (ncvp_debug & 1)
kprintf(" failed\n");
nch->ncp = NULL;
break;
}
if (ncvp_debug & 1)
kprintf(" succeeded\n");
continue;
}
if (makeit > 20) {
error = cache_fromdvp_try(dvp, cred, &saved_dvp);
if (error) {
kprintf("lookupdotdot(longpath) failed %d "
"dvp %p\n", error, dvp);
nch->ncp = NULL;
break;
}
continue;
}
if (fakename) {
kfree(fakename, M_TEMP);
fakename = NULL;
}
error = vop_nlookupdotdot(*dvp->v_ops, dvp, &pvp, cred,
&fakename);
if (error) {
kprintf("lookupdotdot failed %d dvp %p\n", error, dvp);
break;
}
vn_unlock(pvp);
cache_fromdvp(pvp, cred, makeit + 1, nch);
vrele(pvp);
if (nch->ncp == NULL)
break;
error = cache_inefficient_scan(nch, cred, dvp, fakename);
if (error) {
kprintf("cache_fromdvp: scan %p (%s) failed on dvp=%p\n",
pvp, nch->ncp->nc_name, dvp);
cache_drop(nch);
nch->mount = dvp->v_mount;
break;
}
if (ncvp_debug & 1) {
kprintf("cache_fromdvp: scan %p (%s) succeeded\n",
pvp, nch->ncp->nc_name);
}
cache_drop(nch);
nch->mount = dvp->v_mount;
}
if (fakename)
kfree(fakename, M_TEMP);
if (saved_dvp)
vrele(saved_dvp);
if (nch->ncp)
return (0);
return (EINVAL);
}
static
int
cache_fromdvp_try(struct vnode *dvp, struct ucred *cred,
struct vnode **saved_dvp)
{
struct nchandle nch;
struct vnode *pvp;
int error;
static time_t last_fromdvp_report;
char *fakename;
vref(dvp);
nch.mount = dvp->v_mount;
nch.ncp = NULL;
fakename = NULL;
for (;;) {
if (fakename) {
kfree(fakename, M_TEMP);
fakename = NULL;
}
error = vop_nlookupdotdot(*dvp->v_ops, dvp, &pvp, cred,
&fakename);
if (error) {
vrele(dvp);
break;
}
vn_unlock(pvp);
spin_lock_shared(&pvp->v_spin);
if ((nch.ncp = TAILQ_FIRST(&pvp->v_namecache)) != NULL) {
_cache_hold(nch.ncp);
spin_unlock_shared(&pvp->v_spin);
vrele(pvp);
break;
}
spin_unlock_shared(&pvp->v_spin);
if (pvp->v_flag & VROOT) {
nch.ncp = _cache_get(pvp->v_mount->mnt_ncmountpt.ncp);
error = cache_resolve_mp(nch.mount, 1);
_cache_unlock(nch.ncp);
vrele(pvp);
if (error) {
_cache_drop(nch.ncp);
nch.ncp = NULL;
vrele(dvp);
}
break;
}
vrele(dvp);
dvp = pvp;
}
if (error == 0) {
if (last_fromdvp_report != time_uptime) {
last_fromdvp_report = time_uptime;
kprintf("Warning: extremely inefficient path "
"resolution on %s\n",
nch.ncp->nc_name);
}
error = cache_inefficient_scan(&nch, cred, dvp, fakename);
if (*saved_dvp)
vrele(*saved_dvp);
*saved_dvp = dvp;
_cache_drop(nch.ncp);
}
if (fakename)
kfree(fakename, M_TEMP);
return (error);
}
static int
cache_inefficient_scan(struct nchandle *nch, struct ucred *cred,
struct vnode *dvp, char *fakename)
{
struct nlcomponent nlc;
struct nchandle rncp;
struct dirent *den;
struct vnode *pvp;
struct vattr vat;
struct iovec iov;
struct uio uio;
int blksize;
int eofflag;
int bytes;
char *rbuf;
int error;
vat.va_blocksize = 0;
if ((error = VOP_GETATTR(dvp, &vat)) != 0)
return (error);
cache_lock(nch);
error = cache_vref(nch, cred, &pvp);
cache_unlock(nch);
if (error)
return (error);
if (ncvp_debug & 1) {
kprintf("inefficient_scan of (%p,%s): directory iosize %ld "
"vattr fileid = %lld\n",
nch->ncp, nch->ncp->nc_name,
vat.va_blocksize,
(long long)vat.va_fileid);
}
if (fakename) {
nlc.nlc_nameptr = fakename;
nlc.nlc_namelen = strlen(fakename);
rncp = cache_nlookup(nch, &nlc);
goto done;
}
if ((blksize = vat.va_blocksize) == 0)
blksize = DEV_BSIZE;
rbuf = kmalloc(blksize, M_TEMP, M_WAITOK);
rncp.ncp = NULL;
eofflag = 0;
uio.uio_offset = 0;
again:
iov.iov_base = rbuf;
iov.iov_len = blksize;
uio.uio_iov = &iov;
uio.uio_iovcnt = 1;
uio.uio_resid = blksize;
uio.uio_segflg = UIO_SYSSPACE;
uio.uio_rw = UIO_READ;
uio.uio_td = curthread;
if (ncvp_debug & 2)
kprintf("cache_inefficient_scan: readdir @ %08x\n", (int)uio.uio_offset);
error = VOP_READDIR(pvp, &uio, cred, &eofflag, NULL, NULL);
if (error == 0) {
den = (struct dirent *)rbuf;
bytes = blksize - uio.uio_resid;
while (bytes > 0) {
if (ncvp_debug & 2) {
kprintf("cache_inefficient_scan: %*.*s\n",
den->d_namlen, den->d_namlen,
den->d_name);
}
if (den->d_type != DT_WHT &&
den->d_ino == vat.va_fileid) {
if (ncvp_debug & 1) {
kprintf("cache_inefficient_scan: "
"MATCHED inode %lld path %s/%*.*s\n",
(long long)vat.va_fileid,
nch->ncp->nc_name,
den->d_namlen, den->d_namlen,
den->d_name);
}
nlc.nlc_nameptr = den->d_name;
nlc.nlc_namelen = den->d_namlen;
rncp = cache_nlookup(nch, &nlc);
KKASSERT(rncp.ncp != NULL);
break;
}
bytes -= _DIRENT_DIRSIZ(den);
den = _DIRENT_NEXT(den);
}
if (rncp.ncp == NULL && eofflag == 0 && uio.uio_resid != blksize)
goto again;
}
kfree(rbuf, M_TEMP);
done:
vrele(pvp);
if (rncp.ncp) {
if (rncp.ncp->nc_flag & NCF_UNRESOLVED) {
_cache_setvp(rncp.mount, rncp.ncp, dvp, 1);
if (ncvp_debug & 2) {
kprintf("cache_inefficient_scan: setvp %s/%s = %p\n",
nch->ncp->nc_name, rncp.ncp->nc_name, dvp);
}
} else {
if (ncvp_debug & 2) {
kprintf("cache_inefficient_scan: setvp %s/%s already set %p/%p\n",
nch->ncp->nc_name, rncp.ncp->nc_name, dvp,
rncp.ncp->nc_vp);
}
}
if (rncp.ncp->nc_vp == NULL)
error = rncp.ncp->nc_error;
cache_put(&rncp);
} else {
kprintf("cache_inefficient_scan: dvp %p NOT FOUND in %s\n",
dvp, nch->ncp->nc_name);
error = ENOENT;
}
return (error);
}
static int
cache_zap(struct namecache *ncp)
{
struct namecache *par;
struct nchash_head *nchpp;
int refcmp;
int nonblock = 1;
int res = 0;
again:
_cache_setunresolved(ncp, 1);
KKASSERT(ncp->nc_flag & NCF_UNRESOLVED);
KKASSERT(ncp->nc_refs > 0);
nchpp = NULL;
if ((par = ncp->nc_parent) != NULL) {
if (nonblock) {
if (_cache_lock_nonblock(par)) {
ncp->nc_flag |= NCF_DEFEREDZAP;
atomic_add_long(
&pcpu_ncache[mycpu->gd_cpuid].numdefered,
1);
_cache_unlock(ncp);
_cache_drop(ncp);
return res;
}
_cache_hold(par);
} else {
_cache_hold(par);
_cache_lock(par);
}
nchpp = ncp->nc_head;
spin_lock(&nchpp->spin);
}
if (par)
refcmp = 2;
else
refcmp = 1;
if (ncp->nc_refs != refcmp || TAILQ_FIRST(&ncp->nc_list)) {
if (par) {
spin_unlock(&nchpp->spin);
_cache_put(par);
}
_cache_unlock(ncp);
_cache_drop(ncp);
return res;
}
if (par) {
KKASSERT(nchpp == ncp->nc_head);
_cache_unlink_parent(par, ncp, nchpp);
} else {
KKASSERT(ncp->nc_head == NULL);
}
if (ncp->nc_refs != refcmp) {
panic("cache_zap: %p bad refs %d (expected %d)\n",
ncp, ncp->nc_refs, refcmp);
}
ncp->nc_refs = -1;
if (ncp->nc_name)
kfree(ncp->nc_name, M_VFSCACHEAUX);
kfree_obj(ncp, M_VFSCACHE);
res = 1;
if (par) {
refcmp = 1;
if (par->nc_parent)
++refcmp;
par->nc_flag &= ~NCF_DEFEREDZAP;
if ((par->nc_flag & NCF_UNRESOLVED) &&
par->nc_refs == refcmp &&
TAILQ_EMPTY(&par->nc_list))
{
ncp = par;
goto again;
}
_cache_unlock(par);
_cache_drop(par);
}
return 1;
}
typedef enum { CHI_LOW, CHI_HIGH } cache_hs_t;
static cache_hs_t neg_cache_hysteresis_state[2] = { CHI_LOW, CHI_LOW };
static cache_hs_t pos_cache_hysteresis_state[2] = { CHI_LOW, CHI_LOW };
static cache_hs_t exc_cache_hysteresis_state[2] = { CHI_LOW, CHI_LOW };
static int cache_hyst_run[2];
void
cache_hysteresis(int critpath)
{
long poslimit;
long exclimit;
long neglimit;
long xnumunres;
long xnumleafs;
long clean_neg;
long clean_unres;
long clean_excess;
if (atomic_swap_int(&cache_hyst_run[critpath], 1) != 0)
return;
neglimit = maxvnodes / ncnegfactor;
if (critpath == 0)
neglimit = neglimit * 8 / 10;
clean_neg = 0;
switch(neg_cache_hysteresis_state[critpath]) {
case CHI_LOW:
if (vfscache_negs > MINNEG && vfscache_negs > neglimit) {
if (critpath)
clean_neg = ncnegflush;
else
clean_neg = ncnegflush +
vfscache_negs - neglimit;
neg_cache_hysteresis_state[critpath] = CHI_HIGH;
}
break;
case CHI_HIGH:
if (vfscache_negs > MINNEG * 9 / 10 &&
vfscache_negs * 9 / 10 > neglimit
) {
if (critpath)
clean_neg = ncnegflush;
else
clean_neg = ncnegflush +
vfscache_negs * 9 / 10 -
neglimit;
} else {
neg_cache_hysteresis_state[critpath] = CHI_LOW;
}
break;
}
if (clean_neg)
_cache_cleanneg(clean_neg);
if ((poslimit = ncposlimit) == 0)
poslimit = maxvnodes / ncposfactor;
if (critpath == 0)
poslimit = poslimit * 8 / 10;
xnumunres = vfscache_unres;
clean_unres = 0;
switch(pos_cache_hysteresis_state[critpath]) {
case CHI_LOW:
if (xnumunres > poslimit && xnumunres > MINPOS) {
if (critpath)
clean_unres = ncposflush;
else
clean_unres = ncposflush + xnumunres -
poslimit;
pos_cache_hysteresis_state[critpath] = CHI_HIGH;
}
break;
case CHI_HIGH:
if (xnumunres > poslimit * 5 / 6 && xnumunres > MINPOS) {
if (critpath)
clean_unres = ncposflush;
else
clean_unres = ncposflush + xnumunres -
poslimit * 5 / 6;
} else {
pos_cache_hysteresis_state[critpath] = CHI_LOW;
}
break;
}
exclimit = maxvnodes * 2;
if (critpath == 0)
exclimit = exclimit * 8 / 10;
xnumleafs = vfscache_leafs;
clean_excess = 0;
switch(exc_cache_hysteresis_state[critpath]) {
case CHI_LOW:
if (xnumleafs > exclimit && xnumleafs > MINPOS) {
if (critpath)
clean_excess = ncposflush;
else
clean_excess = ncposflush + xnumleafs -
exclimit;
exc_cache_hysteresis_state[critpath] = CHI_HIGH;
}
break;
case CHI_HIGH:
if (xnumleafs > exclimit * 5 / 6 && xnumleafs > MINPOS) {
if (critpath)
clean_excess = ncposflush;
else
clean_excess = ncposflush + xnumleafs -
exclimit * 5 / 6;
} else {
exc_cache_hysteresis_state[critpath] = CHI_LOW;
}
break;
}
if (clean_unres || clean_excess)
_cache_cleanpos(clean_unres, clean_excess);
if (pcpu_ncache[mycpu->gd_cpuid].numdefered + numdefered > neglimit) {
_cache_cleandefered();
}
atomic_swap_int(&cache_hyst_run[critpath], 0);
}
struct nchandle
cache_nlookup(struct nchandle *par_nch, struct nlcomponent *nlc)
{
struct nchandle nch;
struct namecache *ncp;
struct namecache *new_ncp;
struct namecache *rep_ncp;
struct nchash_head *nchpp;
struct mount *mp;
u_int32_t hash;
globaldata_t gd;
int par_locked;
int use_excl;
gd = mycpu;
mp = par_nch->mount;
par_locked = 0;
cache_hysteresis(1);
hash = fnv_32_buf(nlc->nlc_nameptr, nlc->nlc_namelen, FNV1_32_INIT);
hash = fnv_32_buf(&par_nch->ncp, sizeof(par_nch->ncp), hash);
new_ncp = NULL;
use_excl = 0;
nchpp = NCHHASH(hash);
restart:
rep_ncp = NULL;
if (use_excl)
spin_lock(&nchpp->spin);
else
spin_lock_shared(&nchpp->spin);
TAILQ_FOREACH_REVERSE(ncp, &nchpp->list, nchash_list, nc_hash) {
if (ncp->nc_parent == par_nch->ncp &&
ncp->nc_nlen == nlc->nlc_namelen) {
if (ncp->nc_flag & NCF_DESTROYED) {
if (ncp->nc_refs == 1 && rep_ncp == NULL)
rep_ncp = ncp;
continue;
}
if (bcmp(ncp->nc_name, nlc->nlc_nameptr, ncp->nc_nlen))
continue;
_cache_hold(ncp);
if (rep_ncp)
_cache_hold(rep_ncp);
if (use_excl)
spin_unlock(&nchpp->spin);
else
spin_unlock_shared(&nchpp->spin);
if (par_locked) {
_cache_unlock(par_nch->ncp);
par_locked = 0;
}
if (rep_ncp) {
if (_cache_lock_nonblock(rep_ncp) == 0) {
if (rep_ncp->nc_flag & NCF_DESTROYED) {
if (cache_zap(rep_ncp)) {
_cache_drop(ncp);
goto restart;
}
} else {
_cache_unlock(rep_ncp);
_cache_drop(rep_ncp);
}
} else {
_cache_drop(rep_ncp);
}
}
if (_cache_lock_special(ncp) == 0) {
if (ncp->nc_parent != par_nch->ncp ||
ncp->nc_nlen != nlc->nlc_namelen ||
bcmp(ncp->nc_name, nlc->nlc_nameptr,
ncp->nc_nlen) ||
(ncp->nc_flag & NCF_DESTROYED)) {
_cache_put(ncp);
goto restart;
}
_cache_auto_unresolve(mp, ncp);
if (new_ncp) {
_cache_free(new_ncp);
new_ncp = NULL;
}
goto found;
}
_cache_get(ncp);
_cache_put(ncp);
_cache_drop(ncp);
goto restart;
}
}
if (rep_ncp && use_excl) {
if (_cache_lock_nonblock(rep_ncp) == 0) {
_cache_hold(rep_ncp);
if (rep_ncp->nc_parent == par_nch->ncp &&
rep_ncp->nc_nlen == nlc->nlc_namelen &&
(rep_ncp->nc_flag & NCF_DESTROYED) &&
rep_ncp->nc_refs == 2)
{
ncp = rep_ncp;
_cache_ncp_gen_enter(ncp);
bcopy(nlc->nlc_nameptr, ncp->nc_name,
nlc->nlc_namelen);
ncp->nc_flag &= ~NCF_DESTROYED;
spin_unlock(&nchpp->spin);
_cache_setunresolved(ncp, 0);
ncp->nc_flag = NCF_UNRESOLVED;
ncp->nc_error = ENOTCONN;
_cache_ncp_gen_exit(ncp);
if (par_locked) {
_cache_unlock(par_nch->ncp);
par_locked = 0;
}
if (new_ncp) {
_cache_free(new_ncp);
new_ncp = NULL;
}
goto found;
}
_cache_put(rep_ncp);
}
}
if (new_ncp == NULL) {
if (use_excl)
spin_unlock(&nchpp->spin);
else
spin_unlock_shared(&nchpp->spin);
if (rep_ncp == NULL) {
new_ncp = cache_alloc(nlc->nlc_namelen);
if (nlc->nlc_namelen) {
bcopy(nlc->nlc_nameptr, new_ncp->nc_name,
nlc->nlc_namelen);
new_ncp->nc_name[nlc->nlc_namelen] = 0;
}
}
use_excl = 1;
goto restart;
}
if (par_locked == 0) {
spin_unlock(&nchpp->spin);
_cache_lock(par_nch->ncp);
par_locked = 1;
goto restart;
}
ncp = new_ncp;
++ncp->nc_refs;
_cache_link_parent(ncp, par_nch->ncp, nchpp);
spin_unlock(&nchpp->spin);
_cache_unlock(par_nch->ncp);
found:
if (ncp->nc_flag & NCF_UNRESOLVED)
++gd->gd_nchstats->ncs_miss;
else if (ncp->nc_vp)
++gd->gd_nchstats->ncs_goodhits;
else
++gd->gd_nchstats->ncs_neghits;
nch.mount = mp;
nch.ncp = ncp;
_cache_mntref(nch.mount);
return(nch);
}
int
cache_nlookup_maybe_shared(struct nchandle *par_nch,
struct nlcomponent *nlc,
int excl, struct nchandle *res_nch)
{
struct namecache *ncp;
struct nchash_head *nchpp;
struct mount *mp;
u_int32_t hash;
globaldata_t gd;
if (ncp_shared_lock_disable || excl)
return(EWOULDBLOCK);
gd = mycpu;
mp = par_nch->mount;
cache_hysteresis(1);
hash = fnv_32_buf(nlc->nlc_nameptr, nlc->nlc_namelen, FNV1_32_INIT);
hash = fnv_32_buf(&par_nch->ncp, sizeof(par_nch->ncp), hash);
nchpp = NCHHASH(hash);
spin_lock_shared(&nchpp->spin);
TAILQ_FOREACH(ncp, &nchpp->list, nc_hash) {
if (ncp->nc_parent == par_nch->ncp &&
ncp->nc_nlen == nlc->nlc_namelen &&
bcmp(ncp->nc_name, nlc->nlc_nameptr, ncp->nc_nlen) == 0 &&
(ncp->nc_flag & NCF_DESTROYED) == 0
) {
_cache_hold(ncp);
spin_unlock_shared(&nchpp->spin);
if (_cache_lock_shared_special(ncp) == 0) {
if (ncp->nc_parent == par_nch->ncp &&
ncp->nc_nlen == nlc->nlc_namelen &&
bcmp(ncp->nc_name, nlc->nlc_nameptr,
ncp->nc_nlen) == 0 &&
(ncp->nc_flag & NCF_DESTROYED) == 0 &&
(ncp->nc_flag & NCF_UNRESOLVED) == 0 &&
_cache_auto_unresolve_test(mp, ncp) == 0)
{
goto found;
}
_cache_unlock(ncp);
}
_cache_drop(ncp);
return(EWOULDBLOCK);
}
}
spin_unlock_shared(&nchpp->spin);
return(EWOULDBLOCK);
found:
res_nch->mount = mp;
res_nch->ncp = ncp;
++gd->gd_nchstats->ncs_goodhits;
_cache_mntref(res_nch->mount);
KKASSERT(ncp->nc_error != EWOULDBLOCK);
return(ncp->nc_error);
}
struct nchandle
cache_nlookup_nonblock(struct nchandle *par_nch, struct nlcomponent *nlc)
{
struct nchandle nch;
struct namecache *ncp;
struct namecache *new_ncp;
struct nchash_head *nchpp;
struct mount *mp;
u_int32_t hash;
globaldata_t gd;
int par_locked;
gd = mycpu;
mp = par_nch->mount;
par_locked = 0;
hash = fnv_32_buf(nlc->nlc_nameptr, nlc->nlc_namelen, FNV1_32_INIT);
hash = fnv_32_buf(&par_nch->ncp, sizeof(par_nch->ncp), hash);
new_ncp = NULL;
nchpp = NCHHASH(hash);
restart:
spin_lock(&nchpp->spin);
TAILQ_FOREACH(ncp, &nchpp->list, nc_hash) {
if (ncp->nc_parent == par_nch->ncp &&
ncp->nc_nlen == nlc->nlc_namelen &&
bcmp(ncp->nc_name, nlc->nlc_nameptr, ncp->nc_nlen) == 0 &&
(ncp->nc_flag & NCF_DESTROYED) == 0
) {
_cache_hold(ncp);
spin_unlock(&nchpp->spin);
if (par_locked) {
_cache_unlock(par_nch->ncp);
par_locked = 0;
}
if (_cache_lock_special(ncp) == 0) {
if (ncp->nc_parent != par_nch->ncp ||
ncp->nc_nlen != nlc->nlc_namelen ||
bcmp(ncp->nc_name, nlc->nlc_nameptr, ncp->nc_nlen) ||
(ncp->nc_flag & NCF_DESTROYED)) {
kprintf("cache_lookup_nonblock: "
"ncp-race %p %*.*s\n",
ncp,
nlc->nlc_namelen,
nlc->nlc_namelen,
nlc->nlc_nameptr);
_cache_unlock(ncp);
_cache_drop(ncp);
goto failed;
}
_cache_auto_unresolve(mp, ncp);
if (new_ncp) {
_cache_free(new_ncp);
new_ncp = NULL;
}
goto found;
}
_cache_drop(ncp);
goto failed;
}
}
if (new_ncp == NULL) {
spin_unlock(&nchpp->spin);
new_ncp = cache_alloc(nlc->nlc_namelen);
if (nlc->nlc_namelen) {
bcopy(nlc->nlc_nameptr, new_ncp->nc_name,
nlc->nlc_namelen);
new_ncp->nc_name[nlc->nlc_namelen] = 0;
}
goto restart;
}
if (par_locked == 0) {
spin_unlock(&nchpp->spin);
if (_cache_lock_nonblock(par_nch->ncp) == 0) {
par_locked = 1;
goto restart;
}
goto failed;
}
ncp = new_ncp;
++ncp->nc_refs;
_cache_link_parent(ncp, par_nch->ncp, nchpp);
spin_unlock(&nchpp->spin);
_cache_unlock(par_nch->ncp);
found:
if (ncp->nc_flag & NCF_UNRESOLVED)
++gd->gd_nchstats->ncs_miss;
else if (ncp->nc_vp)
++gd->gd_nchstats->ncs_goodhits;
else
++gd->gd_nchstats->ncs_neghits;
nch.mount = mp;
nch.ncp = ncp;
_cache_mntref(nch.mount);
return(nch);
failed:
if (new_ncp) {
_cache_free(new_ncp);
new_ncp = NULL;
}
nch.mount = NULL;
nch.ncp = NULL;
return(nch);
}
struct nchandle
cache_nlookup_nonlocked(struct nchandle *par_nch, struct nlcomponent *nlc)
{
struct nchandle nch;
struct namecache *ncp;
struct nchash_head *nchpp;
struct mount *mp;
u_int32_t hash;
globaldata_t gd;
gd = mycpu;
mp = par_nch->mount;
hash = fnv_32_buf(nlc->nlc_nameptr, nlc->nlc_namelen, FNV1_32_INIT);
hash = fnv_32_buf(&par_nch->ncp, sizeof(par_nch->ncp), hash);
nchpp = NCHHASH(hash);
spin_lock_shared(&nchpp->spin);
TAILQ_FOREACH(ncp, &nchpp->list, nc_hash) {
if (ncp->nc_parent == par_nch->ncp &&
ncp->nc_nlen == nlc->nlc_namelen &&
bcmp(ncp->nc_name, nlc->nlc_nameptr, ncp->nc_nlen) == 0 &&
(ncp->nc_flag & NCF_DESTROYED) == 0
) {
if (_cache_auto_unresolve_test(par_nch->mount, ncp))
break;
if (ncp->nc_flag & NCF_UNRESOLVED)
break;
_cache_hold(ncp);
spin_unlock_shared(&nchpp->spin);
if (_cache_lockstatus(ncp) < 0 ||
(ncp->nc_flag & (NCF_DESTROYED | NCF_UNRESOLVED)))
{
if ((ncvp_debug & 4) &&
(ncp->nc_flag &
(NCF_DESTROYED | NCF_UNRESOLVED)))
{
kprintf("ncp state change: %p %08x %d %s\n",
ncp, ncp->nc_flag, ncp->nc_error,
ncp->nc_name);
}
_cache_drop(ncp);
spin_lock_shared(&nchpp->spin);
break;
}
goto found;
}
}
spin_unlock_shared(&nchpp->spin);
nch.mount = NULL;
nch.ncp = NULL;
return nch;
found:
if (ncp->nc_flag & NCF_UNRESOLVED)
++gd->gd_nchstats->ncs_miss;
else if (ncp->nc_vp)
++gd->gd_nchstats->ncs_goodhits;
else
++gd->gd_nchstats->ncs_neghits;
nch.mount = mp;
nch.ncp = ncp;
_cache_mntref(nch.mount);
return(nch);
}
struct findmount_info {
struct mount *result;
struct mount *nch_mount;
struct namecache *nch_ncp;
};
static __inline
struct ncmount_cache *
ncmount_cache_lookup4(struct mount *mp, struct namecache *ncp)
{
uint32_t hash;
hash = iscsi_crc32(&mp, sizeof(mp));
hash = iscsi_crc32_ext(&ncp, sizeof(ncp), hash);
hash ^= hash >> 16;
hash = hash & ((NCMOUNT_NUMCACHE - 1) & ~(NCMOUNT_SET - 1));
return (&ncmount_cache[hash]);
}
static
struct ncmount_cache *
ncmount_cache_lookup(struct mount *mp, struct namecache *ncp)
{
struct ncmount_cache *ncc;
struct ncmount_cache *best;
int delta;
int best_delta;
int i;
ncc = ncmount_cache_lookup4(mp, ncp);
if (ncc->ncp == ncp && ncc->mp == mp)
return ncc;
delta = (int)(ticks - ncc->ticks);
if (delta < -2)
ncc->ticks = ticks;
best = ncc;
best_delta = delta;
for (i = 1; i < NCMOUNT_SET; ++i) {
++ncc;
if (ncc->ncp == ncp && ncc->mp == mp)
return ncc;
delta = (int)(ticks - ncc->ticks);
if (delta < -2)
ncc->ticks = ticks;
if (delta > best_delta) {
best_delta = delta;
best = ncc;
}
}
return best;
}
struct mount *
cache_findmount(struct nchandle *nch)
{
struct findmount_info info;
struct ncmount_cache *ncc;
struct ncmount_cache ncc_copy;
struct mount *target;
struct pcpu_ncache *pcpu;
struct spinlock *spinlk;
int update;
pcpu = pcpu_ncache;
if (ncmount_cache_enable == 0 || pcpu == NULL) {
ncc = NULL;
goto skip;
}
pcpu += mycpu->gd_cpuid;
again:
ncc = ncmount_cache_lookup(nch->mount, nch->ncp);
if (ncc->ncp == nch->ncp && ncc->mp == nch->mount) {
found:
update = ncc->updating;
if (__predict_true(spin_trylock(&pcpu->umount_spin))) {
spinlk = &pcpu->umount_spin;
} else {
spinlk = &ncc->spin;
spin_lock_shared(spinlk);
}
if (update & 1) {
spin_unlock_any(spinlk);
goto skip;
}
ncc_copy = *ncc;
cpu_lfence();
if (ncc->updating != update) {
spin_unlock_any(spinlk);
goto again;
}
if (ncc_copy.ncp != nch->ncp || ncc_copy.mp != nch->mount) {
spin_unlock_any(spinlk);
goto again;
}
if (ncc_copy.isneg == 0) {
target = ncc_copy.mp_target;
if (target->mnt_ncmounton.mount == nch->mount &&
target->mnt_ncmounton.ncp == nch->ncp) {
if (ncc->ticks != (int)ticks)
ncc->ticks = (int)ticks;
_cache_mntref(target);
}
} else {
if (ncc->ticks != (int)ticks)
ncc->ticks = (int)ticks;
target = NULL;
}
spin_unlock_any(spinlk);
return target;
}
skip:
info.result = NULL;
info.nch_mount = nch->mount;
info.nch_ncp = nch->ncp;
mountlist_scan(cache_findmount_callback, &info,
MNTSCAN_FORWARD | MNTSCAN_NOBUSY | MNTSCAN_NOUNLOCK);
ncc = ncmount_cache_lookup(nch->mount, nch->ncp);
if (ncc->ncp == nch->ncp && ncc->mp == nch->mount) {
if (info.result)
atomic_add_int(&info.result->mnt_refs, -1);
goto found;
}
if ((info.result == NULL ||
(info.result->mnt_kern_flag & MNTK_UNMOUNT) == 0)) {
spin_lock(&ncc->spin);
atomic_add_int_nonlocked(&ncc->updating, 1);
cpu_sfence();
KKASSERT(ncc->updating & 1);
if (ncc->mp != nch->mount) {
if (ncc->mp)
atomic_add_int(&ncc->mp->mnt_refs, -1);
atomic_add_int(&nch->mount->mnt_refs, 1);
ncc->mp = nch->mount;
}
ncc->ncp = nch->ncp;
ncc->ticks = (int)ticks;
if (info.result) {
ncc->isneg = 0;
if (ncc->mp_target != info.result) {
if (ncc->mp_target)
atomic_add_int(&ncc->mp_target->mnt_refs, -1);
ncc->mp_target = info.result;
atomic_add_int(&info.result->mnt_refs, 1);
}
} else {
ncc->isneg = 1;
if (ncc->mp_target) {
atomic_add_int(&ncc->mp_target->mnt_refs, -1);
ncc->mp_target = NULL;
}
}
cpu_sfence();
atomic_add_int_nonlocked(&ncc->updating, 1);
spin_unlock(&ncc->spin);
}
return(info.result);
}
static
int
cache_findmount_callback(struct mount *mp, void *data)
{
struct findmount_info *info = data;
if (mp->mnt_ncmounton.mount == info->nch_mount &&
mp->mnt_ncmounton.ncp == info->nch_ncp
) {
info->result = mp;
_cache_mntref(mp);
return(-1);
}
return(0);
}
void
cache_dropmount(struct mount *mp)
{
_cache_mntrel(mp);
}
void
cache_ismounting(struct mount *mp)
{
struct ncmount_cache *ncc;
struct mount *ncc_mp;
int i;
if (pcpu_ncache == NULL)
return;
for (i = 0; i < NCMOUNT_NUMCACHE; ++i) {
ncc = &ncmount_cache[i];
if (ncc->mp != mp->mnt_ncmounton.mount ||
ncc->ncp != mp->mnt_ncmounton.ncp) {
continue;
}
spin_lock(&ncc->spin);
atomic_add_int_nonlocked(&ncc->updating, 1);
cpu_sfence();
KKASSERT(ncc->updating & 1);
if (ncc->mp != mp->mnt_ncmounton.mount ||
ncc->ncp != mp->mnt_ncmounton.ncp) {
cpu_sfence();
++ncc->updating;
spin_unlock(&ncc->spin);
continue;
}
ncc_mp = ncc->mp;
ncc->ncp = NULL;
ncc->mp = NULL;
if (ncc_mp)
atomic_add_int(&ncc_mp->mnt_refs, -1);
ncc_mp = ncc->mp_target;
ncc->mp_target = NULL;
if (ncc_mp)
atomic_add_int(&ncc_mp->mnt_refs, -1);
ncc->ticks = (int)ticks - hz * 120;
cpu_sfence();
atomic_add_int_nonlocked(&ncc->updating, 1);
spin_unlock(&ncc->spin);
}
ncc = ncmount_cache_lookup(mp->mnt_ncmounton.mount,
mp->mnt_ncmounton.ncp);
spin_lock(&ncc->spin);
atomic_add_int_nonlocked(&ncc->updating, 1);
cpu_sfence();
KKASSERT(ncc->updating & 1);
if (ncc->mp)
atomic_add_int(&ncc->mp->mnt_refs, -1);
atomic_add_int(&mp->mnt_ncmounton.mount->mnt_refs, 1);
ncc->mp = mp->mnt_ncmounton.mount;
ncc->ncp = mp->mnt_ncmounton.ncp;
ncc->ticks = (int)ticks;
ncc->isneg = 0;
if (ncc->mp_target != mp) {
if (ncc->mp_target)
atomic_add_int(&ncc->mp_target->mnt_refs, -1);
ncc->mp_target = mp;
atomic_add_int(&mp->mnt_refs, 1);
}
cpu_sfence();
atomic_add_int_nonlocked(&ncc->updating, 1);
spin_unlock(&ncc->spin);
}
void
cache_unmounting(struct mount *mp)
{
struct ncmount_cache *ncc;
struct pcpu_ncache *pcpu;
struct mount *ncc_mp;
int i;
pcpu = pcpu_ncache;
if (pcpu == NULL)
return;
for (i = 0; i < ncpus; ++i)
spin_lock(&pcpu[i].umount_spin);
for (i = 0; i < NCMOUNT_NUMCACHE; ++i) {
ncc = &ncmount_cache[i];
if (ncc->mp != mp && ncc->mp_target != mp)
continue;
spin_lock(&ncc->spin);
atomic_add_int_nonlocked(&ncc->updating, 1);
cpu_sfence();
if (ncc->mp != mp && ncc->mp_target != mp) {
atomic_add_int_nonlocked(&ncc->updating, 1);
cpu_sfence();
spin_unlock(&ncc->spin);
continue;
}
ncc_mp = ncc->mp;
ncc->ncp = NULL;
ncc->mp = NULL;
if (ncc_mp)
atomic_add_int(&ncc_mp->mnt_refs, -1);
ncc_mp = ncc->mp_target;
ncc->mp_target = NULL;
if (ncc_mp)
atomic_add_int(&ncc_mp->mnt_refs, -1);
ncc->ticks = (int)ticks - hz * 120;
cpu_sfence();
atomic_add_int_nonlocked(&ncc->updating, 1);
spin_unlock(&ncc->spin);
}
for (i = 0; i < ncpus; ++i)
spin_unlock(&pcpu[i].umount_spin);
}
int
cache_resolve(struct nchandle *nch, u_int *genp, struct ucred *cred)
{
struct namecache *par_tmp;
struct namecache *par;
struct namecache *ncp;
struct nchandle nctmp;
struct mount *mp;
struct vnode *dvp;
int error;
ncp = nch->ncp;
mp = nch->mount;
KKASSERT(_cache_lockstatus(ncp) == LK_EXCLUSIVE);
restart:
if ((ncp->nc_flag & NCF_UNRESOLVED) == 0) {
if (ncp->nc_vp && (ncp->nc_vp->v_flag & VRECLAIMED)) {
_cache_ncp_gen_enter(ncp);
_cache_setunresolved(ncp, 0);
if ((ncp->nc_flag & NCF_UNRESOLVED) == 0) {
_cache_ncp_gen_exit(ncp);
*genp += 4;
return (ncp->nc_error);
}
} else if ((ncp->nc_flag & NCF_UNRESOLVED) == 0) {
return (ncp->nc_error);
} else {
_cache_ncp_gen_enter(ncp);
}
} else {
_cache_ncp_gen_enter(ncp);
}
*genp += 4;
if (ncp->nc_flag & NCF_DESTROYED) {
_cache_ncp_gen_exit(ncp);
return(EINVAL);
}
if (ncp == mp->mnt_ncmountpt.ncp) {
error = cache_resolve_mp(mp, 0);
_cache_ncp_gen_exit(ncp);
return error;
}
if (ncp->nc_parent == NULL) {
kprintf("EXDEV case 1 %p %*.*s\n", ncp,
ncp->nc_nlen, ncp->nc_nlen, ncp->nc_name);
ncp->nc_error = EXDEV;
_cache_ncp_gen_exit(ncp);
return(ncp->nc_error);
}
while ((dvp = cache_dvpref(ncp)) == NULL) {
if (ncp->nc_parent->nc_flag & NCF_DESTROYED) {
if (ncvp_debug & 8) {
kprintf("nc_parent destroyed: %s/%s\n",
ncp->nc_parent->nc_name, ncp->nc_name);
}
_cache_ncp_gen_exit(ncp);
return(ENOENT);
}
par = ncp->nc_parent;
_cache_hold(par);
_cache_lock(par);
while ((par_tmp = par->nc_parent) != NULL &&
par_tmp->nc_vp == NULL) {
_cache_hold(par_tmp);
_cache_lock(par_tmp);
_cache_put(par);
par = par_tmp;
}
if (par->nc_parent == NULL) {
kprintf("EXDEV case 2 %*.*s\n",
par->nc_nlen, par->nc_nlen, par->nc_name);
_cache_put(par);
_cache_ncp_gen_exit(ncp);
return (EXDEV);
}
_cache_get(par);
_cache_put(par);
if (par == nch->mount->mnt_ncmountpt.ncp) {
cache_resolve_mp(nch->mount, 0);
} else if ((dvp = cache_dvpref(par)) == NULL) {
kprintf("[diagnostic] cache_resolve: raced on %*.*s\n",
par->nc_nlen, par->nc_nlen, par->nc_name);
_cache_put(par);
continue;
} else {
if (par->nc_flag & NCF_UNRESOLVED) {
nctmp.mount = mp;
nctmp.ncp = par;
par->nc_error = VOP_NRESOLVE(&nctmp, dvp, cred);
}
vrele(dvp);
}
if ((error = par->nc_error) != 0) {
if (par->nc_error != EAGAIN) {
kprintf("EXDEV case 3 %*.*s error %d\n",
par->nc_nlen, par->nc_nlen, par->nc_name,
par->nc_error);
_cache_put(par);
_cache_ncp_gen_exit(ncp);
return(error);
}
kprintf("[diagnostic] cache_resolve: EAGAIN par %p %*.*s\n",
par, par->nc_nlen, par->nc_nlen, par->nc_name);
}
_cache_put(par);
}
if (dvp) {
nctmp.mount = mp;
nctmp.ncp = ncp;
*genp += 4;
ncp->nc_error = VOP_NRESOLVE(&nctmp, dvp, cred);
vrele(dvp);
} else {
ncp->nc_error = EPERM;
}
if (ncp->nc_error == EAGAIN) {
kprintf("[diagnostic] cache_resolve: EAGAIN ncp %p %*.*s\n",
ncp, ncp->nc_nlen, ncp->nc_nlen, ncp->nc_name);
goto restart;
}
_cache_ncp_gen_exit(ncp);
return(ncp->nc_error);
}
static int
cache_resolve_mp(struct mount *mp, int adjgen)
{
struct namecache *ncp = mp->mnt_ncmountpt.ncp;
struct vnode *vp;
int error;
KKASSERT(mp != NULL);
if ((ncp->nc_flag & NCF_UNRESOLVED) == 0) {
if (ncp->nc_vp && (ncp->nc_vp->v_flag & VRECLAIMED))
_cache_setunresolved(ncp, adjgen);
}
if (ncp->nc_flag & NCF_UNRESOLVED) {
_cache_unlock(ncp);
while (vfs_busy(mp, 0))
;
_cache_lock(ncp);
error = VFS_ROOT(mp, &vp);
if (ncp->nc_flag & NCF_UNRESOLVED) {
ncp->nc_error = error;
if (error == 0) {
_cache_setvp(mp, ncp, vp, adjgen);
vput(vp);
} else {
kprintf("[diagnostic] cache_resolve_mp: failed"
" to resolve mount %p err=%d ncp=%p\n",
mp, error, ncp);
_cache_setvp(mp, ncp, NULL, adjgen);
}
} else if (error == 0) {
vput(vp);
}
vfs_unbusy(mp);
}
return(ncp->nc_error);
}
int
cache_resolve_dvp(struct nchandle *nch, struct ucred *cred, struct vnode **dvpp)
{
struct namecache *par_tmp;
struct namecache *par;
struct namecache *ncp;
struct nchandle nctmp;
struct mount *mp;
struct vnode *dvp;
int error;
*dvpp = NULL;
ncp = nch->ncp;
mp = nch->mount;
KKASSERT(_cache_lockstatus(ncp) == LK_EXCLUSIVE);
if (ncp == mp->mnt_ncmountpt.ncp)
return 0;
if (ncp->nc_flag & NCF_DESTROYED)
return(EINVAL);
if (ncp->nc_parent == NULL)
return 0;
while ((dvp = cache_dvpref(ncp)) == NULL) {
if (ncp->nc_parent->nc_flag & NCF_DESTROYED)
return(ENOENT);
par = ncp->nc_parent;
_cache_hold(par);
_cache_lock(par);
while ((par_tmp = par->nc_parent) != NULL &&
par_tmp->nc_vp == NULL) {
_cache_hold(par_tmp);
_cache_lock(par_tmp);
_cache_put(par);
par = par_tmp;
}
if (par->nc_parent == NULL) {
kprintf("EXDEV case 2 %*.*s\n",
par->nc_nlen, par->nc_nlen, par->nc_name);
_cache_put(par);
return (EXDEV);
}
_cache_get(par);
_cache_put(par);
if (par == nch->mount->mnt_ncmountpt.ncp) {
cache_resolve_mp(nch->mount, 1);
} else if ((dvp = cache_dvpref(par)) == NULL) {
kprintf("[diagnostic] cache_resolve: raced on %*.*s\n",
par->nc_nlen, par->nc_nlen, par->nc_name);
_cache_put(par);
continue;
} else {
if (par->nc_flag & NCF_UNRESOLVED) {
nctmp.mount = mp;
nctmp.ncp = par;
par->nc_error = VOP_NRESOLVE(&nctmp, dvp, cred);
}
vrele(dvp);
}
if ((error = par->nc_error) != 0) {
if (par->nc_error != EAGAIN) {
kprintf("EXDEV case 3 %*.*s error %d\n",
par->nc_nlen, par->nc_nlen, par->nc_name,
par->nc_error);
_cache_put(par);
return(error);
}
kprintf("[diagnostic] cache_resolve: EAGAIN par %p %*.*s\n",
par, par->nc_nlen, par->nc_nlen, par->nc_name);
}
_cache_put(par);
}
*dvpp = dvp;
return 0;
}
static void
_cache_cleanneg(long count)
{
struct pcpu_ncache *pn;
struct namecache *ncp;
static uint32_t neg_rover;
uint32_t n;
long vnegs;
n = neg_rover++;
cpu_ccfence();
n = n % (uint32_t)ncpus;
vnegs = vfscache_negs;
cpu_ccfence();
if (vnegs <= MINNEG)
vnegs = MINNEG;
if (count < 1)
count = 1;
pn = &pcpu_ncache[n];
spin_lock(&pn->neg_spin);
count = pn->neg_count * count / vnegs + 1;
spin_unlock(&pn->neg_spin);
while (count > 0) {
spin_lock(&pn->neg_spin);
ncp = TAILQ_FIRST(&pn->neg_list);
if (ncp == NULL) {
spin_unlock(&pn->neg_spin);
break;
}
TAILQ_REMOVE(&pn->neg_list, ncp, nc_vnode);
TAILQ_INSERT_TAIL(&pn->neg_list, ncp, nc_vnode);
_cache_hold(ncp);
spin_unlock(&pn->neg_spin);
if (_cache_lock_special(ncp) == 0) {
if (ncp->nc_vp == NULL &&
ncp->nc_refs == 3 &&
(ncp->nc_flag & NCF_UNRESOLVED) == 0)
{
++pcpu_ncache[mycpu->gd_cpuid].clean_neg_count;
cache_zap(ncp);
} else {
_cache_unlock(ncp);
_cache_drop(ncp);
}
} else {
_cache_drop(ncp);
}
--count;
}
}
static void
_cache_cleanpos(long ucount, long xcount)
{
static volatile int rover;
struct nchash_head *nchpp;
struct namecache *ncp;
long count;
int rover_copy;
count = (ucount > xcount) ? ucount : xcount;
count = count * 4;
while (count > 0 && (ucount > 0 || xcount > 0)) {
rover_copy = atomic_fetchadd_int(&rover, 1);
cpu_ccfence();
nchpp = NCHHASH(rover_copy);
if (TAILQ_FIRST(&nchpp->list) == NULL) {
--count;
continue;
}
spin_lock(&nchpp->spin);
ncp = TAILQ_FIRST(&nchpp->list);
while (ncp && (ncp->nc_flag & NCF_DUMMY))
ncp = TAILQ_NEXT(ncp, nc_hash);
if (ncp) {
TAILQ_REMOVE(&nchpp->list, ncp, nc_hash);
TAILQ_INSERT_TAIL(&nchpp->list, ncp, nc_hash);
if (ncp->nc_refs != ncpbaserefs(ncp)) {
ncp = NULL;
} else if (ucount > 0 &&
(ncp->nc_flag & NCF_UNRESOLVED))
{
--ucount;
--xcount;
_cache_hold(ncp);
} else if (xcount > 0) {
--xcount;
_cache_hold(ncp);
} else {
ncp = NULL;
}
}
spin_unlock(&nchpp->spin);
if (ncp) {
if (_cache_lock_special(ncp) == 0) {
if (ncp->nc_refs == 1 + ncpbaserefs(ncp)) {
++pcpu_ncache[mycpu->gd_cpuid].
clean_pos_count;
cache_zap(ncp);
} else {
_cache_unlock(ncp);
_cache_drop(ncp);
}
} else {
_cache_drop(ncp);
}
}
--count;
}
}
static void
_cache_cleandefered(void)
{
struct nchash_head *nchpp;
struct namecache *ncp;
struct namecache dummy;
int i;
numdefered = 0;
pcpu_ncache[mycpu->gd_cpuid].numdefered = 0;
bzero(&dummy, sizeof(dummy));
dummy.nc_flag = NCF_DESTROYED | NCF_DUMMY;
dummy.nc_refs = 1;
for (i = 0; i <= nchash; ++i) {
nchpp = &nchashtbl[i];
spin_lock(&nchpp->spin);
TAILQ_INSERT_HEAD(&nchpp->list, &dummy, nc_hash);
ncp = &dummy;
while ((ncp = TAILQ_NEXT(ncp, nc_hash)) != NULL) {
if ((ncp->nc_flag & NCF_DEFEREDZAP) == 0)
continue;
TAILQ_REMOVE(&nchpp->list, &dummy, nc_hash);
TAILQ_INSERT_AFTER(&nchpp->list, ncp, &dummy, nc_hash);
_cache_hold(ncp);
spin_unlock(&nchpp->spin);
if (_cache_lock_nonblock(ncp) == 0) {
ncp->nc_flag &= ~NCF_DEFEREDZAP;
_cache_unlock(ncp);
}
_cache_drop(ncp);
spin_lock(&nchpp->spin);
ncp = &dummy;
}
TAILQ_REMOVE(&nchpp->list, &dummy, nc_hash);
spin_unlock(&nchpp->spin);
}
}
void
nchinit(void)
{
struct pcpu_ncache *pn;
globaldata_t gd;
int i;
pcpu_ncache = kmalloc(sizeof(*pcpu_ncache) * ncpus,
M_VFSCACHEAUX, M_WAITOK|M_ZERO);
for (i = 0; i < ncpus; ++i) {
pn = &pcpu_ncache[i];
TAILQ_INIT(&pn->neg_list);
spin_init(&pn->neg_spin, "ncneg");
spin_init(&pn->umount_spin, "ncumm");
}
for (i = 0; i < ncpus; ++i) {
gd = globaldata_find(i);
gd->gd_nchstats = &nchstats[i];
}
nchashtbl = hashinit_ext(vfs_inodehashsize(),
sizeof(struct nchash_head),
M_VFSCACHEAUX, &nchash);
for (i = 0; i <= (int)nchash; ++i) {
TAILQ_INIT(&nchashtbl[i].list);
spin_init(&nchashtbl[i].spin, "nchinit_hash");
}
for (i = 0; i < NCMOUNT_NUMCACHE; ++i)
spin_init(&ncmount_cache[i].spin, "nchinit_cache");
nclockwarn = 5 * hz;
}
void
cache_allocroot(struct nchandle *nch, struct mount *mp, struct vnode *vp)
{
nch->ncp = cache_alloc(0);
nch->mount = mp;
_cache_mntref(mp);
if (vp)
_cache_setvp(nch->mount, nch->ncp, vp, 1);
}
void
vfs_cache_setroot(struct vnode *nvp, struct nchandle *nch)
{
struct vnode *ovp;
struct nchandle onch;
ovp = rootvnode;
onch = rootnch;
rootvnode = nvp;
if (nch)
rootnch = *nch;
else
cache_zero(&rootnch);
if (ovp)
vrele(ovp);
if (onch.ncp)
cache_drop(&onch);
}
void
cache_purge(struct vnode *vp)
{
cache_inval_vp(vp, CINV_DESTROY | CINV_CHILDREN);
}
__read_mostly static int disablecwd;
SYSCTL_INT(_debug, OID_AUTO, disablecwd, CTLFLAG_RW, &disablecwd, 0,
"Disable getcwd");
int
sys___getcwd(struct sysmsg *sysmsg, const struct __getcwd_args *uap)
{
u_int buflen;
int error;
char *buf;
char *bp;
if (disablecwd)
return (ENODEV);
buflen = uap->buflen;
if (buflen == 0)
return (EINVAL);
if (buflen > MAXPATHLEN)
buflen = MAXPATHLEN;
buf = kmalloc(buflen, M_TEMP, M_WAITOK);
bp = kern_getcwd(buf, buflen, &error);
if (error == 0)
error = copyout(bp, uap->buf, strlen(bp) + 1);
kfree(buf, M_TEMP);
return (error);
}
char *
kern_getcwd(char *buf, size_t buflen, int *error)
{
struct proc *p = curproc;
char *bp;
int i, slash_prefixed;
struct filedesc *fdp;
struct nchandle nch;
struct namecache *ncp;
bp = buf;
bp += buflen - 1;
*bp = '\0';
fdp = p->p_fd;
slash_prefixed = 0;
nch = fdp->fd_ncdir;
ncp = nch.ncp;
if (ncp)
_cache_hold(ncp);
while (ncp && (ncp != fdp->fd_nrdir.ncp ||
nch.mount != fdp->fd_nrdir.mount)
) {
if (ncp->nc_flag & NCF_DESTROYED) {
_cache_drop(ncp);
ncp = NULL;
break;
}
if (ncp == nch.mount->mnt_ncmountpt.ncp) {
nch = nch.mount->mnt_ncmounton;
_cache_drop(ncp);
ncp = nch.ncp;
if (ncp)
_cache_hold(ncp);
continue;
}
for (i = ncp->nc_nlen - 1; i >= 0; i--) {
if (bp == buf) {
*error = ERANGE;
bp = NULL;
goto done;
}
*--bp = ncp->nc_name[i];
}
if (bp == buf) {
*error = ERANGE;
bp = NULL;
goto done;
}
*--bp = '/';
slash_prefixed = 1;
while ((nch.ncp = ncp->nc_parent) != NULL) {
if (ncp_shared_lock_disable)
_cache_lock(ncp);
else
_cache_lock_shared(ncp);
if (nch.ncp != ncp->nc_parent) {
_cache_unlock(ncp);
continue;
}
_cache_hold(nch.ncp);
_cache_unlock(ncp);
break;
}
_cache_drop(ncp);
ncp = nch.ncp;
}
if (ncp == NULL) {
*error = ENOENT;
bp = NULL;
goto done;
}
if (!slash_prefixed) {
if (bp == buf) {
*error = ERANGE;
bp = NULL;
goto done;
}
*--bp = '/';
}
*error = 0;
done:
if (ncp)
_cache_drop(ncp);
return (bp);
}
__read_mostly static int disablefullpath;
SYSCTL_INT(_debug, OID_AUTO, disablefullpath, CTLFLAG_RW,
&disablefullpath, 0,
"Disable fullpath lookups");
int
cache_fullpath(struct proc *p, struct nchandle *nchp, struct nchandle *nchbase,
char **retbuf, char **freebuf, int guess)
{
struct nchandle fd_nrdir;
struct nchandle nch;
struct namecache *ncp;
struct mount *mp, *new_mp;
char *bp, *buf;
int slash_prefixed;
int error = 0;
int i;
*retbuf = NULL;
*freebuf = NULL;
buf = kmalloc(MAXPATHLEN, M_TEMP, M_WAITOK);
bp = buf + MAXPATHLEN - 1;
*bp = '\0';
if (nchbase)
fd_nrdir = *nchbase;
else if (p != NULL)
fd_nrdir = p->p_fd->fd_nrdir;
else
fd_nrdir = rootnch;
slash_prefixed = 0;
nch = *nchp;
ncp = nch.ncp;
if (ncp)
_cache_hold(ncp);
mp = nch.mount;
while (ncp && (ncp != fd_nrdir.ncp || mp != fd_nrdir.mount)) {
new_mp = NULL;
if (guess && (ncp->nc_flag & NCF_ISMOUNTPT)) {
new_mp = mount_get_by_nc(ncp);
}
if (ncp == mp->mnt_ncmountpt.ncp) {
new_mp = mp;
}
if (new_mp) {
nch = new_mp->mnt_ncmounton;
_cache_drop(ncp);
ncp = nch.ncp;
if (ncp)
_cache_hold(ncp);
mp = nch.mount;
continue;
}
for (i = ncp->nc_nlen - 1; i >= 0; i--) {
if (bp == buf) {
kfree(buf, M_TEMP);
error = ENOMEM;
goto done;
}
*--bp = ncp->nc_name[i];
}
if (bp == buf) {
kfree(buf, M_TEMP);
error = ENOMEM;
goto done;
}
*--bp = '/';
slash_prefixed = 1;
while ((nch.ncp = ncp->nc_parent) != NULL) {
_cache_lock_shared(ncp);
if (nch.ncp != ncp->nc_parent) {
_cache_unlock(ncp);
continue;
}
_cache_hold(nch.ncp);
_cache_unlock(ncp);
break;
}
_cache_drop(ncp);
ncp = nch.ncp;
}
if (ncp == NULL) {
kfree(buf, M_TEMP);
error = ENOENT;
goto done;
}
if (!slash_prefixed) {
if (bp == buf) {
kfree(buf, M_TEMP);
error = ENOMEM;
goto done;
}
*--bp = '/';
}
*retbuf = bp;
*freebuf = buf;
error = 0;
done:
if (ncp)
_cache_drop(ncp);
return(error);
}
int
vn_fullpath(struct proc *p, struct vnode *vn, char **retbuf,
char **freebuf, int guess)
{
struct namecache *ncp;
struct nchandle nch;
int error;
*freebuf = NULL;
if (disablefullpath)
return (ENODEV);
if (p == NULL)
return (EINVAL);
if (vn == NULL) {
if ((vn = p->p_textvp) == NULL)
return (EINVAL);
}
spin_lock_shared(&vn->v_spin);
TAILQ_FOREACH(ncp, &vn->v_namecache, nc_vnode) {
if (ncp->nc_nlen)
break;
}
if (ncp == NULL) {
spin_unlock_shared(&vn->v_spin);
return (EINVAL);
}
_cache_hold(ncp);
spin_unlock_shared(&vn->v_spin);
nch.ncp = ncp;
nch.mount = vn->v_mount;
error = cache_fullpath(p, &nch, NULL, retbuf, freebuf, guess);
_cache_drop(ncp);
return (error);
}
void
vfscache_rollup_cpu(struct globaldata *gd)
{
struct pcpu_ncache *pn;
long count;
if (pcpu_ncache == NULL)
return;
pn = &pcpu_ncache[gd->gd_cpuid];
if (pn->vfscache_count) {
count = atomic_swap_long(&pn->vfscache_count, 0);
atomic_add_long(&vfscache_count, count);
}
if (pn->vfscache_leafs) {
count = atomic_swap_long(&pn->vfscache_leafs, 0);
atomic_add_long(&vfscache_leafs, count);
}
if (pn->vfscache_unres) {
count = atomic_swap_long(&pn->vfscache_unres, 0);
atomic_add_long(&vfscache_unres, count);
}
if (pn->vfscache_negs) {
count = atomic_swap_long(&pn->vfscache_negs, 0);
atomic_add_long(&vfscache_negs, count);
}
if (pn->inv_kid_quick_count) {
count = atomic_swap_long(&pn->inv_kid_quick_count, 0);
atomic_add_long(&inv_kid_quick_count, count);
}
if (pn->inv_ncp_quick_count) {
count = atomic_swap_long(&pn->inv_ncp_quick_count, 0);
atomic_add_long(&inv_ncp_quick_count, count);
}
if (pn->clean_pos_count) {
count = atomic_swap_long(&pn->clean_pos_count, 0);
atomic_add_long(&clean_pos_count, count);
}
if (pn->clean_neg_count) {
count = atomic_swap_long(&pn->clean_neg_count, 0);
atomic_add_long(&clean_neg_count, count);
}
if (pn->numdefered) {
count = atomic_swap_long(&pn->numdefered, 0);
atomic_add_long(&numdefered, count);
}
}