summaryrefslogtreecommitdiff
path: root/src/cmd/tmpfs.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/tmpfs.c')
-rw-r--r--src/cmd/tmpfs.c198
1 files changed, 198 insertions, 0 deletions
diff --git a/src/cmd/tmpfs.c b/src/cmd/tmpfs.c
new file mode 100644
index 0000000..6d58790
--- /dev/null
+++ b/src/cmd/tmpfs.c
@@ -0,0 +1,198 @@
+#include <camellia/flags.h>
+#include <camellia/fs/dir.h>
+#include <camellia/fs/misc.h>
+#include <camellia/fsutil.h>
+#include <camellia/syscalls.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct node {
+ char *name;
+ size_t namelen;
+ bool directory;
+ char *buf;
+ size_t size, capacity;
+
+ size_t open; /* amount of open handles */
+
+ struct node *sibling, *child;
+ /* Pointer to the sibling/child field referencing the node. NULL for removed
+ * files. */
+ struct node **ref;
+
+ /* allocated by tmpfs_open
+ * freed by node_close when (open == 0 && ref == NULL && self != node_root). */
+};
+
+static struct node node_root = {
+ .directory = true,
+};
+
+/** Responds to open(), finding an existing node, or creating one when applicable. */
+static struct node *tmpfs_open(const char *path, struct ufs_request *req);
+/** Finds a direct child with the given name. */
+static struct node *node_getchild(struct node *parent, const char *name, size_t len);
+/** Corresponds to close(); drops a reference. */
+static void node_close(struct node *node);
+/** Removes a file. It's kept in memory until all the open handles are closed. */
+static long node_remove(struct node *node);
+
+
+static struct node *node_getchild(struct node *parent, const char *name, size_t len) {
+ for (struct node *iter = parent->child; iter; iter = iter->sibling) {
+ if (iter->namelen == len && !memcmp(name, iter->name, len)) {
+ return iter;
+ }
+ }
+ return NULL;
+}
+
+static struct node *tmpfs_open(const char *path, struct ufs_request *req) {
+ /* *path is not null terminated! */
+ struct node *node = &node_root;
+ if (req->len == 0) return NULL;
+ if (req->len == 1) return node; /* "/" */
+ path++;
+ req->len--;
+
+ bool more = true;
+ size_t segpos = 0, seglen; /* segments end with a slash, inclusive */
+ while (more) {
+ struct node *parent = node;
+ char *slash = memchr(path + segpos, '/', req->len - segpos);
+ seglen = (slash ? (size_t)(slash - path + 1) : req->len) - segpos;
+ more = segpos + seglen < req->len;
+
+ node = node_getchild(parent, path + segpos, seglen);
+ if (!node) {
+ if (!more && (req->flags & OPEN_CREATE)) {
+ node = calloc(1, sizeof *node);
+
+ node->name = malloc(seglen + 1);
+ if (node->name == NULL) {
+ free(node);
+ return NULL;
+ }
+ memcpy(node->name, path + segpos, seglen);
+ node->name[seglen] = '\0';
+
+ node->directory = slash;
+ node->namelen = seglen;
+ if (parent->child) {
+ parent->child->ref = &node->sibling;
+ *parent->child->ref = parent->child;
+ }
+ node->ref = &parent->child;
+ *node->ref = node;
+ } else {
+ return NULL;
+ }
+ }
+ segpos += seglen;
+ }
+ node->open++;
+ return node;
+}
+
+static void node_close(struct node *node) {
+ node->open--;
+ if (!node->ref && node != &node_root && node->open == 0) {
+ free(node->buf);
+ free(node);
+ }
+}
+
+static long node_remove(struct node *node) {
+ if (node == &node_root) return -1;
+ if (!node->ref) return -1;
+ if (node->child) return -ENOTEMPTY;
+ *node->ref = node->sibling;
+ node->ref = NULL;
+ node_close(node);
+ return 0;
+}
+
+int main(void) {
+ const size_t buflen = 4096;
+ char *buf = malloc(buflen);
+ if (!buf) return -1;
+
+ for (;;) {
+ struct ufs_request req;
+ hid_t reqh = ufs_wait(buf, buflen, &req);
+ struct node *ptr = req.id;
+ if (reqh < 0) break;
+
+ switch (req.op) {
+ case VFSOP_OPEN:
+ ptr = tmpfs_open(buf, &req);
+ _sys_fs_respond(reqh, ptr, ptr ? 0 : -ENOENT, 0);
+ break;
+
+ case VFSOP_READ:
+ if (ptr->directory) {
+ struct dirbuild db;
+ dir_start(&db, req.offset, buf, buflen);
+ for (struct node *iter = ptr->child; iter; iter = iter->sibling) {
+ dir_appendl(&db, iter->name, iter->namelen);
+ }
+ _sys_fs_respond(reqh, buf, dir_finish(&db), 0);
+ } else {
+ fs_normslice(&req.offset, &req.len, ptr->size, false);
+ _sys_fs_respond(reqh, ptr->buf + req.offset, req.len, 0);
+ }
+ break;
+
+ case VFSOP_WRITE:
+ if (ptr->directory) {
+ _sys_fs_respond(reqh, NULL, -ENOSYS, 0);
+ break;
+ }
+
+ fs_normslice(&req.offset, &req.len, ptr->size, true);
+ if (ptr->capacity <= req.offset + req.len) {
+ ptr->capacity = (req.offset + req.len + 511) & ~511;
+ ptr->buf = realloc(ptr->buf, ptr->capacity);
+ }
+
+ memcpy(ptr->buf + req.offset, buf, req.len);
+ if ((req.flags & WRITE_TRUNCATE) || ptr->size < req.offset + req.len) {
+ ptr->size = req.offset + req.len;
+ }
+ _sys_fs_respond(reqh, NULL, req.len, 0);
+ break;
+
+ case VFSOP_GETSIZE:
+ if (ptr->directory) {
+ // TODO could be cached in ptr->size
+ struct dirbuild db;
+ dir_start(&db, req.offset, NULL, buflen);
+ for (struct node *iter = ptr->child; iter; iter = iter->sibling) {
+ dir_append(&db, iter->name);
+ }
+ _sys_fs_respond(reqh, NULL, dir_finish(&db), 0);
+ } else {
+ _sys_fs_respond(reqh, NULL, ptr->size, 0);
+ }
+ break;
+
+ case VFSOP_REMOVE:
+ _sys_fs_respond(reqh, NULL, node_remove(ptr), 0);
+ break;
+
+ case VFSOP_CLOSE:
+ node_close(ptr);
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+
+ default:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ }
+ }
+ return 1;
+}