From c7af8418c9a2222cde5ab3a6953b31803d0a8ef1 Mon Sep 17 00:00:00 2001
From: dzwdz
Date: Mon, 8 Aug 2022 13:12:41 +0200
Subject: user: union filesystems

---
 src/user/app/init/init.c      |  4 +++
 src/user/app/shell/builtins.c |  2 +-
 src/user/lib/fs/misc.c        | 65 +++++++++++++++++++++++++++++++++++++++++++
 src/user/lib/fs/misc.h        |  1 +
 4 files changed, 71 insertions(+), 1 deletion(-)

(limited to 'src/user')

diff --git a/src/user/app/init/init.c b/src/user/app/init/init.c
index f011ca5..c788cb6 100644
--- a/src/user/app/init/init.c
+++ b/src/user/app/init/init.c
@@ -39,6 +39,10 @@ int main(void) {
 		const char *argv[] = {"/bin/vterm", NULL};
 		execv(argv[0], (void*)argv);
 	}
+	MOUNT_AT("/union/") {
+		const char *list[] = {"/tmp/", "/init/", NULL};
+		fs_union(list);
+	}
 
 	if (fork()) {
 		/* used to trigger a kernel bug
diff --git a/src/user/app/shell/builtins.c b/src/user/app/shell/builtins.c
index 2562c18..0f06482 100644
--- a/src/user/app/shell/builtins.c
+++ b/src/user/app/shell/builtins.c
@@ -125,7 +125,7 @@ static void cmd_ls(int argc, char **argv) {
 			return;
 		}
 		for (;;) {
-			int len = fread(buf, 1, sizeof buf, file);
+			int len = fread(buf, 1, buflen, file);
 			if (len <= 0) break;
 			for (int i = 0; i < len; i++)
 				if (buf[i] == '\0') buf[i] = '\n';
diff --git a/src/user/lib/fs/misc.c b/src/user/lib/fs/misc.c
index 1fb5c3b..54f54fd 100644
--- a/src/user/lib/fs/misc.c
+++ b/src/user/lib/fs/misc.c
@@ -135,6 +135,71 @@ void fs_whitelist(const char **list) {
 	exit(0);
 }
 
+void fs_union(const char **list) {
+	struct fs_wait_response 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 (!_syscall_fs_wait(post, postlen, &res)) {
+		switch (res.op) {
+			case VFSOP_OPEN:
+				if (res.len == 1) { /* root directory */
+					_syscall_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 = _syscall_open(path, prefixlen + res.len, res.flags);
+
+					post[res.len] = '\0';
+				}
+				if (ret < 0) ret = -1;
+				_syscall_fs_respond(NULL, ret, FSR_DELEGATE);
+				break;
+
+		case VFSOP_READ:
+			if (res.capacity > buflen)
+				res.capacity = buflen;
+			bool end = false;
+			dir_start(&db, res.offset, pre, 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
+				handle_t h = _syscall_open(prefix, prefixlen, 0);
+				if (h < 0) continue;
+				end = end || dir_append_from(&db, h);
+				_syscall_close(h);
+			}
+			_syscall_fs_respond(pre, dir_finish(&db), 0);
+			break;
+
+			default:
+				_syscall_fs_respond(NULL, -1, 0);
+				break;
+		}
+	}
+	exit(0);
+}
+
 
 void fs_dir_inject(const char *path) {
 	struct fs_dir_handle {
diff --git a/src/user/lib/fs/misc.h b/src/user/lib/fs/misc.h
index 1a9c555..8fe6834 100644
--- a/src/user/lib/fs/misc.h
+++ b/src/user/lib/fs/misc.h
@@ -8,6 +8,7 @@ bool fork2_n_mount(const char *path);
 
 void fs_passthru(const char *prefix);
 void fs_whitelist(const char **list);
+void fs_union(const char **list);
 
 void fs_dir_inject(const char *path);
 
-- 
cgit v1.2.3