summaryrefslogtreecommitdiff
path: root/src/libc/fs
diff options
context:
space:
mode:
authordzwdz2023-08-14 18:51:07 +0200
committerdzwdz2023-08-14 18:51:07 +0200
commit642b5fb0007b64c77d186fcb018d571152ee1d47 (patch)
tree1c466461f3602d306be309a053edae558ef2568e /src/libc/fs
parent8050069c57b729c18c19b1a03ab6e4bf63b4735e (diff)
reorganization: first steps
Diffstat (limited to 'src/libc/fs')
-rw-r--r--src/libc/fs/dir.c84
-rw-r--r--src/libc/fs/dirinject.c129
-rw-r--r--src/libc/fs/misc.c160
-rw-r--r--src/libc/fs/whitelist.c114
4 files changed, 487 insertions, 0 deletions
diff --git a/src/libc/fs/dir.c b/src/libc/fs/dir.c
new file mode 100644
index 0000000..b7f840d
--- /dev/null
+++ b/src/libc/fs/dir.c
@@ -0,0 +1,84 @@
+#include <camellia/syscalls.h>
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <camellia/fs/dir.h>
+
+void dir_start(struct dirbuild *db, long offset, char *buf, size_t buflen) {
+ db->offset = offset;
+ db->buf = buf;
+ db->bpos = 0;
+ db->blen = buflen;
+ db->error = 0;
+
+ // TODO decide how negative directory offsets should be handled
+ if (offset < 0) db->error = -ENOSYS;
+}
+
+bool dir_append(struct dirbuild *db, const char *name) {
+ return dir_appendl(db, name, strlen(name));
+}
+
+bool dir_appendl(struct dirbuild *db, const char *name, size_t len) {
+ if (db->error) return true;
+ if (len > (size_t)LONG_MAX) {
+ db->error = -1;
+ return true;
+ }
+
+ len++; // account for the null byte
+
+ if (db->offset < (long)len) {
+ name += db->offset;
+ len -= db->offset;
+ db->offset = 0;
+
+ if (db->buf) {
+ // TODO no buffer overrun check
+ memcpy(db->buf + db->bpos, name, len - 1);
+ db->buf[db->bpos + len - 1] = '\0';
+ }
+ db->bpos += len;
+ } else {
+ db->offset -= len;
+ }
+ return false;
+}
+
+bool dir_append_from(struct dirbuild *db, hid_t h) {
+ if (db->error) return true;
+ if (db->buf && db->bpos == db->blen) return false;
+
+ int ret;
+ if (db->buf) {
+ ret = _sys_read(h, db->buf + db->bpos, db->blen - db->bpos, db->offset);
+ if (ret < 0) {
+ db->error = ret;
+ return true;
+ } else if (ret > 0) {
+ /* not eof */
+ db->offset = 0;
+ db->bpos += ret;
+ return false;
+ } /* else ret == 0, EOF, need getsize */
+ }
+
+ ret = _sys_getsize(h);
+ if (ret < 0) {
+ db->error = ret;
+ return true;
+ }
+ if (db->offset < ret) {
+ /* should only occur when !buf, otherwise leaks previous data from buf.
+ * TODO consider impact */
+ db->bpos += ret - db->offset;
+ db->offset = 0;
+ } else {
+ db->offset -= ret;
+ }
+ return false;
+}
+
+long dir_finish(struct dirbuild *db) {
+ return db->error ? db->error : db->bpos;
+}
diff --git a/src/libc/fs/dirinject.c b/src/libc/fs/dirinject.c
new file mode 100644
index 0000000..9b08756
--- /dev/null
+++ b/src/libc/fs/dirinject.c
@@ -0,0 +1,129 @@
+#include <assert.h>
+#include <camellia/fs/dir.h>
+#include <camellia/fs/misc.h>
+#include <camellia/syscalls.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/param.h>
+#include <unistd.h>
+
+typedef struct Handle {
+ int delegate;
+ int plen;
+ char path[];
+} Handle;
+
+static int
+dir_seglen(const char *path)
+{
+ /* if path contains /, return its position + 1
+ * otherwise, return strlen */
+ int len = 0;
+ while (path[len]) {
+ if (path[len] == '/') {
+ len++;
+ break;
+ }
+ len++;
+ }
+ return len;
+}
+
+static int
+find_injects(const char *injects[], const char *path, int plen, struct dirbuild *db)
+{
+ // TODO deduplicate
+ const char *inj;
+ int matches = 0;
+ assert(plen >= 1);
+ assert(path[plen-1] == '/');
+
+ while ((inj = *injects++)) {
+ int ilen = strlen(inj);
+ if (plen < ilen && memcmp(path, inj, plen) == 0) {
+ if (db) {
+ /* inj[plen-1] == '/' */
+ const char *ent = inj + plen;
+ dir_appendl(db, ent, dir_seglen(ent));
+ }
+ matches++;
+ }
+ }
+ return matches;
+}
+
+void
+fs_dirinject2(const char *injects[])
+{
+ const size_t buflen = 4096;
+ char *buf = malloc(buflen);
+ if (!buf) exit(1);
+
+ for (;;) {
+ struct ufs_request req;
+ hid_t reqh = _sys_fs_wait(buf, buflen, &req);
+ if (reqh < 0) break;
+ Handle *hndl = req.id;
+ switch (req.op) {
+ case VFSOP_OPEN: {
+ if (buf[req.len - 1] == '/') {
+ if (find_injects(injects, buf, req.len, NULL) > 0) {
+ /* opening a directory that we're injecting into */
+ hndl = malloc(sizeof(Handle) + req.len);
+ if (hndl == NULL) {
+ _sys_fs_respond(reqh, NULL, -EGENERIC, 0);
+ break;
+ }
+ /* ignore errors from _sys_open */
+ hndl->delegate = _sys_open(buf, req.len, req.flags);
+ hndl->plen = req.len;
+ memcpy(hndl->path, buf, hndl->plen);
+ _sys_fs_respond(reqh, hndl, 0, 0);
+ break;
+ }
+ }
+ /* default behaviour */
+ forward_open(reqh, buf, req.len, req.flags);
+ break;
+ }
+
+ case VFSOP_CLOSE: {
+ if (hndl->delegate >= 0) {
+ close(hndl->delegate);
+ }
+ free(hndl);
+ _sys_fs_respond(reqh, NULL, 0, 0);
+ break;
+ }
+
+ case VFSOP_READ:
+ case VFSOP_GETSIZE: {
+ struct dirbuild db;
+ char *target = NULL;
+ if (req.op == VFSOP_READ) {
+ target = buf;
+ }
+ req.capacity = MIN(req.capacity, buflen);
+
+ dir_start(&db, req.offset, target, req.capacity);
+ find_injects(injects, hndl->path, hndl->plen, &db);
+ if (hndl->delegate >= 0) {
+ dir_append_from(&db, hndl->delegate);
+ }
+ _sys_fs_respond(reqh, target, dir_finish(&db), 0);
+ break;
+ }
+
+ default: {
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ }
+ }
+ }
+ exit(0);
+}
+
+void
+fs_dirinject(const char *path) {
+ fs_dirinject2((const char*[]){ path, NULL });
+}
diff --git a/src/libc/fs/misc.c b/src/libc/fs/misc.c
new file mode 100644
index 0000000..30e5ab4
--- /dev/null
+++ b/src/libc/fs/misc.c
@@ -0,0 +1,160 @@
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <camellia/compat.h>
+#include <camellia/fs/dir.h>
+#include <camellia/fs/misc.h>
+
+void forward_open(hid_t reqh, const char *path, long len, int flags) {
+ // TODO use threads
+ // TODO solve for more complex cases, e.g. fs_union
+ /* done in a separate thread/process because open() can block,
+ * but that should only hold the caller back, and not the fs driver.
+ *
+ * for example, running `httpd` in one term would prevent you from doing
+ * basically anything on the second term, because fs_dirinject would be
+ * stuck on open()ing the socket */
+ if (!_sys_fork(FORK_NOREAP, NULL)) {
+ _sys_fs_respond(reqh, NULL, _sys_open(path, len, flags), FSR_DELEGATE);
+ exit(0);
+ }
+ close(reqh);
+}
+
+void fs_passthru(const char *prefix) {
+ const size_t buflen = 1024;
+ char *buf = malloc(buflen);
+ int prefix_len = prefix ? strlen(prefix) : 0;
+ if (!buf) exit(1);
+
+ for (;;) {
+ struct ufs_request res;
+ hid_t reqh = _sys_fs_wait(buf, buflen, &res);
+ if (reqh < 0) break;
+ switch (res.op) {
+ case VFSOP_OPEN:
+ if (prefix) {
+ if (prefix_len + res.len > buflen) {
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ }
+
+ memmove(buf + prefix_len, buf, res.len);
+ memcpy(buf, prefix, prefix_len);
+ res.len += prefix_len;
+ }
+ forward_open(reqh, buf, res.len, res.flags);
+ break;
+
+ default:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ }
+ }
+ exit(0);
+}
+
+void fs_union(const char **list) {
+ struct ufs_request res;
+
+ /* the buffer is split into two halves:
+ * the second one is filled out with the path by fs_wait
+ * the first one is used for the prepended paths */
+ const size_t buflen = 1024;
+ const size_t prelen = 512;
+ const size_t postlen = buflen - prelen;
+ char *pre = malloc(buflen);
+ char *post = pre + prelen;
+ long ret;
+ struct dirbuild db;
+ if (!pre) exit(1);
+
+ while (!c0_fs_wait(post, postlen, &res)) {
+ switch (res.op) {
+ case VFSOP_OPEN:
+ if (res.len == 1) { /* root directory */
+ c0_fs_respond(NULL, 0, 0);
+ break;
+ }
+
+ ret = -1;
+ for (size_t i = 0; ret < 0 && list[i]; i++) {
+ const char *prefix = list[i];
+ size_t prefixlen = strlen(prefix); // TODO cache
+ if (prefixlen > prelen) continue;
+ char *path = post - prefixlen;
+ memcpy(path, prefix, prefixlen);
+
+ ret = _sys_open(path, prefixlen + res.len, res.flags);
+
+ post[res.len] = '\0';
+ }
+ if (ret < 0) ret = -1;
+ c0_fs_respond(NULL, ret, FSR_DELEGATE);
+ break;
+
+ case VFSOP_READ:
+ case VFSOP_GETSIZE:
+ if (res.capacity > buflen)
+ res.capacity = buflen;
+ bool end = false;
+ char *target = res.op == VFSOP_READ ? pre : NULL;
+ dir_start(&db, res.offset, target, res.capacity);
+ for (size_t i = 0; !end && list[i]; i++) {
+ const char *prefix = list[i];
+ size_t prefixlen = strlen(prefix);
+ // TODO only open the directories once
+ // TODO ensure trailing slash
+ hid_t h = _sys_open(prefix, prefixlen, OPEN_READ);
+ if (h < 0) continue;
+ end = end || dir_append_from(&db, h);
+ _sys_close(h);
+ }
+ c0_fs_respond(target, dir_finish(&db), 0);
+ break;
+
+ default:
+ c0_fs_respond(NULL, -1, 0);
+ break;
+ }
+ }
+ exit(0);
+}
+
+hid_t ufs_wait(char *buf, size_t len, struct ufs_request *req) {
+ hid_t reqh;
+ for (;;) {
+ reqh = _sys_fs_wait(buf, len, req);
+ if (reqh < 0) break;
+ if (req->op == VFSOP_OPEN) {
+ if (req->len == len) {
+ _sys_fs_respond(reqh, NULL, -ENAMETOOLONG, 0);
+ continue;
+ }
+ buf[req->len] = '\0';
+ // TODO ensure passed paths don't have null bytes in them in the kernel
+ }
+ break;
+ }
+ return reqh;
+}
+
+int mount_at(const char *path) {
+ hid_t h;
+ int ret = _sys_fork(FORK_NEWFS, &h);
+ if (ret == 0) {
+ _klogf("%s: impl", path);
+ setproctitle("%s", path);
+ } else if (ret > 0) {
+ _sys_mount(h, path, strlen(path));
+ close(h);
+ } else {
+ _sys_mount(HANDLE_NULLFS, path, strlen(path));
+ }
+ return ret;
+}
diff --git a/src/libc/fs/whitelist.c b/src/libc/fs/whitelist.c
new file mode 100644
index 0000000..54a79c3
--- /dev/null
+++ b/src/libc/fs/whitelist.c
@@ -0,0 +1,114 @@
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <camellia/fs/dir.h>
+#include <camellia/fs/misc.h>
+
+static int dir_seglen2(const char *path, size_t len) {
+ /* returns the length of the first segment of the path, including the trailing / (if any). */
+ for (size_t i = 0; i < len; i++) {
+ if (path[i] == '/')
+ return i + 1;
+ }
+ return len;
+}
+
+/** @return the length of the path w/o suffixes */
+static size_t suffix_parse(const char *path, size_t len, bool *ro_ptr) {
+ bool ro = false;
+ if (len >= 3 && !memcmp(path + len - 3, ":ro", 3)) {
+ ro = true;
+ len -= 3;
+ }
+ if (ro_ptr) *ro_ptr = ro;
+ return len;
+}
+
+/** Check if a path is a prefix of another path. */
+// TODO move to libc; tests
+static bool prefix_match(const char *prefix, size_t plen, const char *full, size_t flen) {
+ if (flen < plen) return false;
+ if (flen == plen)
+ return memcmp(full, prefix, flen) == 0;
+ return plen >= 1
+ && prefix[plen - 1] == '/' /* prefixes must point to one of the parent directories */
+ && memcmp(full, prefix, plen) == 0;
+}
+
+void fs_whitelist(const char **whitelist) {
+ const size_t buflen = 1024;
+ char *buf = malloc(buflen);
+ if (!buf) exit(1);
+ for (;;) {
+ struct ufs_request res;
+ hid_t reqh = _sys_fs_wait(buf, buflen, &res);
+ if (reqh < 0) break;
+
+ char *ipath = res.id; /* the path of the open()ed directory */
+
+ switch (res.op) {
+ case VFSOP_OPEN: {
+ bool error = false;
+ bool passthru = false;
+ bool inject = false;
+
+ for (const char **entry = whitelist; *entry; entry++) {
+ bool ro = false;
+ size_t entry_len = suffix_parse(*entry, strlen(*entry), &ro);
+ /* If *entry is a prefix of the opened path, pass the open() through. */
+ if (prefix_match(*entry, entry_len, buf, res.len)) {
+ passthru = true;
+ if (ro && OPEN_WRITEABLE(res.flags))
+ error = true;
+ break;
+ }
+ /* If the path is a prefix of *entry, we might need to inject a directory. */
+ if (prefix_match(buf, res.len, *entry, entry_len)) {
+ inject = true;
+ }
+ }
+ if (error) {
+ _sys_fs_respond(reqh, NULL, -EACCES, 0);
+ } else if (passthru) {
+ forward_open(reqh, buf, res.len, res.flags);
+ } else if (inject) {
+ // TODO all the inject points could be precomputed
+ ipath = malloc(res.len + 1);
+ memcpy(ipath, buf, res.len);
+ ipath[res.len] = '\0';
+ _sys_fs_respond(reqh, ipath, 0, 0);
+ } else {
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ }
+ break;
+ }
+ case VFSOP_READ:
+ case VFSOP_GETSIZE: {
+ struct dirbuild db;
+ size_t ilen = strlen(ipath);
+ char *target = res.op == VFSOP_READ ? buf : NULL;
+ dir_start(&db, res.offset, target, buflen);
+ for (const char **entry = whitelist; *entry; entry++) {
+ // TODO could be precomputed too
+ size_t elen = suffix_parse(*entry, strlen(*entry), NULL);
+ if (ilen < elen && !memcmp(ipath, *entry, ilen))
+ dir_appendl(&db, *entry + ilen, dir_seglen2(*entry + ilen, elen - ilen));
+ }
+ _sys_fs_respond(reqh, target, dir_finish(&db), 0);
+ break;
+ }
+ case VFSOP_CLOSE: {
+ free(ipath);
+ _sys_fs_respond(reqh, NULL, 0, 0);
+ break;
+ }
+ default: {
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ }
+ }
+ }
+ exit(0);
+}