diff --git a/sys/conf/files b/sys/conf/files index d92b22a3f79..a5fafa573e7 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 21e658b76cb..d2250370618 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 00000000000..2947b9bfae7 --- /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 +#include +#include +#include +#include +#include +#include + +/* --------------------------------------------------------------------- */ +#include +#include +#include +#include + +MALLOC_DECLARE(M_TMPFSMNT); + +#include + +/* --------------------------------------------------------------------- */ + +/* + * 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 00000000000..2ce15d2a6c7 --- /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 + __FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +/* --------------------------------------------------------------------- */ + +/* + * 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 00000000000..134018f698d --- /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 + +/* --------------------------------------------------------------------- */ + +/* + * 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 00000000000..b8e9a3248b5 --- /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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +/* --------------------------------------------------------------------- */ + +/* + * 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 00000000000..73066e1d720 --- /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 +__FBSDID("$FreeBSD$"); + +#include +#include + +#include + +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 00000000000..a402a4f6aa2 --- /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 + +#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 00000000000..e5d3d6ed418 --- /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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* + * 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 00000000000..a3711b6bb3a --- /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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* --------------------------------------------------------------------- */ + +/* + * 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 00000000000..dec3b05f8ea --- /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 e0522ba43dd..e707e9363ca 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 00000000000..84feb88830f --- /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