#define __NAMECACHE_PRIVATE
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: vfs_cache.c,v 1.159 2024/12/07 02:27:38 riastradh Exp $");
#ifdef _KERNEL_OPT
#include "opt_ddb.h"
#include "opt_dtrace.h"
#endif
#include <sys/param.h>
#include <sys/types.h>
#include <sys/atomic.h>
#include <sys/callout.h>
#include <sys/cpu.h>
#include <sys/errno.h>
#include <sys/evcnt.h>
#include <sys/hash.h>
#include <sys/kernel.h>
#include <sys/mount.h>
#include <sys/mutex.h>
#include <sys/namei.h>
#include <sys/param.h>
#include <sys/pool.h>
#include <sys/sdt.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/vnode_impl.h>
#include <miscfs/genfs/genfs.h>
#ifdef _LP64
CTASSERT(sizeof(struct namecache) == 128);
#else
CTASSERT(sizeof(struct namecache) == 64);
#endif
CTASSERT(NC_NLEN_MASK >= MAXPATHLEN);
static void cache_activate(struct namecache *);
static void cache_update_stats(void *);
static int cache_compare_nodes(void *, const void *, const void *);
static void cache_deactivate(void);
static void cache_reclaim(void);
static int cache_stat_sysctl(SYSCTLFN_ARGS);
static pool_cache_t cache_pool __read_mostly;
enum cache_lru_id {
LRU_ACTIVE,
LRU_INACTIVE,
LRU_COUNT
};
static struct {
TAILQ_HEAD(, namecache) list[LRU_COUNT];
u_int count[LRU_COUNT];
} cache_lru __cacheline_aligned;
static kmutex_t cache_lru_lock __cacheline_aligned;
struct nchstats nchstats;
struct nchstats_percpu _NAMEI_CACHE_STATS(uint32_t);
struct nchcpu {
struct nchstats_percpu cur;
struct nchstats_percpu last;
};
static callout_t cache_stat_callout;
static kmutex_t cache_stat_lock __cacheline_aligned;
#define COUNT(f) do { \
lwp_t *l = curlwp; \
KPREEMPT_DISABLE(l); \
struct nchcpu *nchcpu = curcpu()->ci_data.cpu_nch; \
nchcpu->cur.f++; \
KPREEMPT_ENABLE(l); \
} while ( 0);
#define UPDATE(nchcpu, f) do { \
uint32_t cur = atomic_load_relaxed(&nchcpu->cur.f); \
nchstats.f += (uint32_t)(cur - nchcpu->last.f); \
nchcpu->last.f = cur; \
} while ( 0)
int cache_lru_maxdeact __read_mostly = 2;
int cache_lru_maxscan __read_mostly = 64;
int cache_maxlen __read_mostly = NC_NLEN_MASK;
int cache_stat_interval __read_mostly = 300;
static struct sysctllog *cache_sysctllog;
static const char cache_mp_name[] = "";
static const int cache_mp_nlen = sizeof(cache_mp_name) - 1;
static const rb_tree_ops_t cache_rbtree_ops = {
.rbto_compare_nodes = cache_compare_nodes,
.rbto_compare_key = cache_compare_nodes,
.rbto_node_offset = offsetof(struct namecache, nc_tree),
.rbto_context = NULL
};
SDT_PROBE_DEFINE1(vfs, namecache, invalidate, done, "struct vnode *");
SDT_PROBE_DEFINE1(vfs, namecache, purge, parents, "struct vnode *");
SDT_PROBE_DEFINE1(vfs, namecache, purge, children, "struct vnode *");
SDT_PROBE_DEFINE2(vfs, namecache, purge, name, "char *", "size_t");
SDT_PROBE_DEFINE1(vfs, namecache, purge, vfs, "struct mount *");
SDT_PROBE_DEFINE3(vfs, namecache, lookup, hit, "struct vnode *",
"char *", "size_t");
SDT_PROBE_DEFINE3(vfs, namecache, lookup, miss, "struct vnode *",
"char *", "size_t");
SDT_PROBE_DEFINE3(vfs, namecache, lookup, toolong, "struct vnode *",
"char *", "size_t");
SDT_PROBE_DEFINE2(vfs, namecache, revlookup, success, "struct vnode *",
"struct vnode *");
SDT_PROBE_DEFINE2(vfs, namecache, revlookup, fail, "struct vnode *",
"int");
SDT_PROBE_DEFINE2(vfs, namecache, prune, done, "int", "int");
SDT_PROBE_DEFINE3(vfs, namecache, enter, toolong, "struct vnode *",
"char *", "size_t");
SDT_PROBE_DEFINE3(vfs, namecache, enter, done, "struct vnode *",
"char *", "size_t");
static int
cache_compare_nodes(void *context, const void *n1, const void *n2)
{
const struct namecache *nc1 = n1;
const struct namecache *nc2 = n2;
if (nc1->nc_key < nc2->nc_key) {
return -1;
}
if (nc1->nc_key > nc2->nc_key) {
return 1;
}
KASSERT(NC_NLEN(nc1) == NC_NLEN(nc2));
return memcmp(nc1->nc_name, nc2->nc_name, NC_NLEN(nc1));
}
static uintptr_t
cache_key(const char *name, size_t nlen)
{
uintptr_t key;
KASSERT((nlen & ~NC_NLEN_MASK) == 0);
key = hash32_buf(name, nlen, HASH32_STR_INIT);
return (key << NC_NLEN_BITS) | (uintptr_t)nlen;
}
static void
cache_remove(struct namecache *ncp, const bool dir2node)
{
struct vnode *vp, *dvp = ncp->nc_dvp;
vnode_impl_t *dvi = VNODE_TO_VIMPL(dvp);
size_t namelen = NC_NLEN(ncp);
KASSERT(rw_write_held(&dvi->vi_nc_lock));
KASSERT(cache_key(ncp->nc_name, namelen) == ncp->nc_key);
KASSERT(rb_tree_find_node(&dvi->vi_nc_tree, ncp) == ncp);
SDT_PROBE(vfs, namecache, invalidate, done, ncp, 0, 0, 0, 0);
if ((vp = ncp->nc_vp) != NULL) {
vnode_impl_t *vi = VNODE_TO_VIMPL(vp);
if (__predict_true(dir2node)) {
rw_enter(&vi->vi_nc_listlock, RW_WRITER);
TAILQ_REMOVE(&vi->vi_nc_list, ncp, nc_list);
rw_exit(&vi->vi_nc_listlock);
} else {
TAILQ_REMOVE(&vi->vi_nc_list, ncp, nc_list);
}
}
rb_tree_remove_node(&dvi->vi_nc_tree, ncp);
mutex_enter(&cache_lru_lock);
TAILQ_REMOVE(&cache_lru.list[ncp->nc_lrulist], ncp, nc_lru);
cache_lru.count[ncp->nc_lrulist]--;
mutex_exit(&cache_lru_lock);
if (namelen > NCHNAMLEN) {
size_t sz = offsetof(struct namecache, nc_name[namelen]);
kmem_free(ncp, sz);
} else {
pool_cache_put(cache_pool, ncp);
}
}
static struct namecache * __noinline
cache_lookup_entry(struct vnode *dvp, const char *name, size_t namelen,
uintptr_t key)
{
vnode_impl_t *dvi = VNODE_TO_VIMPL(dvp);
struct rb_node *node = dvi->vi_nc_tree.rbt_root;
struct namecache *ncp;
enum cache_lru_id lrulist;
int diff;
KASSERT(namelen <= MAXPATHLEN);
KASSERT(rw_lock_held(&dvi->vi_nc_lock));
for (;;) {
if (__predict_false(RB_SENTINEL_P(node))) {
return NULL;
}
ncp = (struct namecache *)node;
KASSERT((void *)&ncp->nc_tree == (void *)ncp);
KASSERT(ncp->nc_dvp == dvp);
if (ncp->nc_key == key) {
KASSERT(NC_NLEN(ncp) == namelen);
diff = memcmp(ncp->nc_name, name, namelen);
if (__predict_true(diff == 0)) {
break;
}
node = node->rb_nodes[diff < 0];
} else {
node = node->rb_nodes[ncp->nc_key < key];
}
}
lrulist = atomic_load_relaxed(&ncp->nc_lrulist);
if (__predict_false(lrulist != LRU_ACTIVE)) {
cache_activate(ncp);
}
return ncp;
}
bool
cache_lookup(struct vnode *dvp, const char *name, size_t namelen,
uint32_t nameiop, uint32_t cnflags,
int *iswht_ret, struct vnode **vn_ret)
{
vnode_impl_t *dvi = VNODE_TO_VIMPL(dvp);
struct namecache *ncp;
struct vnode *vp;
uintptr_t key;
int error;
bool hit;
krw_t op;
KASSERT(namelen != cache_mp_nlen || name == cache_mp_name);
if (iswht_ret != NULL) {
*iswht_ret = 0;
}
*vn_ret = NULL;
if (__predict_false(namelen > cache_maxlen)) {
SDT_PROBE(vfs, namecache, lookup, toolong, dvp,
name, namelen, 0, 0);
COUNT(ncs_long);
return false;
}
key = cache_key(name, namelen);
if ((cnflags & ISLASTCN) != 0 &&
((cnflags & MAKEENTRY) == 0 || nameiop == CREATE)) {
op = RW_WRITER;
} else {
op = RW_READER;
}
rw_enter(&dvi->vi_nc_lock, op);
ncp = cache_lookup_entry(dvp, name, namelen, key);
if (__predict_false(ncp == NULL)) {
rw_exit(&dvi->vi_nc_lock);
COUNT(ncs_miss);
SDT_PROBE(vfs, namecache, lookup, miss, dvp,
name, namelen, 0, 0);
return false;
}
if (__predict_false((cnflags & MAKEENTRY) == 0)) {
KASSERT((cnflags & ISLASTCN) != 0);
cache_remove(ncp, true);
rw_exit(&dvi->vi_nc_lock);
COUNT(ncs_badhits);
return false;
}
if ((vp = ncp->nc_vp) == NULL) {
if (iswht_ret != NULL) {
*iswht_ret = ncp->nc_whiteout;
} else {
KASSERT(!ncp->nc_whiteout);
}
if (nameiop == CREATE && (cnflags & ISLASTCN) != 0) {
COUNT(ncs_badhits);
cache_remove(ncp, true);
hit = false;
} else {
COUNT(ncs_neghits);
SDT_PROBE(vfs, namecache, lookup, hit, dvp, name,
namelen, 0, 0);
hit = true;
}
rw_exit(&dvi->vi_nc_lock);
return hit;
}
error = vcache_tryvget(vp);
rw_exit(&dvi->vi_nc_lock);
if (error) {
KASSERT(error == EBUSY);
COUNT(ncs_falsehits);
return false;
}
COUNT(ncs_goodhits);
SDT_PROBE(vfs, namecache, lookup, hit, dvp, name, namelen, 0, 0);
*vn_ret = vp;
return true;
}
bool
cache_lookup_raw(struct vnode *dvp, const char *name, size_t namelen,
uint32_t cnflags,
int *iswht_ret, struct vnode **vn_ret)
{
return cache_lookup(dvp, name, namelen, LOOKUP, cnflags | MAKEENTRY,
iswht_ret, vn_ret);
}
bool
cache_lookup_linked(struct vnode *dvp, const char *name, size_t namelen,
struct vnode **vn_ret, krwlock_t **plock,
kauth_cred_t cred)
{
vnode_impl_t *dvi = VNODE_TO_VIMPL(dvp);
struct namecache *ncp;
krwlock_t *oldlock, *newlock;
struct vnode *vp;
uintptr_t key;
int error;
KASSERT(namelen != cache_mp_nlen || name == cache_mp_name);
if (__predict_false((dvp->v_mount->mnt_iflag & IMNT_NCLOOKUP) == 0)) {
return false;
}
if (__predict_false(namelen > cache_maxlen)) {
COUNT(ncs_long);
return false;
}
key = cache_key(name, namelen);
if (*plock != &dvi->vi_nc_lock) {
oldlock = *plock;
newlock = &dvi->vi_nc_lock;
if (!rw_tryenter(&dvi->vi_nc_lock, RW_READER)) {
return false;
}
} else {
oldlock = NULL;
newlock = NULL;
if (*plock == NULL) {
KASSERT(vrefcnt(dvp) > 0);
}
}
if (cred != FSCRED) {
if (dvi->vi_nc_mode == VNOVAL) {
if (newlock != NULL) {
rw_exit(newlock);
}
return false;
}
KASSERT(dvi->vi_nc_uid != VNOVAL);
KASSERT(dvi->vi_nc_gid != VNOVAL);
error = kauth_authorize_vnode(cred,
KAUTH_ACCESS_ACTION(VEXEC,
dvp->v_type, dvi->vi_nc_mode & ALLPERMS),
dvp, NULL,
genfs_can_access(dvp, cred, dvi->vi_nc_uid, dvi->vi_nc_gid,
dvi->vi_nc_mode & ALLPERMS, NULL, VEXEC));
if (error != 0) {
if (newlock != NULL) {
rw_exit(newlock);
}
COUNT(ncs_denied);
return false;
}
}
ncp = cache_lookup_entry(dvp, name, namelen, key);
if (__predict_false(ncp == NULL)) {
if (newlock != NULL) {
rw_exit(newlock);
}
COUNT(ncs_miss);
SDT_PROBE(vfs, namecache, lookup, miss, dvp,
name, namelen, 0, 0);
return false;
}
if ((vp = ncp->nc_vp) == NULL) {
KASSERT(namelen != cache_mp_nlen);
KASSERT(name != cache_mp_name);
COUNT(ncs_neghits);
} else {
COUNT(ncs_goodhits);
}
SDT_PROBE(vfs, namecache, lookup, hit, dvp, name, namelen, 0, 0);
if (oldlock) {
rw_exit(oldlock);
}
if (newlock) {
*plock = newlock;
}
*vn_ret = vp;
return true;
}
int
cache_revlookup(struct vnode *vp, struct vnode **dvpp, char **bpp, char *bufp,
bool checkaccess, accmode_t accmode)
{
vnode_impl_t *vi = VNODE_TO_VIMPL(vp);
struct namecache *ncp;
enum cache_lru_id lrulist;
struct vnode *dvp;
int error, nlen;
char *bp;
KASSERT(vp != NULL);
if (cache_maxlen == 0)
goto out;
rw_enter(&vi->vi_nc_listlock, RW_READER);
if (checkaccess) {
if (vi->vi_nc_mode == VNOVAL) {
rw_exit(&vi->vi_nc_listlock);
return -1;
}
KASSERT(vi->vi_nc_uid != VNOVAL);
KASSERT(vi->vi_nc_gid != VNOVAL);
error = kauth_authorize_vnode(kauth_cred_get(),
KAUTH_ACCESS_ACTION(VEXEC, vp->v_type, vi->vi_nc_mode &
ALLPERMS),
vp, NULL, genfs_can_access(vp, curlwp->l_cred,
vi->vi_nc_uid, vi->vi_nc_gid,
vi->vi_nc_mode & ALLPERMS,
NULL, accmode));
if (error != 0) {
rw_exit(&vi->vi_nc_listlock);
COUNT(ncs_denied);
return SET_ERROR(EACCES);
}
}
TAILQ_FOREACH(ncp, &vi->vi_nc_list, nc_list) {
KASSERT(ncp->nc_vp == vp);
KASSERT(ncp->nc_dvp != NULL);
nlen = NC_NLEN(ncp);
if (nlen == cache_mp_nlen) {
continue;
}
if (ncp->nc_name[0] == '.') {
if (nlen == 1 ||
(nlen == 2 && ncp->nc_name[1] == '.')) {
break;
}
}
lrulist = atomic_load_relaxed(&ncp->nc_lrulist);
if (lrulist != LRU_ACTIVE) {
cache_activate(ncp);
}
if (bufp) {
bp = *bpp;
bp -= nlen;
if (bp <= bufp) {
*dvpp = NULL;
rw_exit(&vi->vi_nc_listlock);
SDT_PROBE(vfs, namecache, revlookup,
fail, vp, ERANGE, 0, 0, 0);
return SET_ERROR(ERANGE);
}
memcpy(bp, ncp->nc_name, nlen);
*bpp = bp;
}
dvp = ncp->nc_dvp;
error = vcache_tryvget(dvp);
rw_exit(&vi->vi_nc_listlock);
if (error) {
KASSERT(error == EBUSY);
if (bufp)
(*bpp) += nlen;
*dvpp = NULL;
SDT_PROBE(vfs, namecache, revlookup, fail, vp,
error, 0, 0, 0);
return -1;
}
*dvpp = dvp;
SDT_PROBE(vfs, namecache, revlookup, success, vp, dvp,
0, 0, 0);
COUNT(ncs_revhits);
return 0;
}
rw_exit(&vi->vi_nc_listlock);
COUNT(ncs_revmiss);
out:
*dvpp = NULL;
return -1;
}
void
cache_enter(struct vnode *dvp, struct vnode *vp,
const char *name, size_t namelen, uint32_t cnflags)
{
vnode_impl_t *dvi = VNODE_TO_VIMPL(dvp);
struct namecache *ncp, *oncp;
int total;
KASSERT(namelen != cache_mp_nlen || name == cache_mp_name);
if ((cnflags & MAKEENTRY) == 0 ||
__predict_false(namelen > cache_maxlen)) {
SDT_PROBE(vfs, namecache, enter, toolong, vp, name, namelen,
0, 0);
return;
}
SDT_PROBE(vfs, namecache, enter, done, vp, name, namelen, 0, 0);
total = atomic_load_relaxed(&cache_lru.count[LRU_ACTIVE]);
total += atomic_load_relaxed(&cache_lru.count[LRU_INACTIVE]);
if (__predict_false(total > desiredvnodes)) {
cache_reclaim();
}
if (__predict_true(namelen <= NCHNAMLEN)) {
ncp = pool_cache_get(cache_pool, PR_WAITOK);
} else {
size_t sz = offsetof(struct namecache, nc_name[namelen]);
ncp = kmem_alloc(sz, KM_SLEEP);
}
ncp->nc_vp = vp;
ncp->nc_dvp = dvp;
ncp->nc_key = cache_key(name, namelen);
ncp->nc_whiteout = ((cnflags & ISWHITEOUT) != 0);
memcpy(ncp->nc_name, name, namelen);
rw_enter(&dvi->vi_nc_lock, RW_WRITER);
oncp = rb_tree_insert_node(&dvi->vi_nc_tree, ncp);
if (oncp != ncp) {
KASSERT(oncp->nc_key == ncp->nc_key);
KASSERT(NC_NLEN(oncp) == NC_NLEN(ncp));
KASSERT(memcmp(oncp->nc_name, name, namelen) == 0);
cache_remove(oncp, true);
oncp = rb_tree_insert_node(&dvi->vi_nc_tree, ncp);
KASSERT(oncp == ncp);
}
mutex_enter(&cache_lru_lock);
ncp->nc_lrulist = LRU_ACTIVE;
cache_lru.count[LRU_ACTIVE]++;
TAILQ_INSERT_TAIL(&cache_lru.list[LRU_ACTIVE], ncp, nc_lru);
cache_deactivate();
mutex_exit(&cache_lru_lock);
if (vp != NULL) {
vnode_impl_t *vi = VNODE_TO_VIMPL(vp);
rw_enter(&vi->vi_nc_listlock, RW_WRITER);
if ((namelen == 1 && name[0] == '.') ||
(namelen == 2 && name[0] == '.' && name[1] == '.')) {
TAILQ_INSERT_TAIL(&vi->vi_nc_list, ncp, nc_list);
} else {
TAILQ_INSERT_HEAD(&vi->vi_nc_list, ncp, nc_list);
}
rw_exit(&vi->vi_nc_listlock);
}
rw_exit(&dvi->vi_nc_lock);
}
void
cache_enter_id(struct vnode *vp, mode_t mode, uid_t uid, gid_t gid, bool valid)
{
vnode_impl_t *vi = VNODE_TO_VIMPL(vp);
if (vp->v_type == VDIR) {
rw_enter(&vi->vi_nc_lock, RW_WRITER);
rw_enter(&vi->vi_nc_listlock, RW_WRITER);
if (valid) {
vi->vi_nc_mode = mode;
vi->vi_nc_uid = uid;
vi->vi_nc_gid = gid;
} else {
vi->vi_nc_mode = VNOVAL;
vi->vi_nc_uid = VNOVAL;
vi->vi_nc_gid = VNOVAL;
}
rw_exit(&vi->vi_nc_listlock);
rw_exit(&vi->vi_nc_lock);
}
}
bool
cache_have_id(struct vnode *vp)
{
if (vp->v_type == VDIR &&
(vp->v_mount->mnt_iflag & IMNT_NCLOOKUP) != 0 &&
atomic_load_relaxed(&VNODE_TO_VIMPL(vp)->vi_nc_mode) != VNOVAL) {
return true;
} else {
return false;
}
}
void
cache_enter_mount(struct vnode *cvp, struct vnode *rvp)
{
KASSERT(vrefcnt(cvp) > 0);
KASSERT(vrefcnt(rvp) > 0);
KASSERT(cvp->v_type == VDIR);
KASSERT((rvp->v_vflag & VV_ROOT) != 0);
if (rvp->v_type == VDIR) {
cache_enter(cvp, rvp, cache_mp_name, cache_mp_nlen, MAKEENTRY);
}
}
bool
cache_lookup_mount(struct vnode *dvp, struct vnode **vn_ret)
{
bool ret;
ret = cache_lookup(dvp, cache_mp_name, cache_mp_nlen, LOOKUP,
MAKEENTRY, NULL, vn_ret);
KASSERT((*vn_ret != NULL) == ret);
return ret;
}
bool
cache_cross_mount(struct vnode **dvp, krwlock_t **plock)
{
return cache_lookup_linked(*dvp, cache_mp_name, cache_mp_nlen,
dvp, plock, FSCRED);
}
void
nchinit(void)
{
cache_pool = pool_cache_init(sizeof(struct namecache),
coherency_unit, 0, 0, "namecache", NULL, IPL_NONE, NULL,
NULL, NULL);
KASSERT(cache_pool != NULL);
mutex_init(&cache_lru_lock, MUTEX_DEFAULT, IPL_NONE);
TAILQ_INIT(&cache_lru.list[LRU_ACTIVE]);
TAILQ_INIT(&cache_lru.list[LRU_INACTIVE]);
mutex_init(&cache_stat_lock, MUTEX_DEFAULT, IPL_NONE);
callout_init(&cache_stat_callout, CALLOUT_MPSAFE);
callout_setfunc(&cache_stat_callout, cache_update_stats, NULL);
callout_schedule(&cache_stat_callout, cache_stat_interval * hz);
KASSERT(cache_sysctllog == NULL);
sysctl_createv(&cache_sysctllog, 0, NULL, NULL,
CTLFLAG_PERMANENT,
CTLTYPE_STRUCT, "namecache_stats",
SYSCTL_DESCR("namecache statistics"),
cache_stat_sysctl, 0, NULL, 0,
CTL_VFS, CTL_CREATE, CTL_EOL);
}
void
cache_cpu_init(struct cpu_info *ci)
{
size_t sz;
sz = roundup2(sizeof(struct nchcpu), coherency_unit);
ci->ci_data.cpu_nch = kmem_zalloc(sz, KM_SLEEP);
KASSERT(((uintptr_t)ci->ci_data.cpu_nch & (coherency_unit - 1)) == 0);
}
void
cache_vnode_init(struct vnode *vp)
{
vnode_impl_t *vi = VNODE_TO_VIMPL(vp);
rw_init(&vi->vi_nc_lock);
rw_init(&vi->vi_nc_listlock);
rb_tree_init(&vi->vi_nc_tree, &cache_rbtree_ops);
TAILQ_INIT(&vi->vi_nc_list);
vi->vi_nc_mode = VNOVAL;
vi->vi_nc_uid = VNOVAL;
vi->vi_nc_gid = VNOVAL;
}
void
cache_vnode_fini(struct vnode *vp)
{
vnode_impl_t *vi = VNODE_TO_VIMPL(vp);
KASSERT(RB_TREE_MIN(&vi->vi_nc_tree) == NULL);
KASSERT(TAILQ_EMPTY(&vi->vi_nc_list));
rw_destroy(&vi->vi_nc_lock);
rw_destroy(&vi->vi_nc_listlock);
}
static void
cache_purge_parents(struct vnode *vp)
{
vnode_impl_t *dvi, *vi = VNODE_TO_VIMPL(vp);
struct vnode *dvp, *blocked;
struct namecache *ncp;
SDT_PROBE(vfs, namecache, purge, parents, vp, 0, 0, 0, 0);
blocked = NULL;
rw_enter(&vi->vi_nc_listlock, RW_WRITER);
while ((ncp = TAILQ_FIRST(&vi->vi_nc_list)) != NULL) {
dvp = ncp->nc_dvp;
dvi = VNODE_TO_VIMPL(dvp);
if (rw_tryenter(&dvi->vi_nc_lock, RW_WRITER)) {
cache_remove(ncp, false);
rw_exit(&dvi->vi_nc_lock);
blocked = NULL;
continue;
}
vhold(dvp);
rw_exit(&vi->vi_nc_listlock);
rw_enter(&dvi->vi_nc_lock, RW_WRITER);
rw_exit(&dvi->vi_nc_lock);
holdrele(dvp);
if (blocked == dvp) {
kpause("ncpurge", false, 1, NULL);
}
rw_enter(&vi->vi_nc_listlock, RW_WRITER);
blocked = dvp;
}
rw_exit(&vi->vi_nc_listlock);
}
static void
cache_purge_children(struct vnode *dvp)
{
vnode_impl_t *dvi = VNODE_TO_VIMPL(dvp);
struct namecache *ncp;
SDT_PROBE(vfs, namecache, purge, children, dvp, 0, 0, 0, 0);
rw_enter(&dvi->vi_nc_lock, RW_WRITER);
while ((ncp = RB_TREE_MIN(&dvi->vi_nc_tree)) != NULL) {
cache_remove(ncp, true);
}
rw_exit(&dvi->vi_nc_lock);
}
static void
cache_purge_name(struct vnode *dvp, const char *name, size_t namelen)
{
vnode_impl_t *dvi = VNODE_TO_VIMPL(dvp);
struct namecache *ncp;
uintptr_t key;
SDT_PROBE(vfs, namecache, purge, name, name, namelen, 0, 0, 0);
key = cache_key(name, namelen);
rw_enter(&dvi->vi_nc_lock, RW_WRITER);
ncp = cache_lookup_entry(dvp, name, namelen, key);
if (ncp) {
cache_remove(ncp, true);
}
rw_exit(&dvi->vi_nc_lock);
}
void
cache_purge1(struct vnode *vp, const char *name, size_t namelen, int flags)
{
if (flags & PURGE_PARENTS) {
cache_purge_parents(vp);
}
if (flags & PURGE_CHILDREN) {
cache_purge_children(vp);
}
if (name != NULL) {
cache_purge_name(vp, name, namelen);
}
}
static bool
cache_vdir_filter(void *cookie, vnode_t *vp)
{
return vp->v_type == VDIR;
}
void
cache_purgevfs(struct mount *mp)
{
struct vnode_iterator *iter;
vnode_t *dvp;
vfs_vnode_iterator_init(mp, &iter);
for (;;) {
dvp = vfs_vnode_iterator_next(iter, cache_vdir_filter, NULL);
if (dvp == NULL) {
break;
}
cache_purge_children(dvp);
vrele(dvp);
}
vfs_vnode_iterator_destroy(iter);
}
static void
cache_activate(struct namecache *ncp)
{
mutex_enter(&cache_lru_lock);
TAILQ_REMOVE(&cache_lru.list[ncp->nc_lrulist], ncp, nc_lru);
TAILQ_INSERT_TAIL(&cache_lru.list[LRU_ACTIVE], ncp, nc_lru);
cache_lru.count[ncp->nc_lrulist]--;
cache_lru.count[LRU_ACTIVE]++;
ncp->nc_lrulist = LRU_ACTIVE;
mutex_exit(&cache_lru_lock);
}
static void
cache_deactivate(void)
{
struct namecache *ncp;
int total, i;
KASSERT(mutex_owned(&cache_lru_lock));
total = cache_lru.count[LRU_ACTIVE] + cache_lru.count[LRU_INACTIVE];
if (total < (desiredvnodes >> 1)) {
return;
}
if (cache_lru.count[LRU_ACTIVE] < cache_lru.count[LRU_INACTIVE]) {
return;
}
for (i = 0; i < cache_lru_maxdeact; i++) {
ncp = TAILQ_FIRST(&cache_lru.list[LRU_ACTIVE]);
if (ncp == NULL) {
break;
}
KASSERT(ncp->nc_lrulist == LRU_ACTIVE);
ncp->nc_lrulist = LRU_INACTIVE;
TAILQ_REMOVE(&cache_lru.list[LRU_ACTIVE], ncp, nc_lru);
TAILQ_INSERT_TAIL(&cache_lru.list[LRU_INACTIVE], ncp, nc_lru);
cache_lru.count[LRU_ACTIVE]--;
cache_lru.count[LRU_INACTIVE]++;
}
}
static void
cache_reclaim(void)
{
struct namecache *ncp;
vnode_impl_t *dvi;
int toscan;
mutex_enter(&cache_lru_lock);
toscan = MIN(cache_lru_maxscan, desiredvnodes >> 7);
toscan = MAX(toscan, 1);
SDT_PROBE(vfs, namecache, prune, done, cache_lru.count[LRU_ACTIVE] +
cache_lru.count[LRU_INACTIVE], toscan, 0, 0, 0);
while (toscan-- != 0) {
cache_deactivate();
ncp = TAILQ_FIRST(&cache_lru.list[LRU_INACTIVE]);
if (ncp == NULL) {
break;
}
dvi = VNODE_TO_VIMPL(ncp->nc_dvp);
KASSERT(ncp->nc_lrulist == LRU_INACTIVE);
KASSERT(dvi != NULL);
if (!rw_tryenter(&dvi->vi_nc_lock, RW_WRITER)) {
TAILQ_REMOVE(&cache_lru.list[LRU_INACTIVE],
ncp, nc_lru);
TAILQ_INSERT_TAIL(&cache_lru.list[LRU_INACTIVE],
ncp, nc_lru);
continue;
}
mutex_exit(&cache_lru_lock);
cache_remove(ncp, true);
rw_exit(&dvi->vi_nc_lock);
mutex_enter(&cache_lru_lock);
}
mutex_exit(&cache_lru_lock);
}
void
namecache_count_pass2(void)
{
COUNT(ncs_pass2);
}
void
namecache_count_2passes(void)
{
COUNT(ncs_2passes);
}
static void
cache_update_stats(void *cookie)
{
CPU_INFO_ITERATOR cii;
struct cpu_info *ci;
mutex_enter(&cache_stat_lock);
for (CPU_INFO_FOREACH(cii, ci)) {
struct nchcpu *nchcpu = ci->ci_data.cpu_nch;
UPDATE(nchcpu, ncs_goodhits);
UPDATE(nchcpu, ncs_neghits);
UPDATE(nchcpu, ncs_badhits);
UPDATE(nchcpu, ncs_falsehits);
UPDATE(nchcpu, ncs_miss);
UPDATE(nchcpu, ncs_long);
UPDATE(nchcpu, ncs_pass2);
UPDATE(nchcpu, ncs_2passes);
UPDATE(nchcpu, ncs_revhits);
UPDATE(nchcpu, ncs_revmiss);
UPDATE(nchcpu, ncs_denied);
}
if (cookie != NULL) {
memcpy(cookie, &nchstats, sizeof(nchstats));
}
callout_schedule(&cache_stat_callout, cache_stat_interval * hz);
mutex_exit(&cache_stat_lock);
}
static int
cache_stat_sysctl(SYSCTLFN_ARGS)
{
struct nchstats stats;
if (oldp == NULL) {
*oldlenp = sizeof(nchstats);
return 0;
}
if (*oldlenp <= 0) {
*oldlenp = 0;
return 0;
}
sysctl_unlock();
cache_update_stats(&stats);
sysctl_relock();
*oldlenp = MIN(sizeof(stats), *oldlenp);
return sysctl_copyout(l, &stats, oldp, *oldlenp);
}
#ifdef DDB
void
namecache_print(struct vnode *vp, void (*pr)(const char *, ...))
{
struct vnode *dvp = NULL;
struct namecache *ncp;
enum cache_lru_id id;
for (id = 0; id < LRU_COUNT; id++) {
TAILQ_FOREACH(ncp, &cache_lru.list[id], nc_lru) {
if (ncp->nc_vp == vp) {
(*pr)("name %.*s\n", NC_NLEN(ncp),
ncp->nc_name);
dvp = ncp->nc_dvp;
}
}
}
if (dvp == NULL) {
(*pr)("name not found\n");
return;
}
for (id = 0; id < LRU_COUNT; id++) {
TAILQ_FOREACH(ncp, &cache_lru.list[id], nc_lru) {
if (ncp->nc_vp == dvp) {
(*pr)("parent %.*s\n", NC_NLEN(ncp),
ncp->nc_name);
}
}
}
}
#endif