summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Moffett <ian@osmora.org>2025-06-27 19:06:14 -0400
committerIan Moffett <ian@osmora.org>2025-06-27 19:06:14 -0400
commiteb829b70db46ad86a1c85740f5ca02b7ba29ce4f (patch)
tree7e2241ae22991057ddf5c5470dcff1ce1f215658
parentbbd861c0cd4c26e2699392fec3ae0ec7df8ab145 (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/tmpfs15
-rw-r--r--sys/fs/tmpfs.c390
-rw-r--r--sys/include/fs/tmpfs.h73
-rw-r--r--sys/include/sys/mount.h2
-rw-r--r--sys/kern/vfs_init.c3
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