diff options
author | delphij <delphij@FreeBSD.org> | 2007-06-16 01:56:05 +0000 |
---|---|---|
committer | delphij <delphij@FreeBSD.org> | 2007-06-16 01:56:05 +0000 |
commit | ef518e41228820c3b563c3410ba24e2306159558 (patch) | |
tree | 6c2d241633f67fcfc925a167771603e0699018e4 | |
parent | 798d1edddef3b9f1fe720c2cf922868cba0a702b (diff) | |
download | FreeBSD-src-ef518e41228820c3b563c3410ba24e2306159558.zip FreeBSD-src-ef518e41228820c3b563c3410ba24e2306159558.tar.gz |
MFp4: Add tmpfs, an efficient memory file system.
Please note that, this is currently considered as an
experimental feature so there could be some rough
edges. Consult http://wiki.freebsd.org/TMPFS for
more information.
For now, connect tmpfs to build on i386 and amd64
architectures only. Please let us know if you have
success with other platforms.
This work was developed by Julio M. Merino Vidal
for NetBSD as a SoC project; Rohit Jalan ported it
from NetBSD to FreeBSD. Howard Su and Glen Leeder
are worked on it to continue this effort.
Obtained from: NetBSD via p4
Submitted by: Howard Su (with some minor changes)
Approved by: re (kensmith)
-rw-r--r-- | sys/conf/files | 5 | ||||
-rw-r--r-- | sys/conf/options | 1 | ||||
-rw-r--r-- | sys/fs/tmpfs/tmpfs.h | 542 | ||||
-rw-r--r-- | sys/fs/tmpfs/tmpfs_fifoops.c | 106 | ||||
-rw-r--r-- | sys/fs/tmpfs/tmpfs_fifoops.h | 63 | ||||
-rw-r--r-- | sys/fs/tmpfs/tmpfs_subr.c | 1321 | ||||
-rw-r--r-- | sys/fs/tmpfs/tmpfs_uma.c | 73 | ||||
-rw-r--r-- | sys/fs/tmpfs/tmpfs_uma.h | 97 | ||||
-rw-r--r-- | sys/fs/tmpfs/tmpfs_vfsops.c | 418 | ||||
-rw-r--r-- | sys/fs/tmpfs/tmpfs_vnops.c | 1365 | ||||
-rw-r--r-- | sys/fs/tmpfs/tmpfs_vnops.h | 85 | ||||
-rw-r--r-- | sys/modules/Makefile | 3 | ||||
-rw-r--r-- | sys/modules/tmpfs/Makefile | 10 |
13 files changed, 4089 insertions, 0 deletions
diff --git a/sys/conf/files b/sys/conf/files index d92b22a..a5fafa5 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1205,6 +1205,11 @@ fs/umapfs/umap_vnops.c optional umapfs fs/unionfs/union_subr.c optional unionfs fs/unionfs/union_vfsops.c optional unionfs fs/unionfs/union_vnops.c optional unionfs +fs/tmpfs/tmpfs_vnops.c optional tmpfs +fs/tmpfs/tmpfs_fifoops.c optional tmpfs +fs/tmpfs/tmpfs_vfsops.c optional tmpfs +fs/tmpfs/tmpfs_subr.c optional tmpfs +fs/tmpfs/tmpfs_uma.c optional tmpfs gdb/gdb_cons.c optional gdb gdb/gdb_main.c optional gdb gdb/gdb_packet.c optional gdb diff --git a/sys/conf/options b/sys/conf/options index 21e658b..d225037 100644 --- a/sys/conf/options +++ b/sys/conf/options @@ -196,6 +196,7 @@ PROCFS opt_dontuse.h PSEUDOFS opt_dontuse.h REISERFS opt_dontuse.h SMBFS opt_dontuse.h +TMPFS opt_dontuse.h UDF opt_dontuse.h UMAPFS opt_dontuse.h UNIONFS opt_dontuse.h diff --git a/sys/fs/tmpfs/tmpfs.h b/sys/fs/tmpfs/tmpfs.h new file mode 100644 index 0000000..2947b9b --- /dev/null +++ b/sys/fs/tmpfs/tmpfs.h @@ -0,0 +1,542 @@ +/* $NetBSD: tmpfs.h,v 1.14 2006/02/10 16:00:02 christos Exp $ */ + +/* + * Copyright (c) 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _FS_TMPFS_TMPFS_H_ +#define _FS_TMPFS_TMPFS_H_ + +/* --------------------------------------------------------------------- + * KERNEL-SPECIFIC DEFINITIONS + * --------------------------------------------------------------------- */ +#include <sys/dirent.h> +#include <sys/mount.h> +#include <sys/queue.h> +#include <sys/vnode.h> +#include <sys/file.h> +#include <sys/lock.h> +#include <sys/mutex.h> + +/* --------------------------------------------------------------------- */ +#include <sys/malloc.h> +#include <sys/systm.h> +#include <sys/vmmeter.h> +#include <vm/swap_pager.h> + +MALLOC_DECLARE(M_TMPFSMNT); + +#include <fs/tmpfs/tmpfs_uma.h> + +/* --------------------------------------------------------------------- */ + +/* + * Internal representation of a tmpfs directory entry. + */ +struct tmpfs_dirent { + TAILQ_ENTRY(tmpfs_dirent) td_entries; + + /* Length of the name stored in this directory entry. This avoids + * the need to recalculate it every time the name is used. */ + uint16_t td_namelen; + + /* The name of the entry, allocated from a string pool. This + * string is not required to be zero-terminated; therefore, the + * td_namelen field must always be used when accessing its value. */ + char * td_name; + + /* Pointer to the node this entry refers to. */ + struct tmpfs_node * td_node; +}; + +/* A directory in tmpfs holds a sorted list of directory entries, which in + * turn point to other files (which can be directories themselves). + * + * In tmpfs, this list is managed by a tail queue, whose head is defined by + * the struct tmpfs_dir type. + * + * It is imporant to notice that directories do not have entries for . and + * .. as other file systems do. These can be generated when requested + * based on information available by other means, such as the pointer to + * the node itself in the former case or the pointer to the parent directory + * in the latter case. This is done to simplify tmpfs's code and, more + * importantly, to remove redundancy. */ +TAILQ_HEAD(tmpfs_dir, tmpfs_dirent); + +#define TMPFS_DIRCOOKIE(dirent) ((off_t)(uintptr_t)(dirent)) +#define TMPFS_DIRCOOKIE_DOT 0 +#define TMPFS_DIRCOOKIE_DOTDOT 1 +#define TMPFS_DIRCOOKIE_EOF 2 + +/* --------------------------------------------------------------------- */ + +/* + * Internal representation of a tmpfs file system node. + * + * This structure is splitted in two parts: one holds attributes common + * to all file types and the other holds data that is only applicable to + * a particular type. The code must be careful to only access those + * attributes that are actually allowed by the node's type. + * + * + * Below is the key of locks used to protected the fields in the following + * structures. + * + */ +struct tmpfs_node { + /* Doubly-linked list entry which links all existing nodes for a + * single file system. This is provided to ease the removal of + * all nodes during the unmount operation. */ + LIST_ENTRY(tmpfs_node) tn_entries; + + /* The node's type. Any of 'VBLK', 'VCHR', 'VDIR', 'VFIFO', + * 'VLNK', 'VREG' and 'VSOCK' is allowed. The usage of vnode + * types instead of a custom enumeration is to make things simpler + * and faster, as we do not need to convert between two types. */ + enum vtype tn_type; + + /* Node identifier. */ + ino_t tn_id; + + /* Node's internal status. This is used by several file system + * operations to do modifications to the node in a delayed + * fashion. */ + int tn_status; +#define TMPFS_NODE_ACCESSED (1 << 1) +#define TMPFS_NODE_MODIFIED (1 << 2) +#define TMPFS_NODE_CHANGED (1 << 3) + + /* The node size. It does not necessarily match the real amount + * of memory consumed by it. */ + off_t tn_size; + + /* Generic node attributes. */ + uid_t tn_uid; + gid_t tn_gid; + mode_t tn_mode; + int tn_flags; + nlink_t tn_links; + struct timespec tn_atime; + struct timespec tn_mtime; + struct timespec tn_ctime; + struct timespec tn_birthtime; + unsigned long tn_gen; + + /* Head of byte-level lock list (used by tmpfs_advlock). */ + struct lockf * tn_lockf; + + /* As there is a single vnode for each active file within the + * system, care has to be taken to avoid allocating more than one + * vnode per file. In order to do this, a bidirectional association + * is kept between vnodes and nodes. + * + * Whenever a vnode is allocated, its v_data field is updated to + * point to the node it references. At the same time, the node's + * tn_vnode field is modified to point to the new vnode representing + * it. Further attempts to allocate a vnode for this same node will + * result in returning a new reference to the value stored in + * tn_vnode. + * + * May be NULL when the node is unused (that is, no vnode has been + * allocated for it or it has been reclaimed). */ + struct vnode * tn_vnode; + + /* Pointer to the node returned by tmpfs_lookup() after doing a + * delete or a rename lookup; its value is only valid in these two + * situations. In case we were looking up . or .., it holds a null + * pointer. */ + struct tmpfs_dirent * tn_lookup_dirent; + + /* interlock to protect tn_vpstate */ + struct mtx tn_interlock; + + /* Identify if current node has vnode assiocate with + * or allocating vnode. + */ + int tn_vpstate; + + /* misc data field for different tn_type node */ + union { + /* Valid when tn_type == VBLK || tn_type == VCHR. */ + dev_t tn_rdev; + + /* Valid when tn_type == VDIR. */ + struct tn_dir{ + /* Pointer to the parent directory. The root + * directory has a pointer to itself in this field; + * this property identifies the root node. */ + struct tmpfs_node * tn_parent; + + /* Head of a tail-queue that links the contents of + * the directory together. See above for a + * description of its contents. */ + struct tmpfs_dir tn_dirhead; + + /* Number and pointer of the first directory entry + * returned by the readdir operation if it were + * called again to continue reading data from the + * same directory as before. This is used to speed + * up reads of long directories, assuming that no + * more than one read is in progress at a given time. + * Otherwise, these values are discarded and a linear + * scan is performed from the beginning up to the + * point where readdir starts returning values. */ + off_t tn_readdir_lastn; + struct tmpfs_dirent * tn_readdir_lastp; + }tn_dir; + + /* Valid when tn_type == VLNK. */ + /* The link's target, allocated from a string pool. */ + char * tn_link; + + /* Valid when tn_type == VREG. */ + struct tn_reg { + /* The contents of regular files stored in a tmpfs + * file system are represented by a single anonymous + * memory object (aobj, for short). The aobj provides + * direct access to any position within the file, + * because its contents are always mapped in a + * contiguous region of virtual memory. It is a task + * of the memory management subsystem (see uvm(9)) to + * issue the required page ins or page outs whenever + * a position within the file is accessed. */ + vm_object_t tn_aobj; + size_t tn_aobj_pages; + + }tn_reg; + + /* Valid when tn_type = VFIFO */ + struct tn_fifo { + fo_rdwr_t *tn_fo_read; + fo_rdwr_t *tn_fo_write; + }tn_fifo; + }tn_spec; +}; +LIST_HEAD(tmpfs_node_list, tmpfs_node); + +#define tn_rdev tn_spec.tn_rdev +#define tn_dir tn_spec.tn_dir +#define tn_link tn_spec.tn_link +#define tn_reg tn_spec.tn_reg +#define tn_fifo tn_spec.tn_fifo + +#define TMPFS_NODE_LOCK(node) mtx_lock(&(node)->tn_interlock) +#define TMPFS_NODE_UNLOCK(node) mtx_unlock(&(node)->tn_interlock) + +#define TMPFS_VNODE_ALLOCATING 1 +#define TMPFS_VNODE_WANT 2 +/* --------------------------------------------------------------------- */ + +/* + * Internal representation of a tmpfs mount point. + */ +struct tmpfs_mount { + /* Maximum number of memory pages available for use by the file + * system, set during mount time. This variable must never be + * used directly as it may be bigger that the current amount of + * free memory; in the extreme case, it will hold the SIZE_MAX + * value. Instead, use the TMPFS_PAGES_MAX macro. */ + size_t tm_pages_max; + + /* Number of pages in use by the file system. Cannot be bigger + * than the value returned by TMPFS_PAGES_MAX in any case. */ + size_t tm_pages_used; + + /* Pointer to the node representing the root directory of this + * file system. */ + struct tmpfs_node * tm_root; + + /* Maximum number of possible nodes for this file system; set + * during mount time. We need a hard limit on the maximum number + * of nodes to avoid allocating too much of them; their objects + * cannot be released until the file system is unmounted. + * Otherwise, we could easily run out of memory by creating lots + * of empty files and then simply removing them. */ + ino_t tm_nodes_max; + + /* Number of nodes currently allocated. This number only grows. + * When it reaches tm_nodes_max, no more new nodes can be allocated. + * Of course, the old, unused ones can be reused. */ + ino_t tm_nodes_last; + + /* Number of nodes currently that are in use. */ + ino_t tm_nodes_inuse; + + /* maximum representable file size */ + u_int64_t tm_maxfilesize; + + /* Nodes are organized in two different lists. The used list + * contains all nodes that are currently used by the file system; + * i.e., they refer to existing files. The available list contains + * all nodes that are currently available for use by new files. + * Nodes must be kept in this list (instead of deleting them) + * because we need to keep track of their generation number (tn_gen + * field). + * + * Note that nodes are lazily allocated: if the available list is + * empty and we have enough space to create more nodes, they will be + * created and inserted in the used list. Once these are released, + * they will go into the available list, remaining alive until the + * file system is unmounted. */ + struct tmpfs_node_list tm_nodes_used; + struct tmpfs_node_list tm_nodes_avail; + + /* All node lock to protect the node list and tmp_pages_used */ + struct mtx allnode_lock; + + /* Pools used to store file system meta data. These are not shared + * across several instances of tmpfs for the reasons described in + * tmpfs_pool.c. */ + uma_zone_t tm_dirent_pool; + uma_zone_t tm_node_pool; + struct tmpfs_str_zone tm_str_pool; +}; +#define TMPFS_LOCK(tm) mtx_lock(&(tm)->allnode_lock) +#define TMPFS_UNLOCK(tm) mtx_unlock(&(tm)->allnode_lock) + +/* --------------------------------------------------------------------- */ + +/* + * This structure maps a file identifier to a tmpfs node. Used by the + * NFS code. + */ +struct tmpfs_fid { + uint16_t tf_len; + uint16_t tf_pad; + unsigned long tf_gen; + ino_t tf_id; +}; + +/* --------------------------------------------------------------------- */ + +#ifdef _KERNEL +/* + * Prototypes for tmpfs_subr.c. + */ + +int tmpfs_alloc_node(struct tmpfs_mount *, enum vtype, + uid_t uid, gid_t gid, mode_t mode, struct tmpfs_node *, + char *, dev_t, struct thread *, struct tmpfs_node **); +void tmpfs_free_node(struct tmpfs_mount *, struct tmpfs_node *); +int tmpfs_alloc_dirent(struct tmpfs_mount *, struct tmpfs_node *, + const char *, uint16_t, struct tmpfs_dirent **); +void tmpfs_free_dirent(struct tmpfs_mount *, struct tmpfs_dirent *, + boolean_t); +int tmpfs_alloc_vp(struct mount *, struct tmpfs_node *, struct vnode **, + struct thread *td); +void tmpfs_free_vp(struct vnode *); +int tmpfs_alloc_file(struct vnode *, struct vnode **, struct vattr *, + struct componentname *, char *); +void tmpfs_dir_attach(struct vnode *, struct tmpfs_dirent *); +void tmpfs_dir_detach(struct vnode *, struct tmpfs_dirent *); +struct tmpfs_dirent * tmpfs_dir_lookup(struct tmpfs_node *node, + struct componentname *cnp); +int tmpfs_dir_getdotdent(struct tmpfs_node *, struct uio *); +int tmpfs_dir_getdotdotdent(struct tmpfs_node *, struct uio *); +struct tmpfs_dirent * tmpfs_dir_lookupbycookie(struct tmpfs_node *, off_t); +int tmpfs_dir_getdents(struct tmpfs_node *, struct uio *, off_t *); +int tmpfs_reg_resize(struct vnode *, off_t); +int tmpfs_chflags(struct vnode *, int, struct ucred *, struct thread *); +int tmpfs_chmod(struct vnode *, mode_t, struct ucred *, struct thread *); +int tmpfs_chown(struct vnode *, uid_t, gid_t, struct ucred *, + struct thread *); +int tmpfs_chsize(struct vnode *, u_quad_t, struct ucred *, struct thread *); +int tmpfs_chtimes(struct vnode *, struct timespec *, struct timespec *, + struct timespec *, int, struct ucred *, struct thread *); +void tmpfs_itimes(struct vnode *, const struct timespec *, + const struct timespec *); + +void tmpfs_update(struct vnode *); +int tmpfs_truncate(struct vnode *, off_t); + +/* --------------------------------------------------------------------- */ + +/* + * Convenience macros to simplify some logical expressions. + */ +#define IMPLIES(a, b) (!(a) || (b)) +#define IFF(a, b) (IMPLIES(a, b) && IMPLIES(b, a)) + +/* --------------------------------------------------------------------- */ + +/* + * Checks that the directory entry pointed by 'de' matches the name 'name' + * with a length of 'len'. + */ +#define TMPFS_DIRENT_MATCHES(de, name, len) \ + (de->td_namelen == (uint16_t)len && \ + memcmp((de)->td_name, (name), (de)->td_namelen) == 0) + +/* --------------------------------------------------------------------- */ + +/* + * Ensures that the node pointed by 'node' is a directory and that its + * contents are consistent with respect to directories. + */ +#define TMPFS_VALIDATE_DIR(node) \ + MPASS((node)->tn_type == VDIR); \ + MPASS((node)->tn_size % sizeof(struct tmpfs_dirent) == 0); \ + MPASS((node)->tn_dir.tn_readdir_lastp == NULL || \ + TMPFS_DIRCOOKIE((node)->tn_dir.tn_readdir_lastp) == (node)->tn_dir.tn_readdir_lastn); + +/* --------------------------------------------------------------------- */ + +/* + * Memory management stuff. + */ + +/* Amount of memory pages to reserve for the system (e.g., to not use by + * tmpfs). + * XXX: Should this be tunable through sysctl, for instance? */ +#define TMPFS_PAGES_RESERVED (4 * 1024 * 1024 / PAGE_SIZE) + +/* + * Returns information about the number of available memory pages, + * including physical and virtual ones. + * + * If 'total' is TRUE, the value returned is the total amount of memory + * pages configured for the system (either in use or free). + * If it is FALSE, the value returned is the amount of free memory pages. + * + * Remember to remove TMPFS_PAGES_RESERVED from the returned value to avoid + * excessive memory usage. + * + */ +static __inline size_t +tmpfs_mem_info(void) +{ + size_t size; + + size = swap_pager_avail + cnt.v_free_count + cnt.v_inactive_count; + size -= size > cnt.v_wire_count ? cnt.v_wire_count : size; + return size; +} + +/* Returns the maximum size allowed for a tmpfs file system. This macro + * must be used instead of directly retrieving the value from tm_pages_max. + * The reason is that the size of a tmpfs file system is dynamic: it lets + * the user store files as long as there is enough free memory (including + * physical memory and swap space). Therefore, the amount of memory to be + * used is either the limit imposed by the user during mount time or the + * amount of available memory, whichever is lower. To avoid consuming all + * the memory for a given mount point, the system will always reserve a + * minimum of TMPFS_PAGES_RESERVED pages, which is also taken into account + * by this macro (see above). */ +static __inline size_t +TMPFS_PAGES_MAX(struct tmpfs_mount *tmp) +{ + size_t freepages; + + freepages = tmpfs_mem_info(); + freepages -= freepages < TMPFS_PAGES_RESERVED ? + freepages : TMPFS_PAGES_RESERVED; + + return MIN(tmp->tm_pages_max, freepages + tmp->tm_pages_used); +} + +/* Returns the available space for the given file system. */ +#define TMPFS_META_SIZE(tmp) ((tmp)->tm_nodes_inuse * (sizeof(struct tmpfs_node) \ + + sizeof(struct dirent))) +#define TMPFS_PAGES_AVAIL(tmp) (TMPFS_PAGES_MAX(tmp) - (tmp)->tm_pages_used - \ + TMPFS_META_SIZE(tmp) / PAGE_SIZE - 1) + +#endif + +/* --------------------------------------------------------------------- */ + +/* + * Macros/functions to convert from generic data structures to tmpfs + * specific ones. + */ + +static inline +struct tmpfs_mount * +VFS_TO_TMPFS(struct mount *mp) +{ + struct tmpfs_mount *tmp; + + MPASS((mp) != NULL && (mp)->mnt_data != NULL); + tmp = (struct tmpfs_mount *)(mp)->mnt_data; + return tmp; +} + +static inline +struct tmpfs_node * +VP_TO_TMPFS_NODE(struct vnode *vp) +{ + struct tmpfs_node *node; + + MPASS((vp) != NULL && (vp)->v_data != NULL); + node = (struct tmpfs_node *)vp->v_data; + return node; +} + +static inline +struct tmpfs_node * +VP_TO_TMPFS_DIR(struct vnode *vp) +{ + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + TMPFS_VALIDATE_DIR(node); + return node; +} + +/* --------------------------------------------------------------------- + * USER AND KERNEL DEFINITIONS + * --------------------------------------------------------------------- */ + +/* + * This structure is used to communicate mount parameters between userland + * and kernel space. + */ +#define TMPFS_ARGS_VERSION 1 +struct tmpfs_args { + int ta_version; + + /* Size counters. */ + ino_t ta_nodes_max; + off_t ta_size_max; + + /* Root node attributes. */ + uid_t ta_root_uid; + gid_t ta_root_gid; + mode_t ta_root_mode; + +}; +#endif /* _FS_TMPFS_TMPFS_H_ */ diff --git a/sys/fs/tmpfs/tmpfs_fifoops.c b/sys/fs/tmpfs/tmpfs_fifoops.c new file mode 100644 index 0000000..2ce15d2 --- /dev/null +++ b/sys/fs/tmpfs/tmpfs_fifoops.c @@ -0,0 +1,106 @@ +/* $NetBSD: tmpfs_fifoops.c,v 1.5 2005/12/11 12:24:29 christos Exp $ */ + +/* + * Copyright (c) 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * tmpfs vnode interface for named pipes. + */ +#include <sys/cdefs.h> + __FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/filedesc.h> +#include <sys/proc.h> +#include <sys/vnode.h> + +#include <vm/vm.h> +#include <vm/vm_object.h> + +#include <fs/tmpfs/tmpfs.h> +#include <fs/tmpfs/tmpfs_fifoops.h> +#include <fs/tmpfs/tmpfs_vnops.h> + +/* --------------------------------------------------------------------- */ + +/* + * vnode operations vector used for fifos stored in a tmpfs file system. + */ +struct vop_vector tmpfs_fifoop_entries = { + .vop_default = &fifo_specops, + .vop_close = tmpfs_fifo_close, + .vop_reclaim = tmpfs_reclaim, + .vop_access = tmpfs_access, + .vop_getattr = tmpfs_getattr, + .vop_setattr = tmpfs_setattr, + .vop_kqfilter = tmpfs_fifo_kqfilter, +}; + + +int +tmpfs_fifo_kqfilter(struct vop_kqfilter_args *ap) +{ + struct vnode *vp; + struct tmpfs_node *node; + + vp = ap->a_vp; + node = VP_TO_TMPFS_NODE(vp); + + switch (ap->a_kn->kn_filter){ + case EVFILT_READ: + node->tn_status |= TMPFS_NODE_ACCESSED; + break; + case EVFILT_WRITE: + node->tn_status |= TMPFS_NODE_MODIFIED; + break; + } + + return fifo_specops.vop_kqfilter(ap); +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_fifo_close(struct vop_close_args *v) +{ + struct tmpfs_node *node; + node = VP_TO_TMPFS_NODE(v->a_vp); + node->tn_status |= TMPFS_NODE_ACCESSED; + + tmpfs_update(v->a_vp); + return fifo_specops.vop_close(v); +} diff --git a/sys/fs/tmpfs/tmpfs_fifoops.h b/sys/fs/tmpfs/tmpfs_fifoops.h new file mode 100644 index 0000000..134018f --- /dev/null +++ b/sys/fs/tmpfs/tmpfs_fifoops.h @@ -0,0 +1,63 @@ +/* $NetBSD: tmpfs_fifoops.h,v 1.3.2.2 2005/12/11 10:29:11 christos Exp $ */ + +/* + * Copyright (c) 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _FS_TMPFS_TMPFS_FIFOOPS_H_ +#define _FS_TMPFS_TMPFS_FIFOOPS_H_ + +#if !defined(_KERNEL) +#error not supposed to be exposed to userland. +#endif + +#include <fs/tmpfs/tmpfs_vnops.h> + +/* --------------------------------------------------------------------- */ + +/* + * Declarations for tmpfs_fifoops.c. + */ + +extern struct vop_vector tmpfs_fifoop_entries; + +vop_close_t tmpfs_fifo_close; +vop_kqfilter_t tmpfs_fifo_kqfilter; + +/* --------------------------------------------------------------------- */ +#endif /* _FS_TMPFS_TMPFS_FIFOOPS_H_ */ diff --git a/sys/fs/tmpfs/tmpfs_subr.c b/sys/fs/tmpfs/tmpfs_subr.c new file mode 100644 index 0000000..b8e9a32 --- /dev/null +++ b/sys/fs/tmpfs/tmpfs_subr.c @@ -0,0 +1,1321 @@ +/* $NetBSD: tmpfs_subr.c,v 1.17 2005/12/11 12:24:29 christos Exp $ */ + +/* + * Copyright (c) 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Efficient memory file system supporting functions. + */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/namei.h> +#include <sys/priv.h> +#include <sys/proc.h> +#include <sys/stat.h> +#include <sys/systm.h> +#include <sys/vnode.h> +#include <sys/vmmeter.h> + +#include <vm/vm.h> +#include <vm/vm_object.h> +#include <vm/vm_page.h> +#include <vm/vm_pager.h> +#include <vm/vm_extern.h> + +#include <fs/tmpfs/tmpfs.h> +#include <fs/tmpfs/tmpfs_fifoops.h> +#include <fs/tmpfs/tmpfs_vnops.h> + +/* --------------------------------------------------------------------- */ + +/* + * Allocates a new node of type 'type' inside the 'tmp' mount point, with + * its owner set to 'uid', its group to 'gid' and its mode set to 'mode', + * using the credentials of the process 'p'. + * + * If the node type is set to 'VDIR', then the parent parameter must point + * to the parent directory of the node being created. It may only be NULL + * while allocating the root node. + * + * If the node type is set to 'VBLK' or 'VCHR', then the rdev parameter + * specifies the device the node represents. + * + * If the node type is set to 'VLNK', then the parameter target specifies + * the file name of the target file for the symbolic link that is being + * created. + * + * Note that new nodes are retrieved from the available list if it has + * items or, if it is empty, from the node pool as long as there is enough + * space to create them. + * + * Returns zero on success or an appropriate error code on failure. + */ +int +tmpfs_alloc_node(struct tmpfs_mount *tmp, enum vtype type, + uid_t uid, gid_t gid, mode_t mode, struct tmpfs_node *parent, + char *target, dev_t rdev, struct thread *p, struct tmpfs_node **node) +{ + struct tmpfs_node *nnode; + + /* If the root directory of the 'tmp' file system is not yet + * allocated, this must be the request to do it. */ + MPASS(IMPLIES(tmp->tm_root == NULL, parent == NULL && type == VDIR)); + + MPASS(IFF(type == VLNK, target != NULL)); + MPASS(IFF(type == VBLK || type == VCHR, rdev != VNOVAL)); + + nnode = NULL; + + TMPFS_LOCK(tmp); + if (LIST_EMPTY(&tmp->tm_nodes_avail)) { + MPASS(tmp->tm_nodes_last <= tmp->tm_nodes_max); + if (tmp->tm_nodes_last == tmp->tm_nodes_max) { + TMPFS_UNLOCK(tmp); + return ENOSPC; + } + TMPFS_UNLOCK(tmp); + nnode = (struct tmpfs_node *)tmpfs_zone_alloc( + tmp->tm_node_pool, M_WAITOK); + if (nnode == NULL) + return ENOSPC; + nnode->tn_id = tmp->tm_nodes_last++; + nnode->tn_gen = arc4random(); + } else { + nnode = LIST_FIRST(&tmp->tm_nodes_avail); + LIST_REMOVE(nnode, tn_entries); + TMPFS_UNLOCK(tmp); + nnode->tn_gen++; + } + MPASS(nnode != NULL); + + /* Generic initialization. */ + nnode->tn_type = type; + nnode->tn_size = 0; + nnode->tn_status = 0; + nnode->tn_flags = 0; + nnode->tn_links = 0; + nanotime(&nnode->tn_atime); + nnode->tn_birthtime = nnode->tn_ctime = nnode->tn_mtime = + nnode->tn_atime; + nnode->tn_uid = uid; + nnode->tn_gid = gid; + nnode->tn_mode = mode; + nnode->tn_lockf = NULL; + nnode->tn_vnode = NULL; + + nnode->tn_vpstate = 0; + mtx_init(&nnode->tn_interlock, "tmpfs node interlock", NULL, MTX_DEF); + /* Type-specific initialization. */ + switch (nnode->tn_type) { + case VBLK: + case VCHR: + nnode->tn_rdev = rdev; + break; + + case VDIR: + TAILQ_INIT(&nnode->tn_dir.tn_dirhead); + nnode->tn_dir.tn_parent = (parent == NULL) ? nnode : parent; + nnode->tn_dir.tn_readdir_lastn = 0; + nnode->tn_dir.tn_readdir_lastp = NULL; + nnode->tn_links++; + nnode->tn_dir.tn_parent->tn_links++; + break; + + case VFIFO: + /* FALLTHROUGH */ + case VSOCK: + break; + + case VLNK: + MPASS(strlen(target) < MAXPATHLEN); + nnode->tn_size = strlen(target); + nnode->tn_link = tmpfs_str_zone_alloc(&tmp->tm_str_pool, + M_WAITOK, nnode->tn_size); + if (nnode->tn_link == NULL) { + nnode->tn_type = VNON; + tmpfs_free_node(tmp, nnode); + return ENOSPC; + } + memcpy(nnode->tn_link, target, nnode->tn_size); + break; + + case VREG: + nnode->tn_reg.tn_aobj = + vm_pager_allocate(OBJT_SWAP, NULL, 0, VM_PROT_DEFAULT, 0); + nnode->tn_reg.tn_aobj_pages = 0; + break; + + default: + MPASS(0); + } + + TMPFS_LOCK(tmp); + LIST_INSERT_HEAD(&tmp->tm_nodes_used, nnode, tn_entries); + tmp->tm_nodes_inuse++; + TMPFS_UNLOCK(tmp); + + *node = nnode; + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * Destroys the node pointed to by node from the file system 'tmp'. + * If the node does not belong to the given mount point, the results are + * unpredicted. + * + * If the node references a directory; no entries are allowed because + * their removal could need a recursive algorithm, something forbidden in + * kernel space. Furthermore, there is not need to provide such + * functionality (recursive removal) because the only primitives offered + * to the user are the removal of empty directories and the deletion of + * individual files. + * + * Note that nodes are not really deleted; in fact, when a node has been + * allocated, it cannot be deleted during the whole life of the file + * system. Instead, they are moved to the available list and remain there + * until reused. + */ +void +tmpfs_free_node(struct tmpfs_mount *tmp, struct tmpfs_node *node) +{ + ino_t id; + unsigned long gen; + size_t pages; + + TMPFS_LOCK(tmp); + LIST_REMOVE(node, tn_entries); + tmp->tm_nodes_inuse--; + TMPFS_UNLOCK(tmp); + + switch (node->tn_type) { + case VNON: + /* Do not do anything. VNON is provided to let the + * allocation routine clean itself easily by avoiding + * duplicating code in it. */ + /* FALLTHROUGH */ + case VBLK: + /* FALLTHROUGH */ + case VCHR: + /* FALLTHROUGH */ + case VDIR: + /* FALLTHROUGH */ + case VFIFO: + /* FALLTHROUGH */ + case VSOCK: + pages = 0; + break; + + case VLNK: + tmpfs_str_zone_free(&tmp->tm_str_pool, node->tn_link, + node->tn_size); + pages = 0; + break; + + case VREG: + if (node->tn_reg.tn_aobj != NULL) { + vm_object_deallocate(node->tn_reg.tn_aobj); + node->tn_reg.tn_aobj = 0; + } + pages = node->tn_reg.tn_aobj_pages; + break; + + default: + MPASS(0); + pages = 0; /* Shut up gcc when !DIAGNOSTIC. */ + break; + } + + id = node->tn_id; + gen = node->tn_gen; + memset(node, 0, sizeof(struct tmpfs_node)); + node->tn_id = id; + node->tn_type = VNON; + node->tn_gen = gen; + + TMPFS_LOCK(tmp); + LIST_INSERT_HEAD(&tmp->tm_nodes_avail, node, tn_entries); + tmp->tm_pages_used -= pages; + TMPFS_UNLOCK(tmp); +} + +/* --------------------------------------------------------------------- */ + +/* + * Allocates a new directory entry for the node node with a name of name. + * The new directory entry is returned in *de. + * + * The link count of node is increased by one to reflect the new object + * referencing it. + * + * Returns zero on success or an appropriate error code on failure. + */ +int +tmpfs_alloc_dirent(struct tmpfs_mount *tmp, struct tmpfs_node *node, + const char *name, uint16_t len, struct tmpfs_dirent **de) +{ + struct tmpfs_dirent *nde; + + nde = (struct tmpfs_dirent *)tmpfs_zone_alloc( + tmp->tm_dirent_pool, M_WAITOK); + if (nde == NULL) + return ENOSPC; + + nde->td_name = tmpfs_str_zone_alloc(&tmp->tm_str_pool, M_WAITOK, len); + if (nde->td_name == NULL) { + tmpfs_zone_free(tmp->tm_dirent_pool, nde); + return ENOSPC; + } + nde->td_namelen = len; + memcpy(nde->td_name, name, len); + + nde->td_node = node; + node->tn_links++; + + *de = nde; + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * Frees a directory entry. It is the caller's responsibility to destroy + * the node referenced by it if needed. + * + * The link count of node is decreased by one to reflect the removal of an + * object that referenced it. This only happens if 'node_exists' is true; + * otherwise the function will not access the node referred to by the + * directory entry, as it may already have been released from the outside. + */ +void +tmpfs_free_dirent(struct tmpfs_mount *tmp, struct tmpfs_dirent *de, + boolean_t node_exists) +{ + if (node_exists) { + struct tmpfs_node *node; + + node = de->td_node; + + MPASS(node->tn_links > 0); + node->tn_links--; + } + + tmpfs_str_zone_free(&tmp->tm_str_pool, de->td_name, de->td_namelen); + tmpfs_zone_free(tmp->tm_dirent_pool, de); +} + +/* --------------------------------------------------------------------- */ + +/* + * Allocates a new vnode for the node node or returns a new reference to + * an existing one if the node had already a vnode referencing it. The + * resulting locked vnode is returned in *vpp. + * + * Returns zero on success or an appropriate error code on failure. + */ +int +tmpfs_alloc_vp(struct mount *mp, struct tmpfs_node *node, struct vnode **vpp, + struct thread *td) +{ + int error; + struct vnode *vp; + + vp = NULL; + +loop: + if (node->tn_vnode != NULL) { + vp = node->tn_vnode; + vget(vp, LK_EXCLUSIVE | LK_RETRY, td); + + /* + * Make sure the vnode is still there after + * getting the interlock to avoid racing a free. + */ + if (node->tn_vnode == NULL || node->tn_vnode != vp) { + vput(vp); + goto loop; + } + + error = 0; + goto out; + } + + /* + * otherwise lock the vp list while we call getnewvnode + * since that can block. + */ + TMPFS_NODE_LOCK(node); + if (node->tn_vpstate & TMPFS_VNODE_ALLOCATING) { + node->tn_vpstate |= TMPFS_VNODE_WANT; + TMPFS_NODE_UNLOCK(node); + (void) tsleep((caddr_t) &node->tn_vpstate, 0, "tmpfs_vplock", 0); + goto loop; + } + + node->tn_vpstate |= TMPFS_VNODE_ALLOCATING; + TMPFS_NODE_UNLOCK(node); + + /* Get a new vnode and associate it with our node. */ + error = getnewvnode("tmpfs", mp, &tmpfs_vnodeop_entries, &vp); + if (error != 0) + goto unlock; + MPASS(vp != NULL); + + error = vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, td); + if (error != 0) { + vp->v_data = NULL; + vput(vp); + vp = NULL; + goto unlock; + } + + vp->v_data = node; + vp->v_type = node->tn_type; + + /* Type-specific initialization. */ + switch (node->tn_type) { + case VBLK: + /* FALLTHROUGH */ + case VCHR: + break; + + case VDIR: + break; + + case VFIFO: + vp->v_op = &tmpfs_fifoop_entries; + break; + + case VLNK: + /* FALLTHROUGH */ + case VREG: + /* FALLTHROUGH */ + case VSOCK: + break; + + default: + MPASS(0); + } + + vnode_pager_setsize(vp, node->tn_size); + insmntque(vp, mp); + + error = 0; + node->tn_vnode = vp; + +unlock: + MPASS(node->tn_vpstate & TMPFS_VNODE_ALLOCATING); + TMPFS_NODE_LOCK(node); + node->tn_vpstate &= ~TMPFS_VNODE_ALLOCATING; + + if (node->tn_vpstate & TMPFS_VNODE_WANT) { + node->tn_vpstate &= ~TMPFS_VNODE_WANT; + TMPFS_NODE_UNLOCK(node); + wakeup((caddr_t) &node->tn_vpstate); + } + else + TMPFS_NODE_UNLOCK(node); + +out: + *vpp = vp; + + MPASS(IFF(error == 0, *vpp != NULL && VOP_ISLOCKED(*vpp, td))); + MPASS(*vpp == node->tn_vnode); + + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Destroys the association between the vnode vp and the node it + * references. + */ +void +tmpfs_free_vp(struct vnode *vp) +{ + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + + node->tn_vnode = NULL; + vp->v_data = NULL; +} + +/* --------------------------------------------------------------------- */ + +/* + * Allocates a new file of type 'type' and adds it to the parent directory + * 'dvp'; this addition is done using the component name given in 'cnp'. + * The ownership of the new file is automatically assigned based on the + * credentials of the caller (through 'cnp'), the group is set based on + * the parent directory and the mode is determined from the 'vap' argument. + * If successful, *vpp holds a vnode to the newly created file and zero + * is returned. Otherwise *vpp is NULL and the function returns an + * appropriate error code. + */ +int +tmpfs_alloc_file(struct vnode *dvp, struct vnode **vpp, struct vattr *vap, + struct componentname *cnp, char *target) +{ + int error; + struct tmpfs_dirent *de; + struct tmpfs_mount *tmp; + struct tmpfs_node *dnode; + struct tmpfs_node *node; + struct tmpfs_node *parent; + + MPASS(VOP_ISLOCKED(dvp, cnp->cn_thread)); + MPASS(cnp->cn_flags & HASBUF); + + tmp = VFS_TO_TMPFS(dvp->v_mount); + dnode = VP_TO_TMPFS_DIR(dvp); + *vpp = NULL; + + /* If the entry we are creating is a directory, we cannot overflow + * the number of links of its parent, because it will get a new + * link. */ + if (vap->va_type == VDIR) { + /* Ensure that we do not overflow the maximum number of links + * imposed by the system. */ + MPASS(dnode->tn_links <= LINK_MAX); + if (dnode->tn_links == LINK_MAX) { + error = EMLINK; + goto out; + } + + parent = dnode; + } else + parent = NULL; + + /* Allocate a node that represents the new file. */ + error = tmpfs_alloc_node(tmp, vap->va_type, cnp->cn_cred->cr_uid, + dnode->tn_gid, vap->va_mode, parent, target, vap->va_rdev, + cnp->cn_thread, &node); + if (error != 0) + goto out; + + /* Allocate a directory entry that points to the new file. */ + error = tmpfs_alloc_dirent(tmp, node, cnp->cn_nameptr, cnp->cn_namelen, + &de); + if (error != 0) { + tmpfs_free_node(tmp, node); + goto out; + } + + /* Allocate a vnode for the new file. */ + error = tmpfs_alloc_vp(dvp->v_mount, node, vpp, cnp->cn_thread); + if (error != 0) { + tmpfs_free_dirent(tmp, de, TRUE); + tmpfs_free_node(tmp, node); + goto out; + } + + /* Now that all required items are allocated, we can proceed to + * insert the new node into the directory, an operation that + * cannot fail. */ + tmpfs_dir_attach(dvp, de); + +out: + + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Attaches the directory entry de to the directory represented by vp. + * Note that this does not change the link count of the node pointed by + * the directory entry, as this is done by tmpfs_alloc_dirent. + */ +void +tmpfs_dir_attach(struct vnode *vp, struct tmpfs_dirent *de) +{ + struct tmpfs_node *dnode; + + dnode = VP_TO_TMPFS_DIR(vp); + TAILQ_INSERT_TAIL(&dnode->tn_dir.tn_dirhead, de, td_entries); + dnode->tn_size += sizeof(struct tmpfs_dirent); + dnode->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \ + TMPFS_NODE_MODIFIED; + vnode_pager_setsize(vp, dnode->tn_size); +} + +/* --------------------------------------------------------------------- */ + +/* + * Detaches the directory entry de from the directory represented by vp. + * Note that this does not change the link count of the node pointed by + * the directory entry, as this is done by tmpfs_free_dirent. + */ +void +tmpfs_dir_detach(struct vnode *vp, struct tmpfs_dirent *de) +{ + struct tmpfs_node *dnode; + + dnode = VP_TO_TMPFS_DIR(vp); + + if (dnode->tn_dir.tn_readdir_lastp == de) { + dnode->tn_dir.tn_readdir_lastn = 0; + dnode->tn_dir.tn_readdir_lastp = NULL; + } + + TAILQ_REMOVE(&dnode->tn_dir.tn_dirhead, de, td_entries); + dnode->tn_size -= sizeof(struct tmpfs_dirent); + dnode->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \ + TMPFS_NODE_MODIFIED; + + vnode_pager_setsize(vp, dnode->tn_size); +} + +/* --------------------------------------------------------------------- */ + +/* + * Looks for a directory entry in the directory represented by node. + * 'cnp' describes the name of the entry to look for. Note that the . + * and .. components are not allowed as they do not physically exist + * within directories. + * + * Returns a pointer to the entry when found, otherwise NULL. + */ +struct tmpfs_dirent * +tmpfs_dir_lookup(struct tmpfs_node *node, struct componentname *cnp) +{ + boolean_t found; + struct tmpfs_dirent *de; + + MPASS(IMPLIES(cnp->cn_namelen == 1, cnp->cn_nameptr[0] != '.')); + MPASS(IMPLIES(cnp->cn_namelen == 2, !(cnp->cn_nameptr[0] == '.' && + cnp->cn_nameptr[1] == '.'))); + TMPFS_VALIDATE_DIR(node); + + found = 0; + TAILQ_FOREACH(de, &node->tn_dir.tn_dirhead, td_entries) { + MPASS(cnp->cn_namelen < 0xffff); + if (de->td_namelen == (uint16_t)cnp->cn_namelen && + memcmp(de->td_name, cnp->cn_nameptr, de->td_namelen) == 0) { + found = 1; + break; + } + } + node->tn_status |= TMPFS_NODE_ACCESSED; + + return found ? de : NULL; +} + +/* --------------------------------------------------------------------- */ + +/* + * Helper function for tmpfs_readdir. Creates a '.' entry for the given + * directory and returns it in the uio space. The function returns 0 + * on success, -1 if there was not enough space in the uio structure to + * hold the directory entry or an appropriate error code if another + * error happens. + */ +int +tmpfs_dir_getdotdent(struct tmpfs_node *node, struct uio *uio) +{ + int error; + struct dirent dent; + + TMPFS_VALIDATE_DIR(node); + MPASS(uio->uio_offset == TMPFS_DIRCOOKIE_DOT); + + dent.d_fileno = node->tn_id; + dent.d_type = DT_DIR; + dent.d_namlen = 1; + dent.d_name[0] = '.'; + dent.d_name[1] = '\0'; + dent.d_reclen = GENERIC_DIRSIZ(&dent); + + if (dent.d_reclen > uio->uio_resid) + error = -1; + else { + error = uiomove(&dent, dent.d_reclen, uio); + if (error == 0) + uio->uio_offset = TMPFS_DIRCOOKIE_DOTDOT; + } + + node->tn_status |= TMPFS_NODE_ACCESSED; + + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Helper function for tmpfs_readdir. Creates a '..' entry for the given + * directory and returns it in the uio space. The function returns 0 + * on success, -1 if there was not enough space in the uio structure to + * hold the directory entry or an appropriate error code if another + * error happens. + */ +int +tmpfs_dir_getdotdotdent(struct tmpfs_node *node, struct uio *uio) +{ + int error; + struct dirent dent; + + TMPFS_VALIDATE_DIR(node); + MPASS(uio->uio_offset == TMPFS_DIRCOOKIE_DOTDOT); + + dent.d_fileno = node->tn_dir.tn_parent->tn_id; + dent.d_type = DT_DIR; + dent.d_namlen = 2; + dent.d_name[0] = '.'; + dent.d_name[1] = '.'; + dent.d_name[2] = '\0'; + dent.d_reclen = GENERIC_DIRSIZ(&dent); + + if (dent.d_reclen > uio->uio_resid) + error = -1; + else { + error = uiomove(&dent, dent.d_reclen, uio); + if (error == 0) { + struct tmpfs_dirent *de; + + de = TAILQ_FIRST(&node->tn_dir.tn_dirhead); + if (de == NULL) + uio->uio_offset = TMPFS_DIRCOOKIE_EOF; + else + uio->uio_offset = TMPFS_DIRCOOKIE(de); + } + } + + node->tn_status |= TMPFS_NODE_ACCESSED; + + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Lookup a directory entry by its associated cookie. + */ +struct tmpfs_dirent * +tmpfs_dir_lookupbycookie(struct tmpfs_node *node, off_t cookie) +{ + struct tmpfs_dirent *de; + + if (cookie == node->tn_dir.tn_readdir_lastn && + node->tn_dir.tn_readdir_lastp != NULL) { + return node->tn_dir.tn_readdir_lastp; + } + + TAILQ_FOREACH(de, &node->tn_dir.tn_dirhead, td_entries) { + if (TMPFS_DIRCOOKIE(de) == cookie) { + break; + } + } + + return de; +} + +/* --------------------------------------------------------------------- */ + +/* + * Helper function for tmpfs_readdir. Returns as much directory entries + * as can fit in the uio space. The read starts at uio->uio_offset. + * The function returns 0 on success, -1 if there was not enough space + * in the uio structure to hold the directory entry or an appropriate + * error code if another error happens. + */ +int +tmpfs_dir_getdents(struct tmpfs_node *node, struct uio *uio, off_t *cntp) +{ + int error; + off_t startcookie; + struct tmpfs_dirent *de; + + TMPFS_VALIDATE_DIR(node); + + /* Locate the first directory entry we have to return. We have cached + * the last readdir in the node, so use those values if appropriate. + * Otherwise do a linear scan to find the requested entry. */ + startcookie = uio->uio_offset; + MPASS(startcookie != TMPFS_DIRCOOKIE_DOT); + MPASS(startcookie != TMPFS_DIRCOOKIE_DOTDOT); + if (startcookie == TMPFS_DIRCOOKIE_EOF) { + return 0; + } else { + de = tmpfs_dir_lookupbycookie(node, startcookie); + } + if (de == NULL) { + return EINVAL; + } + + /* Read as much entries as possible; i.e., until we reach the end of + * the directory or we exhaust uio space. */ + do { + struct dirent d; + + /* Create a dirent structure representing the current + * tmpfs_node and fill it. */ + d.d_fileno = de->td_node->tn_id; + switch (de->td_node->tn_type) { + case VBLK: + d.d_type = DT_BLK; + break; + + case VCHR: + d.d_type = DT_CHR; + break; + + case VDIR: + d.d_type = DT_DIR; + break; + + case VFIFO: + d.d_type = DT_FIFO; + break; + + case VLNK: + d.d_type = DT_LNK; + break; + + case VREG: + d.d_type = DT_REG; + break; + + case VSOCK: + d.d_type = DT_SOCK; + break; + + default: + MPASS(0); + } + d.d_namlen = de->td_namelen; + MPASS(de->td_namelen < sizeof(d.d_name)); + (void)memcpy(d.d_name, de->td_name, de->td_namelen); + d.d_name[de->td_namelen] = '\0'; + d.d_reclen = GENERIC_DIRSIZ(&d); + + /* Stop reading if the directory entry we are treating is + * bigger than the amount of data that can be returned. */ + if (d.d_reclen > uio->uio_resid) { + error = -1; + break; + } + + /* Copy the new dirent structure into the output buffer and + * advance pointers. */ + error = uiomove(&d, d.d_reclen, uio); + + (*cntp)++; + de = TAILQ_NEXT(de, td_entries); + } while (error == 0 && uio->uio_resid > 0 && de != NULL); + + /* Update the offset and cache. */ + if (de == NULL) { + uio->uio_offset = TMPFS_DIRCOOKIE_EOF; + node->tn_dir.tn_readdir_lastn = 0; + node->tn_dir.tn_readdir_lastp = NULL; + } else { + node->tn_dir.tn_readdir_lastn = uio->uio_offset = TMPFS_DIRCOOKIE(de); + node->tn_dir.tn_readdir_lastp = de; + } + + node->tn_status |= TMPFS_NODE_ACCESSED; + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Resizes the aobj associated to the regular file pointed to by vp to + * the size newsize. 'vp' must point to a vnode that represents a regular + * file. 'newsize' must be positive. + * + * Returns zero on success or an appropriate error code on failure. + */ +int +tmpfs_reg_resize(struct vnode *vp, off_t newsize) +{ + int error; + size_t newpages, oldpages; + struct tmpfs_mount *tmp; + struct tmpfs_node *node; + off_t oldsize; + + MPASS(vp->v_type == VREG); + MPASS(newsize >= 0); + + node = VP_TO_TMPFS_NODE(vp); + tmp = VFS_TO_TMPFS(vp->v_mount); + + /* Convert the old and new sizes to the number of pages needed to + * store them. It may happen that we do not need to do anything + * because the last allocated page can accommodate the change on + * its own. */ + oldsize = node->tn_size; + oldpages = round_page(oldsize) / PAGE_SIZE; + MPASS(oldpages == node->tn_reg.tn_aobj_pages); + newpages = round_page(newsize) / PAGE_SIZE; + + if (newpages > oldpages && + newpages - oldpages > TMPFS_PAGES_AVAIL(tmp)) { + error = ENOSPC; + goto out; + } + + node->tn_reg.tn_aobj_pages = newpages; + + tmp->tm_pages_used += (newpages - oldpages); + node->tn_size = newsize; + vnode_pager_setsize(vp, newsize); + if (newsize < oldsize) { + int zerolen = MIN(round_page(newsize), node->tn_size) - newsize; + struct vm_object *uobj = node->tn_reg.tn_aobj; + vm_page_t m; + + /* + * free "backing store" + */ + + if (newpages < oldpages) { + VM_OBJECT_LOCK(uobj); + swap_pager_freespace(uobj, + newpages, oldpages - newpages); + VM_OBJECT_UNLOCK(uobj); + } + + /* + * zero out the truncated part of the last page. + */ + + if (zerolen > 0) { + m = vm_page_grab(uobj, OFF_TO_IDX(newsize), + VM_ALLOC_NORMAL | VM_ALLOC_RETRY); + pmap_zero_page_area(m, PAGE_SIZE - zerolen, + zerolen); + vm_page_wakeup(m); + } + + } + + error = 0; + +out: + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Change flags of the given vnode. + * Caller should execute tmpfs_update on vp after a successful execution. + * The vnode must be locked on entry and remain locked on exit. + */ +int +tmpfs_chflags(struct vnode *vp, int flags, struct ucred *cred, struct thread *p) +{ + int error; + struct tmpfs_node *node; + + MPASS(VOP_ISLOCKED(vp, p)); + + node = VP_TO_TMPFS_NODE(vp); + + /* Disallow this operation if the file system is mounted read-only. */ + if (vp->v_mount->mnt_flag & MNT_RDONLY) + return EROFS; + + /* + * Callers may only modify the file flags on objects they + * have VADMIN rights for. + */ + if ((error = VOP_ACCESS(vp, VADMIN, cred, p))) + return (error); + /* + * Unprivileged processes are not permitted to unset system + * flags, or modify flags if any system flags are set. + */ + if (!priv_check_cred(cred, PRIV_VFS_SYSFLAGS, 0)) { + if (node->tn_flags + & (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND)) { + error = securelevel_gt(cred, 0); + if (error) + return (error); + } + /* Snapshot flag cannot be set or cleared */ + if (((flags & SF_SNAPSHOT) != 0 && + (node->tn_flags & SF_SNAPSHOT) == 0) || + ((flags & SF_SNAPSHOT) == 0 && + (node->tn_flags & SF_SNAPSHOT) != 0)) + return (EPERM); + node->tn_flags = flags; + } else { + if (node->tn_flags + & (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND) || + (flags & UF_SETTABLE) != flags) + return (EPERM); + node->tn_flags &= SF_SETTABLE; + node->tn_flags |= (flags & UF_SETTABLE); + } + node->tn_status |= TMPFS_NODE_CHANGED; + + MPASS(VOP_ISLOCKED(vp, p)); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * Change access mode on the given vnode. + * Caller should execute tmpfs_update on vp after a successful execution. + * The vnode must be locked on entry and remain locked on exit. + */ +int +tmpfs_chmod(struct vnode *vp, mode_t mode, struct ucred *cred, struct thread *p) +{ + int error; + struct tmpfs_node *node; + + MPASS(VOP_ISLOCKED(vp, p)); + + node = VP_TO_TMPFS_NODE(vp); + + /* Disallow this operation if the file system is mounted read-only. */ + if (vp->v_mount->mnt_flag & MNT_RDONLY) + return EROFS; + + /* Immutable or append-only files cannot be modified, either. */ + if (node->tn_flags & (IMMUTABLE | APPEND)) + return EPERM; + + /* + * To modify the permissions on a file, must possess VADMIN + * for that file. + */ + if ((error = VOP_ACCESS(vp, VADMIN, cred, p))) + return (error); + + /* + * Privileged processes may set the sticky bit on non-directories, + * as well as set the setgid bit on a file with a group that the + * process is not a member of. + */ + if (vp->v_type != VDIR && (mode & S_ISTXT)) { + if (priv_check_cred(cred, PRIV_VFS_STICKYFILE, 0)) + return (EFTYPE); + } + if (!groupmember(node->tn_gid, cred) && (mode & S_ISGID)) { + error = priv_check_cred(cred, PRIV_VFS_SETGID, 0); + if (error) + return (error); + } + + + node->tn_mode &= ~ALLPERMS; + node->tn_mode |= mode & ALLPERMS; + + node->tn_status |= TMPFS_NODE_CHANGED; + + MPASS(VOP_ISLOCKED(vp, p)); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * Change ownership of the given vnode. At least one of uid or gid must + * be different than VNOVAL. If one is set to that value, the attribute + * is unchanged. + * Caller should execute tmpfs_update on vp after a successful execution. + * The vnode must be locked on entry and remain locked on exit. + */ +int +tmpfs_chown(struct vnode *vp, uid_t uid, gid_t gid, struct ucred *cred, + struct thread *p) +{ + int error; + struct tmpfs_node *node; + uid_t ouid; + gid_t ogid; + + MPASS(VOP_ISLOCKED(vp, p)); + + node = VP_TO_TMPFS_NODE(vp); + + /* Assign default values if they are unknown. */ + MPASS(uid != VNOVAL || gid != VNOVAL); + if (uid == VNOVAL) + uid = node->tn_uid; + if (gid == VNOVAL) + gid = node->tn_gid; + MPASS(uid != VNOVAL && gid != VNOVAL); + + /* Disallow this operation if the file system is mounted read-only. */ + if (vp->v_mount->mnt_flag & MNT_RDONLY) + return EROFS; + + /* Immutable or append-only files cannot be modified, either. */ + if (node->tn_flags & (IMMUTABLE | APPEND)) + return EPERM; + + /* + * To modify the ownership of a file, must possess VADMIN for that + * file. + */ + if ((error = VOP_ACCESS(vp, VADMIN, cred, p))) + return (error); + + /* + * To change the owner of a file, or change the group of a file to a + * group of which we are not a member, the caller must have + * privilege. + */ + if ((uid != node->tn_uid || + (gid != node->tn_gid && !groupmember(gid, cred))) && + (error = priv_check_cred(cred, PRIV_VFS_CHOWN, 0))) + return (error); + + ogid = node->tn_gid; + ouid = node->tn_uid; + + node->tn_uid = uid; + node->tn_gid = gid; + + node->tn_status |= TMPFS_NODE_CHANGED; + + if ((node->tn_mode & (S_ISUID | S_ISGID)) && (ouid != uid || ogid != gid)) { + if (priv_check_cred(cred, PRIV_VFS_RETAINSUGID, 0)) + node->tn_mode &= ~(S_ISUID | S_ISGID); + } + + MPASS(VOP_ISLOCKED(vp, p)); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * Change size of the given vnode. + * Caller should execute tmpfs_update on vp after a successful execution. + * The vnode must be locked on entry and remain locked on exit. + */ +int +tmpfs_chsize(struct vnode *vp, u_quad_t size, struct ucred *cred, + struct thread *p) +{ + int error; + struct tmpfs_node *node; + + MPASS(VOP_ISLOCKED(vp, p)); + + node = VP_TO_TMPFS_NODE(vp); + + /* Decide whether this is a valid operation based on the file type. */ + error = 0; + switch (vp->v_type) { + case VDIR: + return EISDIR; + + case VREG: + if (vp->v_mount->mnt_flag & MNT_RDONLY) + return EROFS; + break; + + case VBLK: + /* FALLTHROUGH */ + case VCHR: + /* FALLTHROUGH */ + case VFIFO: + /* Allow modifications of special files even if in the file + * system is mounted read-only (we are not modifying the + * files themselves, but the objects they represent). */ + return 0; + + default: + /* Anything else is unsupported. */ + return EOPNOTSUPP; + } + + /* Immutable or append-only files cannot be modified, either. */ + if (node->tn_flags & (IMMUTABLE | APPEND)) + return EPERM; + + error = tmpfs_truncate(vp, size); + /* tmpfs_truncate will raise the NOTE_EXTEND and NOTE_ATTRIB kevents + * for us, as will update tn_status; no need to do that here. */ + + MPASS(VOP_ISLOCKED(vp, p)); + + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Change access and modification times of the given vnode. + * Caller should execute tmpfs_update on vp after a successful execution. + * The vnode must be locked on entry and remain locked on exit. + */ +int +tmpfs_chtimes(struct vnode *vp, struct timespec *atime, struct timespec *mtime, + struct timespec *birthtime, int vaflags, struct ucred *cred, struct thread *l) +{ + int error; + struct tmpfs_node *node; + + MPASS(VOP_ISLOCKED(vp, l)); + + node = VP_TO_TMPFS_NODE(vp); + + /* Disallow this operation if the file system is mounted read-only. */ + if (vp->v_mount->mnt_flag & MNT_RDONLY) + return EROFS; + + /* Immutable or append-only files cannot be modified, either. */ + if (node->tn_flags & (IMMUTABLE | APPEND)) + return EPERM; + + /* XXX: The following comes from UFS code, and can be found in + * several other file systems. Shouldn't this be centralized + * somewhere? */ + if (cred->cr_uid != node->tn_uid && + (error = suser_cred(cred, 0)) && + ((vaflags & VA_UTIMES_NULL) == 0 || + (error = VOP_ACCESS(vp, VWRITE, cred, l)))) + return error; + + if (atime->tv_sec != VNOVAL && atime->tv_nsec != VNOVAL) + node->tn_status |= TMPFS_NODE_ACCESSED; + + if (mtime->tv_sec != VNOVAL && mtime->tv_nsec != VNOVAL) + node->tn_status |= TMPFS_NODE_MODIFIED; + + if (birthtime->tv_nsec != VNOVAL && birthtime->tv_nsec != VNOVAL) + node->tn_status |= TMPFS_NODE_MODIFIED; + + tmpfs_itimes(vp, atime, mtime); + + if (birthtime->tv_nsec != VNOVAL && birthtime->tv_nsec != VNOVAL) + node->tn_birthtime = *birthtime; + MPASS(VOP_ISLOCKED(vp, l)); + + return 0; +} + +/* --------------------------------------------------------------------- */ +/* Sync timestamps */ +void +tmpfs_itimes(struct vnode *vp, const struct timespec *acc, + const struct timespec *mod) +{ + struct tmpfs_node *node; + struct timespec now; + + node = VP_TO_TMPFS_NODE(vp); + + if ((node->tn_status & (TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED | + TMPFS_NODE_CHANGED)) == 0) + return; + + nanotime(&now); + if (node->tn_status & TMPFS_NODE_ACCESSED) { + if (acc == NULL) + acc = &now; + node->tn_atime = *acc; + } + if (node->tn_status & TMPFS_NODE_MODIFIED) { + if (mod == NULL) + mod = &now; + node->tn_mtime = *mod; + } + if (node->tn_status & TMPFS_NODE_CHANGED) { + node->tn_ctime = now; + } + node->tn_status &= + ~(TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED | TMPFS_NODE_CHANGED); +} + +/* --------------------------------------------------------------------- */ + +void +tmpfs_update(struct vnode *vp) +{ + + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + + tmpfs_itimes(vp, NULL, NULL); +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_truncate(struct vnode *vp, off_t length) +{ + boolean_t extended; + int error; + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + extended = length > node->tn_size; + + if (length < 0) { + error = EINVAL; + goto out; + } + + if (node->tn_size == length) { + error = 0; + goto out; + } + + if (length > VFS_TO_TMPFS(vp->v_mount)->tm_maxfilesize) + return (EFBIG); + + error = tmpfs_reg_resize(vp, length); + if (error == 0) { + node->tn_status |= TMPFS_NODE_CHANGED | TMPFS_NODE_MODIFIED; + } + +out: + tmpfs_update(vp); + + return error; +} diff --git a/sys/fs/tmpfs/tmpfs_uma.c b/sys/fs/tmpfs/tmpfs_uma.c new file mode 100644 index 0000000..73066e1 --- /dev/null +++ b/sys/fs/tmpfs/tmpfs_uma.c @@ -0,0 +1,73 @@ +/*- + * Copyright (c) 2007 Rohit Jalan (rohitj@purpe.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <vm/vm.h> + +#include <fs/tmpfs/tmpfs.h> + +uma_zone_t +tmpfs_zone_create(char *name, int size, int align, struct tmpfs_mount *tmp) +{ + uma_zone_t z; + z = uma_zcreate(name, size, NULL, NULL, NULL, NULL, align, M_WAITOK); + return z; +} + +void +tmpfs_zone_destroy(uma_zone_t zone) +{ + uma_zdestroy(zone); +} + +void +tmpfs_str_zone_create(struct tmpfs_str_zone *tsz, struct tmpfs_mount *tmp) +{ + int i, len; + + len = TMPFS_STRZONE_STARTLEN; + for (i = 0; i < TMPFS_STRZONE_ZONECOUNT; ++i) { + tsz->tsz_zone[i] = tmpfs_zone_create( + "TMPFS str", len, UMA_ALIGN_PTR, tmp); + len <<= 1; + } +} + +void +tmpfs_str_zone_destroy(struct tmpfs_str_zone *tsz) +{ + int i, len; + + len = TMPFS_STRZONE_STARTLEN; + for (i = 0; i < TMPFS_STRZONE_ZONECOUNT; ++i) { + tmpfs_zone_destroy(tsz->tsz_zone[i]); + len <<= 1; + } +} + diff --git a/sys/fs/tmpfs/tmpfs_uma.h b/sys/fs/tmpfs/tmpfs_uma.h new file mode 100644 index 0000000..a402a4f --- /dev/null +++ b/sys/fs/tmpfs/tmpfs_uma.h @@ -0,0 +1,97 @@ +/*- + * Copyright (c) 2007 Rohit Jalan (rohitj@purpe.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _TMPFS_UMA_H +#define _TMPFS_UMA_H + +#include <vm/uma.h> + +#define TMPFS_STRZONE_ZONECOUNT 7 +#define TMPFS_STRZONE_STARTLEN (1 << 4) + +struct tmpfs_mount; + +struct tmpfs_str_zone { + uma_zone_t tsz_zone[TMPFS_STRZONE_ZONECOUNT]; +}; + +uma_zone_t tmpfs_zone_create(char *name, int size, int align, + struct tmpfs_mount *m); +void tmpfs_zone_destroy(uma_zone_t zone); + +static __inline void* +tmpfs_zone_alloc(uma_zone_t zone, int flags) +{ + return uma_zalloc(zone, flags); +} + +static __inline void +tmpfs_zone_free(uma_zone_t zone, void *item) +{ + uma_zfree(zone, item); +} + +void tmpfs_str_zone_create(struct tmpfs_str_zone *, struct tmpfs_mount *); +void tmpfs_str_zone_destroy(struct tmpfs_str_zone *); + +static __inline char* +tmpfs_str_zone_alloc(struct tmpfs_str_zone *tsz, int flags, size_t len) +{ + + int i, zlen; + char *ptr; + + MPASS(len <= (TMPFS_STRZONE_STARTLEN << (TMPFS_STRZONE_ZONECOUNT-1))); + + i = 0; + zlen = TMPFS_STRZONE_STARTLEN; + while (len > zlen) { + ++i; + zlen <<= 1; + } + ptr = (char *)tmpfs_zone_alloc(tsz->tsz_zone[i], flags); + return ptr; +} + +static __inline void +tmpfs_str_zone_free(struct tmpfs_str_zone *tsz, char *item, size_t len) +{ + int i, zlen; + + MPASS(len <= (TMPFS_STRZONE_STARTLEN << (TMPFS_STRZONE_ZONECOUNT-1))); + + i = 0; + zlen = TMPFS_STRZONE_STARTLEN; + while (len > zlen) { + ++i; + zlen <<= 1; + } + tmpfs_zone_free(tsz->tsz_zone[i], item); +} + +#endif diff --git a/sys/fs/tmpfs/tmpfs_vfsops.c b/sys/fs/tmpfs/tmpfs_vfsops.c new file mode 100644 index 0000000..e5d3d6e --- /dev/null +++ b/sys/fs/tmpfs/tmpfs_vfsops.c @@ -0,0 +1,418 @@ +/* $NetBSD: tmpfs_vfsops.c,v 1.10 2005/12/11 12:24:29 christos Exp $ */ + +/* + * Copyright (c) 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Efficient memory file system. + * + * tmpfs is a file system that uses NetBSD's virtual memory sub-system + * (the well-known UVM) to store file data and metadata in an efficient + * way. This means that it does not follow the structure of an on-disk + * file system because it simply does not need to. Instead, it uses + * memory-specific data structures and algorithms to automatically + * allocate and release resources. + */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/kernel.h> +#include <sys/stat.h> +#include <sys/systm.h> +#include <sys/sysctl.h> + +#include <vm/vm.h> +#include <vm/vm_object.h> +#include <vm/vm_param.h> + +#include <fs/tmpfs/tmpfs.h> + +/* + * Default permission for root node + */ +#define TMPFS_DEFAULT_ROOT_MODE (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) + +MALLOC_DEFINE(M_TMPFSMNT, "tmpfs mount", "tmpfs mount structures"); + +/* --------------------------------------------------------------------- */ + +static int tmpfs_mount(struct mount *, struct thread *); +static int tmpfs_unmount(struct mount *, int, struct thread *); +static int tmpfs_root(struct mount *, int flags, struct vnode **, + struct thread *); +static int tmpfs_fhtovp(struct mount *, struct fid *, struct vnode **); +static int tmpfs_statfs(struct mount *, struct statfs *, struct thread *); + +/* --------------------------------------------------------------------- */ + +static const char *tmpfs_opts[] = { + "from", "size", "inodes", "uid", "gid", "mode", + NULL +}; + +/* --------------------------------------------------------------------- */ + +#define SWI_MAXMIB 3 + +static int +get_swpgtotal(void) +{ + struct xswdev xsd; + char *sname = "vm.swap_info"; + int soid[SWI_MAXMIB], oid[2]; + int unswdev, total, dmmax, nswapdev; + size_t mibi, len; + + total = 0; + + len = sizeof(dmmax); + if (kernel_sysctlbyname(curthread, "vm.dmmax", &dmmax, &len, + NULL, 0, NULL, 0) != 0) + return total; + + len = sizeof(nswapdev); + if (kernel_sysctlbyname(curthread, "vm.nswapdev", + &nswapdev, &len, + NULL, 0, NULL, 0) != 0) + return total; + + mibi = (SWI_MAXMIB - 1) * sizeof(int); + oid[0] = 0; + oid[1] = 3; + + if (kernel_sysctl(curthread, oid, 2, + soid, &mibi, (void *)sname, strlen(sname), + NULL, 0) != 0) + return total; + + mibi = (SWI_MAXMIB - 1); + for (unswdev = 0; unswdev < nswapdev; ++unswdev) { + soid[mibi] = unswdev; + len = sizeof(struct xswdev); + if (kernel_sysctl(curthread, + soid, mibi + 1, &xsd, &len, NULL, 0, + NULL, 0) != 0) + return total; + if (len == sizeof(struct xswdev)) + total += (xsd.xsw_nblks - dmmax); + } + + /* Not Reached */ + return total; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_mount(struct mount *mp, struct thread *l) +{ + struct tmpfs_args args; + struct tmpfs_mount *tmp; + struct tmpfs_node *root; + size_t pages, mem_size; + ino_t nodes; + int error; + + if (vfs_filteropt(mp->mnt_optnew, tmpfs_opts)) + return (EINVAL); + + if (mp->mnt_flag & MNT_UPDATE) { + /* XXX: There is no support yet to update file system + * settings. Should be added. */ + + return EOPNOTSUPP; + } + + if (vfs_scanopt(mp->mnt_optnew, "gid", "%d", &args.ta_root_gid) != 1) + args.ta_root_gid = 0; + if (vfs_scanopt(mp->mnt_optnew, "uid", "%d", &args.ta_root_uid) != 1) + args.ta_root_uid = 0; + if (vfs_scanopt(mp->mnt_optnew, "mode", "%o", &args.ta_root_mode) != 1) + args.ta_root_mode = TMPFS_DEFAULT_ROOT_MODE; + if(vfs_scanopt(mp->mnt_optnew, "inodes", "%d", &args.ta_nodes_max) != 1) + args.ta_nodes_max = 0; + + if(vfs_scanopt(mp->mnt_optnew, + "size", + "%qu", &args.ta_size_max) != 1) + args.ta_size_max = 0; + + /* Do not allow mounts if we do not have enough memory to preserve + * the minimum reserved pages. */ + mem_size = cnt.v_free_count + cnt.v_inactive_count + get_swpgtotal(); + mem_size -= mem_size > cnt.v_wire_count ? cnt.v_wire_count : mem_size; + if (mem_size < TMPFS_PAGES_RESERVED) + return ENOSPC; + + /* Get the maximum number of memory pages this file system is + * allowed to use, based on the maximum size the user passed in + * the mount structure. A value of zero is treated as if the + * maximum available space was requested. */ + if (args.ta_size_max < PAGE_SIZE || args.ta_size_max >= SIZE_MAX) + pages = SIZE_MAX; + else + pages = args.ta_size_max / PAGE_SIZE + + (args.ta_size_max % PAGE_SIZE == 0 ? 0 : 1); + MPASS(pages > 0); + + if (args.ta_nodes_max <= 3) + nodes = 3 + pages * PAGE_SIZE / 1024; + else + nodes = args.ta_nodes_max; + MPASS(nodes >= 3); + + /* Allocate the tmpfs mount structure and fill it. */ + tmp = (struct tmpfs_mount *)malloc(sizeof(struct tmpfs_mount), + M_TMPFSMNT, M_WAITOK | M_ZERO); + + mtx_init(&tmp->allnode_lock, "tmpfs allnode lock", NULL, MTX_DEF); + tmp->tm_nodes_max = nodes; + tmp->tm_nodes_last = 2; + tmp->tm_nodes_inuse = 0; + tmp->tm_maxfilesize = get_swpgtotal() * PAGE_SIZE; + LIST_INIT(&tmp->tm_nodes_used); + LIST_INIT(&tmp->tm_nodes_avail); + + tmp->tm_pages_max = pages; + tmp->tm_pages_used = 0; + tmp->tm_dirent_pool = tmpfs_zone_create( + "TMPFS dirent", + sizeof(struct tmpfs_dirent), + UMA_ALIGN_PTR, + tmp); + tmp->tm_node_pool = tmpfs_zone_create( + "TMPFS node", + sizeof(struct tmpfs_node), + UMA_ALIGN_PTR, + tmp); + tmpfs_str_zone_create(&tmp->tm_str_pool, tmp); + + /* Allocate the root node. */ + error = tmpfs_alloc_node(tmp, VDIR, args.ta_root_uid, + args.ta_root_gid, args.ta_root_mode & ALLPERMS, NULL, NULL, + VNOVAL, l, &root); + + if (error != 0 || root == NULL) { + tmpfs_str_zone_destroy(&tmp->tm_str_pool); + tmpfs_zone_destroy(tmp->tm_node_pool); + tmpfs_zone_destroy(tmp->tm_dirent_pool); + free(tmp, M_TMPFSMNT); + return error; + } + tmp->tm_root = root; + + MNT_ILOCK(mp); + mp->mnt_flag |= MNT_LOCAL; + mp->mnt_kern_flag |= MNTK_MPSAFE; + MNT_IUNLOCK(mp); + + mp->mnt_data = tmp; + mp->mnt_stat.f_namemax = MAXNAMLEN; + vfs_getnewfsid(mp); + vfs_mountedfrom(mp, "tmpfs"); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* ARGSUSED2 */ +static int +tmpfs_unmount(struct mount *mp, int mntflags, struct thread *l) +{ + int error; + int flags = 0; + struct tmpfs_mount *tmp; + struct tmpfs_node *node; + + /* Handle forced unmounts. */ + if (mntflags & MNT_FORCE) + flags |= FORCECLOSE; + + /* Finalize all pending I/O. */ + error = vflush(mp, 0, flags, l); + if (error != 0) + return error; + + tmp = VFS_TO_TMPFS(mp); + + /* Free all associated data. The loop iterates over the linked list + * we have containing all used nodes. For each of them that is + * a directory, we free all its directory entries. Note that after + * freeing a node, it will automatically go to the available list, + * so we will later have to iterate over it to release its items. */ + node = LIST_FIRST(&tmp->tm_nodes_used); + while (node != NULL) { + struct tmpfs_node *next; + + if (node->tn_type == VDIR) { + struct tmpfs_dirent *de; + + de = TAILQ_FIRST(&node->tn_dir.tn_dirhead); + while (de != NULL) { + struct tmpfs_dirent *nde; + + nde = TAILQ_NEXT(de, td_entries); + tmpfs_free_dirent(tmp, de, FALSE); + de = nde; + node->tn_size -= sizeof(struct tmpfs_dirent); + } + } + + next = LIST_NEXT(node, tn_entries); + tmpfs_free_node(tmp, node); + node = next; + } + node = LIST_FIRST(&tmp->tm_nodes_avail); + while (node != NULL) { + struct tmpfs_node *next; + + next = LIST_NEXT(node, tn_entries); + LIST_REMOVE(node, tn_entries); + tmpfs_zone_free(tmp->tm_node_pool, node); + node = next; + } + + tmpfs_zone_destroy(tmp->tm_dirent_pool); + tmpfs_zone_destroy(tmp->tm_node_pool); + tmpfs_str_zone_destroy(&tmp->tm_str_pool); + + mtx_destroy(&tmp->allnode_lock); + MPASS(tmp->tm_pages_used == 0); + + /* Throw away the tmpfs_mount structure. */ + free(mp->mnt_data, M_TMPFSMNT); + mp->mnt_data = NULL; + + MNT_ILOCK(mp); + mp->mnt_flag &= ~MNT_LOCAL; + MNT_IUNLOCK(mp); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_root(struct mount *mp, int flags, struct vnode **vpp, struct thread *td) +{ + int error; + error = tmpfs_alloc_vp(mp, VFS_TO_TMPFS(mp)->tm_root, vpp, td); + + if (!error) + (*vpp)->v_vflag = VV_ROOT; + + return error; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_fhtovp(struct mount *mp, struct fid *fhp, struct vnode **vpp) +{ + boolean_t found; + struct tmpfs_fid *tfhp; + struct tmpfs_mount *tmp; + struct tmpfs_node *node; + + tmp = VFS_TO_TMPFS(mp); + + tfhp = (struct tmpfs_fid *)fhp; + if (tfhp->tf_len != sizeof(struct tmpfs_fid)) + return EINVAL; + + if (tfhp->tf_id >= tmp->tm_nodes_max) + return EINVAL; + + found = FALSE; + + TMPFS_LOCK(tmp); + LIST_FOREACH(node, &tmp->tm_nodes_used, tn_entries) { + if (node->tn_id == tfhp->tf_id && + node->tn_gen == tfhp->tf_gen) { + found = TRUE; + break; + } + } + TMPFS_UNLOCK(tmp); + + return found ? tmpfs_alloc_vp(mp, node, vpp, curthread) : EINVAL; +} + +/* --------------------------------------------------------------------- */ + +/* ARGSUSED2 */ +static int +tmpfs_statfs(struct mount *mp, struct statfs *sbp, struct thread *l) +{ + fsfilcnt_t freenodes; + struct tmpfs_mount *tmp; + + tmp = VFS_TO_TMPFS(mp); + + sbp->f_iosize = PAGE_SIZE; + sbp->f_bsize = PAGE_SIZE; + + sbp->f_blocks = TMPFS_PAGES_MAX(tmp); + sbp->f_bavail = sbp->f_bfree = TMPFS_PAGES_AVAIL(tmp); + + freenodes = MIN(tmp->tm_nodes_max - tmp->tm_nodes_inuse, + TMPFS_PAGES_AVAIL(tmp) * PAGE_SIZE / sizeof(struct tmpfs_node)); + + sbp->f_files = freenodes + tmp->tm_nodes_inuse; + sbp->f_ffree = freenodes; + /* sbp->f_owner = tmp->tn_uid; */ + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * tmpfs vfs operations. + */ + +struct vfsops tmpfs_vfsops = { + .vfs_mount = tmpfs_mount, + .vfs_unmount = tmpfs_unmount, + .vfs_root = tmpfs_root, + .vfs_statfs = tmpfs_statfs, + .vfs_fhtovp = tmpfs_fhtovp, +}; +VFS_SET(tmpfs_vfsops, tmpfs, 0); diff --git a/sys/fs/tmpfs/tmpfs_vnops.c b/sys/fs/tmpfs/tmpfs_vnops.c new file mode 100644 index 0000000..a3711b6 --- /dev/null +++ b/sys/fs/tmpfs/tmpfs_vnops.c @@ -0,0 +1,1365 @@ +/* $NetBSD: tmpfs_vnops.c,v 1.20 2006/01/26 20:07:34 jmmv Exp $ */ + +/* + * Copyright (c) 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * tmpfs vnode interface. + */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/fcntl.h> +#include <sys/lockf.h> +#include <sys/namei.h> +#include <sys/priv.h> +#include <sys/proc.h> +#include <sys/resourcevar.h> +#include <sys/stat.h> +#include <sys/systm.h> +#include <sys/unistd.h> +#include <sys/vnode.h> + +#include <vm/vm.h> +#include <vm/vm_object.h> +#include <vm/vm_page.h> +#include <vm/vm_pager.h> +#include <sys/sched.h> +#include <sys/sf_buf.h> +#include <machine/_inttypes.h> + +#include <fs/fifofs/fifo.h> +#include <fs/tmpfs/tmpfs_vnops.h> +#include <fs/tmpfs/tmpfs.h> + +/* --------------------------------------------------------------------- */ + +/* + * vnode operations vector used for files stored in a tmpfs file system. + */ +struct vop_vector tmpfs_vnodeop_entries = { + .vop_default = &default_vnodeops, + .vop_lookup = vfs_cache_lookup, + .vop_cachedlookup = tmpfs_lookup, + .vop_create = tmpfs_create, + .vop_mknod = tmpfs_mknod, + .vop_open = tmpfs_open, + .vop_close = tmpfs_close, + .vop_access = tmpfs_access, + .vop_getattr = tmpfs_getattr, + .vop_setattr = tmpfs_setattr, + .vop_read = tmpfs_read, + .vop_write = tmpfs_write, + .vop_fsync = tmpfs_fsync, + .vop_remove = tmpfs_remove, + .vop_link = tmpfs_link, + .vop_rename = tmpfs_rename, + .vop_mkdir = tmpfs_mkdir, + .vop_rmdir = tmpfs_rmdir, + .vop_symlink = tmpfs_symlink, + .vop_readdir = tmpfs_readdir, + .vop_readlink = tmpfs_readlink, + .vop_inactive = tmpfs_inactive, + .vop_reclaim = tmpfs_reclaim, + .vop_print = tmpfs_print, + .vop_pathconf = tmpfs_pathconf, + .vop_advlock = tmpfs_advlock, + .vop_bmap = VOP_EOPNOTSUPP, +}; + +/* --------------------------------------------------------------------- */ + +int +tmpfs_lookup(struct vop_cachedlookup_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode **vpp = v->a_vpp; + struct componentname *cnp = v->a_cnp; + struct thread *td = cnp->cn_thread; + + int error; + struct tmpfs_dirent *de; + struct tmpfs_node *dnode; + + dnode = VP_TO_TMPFS_DIR(dvp); + *vpp = NULLVP; + + /* Check accessibility of requested node as a first step. */ + error = VOP_ACCESS(dvp, VEXEC, cnp->cn_cred, td); + if (error != 0) + goto out; + + /* We cannot be requesting the parent directory of the root node. */ + MPASS(IMPLIES(dnode->tn_type == VDIR && + dnode->tn_dir.tn_parent == dnode, + !(cnp->cn_flags & ISDOTDOT))); + + if (cnp->cn_flags & ISDOTDOT) { + VOP_UNLOCK(dvp, 0, td); + + /* Allocate a new vnode on the matching entry. */ + error = tmpfs_alloc_vp(dvp->v_mount, dnode->tn_dir.tn_parent, vpp, td); + + vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY, td); + + dnode->tn_dir.tn_parent->tn_lookup_dirent = NULL; + } else if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') { + VREF(dvp); + *vpp = dvp; + dnode->tn_lookup_dirent = NULL; + error = 0; + } else { + de = tmpfs_dir_lookup(dnode, cnp); + if (de == NULL) { + /* The entry was not found in the directory. + * This is OK if we are creating or renaming an + * entry and are working on the last component of + * the path name. */ + if ((cnp->cn_flags & ISLASTCN) && + (cnp->cn_nameiop == CREATE || \ + cnp->cn_nameiop == RENAME)) { + error = VOP_ACCESS(dvp, VWRITE, cnp->cn_cred, + cnp->cn_thread); + if (error != 0) + goto out; + + /* Keep the component name in the buffer for + * future uses. */ + cnp->cn_flags |= SAVENAME; + + error = EJUSTRETURN; + } else + error = ENOENT; + } else { + struct tmpfs_node *tnode; + + /* The entry was found, so get its associated + * tmpfs_node. */ + tnode = de->td_node; + + /* If we are not at the last path component and + * found a non-directory or non-link entry (which + * may itself be pointing to a directory), raise + * an error. */ + if ((tnode->tn_type != VDIR && + tnode->tn_type != VLNK) && + !(cnp->cn_flags & ISLASTCN)) { + error = ENOTDIR; + goto out; + } + + /* If we are deleting or renaming the entry, keep + * track of its tmpfs_dirent so that it can be + * easily deleted later. */ + if ((cnp->cn_flags & ISLASTCN) && + (cnp->cn_nameiop == DELETE || + cnp->cn_nameiop == RENAME)) { + error = VOP_ACCESS(dvp, VWRITE, cnp->cn_cred, + cnp->cn_thread); + if (error != 0) + goto out; + + /* Allocate a new vnode on the matching entry. */ + error = tmpfs_alloc_vp(dvp->v_mount, tnode, vpp, td); + if (error != 0) + goto out; + + if ((dnode->tn_mode & S_ISTXT) && + VOP_ACCESS(dvp, VADMIN, cnp->cn_cred, cnp->cn_thread) && + VOP_ACCESS(*vpp, VADMIN, cnp->cn_cred, cnp->cn_thread)) { + error = EPERM; + vput(*vpp); + *vpp = NULL; + goto out; + } + tnode->tn_lookup_dirent = de; + cnp->cn_flags |= SAVENAME; + } + else + error = tmpfs_alloc_vp(dvp->v_mount, tnode, vpp, td); + + } + } + + /* Store the result of this lookup in the cache. Avoid this if the + * request was for creation, as it does not improve timings on + * emprical tests. */ + if ((cnp->cn_flags & MAKEENTRY) && cnp->cn_nameiop != CREATE) + cache_enter(dvp, *vpp, cnp); + +out: + /* If there were no errors, *vpp cannot be null and it must be + * locked. */ + MPASS(IFF(error == 0, *vpp != NULLVP && VOP_ISLOCKED(*vpp, td))); + + return error; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_create(struct vop_create_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode **vpp = v->a_vpp; + struct componentname *cnp = v->a_cnp; + struct vattr *vap = v->a_vap; + + MPASS(vap->va_type == VREG || vap->va_type == VSOCK); + + return tmpfs_alloc_file(dvp, vpp, vap, cnp, NULL); +} +/* --------------------------------------------------------------------- */ + +int +tmpfs_mknod(struct vop_mknod_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode **vpp = v->a_vpp; + struct componentname *cnp = v->a_cnp; + struct vattr *vap = v->a_vap; + + if (vap->va_type != VBLK && vap->va_type != VCHR && + vap->va_type != VFIFO) + return EINVAL; + + return tmpfs_alloc_file(dvp, vpp, vap, cnp, NULL); +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_open(struct vop_open_args *v) +{ + struct vnode *vp = v->a_vp; + int mode = v->a_mode; + + int error; + struct tmpfs_node *node; + + MPASS(VOP_ISLOCKED(vp, v->a_td)); + + node = VP_TO_TMPFS_NODE(vp); + + /* The file is still active but all its names have been removed + * (e.g. by a "rmdir $(pwd)"). It cannot be opened any more as + * it is about to die. */ + if (node->tn_links < 1) + return (ENOENT); + + /* If the file is marked append-only, deny write requests. */ + if (node->tn_flags & APPEND && (mode & (FWRITE | O_APPEND)) == FWRITE) + error = EPERM; + else { + error = 0; + vnode_create_vobject(vp, node->tn_size, v->a_td); + } + + MPASS(VOP_ISLOCKED(vp, v->a_td)); + return error; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_close(struct vop_close_args *v) +{ + struct vnode *vp = v->a_vp; + + struct tmpfs_node *node; + + MPASS(VOP_ISLOCKED(vp, v->a_td)); + + node = VP_TO_TMPFS_NODE(vp); + + if (node->tn_links > 0) { + /* Update node times. No need to do it if the node has + * been deleted, because it will vanish after we return. */ + tmpfs_update(vp); + } + + return 0; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_access(struct vop_access_args *v) +{ + struct vnode *vp = v->a_vp; + int mode = v->a_mode; + struct ucred *cred = v->a_cred; + + int error; + struct tmpfs_node *node; + + MPASS(VOP_ISLOCKED(vp, v->a_td)); + + node = VP_TO_TMPFS_NODE(vp); + + switch (vp->v_type) { + case VDIR: + /* FALLTHROUGH */ + case VLNK: + /* FALLTHROUGH */ + case VREG: + if (mode & VWRITE && vp->v_mount->mnt_flag & MNT_RDONLY) { + error = EROFS; + goto out; + } + break; + + case VBLK: + /* FALLTHROUGH */ + case VCHR: + /* FALLTHROUGH */ + case VSOCK: + /* FALLTHROUGH */ + case VFIFO: + break; + + default: + error = EINVAL; + goto out; + } + + if (mode & VWRITE && node->tn_flags & IMMUTABLE) { + error = EPERM; + goto out; + } + + error = vaccess(vp->v_type, node->tn_mode, node->tn_uid, + node->tn_gid, mode, cred, NULL); + +out: + MPASS(VOP_ISLOCKED(vp, v->a_td)); + + return error; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_getattr(struct vop_getattr_args *v) +{ + struct vnode *vp = v->a_vp; + struct vattr *vap = v->a_vap; + + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + + VATTR_NULL(vap); + + tmpfs_update(vp); + + vap->va_type = vp->v_type; + vap->va_mode = node->tn_mode; + vap->va_nlink = node->tn_links; + vap->va_uid = node->tn_uid; + vap->va_gid = node->tn_gid; + vap->va_fsid = vp->v_mount->mnt_stat.f_fsid.val[0]; + vap->va_fileid = node->tn_id; + vap->va_size = node->tn_size; + vap->va_blocksize = PAGE_SIZE; + vap->va_atime = node->tn_atime; + vap->va_mtime = node->tn_mtime; + vap->va_ctime = node->tn_ctime; + vap->va_birthtime = node->tn_birthtime; + vap->va_gen = node->tn_gen; + vap->va_flags = node->tn_flags; + vap->va_rdev = (vp->v_type == VBLK || vp->v_type == VCHR) ? + node->tn_rdev : VNOVAL; + vap->va_bytes = round_page(node->tn_size); + vap->va_filerev = VNOVAL; + vap->va_vaflags = 0; + vap->va_spare = VNOVAL; /* XXX */ + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* XXX Should this operation be atomic? I think it should, but code in + * XXX other places (e.g., ufs) doesn't seem to be... */ +int +tmpfs_setattr(struct vop_setattr_args *v) +{ + struct vnode *vp = v->a_vp; + struct vattr *vap = v->a_vap; + struct ucred *cred = v->a_cred; + struct thread *l = v->a_td; + + int error; + + MPASS(VOP_ISLOCKED(vp, l)); + + error = 0; + + /* Abort if any unsettable attribute is given. */ + if (vap->va_type != VNON || + vap->va_nlink != VNOVAL || + vap->va_fsid != VNOVAL || + vap->va_fileid != VNOVAL || + vap->va_blocksize != VNOVAL || + vap->va_gen != VNOVAL || + vap->va_rdev != VNOVAL || + vap->va_bytes != VNOVAL) + error = EINVAL; + + if (error == 0 && (vap->va_flags != VNOVAL)) + error = tmpfs_chflags(vp, vap->va_flags, cred, l); + + if (error == 0 && (vap->va_size != VNOVAL)) + error = tmpfs_chsize(vp, vap->va_size, cred, l); + + if (error == 0 && (vap->va_uid != VNOVAL || vap->va_gid != VNOVAL)) + error = tmpfs_chown(vp, vap->va_uid, vap->va_gid, cred, + l); + + if (error == 0 && (vap->va_mode != (mode_t)VNOVAL)) + error = tmpfs_chmod(vp, vap->va_mode, cred, l); + + if (error == 0 && ((vap->va_atime.tv_sec != VNOVAL && + vap->va_atime.tv_nsec != VNOVAL) || + (vap->va_mtime.tv_sec != VNOVAL && + vap->va_mtime.tv_nsec != VNOVAL) || + (vap->va_birthtime.tv_sec != VNOVAL && + vap->va_birthtime.tv_nsec != VNOVAL))) + error = tmpfs_chtimes(vp, &vap->va_atime, &vap->va_mtime, + &vap->va_birthtime, vap->va_vaflags, cred, l); + + /* Update the node times. We give preference to the error codes + * generated by this function rather than the ones that may arise + * from tmpfs_update. */ + tmpfs_update(vp); + + MPASS(VOP_ISLOCKED(vp, l)); + + return error; +} + +/* --------------------------------------------------------------------- */ +static int +tmpfs_uio_xfer(struct tmpfs_mount *tmp, struct tmpfs_node *node, + struct uio *uio, vm_object_t uobj) +{ + struct sf_buf *sf; + vm_pindex_t idx; + vm_offset_t d; + vm_page_t m; + size_t len; + int error = 0; + + /* uobj - locked by caller */ + + VM_OBJECT_LOCK(uobj); + vm_object_pip_add(uobj, 1); + while (error == 0 && uio->uio_resid > 0) { + if (node->tn_size <= uio->uio_offset) + break; + + len = MIN(node->tn_size - uio->uio_offset, uio->uio_resid); + if (len == 0) + break; + + idx = OFF_TO_IDX(uio->uio_offset); + d = uio->uio_offset - IDX_TO_OFF(idx); + len = MIN(len, (PAGE_SIZE - d)); + m = vm_page_grab(uobj, idx, VM_ALLOC_NORMAL | VM_ALLOC_RETRY); + if (uio->uio_rw == UIO_READ && m->valid != VM_PAGE_BITS_ALL) + if (vm_pager_get_pages(uobj, &m, 1, 0) != VM_PAGER_OK) + vm_page_zero_invalid(m, TRUE); + VM_OBJECT_UNLOCK(uobj); + sched_pin(); + sf = sf_buf_alloc(m, SFB_CPUPRIVATE); + error = uiomove((void *)(sf_buf_kva(sf) + d), len, uio); + sf_buf_free(sf); + sched_unpin(); + VM_OBJECT_LOCK(uobj); + vm_page_lock_queues(); + if (error == 0 && uio->uio_rw == UIO_WRITE) { + vm_page_set_validclean(m, d, len); + vm_page_zero_invalid(m, TRUE); + vm_page_dirty(m); + } + vm_page_activate(m); + vm_page_wakeup(m); + vm_page_unlock_queues(); + } + vm_object_pip_subtract(uobj, 1); + VM_OBJECT_UNLOCK(uobj); + return error; +} + +int +tmpfs_read(struct vop_read_args *v) +{ + struct vnode *vp = v->a_vp; + struct uio *uio = v->a_uio; + + struct tmpfs_node *node; + vm_object_t uobj; + + int error; + + node = VP_TO_TMPFS_NODE(vp); + + if (vp->v_type != VREG) { + error = EISDIR; + goto out; + } + + if (uio->uio_offset < 0) { + error = EINVAL; + goto out; + } + + node->tn_status |= TMPFS_NODE_ACCESSED; + + uobj = node->tn_reg.tn_aobj; + error = tmpfs_uio_xfer(VFS_TO_TMPFS(vp->v_mount), node, uio, uobj); + +out: + + return error; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_write(struct vop_write_args *v) +{ + struct vnode *vp = v->a_vp; + struct uio *uio = v->a_uio; + int ioflag = v->a_ioflag; + struct thread *td = uio->uio_td; + + boolean_t extended; + int error; + off_t oldsize; + struct tmpfs_node *node; + vm_object_t uobj; + + node = VP_TO_TMPFS_NODE(vp); + oldsize = node->tn_size; + + if (uio->uio_offset < 0 || vp->v_type != VREG) { + error = EINVAL; + goto out; + } + + if (uio->uio_resid == 0) { + error = 0; + goto out; + } + + if (ioflag & IO_APPEND) + uio->uio_offset = node->tn_size; + + if (uio->uio_offset + uio->uio_resid > + VFS_TO_TMPFS(vp->v_mount)->tm_maxfilesize) + return (EFBIG); + + if (vp->v_type == VREG && td != NULL) { + PROC_LOCK(td->td_proc); + if (uio->uio_offset + uio->uio_resid > + lim_cur(td->td_proc, RLIMIT_FSIZE)) { + psignal(td->td_proc, SIGXFSZ); + PROC_UNLOCK(td->td_proc); + return (EFBIG); + } + PROC_UNLOCK(td->td_proc); + } + + extended = uio->uio_offset + uio->uio_resid > node->tn_size; + if (extended) { + error = tmpfs_reg_resize(vp, uio->uio_offset + uio->uio_resid); + if (error != 0) + goto out; + } + + uobj = node->tn_reg.tn_aobj; + error = tmpfs_uio_xfer(VFS_TO_TMPFS(vp->v_mount), node, uio, uobj); + + node->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED | + (extended ? TMPFS_NODE_CHANGED : 0); + + if (node->tn_mode & (S_ISUID | S_ISGID)) { + if (priv_check_cred(v->a_cred, PRIV_VFS_RETAINSUGID, 0)) + node->tn_mode &= ~(S_ISUID | S_ISGID); + } + + if (error != 0) + (void)tmpfs_reg_resize(vp, oldsize); + +out: + MPASS(IMPLIES(error == 0, uio->uio_resid == 0)); + MPASS(IMPLIES(error != 0, oldsize == node->tn_size)); + + return error; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_fsync(struct vop_fsync_args *v) +{ + struct vnode *vp = v->a_vp; + + MPASS(VOP_ISLOCKED(vp, v->a_td)); + + tmpfs_update(vp); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_remove(struct vop_remove_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode *vp = v->a_vp; + + int error; + struct tmpfs_dirent *de; + struct tmpfs_mount *tmp; + struct tmpfs_node *dnode; + struct tmpfs_node *node; + + MPASS(VOP_ISLOCKED(dvp, v->a_cnp->cn_thread)); + MPASS(VOP_ISLOCKED(vp, v->a_cnp->cn_thread)); + + if (vp->v_type == VDIR) { + error = EISDIR; + goto out; + } + + dnode = VP_TO_TMPFS_DIR(dvp); + node = VP_TO_TMPFS_NODE(vp); + tmp = VFS_TO_TMPFS(vp->v_mount); + de = node->tn_lookup_dirent; + MPASS(de != NULL); + + /* Files marked as immutable or append-only cannot be deleted. */ + if ((node->tn_flags & (IMMUTABLE | APPEND | NOUNLINK)) || + (dnode->tn_flags & APPEND)) { + error = EPERM; + goto out; + } + + /* Remove the entry from the directory; as it is a file, we do not + * have to change the number of hard links of the directory. */ + tmpfs_dir_detach(dvp, de); + + /* Free the directory entry we just deleted. Note that the node + * referred by it will not be removed until the vnode is really + * reclaimed. */ + tmpfs_free_dirent(tmp, de, TRUE); + + if (node->tn_links > 0) + node->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \ + TMPFS_NODE_MODIFIED; + error = 0; + +out: + + return error; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_link(struct vop_link_args *v) +{ + struct vnode *dvp = v->a_tdvp; + struct vnode *vp = v->a_vp; + struct componentname *cnp = v->a_cnp; + + int error; + struct tmpfs_dirent *de; + struct tmpfs_node *dnode; + struct tmpfs_node *node; + + MPASS(VOP_ISLOCKED(dvp, cnp->cn_thread)); + MPASS(cnp->cn_flags & HASBUF); + MPASS(dvp != vp); /* XXX When can this be false? */ + + dnode = VP_TO_TMPFS_DIR(dvp); + node = VP_TO_TMPFS_NODE(vp); + + /* XXX: Why aren't the following two tests done by the caller? */ + + /* Hard links of directories are forbidden. */ + if (vp->v_type == VDIR) { + error = EPERM; + goto out; + } + + /* Cannot create cross-device links. */ + if (dvp->v_mount != vp->v_mount) { + error = EXDEV; + goto out; + } + + /* Ensure that we do not overflow the maximum number of links imposed + * by the system. */ + MPASS(node->tn_links <= LINK_MAX); + if (node->tn_links == LINK_MAX) { + error = EMLINK; + goto out; + } + + /* We cannot create links of files marked immutable or append-only. */ + if (node->tn_flags & (IMMUTABLE | APPEND)) { + error = EPERM; + goto out; + } + + /* Allocate a new directory entry to represent the node. */ + error = tmpfs_alloc_dirent(VFS_TO_TMPFS(vp->v_mount), node, + cnp->cn_nameptr, cnp->cn_namelen, &de); + if (error != 0) + goto out; + + /* Insert the new directory entry into the appropriate directory. */ + tmpfs_dir_attach(dvp, de); + + /* vp link count has changed, so update node times. */ + node->tn_status |= TMPFS_NODE_CHANGED; + tmpfs_update(vp); + + error = 0; +out: + + return error; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_rename(struct vop_rename_args *v) +{ + struct vnode *fdvp = v->a_fdvp; + struct vnode *fvp = v->a_fvp; + struct componentname *fcnp = v->a_fcnp; + struct vnode *tdvp = v->a_tdvp; + struct vnode *tvp = v->a_tvp; + struct componentname *tcnp = v->a_tcnp; + struct tmpfs_node *tnode = 0; /* pacify gcc */ + + char *newname; + int error; + struct tmpfs_dirent *de; + struct tmpfs_mount *tmp; + struct tmpfs_node *fdnode; + struct tmpfs_node *fnode; + struct tmpfs_node *tdnode; + + MPASS(VOP_ISLOCKED(tdvp, tcnp->cn_thread)); + MPASS(IMPLIES(tvp != NULL, VOP_ISLOCKED(tvp, tcnp->cn_thread))); + MPASS(fcnp->cn_flags & HASBUF); + MPASS(tcnp->cn_flags & HASBUF); + + fdnode = VP_TO_TMPFS_DIR(fdvp); + fnode = VP_TO_TMPFS_NODE(fvp); + de = fnode->tn_lookup_dirent; + + /* Disallow cross-device renames. + * XXX Why isn't this done by the caller? */ + if (fvp->v_mount != tdvp->v_mount || + (tvp != NULL && fvp->v_mount != tvp->v_mount)) { + error = EXDEV; + goto out; + } + + tmp = VFS_TO_TMPFS(tdvp->v_mount); + tdnode = VP_TO_TMPFS_DIR(tdvp); + + /* If source and target are the same file, there is nothing to do. */ + if (fvp == tvp) { + error = 0; + goto out; + } + + /* Avoid manipulating '.' and '..' entries. */ + if (de == NULL) { + MPASS(fvp->v_type == VDIR); + error = EINVAL; + goto out; + } + MPASS(de->td_node == fnode); + + /* If re-naming a directory to another preexisting directory + * ensure that the target directory is empty so that its + * removal causes no side effects. + * Kern_rename gurantees the destination to be a directory + * if the source is one. */ + if (tvp != NULL) { + tnode = VP_TO_TMPFS_NODE(tvp); + + if ((tnode->tn_flags & (NOUNLINK | IMMUTABLE | APPEND)) || + (tdnode->tn_flags & (APPEND | IMMUTABLE))) { + error = EPERM; + goto out; + } + + if ((de->td_node->tn_type == VDIR) && (tnode->tn_size > 0)) { + error = ENOTEMPTY; + goto out; + } + } + + /* If we need to move the directory between entries, lock the + * source so that we can safely operate on it. */ + if (fdnode != tdnode) { + error = vn_lock(fdvp, LK_EXCLUSIVE | LK_RETRY, tcnp->cn_thread); + if (error != 0) + goto out; + } + + if ((fnode->tn_flags & (NOUNLINK | IMMUTABLE | APPEND)) + || (fdnode->tn_flags & (APPEND | IMMUTABLE))) { + error = EPERM; + goto out_locked; + } + + /* Ensure that we have enough memory to hold the new name, if it + * has to be changed. */ + if (fcnp->cn_namelen != tcnp->cn_namelen || + memcmp(fcnp->cn_nameptr, tcnp->cn_nameptr, fcnp->cn_namelen) != 0) { + newname = tmpfs_str_zone_alloc(&tmp->tm_str_pool, M_WAITOK, + tcnp->cn_namelen); + if (newname == NULL) { + error = ENOSPC; + goto out_locked; + } + } else + newname = NULL; + + /* If the node is being moved to another directory, we have to do + * the move. */ + if (fdnode != tdnode) { + /* In case we are moving a directory, we have to adjust its + * parent to point to the new parent. */ + if (de->td_node->tn_type == VDIR) { + struct tmpfs_node *n; + + /* Ensure the target directory is not a child of the + * directory being moved. Otherwise, we'd end up + * with stale nodes. */ + n = tdnode; + while (n != n->tn_dir.tn_parent) { + if (n == fnode) { + error = EINVAL; + if (newname != NULL) + tmpfs_str_zone_free(&tmp->tm_str_pool, + newname, tcnp->cn_namelen); + goto out_locked; + } + n = n->tn_dir.tn_parent; + } + + /* Adjust the parent pointer. */ + TMPFS_VALIDATE_DIR(fnode); + de->td_node->tn_dir.tn_parent = tdnode; + + /* As a result of changing the target of the '..' + * entry, the link count of the source and target + * directories has to be adjusted. */ + fdnode->tn_links--; + tdnode->tn_links++; + } + + /* Do the move: just remove the entry from the source directory + * and insert it into the target one. */ + tmpfs_dir_detach(fdvp, de); + tmpfs_dir_attach(tdvp, de); + } + + /* If the name has changed, we need to make it effective by changing + * it in the directory entry. */ + if (newname != NULL) { + MPASS(tcnp->cn_namelen <= MAXNAMLEN); + + tmpfs_str_zone_free(&tmp->tm_str_pool, de->td_name, + de->td_namelen); + de->td_namelen = (uint16_t)tcnp->cn_namelen; + memcpy(newname, tcnp->cn_nameptr, tcnp->cn_namelen); + de->td_name = newname; + + fnode->tn_status |= TMPFS_NODE_CHANGED; + tdnode->tn_status |= TMPFS_NODE_MODIFIED; + } + + /* If we are overwriting an entry, we have to remove the old one + * from the target directory. */ + if (tvp != NULL) { + /* Remove the old entry from the target directory. */ + de = tnode->tn_lookup_dirent; + tmpfs_dir_detach(tdvp, de); + + /* Free the directory entry we just deleted. Note that the + * node referred by it will not be removed until the vnode is + * really reclaimed. */ + tmpfs_free_dirent(VFS_TO_TMPFS(tvp->v_mount), de, TRUE); + } + + error = 0; + +out_locked: + if (fdnode != tdnode) + VOP_UNLOCK(fdvp, 0, tcnp->cn_thread); + +out: + /* Release target nodes. */ + /* XXX: I don't understand when tdvp can be the same as tvp, but + * other code takes care of this... */ + if (tdvp == tvp) + vrele(tdvp); + else + vput(tdvp); + if (tvp != NULL) + vput(tvp); + + /* Release source nodes. */ + vrele(fdvp); + vrele(fvp); + + return error; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_mkdir(struct vop_mkdir_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode **vpp = v->a_vpp; + struct componentname *cnp = v->a_cnp; + struct vattr *vap = v->a_vap; + + MPASS(vap->va_type == VDIR); + + return tmpfs_alloc_file(dvp, vpp, vap, cnp, NULL); +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_rmdir(struct vop_rmdir_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode *vp = v->a_vp; + + int error; + struct tmpfs_dirent *de; + struct tmpfs_mount *tmp; + struct tmpfs_node *dnode; + struct tmpfs_node *node; + + MPASS(VOP_ISLOCKED(dvp, v->a_cnp->cn_thread)); + MPASS(VOP_ISLOCKED(vp, v->a_cnp->cn_thread)); + + tmp = VFS_TO_TMPFS(dvp->v_mount); + dnode = VP_TO_TMPFS_DIR(dvp); + node = VP_TO_TMPFS_DIR(vp); + + + /* Directories with more than two entries ('.' and '..') cannot be + * removed. */ + if (node->tn_size > 0) { + error = ENOTEMPTY; + goto out; + } + + if ((dnode->tn_flags & APPEND) + || (node->tn_flags & (NOUNLINK | IMMUTABLE | APPEND))) { + error = EPERM; + goto out; + } + + /* This invariant holds only if we are not trying to remove "..". + * We checked for that above so this is safe now. */ + MPASS(node->tn_dir.tn_parent == dnode); + + /* Get the directory entry associated with node (vp). This was + * filled by tmpfs_lookup while looking up the entry. */ + de = node->tn_lookup_dirent; + MPASS(TMPFS_DIRENT_MATCHES(de, + v->a_cnp->cn_nameptr, + v->a_cnp->cn_namelen)); + + /* Check flags to see if we are allowed to remove the directory. */ + if (dnode->tn_flags & APPEND + || node->tn_flags & (NOUNLINK | IMMUTABLE | APPEND)) { + error = EPERM; + goto out; + } + + /* Detach the directory entry from the directory (dnode). */ + tmpfs_dir_detach(dvp, de); + + node->tn_links--; + node->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \ + TMPFS_NODE_MODIFIED; + node->tn_dir.tn_parent->tn_links--; + node->tn_dir.tn_parent->tn_status |= TMPFS_NODE_ACCESSED | \ + TMPFS_NODE_CHANGED | TMPFS_NODE_MODIFIED; + + cache_purge(dvp); + cache_purge(vp); + + /* Free the directory entry we just deleted. Note that the node + * referred by it will not be removed until the vnode is really + * reclaimed. */ + tmpfs_free_dirent(tmp, de, TRUE); + + /* Release the deleted vnode (will destroy the node, notify + * interested parties and clean it from the cache). */ + + dnode->tn_status |= TMPFS_NODE_CHANGED; + tmpfs_update(dvp); + + error = 0; + +out: + return error; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_symlink(struct vop_symlink_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode **vpp = v->a_vpp; + struct componentname *cnp = v->a_cnp; + struct vattr *vap = v->a_vap; + char *target = v->a_target; + +#ifdef notyet /* XXX FreeBSD BUG: kern_symlink is not setting VLNK */ + MPASS(vap->va_type == VLNK); +#else + vap->va_type = VLNK; +#endif + + return tmpfs_alloc_file(dvp, vpp, vap, cnp, target); +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_readdir(struct vop_readdir_args *v) +{ + struct vnode *vp = v->a_vp; + struct uio *uio = v->a_uio; + int *eofflag = v->a_eofflag; + u_long **cookies = v->a_cookies; + int *ncookies = v->a_ncookies; + + int error; + off_t startoff; + off_t cnt; + struct tmpfs_node *node; + + /* This operation only makes sense on directory nodes. */ + if (vp->v_type != VDIR) { + error = ENOTDIR; + goto out; + } + + node = VP_TO_TMPFS_DIR(vp); + + startoff = uio->uio_offset; + + cnt = 0; + if (uio->uio_offset == TMPFS_DIRCOOKIE_DOT) { + error = tmpfs_dir_getdotdent(node, uio); + if (error == -1) { + error = 0; + goto outok; + } else if (error != 0) + goto outok; + cnt++; + } + + if (uio->uio_offset == TMPFS_DIRCOOKIE_DOTDOT) { + error = tmpfs_dir_getdotdotdent(node, uio); + if (error == -1) { + error = 0; + goto outok; + } else if (error != 0) + goto outok; + cnt++; + } + + error = tmpfs_dir_getdents(node, uio, &cnt); + if (error == -1) + error = 0; + MPASS(error >= 0); + +outok: + /* This label assumes that startoff has been + * initialized. If the compiler didn't spit out warnings, we'd + * simply make this one be 'out' and drop 'outok'. */ + + if (eofflag != NULL) + *eofflag = + (error == 0 && uio->uio_offset == TMPFS_DIRCOOKIE_EOF); + + /* Update NFS-related variables. */ + if (error == 0 && cookies != NULL && ncookies != NULL) { + off_t i; + off_t off = startoff; + struct tmpfs_dirent *de = NULL; + + *ncookies = cnt; + *cookies = malloc(cnt * sizeof(off_t), M_TEMP, M_WAITOK); + + for (i = 0; i < cnt; i++) { + MPASS(off != TMPFS_DIRCOOKIE_EOF); + if (off == TMPFS_DIRCOOKIE_DOT) { + off = TMPFS_DIRCOOKIE_DOTDOT; + } else { + if (off == TMPFS_DIRCOOKIE_DOTDOT) { + de = TAILQ_FIRST(&node->tn_dir.tn_dirhead); + } else if (de != NULL) { + de = TAILQ_NEXT(de, td_entries); + } else { + de = tmpfs_dir_lookupbycookie(node, + off); + MPASS(de != NULL); + de = TAILQ_NEXT(de, td_entries); + } + if (de == NULL) { + off = TMPFS_DIRCOOKIE_EOF; + } else { + off = TMPFS_DIRCOOKIE(de); + } + } + + (*cookies)[i] = off; + } + MPASS(uio->uio_offset == off); + } + +out: + return error; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_readlink(struct vop_readlink_args *v) +{ + struct vnode *vp = v->a_vp; + struct uio *uio = v->a_uio; + + int error; + struct tmpfs_node *node; + + MPASS(uio->uio_offset == 0); + MPASS(vp->v_type == VLNK); + + node = VP_TO_TMPFS_NODE(vp); + + error = uiomove(node->tn_link, MIN(node->tn_size, uio->uio_resid), + uio); + node->tn_status |= TMPFS_NODE_ACCESSED; + + return error; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_inactive(struct vop_inactive_args *v) +{ + struct vnode *vp = v->a_vp; + struct thread *l = v->a_td; + + struct tmpfs_node *node; + + MPASS(VOP_ISLOCKED(vp, l)); + + node = VP_TO_TMPFS_NODE(vp); + + if (node->tn_links == 0) + vrecycle(vp, l); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_reclaim(struct vop_reclaim_args *v) +{ + struct vnode *vp = v->a_vp; + + struct tmpfs_mount *tmp; + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + tmp = VFS_TO_TMPFS(vp->v_mount); + + vnode_destroy_vobject(vp); + cache_purge(vp); + tmpfs_free_vp(vp); + + /* If the node referenced by this vnode was deleted by the user, + * we must free its associated data structures (now that the vnode + * is being reclaimed). */ + if (node->tn_links == 0) + tmpfs_free_node(tmp, node); + + MPASS(vp->v_data == NULL); + return 0; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_print(struct vop_print_args *v) +{ + struct vnode *vp = v->a_vp; + + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + + printf("tag VT_TMPFS, tmpfs_node %p, flags 0x%x, links %d\n", + node, node->tn_flags, node->tn_links); + printf("\tmode 0%o, owner %d, group %d, size %" PRIdMAX + ", status 0x%x\n", + node->tn_mode, node->tn_uid, node->tn_gid, + (uintmax_t)node->tn_size, node->tn_status); + + if (vp->v_type == VFIFO) + fifo_printinfo(vp); + + printf("\n"); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_pathconf(struct vop_pathconf_args *v) +{ + int name = v->a_name; + register_t *retval = v->a_retval; + + int error; + + error = 0; + + switch (name) { + case _PC_LINK_MAX: + *retval = LINK_MAX; + break; + + case _PC_NAME_MAX: + *retval = NAME_MAX; + break; + + case _PC_PATH_MAX: + *retval = PATH_MAX; + break; + + case _PC_PIPE_BUF: + *retval = PIPE_BUF; + break; + + case _PC_CHOWN_RESTRICTED: + *retval = 1; + break; + + case _PC_NO_TRUNC: + *retval = 1; + break; + + case _PC_SYNC_IO: + *retval = 1; + break; + + case _PC_FILESIZEBITS: + *retval = 0; /* XXX Don't know which value should I return. */ + break; + + default: + error = EINVAL; + } + + return error; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_advlock(struct vop_advlock_args *v) +{ + struct vnode *vp = v->a_vp; + + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + + return lf_advlock(v, &node->tn_lockf, node->tn_size); +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_vptofh(struct vop_vptofh_args *ap) +{ + struct tmpfs_fid *tfhp; + struct tmpfs_node *node; + + tfhp = (struct tmpfs_fid *)ap->a_fhp; + node = VP_TO_TMPFS_NODE(ap->a_vp); + + tfhp->tf_len = sizeof(struct tmpfs_fid); + tfhp->tf_id = node->tn_id; + tfhp->tf_gen = node->tn_gen; + + return (0); +} diff --git a/sys/fs/tmpfs/tmpfs_vnops.h b/sys/fs/tmpfs/tmpfs_vnops.h new file mode 100644 index 0000000..dec3b05 --- /dev/null +++ b/sys/fs/tmpfs/tmpfs_vnops.h @@ -0,0 +1,85 @@ +/* $NetBSD: tmpfs_vnops.h,v 1.7 2005/12/03 17:34:44 christos Exp $ */ + +/* + * Copyright (c) 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _FS_TMPFS_TMPFS_VNOPS_H_ +#define _FS_TMPFS_TMPFS_VNOPS_H_ + +#if !defined(_KERNEL) +#error not supposed to be exposed to userland. +#endif + +/* --------------------------------------------------------------------- */ + +/* + * Declarations for tmpfs_vnops.c. + */ + +extern struct vop_vector tmpfs_vnodeop_entries; + +vop_cachedlookup_t tmpfs_lookup; +vop_create_t tmpfs_create; +vop_mknod_t tmpfs_mknod; +vop_open_t tmpfs_open; +vop_close_t tmpfs_close; +vop_access_t tmpfs_access; +vop_getattr_t tmpfs_getattr; +vop_setattr_t tmpfs_setattr; +vop_read_t tmpfs_read; +vop_write_t tmpfs_write; +vop_fsync_t tmpfs_fsync; +vop_remove_t tmpfs_remove; +vop_link_t tmpfs_link; +vop_rename_t tmpfs_rename; +vop_mkdir_t tmpfs_mkdir; +vop_rmdir_t tmpfs_rmdir; +vop_symlink_t tmpfs_symlink; +vop_readdir_t tmpfs_readdir; +vop_readlink_t tmpfs_readlink; +vop_inactive_t tmpfs_inactive; +vop_reclaim_t tmpfs_reclaim; +vop_print_t tmpfs_print; +vop_pathconf_t tmpfs_pathconf; +vop_advlock_t tmpfs_advlock; +vop_vptofh_t tmpfs_vptofh; + +/* --------------------------------------------------------------------- */ + +#endif /* _FS_TMPFS_TMPFS_VNOPS_H_ */ diff --git a/sys/modules/Makefile b/sys/modules/Makefile index e0522ba..e707e93 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -250,6 +250,7 @@ SUBDIR= ${_3dfx} \ sysvipc \ ti \ tl \ + ${_tmpfs} \ trm \ ${_twa} \ twe \ @@ -409,6 +410,7 @@ _sppp= sppp _sr= sr _stg= stg _streams= streams +_tmpfs= tmpfs _wi= wi _xe= xe .if ${MK_ZFS} != "no" || defined(ALL_MODULES) @@ -510,6 +512,7 @@ _smbfs= smbfs _sound= sound _speaker= speaker _sppp= sppp +_tmpfs= tmpfs _twa= twa _wi= wi .if ${MK_ZFS} != "no" || defined(ALL_MODULES) diff --git a/sys/modules/tmpfs/Makefile b/sys/modules/tmpfs/Makefile new file mode 100644 index 0000000..84feb88 --- /dev/null +++ b/sys/modules/tmpfs/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../fs/tmpfs + +KMOD= tmpfs +SRCS= vnode_if.h \ + tmpfs_vnops.c tmpfs_fifoops.c tmpfs_vfsops.c tmpfs_subr.c \ + tmpfs_uma.c + +.include <bsd.kmod.mk> |