diff options
author | dzwdz | 2023-08-14 18:51:07 +0200 |
---|---|---|
committer | dzwdz | 2023-08-14 18:51:07 +0200 |
commit | 642b5fb0007b64c77d186fcb018d571152ee1d47 (patch) | |
tree | 1c466461f3602d306be309a053edae558ef2568e /src/libc/fs | |
parent | 8050069c57b729c18c19b1a03ab6e4bf63b4735e (diff) |
reorganization: first steps
Diffstat (limited to 'src/libc/fs')
-rw-r--r-- | src/libc/fs/dir.c | 84 | ||||
-rw-r--r-- | src/libc/fs/dirinject.c | 129 | ||||
-rw-r--r-- | src/libc/fs/misc.c | 160 | ||||
-rw-r--r-- | src/libc/fs/whitelist.c | 114 |
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); +} |