diff options
author | Ian Moffett <ian@osmora.org> | 2025-06-27 19:06:14 -0400 |
---|---|---|
committer | Ian Moffett <ian@osmora.org> | 2025-06-27 19:06:14 -0400 |
commit | eb829b70db46ad86a1c85740f5ca02b7ba29ce4f (patch) | |
tree | 7e2241ae22991057ddf5c5470dcff1ce1f215658 | |
parent | bbd861c0cd4c26e2699392fec3ae0ec7df8ab145 (diff) |
kernel: Add initial support for tmpfs
Introduce the initial support for tmpfs, a readable and writable in-memory
filesystem.
Signed-off-by: Ian Moffett <ian@osmora.org>
-rw-r--r-- | share/misc/tmpfs | 15 | ||||
-rw-r--r-- | sys/fs/tmpfs.c | 390 | ||||
-rw-r--r-- | sys/include/fs/tmpfs.h | 73 | ||||
-rw-r--r-- | sys/include/sys/mount.h | 2 | ||||
-rw-r--r-- | sys/kern/vfs_init.c | 3 |
5 files changed, 482 insertions, 1 deletions
diff --git a/share/misc/tmpfs b/share/misc/tmpfs new file mode 100644 index 0000000..38eac22 --- /dev/null +++ b/share/misc/tmpfs @@ -0,0 +1,15 @@ +---------------------------------------------------- +| Readable + writable in-memory filesystem (tmpfs) | ++--------------------------------------------------+ +| Author: Ian Marco Moffett | ++--------------------------------------------------+ + +------------------------------------------------------------------------------ + + [d] /tmp/ -> [next] -> foo.txt -> [next] -> bar.txt + \ + +> /tmp/noises/ -> [next] -> mrow.txt -> [next] -> squeak.txt + \ + +> /tmp/noises1/ -> [next] -> bark.bin -> [next] -> wrff.txt + +------------------------------------------------------------------------------ diff --git a/sys/fs/tmpfs.c b/sys/fs/tmpfs.c new file mode 100644 index 0000000..9dce89a --- /dev/null +++ b/sys/fs/tmpfs.c @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team. + * 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, + * 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. Neither the name of Hyra 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. + */ + +#include <sys/mount.h> +#include <sys/errno.h> +#include <sys/syslog.h> +#include <sys/types.h> +#include <sys/cdefs.h> +#include <sys/param.h> +#include <sys/panic.h> +#include <sys/vnode.h> +#include <vm/dynalloc.h> +#include <vm/vm_obj.h> +#include <vm/vm_page.h> +#include <vm/vm_pager.h> +#include <fs/tmpfs.h> +#include <string.h> + +#define ROOT_RPATH "/tmp" +#define TMPFS_BSIZE DEFAULT_PAGESIZE + +#define pr_trace(fmt, ...) kprintf("tmpfs: " fmt, ##__VA_ARGS__) +#define pr_error(...) pr_trace(__VA_ARGS__) + +static TAILQ_HEAD(, tmpfs_node) root; + +/* + * Generate a vnode for a specific tmpfs + * node. + */ +static int +tmpfs_ref(struct tmpfs_node *np) +{ + struct vnode *vp = NULL; + int retval = 0; + + if (np->vp == NULL) { + spinlock_acquire(&np->lock); + retval = vfs_alloc_vnode(&vp, np->type); + np->vp = vp; + spinlock_release(&np->lock); + } + + if (vp != NULL) { + vp->data = np; + vp->vops = &g_tmpfs_vops; + } + + return retval; +} + +/* + * Perform lookup within the tmpfs namespace + * + * XXX: This operations is serialized + * TODO: Support multiple directories (only fs root now) + * + * @rpath: /tmp/ relative path to lookup + * @res: The result is written here (must NOT be NULL) + */ +static int +tmpfs_do_lookup(const char *rpath, struct tmpfs_node **res) +{ + struct tmpfs_node *cnp; + struct tmpfs_node *dirent; + int error = 0; + + /* + * If the directory is the node that we are + * looking for, return it. But if it is not + * and it is empty then there is nothing + * we can do. + */ + cnp = TAILQ_FIRST(&root); + if (strcmp(cnp->rpath, rpath) == 0) { + *res = cnp; + return 0; + } + if (TAILQ_NELEM(&cnp->dirents) == 0) { + return -ENOENT; + } + + /* + * Go through each tmpfs dirent to see if we can + * find the file we are looking for. + */ + spinlock_acquire(&cnp->lock); + dirent = TAILQ_FIRST(&cnp->dirents); + while (dirent != NULL) { + if (strcmp(dirent->rpath, rpath) == 0) { + break; + } + + dirent = TAILQ_NEXT(dirent, link); + } + + spinlock_release(&cnp->lock); + if (res == NULL) { + return -ENOENT; + } + + if ((error = tmpfs_ref(dirent)) != 0) { + return error; + } + + *res = dirent; + return 0; +} + +/* + * TMPFS lookup callback for the VFS + * + * Takes some arguments and returns a vnode + * in args->vpp + */ +static int +tmpfs_lookup(struct vop_lookup_args *args) +{ + struct tmpfs_node *np; + int error; + + if (args == NULL) { + return -EINVAL; + } + if (args->name == NULL) { + return -EINVAL; + } + + /* + * Attempt to find the node we want, if it already + * has a vnode attached to it then that's something we + * want. However we should allocate a new vnode if we + * need to. + */ + error = tmpfs_do_lookup(args->name, &np); + if (error != 0) { + return error; + } + + *args->vpp = np->vp; + return 0; +} + +/* + * TMPFS create callback for the VFS + * + * Creates a new TMPFS node + */ +static int +tmpfs_create(struct vop_create_args *args) +{ + const char *pcp = args->path; /* Stay away from boat, kids */ + struct vnode *dirvp; + struct tmpfs_node *np; + struct tmpfs_node *root_np; + int error; + + /* Validate inputs */ + if (args == NULL) + return -EINVAL; + if (pcp == NULL) + return -EIO; + if ((dirvp = args->dirvp) == NULL) + return -EIO; + + /* Remove the leading "/tmp/" */ + pcp += sizeof(ROOT_RPATH); + if (*pcp == '\0') { + return -ENOENT; + } + + np = dynalloc(sizeof(*np)); + if (np == NULL) { + return -ENOMEM; + } + + memset(np, 0, sizeof(*np)); + + /* + * TODO: Support multiple directories. + * + * XXX: We currently only create a TMPFS_REG node as + * to keep things initially simple. + */ + root_np = TAILQ_FIRST(&root); + np->dirvp = dirvp; + np->type = TMPFS_REG; + memcpy(np->rpath, pcp, strlen(pcp) + 1); + TAILQ_INSERT_TAIL(&root_np->dirents, np, link); + + if ((error = tmpfs_ref(np)) != 0) { + return error; + } + + *args->vpp = np->vp; + return 0; +} + +/* + * TMPFS write callback for VFS + * + * Node buffers are orthogonally managed. That is, each + * node has their own respective data buffers. When + * writing to a node, we need to take into account of the + * length of the buffer. This value may need to expanded as + * well as more pages allocated if the amount of bytes to + * be written exceeds it. + */ +static int +tmpfs_write(struct vnode *vp, struct sio_txn *sio) +{ + struct tmpfs_node *np; + uint8_t *buf; + + if (sio->buf == NULL || sio->len == 0) { + return -EINVAL; + } + + /* This should not happen but you never know */ + if ((np = vp->data) == NULL) { + return -EIO; + } + + /* Is this even a regular file? */ + if (np->type != VREG) { + return -EISDIR; + } + + spinlock_acquire(&np->lock); + + /* + * If the residual byte count is zero, we need to + * allocate a new page to be used. However if this + * fails we'll throw back an -ENOMEM. + */ + if (np->len == 0) { + np->data = dynalloc(TMPFS_BSIZE); + if (np->data == NULL) { + spinlock_release(&np->lock); + return -ENOMEM; + } + np->len += TMPFS_BSIZE; + } + + /* + * If the length to be written exceeds the residual byte + * count. We will try to expand the buffer by the page + * size. However, if this fails, we will split the write + * into a suitable size that does not overflow what we + * have left. + */ + if ((sio->offset + sio->len) > np->len) { + np->data = dynrealloc(np->data, (sio->offset + sio->len)); + if (np->data == NULL) { + sio->len = np->len; + } else { + np->len = sio->offset + sio->len; + } + } + + buf = np->data; + memcpy(&buf[sio->offset], sio->buf, sio->len); + spinlock_release(&np->lock); + kprintf("%d\n", sio->len); + return sio->len; +} + +/* + * TMPFS read callback for VFS + */ +static int +tmpfs_read(struct vnode *vp, struct sio_txn *sio) +{ + struct tmpfs_node *np; + uint8_t *buf; + + if (sio->buf == NULL || sio->len == 0) { + return -EINVAL; + } + + /* This should not happen but you never know */ + if ((np = vp->data) == NULL) { + return -EIO; + } + + /* Is this even a regular file? */ + if (np->type != VREG) { + return -EISDIR; + } + + spinlock_acquire(&np->lock); + + if (sio->offset > np->len - 1) { + return -EINVAL; + } + if ((sio->offset + sio->len) > np->len) { + sio->len = np->len; + } + + buf = np->data; + memcpy(sio->buf, &buf[sio->offset], sio->len); + spinlock_release(&np->lock); + return sio->len; +} + +static int +tmpfs_reclaim(struct vnode *vp) +{ + struct tmpfs_node *np; + + if ((np = vp->data) == NULL) { + return 0; + } + + np->vp = NULL; + vp->data = NULL; + return 0; +} + +static int +tmpfs_init(struct fs_info *fip) +{ + struct tmpfs_node *np; + struct vnode *vp; + struct mount *mp; + int error; + + /* Grab ourselves a new vnode for /tmp */ + if ((error = vfs_alloc_vnode(&vp, VDIR)) != 0) { + return error; + } + + vp->vops = &g_tmpfs_vops; + mp = vfs_alloc_mount(vp, fip); + vfs_name_mount(mp, "tmp"); + TAILQ_INSERT_TAIL(&g_mountlist, mp, mnt_list); + + /* Pre-allocate the first entry */ + if ((np = dynalloc(sizeof(*np))) == NULL) { + return -ENOMEM; + } + + TAILQ_INIT(&root); + memset(np, 0, sizeof(*np)); + + memcpy(np->rpath, ROOT_RPATH, sizeof(ROOT_RPATH)); + np->type = TMPFS_DIR; + TAILQ_INIT(&np->dirents); + TAILQ_INSERT_TAIL(&root, np, link); + return 0; +} + +const struct vops g_tmpfs_vops = { + .lookup = tmpfs_lookup, + .getattr = NULL, + .read = tmpfs_read, + .write = tmpfs_write, + .reclaim = tmpfs_reclaim, + .create = tmpfs_create, +}; + +const struct vfsops g_tmpfs_vfsops = { + .init = tmpfs_init +}; diff --git a/sys/include/fs/tmpfs.h b/sys/include/fs/tmpfs.h new file mode 100644 index 0000000..b2a5bbe --- /dev/null +++ b/sys/include/fs/tmpfs.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team. + * 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, + * 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. Neither the name of Hyra 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. + */ + +#ifndef _FS_TMPFS_H_ +#define _FS_TMPFS_H_ + +#include <sys/types.h> +#include <sys/limits.h> +#include <sys/vnode.h> +#include <sys/queue.h> +#include <sys/spinlock.h> +#include <vm/vm_obj.h> + +extern const struct vops g_tmpfs_vops; + +/* Tmpfs node types */ +#define TMPFS_NONE (VNON) /* No type */ +#define TMPFS_REG (VREG) /* Regular file [f] */ +#define TMPFS_DIR (VDIR) /* Directory [d] */ + +struct tmpfs_node; + +/* + * A tmpfs node represents an object within the + * tmpfs namespace such as a file, directory, etc. + * + * @rpath: /tmp/ relative path (for lookups) + * @type: The tmpfs node type [one-to-one to vtype] + * @len: Length of buffer + * @data: The backing file data + * @dirvp: Vnode of the parent node + * @vp: Vnode of the current node + * @lock: Lock protecting this node + */ +struct tmpfs_node { + char rpath[PATH_MAX]; + uint8_t type; + size_t len; + void *data; + struct vnode *dirvp; + struct vnode *vp; + struct spinlock lock; + TAILQ_HEAD(, tmpfs_node) dirents; + TAILQ_ENTRY(tmpfs_node) link; +}; + +#endif /* !_FS_TMPFS_H_ */ diff --git a/sys/include/sys/mount.h b/sys/include/sys/mount.h index 1fcdbfa..636c7bf 100644 --- a/sys/include/sys/mount.h +++ b/sys/include/sys/mount.h @@ -47,6 +47,7 @@ #define MOUNT_RAMFS "initramfs" #define MOUNT_DEVFS "devfs" #define MOUNT_CTLFS "ctlfs" +#define MOUNT_TMPFS "tmpfs" struct vfsops; struct mount; @@ -59,6 +60,7 @@ extern mountlist_t g_mountlist; extern const struct vfsops g_initramfs_vfsops; extern const struct vfsops g_devfs_vfsops; extern const struct vfsops g_ctlfs_vfsops; +extern const struct vfsops g_tmpfs_vfsops; struct mount { char *name; diff --git a/sys/kern/vfs_init.c b/sys/kern/vfs_init.c index 8c1bc74..bc7f8b0 100644 --- a/sys/kern/vfs_init.c +++ b/sys/kern/vfs_init.c @@ -37,7 +37,8 @@ struct vnode *g_root_vnode = NULL; static struct fs_info fs_list[] = { {MOUNT_RAMFS, &g_initramfs_vfsops, 0, 0}, {MOUNT_DEVFS, &g_devfs_vfsops, 0, 0}, - {MOUNT_CTLFS, &g_ctlfs_vfsops, 0, 0} + {MOUNT_CTLFS, &g_ctlfs_vfsops, 0, 0}, + {MOUNT_TMPFS, &g_tmpfs_vfsops, 0, 0} }; void |