1171802Sdelphij/* $NetBSD: tmpfs_subr.c,v 1.35 2007/07/09 21:10:50 ad Exp $ */ 2170808Sdelphij 3182739Sdelphij/*- 4170808Sdelphij * Copyright (c) 2005 The NetBSD Foundation, Inc. 5170808Sdelphij * All rights reserved. 6170808Sdelphij * 7170808Sdelphij * This code is derived from software contributed to The NetBSD Foundation 8170808Sdelphij * by Julio M. Merino Vidal, developed as part of Google's Summer of Code 9170808Sdelphij * 2005 program. 10170808Sdelphij * 11170808Sdelphij * Redistribution and use in source and binary forms, with or without 12170808Sdelphij * modification, are permitted provided that the following conditions 13170808Sdelphij * are met: 14170808Sdelphij * 1. Redistributions of source code must retain the above copyright 15170808Sdelphij * notice, this list of conditions and the following disclaimer. 16170808Sdelphij * 2. Redistributions in binary form must reproduce the above copyright 17170808Sdelphij * notice, this list of conditions and the following disclaimer in the 18170808Sdelphij * documentation and/or other materials provided with the distribution. 19170808Sdelphij * 20170808Sdelphij * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21170808Sdelphij * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22170808Sdelphij * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23170808Sdelphij * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24170808Sdelphij * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25170808Sdelphij * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26170808Sdelphij * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27170808Sdelphij * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28170808Sdelphij * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29170808Sdelphij * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30170808Sdelphij * POSSIBILITY OF SUCH DAMAGE. 31170808Sdelphij */ 32170808Sdelphij 33170808Sdelphij/* 34170808Sdelphij * Efficient memory file system supporting functions. 35170808Sdelphij */ 36170808Sdelphij#include <sys/cdefs.h> 37170808Sdelphij__FBSDID("$FreeBSD$"); 38170808Sdelphij 39170808Sdelphij#include <sys/param.h> 40245115Sgleb#include <sys/fnv_hash.h> 41248084Sattilio#include <sys/lock.h> 42170808Sdelphij#include <sys/namei.h> 43170808Sdelphij#include <sys/priv.h> 44170808Sdelphij#include <sys/proc.h> 45248084Sattilio#include <sys/rwlock.h> 46170808Sdelphij#include <sys/stat.h> 47170808Sdelphij#include <sys/systm.h> 48232960Sgleb#include <sys/sysctl.h> 49170808Sdelphij#include <sys/vnode.h> 50170808Sdelphij#include <sys/vmmeter.h> 51170808Sdelphij 52170808Sdelphij#include <vm/vm.h> 53239065Skib#include <vm/vm_param.h> 54170808Sdelphij#include <vm/vm_object.h> 55170808Sdelphij#include <vm/vm_page.h> 56229821Salc#include <vm/vm_pageout.h> 57170808Sdelphij#include <vm/vm_pager.h> 58170808Sdelphij#include <vm/vm_extern.h> 59170808Sdelphij 60170808Sdelphij#include <fs/tmpfs/tmpfs.h> 61170808Sdelphij#include <fs/tmpfs/tmpfs_fifoops.h> 62170808Sdelphij#include <fs/tmpfs/tmpfs_vnops.h> 63170808Sdelphij 64245115Sglebstruct tmpfs_dir_cursor { 65245115Sgleb struct tmpfs_dirent *tdc_current; 66245115Sgleb struct tmpfs_dirent *tdc_tree; 67245115Sgleb}; 68245115Sgleb 69232960SglebSYSCTL_NODE(_vfs, OID_AUTO, tmpfs, CTLFLAG_RW, 0, "tmpfs file system"); 70232960Sgleb 71233998Sglebstatic long tmpfs_pages_reserved = TMPFS_PAGES_MINRESERVED; 72233998Sgleb 73233998Sglebstatic int 74233998Sglebsysctl_mem_reserved(SYSCTL_HANDLER_ARGS) 75233998Sgleb{ 76233998Sgleb int error; 77233998Sgleb long pages, bytes; 78233998Sgleb 79233998Sgleb pages = *(long *)arg1; 80233998Sgleb bytes = pages * PAGE_SIZE; 81233998Sgleb 82233998Sgleb error = sysctl_handle_long(oidp, &bytes, 0, req); 83233998Sgleb if (error || !req->newptr) 84233998Sgleb return (error); 85233998Sgleb 86233998Sgleb pages = bytes / PAGE_SIZE; 87233998Sgleb if (pages < TMPFS_PAGES_MINRESERVED) 88233998Sgleb return (EINVAL); 89233998Sgleb 90233998Sgleb *(long *)arg1 = pages; 91233998Sgleb return (0); 92233998Sgleb} 93233998Sgleb 94233998SglebSYSCTL_PROC(_vfs_tmpfs, OID_AUTO, memory_reserved, CTLTYPE_LONG|CTLFLAG_RW, 95234325Sgleb &tmpfs_pages_reserved, 0, sysctl_mem_reserved, "L", 96234325Sgleb "Amount of available memory and swap below which tmpfs growth stops"); 97233998Sgleb 98245115Sglebstatic __inline int tmpfs_dirtree_cmp(struct tmpfs_dirent *a, 99245115Sgleb struct tmpfs_dirent *b); 100245115SglebRB_PROTOTYPE_STATIC(tmpfs_dir, tmpfs_dirent, uh.td_entries, tmpfs_dirtree_cmp); 101245115Sgleb 102233998Sglebsize_t 103233998Sglebtmpfs_mem_avail(void) 104233998Sgleb{ 105233998Sgleb vm_ooffset_t avail; 106233998Sgleb 107233998Sgleb avail = swap_pager_avail + cnt.v_free_count + cnt.v_cache_count - 108233998Sgleb tmpfs_pages_reserved; 109233998Sgleb if (__predict_false(avail < 0)) 110233998Sgleb avail = 0; 111233998Sgleb return (avail); 112233998Sgleb} 113233998Sgleb 114233998Sglebsize_t 115233998Sglebtmpfs_pages_used(struct tmpfs_mount *tmp) 116233998Sgleb{ 117233998Sgleb const size_t node_size = sizeof(struct tmpfs_node) + 118233998Sgleb sizeof(struct tmpfs_dirent); 119233998Sgleb size_t meta_pages; 120233998Sgleb 121233998Sgleb meta_pages = howmany((uintmax_t)tmp->tm_nodes_inuse * node_size, 122233998Sgleb PAGE_SIZE); 123233998Sgleb return (meta_pages + tmp->tm_pages_used); 124233998Sgleb} 125233998Sgleb 126233998Sglebstatic size_t 127233998Sglebtmpfs_pages_check_avail(struct tmpfs_mount *tmp, size_t req_pages) 128233998Sgleb{ 129233998Sgleb if (tmpfs_mem_avail() < req_pages) 130233998Sgleb return (0); 131233998Sgleb 132233998Sgleb if (tmp->tm_pages_max != SIZE_MAX && 133233998Sgleb tmp->tm_pages_max < req_pages + tmpfs_pages_used(tmp)) 134233998Sgleb return (0); 135233998Sgleb 136233998Sgleb return (1); 137233998Sgleb} 138233998Sgleb 139170808Sdelphij/* --------------------------------------------------------------------- */ 140170808Sdelphij 141170808Sdelphij/* 142170808Sdelphij * Allocates a new node of type 'type' inside the 'tmp' mount point, with 143170808Sdelphij * its owner set to 'uid', its group to 'gid' and its mode set to 'mode', 144170808Sdelphij * using the credentials of the process 'p'. 145170808Sdelphij * 146170808Sdelphij * If the node type is set to 'VDIR', then the parent parameter must point 147170808Sdelphij * to the parent directory of the node being created. It may only be NULL 148170808Sdelphij * while allocating the root node. 149170808Sdelphij * 150170808Sdelphij * If the node type is set to 'VBLK' or 'VCHR', then the rdev parameter 151170808Sdelphij * specifies the device the node represents. 152170808Sdelphij * 153170808Sdelphij * If the node type is set to 'VLNK', then the parameter target specifies 154170808Sdelphij * the file name of the target file for the symbolic link that is being 155170808Sdelphij * created. 156170808Sdelphij * 157170808Sdelphij * Note that new nodes are retrieved from the available list if it has 158170808Sdelphij * items or, if it is empty, from the node pool as long as there is enough 159170808Sdelphij * space to create them. 160170808Sdelphij * 161170808Sdelphij * Returns zero on success or an appropriate error code on failure. 162170808Sdelphij */ 163170808Sdelphijint 164170808Sdelphijtmpfs_alloc_node(struct tmpfs_mount *tmp, enum vtype type, 165170808Sdelphij uid_t uid, gid_t gid, mode_t mode, struct tmpfs_node *parent, 166191990Sattilio char *target, dev_t rdev, struct tmpfs_node **node) 167170808Sdelphij{ 168170808Sdelphij struct tmpfs_node *nnode; 169250030Skib vm_object_t obj; 170170808Sdelphij 171170808Sdelphij /* If the root directory of the 'tmp' file system is not yet 172170808Sdelphij * allocated, this must be the request to do it. */ 173170808Sdelphij MPASS(IMPLIES(tmp->tm_root == NULL, parent == NULL && type == VDIR)); 174170808Sdelphij 175170808Sdelphij MPASS(IFF(type == VLNK, target != NULL)); 176170808Sdelphij MPASS(IFF(type == VBLK || type == VCHR, rdev != VNOVAL)); 177170808Sdelphij 178202708Sjh if (tmp->tm_nodes_inuse >= tmp->tm_nodes_max) 179171029Sdelphij return (ENOSPC); 180233998Sgleb if (tmpfs_pages_check_avail(tmp, 1) == 0) 181233998Sgleb return (ENOSPC); 182170808Sdelphij 183171029Sdelphij nnode = (struct tmpfs_node *)uma_zalloc_arg( 184171029Sdelphij tmp->tm_node_pool, tmp, M_WAITOK); 185170808Sdelphij 186170808Sdelphij /* Generic initialization. */ 187170808Sdelphij nnode->tn_type = type; 188171068Sdelphij vfs_timestamp(&nnode->tn_atime); 189170808Sdelphij nnode->tn_birthtime = nnode->tn_ctime = nnode->tn_mtime = 190170808Sdelphij nnode->tn_atime; 191170808Sdelphij nnode->tn_uid = uid; 192170808Sdelphij nnode->tn_gid = gid; 193170808Sdelphij nnode->tn_mode = mode; 194171362Sdelphij nnode->tn_id = alloc_unr(tmp->tm_ino_unr); 195171070Sdelphij 196170808Sdelphij /* Type-specific initialization. */ 197170808Sdelphij switch (nnode->tn_type) { 198170808Sdelphij case VBLK: 199170808Sdelphij case VCHR: 200170808Sdelphij nnode->tn_rdev = rdev; 201170808Sdelphij break; 202170808Sdelphij 203170808Sdelphij case VDIR: 204245115Sgleb RB_INIT(&nnode->tn_dir.tn_dirhead); 205245115Sgleb LIST_INIT(&nnode->tn_dir.tn_dupindex); 206173725Sdelphij MPASS(parent != nnode); 207173725Sdelphij MPASS(IMPLIES(parent == NULL, tmp->tm_root == NULL)); 208170808Sdelphij nnode->tn_dir.tn_parent = (parent == NULL) ? nnode : parent; 209170808Sdelphij nnode->tn_dir.tn_readdir_lastn = 0; 210170808Sdelphij nnode->tn_dir.tn_readdir_lastp = NULL; 211170808Sdelphij nnode->tn_links++; 212197953Sdelphij TMPFS_NODE_LOCK(nnode->tn_dir.tn_parent); 213170808Sdelphij nnode->tn_dir.tn_parent->tn_links++; 214197953Sdelphij TMPFS_NODE_UNLOCK(nnode->tn_dir.tn_parent); 215170808Sdelphij break; 216170808Sdelphij 217170808Sdelphij case VFIFO: 218170808Sdelphij /* FALLTHROUGH */ 219170808Sdelphij case VSOCK: 220170808Sdelphij break; 221170808Sdelphij 222170808Sdelphij case VLNK: 223170808Sdelphij MPASS(strlen(target) < MAXPATHLEN); 224170808Sdelphij nnode->tn_size = strlen(target); 225171087Sdelphij nnode->tn_link = malloc(nnode->tn_size, M_TMPFSNAME, 226171087Sdelphij M_WAITOK); 227170808Sdelphij memcpy(nnode->tn_link, target, nnode->tn_size); 228170808Sdelphij break; 229170808Sdelphij 230170808Sdelphij case VREG: 231250030Skib obj = nnode->tn_reg.tn_aobj = 232194766Skib vm_pager_allocate(OBJT_SWAP, NULL, 0, VM_PROT_DEFAULT, 0, 233194766Skib NULL /* XXXKIB - tmpfs needs swap reservation */); 234250030Skib VM_OBJECT_WLOCK(obj); 235250030Skib /* OBJ_TMPFS is set together with the setting of vp->v_object */ 236250030Skib vm_object_set_flag(obj, OBJ_NOSPLIT); 237250030Skib vm_object_clear_flag(obj, OBJ_ONEMAPPING); 238250030Skib VM_OBJECT_WUNLOCK(obj); 239170808Sdelphij break; 240170808Sdelphij 241170808Sdelphij default: 242174384Sdelphij panic("tmpfs_alloc_node: type %p %d", nnode, (int)nnode->tn_type); 243170808Sdelphij } 244170808Sdelphij 245170808Sdelphij TMPFS_LOCK(tmp); 246170808Sdelphij LIST_INSERT_HEAD(&tmp->tm_nodes_used, nnode, tn_entries); 247170808Sdelphij tmp->tm_nodes_inuse++; 248170808Sdelphij TMPFS_UNLOCK(tmp); 249170808Sdelphij 250170808Sdelphij *node = nnode; 251170808Sdelphij return 0; 252170808Sdelphij} 253170808Sdelphij 254170808Sdelphij/* --------------------------------------------------------------------- */ 255170808Sdelphij 256170808Sdelphij/* 257170808Sdelphij * Destroys the node pointed to by node from the file system 'tmp'. 258170808Sdelphij * If the node does not belong to the given mount point, the results are 259170808Sdelphij * unpredicted. 260170808Sdelphij * 261170808Sdelphij * If the node references a directory; no entries are allowed because 262170808Sdelphij * their removal could need a recursive algorithm, something forbidden in 263170808Sdelphij * kernel space. Furthermore, there is not need to provide such 264170808Sdelphij * functionality (recursive removal) because the only primitives offered 265170808Sdelphij * to the user are the removal of empty directories and the deletion of 266170808Sdelphij * individual files. 267170808Sdelphij * 268170808Sdelphij * Note that nodes are not really deleted; in fact, when a node has been 269170808Sdelphij * allocated, it cannot be deleted during the whole life of the file 270170808Sdelphij * system. Instead, they are moved to the available list and remain there 271170808Sdelphij * until reused. 272170808Sdelphij */ 273170808Sdelphijvoid 274170808Sdelphijtmpfs_free_node(struct tmpfs_mount *tmp, struct tmpfs_node *node) 275170808Sdelphij{ 276218640Salc vm_object_t uobj; 277170808Sdelphij 278171799Sdelphij#ifdef INVARIANTS 279171799Sdelphij TMPFS_NODE_LOCK(node); 280171799Sdelphij MPASS(node->tn_vnode == NULL); 281197953Sdelphij MPASS((node->tn_vpstate & TMPFS_VNODE_ALLOCATING) == 0); 282171799Sdelphij TMPFS_NODE_UNLOCK(node); 283171799Sdelphij#endif 284171799Sdelphij 285170808Sdelphij TMPFS_LOCK(tmp); 286170808Sdelphij LIST_REMOVE(node, tn_entries); 287170808Sdelphij tmp->tm_nodes_inuse--; 288170808Sdelphij TMPFS_UNLOCK(tmp); 289170808Sdelphij 290170808Sdelphij switch (node->tn_type) { 291170808Sdelphij case VNON: 292170808Sdelphij /* Do not do anything. VNON is provided to let the 293170808Sdelphij * allocation routine clean itself easily by avoiding 294170808Sdelphij * duplicating code in it. */ 295170808Sdelphij /* FALLTHROUGH */ 296170808Sdelphij case VBLK: 297170808Sdelphij /* FALLTHROUGH */ 298170808Sdelphij case VCHR: 299170808Sdelphij /* FALLTHROUGH */ 300170808Sdelphij case VDIR: 301170808Sdelphij /* FALLTHROUGH */ 302170808Sdelphij case VFIFO: 303170808Sdelphij /* FALLTHROUGH */ 304170808Sdelphij case VSOCK: 305170808Sdelphij break; 306170808Sdelphij 307170808Sdelphij case VLNK: 308171087Sdelphij free(node->tn_link, M_TMPFSNAME); 309170808Sdelphij break; 310170808Sdelphij 311170808Sdelphij case VREG: 312218640Salc uobj = node->tn_reg.tn_aobj; 313218640Salc if (uobj != NULL) { 314218640Salc TMPFS_LOCK(tmp); 315218640Salc tmp->tm_pages_used -= uobj->size; 316218640Salc TMPFS_UNLOCK(tmp); 317251149Skib KASSERT((uobj->flags & OBJ_TMPFS) == 0, 318251149Skib ("leaked OBJ_TMPFS node %p vm_obj %p", node, uobj)); 319218640Salc vm_object_deallocate(uobj); 320218640Salc } 321170808Sdelphij break; 322170808Sdelphij 323170808Sdelphij default: 324174384Sdelphij panic("tmpfs_free_node: type %p %d", node, (int)node->tn_type); 325170808Sdelphij } 326170808Sdelphij 327171362Sdelphij free_unr(tmp->tm_ino_unr, node->tn_id); 328171029Sdelphij uma_zfree(tmp->tm_node_pool, node); 329170808Sdelphij} 330170808Sdelphij 331170808Sdelphij/* --------------------------------------------------------------------- */ 332170808Sdelphij 333245115Sglebstatic __inline uint32_t 334245115Sglebtmpfs_dirent_hash(const char *name, u_int len) 335245115Sgleb{ 336245115Sgleb uint32_t hash; 337245115Sgleb 338245115Sgleb hash = fnv_32_buf(name, len, FNV1_32_INIT + len) & TMPFS_DIRCOOKIE_MASK; 339245115Sgleb#ifdef TMPFS_DEBUG_DIRCOOKIE_DUP 340245115Sgleb hash &= 0xf; 341245115Sgleb#endif 342245115Sgleb if (hash < TMPFS_DIRCOOKIE_MIN) 343245115Sgleb hash += TMPFS_DIRCOOKIE_MIN; 344245115Sgleb 345245115Sgleb return (hash); 346245115Sgleb} 347245115Sgleb 348245115Sglebstatic __inline off_t 349245115Sglebtmpfs_dirent_cookie(struct tmpfs_dirent *de) 350245115Sgleb{ 351245115Sgleb MPASS(de->td_cookie >= TMPFS_DIRCOOKIE_MIN); 352245115Sgleb 353245115Sgleb return (de->td_cookie); 354245115Sgleb} 355245115Sgleb 356245115Sglebstatic __inline boolean_t 357245115Sglebtmpfs_dirent_dup(struct tmpfs_dirent *de) 358245115Sgleb{ 359245115Sgleb return ((de->td_cookie & TMPFS_DIRCOOKIE_DUP) != 0); 360245115Sgleb} 361245115Sgleb 362245115Sglebstatic __inline boolean_t 363245115Sglebtmpfs_dirent_duphead(struct tmpfs_dirent *de) 364245115Sgleb{ 365245115Sgleb return ((de->td_cookie & TMPFS_DIRCOOKIE_DUPHEAD) != 0); 366245115Sgleb} 367245115Sgleb 368245115Sglebvoid 369245115Sglebtmpfs_dirent_init(struct tmpfs_dirent *de, const char *name, u_int namelen) 370245115Sgleb{ 371245115Sgleb de->td_hash = de->td_cookie = tmpfs_dirent_hash(name, namelen); 372245115Sgleb memcpy(de->ud.td_name, name, namelen); 373245115Sgleb de->td_namelen = namelen; 374245115Sgleb} 375245115Sgleb 376170808Sdelphij/* 377170808Sdelphij * Allocates a new directory entry for the node node with a name of name. 378170808Sdelphij * The new directory entry is returned in *de. 379170808Sdelphij * 380170808Sdelphij * The link count of node is increased by one to reflect the new object 381170808Sdelphij * referencing it. 382170808Sdelphij * 383170808Sdelphij * Returns zero on success or an appropriate error code on failure. 384170808Sdelphij */ 385170808Sdelphijint 386170808Sdelphijtmpfs_alloc_dirent(struct tmpfs_mount *tmp, struct tmpfs_node *node, 387245115Sgleb const char *name, u_int len, struct tmpfs_dirent **de) 388170808Sdelphij{ 389170808Sdelphij struct tmpfs_dirent *nde; 390170808Sdelphij 391245115Sgleb nde = uma_zalloc(tmp->tm_dirent_pool, M_WAITOK); 392170808Sdelphij nde->td_node = node; 393245115Sgleb if (name != NULL) { 394245115Sgleb nde->ud.td_name = malloc(len, M_TMPFSNAME, M_WAITOK); 395245115Sgleb tmpfs_dirent_init(nde, name, len); 396245115Sgleb } else 397245115Sgleb nde->td_namelen = 0; 398211598Sed if (node != NULL) 399211598Sed node->tn_links++; 400170808Sdelphij 401170808Sdelphij *de = nde; 402170808Sdelphij 403170808Sdelphij return 0; 404170808Sdelphij} 405170808Sdelphij 406170808Sdelphij/* --------------------------------------------------------------------- */ 407170808Sdelphij 408170808Sdelphij/* 409170808Sdelphij * Frees a directory entry. It is the caller's responsibility to destroy 410170808Sdelphij * the node referenced by it if needed. 411170808Sdelphij * 412170808Sdelphij * The link count of node is decreased by one to reflect the removal of an 413170808Sdelphij * object that referenced it. This only happens if 'node_exists' is true; 414170808Sdelphij * otherwise the function will not access the node referred to by the 415170808Sdelphij * directory entry, as it may already have been released from the outside. 416170808Sdelphij */ 417170808Sdelphijvoid 418245115Sglebtmpfs_free_dirent(struct tmpfs_mount *tmp, struct tmpfs_dirent *de) 419170808Sdelphij{ 420245115Sgleb struct tmpfs_node *node; 421170808Sdelphij 422245115Sgleb node = de->td_node; 423245115Sgleb if (node != NULL) { 424245115Sgleb MPASS(node->tn_links > 0); 425245115Sgleb node->tn_links--; 426170808Sdelphij } 427245115Sgleb if (!tmpfs_dirent_duphead(de) && de->ud.td_name != NULL) 428245115Sgleb free(de->ud.td_name, M_TMPFSNAME); 429171029Sdelphij uma_zfree(tmp->tm_dirent_pool, de); 430170808Sdelphij} 431170808Sdelphij 432170808Sdelphij/* --------------------------------------------------------------------- */ 433170808Sdelphij 434250189Skibvoid 435250189Skibtmpfs_destroy_vobject(struct vnode *vp, vm_object_t obj) 436250189Skib{ 437250189Skib 438250189Skib if (vp->v_type != VREG || obj == NULL) 439250189Skib return; 440250189Skib 441250189Skib VM_OBJECT_WLOCK(obj); 442250189Skib VI_LOCK(vp); 443250189Skib vm_object_clear_flag(obj, OBJ_TMPFS); 444250189Skib obj->un_pager.swp.swp_tmpfs = NULL; 445250189Skib VI_UNLOCK(vp); 446250189Skib VM_OBJECT_WUNLOCK(obj); 447250189Skib} 448250189Skib 449170808Sdelphij/* 450250189Skib * Need to clear v_object for insmntque failure. 451250189Skib */ 452250189Skibstatic void 453250189Skibtmpfs_insmntque_dtr(struct vnode *vp, void *dtr_arg) 454250189Skib{ 455250189Skib 456250189Skib tmpfs_destroy_vobject(vp, vp->v_object); 457250189Skib vp->v_object = NULL; 458250189Skib vp->v_data = NULL; 459250189Skib vp->v_op = &dead_vnodeops; 460250189Skib vgone(vp); 461250189Skib vput(vp); 462250189Skib} 463250189Skib 464250189Skib/* 465170808Sdelphij * Allocates a new vnode for the node node or returns a new reference to 466170808Sdelphij * an existing one if the node had already a vnode referencing it. The 467170808Sdelphij * resulting locked vnode is returned in *vpp. 468170808Sdelphij * 469170808Sdelphij * Returns zero on success or an appropriate error code on failure. 470170808Sdelphij */ 471170808Sdelphijint 472171799Sdelphijtmpfs_alloc_vp(struct mount *mp, struct tmpfs_node *node, int lkflag, 473191990Sattilio struct vnode **vpp) 474170808Sdelphij{ 475170808Sdelphij struct vnode *vp; 476250030Skib vm_object_t object; 477250030Skib int error; 478170808Sdelphij 479250030Skib error = 0; 480170808Sdelphijloop: 481171799Sdelphij TMPFS_NODE_LOCK(node); 482253967Skibloop1: 483171704Sdelphij if ((vp = node->tn_vnode) != NULL) { 484197953Sdelphij MPASS((node->tn_vpstate & TMPFS_VNODE_DOOMED) == 0); 485171799Sdelphij VI_LOCK(vp); 486253967Skib if ((node->tn_type == VDIR && node->tn_dir.tn_parent == NULL) || 487253967Skib ((vp->v_iflag & VI_DOOMED) != 0 && 488253967Skib (lkflag & LK_NOWAIT) != 0)) { 489253967Skib VI_UNLOCK(vp); 490253967Skib TMPFS_NODE_UNLOCK(node); 491253967Skib error = ENOENT; 492253967Skib vp = NULL; 493253967Skib goto out; 494253967Skib } 495253967Skib if ((vp->v_iflag & VI_DOOMED) != 0) { 496253967Skib VI_UNLOCK(vp); 497253967Skib node->tn_vpstate |= TMPFS_VNODE_WRECLAIM; 498253967Skib while ((node->tn_vpstate & TMPFS_VNODE_WRECLAIM) != 0) { 499253967Skib msleep(&node->tn_vnode, TMPFS_NODE_MTX(node), 500253967Skib 0, "tmpfsE", 0); 501253967Skib } 502253967Skib goto loop1; 503253967Skib } 504171799Sdelphij TMPFS_NODE_UNLOCK(node); 505232959Sgleb error = vget(vp, lkflag | LK_INTERLOCK, curthread); 506253967Skib if (error == ENOENT) 507253967Skib goto loop; 508232959Sgleb if (error != 0) { 509232959Sgleb vp = NULL; 510232959Sgleb goto out; 511232959Sgleb } 512171070Sdelphij 513170808Sdelphij /* 514170808Sdelphij * Make sure the vnode is still there after 515170808Sdelphij * getting the interlock to avoid racing a free. 516170808Sdelphij */ 517170808Sdelphij if (node->tn_vnode == NULL || node->tn_vnode != vp) { 518170808Sdelphij vput(vp); 519170808Sdelphij goto loop; 520170808Sdelphij } 521170808Sdelphij 522170808Sdelphij goto out; 523170808Sdelphij } 524170808Sdelphij 525197953Sdelphij if ((node->tn_vpstate & TMPFS_VNODE_DOOMED) || 526197953Sdelphij (node->tn_type == VDIR && node->tn_dir.tn_parent == NULL)) { 527197953Sdelphij TMPFS_NODE_UNLOCK(node); 528197953Sdelphij error = ENOENT; 529197953Sdelphij vp = NULL; 530197953Sdelphij goto out; 531197953Sdelphij } 532197953Sdelphij 533170808Sdelphij /* 534170808Sdelphij * otherwise lock the vp list while we call getnewvnode 535170808Sdelphij * since that can block. 536170808Sdelphij */ 537170808Sdelphij if (node->tn_vpstate & TMPFS_VNODE_ALLOCATING) { 538170808Sdelphij node->tn_vpstate |= TMPFS_VNODE_WANT; 539171704Sdelphij error = msleep((caddr_t) &node->tn_vpstate, 540171704Sdelphij TMPFS_NODE_MTX(node), PDROP | PCATCH, 541171799Sdelphij "tmpfs_alloc_vp", 0); 542171704Sdelphij if (error) 543171704Sdelphij return error; 544171704Sdelphij 545170808Sdelphij goto loop; 546171704Sdelphij } else 547171704Sdelphij node->tn_vpstate |= TMPFS_VNODE_ALLOCATING; 548171704Sdelphij 549170808Sdelphij TMPFS_NODE_UNLOCK(node); 550170808Sdelphij 551170808Sdelphij /* Get a new vnode and associate it with our node. */ 552170808Sdelphij error = getnewvnode("tmpfs", mp, &tmpfs_vnodeop_entries, &vp); 553170808Sdelphij if (error != 0) 554170808Sdelphij goto unlock; 555170808Sdelphij MPASS(vp != NULL); 556170808Sdelphij 557175202Sattilio (void) vn_lock(vp, lkflag | LK_RETRY); 558170808Sdelphij 559170808Sdelphij vp->v_data = node; 560170808Sdelphij vp->v_type = node->tn_type; 561170808Sdelphij 562170808Sdelphij /* Type-specific initialization. */ 563170808Sdelphij switch (node->tn_type) { 564170808Sdelphij case VBLK: 565170808Sdelphij /* FALLTHROUGH */ 566170808Sdelphij case VCHR: 567171704Sdelphij /* FALLTHROUGH */ 568170808Sdelphij case VLNK: 569170808Sdelphij /* FALLTHROUGH */ 570170808Sdelphij case VSOCK: 571170808Sdelphij break; 572171704Sdelphij case VFIFO: 573171704Sdelphij vp->v_op = &tmpfs_fifoop_entries; 574171704Sdelphij break; 575250030Skib case VREG: 576250030Skib object = node->tn_reg.tn_aobj; 577250030Skib VM_OBJECT_WLOCK(object); 578250030Skib VI_LOCK(vp); 579250030Skib KASSERT(vp->v_object == NULL, ("Not NULL v_object in tmpfs")); 580250030Skib vp->v_object = object; 581250030Skib object->un_pager.swp.swp_tmpfs = vp; 582250030Skib vm_object_set_flag(object, OBJ_TMPFS); 583250030Skib VI_UNLOCK(vp); 584250030Skib VM_OBJECT_WUNLOCK(object); 585250030Skib break; 586173725Sdelphij case VDIR: 587197953Sdelphij MPASS(node->tn_dir.tn_parent != NULL); 588173725Sdelphij if (node->tn_dir.tn_parent == node) 589173725Sdelphij vp->v_vflag |= VV_ROOT; 590173725Sdelphij break; 591170808Sdelphij 592170808Sdelphij default: 593174384Sdelphij panic("tmpfs_alloc_vp: type %p %d", node, (int)node->tn_type); 594170808Sdelphij } 595170808Sdelphij 596250189Skib error = insmntque1(vp, mp, tmpfs_insmntque_dtr, NULL); 597179808Skib if (error) 598171799Sdelphij vp = NULL; 599170808Sdelphij 600170808Sdelphijunlock: 601171704Sdelphij TMPFS_NODE_LOCK(node); 602171799Sdelphij 603170808Sdelphij MPASS(node->tn_vpstate & TMPFS_VNODE_ALLOCATING); 604170808Sdelphij node->tn_vpstate &= ~TMPFS_VNODE_ALLOCATING; 605171799Sdelphij node->tn_vnode = vp; 606171070Sdelphij 607170808Sdelphij if (node->tn_vpstate & TMPFS_VNODE_WANT) { 608170808Sdelphij node->tn_vpstate &= ~TMPFS_VNODE_WANT; 609170808Sdelphij TMPFS_NODE_UNLOCK(node); 610170808Sdelphij wakeup((caddr_t) &node->tn_vpstate); 611171068Sdelphij } else 612170808Sdelphij TMPFS_NODE_UNLOCK(node); 613171070Sdelphij 614170808Sdelphijout: 615170808Sdelphij *vpp = vp; 616170808Sdelphij 617171799Sdelphij#ifdef INVARIANTS 618232959Sgleb if (error == 0) { 619232959Sgleb MPASS(*vpp != NULL && VOP_ISLOCKED(*vpp)); 620232959Sgleb TMPFS_NODE_LOCK(node); 621232959Sgleb MPASS(*vpp == node->tn_vnode); 622232959Sgleb TMPFS_NODE_UNLOCK(node); 623232959Sgleb } 624171799Sdelphij#endif 625170808Sdelphij 626170808Sdelphij return error; 627170808Sdelphij} 628170808Sdelphij 629170808Sdelphij/* --------------------------------------------------------------------- */ 630170808Sdelphij 631170808Sdelphij/* 632170808Sdelphij * Destroys the association between the vnode vp and the node it 633170808Sdelphij * references. 634170808Sdelphij */ 635170808Sdelphijvoid 636170808Sdelphijtmpfs_free_vp(struct vnode *vp) 637170808Sdelphij{ 638170808Sdelphij struct tmpfs_node *node; 639170808Sdelphij 640170808Sdelphij node = VP_TO_TMPFS_NODE(vp); 641170808Sdelphij 642197953Sdelphij mtx_assert(TMPFS_NODE_MTX(node), MA_OWNED); 643170808Sdelphij node->tn_vnode = NULL; 644253967Skib if ((node->tn_vpstate & TMPFS_VNODE_WRECLAIM) != 0) 645253967Skib wakeup(&node->tn_vnode); 646253967Skib node->tn_vpstate &= ~TMPFS_VNODE_WRECLAIM; 647170808Sdelphij vp->v_data = NULL; 648170808Sdelphij} 649170808Sdelphij 650170808Sdelphij/* --------------------------------------------------------------------- */ 651170808Sdelphij 652170808Sdelphij/* 653170808Sdelphij * Allocates a new file of type 'type' and adds it to the parent directory 654170808Sdelphij * 'dvp'; this addition is done using the component name given in 'cnp'. 655170808Sdelphij * The ownership of the new file is automatically assigned based on the 656170808Sdelphij * credentials of the caller (through 'cnp'), the group is set based on 657170808Sdelphij * the parent directory and the mode is determined from the 'vap' argument. 658170808Sdelphij * If successful, *vpp holds a vnode to the newly created file and zero 659170808Sdelphij * is returned. Otherwise *vpp is NULL and the function returns an 660170808Sdelphij * appropriate error code. 661170808Sdelphij */ 662170808Sdelphijint 663170808Sdelphijtmpfs_alloc_file(struct vnode *dvp, struct vnode **vpp, struct vattr *vap, 664170808Sdelphij struct componentname *cnp, char *target) 665170808Sdelphij{ 666170808Sdelphij int error; 667170808Sdelphij struct tmpfs_dirent *de; 668170808Sdelphij struct tmpfs_mount *tmp; 669170808Sdelphij struct tmpfs_node *dnode; 670170808Sdelphij struct tmpfs_node *node; 671170808Sdelphij struct tmpfs_node *parent; 672170808Sdelphij 673176559Sattilio MPASS(VOP_ISLOCKED(dvp)); 674170808Sdelphij MPASS(cnp->cn_flags & HASBUF); 675170808Sdelphij 676170808Sdelphij tmp = VFS_TO_TMPFS(dvp->v_mount); 677170808Sdelphij dnode = VP_TO_TMPFS_DIR(dvp); 678170808Sdelphij *vpp = NULL; 679170808Sdelphij 680170808Sdelphij /* If the entry we are creating is a directory, we cannot overflow 681170808Sdelphij * the number of links of its parent, because it will get a new 682170808Sdelphij * link. */ 683170808Sdelphij if (vap->va_type == VDIR) { 684170808Sdelphij /* Ensure that we do not overflow the maximum number of links 685170808Sdelphij * imposed by the system. */ 686170808Sdelphij MPASS(dnode->tn_links <= LINK_MAX); 687170808Sdelphij if (dnode->tn_links == LINK_MAX) { 688170808Sdelphij error = EMLINK; 689170808Sdelphij goto out; 690170808Sdelphij } 691170808Sdelphij 692170808Sdelphij parent = dnode; 693173725Sdelphij MPASS(parent != NULL); 694170808Sdelphij } else 695170808Sdelphij parent = NULL; 696170808Sdelphij 697170808Sdelphij /* Allocate a node that represents the new file. */ 698170808Sdelphij error = tmpfs_alloc_node(tmp, vap->va_type, cnp->cn_cred->cr_uid, 699191990Sattilio dnode->tn_gid, vap->va_mode, parent, target, vap->va_rdev, &node); 700170808Sdelphij if (error != 0) 701170808Sdelphij goto out; 702170808Sdelphij 703170808Sdelphij /* Allocate a directory entry that points to the new file. */ 704170808Sdelphij error = tmpfs_alloc_dirent(tmp, node, cnp->cn_nameptr, cnp->cn_namelen, 705170808Sdelphij &de); 706170808Sdelphij if (error != 0) { 707170808Sdelphij tmpfs_free_node(tmp, node); 708170808Sdelphij goto out; 709170808Sdelphij } 710170808Sdelphij 711170808Sdelphij /* Allocate a vnode for the new file. */ 712191990Sattilio error = tmpfs_alloc_vp(dvp->v_mount, node, LK_EXCLUSIVE, vpp); 713170808Sdelphij if (error != 0) { 714245115Sgleb tmpfs_free_dirent(tmp, de); 715170808Sdelphij tmpfs_free_node(tmp, node); 716170808Sdelphij goto out; 717170808Sdelphij } 718170808Sdelphij 719170808Sdelphij /* Now that all required items are allocated, we can proceed to 720170808Sdelphij * insert the new node into the directory, an operation that 721170808Sdelphij * cannot fail. */ 722211598Sed if (cnp->cn_flags & ISWHITEOUT) 723211598Sed tmpfs_dir_whiteout_remove(dvp, cnp); 724170808Sdelphij tmpfs_dir_attach(dvp, de); 725170808Sdelphij 726170808Sdelphijout: 727170808Sdelphij 728170808Sdelphij return error; 729170808Sdelphij} 730170808Sdelphij 731170808Sdelphij/* --------------------------------------------------------------------- */ 732170808Sdelphij 733245115Sglebstatic struct tmpfs_dirent * 734245115Sglebtmpfs_dir_first(struct tmpfs_node *dnode, struct tmpfs_dir_cursor *dc) 735245115Sgleb{ 736245115Sgleb struct tmpfs_dirent *de; 737245115Sgleb 738245115Sgleb de = RB_MIN(tmpfs_dir, &dnode->tn_dir.tn_dirhead); 739245115Sgleb dc->tdc_tree = de; 740245115Sgleb if (de != NULL && tmpfs_dirent_duphead(de)) 741245115Sgleb de = LIST_FIRST(&de->ud.td_duphead); 742245115Sgleb dc->tdc_current = de; 743245115Sgleb 744245115Sgleb return (dc->tdc_current); 745245115Sgleb} 746245115Sgleb 747245115Sglebstatic struct tmpfs_dirent * 748245115Sglebtmpfs_dir_next(struct tmpfs_node *dnode, struct tmpfs_dir_cursor *dc) 749245115Sgleb{ 750245115Sgleb struct tmpfs_dirent *de; 751245115Sgleb 752245115Sgleb MPASS(dc->tdc_tree != NULL); 753245115Sgleb if (tmpfs_dirent_dup(dc->tdc_current)) { 754245115Sgleb dc->tdc_current = LIST_NEXT(dc->tdc_current, uh.td_dup.entries); 755245115Sgleb if (dc->tdc_current != NULL) 756245115Sgleb return (dc->tdc_current); 757245115Sgleb } 758245115Sgleb dc->tdc_tree = dc->tdc_current = RB_NEXT(tmpfs_dir, 759245115Sgleb &dnode->tn_dir.tn_dirhead, dc->tdc_tree); 760245115Sgleb if ((de = dc->tdc_current) != NULL && tmpfs_dirent_duphead(de)) { 761245115Sgleb dc->tdc_current = LIST_FIRST(&de->ud.td_duphead); 762245115Sgleb MPASS(dc->tdc_current != NULL); 763245115Sgleb } 764245115Sgleb 765245115Sgleb return (dc->tdc_current); 766245115Sgleb} 767245115Sgleb 768245115Sgleb/* Lookup directory entry in RB-Tree. Function may return duphead entry. */ 769245115Sglebstatic struct tmpfs_dirent * 770245115Sglebtmpfs_dir_xlookup_hash(struct tmpfs_node *dnode, uint32_t hash) 771245115Sgleb{ 772245115Sgleb struct tmpfs_dirent *de, dekey; 773245115Sgleb 774245115Sgleb dekey.td_hash = hash; 775245115Sgleb de = RB_FIND(tmpfs_dir, &dnode->tn_dir.tn_dirhead, &dekey); 776245115Sgleb return (de); 777245115Sgleb} 778245115Sgleb 779245115Sgleb/* Lookup directory entry by cookie, initialize directory cursor accordingly. */ 780245115Sglebstatic struct tmpfs_dirent * 781245115Sglebtmpfs_dir_lookup_cookie(struct tmpfs_node *node, off_t cookie, 782245115Sgleb struct tmpfs_dir_cursor *dc) 783245115Sgleb{ 784245115Sgleb struct tmpfs_dir *dirhead = &node->tn_dir.tn_dirhead; 785245115Sgleb struct tmpfs_dirent *de, dekey; 786245115Sgleb 787245115Sgleb MPASS(cookie >= TMPFS_DIRCOOKIE_MIN); 788245115Sgleb 789245115Sgleb if (cookie == node->tn_dir.tn_readdir_lastn && 790245115Sgleb (de = node->tn_dir.tn_readdir_lastp) != NULL) { 791245115Sgleb /* Protect against possible race, tn_readdir_last[pn] 792245115Sgleb * may be updated with only shared vnode lock held. */ 793245115Sgleb if (cookie == tmpfs_dirent_cookie(de)) 794245115Sgleb goto out; 795245115Sgleb } 796245115Sgleb 797245115Sgleb if ((cookie & TMPFS_DIRCOOKIE_DUP) != 0) { 798245115Sgleb LIST_FOREACH(de, &node->tn_dir.tn_dupindex, 799245115Sgleb uh.td_dup.index_entries) { 800245115Sgleb MPASS(tmpfs_dirent_dup(de)); 801245115Sgleb if (de->td_cookie == cookie) 802245115Sgleb goto out; 803245115Sgleb /* dupindex list is sorted. */ 804245115Sgleb if (de->td_cookie < cookie) { 805245115Sgleb de = NULL; 806245115Sgleb goto out; 807245115Sgleb } 808245115Sgleb } 809245115Sgleb MPASS(de == NULL); 810245115Sgleb goto out; 811245115Sgleb } 812245115Sgleb 813245115Sgleb MPASS((cookie & TMPFS_DIRCOOKIE_MASK) == cookie); 814245115Sgleb dekey.td_hash = cookie; 815245115Sgleb /* Recover if direntry for cookie was removed */ 816245115Sgleb de = RB_NFIND(tmpfs_dir, dirhead, &dekey); 817245115Sgleb dc->tdc_tree = de; 818245115Sgleb dc->tdc_current = de; 819245115Sgleb if (de != NULL && tmpfs_dirent_duphead(de)) { 820245115Sgleb dc->tdc_current = LIST_FIRST(&de->ud.td_duphead); 821245115Sgleb MPASS(dc->tdc_current != NULL); 822245115Sgleb } 823245115Sgleb return (dc->tdc_current); 824245115Sgleb 825245115Sglebout: 826245115Sgleb dc->tdc_tree = de; 827245115Sgleb dc->tdc_current = de; 828245115Sgleb if (de != NULL && tmpfs_dirent_dup(de)) 829245115Sgleb dc->tdc_tree = tmpfs_dir_xlookup_hash(node, 830245115Sgleb de->td_hash); 831245115Sgleb return (dc->tdc_current); 832245115Sgleb} 833245115Sgleb 834170808Sdelphij/* 835245115Sgleb * Looks for a directory entry in the directory represented by node. 836245115Sgleb * 'cnp' describes the name of the entry to look for. Note that the . 837245115Sgleb * and .. components are not allowed as they do not physically exist 838245115Sgleb * within directories. 839245115Sgleb * 840245115Sgleb * Returns a pointer to the entry when found, otherwise NULL. 841245115Sgleb */ 842245115Sglebstruct tmpfs_dirent * 843245115Sglebtmpfs_dir_lookup(struct tmpfs_node *node, struct tmpfs_node *f, 844245115Sgleb struct componentname *cnp) 845245115Sgleb{ 846245115Sgleb struct tmpfs_dir_duphead *duphead; 847245115Sgleb struct tmpfs_dirent *de; 848245115Sgleb uint32_t hash; 849245115Sgleb 850245115Sgleb MPASS(IMPLIES(cnp->cn_namelen == 1, cnp->cn_nameptr[0] != '.')); 851245115Sgleb MPASS(IMPLIES(cnp->cn_namelen == 2, !(cnp->cn_nameptr[0] == '.' && 852245115Sgleb cnp->cn_nameptr[1] == '.'))); 853245115Sgleb TMPFS_VALIDATE_DIR(node); 854245115Sgleb 855245115Sgleb hash = tmpfs_dirent_hash(cnp->cn_nameptr, cnp->cn_namelen); 856245115Sgleb de = tmpfs_dir_xlookup_hash(node, hash); 857245115Sgleb if (de != NULL && tmpfs_dirent_duphead(de)) { 858245115Sgleb duphead = &de->ud.td_duphead; 859245115Sgleb LIST_FOREACH(de, duphead, uh.td_dup.entries) { 860245115Sgleb if (TMPFS_DIRENT_MATCHES(de, cnp->cn_nameptr, 861245115Sgleb cnp->cn_namelen)) 862245115Sgleb break; 863245115Sgleb } 864245115Sgleb } else if (de != NULL) { 865245115Sgleb if (!TMPFS_DIRENT_MATCHES(de, cnp->cn_nameptr, 866245115Sgleb cnp->cn_namelen)) 867245115Sgleb de = NULL; 868245115Sgleb } 869245115Sgleb if (de != NULL && f != NULL && de->td_node != f) 870245115Sgleb de = NULL; 871245115Sgleb 872245115Sgleb return (de); 873245115Sgleb} 874245115Sgleb 875245115Sgleb/* 876245115Sgleb * Attach duplicate-cookie directory entry nde to dnode and insert to dupindex 877245115Sgleb * list, allocate new cookie value. 878245115Sgleb */ 879245115Sglebstatic void 880245115Sglebtmpfs_dir_attach_dup(struct tmpfs_node *dnode, 881245115Sgleb struct tmpfs_dir_duphead *duphead, struct tmpfs_dirent *nde) 882245115Sgleb{ 883245115Sgleb struct tmpfs_dir_duphead *dupindex; 884245115Sgleb struct tmpfs_dirent *de, *pde; 885245115Sgleb 886245115Sgleb dupindex = &dnode->tn_dir.tn_dupindex; 887245115Sgleb de = LIST_FIRST(dupindex); 888245115Sgleb if (de == NULL || de->td_cookie < TMPFS_DIRCOOKIE_DUP_MAX) { 889245115Sgleb if (de == NULL) 890245115Sgleb nde->td_cookie = TMPFS_DIRCOOKIE_DUP_MIN; 891245115Sgleb else 892245115Sgleb nde->td_cookie = de->td_cookie + 1; 893245115Sgleb MPASS(tmpfs_dirent_dup(nde)); 894245115Sgleb LIST_INSERT_HEAD(dupindex, nde, uh.td_dup.index_entries); 895245115Sgleb LIST_INSERT_HEAD(duphead, nde, uh.td_dup.entries); 896245115Sgleb return; 897245115Sgleb } 898245115Sgleb 899245115Sgleb /* 900245115Sgleb * Cookie numbers are near exhaustion. Scan dupindex list for unused 901245115Sgleb * numbers. dupindex list is sorted in descending order. Keep it so 902245115Sgleb * after inserting nde. 903245115Sgleb */ 904245115Sgleb while (1) { 905245115Sgleb pde = de; 906245115Sgleb de = LIST_NEXT(de, uh.td_dup.index_entries); 907245115Sgleb if (de == NULL && pde->td_cookie != TMPFS_DIRCOOKIE_DUP_MIN) { 908245115Sgleb /* 909245115Sgleb * Last element of the index doesn't have minimal cookie 910245115Sgleb * value, use it. 911245115Sgleb */ 912245115Sgleb nde->td_cookie = TMPFS_DIRCOOKIE_DUP_MIN; 913245115Sgleb LIST_INSERT_AFTER(pde, nde, uh.td_dup.index_entries); 914245115Sgleb LIST_INSERT_HEAD(duphead, nde, uh.td_dup.entries); 915245115Sgleb return; 916245115Sgleb } else if (de == NULL) { 917245115Sgleb /* 918245115Sgleb * We are so lucky have 2^30 hash duplicates in single 919245115Sgleb * directory :) Return largest possible cookie value. 920245115Sgleb * It should be fine except possible issues with 921245115Sgleb * VOP_READDIR restart. 922245115Sgleb */ 923245115Sgleb nde->td_cookie = TMPFS_DIRCOOKIE_DUP_MAX; 924245115Sgleb LIST_INSERT_HEAD(dupindex, nde, 925245115Sgleb uh.td_dup.index_entries); 926245115Sgleb LIST_INSERT_HEAD(duphead, nde, uh.td_dup.entries); 927245115Sgleb return; 928245115Sgleb } 929245115Sgleb if (de->td_cookie + 1 == pde->td_cookie || 930245115Sgleb de->td_cookie >= TMPFS_DIRCOOKIE_DUP_MAX) 931245115Sgleb continue; /* No hole or invalid cookie. */ 932245115Sgleb nde->td_cookie = de->td_cookie + 1; 933245115Sgleb MPASS(tmpfs_dirent_dup(nde)); 934245115Sgleb MPASS(pde->td_cookie > nde->td_cookie); 935245115Sgleb MPASS(nde->td_cookie > de->td_cookie); 936245115Sgleb LIST_INSERT_BEFORE(de, nde, uh.td_dup.index_entries); 937245115Sgleb LIST_INSERT_HEAD(duphead, nde, uh.td_dup.entries); 938245115Sgleb return; 939245115Sgleb }; 940245115Sgleb} 941245115Sgleb 942245115Sgleb/* 943170808Sdelphij * Attaches the directory entry de to the directory represented by vp. 944170808Sdelphij * Note that this does not change the link count of the node pointed by 945170808Sdelphij * the directory entry, as this is done by tmpfs_alloc_dirent. 946170808Sdelphij */ 947170808Sdelphijvoid 948170808Sdelphijtmpfs_dir_attach(struct vnode *vp, struct tmpfs_dirent *de) 949170808Sdelphij{ 950170808Sdelphij struct tmpfs_node *dnode; 951245115Sgleb struct tmpfs_dirent *xde, *nde; 952170808Sdelphij 953171704Sdelphij ASSERT_VOP_ELOCKED(vp, __func__); 954245115Sgleb MPASS(de->td_namelen > 0); 955245115Sgleb MPASS(de->td_hash >= TMPFS_DIRCOOKIE_MIN); 956245115Sgleb MPASS(de->td_cookie == de->td_hash); 957245115Sgleb 958170808Sdelphij dnode = VP_TO_TMPFS_DIR(vp); 959245115Sgleb dnode->tn_dir.tn_readdir_lastn = 0; 960245115Sgleb dnode->tn_dir.tn_readdir_lastp = NULL; 961245115Sgleb 962245115Sgleb MPASS(!tmpfs_dirent_dup(de)); 963245115Sgleb xde = RB_INSERT(tmpfs_dir, &dnode->tn_dir.tn_dirhead, de); 964245115Sgleb if (xde != NULL && tmpfs_dirent_duphead(xde)) 965245115Sgleb tmpfs_dir_attach_dup(dnode, &xde->ud.td_duphead, de); 966245115Sgleb else if (xde != NULL) { 967245115Sgleb /* 968245115Sgleb * Allocate new duphead. Swap xde with duphead to avoid 969245115Sgleb * adding/removing elements with the same hash. 970245115Sgleb */ 971245115Sgleb MPASS(!tmpfs_dirent_dup(xde)); 972245115Sgleb tmpfs_alloc_dirent(VFS_TO_TMPFS(vp->v_mount), NULL, NULL, 0, 973245115Sgleb &nde); 974245115Sgleb /* *nde = *xde; XXX gcc 4.2.1 may generate invalid code. */ 975245115Sgleb memcpy(nde, xde, sizeof(*xde)); 976245115Sgleb xde->td_cookie |= TMPFS_DIRCOOKIE_DUPHEAD; 977245115Sgleb LIST_INIT(&xde->ud.td_duphead); 978245115Sgleb xde->td_namelen = 0; 979245115Sgleb xde->td_node = NULL; 980245115Sgleb tmpfs_dir_attach_dup(dnode, &xde->ud.td_duphead, nde); 981245115Sgleb tmpfs_dir_attach_dup(dnode, &xde->ud.td_duphead, de); 982245115Sgleb } 983170808Sdelphij dnode->tn_size += sizeof(struct tmpfs_dirent); 984170808Sdelphij dnode->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \ 985170808Sdelphij TMPFS_NODE_MODIFIED; 986170808Sdelphij} 987170808Sdelphij 988170808Sdelphij/* --------------------------------------------------------------------- */ 989170808Sdelphij 990170808Sdelphij/* 991170808Sdelphij * Detaches the directory entry de from the directory represented by vp. 992170808Sdelphij * Note that this does not change the link count of the node pointed by 993170808Sdelphij * the directory entry, as this is done by tmpfs_free_dirent. 994170808Sdelphij */ 995170808Sdelphijvoid 996170808Sdelphijtmpfs_dir_detach(struct vnode *vp, struct tmpfs_dirent *de) 997170808Sdelphij{ 998245115Sgleb struct tmpfs_mount *tmp; 999245115Sgleb struct tmpfs_dir *head; 1000170808Sdelphij struct tmpfs_node *dnode; 1001245115Sgleb struct tmpfs_dirent *xde; 1002170808Sdelphij 1003171704Sdelphij ASSERT_VOP_ELOCKED(vp, __func__); 1004245115Sgleb 1005170808Sdelphij dnode = VP_TO_TMPFS_DIR(vp); 1006245115Sgleb head = &dnode->tn_dir.tn_dirhead; 1007245115Sgleb dnode->tn_dir.tn_readdir_lastn = 0; 1008245115Sgleb dnode->tn_dir.tn_readdir_lastp = NULL; 1009170808Sdelphij 1010245115Sgleb if (tmpfs_dirent_dup(de)) { 1011245115Sgleb /* Remove duphead if de was last entry. */ 1012245115Sgleb if (LIST_NEXT(de, uh.td_dup.entries) == NULL) { 1013245115Sgleb xde = tmpfs_dir_xlookup_hash(dnode, de->td_hash); 1014245115Sgleb MPASS(tmpfs_dirent_duphead(xde)); 1015245115Sgleb } else 1016245115Sgleb xde = NULL; 1017245115Sgleb LIST_REMOVE(de, uh.td_dup.entries); 1018245115Sgleb LIST_REMOVE(de, uh.td_dup.index_entries); 1019245115Sgleb if (xde != NULL) { 1020245115Sgleb if (LIST_EMPTY(&xde->ud.td_duphead)) { 1021245115Sgleb RB_REMOVE(tmpfs_dir, head, xde); 1022245115Sgleb tmp = VFS_TO_TMPFS(vp->v_mount); 1023245115Sgleb MPASS(xde->td_node == NULL); 1024245115Sgleb tmpfs_free_dirent(tmp, xde); 1025245115Sgleb } 1026245115Sgleb } 1027245115Sgleb } else 1028245115Sgleb RB_REMOVE(tmpfs_dir, head, de); 1029170808Sdelphij 1030170808Sdelphij dnode->tn_size -= sizeof(struct tmpfs_dirent); 1031170808Sdelphij dnode->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \ 1032170808Sdelphij TMPFS_NODE_MODIFIED; 1033170808Sdelphij} 1034170808Sdelphij 1035245115Sglebvoid 1036245115Sglebtmpfs_dir_destroy(struct tmpfs_mount *tmp, struct tmpfs_node *dnode) 1037170808Sdelphij{ 1038245115Sgleb struct tmpfs_dirent *de, *dde, *nde; 1039170808Sdelphij 1040245115Sgleb RB_FOREACH_SAFE(de, tmpfs_dir, &dnode->tn_dir.tn_dirhead, nde) { 1041245115Sgleb RB_REMOVE(tmpfs_dir, &dnode->tn_dir.tn_dirhead, de); 1042245115Sgleb /* Node may already be destroyed. */ 1043245115Sgleb de->td_node = NULL; 1044245115Sgleb if (tmpfs_dirent_duphead(de)) { 1045245115Sgleb while ((dde = LIST_FIRST(&de->ud.td_duphead)) != NULL) { 1046245115Sgleb LIST_REMOVE(dde, uh.td_dup.entries); 1047245115Sgleb dde->td_node = NULL; 1048245115Sgleb tmpfs_free_dirent(tmp, dde); 1049245115Sgleb } 1050170808Sdelphij } 1051245115Sgleb tmpfs_free_dirent(tmp, de); 1052170808Sdelphij } 1053170808Sdelphij} 1054170808Sdelphij 1055170808Sdelphij/* --------------------------------------------------------------------- */ 1056170808Sdelphij 1057170808Sdelphij/* 1058170808Sdelphij * Helper function for tmpfs_readdir. Creates a '.' entry for the given 1059170808Sdelphij * directory and returns it in the uio space. The function returns 0 1060170808Sdelphij * on success, -1 if there was not enough space in the uio structure to 1061170808Sdelphij * hold the directory entry or an appropriate error code if another 1062170808Sdelphij * error happens. 1063170808Sdelphij */ 1064245115Sglebstatic int 1065170808Sdelphijtmpfs_dir_getdotdent(struct tmpfs_node *node, struct uio *uio) 1066170808Sdelphij{ 1067170808Sdelphij int error; 1068170808Sdelphij struct dirent dent; 1069170808Sdelphij 1070170808Sdelphij TMPFS_VALIDATE_DIR(node); 1071170808Sdelphij MPASS(uio->uio_offset == TMPFS_DIRCOOKIE_DOT); 1072170808Sdelphij 1073170808Sdelphij dent.d_fileno = node->tn_id; 1074170808Sdelphij dent.d_type = DT_DIR; 1075170808Sdelphij dent.d_namlen = 1; 1076170808Sdelphij dent.d_name[0] = '.'; 1077170808Sdelphij dent.d_name[1] = '\0'; 1078170808Sdelphij dent.d_reclen = GENERIC_DIRSIZ(&dent); 1079170808Sdelphij 1080170808Sdelphij if (dent.d_reclen > uio->uio_resid) 1081245115Sgleb error = EJUSTRETURN; 1082245115Sgleb else 1083170808Sdelphij error = uiomove(&dent, dent.d_reclen, uio); 1084170808Sdelphij 1085170808Sdelphij node->tn_status |= TMPFS_NODE_ACCESSED; 1086170808Sdelphij 1087170808Sdelphij return error; 1088170808Sdelphij} 1089170808Sdelphij 1090170808Sdelphij/* --------------------------------------------------------------------- */ 1091170808Sdelphij 1092170808Sdelphij/* 1093170808Sdelphij * Helper function for tmpfs_readdir. Creates a '..' entry for the given 1094170808Sdelphij * directory and returns it in the uio space. The function returns 0 1095170808Sdelphij * on success, -1 if there was not enough space in the uio structure to 1096170808Sdelphij * hold the directory entry or an appropriate error code if another 1097170808Sdelphij * error happens. 1098170808Sdelphij */ 1099245115Sglebstatic int 1100170808Sdelphijtmpfs_dir_getdotdotdent(struct tmpfs_node *node, struct uio *uio) 1101170808Sdelphij{ 1102170808Sdelphij int error; 1103170808Sdelphij struct dirent dent; 1104170808Sdelphij 1105170808Sdelphij TMPFS_VALIDATE_DIR(node); 1106170808Sdelphij MPASS(uio->uio_offset == TMPFS_DIRCOOKIE_DOTDOT); 1107170808Sdelphij 1108197953Sdelphij /* 1109197953Sdelphij * Return ENOENT if the current node is already removed. 1110197953Sdelphij */ 1111197953Sdelphij TMPFS_ASSERT_LOCKED(node); 1112197953Sdelphij if (node->tn_dir.tn_parent == NULL) { 1113197953Sdelphij return (ENOENT); 1114197953Sdelphij } 1115197953Sdelphij 1116197953Sdelphij TMPFS_NODE_LOCK(node->tn_dir.tn_parent); 1117170808Sdelphij dent.d_fileno = node->tn_dir.tn_parent->tn_id; 1118197953Sdelphij TMPFS_NODE_UNLOCK(node->tn_dir.tn_parent); 1119197953Sdelphij 1120170808Sdelphij dent.d_type = DT_DIR; 1121170808Sdelphij dent.d_namlen = 2; 1122170808Sdelphij dent.d_name[0] = '.'; 1123170808Sdelphij dent.d_name[1] = '.'; 1124170808Sdelphij dent.d_name[2] = '\0'; 1125170808Sdelphij dent.d_reclen = GENERIC_DIRSIZ(&dent); 1126170808Sdelphij 1127170808Sdelphij if (dent.d_reclen > uio->uio_resid) 1128245115Sgleb error = EJUSTRETURN; 1129245115Sgleb else 1130170808Sdelphij error = uiomove(&dent, dent.d_reclen, uio); 1131170808Sdelphij 1132170808Sdelphij node->tn_status |= TMPFS_NODE_ACCESSED; 1133170808Sdelphij 1134170808Sdelphij return error; 1135170808Sdelphij} 1136170808Sdelphij 1137170808Sdelphij/* --------------------------------------------------------------------- */ 1138170808Sdelphij 1139170808Sdelphij/* 1140170808Sdelphij * Helper function for tmpfs_readdir. Returns as much directory entries 1141170808Sdelphij * as can fit in the uio space. The read starts at uio->uio_offset. 1142170808Sdelphij * The function returns 0 on success, -1 if there was not enough space 1143170808Sdelphij * in the uio structure to hold the directory entry or an appropriate 1144170808Sdelphij * error code if another error happens. 1145170808Sdelphij */ 1146170808Sdelphijint 1147245115Sglebtmpfs_dir_getdents(struct tmpfs_node *node, struct uio *uio, int cnt, 1148245115Sgleb u_long *cookies, int *ncookies) 1149170808Sdelphij{ 1150245115Sgleb struct tmpfs_dir_cursor dc; 1151245115Sgleb struct tmpfs_dirent *de; 1152245115Sgleb off_t off; 1153170808Sdelphij int error; 1154170808Sdelphij 1155170808Sdelphij TMPFS_VALIDATE_DIR(node); 1156170808Sdelphij 1157245115Sgleb off = 0; 1158245115Sgleb switch (uio->uio_offset) { 1159245115Sgleb case TMPFS_DIRCOOKIE_DOT: 1160245115Sgleb error = tmpfs_dir_getdotdent(node, uio); 1161245115Sgleb if (error != 0) 1162245115Sgleb return (error); 1163245115Sgleb uio->uio_offset = TMPFS_DIRCOOKIE_DOTDOT; 1164245115Sgleb if (cnt != 0) 1165245115Sgleb cookies[(*ncookies)++] = off = uio->uio_offset; 1166245115Sgleb case TMPFS_DIRCOOKIE_DOTDOT: 1167245115Sgleb error = tmpfs_dir_getdotdotdent(node, uio); 1168245115Sgleb if (error != 0) 1169245115Sgleb return (error); 1170245115Sgleb de = tmpfs_dir_first(node, &dc); 1171245115Sgleb if (de == NULL) 1172245115Sgleb uio->uio_offset = TMPFS_DIRCOOKIE_EOF; 1173245115Sgleb else 1174245115Sgleb uio->uio_offset = tmpfs_dirent_cookie(de); 1175245115Sgleb if (cnt != 0) 1176245115Sgleb cookies[(*ncookies)++] = off = uio->uio_offset; 1177245115Sgleb if (de == NULL) 1178245115Sgleb return (0); 1179245115Sgleb break; 1180245115Sgleb case TMPFS_DIRCOOKIE_EOF: 1181245115Sgleb return (0); 1182245115Sgleb default: 1183245115Sgleb de = tmpfs_dir_lookup_cookie(node, uio->uio_offset, &dc); 1184245115Sgleb if (de == NULL) 1185245115Sgleb return (EINVAL); 1186245115Sgleb if (cnt != 0) 1187245115Sgleb off = tmpfs_dirent_cookie(de); 1188170808Sdelphij } 1189170808Sdelphij 1190170808Sdelphij /* Read as much entries as possible; i.e., until we reach the end of 1191170808Sdelphij * the directory or we exhaust uio space. */ 1192170808Sdelphij do { 1193170808Sdelphij struct dirent d; 1194170808Sdelphij 1195170808Sdelphij /* Create a dirent structure representing the current 1196170808Sdelphij * tmpfs_node and fill it. */ 1197211598Sed if (de->td_node == NULL) { 1198211598Sed d.d_fileno = 1; 1199211598Sed d.d_type = DT_WHT; 1200211598Sed } else { 1201211598Sed d.d_fileno = de->td_node->tn_id; 1202211598Sed switch (de->td_node->tn_type) { 1203211598Sed case VBLK: 1204211598Sed d.d_type = DT_BLK; 1205211598Sed break; 1206170808Sdelphij 1207211598Sed case VCHR: 1208211598Sed d.d_type = DT_CHR; 1209211598Sed break; 1210170808Sdelphij 1211211598Sed case VDIR: 1212211598Sed d.d_type = DT_DIR; 1213211598Sed break; 1214170808Sdelphij 1215211598Sed case VFIFO: 1216211598Sed d.d_type = DT_FIFO; 1217211598Sed break; 1218170808Sdelphij 1219211598Sed case VLNK: 1220211598Sed d.d_type = DT_LNK; 1221211598Sed break; 1222170808Sdelphij 1223211598Sed case VREG: 1224211598Sed d.d_type = DT_REG; 1225211598Sed break; 1226170808Sdelphij 1227211598Sed case VSOCK: 1228211598Sed d.d_type = DT_SOCK; 1229211598Sed break; 1230170808Sdelphij 1231211598Sed default: 1232211598Sed panic("tmpfs_dir_getdents: type %p %d", 1233211598Sed de->td_node, (int)de->td_node->tn_type); 1234211598Sed } 1235170808Sdelphij } 1236170808Sdelphij d.d_namlen = de->td_namelen; 1237170808Sdelphij MPASS(de->td_namelen < sizeof(d.d_name)); 1238245115Sgleb (void)memcpy(d.d_name, de->ud.td_name, de->td_namelen); 1239170808Sdelphij d.d_name[de->td_namelen] = '\0'; 1240170808Sdelphij d.d_reclen = GENERIC_DIRSIZ(&d); 1241170808Sdelphij 1242170808Sdelphij /* Stop reading if the directory entry we are treating is 1243170808Sdelphij * bigger than the amount of data that can be returned. */ 1244170808Sdelphij if (d.d_reclen > uio->uio_resid) { 1245245115Sgleb error = EJUSTRETURN; 1246170808Sdelphij break; 1247170808Sdelphij } 1248170808Sdelphij 1249170808Sdelphij /* Copy the new dirent structure into the output buffer and 1250170808Sdelphij * advance pointers. */ 1251170808Sdelphij error = uiomove(&d, d.d_reclen, uio); 1252217633Skib if (error == 0) { 1253245115Sgleb de = tmpfs_dir_next(node, &dc); 1254245115Sgleb if (cnt != 0) { 1255245115Sgleb if (de == NULL) 1256245115Sgleb off = TMPFS_DIRCOOKIE_EOF; 1257245115Sgleb else 1258245115Sgleb off = tmpfs_dirent_cookie(de); 1259245115Sgleb MPASS(*ncookies < cnt); 1260245115Sgleb cookies[(*ncookies)++] = off; 1261245115Sgleb } 1262217633Skib } 1263170808Sdelphij } while (error == 0 && uio->uio_resid > 0 && de != NULL); 1264170808Sdelphij 1265170808Sdelphij /* Update the offset and cache. */ 1266245115Sgleb if (cnt == 0) { 1267245115Sgleb if (de == NULL) 1268245115Sgleb off = TMPFS_DIRCOOKIE_EOF; 1269245115Sgleb else 1270245115Sgleb off = tmpfs_dirent_cookie(de); 1271170808Sdelphij } 1272170808Sdelphij 1273245115Sgleb uio->uio_offset = off; 1274245115Sgleb node->tn_dir.tn_readdir_lastn = off; 1275245115Sgleb node->tn_dir.tn_readdir_lastp = de; 1276245115Sgleb 1277170808Sdelphij node->tn_status |= TMPFS_NODE_ACCESSED; 1278170808Sdelphij return error; 1279170808Sdelphij} 1280170808Sdelphij 1281211598Sedint 1282211598Sedtmpfs_dir_whiteout_add(struct vnode *dvp, struct componentname *cnp) 1283211598Sed{ 1284211598Sed struct tmpfs_dirent *de; 1285211598Sed int error; 1286211598Sed 1287211598Sed error = tmpfs_alloc_dirent(VFS_TO_TMPFS(dvp->v_mount), NULL, 1288211598Sed cnp->cn_nameptr, cnp->cn_namelen, &de); 1289211598Sed if (error != 0) 1290211598Sed return (error); 1291211598Sed tmpfs_dir_attach(dvp, de); 1292211598Sed return (0); 1293211598Sed} 1294211598Sed 1295211598Sedvoid 1296211598Sedtmpfs_dir_whiteout_remove(struct vnode *dvp, struct componentname *cnp) 1297211598Sed{ 1298211598Sed struct tmpfs_dirent *de; 1299211598Sed 1300211598Sed de = tmpfs_dir_lookup(VP_TO_TMPFS_DIR(dvp), NULL, cnp); 1301211598Sed MPASS(de != NULL && de->td_node == NULL); 1302211598Sed tmpfs_dir_detach(dvp, de); 1303245115Sgleb tmpfs_free_dirent(VFS_TO_TMPFS(dvp->v_mount), de); 1304211598Sed} 1305211598Sed 1306170808Sdelphij/* --------------------------------------------------------------------- */ 1307170808Sdelphij 1308170808Sdelphij/* 1309218681Salc * Resizes the aobj associated with the regular file pointed to by 'vp' to the 1310218681Salc * size 'newsize'. 'vp' must point to a vnode that represents a regular file. 1311218681Salc * 'newsize' must be positive. 1312170808Sdelphij * 1313170808Sdelphij * Returns zero on success or an appropriate error code on failure. 1314170808Sdelphij */ 1315170808Sdelphijint 1316230180Salctmpfs_reg_resize(struct vnode *vp, off_t newsize, boolean_t ignerr) 1317170808Sdelphij{ 1318170808Sdelphij struct tmpfs_mount *tmp; 1319170808Sdelphij struct tmpfs_node *node; 1320218640Salc vm_object_t uobj; 1321229821Salc vm_page_t m, ma[1]; 1322229821Salc vm_pindex_t idx, newpages, oldpages; 1323170808Sdelphij off_t oldsize; 1324229821Salc int base, rv; 1325170808Sdelphij 1326170808Sdelphij MPASS(vp->v_type == VREG); 1327170808Sdelphij MPASS(newsize >= 0); 1328170808Sdelphij 1329170808Sdelphij node = VP_TO_TMPFS_NODE(vp); 1330218640Salc uobj = node->tn_reg.tn_aobj; 1331170808Sdelphij tmp = VFS_TO_TMPFS(vp->v_mount); 1332170808Sdelphij 1333218681Salc /* 1334218681Salc * Convert the old and new sizes to the number of pages needed to 1335170808Sdelphij * store them. It may happen that we do not need to do anything 1336170808Sdelphij * because the last allocated page can accommodate the change on 1337218681Salc * its own. 1338218681Salc */ 1339170808Sdelphij oldsize = node->tn_size; 1340218640Salc oldpages = OFF_TO_IDX(oldsize + PAGE_MASK); 1341218640Salc MPASS(oldpages == uobj->size); 1342218640Salc newpages = OFF_TO_IDX(newsize + PAGE_MASK); 1343170808Sdelphij if (newpages > oldpages && 1344233998Sgleb tmpfs_pages_check_avail(tmp, newpages - oldpages) == 0) 1345218681Salc return (ENOSPC); 1346170808Sdelphij 1347248084Sattilio VM_OBJECT_WLOCK(uobj); 1348170808Sdelphij if (newsize < oldsize) { 1349170808Sdelphij /* 1350229821Salc * Zero the truncated part of the last page. 1351229821Salc */ 1352229821Salc base = newsize & PAGE_MASK; 1353229821Salc if (base != 0) { 1354229821Salc idx = OFF_TO_IDX(newsize); 1355229821Salcretry: 1356229821Salc m = vm_page_lookup(uobj, idx); 1357229821Salc if (m != NULL) { 1358254138Sattilio if (vm_page_sleep_if_busy(m, "tmfssz")) 1359229821Salc goto retry; 1360230120Salc MPASS(m->valid == VM_PAGE_BITS_ALL); 1361229821Salc } else if (vm_pager_has_page(uobj, idx, NULL, NULL)) { 1362229821Salc m = vm_page_alloc(uobj, idx, VM_ALLOC_NORMAL); 1363229821Salc if (m == NULL) { 1364248084Sattilio VM_OBJECT_WUNLOCK(uobj); 1365229821Salc VM_WAIT; 1366248084Sattilio VM_OBJECT_WLOCK(uobj); 1367229821Salc goto retry; 1368229821Salc } else if (m->valid != VM_PAGE_BITS_ALL) { 1369229821Salc ma[0] = m; 1370229821Salc rv = vm_pager_get_pages(uobj, ma, 1, 0); 1371229821Salc m = vm_page_lookup(uobj, idx); 1372229821Salc } else 1373229821Salc /* A cached page was reactivated. */ 1374229821Salc rv = VM_PAGER_OK; 1375229821Salc vm_page_lock(m); 1376229821Salc if (rv == VM_PAGER_OK) { 1377229821Salc vm_page_deactivate(m); 1378229821Salc vm_page_unlock(m); 1379254138Sattilio vm_page_xunbusy(m); 1380229821Salc } else { 1381229821Salc vm_page_free(m); 1382229821Salc vm_page_unlock(m); 1383230180Salc if (ignerr) 1384230180Salc m = NULL; 1385230180Salc else { 1386248084Sattilio VM_OBJECT_WUNLOCK(uobj); 1387230180Salc return (EIO); 1388230180Salc } 1389229821Salc } 1390229821Salc } 1391229821Salc if (m != NULL) { 1392229821Salc pmap_zero_page_area(m, base, PAGE_SIZE - base); 1393229821Salc vm_page_dirty(m); 1394229821Salc vm_pager_page_unswapped(m); 1395229821Salc } 1396229821Salc } 1397229821Salc 1398229821Salc /* 1399218681Salc * Release any swap space and free any whole pages. 1400170808Sdelphij */ 1401170808Sdelphij if (newpages < oldpages) { 1402218640Salc swap_pager_freespace(uobj, newpages, oldpages - 1403218640Salc newpages); 1404223677Salc vm_object_page_remove(uobj, newpages, 0, 0); 1405170808Sdelphij } 1406170808Sdelphij } 1407218640Salc uobj->size = newpages; 1408248084Sattilio VM_OBJECT_WUNLOCK(uobj); 1409229821Salc 1410229821Salc TMPFS_LOCK(tmp); 1411229821Salc tmp->tm_pages_used += (newpages - oldpages); 1412229821Salc TMPFS_UNLOCK(tmp); 1413229821Salc 1414229821Salc node->tn_size = newsize; 1415218681Salc return (0); 1416170808Sdelphij} 1417170808Sdelphij 1418170808Sdelphij/* --------------------------------------------------------------------- */ 1419170808Sdelphij 1420170808Sdelphij/* 1421170808Sdelphij * Change flags of the given vnode. 1422170808Sdelphij * Caller should execute tmpfs_update on vp after a successful execution. 1423170808Sdelphij * The vnode must be locked on entry and remain locked on exit. 1424170808Sdelphij */ 1425170808Sdelphijint 1426248597Spjdtmpfs_chflags(struct vnode *vp, u_long flags, struct ucred *cred, 1427248597Spjd struct thread *p) 1428170808Sdelphij{ 1429170808Sdelphij int error; 1430170808Sdelphij struct tmpfs_node *node; 1431170808Sdelphij 1432176559Sattilio MPASS(VOP_ISLOCKED(vp)); 1433170808Sdelphij 1434170808Sdelphij node = VP_TO_TMPFS_NODE(vp); 1435170808Sdelphij 1436255008Sken if ((flags & ~(SF_APPEND | SF_ARCHIVED | SF_IMMUTABLE | SF_NOUNLINK | 1437255008Sken UF_APPEND | UF_ARCHIVE | UF_HIDDEN | UF_IMMUTABLE | UF_NODUMP | 1438255008Sken UF_NOUNLINK | UF_OFFLINE | UF_OPAQUE | UF_READONLY | UF_REPARSE | 1439255008Sken UF_SPARSE | UF_SYSTEM)) != 0) 1440234347Sjh return (EOPNOTSUPP); 1441234347Sjh 1442170808Sdelphij /* Disallow this operation if the file system is mounted read-only. */ 1443170808Sdelphij if (vp->v_mount->mnt_flag & MNT_RDONLY) 1444170808Sdelphij return EROFS; 1445170808Sdelphij 1446170808Sdelphij /* 1447170808Sdelphij * Callers may only modify the file flags on objects they 1448170808Sdelphij * have VADMIN rights for. 1449170808Sdelphij */ 1450170808Sdelphij if ((error = VOP_ACCESS(vp, VADMIN, cred, p))) 1451170808Sdelphij return (error); 1452170808Sdelphij /* 1453170808Sdelphij * Unprivileged processes are not permitted to unset system 1454170808Sdelphij * flags, or modify flags if any system flags are set. 1455170808Sdelphij */ 1456170808Sdelphij if (!priv_check_cred(cred, PRIV_VFS_SYSFLAGS, 0)) { 1457234347Sjh if (node->tn_flags & 1458234347Sjh (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND)) { 1459170808Sdelphij error = securelevel_gt(cred, 0); 1460170808Sdelphij if (error) 1461170808Sdelphij return (error); 1462170808Sdelphij } 1463170808Sdelphij } else { 1464234347Sjh if (node->tn_flags & 1465234347Sjh (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND) || 1466234347Sjh ((flags ^ node->tn_flags) & SF_SETTABLE)) 1467170808Sdelphij return (EPERM); 1468170808Sdelphij } 1469234347Sjh node->tn_flags = flags; 1470170808Sdelphij node->tn_status |= TMPFS_NODE_CHANGED; 1471170808Sdelphij 1472176559Sattilio MPASS(VOP_ISLOCKED(vp)); 1473170808Sdelphij 1474170808Sdelphij return 0; 1475170808Sdelphij} 1476170808Sdelphij 1477170808Sdelphij/* --------------------------------------------------------------------- */ 1478170808Sdelphij 1479170808Sdelphij/* 1480170808Sdelphij * Change access mode on the given vnode. 1481170808Sdelphij * Caller should execute tmpfs_update on vp after a successful execution. 1482170808Sdelphij * The vnode must be locked on entry and remain locked on exit. 1483170808Sdelphij */ 1484170808Sdelphijint 1485170808Sdelphijtmpfs_chmod(struct vnode *vp, mode_t mode, struct ucred *cred, struct thread *p) 1486170808Sdelphij{ 1487170808Sdelphij int error; 1488170808Sdelphij struct tmpfs_node *node; 1489170808Sdelphij 1490176559Sattilio MPASS(VOP_ISLOCKED(vp)); 1491170808Sdelphij 1492170808Sdelphij node = VP_TO_TMPFS_NODE(vp); 1493170808Sdelphij 1494170808Sdelphij /* Disallow this operation if the file system is mounted read-only. */ 1495170808Sdelphij if (vp->v_mount->mnt_flag & MNT_RDONLY) 1496170808Sdelphij return EROFS; 1497170808Sdelphij 1498170808Sdelphij /* Immutable or append-only files cannot be modified, either. */ 1499170808Sdelphij if (node->tn_flags & (IMMUTABLE | APPEND)) 1500170808Sdelphij return EPERM; 1501170808Sdelphij 1502170808Sdelphij /* 1503170808Sdelphij * To modify the permissions on a file, must possess VADMIN 1504170808Sdelphij * for that file. 1505170808Sdelphij */ 1506170808Sdelphij if ((error = VOP_ACCESS(vp, VADMIN, cred, p))) 1507170808Sdelphij return (error); 1508170808Sdelphij 1509170808Sdelphij /* 1510170808Sdelphij * Privileged processes may set the sticky bit on non-directories, 1511170808Sdelphij * as well as set the setgid bit on a file with a group that the 1512170808Sdelphij * process is not a member of. 1513170808Sdelphij */ 1514170808Sdelphij if (vp->v_type != VDIR && (mode & S_ISTXT)) { 1515170808Sdelphij if (priv_check_cred(cred, PRIV_VFS_STICKYFILE, 0)) 1516170808Sdelphij return (EFTYPE); 1517170808Sdelphij } 1518170808Sdelphij if (!groupmember(node->tn_gid, cred) && (mode & S_ISGID)) { 1519170808Sdelphij error = priv_check_cred(cred, PRIV_VFS_SETGID, 0); 1520170808Sdelphij if (error) 1521170808Sdelphij return (error); 1522170808Sdelphij } 1523170808Sdelphij 1524170808Sdelphij 1525170808Sdelphij node->tn_mode &= ~ALLPERMS; 1526170808Sdelphij node->tn_mode |= mode & ALLPERMS; 1527170808Sdelphij 1528170808Sdelphij node->tn_status |= TMPFS_NODE_CHANGED; 1529170808Sdelphij 1530176559Sattilio MPASS(VOP_ISLOCKED(vp)); 1531170808Sdelphij 1532170808Sdelphij return 0; 1533170808Sdelphij} 1534170808Sdelphij 1535170808Sdelphij/* --------------------------------------------------------------------- */ 1536170808Sdelphij 1537170808Sdelphij/* 1538170808Sdelphij * Change ownership of the given vnode. At least one of uid or gid must 1539170808Sdelphij * be different than VNOVAL. If one is set to that value, the attribute 1540170808Sdelphij * is unchanged. 1541170808Sdelphij * Caller should execute tmpfs_update on vp after a successful execution. 1542170808Sdelphij * The vnode must be locked on entry and remain locked on exit. 1543170808Sdelphij */ 1544170808Sdelphijint 1545170808Sdelphijtmpfs_chown(struct vnode *vp, uid_t uid, gid_t gid, struct ucred *cred, 1546170808Sdelphij struct thread *p) 1547170808Sdelphij{ 1548170808Sdelphij int error; 1549170808Sdelphij struct tmpfs_node *node; 1550170808Sdelphij uid_t ouid; 1551170808Sdelphij gid_t ogid; 1552170808Sdelphij 1553176559Sattilio MPASS(VOP_ISLOCKED(vp)); 1554170808Sdelphij 1555170808Sdelphij node = VP_TO_TMPFS_NODE(vp); 1556170808Sdelphij 1557170808Sdelphij /* Assign default values if they are unknown. */ 1558170808Sdelphij MPASS(uid != VNOVAL || gid != VNOVAL); 1559170808Sdelphij if (uid == VNOVAL) 1560170808Sdelphij uid = node->tn_uid; 1561170808Sdelphij if (gid == VNOVAL) 1562170808Sdelphij gid = node->tn_gid; 1563170808Sdelphij MPASS(uid != VNOVAL && gid != VNOVAL); 1564170808Sdelphij 1565170808Sdelphij /* Disallow this operation if the file system is mounted read-only. */ 1566170808Sdelphij if (vp->v_mount->mnt_flag & MNT_RDONLY) 1567170808Sdelphij return EROFS; 1568170808Sdelphij 1569170808Sdelphij /* Immutable or append-only files cannot be modified, either. */ 1570170808Sdelphij if (node->tn_flags & (IMMUTABLE | APPEND)) 1571170808Sdelphij return EPERM; 1572170808Sdelphij 1573170808Sdelphij /* 1574170808Sdelphij * To modify the ownership of a file, must possess VADMIN for that 1575170808Sdelphij * file. 1576170808Sdelphij */ 1577170808Sdelphij if ((error = VOP_ACCESS(vp, VADMIN, cred, p))) 1578170808Sdelphij return (error); 1579170808Sdelphij 1580170808Sdelphij /* 1581170808Sdelphij * To change the owner of a file, or change the group of a file to a 1582170808Sdelphij * group of which we are not a member, the caller must have 1583170808Sdelphij * privilege. 1584170808Sdelphij */ 1585171070Sdelphij if ((uid != node->tn_uid || 1586170808Sdelphij (gid != node->tn_gid && !groupmember(gid, cred))) && 1587170808Sdelphij (error = priv_check_cred(cred, PRIV_VFS_CHOWN, 0))) 1588170808Sdelphij return (error); 1589170808Sdelphij 1590170808Sdelphij ogid = node->tn_gid; 1591170808Sdelphij ouid = node->tn_uid; 1592170808Sdelphij 1593170808Sdelphij node->tn_uid = uid; 1594170808Sdelphij node->tn_gid = gid; 1595170808Sdelphij 1596170808Sdelphij node->tn_status |= TMPFS_NODE_CHANGED; 1597170808Sdelphij 1598170808Sdelphij if ((node->tn_mode & (S_ISUID | S_ISGID)) && (ouid != uid || ogid != gid)) { 1599170808Sdelphij if (priv_check_cred(cred, PRIV_VFS_RETAINSUGID, 0)) 1600170808Sdelphij node->tn_mode &= ~(S_ISUID | S_ISGID); 1601170808Sdelphij } 1602170808Sdelphij 1603176559Sattilio MPASS(VOP_ISLOCKED(vp)); 1604170808Sdelphij 1605170808Sdelphij return 0; 1606170808Sdelphij} 1607170808Sdelphij 1608170808Sdelphij/* --------------------------------------------------------------------- */ 1609170808Sdelphij 1610170808Sdelphij/* 1611170808Sdelphij * Change size of the given vnode. 1612170808Sdelphij * Caller should execute tmpfs_update on vp after a successful execution. 1613170808Sdelphij * The vnode must be locked on entry and remain locked on exit. 1614170808Sdelphij */ 1615170808Sdelphijint 1616170808Sdelphijtmpfs_chsize(struct vnode *vp, u_quad_t size, struct ucred *cred, 1617170808Sdelphij struct thread *p) 1618170808Sdelphij{ 1619170808Sdelphij int error; 1620170808Sdelphij struct tmpfs_node *node; 1621170808Sdelphij 1622176559Sattilio MPASS(VOP_ISLOCKED(vp)); 1623170808Sdelphij 1624170808Sdelphij node = VP_TO_TMPFS_NODE(vp); 1625170808Sdelphij 1626170808Sdelphij /* Decide whether this is a valid operation based on the file type. */ 1627170808Sdelphij error = 0; 1628170808Sdelphij switch (vp->v_type) { 1629170808Sdelphij case VDIR: 1630170808Sdelphij return EISDIR; 1631170808Sdelphij 1632170808Sdelphij case VREG: 1633170808Sdelphij if (vp->v_mount->mnt_flag & MNT_RDONLY) 1634170808Sdelphij return EROFS; 1635170808Sdelphij break; 1636170808Sdelphij 1637170808Sdelphij case VBLK: 1638170808Sdelphij /* FALLTHROUGH */ 1639170808Sdelphij case VCHR: 1640170808Sdelphij /* FALLTHROUGH */ 1641170808Sdelphij case VFIFO: 1642170808Sdelphij /* Allow modifications of special files even if in the file 1643170808Sdelphij * system is mounted read-only (we are not modifying the 1644170808Sdelphij * files themselves, but the objects they represent). */ 1645170808Sdelphij return 0; 1646170808Sdelphij 1647170808Sdelphij default: 1648170808Sdelphij /* Anything else is unsupported. */ 1649170808Sdelphij return EOPNOTSUPP; 1650170808Sdelphij } 1651170808Sdelphij 1652170808Sdelphij /* Immutable or append-only files cannot be modified, either. */ 1653170808Sdelphij if (node->tn_flags & (IMMUTABLE | APPEND)) 1654170808Sdelphij return EPERM; 1655170808Sdelphij 1656170808Sdelphij error = tmpfs_truncate(vp, size); 1657170808Sdelphij /* tmpfs_truncate will raise the NOTE_EXTEND and NOTE_ATTRIB kevents 1658170808Sdelphij * for us, as will update tn_status; no need to do that here. */ 1659170808Sdelphij 1660176559Sattilio MPASS(VOP_ISLOCKED(vp)); 1661170808Sdelphij 1662170808Sdelphij return error; 1663170808Sdelphij} 1664170808Sdelphij 1665170808Sdelphij/* --------------------------------------------------------------------- */ 1666170808Sdelphij 1667170808Sdelphij/* 1668170808Sdelphij * Change access and modification times of the given vnode. 1669170808Sdelphij * Caller should execute tmpfs_update on vp after a successful execution. 1670170808Sdelphij * The vnode must be locked on entry and remain locked on exit. 1671170808Sdelphij */ 1672170808Sdelphijint 1673170808Sdelphijtmpfs_chtimes(struct vnode *vp, struct timespec *atime, struct timespec *mtime, 1674170808Sdelphij struct timespec *birthtime, int vaflags, struct ucred *cred, struct thread *l) 1675170808Sdelphij{ 1676170808Sdelphij int error; 1677170808Sdelphij struct tmpfs_node *node; 1678170808Sdelphij 1679176559Sattilio MPASS(VOP_ISLOCKED(vp)); 1680170808Sdelphij 1681170808Sdelphij node = VP_TO_TMPFS_NODE(vp); 1682170808Sdelphij 1683170808Sdelphij /* Disallow this operation if the file system is mounted read-only. */ 1684170808Sdelphij if (vp->v_mount->mnt_flag & MNT_RDONLY) 1685170808Sdelphij return EROFS; 1686170808Sdelphij 1687170808Sdelphij /* Immutable or append-only files cannot be modified, either. */ 1688170808Sdelphij if (node->tn_flags & (IMMUTABLE | APPEND)) 1689170808Sdelphij return EPERM; 1690170808Sdelphij 1691171087Sdelphij /* Determine if the user have proper privilege to update time. */ 1692171087Sdelphij if (vaflags & VA_UTIMES_NULL) { 1693171087Sdelphij error = VOP_ACCESS(vp, VADMIN, cred, l); 1694171087Sdelphij if (error) 1695171087Sdelphij error = VOP_ACCESS(vp, VWRITE, cred, l); 1696171087Sdelphij } else 1697171087Sdelphij error = VOP_ACCESS(vp, VADMIN, cred, l); 1698171087Sdelphij if (error) 1699171087Sdelphij return (error); 1700170808Sdelphij 1701170808Sdelphij if (atime->tv_sec != VNOVAL && atime->tv_nsec != VNOVAL) 1702170808Sdelphij node->tn_status |= TMPFS_NODE_ACCESSED; 1703170808Sdelphij 1704170808Sdelphij if (mtime->tv_sec != VNOVAL && mtime->tv_nsec != VNOVAL) 1705170808Sdelphij node->tn_status |= TMPFS_NODE_MODIFIED; 1706170808Sdelphij 1707170808Sdelphij if (birthtime->tv_nsec != VNOVAL && birthtime->tv_nsec != VNOVAL) 1708170808Sdelphij node->tn_status |= TMPFS_NODE_MODIFIED; 1709171070Sdelphij 1710170808Sdelphij tmpfs_itimes(vp, atime, mtime); 1711171070Sdelphij 1712170808Sdelphij if (birthtime->tv_nsec != VNOVAL && birthtime->tv_nsec != VNOVAL) 1713170808Sdelphij node->tn_birthtime = *birthtime; 1714176559Sattilio MPASS(VOP_ISLOCKED(vp)); 1715170808Sdelphij 1716170808Sdelphij return 0; 1717170808Sdelphij} 1718170808Sdelphij 1719170808Sdelphij/* --------------------------------------------------------------------- */ 1720170808Sdelphij/* Sync timestamps */ 1721170808Sdelphijvoid 1722170808Sdelphijtmpfs_itimes(struct vnode *vp, const struct timespec *acc, 1723170808Sdelphij const struct timespec *mod) 1724170808Sdelphij{ 1725170808Sdelphij struct tmpfs_node *node; 1726170808Sdelphij struct timespec now; 1727170808Sdelphij 1728170808Sdelphij node = VP_TO_TMPFS_NODE(vp); 1729170808Sdelphij 1730170808Sdelphij if ((node->tn_status & (TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED | 1731170808Sdelphij TMPFS_NODE_CHANGED)) == 0) 1732170808Sdelphij return; 1733170808Sdelphij 1734170922Sdelphij vfs_timestamp(&now); 1735170808Sdelphij if (node->tn_status & TMPFS_NODE_ACCESSED) { 1736170808Sdelphij if (acc == NULL) 1737170808Sdelphij acc = &now; 1738170808Sdelphij node->tn_atime = *acc; 1739170808Sdelphij } 1740170808Sdelphij if (node->tn_status & TMPFS_NODE_MODIFIED) { 1741170808Sdelphij if (mod == NULL) 1742170808Sdelphij mod = &now; 1743170808Sdelphij node->tn_mtime = *mod; 1744170808Sdelphij } 1745170808Sdelphij if (node->tn_status & TMPFS_NODE_CHANGED) { 1746170808Sdelphij node->tn_ctime = now; 1747170808Sdelphij } 1748170808Sdelphij node->tn_status &= 1749170808Sdelphij ~(TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED | TMPFS_NODE_CHANGED); 1750170808Sdelphij} 1751170808Sdelphij 1752170808Sdelphij/* --------------------------------------------------------------------- */ 1753170808Sdelphij 1754170808Sdelphijvoid 1755170808Sdelphijtmpfs_update(struct vnode *vp) 1756170808Sdelphij{ 1757170808Sdelphij 1758170808Sdelphij tmpfs_itimes(vp, NULL, NULL); 1759170808Sdelphij} 1760170808Sdelphij 1761170808Sdelphij/* --------------------------------------------------------------------- */ 1762170808Sdelphij 1763170808Sdelphijint 1764170808Sdelphijtmpfs_truncate(struct vnode *vp, off_t length) 1765170808Sdelphij{ 1766170808Sdelphij int error; 1767170808Sdelphij struct tmpfs_node *node; 1768170808Sdelphij 1769170808Sdelphij node = VP_TO_TMPFS_NODE(vp); 1770170808Sdelphij 1771170808Sdelphij if (length < 0) { 1772170808Sdelphij error = EINVAL; 1773170808Sdelphij goto out; 1774170808Sdelphij } 1775170808Sdelphij 1776170808Sdelphij if (node->tn_size == length) { 1777170808Sdelphij error = 0; 1778170808Sdelphij goto out; 1779170808Sdelphij } 1780170808Sdelphij 1781170808Sdelphij if (length > VFS_TO_TMPFS(vp->v_mount)->tm_maxfilesize) 1782170808Sdelphij return (EFBIG); 1783170808Sdelphij 1784230180Salc error = tmpfs_reg_resize(vp, length, FALSE); 1785170808Sdelphij if (error == 0) { 1786170808Sdelphij node->tn_status |= TMPFS_NODE_CHANGED | TMPFS_NODE_MODIFIED; 1787170808Sdelphij } 1788170808Sdelphij 1789170808Sdelphijout: 1790170808Sdelphij tmpfs_update(vp); 1791170808Sdelphij 1792170808Sdelphij return error; 1793170808Sdelphij} 1794245115Sgleb 1795245115Sglebstatic __inline int 1796245115Sglebtmpfs_dirtree_cmp(struct tmpfs_dirent *a, struct tmpfs_dirent *b) 1797245115Sgleb{ 1798245115Sgleb if (a->td_hash > b->td_hash) 1799245115Sgleb return (1); 1800245115Sgleb else if (a->td_hash < b->td_hash) 1801245115Sgleb return (-1); 1802245115Sgleb return (0); 1803245115Sgleb} 1804245115Sgleb 1805245115SglebRB_GENERATE_STATIC(tmpfs_dir, tmpfs_dirent, uh.td_entries, tmpfs_dirtree_cmp); 1806