/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); }