vncache.c revision b819cea2f73f98c5662230cc9affc8cc84f77fcf
/*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*/
/*
* Copyright 2014 Nexenta Systems, Inc. All rights reserved.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/cmn_err.h>
#include <sys/cred.h>
#include <sys/debug.h>
#include <sys/errno.h>
#include <sys/t_lock.h>
#include <sys/user.h>
#include <sys/uio.h>
#include <sys/file.h>
#include <sys/pathname.h>
#include <sys/sysmacros.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <sys/avl.h>
#include <sys/stat.h>
#include <sys/mode.h>
#include <fcntl.h>
#include <unistd.h>
#include "vncache.h"
kmem_cache_t *vn_cache;
/*
* You can dump this AVL tree with mdb, i.e.
* vncache_avl ::walk avl |::print -s1 vnode_t
*/
avl_tree_t vncache_avl;
kmutex_t vncache_lock;
/*
* Vnode cache.
*/
/* ARGSUSED */
static int
vn_cache_constructor(void *buf, void *cdrarg, int kmflags)
{
struct vnode *vp;
vp = buf;
bzero(vp, sizeof (*vp));
mutex_init(&vp->v_lock, NULL, MUTEX_DEFAULT, NULL);
vp->v_fd = -1;
return (0);
}
/* ARGSUSED */
static void
vn_cache_destructor(void *buf, void *cdrarg)
{
struct vnode *vp;
vp = buf;
mutex_destroy(&vp->v_lock);
}
/*
* Used by file systems when fs-specific nodes (e.g., ufs inodes) are
* cached by the file system and vnodes remain associated.
*/
void
vn_recycle(vnode_t *vp)
{
ASSERT(vp->v_fd == -1);
vp->v_rdcnt = 0;
vp->v_wrcnt = 0;
if (vp->v_path) {
strfree(vp->v_path);
vp->v_path = NULL;
}
}
/*
* Used to reset the vnode fields including those that are directly accessible
* as well as those which require an accessor function.
*
* Does not initialize:
* synchronization objects: v_lock, v_vsd_lock, v_nbllock, v_cv
* v_data (since FS-nodes and vnodes point to each other and should
* be updated simultaneously)
* v_op (in case someone needs to make a VOP call on this object)
*/
void
vn_reinit(vnode_t *vp)
{
vp->v_count = 1;
vp->v_vfsp = NULL;
vp->v_stream = NULL;
vp->v_flag = 0;
vp->v_type = VNON;
vp->v_rdev = NODEV;
vn_recycle(vp);
}
vnode_t *
vn_alloc(int kmflag)
{
vnode_t *vp;
vp = kmem_cache_alloc(vn_cache, kmflag);
if (vp != NULL) {
vn_reinit(vp);
}
return (vp);
}
void
vn_free(vnode_t *vp)
{
/*
* Some file systems call vn_free() with v_count of zero,
* some with v_count of 1. In any case, the value should
* never be anything else.
*/
ASSERT((vp->v_count == 0) || (vp->v_count == 1));
if (vp->v_path != NULL) {
strfree(vp->v_path);
vp->v_path = NULL;
}
ASSERT(vp->v_fd != -1);
(void) close(vp->v_fd);
vp->v_fd = -1;
kmem_cache_free(vn_cache, vp);
}
int
vncache_cmp(const void *v1, const void *v2)
{
const vnode_t *vp1, *vp2;
vp1 = v1;
vp2 = v2;
if (vp1->v_st_dev < vp2->v_st_dev)
return (-1);
if (vp1->v_st_dev > vp2->v_st_dev)
return (+1);
if (vp1->v_st_ino < vp2->v_st_ino)
return (-1);
if (vp1->v_st_ino > vp2->v_st_ino)
return (+1);
return (0);
}
vnode_t *
vncache_lookup(struct stat *st)
{
vnode_t tmp_vn;
vnode_t *vp;
tmp_vn.v_st_dev = st->st_dev;
tmp_vn.v_st_ino = st->st_ino;
mutex_enter(&vncache_lock);
vp = avl_find(&vncache_avl, &tmp_vn, NULL);
if (vp != NULL)
vn_hold(vp);
mutex_exit(&vncache_lock);
return (vp);
}
vnode_t *
vncache_enter(struct stat *st, vnode_t *dvp, char *name, int fd)
{
vnode_t *old_vp;
vnode_t *new_vp;
vfs_t *vfs;
char *vpath;
avl_index_t where;
int len;
/*
* Fill in v_path
* Note: fsop_root() calls with dvp=NULL
*/
len = strlen(name) + 1;
if (dvp == NULL) {
vpath = kmem_alloc(len, KM_SLEEP);
(void) strlcpy(vpath, name, len);
vfs = rootvfs;
} else {
/* add to length for parent path + "/" */
len += (strlen(dvp->v_path) + 1);
vpath = kmem_alloc(len, KM_SLEEP);
(void) snprintf(vpath, len, "%s/%s", dvp->v_path, name);
vfs = dvp->v_vfsp;
}
new_vp = vn_alloc(KM_SLEEP);
new_vp->v_path = vpath;
new_vp->v_fd = fd;
new_vp->v_st_dev = st->st_dev;
new_vp->v_st_ino = st->st_ino;
new_vp->v_vfsp = vfs;
new_vp->v_type = IFTOVT(st->st_mode);
mutex_enter(&vncache_lock);
old_vp = avl_find(&vncache_avl, new_vp, &where);
if (old_vp != NULL)
vn_hold(old_vp);
else
avl_insert(&vncache_avl, new_vp, where);
mutex_exit(&vncache_lock);
/* If we lost the race, free new_vp */
if (old_vp != NULL) {
vn_free(new_vp);
return (old_vp);
}
return (new_vp);
}
/*
* Called after a successful rename to update v_path
*/
void
vncache_renamed(vnode_t *vp, vnode_t *to_dvp, char *to_name)
{
char *vpath;
char *ovpath;
int len;
len = strlen(to_name) + 1;
/* add to length for parent path + "/" */
len += (strlen(to_dvp->v_path) + 1);
vpath = kmem_alloc(len, KM_SLEEP);
(void) snprintf(vpath, len, "%s/%s", to_dvp->v_path, to_name);
mutex_enter(&vncache_lock);
ovpath = vp->v_path;
vp->v_path = vpath;
mutex_exit(&vncache_lock);
strfree(ovpath);
}
/*
* Last reference to this vnode is (possibly) going away.
* This is normally called by vn_rele() when v_count==1.
* Note that due to lock order concerns, we have to take
* the vncache_lock (for the avl tree) and then recheck
* v_count, which might have gained a ref during the time
* we did not hold vp->v_lock.
*/
void
vncache_inactive(vnode_t *vp)
{
uint_t count;
mutex_enter(&vncache_lock);
mutex_enter(&vp->v_lock);
if ((count = vp->v_count) <= 1) {
/* This is (still) the last ref. */
avl_remove(&vncache_avl, vp);
}
mutex_exit(&vp->v_lock);
mutex_exit(&vncache_lock);
if (count <= 1) {
vn_free(vp);
}
}
#pragma init(vncache_init)
int
vncache_init(void)
{
vn_cache = kmem_cache_create("vn_cache", sizeof (struct vnode),
VNODE_ALIGN, vn_cache_constructor, vn_cache_destructor, NULL, NULL,
NULL, 0);
avl_create(&vncache_avl,
vncache_cmp,
sizeof (vnode_t),
offsetof(vnode_t, v_avl_node));
mutex_init(&vncache_lock, NULL, MUTEX_DEFAULT, NULL);
return (0);
}
#pragma fini(vncache_fini)
void
vncache_fini(void)
{
mutex_destroy(&vncache_lock);
avl_destroy(&vncache_avl);
kmem_cache_destroy(vn_cache);
}