/* * 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. * Copyright 2024 RackTop Systems, Inc. */ /* * The SMB server supports its local file system operations using * kernel-style VOP_... calls. This layer simulates creating and * finding vnodes for "libfksmbsrv". * * The vnodes manged here are always paired with a private struct * (see fakefs_node_t) to hold the details we need to find them * in our cache and the file descriptor used in simulations. * * The actual VOP_... and VFS_... call simulations are in other * files, generall named after the original kernel ones. * (eg. fake_vfs.c) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vncache.h" #define VTOF(vp) ((struct fakefs_node *)(vp)->v_data) #define FTOV(fnp) ((fnp)->fn_vnode) /* Private to the fake vnode impl. */ typedef struct fakefs_node { avl_node_t fn_avl_node; vnode_t *fn_vnode; dev_t fn_st_dev; ino_t fn_st_ino; int fn_fd; int fn_mode; } fakefs_node_t; typedef struct fnode_vnode { struct fakefs_node fn; struct vnode vn; } fnode_vnode_t; /* * You can dump this AVL tree with mdb, i.e. * fncache_avl ::walk avl |::print fakefs_node_t * fncache_avl ::walk avl |::print fnode_vnode_t fn vn.v_path */ avl_tree_t fncache_avl; kmutex_t fncache_lock; /* * Fake node / vnode cache. */ kmem_cache_t *fn_cache; /* ARGSUSED */ static int fn_cache_constructor(void *buf, void *cdrarg, int kmflags) { fnode_vnode_t *fvp = buf; bzero(fvp, sizeof (*fvp)); fvp->fn.fn_vnode = &fvp->vn; fvp->fn.fn_fd = -1; fvp->vn.v_data = &fvp->fn; mutex_init(&fvp->vn.v_lock, NULL, MUTEX_DEFAULT, NULL); return (0); } /* ARGSUSED */ static void fn_cache_destructor(void *buf, void *cdrarg) { fnode_vnode_t *fvp = buf; mutex_destroy(&fvp->vn.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) { fakefs_node_t *fnp = VTOF(vp); ASSERT(fnp->fn_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) { fnode_vnode_t *fvp; vnode_t *vp = NULL; fvp = kmem_cache_alloc(fn_cache, kmflag); if (fvp != NULL) { vp = &fvp->vn; vn_reinit(vp); } return (vp); } void vn_free(vnode_t *vp) { fakefs_node_t *fnp = VTOF(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(fnp->fn_fd > 2); (void) close(fnp->fn_fd); fnp->fn_fd = -1; /* * Make sure fnp points to the beginning of fnode_vnode_t, * which is what we must pass to kmem_cache_free. */ CTASSERT(offsetof(fnode_vnode_t, fn) == 0); kmem_cache_free(fn_cache, fnp); } static int fncache_cmp(const void *v1, const void *v2) { const fakefs_node_t *np1 = v1; const fakefs_node_t *np2 = v2; /* The args are really fnode_vnode_t */ CTASSERT(offsetof(fnode_vnode_t, fn) == 0); if (np1->fn_st_dev < np2->fn_st_dev) return (-1); if (np1->fn_st_dev > np2->fn_st_dev) return (+1); if (np1->fn_st_ino < np2->fn_st_ino) return (-1); if (np1->fn_st_ino > np2->fn_st_ino) return (+1); return (0); } int vncache_cmp(const vnode_t *vp1, const vnode_t *vp2) { fakefs_node_t *np1 = VTOF(vp1); fakefs_node_t *np2 = VTOF(vp2); return (fncache_cmp(np1, np2)); } vnode_t * vncache_lookup(struct stat *st) { fakefs_node_t tmp_fn; fakefs_node_t *fnp; vnode_t *vp = NULL; tmp_fn.fn_st_dev = st->st_dev; tmp_fn.fn_st_ino = st->st_ino; mutex_enter(&fncache_lock); fnp = avl_find(&fncache_avl, &tmp_fn, NULL); if (fnp != NULL) { vp = FTOV(fnp); VN_HOLD(vp); } mutex_exit(&fncache_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; fakefs_node_t *old_fnp; fakefs_node_t *new_fnp; vfs_t *vfs; char *vpath; avl_index_t where; int len; ASSERT(fd > 2); /* * 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; } /* Note: (vp : fnp) linkage setup in constructor */ new_vp = vn_alloc(KM_SLEEP); new_vp->v_path = vpath; new_vp->v_vfsp = vfs; new_vp->v_type = IFTOVT(st->st_mode); new_fnp = VTOF(new_vp); new_fnp->fn_fd = fd; new_fnp->fn_st_dev = st->st_dev; new_fnp->fn_st_ino = st->st_ino; old_vp = NULL; mutex_enter(&fncache_lock); old_fnp = avl_find(&fncache_avl, new_fnp, &where); if (old_fnp != NULL) { DTRACE_PROBE1(found, fakefs_node_t *, old_fnp); old_vp = FTOV(old_fnp); VN_HOLD(old_vp); } else { DTRACE_PROBE1(insert, fakefs_node_t *, new_fnp); avl_insert(&fncache_avl, new_fnp, where); } mutex_exit(&fncache_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(&fncache_lock); ovpath = vp->v_path; vp->v_path = vpath; mutex_exit(&fncache_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 fncache_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) { fakefs_node_t *fnp = VTOF(vp); vnode_t *xvp; uint_t count; mutex_enter(&fncache_lock); mutex_enter(&vp->v_lock); if ((count = vp->v_count) <= 1) { /* This is (still) the last ref. */ DTRACE_PROBE1(remove, fakefs_node_t *, fnp); avl_remove(&fncache_avl, fnp); } mutex_exit(&vp->v_lock); mutex_exit(&fncache_lock); if (count > 1) return; /* * See fake_lookup_xattrdir() */ xvp = vp->v_xattrdir; vp->v_xattrdir = NULL; vn_free(vp); if (xvp != NULL) { ASSERT((xvp->v_flag & V_XATTRDIR) != 0); VN_RELE(xvp); } } int vncache_getfd(vnode_t *vp) { fakefs_node_t *fnp = VTOF(vp); ASSERT(fnp->fn_fd > 2); return (fnp->fn_fd); } /* * See fake_lookup_xattrdir() * Special case vnode creation. */ void vncache_setfd(vnode_t *vp, int fd) { fakefs_node_t *fnp = VTOF(vp); ASSERT(fnp->fn_fd == -1); ASSERT(fd > 2); fnp->fn_fd = fd; } int vncache_init(void) { fn_cache = kmem_cache_create("fn_cache", sizeof (fnode_vnode_t), VNODE_ALIGN, fn_cache_constructor, fn_cache_destructor, NULL, NULL, NULL, 0); avl_create(&fncache_avl, fncache_cmp, sizeof (fnode_vnode_t), offsetof(fnode_vnode_t, fn.fn_avl_node)); mutex_init(&fncache_lock, NULL, MUTEX_DEFAULT, NULL); return (0); } void vncache_fini(void) { mutex_destroy(&fncache_lock); avl_destroy(&fncache_avl); kmem_cache_destroy(fn_cache); }