summaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
authordzwdz2023-08-14 18:51:07 +0200
committerdzwdz2023-08-14 18:51:07 +0200
commit642b5fb0007b64c77d186fcb018d571152ee1d47 (patch)
tree1c466461f3602d306be309a053edae558ef2568e /src/cmd
parent8050069c57b729c18c19b1a03ab6e4bf63b4735e (diff)
reorganization: first steps
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/drawmouse/drawmouse.c87
-rw-r--r--src/cmd/dvd/dvd.c43
m---------src/cmd/ext2fs/ext20
-rw-r--r--src/cmd/ext2fs/main.c243
-rw-r--r--src/cmd/find/find.c55
-rw-r--r--src/cmd/httpd/httpd.c77
-rw-r--r--src/cmd/init/driver/driver.h8
-rw-r--r--src/cmd/init/driver/initctl.c44
-rw-r--r--src/cmd/init/driver/ps2.c129
-rw-r--r--src/cmd/init/driver/termcook.c103
-rw-r--r--src/cmd/init/init.c151
-rw-r--r--src/cmd/iochk/iochk.c97
-rw-r--r--src/cmd/iostress/iostress.c43
-rw-r--r--src/cmd/logfs/logfs.c34
-rw-r--r--src/cmd/login/login.c89
-rw-r--r--src/cmd/netdog/nd.c48
-rw-r--r--src/cmd/netstack/arp.c151
-rw-r--r--src/cmd/netstack/ether.c59
-rw-r--r--src/cmd/netstack/fs.c320
-rw-r--r--src/cmd/netstack/icmp.c34
-rw-r--r--src/cmd/netstack/ipv4.c216
-rw-r--r--src/cmd/netstack/netstack.c53
-rw-r--r--src/cmd/netstack/proto.h107
-rw-r--r--src/cmd/netstack/tcp.c268
-rw-r--r--src/cmd/netstack/udp.c124
-rw-r--r--src/cmd/netstack/util.c60
-rw-r--r--src/cmd/netstack/util.h44
-rw-r--r--src/cmd/ps/ps.c54
-rw-r--r--src/cmd/shell/builtins.c254
-rw-r--r--src/cmd/shell/builtins.h10
-rw-r--r--src/cmd/shell/parser.c76
-rw-r--r--src/cmd/shell/shell.c178
-rw-r--r--src/cmd/shell/shell.h11
-rw-r--r--src/cmd/testelf/main.c26
-rw-r--r--src/cmd/tests/kernel/fdlimit.c49
-rw-r--r--src/cmd/tests/kernel/fs.c79
-rw-r--r--src/cmd/tests/kernel/misc.c66
-rw-r--r--src/cmd/tests/kernel/miscsyscall.c315
-rw-r--r--src/cmd/tests/kernel/path.c108
-rw-r--r--src/cmd/tests/kernel/threads.c55
-rw-r--r--src/cmd/tests/libc/esemaphore.c95
-rw-r--r--src/cmd/tests/libc/setjmp.c31
-rw-r--r--src/cmd/tests/libc/string.c124
-rw-r--r--src/cmd/tests/shared/printf.c55
-rw-r--r--src/cmd/tests/shared/ringbuf.c49
-rw-r--r--src/cmd/tests/stress.c28
-rw-r--r--src/cmd/tests/tests.c68
-rw-r--r--src/cmd/tests/tests.h37
-rw-r--r--src/cmd/tmpfs/tmpfs.c198
-rw-r--r--src/cmd/vterm/draw.c17
-rw-r--r--src/cmd/vterm/font.c76
-rw-r--r--src/cmd/vterm/vterm.c72
-rw-r--r--src/cmd/vterm/vterm.h39
53 files changed, 4857 insertions, 0 deletions
diff --git a/src/cmd/drawmouse/drawmouse.c b/src/cmd/drawmouse/drawmouse.c
new file mode 100644
index 0000000..31a1255
--- /dev/null
+++ b/src/cmd/drawmouse/drawmouse.c
@@ -0,0 +1,87 @@
+#include <camellia.h>
+#include <camellia/syscalls.h>
+#include <shared/ring.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <draw.h>
+
+#define MOUSE_SIZE 10
+
+#define eprintf(fmt, ...) fprintf(stderr, "drawmouse: "fmt"\n" __VA_OPT__(,) __VA_ARGS__)
+
+struct framebuf fb, lastbehind;
+struct rect dirty;
+
+static uint8_t r_buf[64];
+static ring_t r = {(void*)r_buf, sizeof r_buf, 0, 0};
+static struct {uint32_t x, y;} cursor, lastcur;
+
+
+static void draw_mouse(void) {
+ static bool drawn = false;
+ if (drawn)
+ fb_cpy(&fb, &lastbehind, lastcur.x, lastcur.y, 0, 0, MOUSE_SIZE, MOUSE_SIZE);
+ drawn = true;
+ dirty_mark(&dirty, lastcur.x, lastcur.y);
+ dirty_mark(&dirty, lastcur.x + MOUSE_SIZE, lastcur.y + MOUSE_SIZE);
+ fb_cpy(&lastbehind, &fb, 0, 0, cursor.x, cursor.y, MOUSE_SIZE, MOUSE_SIZE);
+ for (int i = 0; i < MOUSE_SIZE; i++) {
+ for (int j = 0; j < MOUSE_SIZE - i; j++) {
+ uint32_t *px = fb_pixel(&fb, cursor.x + i, cursor.y + j);
+ if (px) {
+ *px ^= 0x808080;
+ dirty_mark(&dirty, cursor.x + i, cursor.y + j);
+ }
+ }
+ }
+ lastcur = cursor;
+}
+
+
+struct packet {
+ uint8_t left : 1;
+ uint8_t right : 1;
+ uint8_t middle : 1;
+ uint8_t _useless : 5;
+ int8_t dx, dy;
+} __attribute__((packed));
+
+int main(void) {
+ char buf[64];
+ hid_t fd = camellia_open("/kdev/ps2/mouse", OPEN_READ);
+ if (fd < 0) {
+ eprintf("couldn't open mouse");
+ return 1;
+ }
+
+ if (fb_setup(&fb, "/kdev/video/") < 0) {
+ eprintf("fb_setup error");
+ return 1;
+ }
+ fb_anon(&lastbehind, MOUSE_SIZE, MOUSE_SIZE);
+
+
+ for (;;) {
+ int len = _sys_read(fd, buf, sizeof buf, 0);
+ if (len == 0) break;
+ ring_put(&r, buf, len);
+ while (ring_used(&r) >= 3) {
+ struct packet p;
+ ring_get(&r, &p, sizeof p);
+ p.dy *= -1;
+ // TODO check mouse click
+ if (-p.dx > (int)cursor.x) p.dx = -cursor.x;
+ if (-p.dy > (int)cursor.y) p.dy = -cursor.y;
+ cursor.x += p.dx;
+ cursor.y += p.dy;
+ if (cursor.x >= fb.width) cursor.x = fb.width - 1;
+ if (cursor.y >= fb.height) cursor.y = fb.height - 1;
+ draw_mouse();
+ if (p.left && p.right) return 0;
+ }
+ dirty_flush(&dirty, &fb);
+ }
+ return 0;
+}
diff --git a/src/cmd/dvd/dvd.c b/src/cmd/dvd/dvd.c
new file mode 100644
index 0000000..a15b440
--- /dev/null
+++ b/src/cmd/dvd/dvd.c
@@ -0,0 +1,43 @@
+#include <camellia/execbuf.h>
+#include <camellia/syscalls.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <draw.h>
+
+#define eprintf(fmt, ...) fprintf(stderr, "vterm: "fmt"\n" __VA_OPT__(,) __VA_ARGS__)
+
+struct framebuf fb;
+struct rect dirty;
+
+void draw_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t col) {
+ for (uint32_t i = 0; i < w; i++) {
+ for (uint32_t j = 0; j < h; j++) {
+ *((uint32_t*)(fb.b + fb.pitch * (y+j) + 4 * (x+i))) = col;
+ }
+ }
+ dirty_mark(&dirty, x, y);
+ dirty_mark(&dirty, x + w, y + h);
+}
+
+int main(void) {
+ if (fb_setup(&fb, "/kdev/video/") < 0) {
+ eprintf("fb_setup error");
+ return 1;
+ }
+ int dx = 2, dy = 2, x = 100, y = 100, w = 150, h = 70;
+ uint32_t col = 0x800000;
+
+ for (;;) {
+ if (x + dx < 0 || (size_t)(x + dx + w) >= fb.width) dx *= -1;
+ if (y + dy < 0 || (size_t)(y + dy + h) >= fb.height) dy *= -1;
+ x += dx;
+ y += dy;
+ draw_rect(x, y, w, h, col++);
+ dirty_flush(&dirty, &fb);
+ _sys_sleep(1000 / 60);
+ }
+
+ return 1;
+}
diff --git a/src/cmd/ext2fs/ext2 b/src/cmd/ext2fs/ext2
new file mode 160000
+Subproject 8db7b4fbb2429b504d7c711dae89d917de16bed
diff --git a/src/cmd/ext2fs/main.c b/src/cmd/ext2fs/main.c
new file mode 100644
index 0000000..12ef3bc
--- /dev/null
+++ b/src/cmd/ext2fs/main.c
@@ -0,0 +1,243 @@
+#include "ext2/ex_cache.h"
+#include "ext2/ext2.h"
+#include <assert.h>
+#include <camellia/flags.h>
+#include <camellia/fs/dir.h>
+#include <camellia/fs/misc.h>
+#include <camellia/fsutil.h>
+#include <camellia/syscalls.h>
+#include <err.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct handle {
+ uint32_t n;
+ bool dir;
+};
+
+static int my_read(void *fp, void *buf, size_t len, size_t off);
+static int my_write(void *fp, const void *buf, size_t len, size_t off);
+static void do_open(struct ext2 *fs, hid_t reqh, struct ufs_request *req, char *buf);
+static void do_read(struct ext2 *fs, hid_t reqh, struct ufs_request *req, char *buf, size_t buflen);
+static void do_write(struct ext2 *fs, hid_t reqh, struct ufs_request *req, char *buf);
+static void do_getsize(struct ext2 *fs, hid_t reqh, struct ufs_request *req);
+
+static int
+my_read(void *fp, void *buf, size_t len, size_t off)
+{
+ if (fseek(fp, off, SEEK_SET) < 0) {
+ return -1;
+ } else if (fread(buf, len, 1, fp) == 1) {
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+static int
+my_write(void *fp, const void *buf, size_t len, size_t off)
+{
+ if (fseek(fp, off, SEEK_SET) < 0) {
+ return -1;
+ } else if (fwrite(buf, len, 1, fp) == 1) {
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+static void
+do_open(struct ext2 *fs, hid_t reqh, struct ufs_request *req, char *buf)
+{
+ bool is_dir = req->len == 0 || buf[req->len-1] == '/';
+ uint32_t n = ext2c_walk(fs, buf, req->len);
+ if (n == 0) {
+ if (is_dir) {
+ _sys_fs_respond(reqh, NULL, -ENOSYS, 0);
+ return;
+ }
+ /* buf[0] == '/', strrchr != NULL */
+ char *name = strrchr(buf, '/') + 1;
+ uint32_t dir_n = ext2c_walk(fs, buf, name - buf);
+ if (dir_n == 0) {
+ _sys_fs_respond(reqh, NULL, -ENOENT, 0);
+ return;
+ }
+ n = ext2_alloc_inode(fs, 0100700);
+ if (n == 0) {
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ return;
+ }
+ if (ext2_link(fs, dir_n, name, n, 1) < 0) {
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ return;
+ }
+ } else {
+ struct ext2d_inode *inode = ext2_req_inode(fs, n);
+ if (!inode) {
+ _sys_fs_respond(reqh, NULL, -ENOENT, 0);
+ return;
+ }
+ int type = (inode->perms >> 12) & 0xF;
+ ext2_dropreq(fs, inode, false);
+
+ if ((type == 0x8 && is_dir) || (type == 0x4 && !is_dir)) {
+ _sys_fs_respond(reqh, NULL, -ENOENT, 0);
+ return;
+ } else if (type != 0x8 && type != 0x4) {
+ _sys_fs_respond(reqh, NULL, -ENOSYS, 0);
+ return;
+ }
+ }
+
+ struct handle *h = malloc(sizeof *h);
+ if (!h) {
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ return;
+ }
+ h->n = n;
+ h->dir = is_dir;
+ _sys_fs_respond(reqh, h, 0, 0);
+}
+
+static void
+do_read(struct ext2 *fs, hid_t reqh, struct ufs_request *req, char *buf, size_t buflen)
+{
+ struct handle *h = req->id;
+ if (!h->dir) {
+ struct ext2d_inode *inode = ext2_req_inode(fs, h->n);
+ if (!inode) goto err;
+ fs_normslice(&req->offset, &req->capacity, inode->size_lower, false);
+ ext2_dropreq(fs, inode, false);
+
+ void *b = ext2_req_file(fs, h->n, &req->capacity, req->offset);
+ if (b) {
+ _sys_fs_respond(reqh, b, req->capacity, 0);
+ ext2_dropreq(fs, b, false);
+ } else if (req->capacity == 0) {
+ /* set by ext2_req_file on EOF */
+ _sys_fs_respond(reqh, b, 0, 0);
+ } else goto err;
+ } else {
+ struct dirbuild db;
+ char namebuf[257];
+ if (req->capacity > buflen)
+ req->capacity = buflen;
+ dir_start(&db, req->offset, buf, buflen);
+ for (struct ext2_diriter iter = {0}; ext2_diriter(&iter, fs, h->n); ) {
+ if (iter.ent->namelen_lower == 1 && iter.ent->name[0] == '.') {
+ continue;
+ }
+ if (iter.ent->namelen_lower == 2
+ && iter.ent->name[0] == '.'
+ && iter.ent->name[1] == '.')
+ {
+ continue;
+ }
+ if (iter.ent->type == 2) { /* dir */
+ memcpy(namebuf, iter.ent->name, iter.ent->namelen_lower);
+ namebuf[iter.ent->namelen_lower] = '/';
+ dir_appendl(&db, namebuf, iter.ent->namelen_lower + 1);
+ } else {
+ dir_appendl(&db, iter.ent->name, iter.ent->namelen_lower);
+ }
+ }
+ _sys_fs_respond(reqh, buf, dir_finish(&db), 0);
+ }
+ return;
+err:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+}
+
+static void
+do_write(struct ext2 *fs, hid_t reqh, struct ufs_request *req, char *buf)
+{
+ struct handle *h = req->id;
+ if (h->dir) goto err;
+
+ struct ext2d_inode *inode = ext2_req_inode(fs, h->n);
+ if (!inode) goto err;
+ fs_normslice(&req->offset, &req->len, inode->size_lower, true);
+ if ((req->flags & WRITE_TRUNCATE) || inode->size_lower < req->offset + req->len) {
+ inode->size_lower = req->offset + req->len;
+ if (ext2_dropreq(fs, inode, true) < 0) {
+ goto err;
+ }
+ } else {
+ ext2_dropreq(fs, inode, false);
+ }
+ inode = NULL;
+
+ int ret = ext2_write(fs, h->n, buf, req->len, req->offset);
+ _sys_fs_respond(reqh, NULL, ret, 0);
+ return;
+err:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+}
+
+static void
+do_getsize(struct ext2 *fs, hid_t reqh, struct ufs_request *req) {
+ struct handle *h = req->id;
+ if (h->dir) goto err;
+
+ struct ext2d_inode *inode = ext2_req_inode(fs, h->n);
+ if (!inode) goto err;
+ _sys_fs_respond(reqh, NULL, inode->size_lower, 0);
+ ext2_dropreq(fs, inode, false);
+ return;
+err:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+}
+
+int
+main(int argc, char **argv)
+{
+ intr_set(NULL);
+
+ if (argc < 2) errx(1, "bad usage");
+ // TODO pread/pwrite for normal handles
+ FILE *disk = fopen(argv[1], "r+");
+ if (!disk) err(1, "couldn't open '%s'", argv[1]);
+
+ struct e2device *dev = exc_init(my_read, my_write, (void*)disk);
+ if (!dev) errx(1, "exc_init failed");
+ struct ext2 *fs = ext2_opendev(dev, exc_req, exc_drop);
+ if (!fs) errx(1, "ext2_opendev failed");
+
+ const size_t buflen = 4096;
+ char *buf = malloc(buflen);
+ struct ufs_request req;
+ for (;;) {
+ hid_t reqh = ufs_wait(buf, buflen, &req);
+ struct handle *h = req.id;
+ if (reqh < 0) break;
+ switch (req.op) {
+ case VFSOP_OPEN:
+ do_open(fs, reqh, &req, buf);
+ break;
+ case VFSOP_READ:
+ do_read(fs, reqh, &req, buf, buflen);
+ break;
+ case VFSOP_WRITE:
+ do_write(fs, reqh, &req, buf);
+ break;
+ case VFSOP_GETSIZE:
+ do_getsize(fs, reqh, &req);
+ break;
+ case VFSOP_CLOSE:
+ free(h);
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ default:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ }
+ }
+ warnx("cleaning up");
+
+ return 1;
+}
diff --git a/src/cmd/find/find.c b/src/cmd/find/find.c
new file mode 100644
index 0000000..d473b82
--- /dev/null
+++ b/src/cmd/find/find.c
@@ -0,0 +1,55 @@
+#include <camellia/path.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+void recurse(char *path) {
+ DIR *d = opendir(path);
+ if (!d) {
+ warn("couldn't open %s", path);
+ return;
+ }
+ for (;;) {
+ struct dirent *dent;
+ errno = 0;
+ dent = readdir(d);
+ if (!dent) {
+ if (errno) {
+ warn("when reading %s", path);
+ }
+ break;
+ }
+ printf("%s%s\n", path, dent->d_name);
+ /* if the string ends with '/' */
+ if (strchr(dent->d_name, '\0')[-1] == '/') {
+ // TODO no overflow check
+ char *pend = strchr(path, '\0');
+ strcpy(pend, dent->d_name);
+ recurse(path);
+ *pend = '\0';
+ }
+ }
+ closedir(d);
+}
+
+void find(const char *path) {
+ // TODO bound checking
+ // TODO or just implement asprintf()
+ char *buf = malloc(PATH_MAX);
+ memcpy(buf, path, strlen(path)+1);
+ recurse(buf);
+ free(buf);
+}
+
+int main(int argc, char **argv) {
+ if (argc < 2) {
+ find("/");
+ } else {
+ for (int i = 1; i < argc; i++)
+ find(argv[i]);
+ }
+ return 0;
+}
diff --git a/src/cmd/httpd/httpd.c b/src/cmd/httpd/httpd.c
new file mode 100644
index 0000000..668e534
--- /dev/null
+++ b/src/cmd/httpd/httpd.c
@@ -0,0 +1,77 @@
+/* garbage httpd, just to see if it works
+ * easily DoSable (like the rest of the network stack), vulnerable to path traversal, etc */
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+static void handle(FILE *c) {
+ char buf[2048];
+ fgets(buf, sizeof buf, c);
+ printf("%s", buf);
+
+ if (memcmp(buf, "GET /", 5) != 0) {
+ fprintf(c, "HTTP/1.1 400 Bad Request\r\n\r\n");
+ return;
+ }
+ char *path = buf + 4;
+ char *end = strchr(path, ' ');
+ if (end) *end = '\0';
+
+ hid_t h = _sys_open(path, strlen(path), OPEN_READ);
+ if (h < 0) {
+ fprintf(c, "HTTP/1.1 404 Not Found\r\n\r\n");
+ return;
+ }
+ FILE *f = fdopen(h, "r");
+ if (!f) {
+ fprintf(c, "HTTP/1.1 500 Internal Server Error\r\n\r\n");
+ return;
+ }
+
+ if (path[strlen(path) - 1] != '/') {
+ /* regular file */
+ fprintf(c, "HTTP/1.1 200 OK\r\n");
+ fprintf(c, "\r\n");
+ for (;;) {
+ int len = fread(buf, 1, sizeof buf, f);
+ if (len <= 0) break;
+ fwrite(buf, 1, len, c);
+ }
+ } else {
+ /* directory listing */
+ fprintf(c, "HTTP/1.1 200 OK\r\n");
+ fprintf(c, "Content-Type: text/html; charset=UTF-8\r\n");
+ fprintf(c, "\r\n");
+ fprintf(c, "<h1>directory listing for %s</h1><hr><ul><li><a href=..>..</a></li>", path);
+ for (;;) {
+ int len = fread(buf, 1, sizeof buf, f);
+ if (len <= 0) break;
+ // TODO directory library
+ // based on find.c
+ for (int pos = 0; pos < len; ) {
+ if (buf[pos] == '\0') break;
+ const char *end = memchr(buf + pos, 0, len - pos);
+ if (!end) break;
+ fprintf(c, "<li><a href=\"%s\">%s</a></li>", buf + pos, buf + pos);
+ pos += end - (buf + pos) + 1;
+ }
+ }
+ }
+ fclose(f);
+}
+
+int main(int argc, char **argv) {
+ const char *path = (argc > 1) ? argv[1] : "/net/listen/0.0.0.0/tcp/80";
+ hid_t conn;
+ for (;;) {
+ conn = _sys_open(path, strlen(path), OPEN_RW);
+ if (conn < 0)
+ errx(1, "open('%s') failed, errno %d", path, -conn);
+ FILE *f = fdopen(conn, "a+");
+ handle(f);
+ fclose(f);
+ }
+}
diff --git a/src/cmd/init/driver/driver.h b/src/cmd/init/driver/driver.h
new file mode 100644
index 0000000..98c18f1
--- /dev/null
+++ b/src/cmd/init/driver/driver.h
@@ -0,0 +1,8 @@
+#pragma once
+#include <camellia/types.h>
+
+void initctl_drv(hid_t killswitch);
+void ps2_drv(void);
+void tmpfs_drv(void);
+
+void termcook(void);
diff --git a/src/cmd/init/driver/initctl.c b/src/cmd/init/driver/initctl.c
new file mode 100644
index 0000000..fed71b7
--- /dev/null
+++ b/src/cmd/init/driver/initctl.c
@@ -0,0 +1,44 @@
+#include "driver.h"
+#include <camellia/syscalls.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <camellia/compat.h>
+
+void initctl_drv(hid_t killswitch) {
+ struct ufs_request res;
+ char buf[64];
+ const size_t buflen = sizeof buf;
+ while (!c0_fs_wait(buf, buflen, &res)) {
+ switch (res.op) {
+ case VFSOP_OPEN:
+ c0_fs_respond(NULL, res.len == 0 ? 0 : -1, 0);
+ break;
+ case VFSOP_WRITE:
+ /* null terminate */
+ if (res.len > buflen - 1)
+ res.len = buflen - 1;
+ buf[res.len] = '\0';
+ /* cut at first whitespace */
+ for (size_t i = 0; buf[i]; i++) {
+ if (isspace(buf[i])) {
+ buf[i] = '\0';
+ break;
+ }
+ }
+ if (!strcmp(buf, "halt")) {
+ _sys_write(killswitch, "halt", 4, 0, 0);
+ }
+ if (!strcmp(buf, "intr")) {
+ _sys_write(killswitch, "intr", 4, 0, 0);
+ }
+ c0_fs_respond(NULL, res.len, 0);
+ break;
+ default:
+ c0_fs_respond(NULL, -ENOSYS, 0);
+ break;
+ }
+ }
+ exit(1);
+}
diff --git a/src/cmd/init/driver/ps2.c b/src/cmd/init/driver/ps2.c
new file mode 100644
index 0000000..5470249
--- /dev/null
+++ b/src/cmd/init/driver/ps2.c
@@ -0,0 +1,129 @@
+#include "driver.h"
+#include <assert.h>
+#include <camellia/compat.h>
+#include <camellia/syscalls.h>
+#include <errno.h>
+#include <shared/ring.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <thread.h>
+
+
+static const char keymap_lower[] = {
+ '\0', '\0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', '\t',
+ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\r', '\0', 'a', 's',
+ 'd', 'f', 'g', 'h', 'j', 'k', 'l', '\0', '\'', '`', '\0', '\\', 'z', 'x', 'c', 'v',
+ 'b', 'n', 'm', ',', '.', '/', '\0', '*', '\0', ' ', '\0', '\0', '\0', '\0', '\0', '\0',
+ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '7', '8', '9', '-', '4', '5', '6', '+', '1',
+ '2', '3', '0', '.', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
+ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
+ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
+};
+
+static const char keymap_upper[] = {
+ '\0', '\0', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b', '\t',
+ 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\r', '\0', 'A', 'S',
+ 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', '\0', '|', 'Z', 'X', 'C', 'V',
+ 'B', 'N', 'M', '<', '>', '?', '\0', '*', '\0', ' ', '\0', '\0', '\0', '\0', '\0', '\0',
+ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '7', '8', '9', '-', '4', '5', '6', '+', '1',
+ '2', '3', '0', '.', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
+ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
+ '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
+};
+
+
+static volatile uint8_t backlog_buf[16];
+static volatile ring_t backlog = {(void*)backlog_buf, sizeof backlog_buf, 0, 0};
+
+static hid_t fd;
+
+static bool keys[0x80] = {0};
+
+static void parse_scancode(uint8_t s) {
+ bool ctrl = keys[0x1D];
+ bool shift = keys[0x2A] || keys[0x36];
+ bool down = !(s & 0x80);
+ char c;
+ s &= 0x7f;
+ keys[s] = down;
+
+ c = shift ? keymap_upper[s] : keymap_lower[s];
+ if (ctrl && keymap_upper[s] >= 'A' && keymap_upper[s] <= 'Z')
+ c = keymap_upper[s] - 'A' + 1;
+ if (down && c) ring_put1b((void*)&backlog, c);
+}
+
+
+/** Is a thread waiting for /kdev/ps2/kb? */
+static volatile bool blocked = false;
+/* for use in read_thread */
+static hid_t rt_reqh;
+static size_t rt_cap;
+
+static void read_thread(void *unused) {
+ char buf[512];
+ (void)unused;
+
+ assert(blocked);
+ while (ring_used((void*)&backlog) == 0) {
+ /* read raw input until we have something to output */
+ int len = _sys_read(fd, buf, sizeof buf, 0);
+ if (len == 0) break;
+ for (int i = 0; i < len; i++)
+ parse_scancode(buf[i]);
+ }
+ if (ring_used((void*)&backlog) > 0) {
+ int ret = ring_get((void*)&backlog, buf, rt_cap);
+ _sys_fs_respond(rt_reqh, buf, ret, 0);
+ } else {
+ _sys_fs_respond(rt_reqh, 0, -EGENERIC, 0);
+ }
+ blocked = false;
+}
+
+static void fs_loop(void) {
+ static char buf[512];
+ int ret;
+ for (;;) {
+ struct ufs_request res;
+ hid_t reqh = _sys_fs_wait(buf, sizeof buf, &res);
+ if (reqh < 0) return;
+
+ switch (res.op) {
+ case VFSOP_OPEN:
+ if (res.len == 0) {
+ _sys_fs_respond(reqh, NULL, 1, 0);
+ } else {
+ _sys_fs_respond(reqh, NULL, -ENOENT, 0);
+ }
+ break;
+
+ case VFSOP_READ:
+ if (blocked) {
+ _sys_fs_respond(reqh, NULL, -EAGAIN, 0);
+ break;
+ } else if (ring_used((void*)&backlog) > 0) {
+ ret = ring_get((void*)&backlog, buf, res.capacity);
+ _sys_fs_respond(reqh, buf, ret, 0);
+ } else {
+ blocked = true;
+ rt_reqh = reqh;
+ rt_cap = res.capacity;
+ thread_create(0, read_thread, 0);
+ }
+ break;
+
+ default:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ }
+ }
+}
+
+void ps2_drv(void) {
+ fd = _sys_open("/kdev/ps2/kb", 12, 0);
+ if (fd < 0) exit(1);
+
+ fs_loop();
+ exit(0);
+}
diff --git a/src/cmd/init/driver/termcook.c b/src/cmd/init/driver/termcook.c
new file mode 100644
index 0000000..a76f3a8
--- /dev/null
+++ b/src/cmd/init/driver/termcook.c
@@ -0,0 +1,103 @@
+#include "driver.h"
+#include <camellia/syscalls.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+enum tstate {
+ Normal,
+ Esc,
+ CSI,
+};
+
+static void w_output(hid_t output, const char *buf, size_t len) {
+ size_t pos = 0;
+ while (pos < len) {
+ int ret = _sys_write(output, buf + pos, len - pos, pos, 0);
+ if (ret < 0) break;
+ pos += ret;
+ }
+}
+
+static void line_editor(hid_t input, hid_t output) {
+ char readbuf[16], linebuf[256];
+ size_t linepos = 0;
+ enum tstate state = Normal;
+ for (;;) {
+ int readlen = _sys_read(input, readbuf, sizeof readbuf, -1);
+ if (readlen < 0) return;
+ for (int i = 0; i < readlen; i++) {
+ char c = readbuf[i];
+ switch (state) {
+ case Normal:
+ switch (c) {
+ case '\b':
+ case 0x7f:
+ if (linepos != 0) {
+ printf("\b \b");
+ linepos--;
+ }
+ break;
+ case 3: /* C-c */
+ _sys_exit(1);
+ case 4: /* EOT, C-d */
+ if (linepos > 0) {
+ w_output(output, linebuf, linepos);
+ linepos = 0;
+ } else {
+ _sys_write(output, NULL, 0, 0, 0); /* EOF */
+ }
+ break;
+ case '\n':
+ case '\r':
+ printf("\n");
+ if (linepos < sizeof linebuf)
+ linebuf[linepos++] = '\n';
+ w_output(output, linebuf, linepos);
+ linepos = 0;
+ break;
+ case '\e':
+ state = Esc;
+ break;
+ case '\t':
+ break;
+ default:
+ if (linepos < sizeof linebuf) {
+ linebuf[linepos++] = c;
+ printf("%c", c);
+ }
+ break;
+ }
+ break;
+ case Esc:
+ if (c == '[') state = CSI;
+ else state = Normal;
+ break;
+ case CSI:
+ if (0x40 <= c && c <= 0x7E) state = Normal;
+ break;
+ }
+ }
+ }
+}
+
+void termcook(void) {
+ hid_t stdin_pipe[2] = {-1, -1};
+ if (_sys_pipe(stdin_pipe, 0) < 0)
+ return;
+
+ if (!fork()) {
+ /* the caller continues in a child process,
+ * so it can be killed when the line editor quits */
+ _sys_dup(stdin_pipe[0], 0, 0);
+ close(stdin_pipe[0]);
+ close(stdin_pipe[1]);
+ return;
+ }
+ if (!fork()) {
+ close(stdin_pipe[0]);
+ line_editor(0, stdin_pipe[1]);
+ exit(0);
+ }
+ exit(_sys_await());
+}
diff --git a/src/cmd/init/init.c b/src/cmd/init/init.c
new file mode 100644
index 0000000..fcebfc7
--- /dev/null
+++ b/src/cmd/init/init.c
@@ -0,0 +1,151 @@
+#include "driver/driver.h"
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <camellia/fs/misc.h>
+
+#define die(fmt, ...) do { fprintf(stderr, "init: " fmt, __VA_ARGS__); exit(1); } while (0)
+
+static char title[128];
+
+void redirect(const char *exe, const char *out, const char *in) {
+ if (!fork()) {
+ snprintf(title, sizeof title, "sh >%s", out);
+ setproctitle(title);
+
+ if (!freopen(out, "a+", stderr)) {
+ fprintf(stdout, "couldn't open %s\n", out);
+ exit(1);
+ }
+ if (!freopen(out, "a+", stdout))
+ die("couldn't open %s\n", out);
+ if (!freopen(in, "r", stdin))
+ die(" couldn't open %s\n", in);
+
+ for (;;) {
+ if (!fork()) {
+ const char *argv[] = {exe, NULL};
+ termcook();
+ execv(exe, (void*)argv);
+ fprintf(stderr, "init: couldn't start %s\n", exe);
+ _sys_sleep(5000);
+ exit(1);
+ }
+ _sys_await();
+ _sys_intr();
+ }
+ }
+}
+
+int main(void) {
+ const char *teststr = "I am teststr.\n";
+ hid_t killswitch_pipe[2];
+
+ freopen("/kdev/com1", "a+", stdout);
+ freopen("/kdev/com1", "a+", stderr);
+ printf("[init] stage 2, main at %p, teststr at %p\n", &main, teststr);
+
+ MOUNT_AT("/") {
+ fs_dirinject2((const char*[]){
+ "/keyboard/",
+ "/usr/",
+ "/bin/",
+ "/Users/",
+ "/tmp/",
+ "/vtty",
+ "/net/",
+ "/initctl",
+ NULL
+ });
+ }
+
+ MOUNT_AT("/keyboard") {
+ MOUNT_AT("/") { fs_whitelist((const char*[]){"/kdev/ps2/kb", NULL}); }
+ ps2_drv();
+ }
+ MOUNT_AT("/usr/") {
+ fs_union((const char*[]){
+ "/init/usr/",
+ NULL
+ });
+ }
+ MOUNT_AT("/bin/") {
+ fs_union((const char*[]){
+ "/init/bin/amd64/",
+ "/init/bin/sh/",
+ "/init/usr/bin/",
+ "/init/usr/local/bin/",
+ NULL
+ });
+ }
+ MOUNT_AT("/Users/") {
+ MOUNT_AT("/tmp/") {
+ const char *argv[] = {"/bin/tmpfs", NULL};
+ execv(argv[0], (void*)argv);
+ }
+ // TODO a simple union isn't enough here
+ fs_union((const char*[]){
+ "/tmp/",
+ "/init/Users/",
+ NULL
+ });
+ }
+ MOUNT_AT("/tmp/") {
+ const char *allow[] = {"/bin/tmpfs", NULL};
+ const char *argv[] = {"/bin/tmpfs", NULL};
+ MOUNT_AT("/") { fs_whitelist(allow); }
+ execv(argv[0], (void*)argv);
+ }
+ MOUNT_AT("/vtty") {
+ const char *allow[] = {"/bin/vterm", "/kdev/video/", "/keyboard", "/init/usr/share/fonts/", NULL};
+ const char *argv[] = {"/bin/vterm", NULL};
+ MOUNT_AT("/") { fs_whitelist(allow); }
+ execv(argv[0], (void*)argv);
+ }
+ MOUNT_AT("/net/") {
+ const char *allow[] = {"/bin/netstack", "/kdev/eth", NULL};
+ const char *argv[] = {"/bin/netstack", "/kdev/eth", "192.168.0.11", "192.168.0.2", NULL};
+ MOUNT_AT("/") { fs_whitelist(allow); }
+ execv(argv[0], (void*)argv);
+ }
+
+ if (_sys_pipe(killswitch_pipe, 0) < 0) {
+ printf("couldn't create the killswitch pipe, quitting...\n");
+ return 1;
+ }
+ MOUNT_AT("/initctl") {
+ close(killswitch_pipe[0]);
+ initctl_drv(killswitch_pipe[1]);
+ }
+ close(killswitch_pipe[1]);
+
+ if (!fork()) {
+ // TODO close on exec
+ close(killswitch_pipe[0]);
+ redirect("/bin/shell", "/kdev/com1", "/kdev/com1");
+ redirect("/bin/shell", "/vtty", "/keyboard");
+ exit(1);
+ }
+
+ char buf[128];
+ for (;;) {
+ if (_sys_read(killswitch_pipe[0], buf, 128, 0) != 4) {
+ break;
+ }
+ if (memcmp(buf, "intr", 4) == 0) {
+ _sys_intr();
+ } else if (memcmp(buf, "halt", 4) == 0) {
+ break;
+ }
+ }
+ printf("[init] intr\n");
+ _sys_intr();
+ _sys_sleep(1000);
+ printf("[init] filicide\n");
+ _sys_filicide();
+ printf("[init] goodbye\n");
+ return 0;
+}
diff --git a/src/cmd/iochk/iochk.c b/src/cmd/iochk/iochk.c
new file mode 100644
index 0000000..0850821
--- /dev/null
+++ b/src/cmd/iochk/iochk.c
@@ -0,0 +1,97 @@
+#include <assert.h>
+#include <camellia.h>
+#include <camellia/syscalls.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static bool verbose = false;
+
+#define verbosef(...) do { if (verbose) printf(__VA_ARGS__); } while (0)
+#define eprintf(fmt, ...) fprintf(stderr, "iochk: "fmt"\n" __VA_OPT__(,) __VA_ARGS__)
+
+
+void check(hid_t h) {
+ const size_t buflen = 4096;
+ const size_t offsets[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 16, 32, 64, 128, 256,
+ 512, 1024, 2048,
+ };
+ char *buflast = malloc(buflen);
+ char *bufcur = malloc(buflen);
+ if (!buflast || !bufcur) {
+ eprintf("out of memory");
+ goto end;
+ }
+
+ long offlast = 0;
+ long retlast = _sys_read(h, buflast, buflen, offlast);
+ if (retlast < 0) {
+ eprintf("error %ld when reading at offset %ld", retlast, offlast);
+ goto end;
+ }
+
+ for (size_t i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
+ char *tmp;
+ long offcur = offsets[i];
+ long diff = offcur - offlast;
+ assert(diff >= 0);
+ if (retlast < diff) break;
+
+ long retcur = _sys_read(h, bufcur, buflen, offcur);
+ if (retcur < 0) {
+ eprintf("error %ld when reading at offset %ld", retlast, offcur);
+ break;
+ }
+ if (retcur < retlast + offlast - offcur) {
+ verbosef("warn: unexpected ret %ld < %ld + %ld - %ld\n", retcur, retlast, offlast, offcur);
+ }
+ if (memcmp(buflast + diff, bufcur, retlast - diff)) {
+ eprintf("inconsistent read from offsets %ld and %ld", offlast, offcur);
+ }
+
+ offlast = offcur;
+ retlast = retcur;
+ tmp = bufcur;
+ bufcur = buflast;
+ buflast = tmp;
+ }
+
+ // TODO check negative offsets
+
+end:
+ free(buflast);
+ free(bufcur);
+}
+
+int main(int argc, char **argv) {
+ int c;
+ while ((c = getopt(argc, argv, "v")) != -1) {
+ switch (c) {
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ return 1;
+ }
+ }
+ if (optind >= argc) {
+ eprintf("no files given");
+ return 1;
+ }
+ for (; optind < argc; optind++) {
+ const char *path = argv[optind];
+ verbosef("checking %s...\n", path);
+ hid_t h = camellia_open(path, OPEN_READ);
+ if (h < 0) {
+ eprintf("couldn't open %s", path);
+ continue;
+ }
+ check(h);
+ close(h);
+ }
+ return 0;
+}
diff --git a/src/cmd/iostress/iostress.c b/src/cmd/iostress/iostress.c
new file mode 100644
index 0000000..ac555de
--- /dev/null
+++ b/src/cmd/iostress/iostress.c
@@ -0,0 +1,43 @@
+#include <camellia/syscalls.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <x86intrin.h>
+
+int main(int argc, char **argv) {
+ long num_runs = 4;
+ long num_calls = 512;
+ long num_bytes = 1;
+ uint64_t *results;
+ char *inbuf;
+
+ if (argc > 1) num_runs = strtol(argv[1], NULL, 0);
+ if (argc > 2) num_calls = strtol(argv[2], NULL, 0);
+ if (argc > 3) num_bytes = strtol(argv[3], NULL, 0);
+ if (argc > 4 || num_runs == 0 || num_calls == 0) {
+ fprintf(stderr, "usage: %s [num_runs] [num_calls] [num_bytes]\n", argv[0]);
+ return 1;
+ }
+
+ results = malloc(sizeof(*results) * num_runs);
+ inbuf = malloc(num_bytes);
+ memset(inbuf, '.', num_bytes);
+
+ for (long i = 0; i < num_runs; i++) {
+ uint64_t time = __rdtsc();
+ for (long j = 0; j < num_calls; j++)
+ _sys_write(1, inbuf, num_bytes, -1, 0);
+ results[i] = __rdtsc() - time;
+ _sys_write(1, "\n", 1, -1, 0);
+ }
+
+ uint64_t total = 0;
+ for (long i = 0; i < num_runs; i++) {
+ uint64_t scaled = results[i] / 3000;
+ total += scaled;
+ fprintf(stderr, "run %ld: %lu\n", i, scaled);
+ }
+ fprintf(stderr, "%lu calls, %lu bytes. avg %lu\n", num_calls, num_bytes, total / num_runs);
+
+ return 0;
+}
diff --git a/src/cmd/logfs/logfs.c b/src/cmd/logfs/logfs.c
new file mode 100644
index 0000000..a50d530
--- /dev/null
+++ b/src/cmd/logfs/logfs.c
@@ -0,0 +1,34 @@
+#include <camellia.h>
+#include <camellia/syscalls.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <camellia/fs/misc.h>
+
+_Noreturn void fs(void) {
+ const size_t buflen = 1024;
+ char *buf = malloc(buflen);
+ if (!buf) err(1, "malloc");
+ for (;;) {
+ struct ufs_request req;
+ hid_t reqh = ufs_wait(buf, buflen, &req);
+ if (reqh < 0) errx(1, "ufs_wait error");
+
+ switch (req.op) {
+ case VFSOP_OPEN:
+ printf("[logfs] open(\"%s\", 0x%x)\n", buf, req.flags);
+ forward_open(reqh, buf, req.len, req.flags);
+ break;
+ default:
+ /* Unsupported vfs operation.
+ * Currently if you never create your own file descriptors you won't receive
+ * anything but VFSOP_OPEN, but it's idiomatic to handle this anyways. */
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ }
+ }
+}
+
+int main(void) {
+ fs();
+}
diff --git a/src/cmd/login/login.c b/src/cmd/login/login.c
new file mode 100644
index 0000000..0f9e8b7
--- /dev/null
+++ b/src/cmd/login/login.c
@@ -0,0 +1,89 @@
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <camellia/fs/misc.h>
+
+static const char *shell = "/bin/shell";
+
+static void cutspace(char *s) {
+ for (; *s; s++) {
+ if (isspace(*s)) {
+ *s = '\0';
+ break;
+ }
+ }
+}
+
+static bool segcmp(const char *path, int idx, const char *s2) {
+ if (idx < 0) return false;
+ while (idx > 0) {
+ if (*path == '\0') return false;
+ if (*path == '/') idx--;
+ path++;
+ }
+ /* path is at the start of the selected segment */
+ while (*s2 && *path++ == *s2++);
+ return (*path == '\0' || *path == '/') && *s2 == '\0';
+}
+
+static void drv(const char *user) {
+ char *buf = malloc(PATH_MAX);
+ for (;;) {
+ struct ufs_request req;
+ hid_t reqh = ufs_wait(buf, PATH_MAX, &req);
+ if (reqh < 0) break;
+ switch (req.op) {
+ case VFSOP_OPEN:
+ if (segcmp(buf, 1, "Users") && segcmp(buf, 2, user)) { // /Users/$user/**
+ forward_open(reqh, buf, req.len, req.flags);
+ } else if (segcmp(buf, 1, "Users") && segcmp(buf, 3, "private")) { // /Users/*/private/**
+ _sys_fs_respond(reqh, NULL, -EACCES, 0);
+ } else if (!OPEN_WRITEABLE(req.flags)) {
+ forward_open(reqh, buf, req.len, req.flags);
+ } else {
+ _sys_fs_respond(reqh, NULL, -EACCES, 0);
+ }
+ break;
+
+ default:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ }
+ }
+ free(buf);
+}
+
+static void trylogin(const char *user) {
+ if (strcmp(user, "root") != 0) {
+ char buf[128];
+ snprintf(buf, sizeof buf, "/Users/%s/", user);
+ if (chdir(buf) < 0) {
+ printf("no such user: %s\n", user);
+ return;
+ }
+ MOUNT_AT("/") { drv(user); }
+ }
+
+ execv(shell, NULL);
+ fprintf(stderr, "login: couldn't launch %s\n", shell);
+ exit(1);
+}
+
+int main(void) {
+ char user[64];
+ printf("\nCamellia\n");
+ for (;;) {
+ printf("login: ");
+ fgets(user, sizeof user, stdin);
+ if (ferror(stdin)) return -1;
+
+ cutspace(user);
+ if (user[0]) trylogin(user);
+ }
+}
diff --git a/src/cmd/netdog/nd.c b/src/cmd/netdog/nd.c
new file mode 100644
index 0000000..af5c264
--- /dev/null
+++ b/src/cmd/netdog/nd.c
@@ -0,0 +1,48 @@
+#include <camellia.h>
+#include <camellia/syscalls.h>
+#include <stdio.h>
+#include <string.h>
+#include <thread.h>
+
+#define eprintf(fmt, ...) fprintf(stderr, "netdog: "fmt"\n" __VA_OPT__(,) __VA_ARGS__)
+
+hid_t conn;
+
+void send_stdin(void *arg) { (void)arg;
+ static char buf[4096];
+ for (;;) {
+ // TODO define STDIN_FILENO
+ long ret = _sys_read(0, buf, sizeof buf, -1);
+ if (ret <= 0) return; /* instead of sending an empty packet, quit. */
+ ret = _sys_write(conn, buf, ret, -1, 0);
+ if (ret < 0) return;
+ }
+}
+
+void recv_stdout(void *arg) { (void)arg;
+ static char buf[4096];
+ for (;;) {
+ long ret = _sys_read(conn, buf, sizeof buf, -1);
+ if (ret < 0) return;
+ ret = _sys_write(1, buf, ret, -1, 0);
+ if (ret < 0) return;
+ }
+}
+
+int main(int argc, char **argv) {
+ if (argc < 2) {
+ eprintf("no argument");
+ return 1;
+ }
+
+ conn = camellia_open(argv[1], OPEN_RW);
+ if (conn < 0) {
+ eprintf("couldn't open '%s', err %u", argv[1], -conn);
+ return -conn;
+ }
+
+ thread_create(0, send_stdin, NULL);
+ thread_create(0, recv_stdout, NULL);
+ _sys_await();
+ return 0;
+}
diff --git a/src/cmd/netstack/arp.c b/src/cmd/netstack/arp.c
new file mode 100644
index 0000000..3a1c8da
--- /dev/null
+++ b/src/cmd/netstack/arp.c
@@ -0,0 +1,151 @@
+#include "proto.h"
+#include "util.h"
+#include <assert.h>
+#include <camellia/syscalls.h>
+#include <string.h>
+
+enum {
+ HdrType = 0,
+ HdrTypeEther = 1,
+ ProtoType = 2,
+ HdrALen = 4,
+ ProtoALen = 5,
+ Operation = 6,
+ OpReq = 1,
+ OpReply = 2,
+};
+
+struct arpc {
+ struct arpc *next;
+ uint32_t ip;
+ mac_t mac;
+};
+static struct arpc *arpcache;
+static void arpcache_put(uint32_t ip, mac_t mac);
+
+void arp_parse(const uint8_t *buf, size_t len) {
+ if (len < Operation + 2) return;
+ uint16_t htype = nget16(buf + HdrType);
+ uint16_t ptype = nget16(buf + ProtoType);
+ uint16_t op = nget16(buf + Operation);
+
+ if (!(htype == HdrTypeEther && ptype == ET_IPv4)) return;
+ enum { /* only valid for this combination of header + proto */
+ SrcMAC = 8,
+ SrcIP = 14,
+ DstMAC = 18,
+ DstIP = 24,
+ };
+ if (len < DstIP + 4) return;
+ arpcache_put(nget32(buf + SrcIP), *(mac_t*)buf + SrcMAC);
+
+ if (op == OpReq) {
+ uint32_t daddr = nget32(buf + DstIP);
+ if (daddr == state.ip) {
+ uint8_t *pkt = ether_start(30, (struct ethernet){
+ .dst = (void*)(buf + SrcMAC),
+ .type = ET_ARP,
+ });
+ nput16(pkt + HdrType, HdrTypeEther);
+ nput16(pkt + ProtoType, ET_IPv4);
+ pkt[HdrALen] = 6;
+ pkt[ProtoALen] = 4;
+ nput16(pkt + Operation, OpReply);
+ memcpy(pkt + SrcMAC, state.mac, 6);
+ nput32(pkt + SrcIP, state.ip);
+ memcpy(pkt + DstMAC, buf + SrcMAC, 10); /* sender's MAC and IP */
+ ether_finish(pkt);
+ }
+ }
+}
+
+void arp_request(uint32_t ip) {
+ enum {
+ SrcMAC = 8,
+ SrcIP = 14,
+ DstMAC = 18,
+ DstIP = 24,
+ };
+ uint8_t *pkt = ether_start(28, (struct ethernet){
+ .src = &state.mac,
+ .dst = &MAC_BROADCAST,
+ .type = ET_ARP,
+ });
+ nput16(pkt + HdrType, HdrTypeEther);
+ nput16(pkt + ProtoType, ET_IPv4);
+ pkt[HdrALen] = 6;
+ pkt[ProtoALen] = 4;
+ nput16(pkt + Operation, OpReq);
+ memcpy(pkt + SrcMAC, state.mac, 6);
+ nput32(pkt + SrcIP, state.ip);
+ memcpy(pkt + DstMAC, &MAC_BROADCAST, 6);
+ nput32(pkt + DstIP, ip);
+ ether_finish(pkt);
+}
+
+static void arpcache_put(uint32_t ip, mac_t mac) {
+ for (struct arpc *iter = arpcache; iter; iter = iter->next) {
+ if (memcmp(iter->mac, mac, 6) == 0) {
+ if (iter->ip == ip) return; /* cache entry correct */
+ else break; /* cache entry needs updating */
+ }
+ }
+ struct arpc *e = malloc(sizeof *e);
+ e->next = arpcache;
+ e->ip = ip;
+ memcpy(e->mac, mac, 6);
+ arpcache = e;
+}
+
+int arpcache_get(uint32_t ip, mac_t *mac) {
+ for (struct arpc *iter = arpcache; iter; iter = iter->next) {
+ if (iter->ip == ip) {
+ if (mac) memcpy(mac, iter->mac, 6);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+void arp_fsread(hid_t h, long offset) {
+ const char *fmt = "%08x\t%02x:%02x:%02x:%02x:%02x:%02x\n";
+ long linelen = snprintf(NULL, 0, fmt, 0, 1, 2, 3, 4, 5, 6) + 1;
+ char buf[28];
+ assert(linelen <= (long)sizeof(buf));
+ if (offset < 0) goto err;
+
+ struct arpc *cur = arpcache;
+ if (!cur) goto err;
+ for (; linelen <= offset; offset -= linelen) {
+ cur = cur->next;
+ if (!cur) goto err;
+ }
+ assert(0 <= offset && offset < linelen);
+
+ snprintf(buf, sizeof buf, fmt, cur->ip,
+ cur->mac[0],
+ cur->mac[1],
+ cur->mac[2],
+ cur->mac[3],
+ cur->mac[4],
+ cur->mac[5]);
+ _sys_fs_respond(h, buf + offset, linelen - offset, 0);
+ return;
+err:
+ _sys_fs_respond(h, NULL, -1, 0);
+}
+
+long arp_fswrite(const char *buf, long len, long offset) {
+ if (offset != -1) return -1;
+ uint32_t ip;
+ char tmp[16];
+ size_t iplen = len < 15 ? len : 15;
+ memcpy(tmp, buf, iplen);
+ tmp[iplen] = '\0';
+ if (ip_parse(tmp, &ip) < 0) {
+ return -1;
+ } else {
+ arp_request(ip);
+ return len;
+ }
+}
diff --git a/src/cmd/netstack/ether.c b/src/cmd/netstack/ether.c
new file mode 100644
index 0000000..52abac2
--- /dev/null
+++ b/src/cmd/netstack/ether.c
@@ -0,0 +1,59 @@
+#include <camellia/syscalls.h>
+#include "proto.h"
+#include "util.h"
+
+enum {
+ DstMAC = 0,
+ SrcMAC = 6,
+ EtherType = 12,
+ Payload = 14,
+};
+struct ethq *ether_queue;
+
+void ether_parse(const uint8_t *buf, size_t len) {
+ struct ethernet ether = (struct ethernet){
+ .src = (void*)(buf + SrcMAC),
+ .dst = (void*)(buf + DstMAC),
+ .type = nget16(buf + EtherType),
+ };
+
+ for (struct ethq **iter = &ether_queue; iter && *iter; ) {
+ struct ethq *qe = *iter;
+ _sys_fs_respond(qe->h, buf, len, 0);
+ /* remove entry */
+ /* yes, doing it this way here doesn't make sense. i'm preparing for filtering */
+ *iter = qe->next;
+ free(qe);
+ }
+
+ switch (ether.type) {
+ case ET_IPv4:
+ ipv4_parse(buf + Payload, len - Payload, ether);
+ break;
+ case ET_ARP:
+ arp_parse(buf + Payload, len - Payload);
+ break;
+ }
+}
+
+static const size_t fhoff = sizeof(size_t);
+uint8_t *ether_start(size_t len, struct ethernet ether) {
+ if (len < 60 - Payload) len = 60 - Payload;
+
+ if (!ether.dst) eprintf("NULL ether.dst!"); // TODO arp? i guess?
+ if (!ether.src) ether.src = &state.mac;
+
+ uint8_t *buf = malloc(fhoff + Payload + len);
+ memset(buf, 0, fhoff + Payload + len);
+ *(size_t*)buf = len + Payload;
+ memcpy(buf + fhoff + DstMAC, ether.dst, 6);
+ memcpy(buf + fhoff + SrcMAC, ether.src, 6);
+ nput16(buf + fhoff + EtherType, ether.type);
+ return buf + fhoff + Payload;
+}
+void ether_finish(uint8_t *pkt) {
+ uint8_t *buf = pkt - Payload - fhoff;
+ size_t len = *(size_t*)buf;
+ _sys_write(state.raw_h, buf + fhoff, len, 0, 0);
+ free(buf);
+}
diff --git a/src/cmd/netstack/fs.c b/src/cmd/netstack/fs.c
new file mode 100644
index 0000000..6d51c35
--- /dev/null
+++ b/src/cmd/netstack/fs.c
@@ -0,0 +1,320 @@
+/*
+ * path format:
+ * /net/raw
+ * raw ethernet frames (read-write)
+ * /net/arp
+ * ARP cache (currently read-only)
+ * /net/connect/0.0.0.0/1.2.3.4/udp/53
+ * connect from 0.0.0.0 (any ip) to 1.2.3.4 on udp port 53
+ * /net/listen/0.0.0.0/{tcp,udp}/53
+ * waits for a connection to any ip on udp port 53
+ * open() returns once a connection to ip 0.0.0.0 on udp port 53 is received
+ */
+#include "proto.h"
+#include "util.h"
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+enum handle_type {
+ H_ETHER,
+ H_TCP,
+ H_UDP,
+ H_ARP,
+};
+
+struct strqueue {
+ struct strqueue *next;
+ size_t len;
+ char buf[];
+};
+
+struct handle {
+ enum handle_type type;
+ struct {
+ struct udp_conn *c;
+ struct strqueue *rx, *rxlast;
+ } udp;
+ struct {
+ struct tcp_conn *c;
+ size_t readcap;
+ } tcp;
+ bool dead;
+ hid_t reqh;
+};
+
+
+static void tcp_listen_callback(struct tcp_conn *c, void *arg) {
+ struct handle *h = arg;
+ h->tcp.c = c;
+ _sys_fs_respond(h->reqh, h, 0, 0);
+ h->reqh = -1;
+}
+
+/* also called from recv_enqueue. yes, it's a mess */
+static void tcp_recv_callback(void *arg) {
+ struct handle *h = arg;
+ char buf[1024];
+ if (h->reqh >= 0) {
+ if (h->tcp.readcap > sizeof buf)
+ h->tcp.readcap = sizeof buf;
+ size_t len = tcpc_tryread(h->tcp.c, buf, h->tcp.readcap);
+ if (len > 0) {
+ _sys_fs_respond(h->reqh, buf, len, 0);
+ h->reqh = -1;
+ }
+ }
+}
+
+static void tcp_close_callback(void *arg) {
+ struct handle *h = arg;
+ h->dead = true;
+ if (h->reqh >= 0) {
+ _sys_fs_respond(h->reqh, NULL, -ECONNRESET, 0);
+ h->reqh = -1;
+ return;
+ }
+}
+
+static void udp_listen_callback(struct udp_conn *c, void *arg) {
+ struct handle *h = arg;
+ h->udp.c = c;
+ _sys_fs_respond(h->reqh, h, 0, 0);
+ h->reqh = -1;
+}
+
+static void udp_recv_callback(const void *buf, size_t len, void *arg) {
+ struct handle *h = arg;
+ if (h->reqh >= 0) {
+ _sys_fs_respond(h->reqh, buf, len, 0);
+ h->reqh = -1;
+ return;
+ }
+ struct strqueue *sq = malloc(sizeof(*sq) + len);
+ sq->next = NULL;
+ sq->len = len;
+ memcpy(sq->buf, buf, len);
+ if (h->udp.rx) {
+ h->udp.rxlast->next = sq;
+ h->udp.rxlast = sq;
+ } else {
+ h->udp.rx = sq;
+ h->udp.rxlast = sq;
+ }
+}
+
+static void recv_enqueue(struct handle *h, hid_t reqh, size_t readcap) {
+ if (h->reqh > 0) {
+ // TODO queue
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ return;
+ }
+ if (h->type == H_UDP && h->udp.rx) {
+ _sys_fs_respond(reqh, h->udp.rx->buf, h->udp.rx->len, 0);
+ h->udp.rx = h->udp.rx->next;
+ free(h->udp.rx);
+ return;
+ }
+ h->reqh = reqh;
+ if (h->type == H_TCP) {
+ h->tcp.readcap = readcap;
+ tcp_recv_callback(h);
+ }
+}
+
+static void fs_open(hid_t reqh, char *path, int flags) {
+#define respond(buf, val) do{ _sys_fs_respond(reqh, buf, val, 0); return; }while(0)
+ struct handle *h;
+ if (*path != '/') respond(NULL, -1);
+ path++;
+
+ if (strcmp(path, "raw") == 0) {
+ h = malloc(sizeof *h);
+ memset(h, 0, sizeof *h);
+ h->type = H_ETHER;
+ respond(h, 0);
+ } else if (strcmp(path, "arp") == 0) {
+ h = malloc(sizeof *h);
+ memset(h, 0, sizeof *h);
+ h->type = H_ARP;
+ respond(h, 0);
+ }
+
+ /* everything below ends up sending packets */
+ if (!OPEN_WRITEABLE(flags))
+ respond(NULL, -EACCES);
+
+ char *save;
+ const char *verb, *proto, *port_s;
+ uint32_t srcip, dstip;
+
+ verb = strtok_r(path, "/", &save);
+ if (!verb) respond(NULL, -1);
+
+ if (ip_parse(strtok_r(NULL, "/", &save), &srcip) < 0)
+ respond(NULL, -1);
+ if (srcip != 0) {
+ eprintf("unimplemented");
+ respond(NULL, -1);
+ }
+
+ if (strcmp(verb, "listen") == 0) {
+ proto = strtok_r(NULL, "/", &save);
+ if (!proto) respond(NULL, -1);
+ if (strcmp(proto, "udp") == 0) {
+ port_s = strtok_r(NULL, "/", &save);
+ if (port_s) {
+ uint16_t port = strtol(port_s, NULL, 0);
+ h = malloc(sizeof *h);
+ memset(h, 0, sizeof *h);
+ h->type = H_UDP;
+ h->reqh = reqh;
+ udp_listen(port, udp_listen_callback, udp_recv_callback, h);
+ return;
+ }
+ }
+ if (strcmp(proto, "tcp") == 0) {
+ port_s = strtok_r(NULL, "/", &save);
+ if (port_s) {
+ uint16_t port = strtol(port_s, NULL, 0);
+ h = malloc(sizeof *h);
+ memset(h, 0, sizeof *h);
+ h->type = H_TCP;
+ h->reqh = reqh;
+ tcp_listen(port, tcp_listen_callback, tcp_recv_callback, tcp_close_callback, h);
+ return;
+ }
+ }
+ } else if (strcmp(verb, "connect") == 0) {
+ if (ip_parse(strtok_r(NULL, "/", &save), &dstip) < 0)
+ respond(NULL, -1);
+ proto = strtok_r(NULL, "/", &save);
+ if (!proto) respond(NULL, -1);
+ if (strcmp(proto, "tcp") == 0) {
+ port_s = strtok_r(NULL, "/", &save);
+ if (port_s) {
+ uint16_t port = strtol(port_s, NULL, 0);
+ h = malloc(sizeof *h);
+ memset(h, 0, sizeof *h);
+ h->type = H_TCP;
+ h->tcp.c = tcpc_new((struct tcp){
+ .dst = port,
+ .ip.dst = dstip,
+ }, tcp_recv_callback, tcp_close_callback, h);
+ if (h->tcp.c) {
+ respond(h, 0);
+ } else {
+ free(h);
+ respond(NULL, -1);
+ }
+ }
+ }
+ if (strcmp(proto, "udp") == 0) {
+ port_s = strtok_r(NULL, "/", &save);
+ if (port_s) {
+ uint16_t port = strtol(port_s, NULL, 0);
+ h = malloc(sizeof *h);
+ memset(h, 0, sizeof *h);
+ h->type = H_UDP;
+ h->udp.c = udpc_new((struct udp){
+ .dst = port,
+ .ip.dst = dstip,
+ }, udp_recv_callback, h);
+ if (h->udp.c) {
+ respond(h, 0);
+ } else {
+ free(h);
+ respond(NULL, -1);
+ }
+ }
+ }
+ }
+ respond(NULL, -1);
+#undef respond
+}
+
+void fs_thread(void *arg) { (void)arg;
+ const size_t buflen = 4096;
+ char *buf = malloc(buflen);
+ for (;;) {
+ struct ufs_request res;
+ hid_t reqh = _sys_fs_wait(buf, buflen, &res);
+ if (reqh < 0) break;
+ struct handle *h = res.id;
+ long ret;
+ switch (res.op) {
+ case VFSOP_OPEN:
+ if (res.len < buflen) {
+ buf[res.len] = '\0';
+ fs_open(reqh, buf, res.flags);
+ } else {
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ }
+ break;
+ case VFSOP_READ:
+ if (h->dead) {
+ _sys_fs_respond(reqh, NULL, -ECONNRESET, 0);
+ break;
+ }
+ switch (h->type) {
+ case H_ETHER: {
+ struct ethq *qe;
+ qe = malloc(sizeof *qe);
+ qe->h = reqh;
+ qe->next = ether_queue;
+ ether_queue = qe;
+ break;}
+ case H_TCP:
+ case H_UDP:
+ recv_enqueue(h, reqh, res.capacity);
+ break;
+ case H_ARP:
+ arp_fsread(reqh, res.offset);
+ break;
+ default:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ }
+ break;
+ case VFSOP_WRITE:
+ if (h->dead) {
+ _sys_fs_respond(reqh, NULL, -ECONNRESET, 0);
+ break;
+ }
+ switch (h->type) {
+ case H_ETHER:
+ ret = _sys_write(state.raw_h, buf, res.len, 0, 0);
+ _sys_fs_respond(reqh, NULL, ret, 0);
+ break;
+ case H_TCP:
+ tcpc_send(h->tcp.c, buf, res.len);
+ _sys_fs_respond(reqh, NULL, res.len, 0);
+ break;
+ case H_UDP:
+ udpc_send(h->udp.c, buf, res.len);
+ _sys_fs_respond(reqh, NULL, res.len, 0);
+ break;
+ case H_ARP:
+ _sys_fs_respond(reqh, NULL, arp_fswrite(buf, res.len, res.offset), 0);
+ break;
+ default:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ }
+ break;
+ case VFSOP_CLOSE:
+ // TODO remove entries in queue
+ // TODO why does close even have _sys_fs_respond?
+ if (h->type == H_TCP) tcpc_close(h->tcp.c);
+ if (h->type == H_UDP) udpc_close(h->udp.c);
+ free(h);
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ default:
+ _sys_fs_respond(reqh, NULL, -1, 0);
+ break;
+ }
+ }
+ free(buf);
+}
diff --git a/src/cmd/netstack/icmp.c b/src/cmd/netstack/icmp.c
new file mode 100644
index 0000000..0c6a502
--- /dev/null
+++ b/src/cmd/netstack/icmp.c
@@ -0,0 +1,34 @@
+#include "proto.h"
+#include "util.h"
+
+enum {
+ Type = 0,
+ Code = 1,
+ Checksum = 2,
+ Payload = 4,
+};
+
+void icmp_parse(const uint8_t *buf, size_t len, struct ipv4 ip) {
+ if (len < Payload) return;
+ uint8_t type = buf[Type];
+ if (type == 8 && ip.dst == state.ip) {
+ /* echo reply */
+ icmp_send(buf + Payload, len - Payload, (struct icmp){
+ .type = 0,
+ .ip.dst = ip.src,
+ .ip.e.dst = ip.e.src,
+ });
+ }
+}
+
+void icmp_send(const void *payload, size_t len, struct icmp i) {
+ i.ip.proto = 1;
+ uint8_t *pkt = malloc(Payload + len);
+ pkt[Type] = i.type;
+ pkt[Code] = i.code;
+ memcpy(pkt + Payload, payload, len);
+ nput16(pkt + Checksum, 0);
+ nput16(pkt + Checksum, ip_checksum(pkt, Payload + len));
+ ipv4_send(pkt, Payload + len, i.ip);
+ free(pkt);
+}
diff --git a/src/cmd/netstack/ipv4.c b/src/cmd/netstack/ipv4.c
new file mode 100644
index 0000000..1336dc1
--- /dev/null
+++ b/src/cmd/netstack/ipv4.c
@@ -0,0 +1,216 @@
+#include "proto.h"
+#include "util.h"
+#include <assert.h>
+#include <stdlib.h>
+
+enum {
+ Version = 0,
+ HdrLen = 0,
+ TotalLen = 2,
+ Id = 4,
+ FragInfo = 6,
+ EvilBit = 0x8000,
+ DontFrag = 0x4000,
+ MoreFrags = 0x2000,
+ FragOff = 0x1FFF,
+ TTL = 8,
+ Proto = 9,
+ Checksum = 10,
+ SrcIP = 12,
+ DstIP = 16,
+};
+static void ipv4_dispatch(const uint8_t *buf, size_t len, struct ipv4 ip);
+
+struct fragment {
+ struct fragment *next; /* sorted */
+ size_t len, offset;
+ bool last;
+ uint8_t buf[];
+};
+struct fragmented {
+ /* src, dst, proto, id come from the first arrived packet
+ * and are used to tell fragmenteds apart.
+ * the rest comes from the sequentially first packet.
+ * ip.h.header points to a malloc'd buffer*/
+ struct ipv4 h;
+
+ struct fragment *first;
+ struct fragmented *next, **prev; /* *(inc->prev) == inc */
+ // TODO timer
+};
+struct fragmented *fragmenteds;
+static struct fragmented *fragmented_find(struct ipv4 fraghdr);
+static void fragmented_tryinsert(const uint8_t *payload, size_t plen, struct ipv4 ip);
+static void fragmented_tryfinish(struct fragmented *inc);
+static void fragmented_free(struct fragmented *inc);
+
+static struct fragmented *fragmented_find(struct ipv4 fraghdr) {
+ struct fragmented *inc;
+ for (inc = fragmenteds; inc; inc = inc->next) {
+ if (inc->h.src == fraghdr.src &&
+ inc->h.dst == fraghdr.dst &&
+ inc->h.proto == fraghdr.proto &&
+ inc->h.id == fraghdr.id)
+ {
+ return inc;
+ }
+ }
+ inc = malloc(sizeof *inc);
+ memset(inc, 0, sizeof *inc);
+ inc->h.src = fraghdr.src;
+ inc->h.dst = fraghdr.dst;
+ inc->h.proto = fraghdr.proto;
+ inc->h.id = fraghdr.id;
+
+ inc->next = fragmenteds;
+ if (inc->next) inc->next->prev = &inc->next;
+ inc->prev = &fragmenteds;
+ *inc->prev = inc;
+ return inc;
+}
+
+static void fragmented_tryinsert(const uint8_t *payload, size_t plen, struct ipv4 ip) {
+ struct fragmented *inc = fragmented_find(ip);
+ size_t off = (ip.fraginfo & FragOff) * 8;
+ bool last = !(ip.fraginfo & MoreFrags);
+ // eprintf("fragmented packet, %u + %u, part of 0x%x", off, plen, inc);
+
+ /* find the first fragment at a bigger offset, and insert before it */
+ struct fragment **insert = &inc->first;
+ for (; *insert; insert = &(*insert)->next) {
+ if ((*insert)->offset > off) break;
+ if ((*insert)->offset == off) return; /* duplicate packet */
+ }
+ /* later on: frag->next = *insert;
+ * if we're the last fragment, frag->next must == NULL */
+ if (last && *insert != NULL) return;
+
+ struct fragment *frag = malloc(sizeof(struct fragment) + plen);
+ frag->next = *insert;
+ *insert = frag;
+ frag->len = plen;
+ frag->offset = off;
+ frag->last = last;
+ memcpy(frag->buf, payload, plen);
+
+ if (off == 0) { /* save header */
+ assert(!inc->h.header);
+ void *headercpy = malloc(ip.hlen);
+ memcpy(headercpy, ip.header, ip.hlen);
+ inc->h = ip;
+ inc->h.header = headercpy;
+ }
+
+ fragmented_tryfinish(inc);
+}
+
+static void fragmented_tryfinish(struct fragmented *inc) {
+ if (inc->first->offset != 0) return;
+ for (struct fragment *iter = inc->first; iter; iter = iter->next) {
+ size_t iterend = iter->offset + iter->len;
+ struct fragment *next = iter->next;
+ if (next) {
+ if (iterend < next->offset) return; /* incomplete */
+ if (iterend > next->offset) {
+ fragmented_free(inc);
+ return;
+ }
+ } else if (iter->last) {
+ void *buf = malloc(iterend);
+ for (struct fragment *iter = inc->first; iter; iter = iter->next) {
+ assert(iter->offset + iter->len <= iterend);
+ memcpy(buf + iter->offset, iter->buf, iter->len);
+ }
+ ipv4_dispatch(buf, iterend, inc->h);
+ free(buf);
+ fragmented_free(inc);
+ }
+ }
+}
+
+static void fragmented_free(struct fragmented *inc) {
+ if (inc->next) {
+ inc->next->prev = inc->prev;
+ *inc->next->prev = inc->next;
+ } else {
+ *inc->prev = NULL;
+ }
+
+ for (struct fragment *next, *iter = inc->first; iter; iter = next) {
+ next = iter->next;
+ free(iter);
+ }
+ free((void*)inc->h.header);
+ free(inc);
+}
+
+
+static void ipv4_dispatch(const uint8_t *buf, size_t len, struct ipv4 ip) {
+ switch (ip.proto) {
+ case 0x01: icmp_parse(buf, len, ip); break;
+ case 0x06: tcp_parse(buf, len, ip); break;
+ case 0x11: udp_parse(buf, len, ip); break;
+ }
+}
+
+void ipv4_parse(const uint8_t *buf, size_t len, struct ethernet ether) {
+ uint8_t version, headerlen;
+ uint16_t totallen;
+
+ version = buf[Version] >> 4;
+ if (version != 4) return;
+ headerlen = (buf[HdrLen] & 0xf) * 4;
+ totallen = nget16(buf + TotalLen);
+ if (totallen < headerlen) return;
+
+ /* ignores checksum. TODO? */
+
+ struct ipv4 ip = (struct ipv4){
+ .e = ether,
+ .src = nget32(buf + SrcIP),
+ .dst = nget32(buf + DstIP),
+ .id = nget16(buf + Id),
+ .fraginfo = nget16(buf + FragInfo),
+ .proto = buf[Proto],
+ .header = buf,
+ .hlen = headerlen,
+ };
+
+ if (ip.fraginfo & ~(EvilBit | DontFrag)) {
+ fragmented_tryinsert(buf + headerlen, totallen - headerlen, ip);
+ } else {
+ if (totallen > len) return;
+ ipv4_dispatch(buf + headerlen, totallen - headerlen, ip);
+ }
+}
+
+static uint16_t next_id = 0;
+void ipv4_send(const void *payload, size_t len, struct ipv4 ip) {
+ const size_t mtu = 1500;
+ const size_t hdrlen = 20;
+
+ ip.e.type = ET_IPv4;
+ if (!ip.src) ip.src = state.ip;
+ if (!ip.e.dst && ip.dst == 0xFFFFFFFF)
+ ip.e.dst = &MAC_BROADCAST;
+
+ uint16_t id = next_id++;
+ for (size_t off = 0, fraglen = mtu - hdrlen; off < len; off += fraglen) {
+ if (fraglen > len - off)
+ fraglen = len - off;
+ bool last = off + fraglen >= len;
+ uint8_t *pkt = ether_start(hdrlen + fraglen, ip.e);
+ pkt[Version] = 0x40;
+ pkt[HdrLen] |= hdrlen / 4;
+ nput16(pkt + TotalLen, hdrlen + fraglen);
+ nput16(pkt + Id, id);
+ nput16(pkt + FragInfo, (off >> 3) | (last ? 0 : MoreFrags));
+ pkt[TTL] = 0xFF;
+ pkt[Proto] = ip.proto;
+ nput32(pkt + SrcIP, ip.src);
+ nput32(pkt + DstIP, ip.dst);
+ nput16(pkt + Checksum, ip_checksum(pkt, hdrlen));
+ memcpy(pkt + hdrlen, payload + off, fraglen);
+ ether_finish(pkt);
+ }
+}
diff --git a/src/cmd/netstack/netstack.c b/src/cmd/netstack/netstack.c
new file mode 100644
index 0000000..2636429
--- /dev/null
+++ b/src/cmd/netstack/netstack.c
@@ -0,0 +1,53 @@
+#include "proto.h"
+#include "util.h"
+#include <camellia.h>
+#include <camellia/syscalls.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <thread.h>
+
+struct net_state state = {
+ // TODO dynamically get mac
+ .mac = {0x52, 0x54, 0x00, 0xCA, 0x77, 0x1A},
+};
+
+void network_thread(void *arg) { (void)arg;
+ const size_t buflen = 4096;
+ char *buf = malloc(buflen);
+ for (;;) {
+ long ret = _sys_read(state.raw_h, buf, buflen, -1);
+ if (ret < 0) break;
+ ether_parse((void*)buf, ret);
+ }
+ free(buf);
+}
+
+void fs_thread(void *arg);
+
+int main(int argc, char **argv) {
+ if (argc < 4) {
+ eprintf("usage: netstack iface ip gateway");
+ return 1;
+ }
+ state.raw_h = camellia_open(argv[1], OPEN_RW);
+ if (state.raw_h < 0) {
+ eprintf("couldn't open %s", argv[1]);
+ return 1;
+ }
+ if (ip_parse(argv[2], &state.ip) < 0) {
+ eprintf("invalid ip");
+ return -1;
+ }
+ if (ip_parse(argv[3], &state.gateway) < 0) {
+ eprintf("invalid gateway");
+ return -1;
+ }
+ setproctitle(argv[2]);
+ arp_request(state.gateway);
+ thread_create(0, network_thread, NULL);
+ thread_create(0, fs_thread, NULL);
+ _sys_await();
+ return 0;
+}
diff --git a/src/cmd/netstack/proto.h b/src/cmd/netstack/proto.h
new file mode 100644
index 0000000..8ea11ac
--- /dev/null
+++ b/src/cmd/netstack/proto.h
@@ -0,0 +1,107 @@
+#pragma once
+#include <camellia/types.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+typedef uint8_t mac_t[6];
+static const mac_t MAC_BROADCAST = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+extern struct net_state {
+ mac_t mac;
+ uint32_t ip, gateway;
+
+ hid_t raw_h;
+} state;
+
+enum { /* ethertype */
+ ET_IPv4 = 0x800,
+ ET_ARP = 0x806,
+};
+
+struct ethernet {
+ const mac_t *src, *dst;
+ uint16_t type;
+};
+
+struct ipv4 {
+ struct ethernet e;
+ uint32_t src, dst;
+ uint16_t id, fraginfo;
+ uint8_t proto;
+ const uint8_t *header; size_t hlen;
+};
+
+struct tcp {
+ struct ipv4 ip;
+ uint16_t src, dst;
+};
+
+struct udp {
+ struct ipv4 ip;
+ uint16_t src, dst;
+};
+
+struct icmp {
+ struct ipv4 ip;
+ uint8_t type, code;
+};
+
+
+/* NOT THREADSAFE, YET USED FROM CONCURRENT THREADS
+ * will break if i implement a scheduler*/
+struct ethq {
+ struct ethq *next;
+ hid_t h;
+};
+extern struct ethq *ether_queue;
+
+void arp_parse(const uint8_t *buf, size_t len);
+void arp_request(uint32_t ip);
+/* 0 on success, -1 on failure */
+int arpcache_get(uint32_t ip, mac_t *mac);
+void arp_fsread(hid_t h, long offset);
+long arp_fswrite(const char *buf, long len, long offset);
+
+void icmp_parse(const uint8_t *buf, size_t len, struct ipv4 ip);
+void icmp_send(const void *payload, size_t len, struct icmp i);
+
+void ipv4_parse(const uint8_t *buf, size_t len, struct ethernet ether);
+void ipv4_send(const void *payload, size_t len, struct ipv4 ip);
+
+void ether_parse(const uint8_t *buf, size_t len);
+uint8_t *ether_start(size_t len, struct ethernet ether);
+void ether_finish(uint8_t *pkt);
+
+struct udp_conn;
+void udp_parse(const uint8_t *buf, size_t len, struct ipv4 ip);
+/* calls callback once, after a client connects. */
+void udp_listen(
+ uint16_t port,
+ void (*on_conn)(struct udp_conn *, void *carg),
+ void (*on_recv)(const void *, size_t, void *carg),
+ void *carg);
+struct udp_conn *udpc_new(
+ struct udp u,
+ void (*on_recv)(const void *, size_t, void *carg),
+ void *carg);
+// TODO udp_onclosed
+void udpc_send(struct udp_conn *, const void *buf, size_t len);
+/* frees */
+void udpc_close(struct udp_conn *);
+
+struct tcp_conn;
+void tcp_parse(const uint8_t *buf, size_t len, struct ipv4 ip);
+void tcp_listen(
+ uint16_t port,
+ void (*on_conn)(struct tcp_conn *, void *carg),
+ void (*on_recv)(void *carg),
+ void (*on_close)(void *carg),
+ void *carg);
+struct tcp_conn *tcpc_new(
+ struct tcp t,
+ void (*on_recv)(void *carg),
+ void (*on_close)(void *carg),
+ void *carg);
+size_t tcpc_tryread(struct tcp_conn *, void *buf, size_t len);
+void tcpc_send(struct tcp_conn *, const void *buf, size_t len);
+void tcpc_close(struct tcp_conn *);
diff --git a/src/cmd/netstack/tcp.c b/src/cmd/netstack/tcp.c
new file mode 100644
index 0000000..d1adeab
--- /dev/null
+++ b/src/cmd/netstack/tcp.c
@@ -0,0 +1,268 @@
+/* Welcome to spaghetti land.
+ * This is anything but production quality. It's throwaway code, meant
+ * only to see how networking could fit into the architecture of the
+ * system. */
+#include "proto.h"
+#include "util.h"
+#include <assert.h>
+#include <shared/ring.h>
+
+enum {
+ SrcPort = 0,
+ DstPort = 2,
+ Seq = 4,
+ AckNum = 8, /* last processed seq + 1 */
+ Flags = 12,
+ FlagSize = 0xF000, /* size of header / 4 bytes */
+ /* 3 bits: 0, still unused
+ * 3 bits: modern stuff not in the original RFC */
+ FlagURG = 0x0020, /* urgent */
+ FlagACK = 0x0010,
+ FlagPSH = 0x0008, /* force buffer flush */
+ FlagRST = 0x0004, /* reset connection */
+ FlagSYN = 0x0002, /* first packet; sync sequence numbers */
+ FlagFIN = 0x0001, /* last packet */
+ FlagAll = 0x003F,
+ WinSize = 14, /* amount of data we're willing to receive */
+ Checksum = 16,
+ Urgent = 18,
+ MinHdr = 20,
+};
+
+enum tcp_state {
+ LISTEN,
+ SYN_SENT,
+ SYN_RCVD,
+ ESTABILISHED,
+ LAST_ACK,
+ CLOSED,
+};
+
+struct tcp_conn {
+ uint32_t lip, rip;
+ mac_t rmac;
+ uint16_t lport, rport;
+ struct tcp_conn *next, **link;
+
+ enum tcp_state state;
+ uint32_t lack, rack;
+ uint32_t lseq;
+ bool uclosed; /* did the user close? */
+
+ void (*on_conn)(struct tcp_conn *, void *carg);
+ void (*on_recv)(void *carg);
+ void (*on_close)(void *carg);
+ void *carg;
+
+ ring_t rx;
+};
+static struct tcp_conn *conns;
+static void conns_append(struct tcp_conn *c) {
+ c->next = conns;
+ if (c->next)
+ c->next->link = &c->next;
+ c->link = &conns;
+ *c->link = c;
+}
+static void tcpc_sendraw(struct tcp_conn *c, uint16_t flags, const void *buf, size_t len) {
+ uint8_t *pkt = malloc(MinHdr + len);
+ memset(pkt, 0, MinHdr);
+
+ nput16(pkt + SrcPort, c->lport);
+ nput16(pkt + DstPort, c->rport);
+ nput32(pkt + Seq, c->lseq);
+ c->lseq += len;
+ nput32(pkt + AckNum, c->lack);
+ flags |= (MinHdr / 4) << 12;
+ nput16(pkt + Flags, flags);
+ nput16(pkt + WinSize, ring_avail(&c->rx));
+ memcpy(pkt + MinHdr, buf, len);
+ nput16(pkt + Checksum, ip_checksumphdr(pkt, MinHdr + len, c->lip, c->rip, 6));
+
+ ipv4_send(pkt, MinHdr + len, (struct ipv4){
+ .proto = 6,
+ .src = c->lip,
+ .dst = c->rip,
+ .e.dst = &c->rmac,
+ });
+ free(pkt);
+}
+void tcp_listen(
+ uint16_t port,
+ void (*on_conn)(struct tcp_conn *, void *carg),
+ void (*on_recv)(void *carg),
+ void (*on_close)(void *carg),
+ void *carg)
+{
+ struct tcp_conn *c = malloc(sizeof *c);
+ memset(c, 0, sizeof *c);
+ c->lport = port;
+ c->lip = state.ip;
+ c->state = LISTEN;
+ c->on_conn = on_conn;
+ c->on_recv = on_recv;
+ c->on_close = on_close;
+ c->carg = carg;
+ // TODO setting the ring size super low loses every nth byte. probably a bug with ring_t itself!
+ c->rx = (ring_t){malloc(4096), 4096, 0, 0};
+ conns_append(c);
+}
+struct tcp_conn *tcpc_new(
+ struct tcp t,
+ void (*on_recv)(void *carg),
+ void (*on_close)(void *carg),
+ void *carg)
+{
+ struct tcp_conn *c = malloc(sizeof *c);
+ memset(c, 0, sizeof *c);
+ c->lip = t.ip.src ? t.ip.src : state.ip;
+ c->rip = t.ip.dst;
+ c->lport = t.src ? t.src : 50002; // TODO randomize source ports
+ c->rport = t.dst;
+ if (arpcache_get(c->rip, &c->rmac) < 0) {
+ // TODO wait for ARP reply
+ arp_request(c->rip);
+ if (arpcache_get(state.gateway, &c->rmac) < 0) {
+ eprintf("neither target nor gateway not in ARP cache, dropping");
+ free(c);
+ return NULL;
+ }
+ }
+
+ c->state = SYN_SENT;
+ c->on_recv = on_recv;
+ c->on_close = on_close;
+ c->carg = carg;
+ c->rx = (ring_t){malloc(4096), 4096, 0, 0};
+ conns_append(c);
+
+ tcpc_sendraw(c, FlagSYN, NULL, 0);
+ c->lseq++;
+ return c;
+}
+size_t tcpc_tryread(struct tcp_conn *c, void *buf, size_t len) {
+ if (!buf) return ring_used(&c->rx);
+ return ring_get(&c->rx, buf, len);
+}
+void tcpc_send(struct tcp_conn *c, const void *buf, size_t len) {
+ tcpc_sendraw(c, FlagACK | FlagPSH, buf, len);
+}
+static void tcpc_tryfree(struct tcp_conn *c) {
+ if (c->state == CLOSED && c->uclosed) {
+ if (c->next) c->next->link = c->link;
+ *c->link = c->next;
+ free(c->rx.buf);
+ free(c);
+ }
+}
+void tcpc_close(struct tcp_conn *c) {
+ /* ONLY FOR USE BY THE USER, drops their reference */
+ assert(!c->uclosed);
+ c->uclosed = true;
+ if (c->state != CLOSED && c->state != LAST_ACK && c->state != LISTEN) {
+ tcpc_sendraw(c, FlagFIN | FlagACK, NULL, 0);
+ c->state = LAST_ACK;
+ c->on_conn = NULL;
+ c->on_close = NULL;
+ }
+ tcpc_tryfree(c);
+}
+
+void tcp_parse(const uint8_t *buf, size_t len, struct ipv4 ip) {
+ if (len < 20) return;
+ uint16_t srcport = nget16(buf + SrcPort);
+ uint16_t dstport = nget16(buf + DstPort);
+ uint32_t seq = nget32(buf + Seq);
+ uint32_t acknum = nget32(buf + AckNum);
+ uint16_t flags = nget16(buf + Flags);
+ // uint16_t winsize = nget16(buf + WinSize);
+ // uint16_t chksum = nget16(buf + Checksum);
+ uint16_t hdrlen = ((flags & FlagSize) >> 12) * 4;
+ if (hdrlen > len) return;
+ uint16_t payloadlen = len - hdrlen;
+
+ for (struct tcp_conn *iter = conns; iter; iter = iter->next) {
+ if (iter->state == CLOSED) continue;
+ if (iter->lport != dstport) continue;
+
+ if (iter->state == LISTEN && (flags & FlagAll) == FlagSYN) {
+ iter->state = SYN_RCVD;
+ iter->rip = ip.src;
+ iter->rport = srcport;
+ iter->lack = seq + 1;
+ memcpy(&iter->rmac, ip.e.src, sizeof(mac_t));
+ tcpc_sendraw(iter, FlagSYN | FlagACK, NULL, 0);
+ iter->lseq++;
+ if (iter->on_conn) iter->on_conn(iter, iter->carg);
+ return;
+ }
+
+ if (iter->rip == ip.src && iter->rport == srcport) {
+ // TODO doesn't handle seq/ack overflows
+ if (iter->state == SYN_SENT) {
+ if (flags & FlagSYN) {
+ iter->state = ESTABILISHED;
+ iter->lack = seq + 1;
+ tcpc_sendraw(iter, FlagACK, NULL, 0);
+ return;
+ } else {
+ // TODO resend syn?
+ return;
+ }
+ }
+ if (flags & FlagACK) {
+ if (iter->rack < acknum)
+ iter->rack = acknum;
+ if (iter->state == LAST_ACK) {
+ // TODO check if ack has correct number
+ iter->state = CLOSED;
+ tcpc_tryfree(iter);
+ // TODO free (also after a timeout)
+ return;
+ }
+ }
+ if (iter->lack != seq && iter->lack - 1 != seq) {
+ eprintf("remote seq jumped by %d", seq - iter->lack);
+ tcpc_sendraw(iter, FlagACK, NULL, 0);
+ return;
+ }
+ // TODO check if overflows window size
+ if (payloadlen) {
+ iter->lack = seq + payloadlen;
+ ring_put(&iter->rx, buf + hdrlen, payloadlen);
+ if (iter->on_recv) iter->on_recv(iter->carg);
+ tcpc_sendraw(iter, FlagACK, NULL, 0);
+ }
+ if (flags & FlagFIN) {
+ // TODO should resend the packet until an ACK is received
+ // TODO duplicated in tcpc_close
+ tcpc_sendraw(iter, FlagFIN | FlagACK, NULL, 0);
+ iter->state = LAST_ACK;
+ if (iter->on_close) iter->on_close(iter->carg);
+ return;
+ }
+ return;
+ }
+ }
+
+ if ((flags & FlagRST) == 0) {
+ uint8_t *pkt = malloc(MinHdr);
+ memset(pkt, 0, MinHdr);
+ nput16(pkt + SrcPort, dstport);
+ nput16(pkt + DstPort, srcport);
+ nput32(pkt + Seq, acknum);
+ nput32(pkt + AckNum, seq + 1);
+ uint16_t pktflags = FlagRST | FlagACK;
+ pktflags |= (MinHdr / 4) << 12;
+ nput16(pkt + Flags, pktflags);
+ nput16(pkt + Checksum, ip_checksumphdr(pkt, MinHdr, ip.src, ip.dst, 6));
+
+ ipv4_send(pkt, MinHdr, (struct ipv4){
+ .proto = 6,
+ .src = ip.dst,
+ .dst = ip.src,
+ .e.dst = ip.e.src,
+ });
+ free(pkt);
+ }
+}
diff --git a/src/cmd/netstack/udp.c b/src/cmd/netstack/udp.c
new file mode 100644
index 0000000..3d560ae
--- /dev/null
+++ b/src/cmd/netstack/udp.c
@@ -0,0 +1,124 @@
+#include "proto.h"
+#include "util.h"
+
+enum {
+ SrcPort = 0,
+ DstPort = 2,
+ Length = 4,
+ Checksum = 6,
+ Payload = 8,
+};
+
+
+struct udp_conn {
+ uint32_t lip, rip;
+ uint16_t lport, rport;
+ mac_t rmac;
+ void (*on_conn)(struct udp_conn *, void *); /* if non-NULL - listening, if NULL - estabilished */
+ void (*on_recv)(const void *, size_t, void *);
+ void *carg;
+ struct udp_conn *next, **link;
+};
+static struct udp_conn *conns;
+static void conns_append(struct udp_conn *c) {
+ c->next = conns;
+ if (c->next)
+ c->next->link = &c->next;
+ c->link = &conns;
+ *c->link = c;
+}
+void udp_listen(
+ uint16_t port,
+ void (*on_conn)(struct udp_conn *, void *carg),
+ void (*on_recv)(const void *, size_t, void *carg),
+ void *carg)
+{
+ if (!on_conn) return;
+ struct udp_conn *c = malloc(sizeof *c);
+ c->lport = port;
+ c->lip = state.ip;
+ c->on_conn = on_conn;
+ c->on_recv = on_recv;
+ c->carg = carg;
+ conns_append(c);
+}
+struct udp_conn *udpc_new(
+ struct udp u,
+ void (*on_recv)(const void *, size_t, void *carg),
+ void *carg)
+{
+ struct udp_conn *c = malloc(sizeof *c);
+ memset(c, 0, sizeof *c);
+ c->lip = u.ip.src;
+ c->rip = u.ip.dst;
+ c->lport = u.src ? u.src : 50000; // TODO randomize source ports
+ c->rport = u.dst;
+ if (arpcache_get(c->rip, &c->rmac) < 0) {
+ // TODO make arp request, wait for reply
+ eprintf("not in ARP cache, unimplemented");
+ free(c);
+ return NULL;
+ }
+ c->on_recv = on_recv;
+ c->carg = carg;
+ conns_append(c);
+ return c;
+}
+void udpc_send(struct udp_conn *c, const void *buf, size_t len) {
+ uint8_t *pkt = malloc(Payload + len);
+ nput16(pkt + SrcPort, c->lport);
+ nput16(pkt + DstPort, c->rport);
+ nput16(pkt + Length, Payload + len);
+ nput16(pkt + Checksum, 0);
+ memcpy(pkt + Payload, buf, len);
+ ipv4_send(pkt, Payload + len, (struct ipv4){
+ .proto = 0x11,
+ .src = c->lip,
+ .dst = c->rip,
+ .e.dst = &c->rmac,
+ });
+ free(pkt);
+}
+void udpc_close(struct udp_conn *c) {
+ if (c->next) c->next->link = c->link;
+ *(c->link) = c->next;
+ free(c);
+}
+
+
+void udp_parse(const uint8_t *buf, size_t len, struct ipv4 ip) {
+ uint16_t srcport = nget16(buf + SrcPort);
+ uint16_t dstport = nget16(buf + DstPort);
+ bool active = false;
+
+ for (struct udp_conn *iter = conns; iter; iter = iter->next) {
+ if (iter->on_conn && dstport == iter->lport) {
+ iter->on_conn(iter, iter->carg);
+ iter->on_conn = NULL;
+ iter->rport = srcport;
+ memcpy(&iter->rmac, ip.e.src, sizeof(mac_t));
+ iter->rip = ip.src;
+ }
+ if (iter->rip == ip.src &&
+ iter->rport == srcport &&
+ iter->lport == dstport &&
+ iter->on_recv)
+ {
+ active = true;
+ iter->on_recv(buf + Payload, len - Payload, iter->carg);
+ }
+ }
+
+ if (!active) {
+ uint8_t *pkt = malloc(4 + ip.hlen + 8);
+ nput32(pkt, 0);
+ memcpy(pkt + 4, ip.header, ip.hlen + 8);
+ icmp_send(pkt, 4 + ip.hlen + 8, (struct icmp){
+ .type = 3, /* destination unreachable */
+ .code = 3, /* port unreachable */
+ .ip.dst = ip.src,
+ .ip.e.dst = ip.e.src,
+ });
+ free(pkt);
+ }
+}
diff --git a/src/cmd/netstack/util.c b/src/cmd/netstack/util.c
new file mode 100644
index 0000000..68092aa
--- /dev/null
+++ b/src/cmd/netstack/util.c
@@ -0,0 +1,60 @@
+#include "util.h"
+
+/* https://www.w3.org/TR/PNG/#D-CRCAppendix */
+static uint32_t crc_table[256];
+uint32_t crc32(const uint8_t *buf, size_t len) {
+ if (!crc_table[1]) {
+ for (int i = 0; i < 256; i++) {
+ uint32_t c = i;
+ for (int j = 0; j < 8; j++)
+ c = ((c&1) ? 0xedb88320 : 0) ^ (c >> 1);
+ crc_table[i] = c;
+ }
+ }
+
+ uint32_t c = 0xFFFFFFFF;
+ for (size_t i = 0; i < len; i++)
+ c = crc_table[(c ^ buf[i]) & 0xff] ^ (c >> 8);
+ return ~c;
+}
+
+uint16_t ip_checksum(const uint8_t *buf, size_t len) {
+ uint32_t c = 0;
+ while (len >= 2) {
+ c += nget16(buf);
+ buf += 2; len -= 2;
+ }
+ if (len) c += (*buf) << 8;
+ while (c > 0xFFFF) c = (c & 0xFFFF) + (c >> 16);
+ return ~c;
+}
+
+uint16_t ip_checksumphdr(
+ const uint8_t *buf, size_t len,
+ uint32_t ip1, uint32_t ip2,
+ uint16_t proto)
+{
+ uint32_t c = (uint16_t)~ip_checksum(buf, len);
+ c += (ip1 & 0xFFFF) + (ip1 >> 16);
+ c += (ip2 & 0xFFFF) + (ip2 >> 16);
+ c += proto + len;
+ while (c > 0xFFFF) c = (c & 0xFFFF) + (c >> 16);
+ return ~c;
+}
+
+int ip_parse(const char *s, uint32_t *dest) {
+ if (!s) return -1;
+
+ uint32_t ip = strtol(s, (char**)&s, 0);
+ int parts = 1;
+ for (; parts < 4; parts++) {
+ if (!*s) break;
+ if (*s++ != '.') return -1;
+ ip <<= 8;
+ ip += strtol(s, (char**)&s, 0);
+ }
+ if (parts > 1)
+ ip = ((ip & 0xFFFFFF00) << 8 * (4 - parts)) | (ip & 0xFF);
+ *dest = ip;
+ return 0;
+}
diff --git a/src/cmd/netstack/util.h b/src/cmd/netstack/util.h
new file mode 100644
index 0000000..0b29560
--- /dev/null
+++ b/src/cmd/netstack/util.h
@@ -0,0 +1,44 @@
+#pragma once
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define eprintf(fmt, ...) fprintf(stderr, "netstack: "fmt"\n" __VA_OPT__(,) __VA_ARGS__)
+
+uint32_t crc32(const uint8_t *buf, size_t len);
+uint16_t ip_checksum(const uint8_t *buf, size_t len);
+uint16_t ip_checksumphdr(
+ const uint8_t *buf, size_t len,
+ uint32_t ip1, uint32_t ip2,
+ uint16_t proto);
+/* 0 on success, negative failure */
+int ip_parse(const char *s, uint32_t *ip);
+
+static inline void nput16(void *vbuf, uint16_t n) {
+ uint8_t *b = vbuf;
+ b[0] = n >> 8;
+ b[1] = n >> 0;
+}
+
+static inline void nput32(void *vbuf, uint32_t n) {
+ uint8_t *b = vbuf;
+ b[0] = n >> 24;
+ b[1] = n >> 16;
+ b[2] = n >> 8;
+ b[3] = n >> 0;
+}
+
+static inline uint16_t nget16(const void *vbuf) {
+ const uint8_t *b = vbuf;
+ return (b[0] << 8)
+ | (b[1] << 0);
+}
+
+static inline uint32_t nget32(const void *vbuf) {
+ const uint8_t *b = vbuf;
+ return (b[0] << 24)
+ | (b[1] << 16)
+ | (b[2] << 8)
+ | (b[3] << 0);
+}
diff --git a/src/cmd/ps/ps.c b/src/cmd/ps/ps.c
new file mode 100644
index 0000000..76a5841
--- /dev/null
+++ b/src/cmd/ps/ps.c
@@ -0,0 +1,54 @@
+#include <_proc.h>
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int
+main(void)
+{
+ char *readbuf = malloc(4096);
+ char *procbuf = malloc(4096);
+ FILE *f = fopen("/proc/", "r");
+ if (!f) {
+ err(1, "couldn't open /proc/");
+ }
+
+ // TODO library for iterating over directories
+ for (;;) {
+ int len = fread(readbuf, 1, 4096, f);
+ if (len <= 0) break;
+ for (int pos = 0; pos < len; ) {
+ char *end = memchr(readbuf + pos, 0, len - pos);
+ if (!end) {
+ errx(1, "unimplemented: buffer overflow");
+ }
+ size_t entryl = end - (readbuf+pos) + 1;
+ if (isdigit(readbuf[pos])) {
+ FILE *g;
+ sprintf(procbuf, "/proc/%smem", readbuf+pos);
+ g = fopen(procbuf, "r");
+ if (!g) {
+ warn("couldn't open \"%s\"", procbuf);
+ strcpy(procbuf, "(can't peek)");
+ } else {
+ fseek(g, (long)&_psdata_loc->desc, SEEK_SET);
+ if (fread(procbuf, 1, 128, g) <= 0) {
+ strcpy(procbuf, "(no psdata)");
+ }
+ procbuf[128] = '\0';
+ fclose(g);
+ }
+ end[-1] = '\0'; /* remove trailing slash */
+ printf("%s\t%s\n", readbuf+pos, procbuf);
+ }
+ pos += entryl;
+ }
+ }
+
+ free(readbuf);
+ free(procbuf);
+ fclose(f);
+ return 0;
+}
diff --git a/src/cmd/shell/builtins.c b/src/cmd/shell/builtins.c
new file mode 100644
index 0000000..9c294b2
--- /dev/null
+++ b/src/cmd/shell/builtins.c
@@ -0,0 +1,254 @@
+#include "builtins.h"
+#include "shell.h"
+#include <camellia.h>
+#include <camellia/fs/misc.h>
+#include <camellia/path.h>
+#include <err.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define DEFAULT_ARGV(...) \
+ char *_argv_default[] = {argv[0], __VA_ARGS__}; \
+ if (argc <= 1) { \
+ argc = sizeof(_argv_default) / sizeof(*_argv_default); \
+ argv = _argv_default; \
+ }
+
+static void cmd_cat(int argc, char **argv) {
+ const size_t buflen = 4096;
+ char *buf = malloc(buflen);
+
+ DEFAULT_ARGV("!stdin");
+ for (int i = 1; i < argc; i++) {
+ FILE *file = fopen(argv[i], "r");
+ if (!file) {
+ perror(argv[i]);
+ return;
+ }
+ if (!strcmp(argv[i], "!stdin")) fextflags(file, FEXT_NOFILL);
+ for (;;) {
+ int len = fread(buf, 1, buflen, file);
+ if (len <= 0) break;
+ fwrite(buf, 1, len, stdout);
+ }
+ if (ferror(file)) {
+ perror(argv[i]);
+ return;
+ }
+ fclose(file);
+ }
+}
+
+static void cmd_echo(int argc, char **argv) {
+ bool newline = true;
+ int i = 1;
+
+ if (!strcmp("-n", argv[i])) {
+ i++;
+ newline = false;
+ }
+
+ printf("%s", argv[i++]);
+ for (; i < argc; i++)
+ printf(" %s", argv[i]);
+ if (newline)
+ printf("\n");
+}
+
+void cmd_getsize(int argc, char **argv) {
+ if (argc < 2) errx(1, "no arguments");
+ for (int i = 1; i < argc; i++) {
+ hid_t h = camellia_open(argv[i], OPEN_READ);
+ if (h < 0) {
+ warn("error opening %s", argv[i]);
+ continue;
+ }
+ printf("%s: %d\n", argv[i], (int)_sys_getsize(h));
+ _sys_close(h);
+ }
+}
+
+void cmd_hexdump(int argc, char **argv) {
+ DEFAULT_ARGV("!stdin");
+ const size_t buflen = 4096;
+ uint8_t *buf = malloc(buflen);
+ FILE *file;
+ bool canonical = strcmp(argv[0], "hd") == 0;
+ size_t readlen = ~0;
+ size_t pos = 0;
+ bool raw = false;
+
+ int c;
+ optind = 0;
+ while ((c = getopt(argc, argv, "Cn:s:r")) != -1) {
+ switch (c) {
+ case 'C':
+ canonical = true;
+ break;
+ case 'n':
+ readlen = strtol(optarg, NULL, 0);
+ break;
+ case 's':
+ pos = strtol(optarg, NULL, 0);
+ break;
+ case 'r': /* "raw" mode, print data as soon as it's read without buffering */
+ raw = true;
+ break;
+ default:
+ return;
+ }
+ }
+ if (readlen != (size_t)~0)
+ readlen += pos;
+
+ for (; optind < argc; optind++) {
+ file = fopen(argv[optind], "r");
+ if (!file) {
+ warn("couldn't open %s", argv[optind]);
+ continue;
+ }
+ if (raw) fextflags(file, FEXT_NOFILL);
+ fseek(file, pos, SEEK_SET);
+ bool skipped = false;
+ while (pos < readlen) {
+ size_t len = buflen;
+ if (len > readlen - pos)
+ len = readlen - pos;
+ len = fread(buf, 1, len, file);
+ if (len == 0) break;
+
+ for (size_t i = 0; i < len; i += 16) {
+ if (i >= 16 && !memcmp(buf + i, buf + i - 16, 16)) {
+ if (!skipped) {
+ printf("*\n");
+ skipped = true;
+ }
+ continue;
+ } else skipped = false;
+ printf("%08zx ", pos + i);
+
+ for (size_t j = i; j < i + 8 && j < len; j++)
+ printf("%02x ", buf[j]);
+ printf(" ");
+ for (size_t j = i + 8; j < i + 16 && j < len; j++)
+ printf("%02x ", buf[j]);
+
+ if (canonical) {
+ printf(" |");
+
+ for (size_t j = i; j < i + 16 && j < len; j++) {
+ char c = '.';
+ if (0x20 <= buf[j] && buf[j] < 0x7f) c = buf[j];
+ printf("%c", c);
+ }
+ printf("|\n");
+ } else {
+ printf("\n");
+ }
+ }
+ pos += len;
+ }
+ printf("%08zx\n", pos);
+ fclose(file);
+ }
+}
+
+static void cmd_ls(int argc, char **argv) {
+ FILE *file;
+ const size_t buflen = 4096;
+ char *buf = malloc(buflen);
+
+ DEFAULT_ARGV(".");
+ for (int i = 1; i < argc; i++) {
+ char *path = (void*)argv[i];
+ int pathlen = strlen(path);
+
+ if (!pathlen || path[pathlen - 1] != '/') {
+ memcpy(buf, path, pathlen);
+ buf[pathlen] = '/';
+ buf[pathlen+1] = '\0';
+ path = buf;
+ }
+
+ file = fopen(path, "r");
+ if (!file) {
+ warn("couldn't open %s", argv[i]);
+ continue;
+ }
+ for (;;) {
+ 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';
+ fwrite(buf, 1, len, stdout);
+ }
+ fclose(file);
+ }
+}
+
+static void cmd_mkdir(int argc, char **argv) {
+ // TODO mkdir -p
+ if (argc < 2) errx(1, "no arguments");
+ for (int i = 1; i < argc; i++) {
+ if (mkdir(argv[i], 0777) < 0)
+ perror(argv[i]);
+ }
+}
+
+static void cmd_rm(int argc, char **argv) {
+ if (argc < 2) errx(1, "no arguments");
+ for (int i = 1; i < argc; i++) {
+ if (unlink(argv[i]) < 0)
+ perror(argv[i]);
+ }
+}
+
+static void cmd_sleep(int argc, char **argv) {
+ if (argc < 2) errx(1, "no arguments");
+ _sys_sleep(strtol(argv[1], NULL, 0) * 1000);
+}
+
+static void cmd_touch(int argc, char **argv) {
+ if (argc < 2) errx(1, "no arguments");
+ for (int i = 1; i < argc; i++) {
+ FILE *f = fopen(argv[i], "a");
+ if (!f) perror(argv[i]);
+ fclose(f);
+ }
+}
+
+static void cmd_whitelist(int argc, char **argv) {
+ int split = 1;
+ for (; split < argc;) {
+ if (!strcmp("--", argv[split])) break;
+ split++;
+ }
+ argv[split] = NULL;
+ MOUNT_AT("/") { fs_whitelist((void*)&argv[1]); }
+
+ if (split < argc) {
+ run_args(argc - split - 1, &argv[split + 1], NULL);
+ } else {
+ const char **argv = (const char*[]){"shell", NULL};
+ run_args(1, (void*)argv, NULL);
+ }
+}
+
+struct builtin builtins[] = {
+ {"cat", cmd_cat},
+ {"echo", cmd_echo},
+ {"getsize", cmd_getsize},
+ {"hd", cmd_hexdump},
+ {"hexdump", cmd_hexdump},
+ {"ls", cmd_ls},
+ {"mkdir", cmd_mkdir},
+ {"rm", cmd_rm},
+ {"sleep", cmd_sleep},
+ {"touch", cmd_touch},
+ {"whitelist", cmd_whitelist},
+ {NULL, NULL},
+};
diff --git a/src/cmd/shell/builtins.h b/src/cmd/shell/builtins.h
new file mode 100644
index 0000000..1e422bb
--- /dev/null
+++ b/src/cmd/shell/builtins.h
@@ -0,0 +1,10 @@
+#pragma once
+#include <camellia/syscalls.h>
+#include <stdbool.h>
+
+struct builtin {
+ const char *name;
+ void (*fn)(int argc, char **argv);
+};
+
+extern struct builtin builtins[];
diff --git a/src/cmd/shell/parser.c b/src/cmd/shell/parser.c
new file mode 100644
index 0000000..ad09348
--- /dev/null
+++ b/src/cmd/shell/parser.c
@@ -0,0 +1,76 @@
+#include "shell.h"
+#include <ctype.h>
+#include <stdbool.h>
+#include <string.h>
+
+static char skipspace(char **sp) {
+ char *s = *sp;
+ while (*s && isspace(*s)) s++;
+ *sp = s;
+ return *s;
+}
+
+static bool isspecial(char c) {
+ return c == '>' || c == '#';
+}
+
+static char *parg(char **sp) {
+ char *s = *sp;
+ char *res = NULL;
+
+ if (skipspace(&s)) {
+ // TODO incorrectly handles strings like a"b"
+ switch (*s) {
+ case '"':
+ s++;
+ res = s;
+ while (*s && *s != '"')
+ s++;
+ break;
+ default:
+ res = s;
+ while (*s && !isspace(*s) && !isspecial(*s))
+ s++;
+ if (*s == '#') {
+ *s = '\0'; /* end parsing early */
+ if (res == s) /* don't output an empty arg */
+ res = NULL;
+ }
+ break;
+ }
+ if (*s) *s++ = '\0';
+ }
+
+ *sp = s;
+ return res;
+}
+
+int parse(char *s, char **argv, size_t argvlen, struct redir *redir) {
+ if (argvlen == 0) return -1;
+ size_t argc = 0;
+ char *arg;
+
+ *argv = NULL;
+ redir->stdout = NULL;
+ redir->append = false;
+
+ while (skipspace(&s)) {
+ switch (*s) {
+ case '>':
+ s++;
+ if (*s == '>') {
+ s++;
+ redir->append = true;
+ }
+ redir->stdout = parg(&s);
+ break;
+ default:
+ arg = parg(&s);
+ argv[argc++] = arg;
+ if (argc >= argvlen)
+ return -1;
+ }
+ }
+ argv[argc] = NULL;
+ return argc;
+}
diff --git a/src/cmd/shell/shell.c b/src/cmd/shell/shell.c
new file mode 100644
index 0000000..185aa7e
--- /dev/null
+++ b/src/cmd/shell/shell.c
@@ -0,0 +1,178 @@
+#include "builtins.h"
+#include "shell.h"
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <err.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <camellia/fs/misc.h>
+#include <x86intrin.h>
+
+int main();
+
+static void execp(char **argv) {
+ if (!argv || !*argv) return;
+ if (strncmp(argv[0], "/", 1) == 0 ||
+ strncmp(argv[0], "./", 2) == 0 ||
+ strncmp(argv[0], "../", 3) == 0)
+ {
+ execv(argv[0], argv);
+ } else {
+ size_t cmdlen = strlen(argv[0]);
+ char *s = malloc(cmdlen + 6);
+ if (!s) err(1, "malloc");
+ memcpy(s, "/bin/", 5);
+ memcpy(s + 5, argv[0], cmdlen + 1);
+ execv(s, argv);
+ free(s);
+ }
+}
+
+void run_args(int argc, char **argv, struct redir *redir) {
+ if (!*argv) return;
+
+ /* "special" commands that can't be handled in a subprocess */
+ if (!strcmp(argv[0], "mount")) {
+ if (argc < 3) {
+ fprintf(stderr, "mount: not enough arguments\n");
+ return;
+ }
+ MOUNT_AT("/") {
+ fs_dirinject(argv[1]);
+ }
+ MOUNT_AT(argv[1]) {
+ run_args(argc - 2, argv + 2, redir);
+ exit(1);
+ }
+ return;
+ } else if (!strcmp(argv[0], "shadow")) {
+ if (argc < 2) {
+ fprintf(stderr, "shadow: missing path\n");
+ } else {
+ _sys_mount(HANDLE_NULLFS, argv[1], strlen(argv[1]));
+ }
+ } else if (!strcmp(argv[0], "procmnt")) {
+ if (argc < 2) {
+ fprintf(stderr, "procmnt: missing mountpoint\n");
+ return;
+ }
+ _sys_mount(HANDLE_PROCFS, argv[1], strlen(argv[1]));
+ /*
+ if (!(3 <= argc && !strcmp(argv[2], "raw"))) {
+ if (!mount_at("/")) {
+ fs_dirinject(argv[1]);
+ exit(1);
+ }
+ }
+ */
+ return;
+ } else if (!strcmp(argv[0], "cd")) {
+ if (chdir(argc > 1 ? argv[1] : "/") < 0)
+ perror("cd");
+ return;
+ } else if (!strcmp(argv[0], "time")) {
+ uint64_t time = __rdtsc();
+ uint64_t div = 3000;
+ run_args(argc - 1, argv + 1, redir);
+ time = __rdtsc() - time;
+ printf("%lu ns (assuming 3GHz)\n", time / div);
+ return;
+ } else if (!strcmp(argv[0], "exit")) {
+ exit(0);
+ } else if (!strcmp(argv[0], "getpid")) {
+ printf("my\t%d\nparent\t%d\n", getpid(), getppid());
+ return;
+ }
+
+ if (fork()) {
+ _sys_await();
+ return;
+ }
+
+ if (redir && redir->stdout) {
+ FILE *f = fopen(redir->stdout, redir->append ? "a" : "w");
+ if (!f) {
+ err(1, "couldn't open %s for redirection", redir->stdout);
+ }
+
+ /* a workaround for file offsets not being preserved across exec()s.
+ * TODO document that weird behaviour of exec() */
+ hid_t p[2];
+ if (_sys_pipe(p, 0) < 0) {
+ errx(1, "couldn't create redirection pipe");
+ }
+ if (!_sys_fork(FORK_NOREAP, NULL)) {
+ /* the child forwards data from the pipe to the file */
+ const size_t buflen = 512;
+ char *buf = malloc(buflen);
+ if (!buf) err(1, "when redirecting");
+ close(p[1]);
+ for (;;) {
+ long len = _sys_read(p[0], buf, buflen, 0);
+ if (len < 0) exit(0);
+ fwrite(buf, 1, len, f);
+ if (ferror(f)) exit(0);
+ }
+ }
+
+ fclose(f);
+ close(p[0]);
+ if (_sys_dup(p[1], 1, 0) < 0) {
+ errx(1, "dup() failed when redirecting");
+ }
+ }
+
+ for (struct builtin *iter = builtins; iter->name; iter++) {
+ if (!strcmp(argv[0], iter->name)) {
+ setprogname(argv[0]);
+ iter->fn(argc, argv);
+ exit(0);
+ }
+ }
+
+ execp(argv);
+ if (errno == EINVAL) {
+ errx(1, "%s isn't a valid executable", argv[0]);
+ } else {
+ errx(1, "unknown command: %s", argv[0]);
+ }
+}
+
+static void run(char *cmd) {
+#define ARGV_MAX 16
+ char *argv[ARGV_MAX];
+ struct redir redir;
+ int argc = parse(cmd, argv, ARGV_MAX, &redir);
+ if (argc < 0) {
+ warn("parsing error");
+ } else {
+ run_args(argc, argv, &redir);
+ }
+}
+
+int main(int argc, char **argv) {
+ static char buf[256];
+ FILE *f = stdin;
+
+ if (argc > 1) {
+ f = fopen(argv[1], "r");
+ if (!f) {
+ err(1, "couldn't open %s", argv[1]);
+ return 1;
+ }
+ }
+
+ for (;;) {
+ if (f == stdin) {
+ printf("%s $ ", getcwd(buf, sizeof buf));
+ }
+ if (!fgets(buf, 256, f)) {
+ return 0;
+ }
+ run(buf);
+ }
+}
diff --git a/src/cmd/shell/shell.h b/src/cmd/shell/shell.h
new file mode 100644
index 0000000..050e704
--- /dev/null
+++ b/src/cmd/shell/shell.h
@@ -0,0 +1,11 @@
+#pragma once
+#include <stdbool.h>
+#include <stddef.h>
+
+struct redir {
+ const char *stdout;
+ bool append;
+};
+
+int parse(char *s, char **argv, size_t argvlen, struct redir *redir);
+void run_args(int argc, char **argv, struct redir *redir);
diff --git a/src/cmd/testelf/main.c b/src/cmd/testelf/main.c
new file mode 100644
index 0000000..0cbe56c
--- /dev/null
+++ b/src/cmd/testelf/main.c
@@ -0,0 +1,26 @@
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+const char *str = "Hello!", *str2 = "World.";
+
+int main(int argc, char **argv) {
+ printf("elftest's &main == %p\n", &main);
+ printf("%s %s\n", str, str2);
+ printf("argc == %u\n", argc);
+ for (int i = 0; i < argc; i++)
+ printf("argv[%u] == %p == \"%s\"\n", i, argv[i], argv[i]);
+ if (argv[1] && strcmp(argv[1], "stackexec") == 0) {
+ /* exec something with arguments on the stack */
+ const char s_d[] = "I am a pretty long string on the stack. Oh my. " \
+ "I hope I won't get corrupted.\0";
+ char s[sizeof(s_d)];
+ memcpy(s, s_d, sizeof(s_d));
+ const char *argv2[] = {"/bin/testelf", s, s, "hello", s, s, s, "lol", NULL};
+ printf("argv2 == %p, s == %p\n== exec ==\n", argv2, s);
+ execv((void*)argv2[0], (void*)argv2);
+ err(1, "execv");
+ }
+ return 0;
+}
diff --git a/src/cmd/tests/kernel/fdlimit.c b/src/cmd/tests/kernel/fdlimit.c
new file mode 100644
index 0000000..f332357
--- /dev/null
+++ b/src/cmd/tests/kernel/fdlimit.c
@@ -0,0 +1,49 @@
+#include "../tests.h"
+#include <camellia/flags.h>
+#include <errno.h>
+#include <unistd.h>
+
+static void test_fdlimit_pipe(void) {
+ hid_t ends[2];
+ hid_t h[2] = {-1, -1};
+ for (;;) {
+ hid_t cur = _sys_open("/", 1, OPEN_READ);
+ if (cur == -EMFILE) break;
+ test(cur >= 0);
+ h[0] = h[1];
+ h[1] = cur;
+ }
+ test(h[0] >= 0); /* we need at least two handles */
+
+ test(_sys_pipe(ends, 0) == -EMFILE);
+ test(_sys_open("/", 1, OPEN_READ) == -EMFILE);
+
+ close(h[1]);
+ test(_sys_pipe(ends, 0) == -EMFILE);
+ test(_sys_open("/", 1, OPEN_READ) == h[1]);
+ test(_sys_open("/", 1, OPEN_READ) == -EMFILE);
+
+ close(h[0]);
+ close(h[1]);
+ test(_sys_pipe(ends, 0) == 0);
+}
+
+static void test_fdlimit_fork(void) {
+ for (;;) {
+ hid_t cur = _sys_open("/", 1, OPEN_READ);
+ if (cur == -EMFILE) break;
+ test(cur >= 0);
+ }
+
+ if (!_sys_fork(0, NULL)) _sys_exit(123);
+
+ test(_sys_fork(FORK_NEWFS, NULL) == -EMFILE);
+ test(_sys_await() == 123);
+ test(_sys_await() == ~0);
+}
+
+void r_k_fdlimit(void) {
+ /* all these tests implicitly test if the vfs returns -EMFILE */
+ run_test(test_fdlimit_pipe);
+ run_test(test_fdlimit_fork);
+}
diff --git a/src/cmd/tests/kernel/fs.c b/src/cmd/tests/kernel/fs.c
new file mode 100644
index 0000000..b5684d7
--- /dev/null
+++ b/src/cmd/tests/kernel/fs.c
@@ -0,0 +1,79 @@
+#include "../tests.h"
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <stdbool.h>
+#include <string.h>
+
+static void test_unfinished_req(void) {
+ hid_t h = -1;
+ int ret = _sys_fork(FORK_NEWFS, &h);
+ test(0 <= ret);
+ if (ret == 0) {
+ // TODO make a similar test with all 0s passed to fs_wait
+ struct ufs_request res;
+ _sys_fs_wait(NULL, 0, &res);
+ // TODO second fs_wait
+ exit(0);
+ } else {
+ test(0 <= h);
+ test(_sys_mount(h, "/", 1) == 0);
+ int ret = _sys_open("/", 1, 0);
+ test(ret < 0);
+ // the handler quits while handling that call - but this syscall should return anyways
+ }
+}
+
+static void test_orphaned_fs(void) {
+ hid_t h = -1;
+ int ret = _sys_fork(FORK_NEWFS, &h);
+ test(0 <= ret);
+ if (ret == 0) {
+ exit(0);
+ } else {
+ test(0 <= h);
+ test(_sys_mount(h, "/", 1) == 0);
+ int ret = _sys_open("/", 1, 0);
+ test(ret < 0);
+ }
+}
+
+static void test_fs_cleanup(void) {
+ const char *msg = "success";
+ int msglen = 8;
+ char buf[16];
+ hid_t ends[2];
+ test(_sys_pipe(ends, 0) >= 0);
+ if (!_sys_fork(0, NULL)) {
+ hid_t h = -1;
+ if (_sys_fork(FORK_NEWFS, &h) == 0) {
+ for (;;) {
+ struct ufs_request req;
+ hid_t reqh = _sys_fs_wait(buf, sizeof buf, &req);
+ if (reqh < 0) break;
+ _sys_fs_respond(reqh, NULL, 0, 0); /* success */
+ }
+ /* this is the test: does it break out of the loop
+ * when it should cleanup */
+ _sys_write(ends[1], msg, msglen, -1, 0);
+ exit(0);
+ } else {
+ test(_sys_mount(h, "/", 1) == 0);
+ h = _sys_open("/", 1, 0);
+ test(h >= 0);
+ _sys_close(h);
+ // TODO another test without the delay
+ _sys_sleep(0);
+ exit(0);
+ }
+ } else {
+ test(_sys_read(ends[0], buf, sizeof buf, 0) == msglen);
+ test(memcmp(buf, msg, msglen) == 0);
+ }
+}
+
+void r_k_fs(void) {
+ run_test(test_unfinished_req);
+ run_test(test_orphaned_fs);
+ run_test(test_fs_cleanup);
+ run_test(test_fs_cleanup);
+}
diff --git a/src/cmd/tests/kernel/misc.c b/src/cmd/tests/kernel/misc.c
new file mode 100644
index 0000000..5d6b531
--- /dev/null
+++ b/src/cmd/tests/kernel/misc.c
@@ -0,0 +1,66 @@
+#include "../tests.h"
+#include <camellia/errno.h>
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <string.h>
+#include <unistd.h>
+
+static void test_fault_kill(void) {
+ if (!fork()) { /* invalid memory access */
+ asm volatile("movb $69, 0" ::: "memory");
+ test_fail();
+ }
+ if (!fork()) { /* #GP */
+ asm volatile("hlt" ::: "memory");
+ test_fail();
+ }
+
+ int await_cnt = 0;
+ while (_sys_await() != ~0) await_cnt++;
+ test(await_cnt == 2);
+}
+
+static void test_efault(void) {
+#if 0
+ const char *str = "o, 16 characters";
+ char str2[16];
+ char *invalid = (void*)0x1000;
+ hid_t h;
+
+ memcpy(str2, str, 16);
+
+ test((h = _sys_open(TMPFILEPATH, strlen(TMPFILEPATH), OPEN_CREATE | OPEN_WRITE)) >= 0);
+ test(_sys_write(h, str, 16, 0, 0) == 16);
+ test(_sys_write(h, str2, 16, 0, 0) == 16);
+
+ test(_sys_write(h, invalid, 16, 0, 0) == -EFAULT);
+
+ /* x64 non-canonical pointers */
+ test(_sys_write(h, (void*)((uintptr_t)str ^ 0x8000000000000000), 16, 0, 0) == -EFAULT);
+ test(_sys_write(h, (void*)((uintptr_t)str ^ 0x1000000000000000), 16, 0, 0) == -EFAULT);
+ test(_sys_write(h, (void*)((uintptr_t)str ^ 0x0100000000000000), 16, 0, 0) == -EFAULT);
+ test(_sys_write(h, (void*)((uintptr_t)str ^ 0x0010000000000000), 16, 0, 0) == -EFAULT);
+ test(_sys_write(h, (void*)((uintptr_t)str ^ 0x0001000000000000), 16, 0, 0) == -EFAULT);
+ test(_sys_write(h, (void*)((uintptr_t)str ^ 0x0000800000000000), 16, 0, 0) == -EFAULT);
+
+ test(_sys_write(h, (void*)((uintptr_t)str2 ^ 0x8000000000000000), 16, 0, 0) == -EFAULT);
+ test(_sys_write(h, (void*)((uintptr_t)str2 ^ 0x1000000000000000), 16, 0, 0) == -EFAULT);
+ test(_sys_write(h, (void*)((uintptr_t)str2 ^ 0x0100000000000000), 16, 0, 0) == -EFAULT);
+ test(_sys_write(h, (void*)((uintptr_t)str2 ^ 0x0010000000000000), 16, 0, 0) == -EFAULT);
+ test(_sys_write(h, (void*)((uintptr_t)str2 ^ 0x0001000000000000), 16, 0, 0) == -EFAULT);
+ test(_sys_write(h, (void*)((uintptr_t)str2 ^ 0x0000800000000000), 16, 0, 0) == -EFAULT);
+
+ test(_sys_write(h, str, 16, 0, 0) == 16);
+ close(h);
+#endif
+}
+
+static void test_invalid_syscall(void) {
+ test(_syscall(~0, 0, 0, 0, 0, 0) < 0);
+}
+
+void r_k_misc(void) {
+ run_test(test_fault_kill);
+ run_test(test_efault);
+ run_test(test_invalid_syscall);
+}
diff --git a/src/cmd/tests/kernel/miscsyscall.c b/src/cmd/tests/kernel/miscsyscall.c
new file mode 100644
index 0000000..459da2a
--- /dev/null
+++ b/src/cmd/tests/kernel/miscsyscall.c
@@ -0,0 +1,315 @@
+#include "../tests.h"
+#include <camellia/errno.h>
+#include <camellia/execbuf.h>
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <string.h>
+#include <unistd.h>
+
+
+static void test_await(void) {
+ /* creates 16 child processes, each returning a different value. then checks
+ * if await() returns every value exactly once */
+ int ret;
+ int counts[16] = {0};
+
+ for (int i = 0; i < 16; i++)
+ if (!fork())
+ exit(i);
+
+ while ((ret = _sys_await()) != ~0) {
+ test(0 <= ret && ret < 16);
+ counts[ret]++;
+ }
+
+ for (int i = 0; i < 16; i++)
+ test(counts[i] == 1);
+}
+
+static void test_await2(void) {
+ /* hangs on failure */
+ if (!fork()) {
+ if (!fork()) {
+ for (;;) _sys_sleep(1000000000);
+ } else {
+ exit(123);
+ }
+ }
+ test(_sys_await() == 123);
+ test(_sys_await() == ~0); /* don't wait for tombstone */
+ _sys_filicide();
+}
+
+static void test_wait2_basic(void) {
+ struct sys_wait2 data = {0};
+ int pid;
+ pid = fork();
+ if (pid == 0) {
+ exit(420);
+ }
+ test(_sys_wait2(-1, 0, &data) == pid);
+ test(data.status == 420);
+ test(_sys_wait2(-1, 0, NULL) == -ECHILD);
+}
+
+static void test_pipe(void) {
+ const char *pipe_msgs[2] = {"hello", "world"};
+ hid_t ends[2];
+ char buf[16];
+
+ /* test regular reads / writes */
+ test(_sys_pipe(ends, 0) >= 0);
+ if (!fork()) {
+ /* those repeated asserts ensure that you can't read/write to the wrong ends */
+ test(_sys_read(ends[1], buf, 16, 0) < 0);
+ test(_sys_write(ends[0], buf, 16, 0, 0) < 0);
+
+ test(5 == _sys_write(ends[1], pipe_msgs[0], 5, -1, 0));
+
+ test(_sys_read(ends[1], buf, 16, 0) < 0);
+ test(_sys_write(ends[0], buf, 16, 0, 0) < 0);
+
+ test(5 == _sys_write(ends[1], pipe_msgs[1], 5, -1, 0));
+
+ exit(0);
+ } else {
+ test(_sys_read(ends[1], buf, 16, 0) < 0);
+ test(_sys_write(ends[0], buf, 16, 0, 0) < 0);
+
+ test(5 == _sys_read(ends[0], buf, 16, 0));
+ test(!memcmp(buf, pipe_msgs[0], 5));
+
+ test(_sys_read(ends[1], buf, 16, 0) < 0);
+ test(_sys_write(ends[0], buf, 16, 0, 0) < 0);
+
+ test(5 == _sys_read(ends[0], buf, 16, 0));
+ test(!memcmp(buf, pipe_msgs[1], 5));
+
+ _sys_await();
+ }
+ close(ends[0]);
+ close(ends[1]);
+
+
+ /* writing to pipes with one end closed */
+ test(_sys_pipe(ends, 0) >= 0);
+ for (int i = 0; i < 16; i++) {
+ if (!fork()) {
+ close(ends[1]);
+ test(_sys_read(ends[0], buf, 16, 0) < 0);
+ exit(0);
+ }
+ }
+ close(ends[1]);
+ for (int i = 0; i < 16; i++)
+ _sys_await();
+ close(ends[0]);
+
+ test(_sys_pipe(ends, 0) >= 0);
+ for (int i = 0; i < 16; i++) {
+ if (!fork()) {
+ close(ends[0]);
+ test(_sys_write(ends[1], buf, 16, 0, 0) < 0);
+ exit(0);
+ }
+ }
+ close(ends[0]);
+ for (int i = 0; i < 16; i++)
+ _sys_await();
+ close(ends[1]);
+
+
+ /* queueing */
+ test(_sys_pipe(ends, 0) >= 0);
+ for (int i = 0; i < 16; i++) {
+ if (!fork()) {
+ test(_sys_write(ends[1], pipe_msgs[0], 5, -1, 0) == 5);
+ exit(0);
+ }
+ }
+ close(ends[1]);
+ for (int i = 0; i < 16; i++) {
+ test(_sys_read(ends[0], buf, sizeof buf, 0) == 5);
+ _sys_await();
+ }
+ test(_sys_read(ends[0], buf, sizeof buf, 0) < 0);
+ close(ends[0]);
+
+ test(_sys_pipe(ends, 0) >= 0);
+ for (int i = 0; i < 16; i++) {
+ if (!fork()) {
+ memset(buf, 0, sizeof buf);
+ test(_sys_read(ends[0], buf, 5, -1) == 5);
+ test(!memcmp(buf, pipe_msgs[1], 5));
+ exit(0);
+ }
+ }
+ close(ends[0]);
+ for (int i = 0; i < 16; i++) {
+ test(_sys_write(ends[1], pipe_msgs[1], 5, -1, 0) == 5);
+ _sys_await();
+ }
+ test(_sys_write(ends[1], pipe_msgs[1], 5, -1, 0) < 0);
+ close(ends[1]);
+
+
+ // not a to.do detect when all processes that can read are stuck on writing to the pipe and vice versa
+ // it seems like linux just lets the process hang endlessly.
+}
+
+static void test_memflag(void) {
+ void *page = (void*)0x77777000;
+ _sys_memflag(page, 4096, MEMFLAG_PRESENT); // allocate page
+ memset(page, 77, 4096); // write to it
+ _sys_memflag(page, 4096, 0); // free it
+
+ if (!fork()) {
+ memset(page, 11, 4096); // should segfault
+ exit(0);
+ } else {
+ test(_sys_await() != 0); // test if the process crashed
+ }
+
+ char *pages[4];
+ for (size_t i = 0; i < 4; i++) {
+ pages[i] = _sys_memflag(NULL, 4096, MEMFLAG_FINDFREE);
+ test(pages[i] == _sys_memflag(NULL, 4096, MEMFLAG_FINDFREE | MEMFLAG_PRESENT));
+ if (i > 0) test(pages[i] != pages[i-1]);
+ *pages[i] = 0x77;
+ }
+
+ for (size_t i = 0; i < 4; i++)
+ _sys_memflag(pages, 4096, MEMFLAG_PRESENT);
+}
+
+static void test_dup(void) {
+ hid_t pipe[2];
+ hid_t h1, h2;
+ test(_sys_pipe(pipe, 0) >= 0);
+
+ if (!fork()) {
+ close(pipe[0]);
+
+ h1 = _sys_dup(pipe[1], -1, 0);
+ test(h1 >= 0);
+ test(h1 != pipe[1]);
+ h2 = _sys_dup(h1, -1, 0);
+ test(h2 >= 0);
+ test(h2 != pipe[1] && h2 != h1);
+
+ _sys_write(pipe[1], "og", 2, 0, 0);
+ _sys_write(h1, "h1", 2, 0, 0);
+ _sys_write(h2, "h2", 2, 0, 0);
+
+ close(pipe[1]);
+ _sys_write(h1, "h1", 2, 0, 0);
+ _sys_write(h2, "h2", 2, 0, 0);
+
+ test(_sys_dup(h1, pipe[1], 0) == pipe[1]);
+ test(_sys_dup(h2, pipe[1], 0) == pipe[1]);
+ test(_sys_dup(h1, pipe[1], 0) == pipe[1]);
+ test(_sys_dup(h2, pipe[1], 0) == pipe[1]);
+ close(h1);
+ close(h2);
+
+ test(_sys_dup(pipe[1], h2, 0) == h2);
+ _sys_write(h2, "h2", 2, 0, 0);
+ close(h2);
+
+ test(_sys_dup(pipe[1], h1, 0) == h1);
+ _sys_write(h1, "h1", 2, 0, 0);
+ close(h1);
+
+ exit(0);
+ } else {
+ char buf[16];
+ size_t count = 0;
+ close(pipe[1]);
+ while (_sys_read(pipe[0], buf, sizeof buf, 0) >= 0)
+ count++;
+ test(count == 7);
+ _sys_await();
+ }
+
+
+ close(pipe[0]);
+}
+
+static void test_execbuf(void) {
+ if (!fork()) {
+ uint64_t buf[] = {
+ EXECBUF_SYSCALL, _SYS_EXIT, 123, 0, 0, 0, 0,
+ };
+ _sys_execbuf(buf, sizeof buf);
+ test_fail();
+ } else {
+ test(_sys_await() == 123);
+ }
+}
+
+static void test_sleep(void) {
+ hid_t reader;
+ FILE *writer;
+ if (!forkpipe(&writer, &reader)) {
+ if (!fork()) {
+ if (!fork()) {
+ _sys_sleep(100);
+ fprintf(writer, "1");
+ _sys_sleep(200);
+ fprintf(writer, "3");
+ _sys_sleep(200);
+ fprintf(writer, "5");
+ _sys_exit(0);
+ }
+ if (!fork()) {
+ fprintf(writer, "0");
+ _sys_sleep(200);
+ fprintf(writer, "2");
+ _sys_sleep(200);
+ fprintf(writer, "4");
+ /* get killed while asleep
+ * a peaceful death, i suppose. */
+ for (;;) _sys_sleep(1000000000);
+ }
+ _sys_await();
+ _sys_filicide();
+ _sys_exit(0);
+ }
+
+ /* this part checks if, after killing an asleep process,
+ * other processes can still wake up */
+ _sys_sleep(600);
+ fprintf(writer, "6");
+ exit(0);
+ } else {
+ const char *expected = "0123456";
+ size_t target = strlen(expected);
+ size_t pos = 0;
+ for (;;) {
+ char buf[128];
+ long ret = _sys_read(reader, buf, sizeof buf, 0);
+ if (ret < 0) break;
+ test(pos + ret <= target);
+ test(memcmp(buf, expected + pos, ret) == 0);
+ pos += ret;
+ }
+ test(pos == target);
+ }
+}
+
+static void test_badopen(void) {
+ test(_sys_open(TMPFILEPATH, strlen(TMPFILEPATH), OPEN_CREATE | OPEN_WRITE) >= 0);
+ test(_sys_open(TMPFILEPATH, strlen(TMPFILEPATH), OPEN_CREATE) == -EINVAL);
+}
+
+void r_k_miscsyscall(void) {
+ run_test(test_await);
+ run_test(test_await2);
+ run_test(test_wait2_basic);
+ run_test(test_pipe);
+ run_test(test_memflag);
+ run_test(test_dup);
+ run_test(test_execbuf);
+ run_test(test_sleep);
+ run_test(test_badopen);
+}
diff --git a/src/cmd/tests/kernel/path.c b/src/cmd/tests/kernel/path.c
new file mode 100644
index 0000000..5a22c36
--- /dev/null
+++ b/src/cmd/tests/kernel/path.c
@@ -0,0 +1,108 @@
+#include "../tests.h"
+#include <camellia/path.h>
+#include <string.h>
+#include <camellia/compat.h>
+#include <camellia/fs/misc.h>
+
+static void test_path_simplify(void) {
+ const char *testcases[][2] = {
+ {"/", "/"},
+ {"/.", "/"},
+ {"//", "/"},
+ {"/asdf", "/asdf"},
+ {"/asdf/", "/asdf/"},
+ {"/asdf//", "/asdf/"},
+ {"/asdf/./", "/asdf/"},
+ {"/a/./b", "/a/b"},
+ {"/a/./b/", "/a/b/"},
+
+ // some slightly less easy cases
+ {"/asdf/..", "/"},
+ {"/asdf/../", "/"},
+ {"/asdf/.", "/asdf/"},
+ {"/asdf//.", "/asdf/"},
+
+ {"/foo/bar/..", "/foo/"},
+ {"/foo/bar/../baz", "/foo/baz"},
+ {"/foo/bar/../baz/", "/foo/baz/"},
+ {"/foo/bar/xyz/..", "/foo/bar/"},
+ {"/foo/bar/xyz/../", "/foo/bar/"},
+
+ // going under the root or close to it
+ {"/..", NULL},
+ {"/../asdf", NULL},
+ {"/../asdf/", NULL},
+ {"/./a/../..", NULL},
+ {"/a/a/../..", "/"},
+ {"/a/../a/..", "/"},
+ {"/a/../../a", NULL},
+ {"/../a/../a", NULL},
+ {"/../../a/a", NULL},
+ {"/////../..", NULL},
+ {"//a//../..", NULL},
+
+ // relative paths aren't allowed
+ {"relative", NULL},
+ {"some/stuff", NULL},
+ {"./stuff", NULL},
+ {"../stuff", NULL},
+ {"", NULL},
+ };
+
+ char buf[256];
+ for (size_t i = 0; i < sizeof(testcases) / sizeof(testcases[0]); i++) {
+ const char *input = testcases[i][0];
+ const char *expected = testcases[i][1];
+ int len = path_simplify(input, buf, strlen(input));
+ if (expected == NULL) {
+ test(len == 0);
+ } else {
+ // TODO an argument for printing info on test failure
+ test(len == (int)strlen(expected) && !memcmp(expected, buf, len));
+ }
+ }
+}
+
+static void mount_resolve_drv(const char *path) {
+ if (mount_at(path) != 0) return;
+
+ struct ufs_request res;
+ while (!c0_fs_wait(NULL, 0, &res)) {
+ // TODO does the first argument of c0_fs_respond need to be non-const?
+ c0_fs_respond((void*)path, strlen(path), 0);
+ }
+ exit(1);
+}
+
+static void test_mount_resolve(void) {
+ const char *testcases[][2] = {
+ {"/", "/"},
+ {"/test", "/"},
+ {"/dir", "/dir"},
+ {"/dir/..", "/"},
+ {"/dir/../dir", "/dir"},
+ {"/dirry", "/"},
+ {"/dir/", "/dir"},
+ {"/dir/shadowed", "/dir"},
+ {"/dir/shadowed/", "/dir"},
+ };
+ mount_resolve_drv("/");
+ mount_resolve_drv("/dir/shadowed");
+ mount_resolve_drv("/dir");
+
+ char buf[16];
+ for (size_t i = 0; i < sizeof(testcases) / sizeof(testcases[0]); i++) {
+ const char *input = testcases[i][0];
+ const char *expected = testcases[i][1];
+ hid_t h = _sys_open(input, strlen(input), 0);
+ test(h >= 0);
+ int len = _sys_read(h, buf, sizeof buf, 0);
+ test(len == (int)strlen(expected) && !memcmp(expected, buf, len));
+ _sys_close(h);
+ }
+}
+
+void r_k_path(void) {
+ run_test(test_path_simplify);
+ run_test(test_mount_resolve);
+}
diff --git a/src/cmd/tests/kernel/threads.c b/src/cmd/tests/kernel/threads.c
new file mode 100644
index 0000000..b3c1c06
--- /dev/null
+++ b/src/cmd/tests/kernel/threads.c
@@ -0,0 +1,55 @@
+#include "../tests.h"
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <string.h>
+#include <esemaphore.h>
+#include <thread.h>
+
+int global_n;
+static void basic_thread(void *sem) {
+ global_n = 10;
+ esem_signal(sem);
+}
+static void test_basic_thread(void) {
+ struct evil_sem *sem = esem_new(0);
+ global_n = 0;
+ thread_create(FORK_NOREAP, basic_thread, sem);
+ esem_wait(sem);
+ test(global_n == 10);
+}
+
+hid_t global_h;
+static void shared_handle(void *sem) {
+ hid_t ends[2];
+ test(_sys_pipe(ends, 0) >= 0);
+ global_h = ends[0];
+ esem_signal(sem);
+ _sys_write(ends[1], "Hello!", 7, -1, 0);
+}
+static void test_shared_handle(void) {
+ struct evil_sem *sem = esem_new(0);
+ char buf[16];
+ global_h = -1;
+ thread_create(FORK_NOREAP, shared_handle, sem);
+ esem_wait(sem);
+
+ test(global_h >= 0);
+ test(_sys_read(global_h, buf, sizeof buf, 0) == 7);
+ test(!strcmp("Hello!", buf));
+}
+
+static void many_thread(void *arg) {
+ *(uint64_t*)arg += 10;
+}
+static void test_many_threads(void) {
+ uint64_t n = 0;
+ for (int i = 0; i < 10; i++) thread_create(0, many_thread, &n);
+ for (int i = 0; i < 10; i++) _sys_await();
+ test(n == 100);
+}
+
+void r_k_threads(void) {
+ run_test(test_basic_thread);
+ run_test(test_shared_handle);
+ run_test(test_many_threads);
+}
diff --git a/src/cmd/tests/libc/esemaphore.c b/src/cmd/tests/libc/esemaphore.c
new file mode 100644
index 0000000..f089f4f
--- /dev/null
+++ b/src/cmd/tests/libc/esemaphore.c
@@ -0,0 +1,95 @@
+#include "../tests.h"
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <esemaphore.h>
+
+static void odd(hid_t out, struct evil_sem *sem1, struct evil_sem *sem2) {
+ _sys_write(out, "1", 1, -1, 0);
+ esem_signal(sem1);
+
+ esem_wait(sem2);
+ _sys_write(out, "3", 1, -1, 0);
+ esem_signal(sem1);
+
+ esem_wait(sem2);
+ _sys_write(out, "5", 1, -1, 0);
+ esem_signal(sem1);
+}
+
+static void even(hid_t out, struct evil_sem *sem1, struct evil_sem *sem2) {
+ esem_wait(sem1);
+ _sys_write(out, "2", 1, -1, 0);
+ esem_signal(sem2);
+
+ esem_wait(sem1);
+ _sys_write(out, "4", 1, -1, 0);
+ esem_signal(sem2);
+
+ esem_wait(sem1);
+ _sys_write(out, "6", 1, -1, 0);
+ esem_signal(sem2);
+}
+
+static void test_semaphore(void) {
+ struct evil_sem *sem1, *sem2;
+ hid_t pipe[2];
+ test(_sys_pipe(pipe, 0) >= 0);
+
+ if (!fork()) {
+ sem1 = esem_new(0);
+ sem2 = esem_new(0);
+ test(sem1 && sem2);
+ if (!fork()) {
+ odd(pipe[1], sem1, sem2);
+ exit(69);
+ } else {
+ even(pipe[1], sem1, sem2);
+ test(_sys_await() == 69);
+ }
+ esem_free(sem1);
+ esem_free(sem2);
+
+ _sys_write(pipe[1], "|", 1, -1, 0);
+
+ sem1 = esem_new(0);
+ sem2 = esem_new(0);
+ test(sem1 && sem2);
+ if (!fork()) {
+ even(pipe[1], sem1, sem2);
+ exit(69);
+ } else {
+ odd(pipe[1], sem1, sem2);
+ test(_sys_await() == 69);
+ _sys_await();
+ }
+ esem_free(sem1);
+ esem_free(sem2);
+
+ _sys_filicide();
+ exit(0);
+ } else {
+ close(pipe[1]);
+
+ char buf[16];
+ size_t pos = 0;
+ for (;;) {
+ int ret = _sys_read(pipe[0], buf + pos, sizeof(buf) - pos, 0);
+ if (ret < 0) break;
+ pos += ret;
+ }
+ buf[pos] = '\0'; // idc about the "potential" overflow
+ if (strcmp(buf, "123456|123456")) {
+ printf("%s\n", buf);
+ test_fail();
+ }
+
+ _sys_await();
+ }
+}
+
+void r_libc_esemaphore(void) {
+ run_test(test_semaphore);
+}
diff --git a/src/cmd/tests/libc/setjmp.c b/src/cmd/tests/libc/setjmp.c
new file mode 100644
index 0000000..0dded9d
--- /dev/null
+++ b/src/cmd/tests/libc/setjmp.c
@@ -0,0 +1,31 @@
+#include "../tests.h"
+#include <stdbool.h>
+#include <setjmp.h>
+
+static void test_setjmp(void) {
+ jmp_buf env;
+ volatile bool inner;
+ int val;
+ inner = false;
+ if (!(val = setjmp(env))) {
+ inner = true;
+ longjmp(env, 1234);
+ test(0);
+ } else {
+ test(val == 1234);
+ test(inner);
+ }
+ inner = false;
+ if (!(val = setjmp(env))) {
+ inner = true;
+ longjmp(env, 0);
+ test(0);
+ } else {
+ test(val == 1);
+ test(inner);
+ }
+}
+
+void r_libc_setjmp(void) {
+ run_test(test_setjmp);
+}
diff --git a/src/cmd/tests/libc/string.c b/src/cmd/tests/libc/string.c
new file mode 100644
index 0000000..6afe350
--- /dev/null
+++ b/src/cmd/tests/libc/string.c
@@ -0,0 +1,124 @@
+#include "../tests.h"
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void test_memcmp(void) {
+ test(0 == memcmp("some", "thing", 0));
+ test(0 != memcmp("some", "thing", 1));
+ test(0 != memcmp("some", "thing", 4));
+
+ test(0 == memcmp("test", "tennis", 0));
+ test(0 == memcmp("test", "tennis", 1));
+ test(0 == memcmp("test", "tennis", 2));
+ test(0 != memcmp("test", "tennis", 3));
+ test(0 != memcmp("test", "tennis", 4));
+ test(0 != memcmp("test", "tennis", 5));
+
+ test(0 > memcmp("foo", "moo", 4));
+ test(0 < memcmp("moo", "foo", 4));
+ test(0 > memcmp("555", "654", 3));
+ test(0 < memcmp("654", "555", 3));
+}
+
+static bool memall(const unsigned char *s, unsigned char c, size_t n) {
+ for (size_t i = 0; i < n; i++)
+ if (s[i] != c) return false;
+ return true;
+}
+
+static void test_memset(void) {
+ const size_t buflen = 4096;
+ void *buf = malloc(buflen);
+ test(buf);
+ for (int i = 0; i < 257; i++) {
+ memset(buf, i, buflen);
+ test(memall(buf, i & 0xff, buflen));
+ }
+ free(buf);
+}
+
+static void test_memmove(void) {
+ const int partsize = 64;
+ char buf[partsize * 3];
+ for (int i = 0; i < partsize * 2; i++) {
+ memset(buf, 0, sizeof buf);
+ for (int j = 0; j < partsize; j++) buf[i + j] = j;
+ memmove(buf + partsize, buf + i, partsize);
+ for (int j = 0; j < partsize; j++) test(buf[partsize + j] == j);
+ }
+}
+
+static void test_strcmp(void) {
+ test(0 == strcmp("string", "string"));
+ test(0 > strcmp("str", "string"));
+ test(0 < strcmp("string", "str"));
+
+ test(0 != strcmp("stress", "string"));
+
+ test(0 != strncmp("abc", "ab", 3));
+ test(0 == strncmp("abc", "ab", 2));
+}
+
+static void test_strtol(void) {
+ char *end;
+ test(1234 == strtol("1234", NULL, 10));
+ test(1234 == strtol("+1234", NULL, 10));
+ test(-1234 == strtol("-1234", NULL, 10));
+
+ test(1234 == strtol("1234", &end, 10));
+ test(!strcmp("", end));
+ test(1234 == strtol(" 1234hello", &end, 10));
+ test(!strcmp("hello", end));
+
+ test(1234 == strtol(" 1234hello", &end, 0));
+ test(!strcmp("hello", end));
+ test(0xCAF3 == strtol(" 0xCaF3hello", &end, 0));
+ test(!strcmp("hello", end));
+ test(01234 == strtol(" 01234hello", &end, 0));
+ test(!strcmp("hello", end));
+}
+
+static void test_strspn(void) {
+ test(0 == strspn("", "1234"));
+ test(0 == strspn("asdf", "1234"));
+ test(0 == strspn("a2df", "1234"));
+ test(2 == strspn("42df", "1234"));
+ test(4 == strspn("4211", "1234"));
+
+ test(0 == strcspn("", "1234"));
+ test(4 == strcspn("asdf", "1234"));
+ test(1 == strcspn("a2df", "1234"));
+}
+
+static void test_strtok(void) {
+ const char *sep = " \t";
+ {
+ char line[] = "LINE TO BE SEPARATED";
+ test(!strcmp(strtok(line, sep), "LINE"));
+ test(!strcmp(strtok(NULL, sep), "TO"));
+ test(!strcmp(strtok(NULL, sep), "BE"));
+ test(!strcmp(strtok(NULL, sep), "SEPARATED"));
+ for (int i = 0; i < 4; i++)
+ test(strtok(NULL, sep) == NULL);
+ }
+ {
+ char line[] = " LINE TO\tBE \t SEPARATED ";
+ test(!strcmp(strtok(line, sep), "LINE"));
+ test(!strcmp(strtok(NULL, sep), "TO"));
+ test(!strcmp(strtok(NULL, sep), "BE"));
+ test(!strcmp(strtok(NULL, sep), "SEPARATED"));
+ for (int i = 0; i < 4; i++)
+ test(strtok(NULL, sep) == NULL);
+ }
+}
+
+void r_libc_string(void) {
+ run_test(test_memcmp);
+ run_test(test_memset);
+ run_test(test_memmove);
+ run_test(test_strcmp);
+ run_test(test_strtol);
+ run_test(test_strspn);
+ run_test(test_strtok);
+}
diff --git a/src/cmd/tests/shared/printf.c b/src/cmd/tests/shared/printf.c
new file mode 100644
index 0000000..d8df48a
--- /dev/null
+++ b/src/cmd/tests/shared/printf.c
@@ -0,0 +1,55 @@
+#include "../tests.h"
+#include <stdio.h>
+#include <string.h>
+
+#pragma GCC diagnostic ignored "-Wformat-truncation"
+
+static void test_printf(void) {
+ char buf[64];
+ memset(buf, '?', 64);
+
+ /* test proper overflow handling in snprintf */
+ test(13 == snprintf(buf, 15, "That's 0x%x", 0x1337));
+ test(!memcmp(buf, "That's 0x1337\0??", 16));
+ test(17 == snprintf(buf, 15, "%05x %05x %05x", 0, 0, 0));
+ test(!memcmp(buf, "00000 00000 00\0?", 16));
+
+ /* all the other stuff */
+ snprintf(buf, sizeof buf, "%010x", 0x1BABE);
+ test(!strcmp(buf, "000001babe"));
+ snprintf(buf, sizeof buf, "%10x", 0x1BABE);
+ test(!strcmp(buf, " 1babe"));
+ snprintf(buf, sizeof buf, "%10s", "hello");
+ test(!strcmp(buf, " hello"));
+
+ snprintf(buf, sizeof buf, "%s%%%s", "ab", "cd");
+ test(!strcmp(buf, "ab%cd"));
+
+ snprintf(buf, sizeof buf, "%05u,%05u", 1234, 56789);
+ test(!strcmp(buf, "01234,56789"));
+
+ snprintf(buf, sizeof buf, "%5d,%5d", 123, 4567);
+ test(!strcmp(buf, " 123, 4567"));
+ snprintf(buf, sizeof buf, "%5d,%5d", -123, -4567);
+ test(!strcmp(buf, " -123,-4567"));
+
+ snprintf(buf, sizeof buf, "%u,%d,%x", 0, 0, 0);
+ test(!strcmp(buf, "0,0,0"));
+
+ /* precision */
+ snprintf(buf, sizeof buf, "%5.2u,%5.2d,%5.2x", 0, 0, 0);
+ test(!strcmp(buf, " 00, 00, 00"));
+ snprintf(buf, sizeof buf, "%5.2u,%5.2d,%5.2x", 10, -10, 0x10);
+ test(!strcmp(buf, " 10, -10, 10"));
+ snprintf(buf, sizeof buf, "%5.3d", -1);
+ test(!strcmp(buf, " -001"));
+ snprintf(buf, sizeof buf, "%.5d", 123);
+ test(!strcmp(buf, "00123"));
+
+ snprintf(buf, sizeof buf, "%.1s,%.10s,%.*s", "hello", "hello", 3, "hello");
+ test(!strcmp(buf, "h,hello,hel"));
+}
+
+void r_s_printf(void) {
+ run_test(test_printf);
+}
diff --git a/src/cmd/tests/shared/ringbuf.c b/src/cmd/tests/shared/ringbuf.c
new file mode 100644
index 0000000..d2a35a1
--- /dev/null
+++ b/src/cmd/tests/shared/ringbuf.c
@@ -0,0 +1,49 @@
+#include "../tests.h"
+#include <shared/ring.h>
+#include <string.h>
+
+static void test_ringbuf(void) {
+ char backbuf[16], cmpbuf[16];
+ size_t num_read = 0, num_written = 0;
+ uint8_t c;
+
+ ring_t r = {backbuf, 16, 0, 0};
+
+ /* aliasing */
+ for (size_t i = 0; i < 16; i++) {
+ test(ring_used(&r) == 0);
+ test(ring_avail(&r) == 16);
+ ring_put(&r, "11 bytes...", 11);
+ test(ring_used(&r) == 11);
+ test(ring_avail(&r) == 5);
+
+ memset(cmpbuf, 0, sizeof cmpbuf);
+ test(ring_get(&r, cmpbuf, 16) == 11);
+ test(memcmp(cmpbuf, "11 bytes...", 11) == 0);
+ }
+
+ test(ring_used(&r) == 0);
+ for (size_t i = 0; i < 7; i++)
+ ring_put1b(&r, num_written++);
+ test(ring_used(&r) == 7);
+ for (size_t i = 0; i < 3; i++) {
+ ring_get(&r, &c, 1);
+ test(num_read++ == c);
+ }
+ test(ring_used(&r) == 4);
+
+ for (size_t j = 0; j < 40; j++) {
+ for (size_t i = 0; i < 7; i++)
+ ring_put1b(&r, num_written++ & 0xff);
+ test(ring_used(&r) == 11);
+ for (size_t i = 0; i < 7; i++) {
+ ring_get(&r, &c, 1);
+ test((num_read++ & 0xff) == c);
+ }
+ test(ring_used(&r) == 4);
+ }
+}
+
+void r_s_ringbuf(void) {
+ run_test(test_ringbuf);
+}
diff --git a/src/cmd/tests/stress.c b/src/cmd/tests/stress.c
new file mode 100644
index 0000000..1ef018c
--- /dev/null
+++ b/src/cmd/tests/stress.c
@@ -0,0 +1,28 @@
+#include "tests.h"
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static void run_forked(void (*fn)()) {
+ if (!fork()) {
+ fn();
+ exit(0);
+ } else {
+ /* successful tests must return 0
+ * TODO add a better fail msg */
+ if (_sys_await() != 0) test_fail();
+ }
+}
+
+
+static void stress_fork(void) {
+ for (size_t i = 0; i < 2048; i++) {
+ if (!fork()) exit(0);
+ _sys_await();
+ }
+}
+
+void stress_all(void) {
+ run_forked(stress_fork);
+}
diff --git a/src/cmd/tests/tests.c b/src/cmd/tests/tests.c
new file mode 100644
index 0000000..5cba682
--- /dev/null
+++ b/src/cmd/tests/tests.c
@@ -0,0 +1,68 @@
+#include "tests.h"
+#include <camellia/syscalls.h>
+#include <unistd.h>
+
+__attribute__((visibility("hidden")))
+extern char __executable_start[];
+
+FILE *fail_trig;
+
+void run_test(void (*fn)()) {
+ if (!fork()) {
+ fn();
+ _sys_filicide();
+ exit(0);
+ } else {
+ /* successful tests must return 0 */
+ if (_sys_await() != 0) {
+ test_failf("%p, base %p", (void*)((void*)fn - (void*)__executable_start), __executable_start);
+ }
+ }
+}
+
+int forkpipe(FILE **f, hid_t *h) {
+ hid_t ends[2];
+ if (_sys_pipe(ends, 0) < 0) {
+ fprintf(stderr, "couldn't create pipe\n");
+ exit(1);
+ }
+ int ret = fork();
+ if (!ret) {
+ close(ends[0]);
+ *f = fdopen(ends[1], "w");
+ *h = -1;
+ } else {
+ close(ends[1]);
+ *f = NULL;
+ *h = ends[0];
+ }
+ return ret;
+}
+
+int main(void) {
+ hid_t reader;
+ if (!forkpipe(&fail_trig, &reader)) {
+ r_k_miscsyscall();
+ r_k_fs();
+ r_k_fdlimit();
+ r_k_misc();
+ r_k_path();
+ r_k_threads();
+ r_libc_esemaphore();
+ r_libc_setjmp();
+ r_libc_string();
+ r_s_printf();
+ r_s_ringbuf();
+ exit(0);
+ } else {
+ for (;;) {
+ char buf[128];
+ long ret = _sys_read(reader, buf, sizeof buf, 0);
+ if (ret < 0) break;
+ printf("\033[31mFAIL\033[0m ");
+ fwrite(buf, ret, 1, stdout);
+ printf("\n");
+ }
+ }
+ return 0;
+}
diff --git a/src/cmd/tests/tests.h b/src/cmd/tests/tests.h
new file mode 100644
index 0000000..5037e1a
--- /dev/null
+++ b/src/cmd/tests/tests.h
@@ -0,0 +1,37 @@
+#pragma once
+#include <camellia/syscalls.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define TMPFILEPATH "/tmp/.test_internal"
+
+void run_test(void (*fn)());
+
+void r_k_fdlimit(void);
+void r_k_fs(void);
+void r_k_misc(void);
+void r_k_miscsyscall(void);
+void r_k_path(void);
+void r_k_threads(void);
+void r_libc_esemaphore(void);
+void r_libc_setjmp(void);
+void r_libc_string(void);
+void r_s_printf(void);
+void r_s_ringbuf(void);
+
+extern FILE *fail_trig;
+
+int forkpipe(FILE **f, hid_t *h);
+
+#define argify(str) str, sizeof(str) - 1
+#define test_fail() do { \
+ fprintf(fail_trig, "%s():%u", __func__, __LINE__); \
+ fflush(fail_trig); \
+ exit(0); \
+} while (0)
+#define test_failf(fmt, ...) do { \
+ fprintf(fail_trig, "%s():%u " fmt, __func__, __LINE__, __VA_ARGS__); \
+ fflush(fail_trig); \
+ exit(0); \
+} while (0)
+#define test(cond) if (!(cond)) test_fail();
diff --git a/src/cmd/tmpfs/tmpfs.c b/src/cmd/tmpfs/tmpfs.c
new file mode 100644
index 0000000..6d58790
--- /dev/null
+++ b/src/cmd/tmpfs/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;
+}
diff --git a/src/cmd/vterm/draw.c b/src/cmd/vterm/draw.c
new file mode 100644
index 0000000..ee36a0f
--- /dev/null
+++ b/src/cmd/vterm/draw.c
@@ -0,0 +1,17 @@
+#include "vterm.h"
+#include <camellia/execbuf.h>
+#include <camellia/syscalls.h>
+#include <string.h>
+
+struct framebuf fb;
+struct rect dirty;
+
+void scroll(void) {
+ size_t row_len = fb.pitch * font.h;
+ memmove(fb.b, fb.b + row_len, fb.len - row_len);
+ memset(fb.b + fb.len - row_len, 0, row_len);
+ cursor.y--;
+
+ dirty.x1 = 0; dirty.y1 = 0;
+ dirty.x2 = ~0; dirty.y2 = ~0;
+}
diff --git a/src/cmd/vterm/font.c b/src/cmd/vterm/font.c
new file mode 100644
index 0000000..777b094
--- /dev/null
+++ b/src/cmd/vterm/font.c
@@ -0,0 +1,76 @@
+#include "vterm.h"
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct psf2 font;
+void *font_data;
+
+void font_load(const char *path) {
+ FILE *f = fopen(path, "r");
+ if (!f) {
+ err(1, "couldn't open font \"%s\"", path);
+ }
+
+ void *buf;
+ long buflen;
+
+ fseek(f, 0, SEEK_END);
+ buflen = ftell(f);
+ if (buflen < 0) {
+ errx(1, "can't get the size of \"%s\"", path);
+ }
+
+ buf = malloc(buflen);
+ if (!buf) {
+ err(1, "out of memory");
+ }
+
+ fseek(f, 0, SEEK_SET);
+ fread(buf, 1, buflen, f);
+ if (ferror(f)) {
+ err(1, "error reading font");
+ }
+ fclose(f);
+
+ if (!memcmp(buf, "\x72\xb5\x4a\x86", 4)) {
+ memcpy(&font, buf, sizeof font);
+ font_data = buf + font.glyph_offset;
+ } else if (!memcmp(buf, "\x36\x04", 2)) {
+ struct psf1 *hdr = buf;
+ font = (struct psf2){
+ .glyph_amt = 256,
+ .glyph_size = hdr->h,
+ .h = hdr->h,
+ .w = 8,
+ };
+ font_data = buf + 4;
+ } else {
+ errx(1, "invalid psf header");
+ }
+}
+
+void font_blit(uint32_t glyph, int x, int y) {
+ if (glyph >= font.glyph_amt) glyph = 0;
+ if (x < 0 || (x+1) * font.w >= fb.width ||
+ y < 0 || (y+1) * font.h >= fb.height)
+ {
+ return;
+ }
+
+ dirty_mark(&dirty, x * font.w, y * font.h);
+ dirty_mark(&dirty, (x+1) * font.w - 1, (y+1) * font.h - 1);
+
+ char *bitmap = font_data + font.glyph_size * glyph;
+ for (size_t i = 0; i < font.w; i++) {
+ for (size_t j = 0; j < font.h; j++) {
+ size_t idx = j * font.w + i;
+ char byte = bitmap[idx / 8];
+ byte >>= (7-(idx&7));
+ byte &= 1;
+ *((uint32_t*)&fb.b[fb.pitch * (y * font.h + j) + 4 * (x * font.w + i)]) = byte * 0xB0B0B0;
+ }
+ }
+ return;
+}
diff --git a/src/cmd/vterm/vterm.c b/src/cmd/vterm/vterm.c
new file mode 100644
index 0000000..f365f6b
--- /dev/null
+++ b/src/cmd/vterm/vterm.c
@@ -0,0 +1,72 @@
+#include "vterm.h"
+#include <camellia/syscalls.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <camellia/compat.h>
+
+struct point cursor = {0};
+
+void in_char(char c) {
+ switch (c) {
+ case '\n':
+ cursor.x = 0;
+ cursor.y++;
+ break;
+ case '\b':
+ if (cursor.x > 0) cursor.x--;
+ break;
+ case '\t':
+ /* rounds down to nearest multiple of 8 and adds 8
+ = adding 1 and rounding up to the nearest multiple of 8 */
+ cursor.x = (cursor.x & ~7) + 8;
+ break;
+ default:
+ font_blit(c, cursor.x, cursor.y);
+ cursor.x++;
+ }
+
+ if (cursor.x * font.w >= fb.width) {
+ cursor.x = 0;
+ cursor.y++;
+ }
+ while ((cursor.y + 1) * font.h >= fb.height) scroll();
+}
+
+int main(void) {
+ if (fb_setup(&fb, "/kdev/video/") < 0) {
+ eprintf("fb_setup error");
+ return 1;
+ }
+ font_load("/init/usr/share/fonts/spleen/spleen-8x16.psfu");
+
+ static char buf[512];
+ struct ufs_request res;
+ while (!c0_fs_wait(buf, sizeof buf, &res)) {
+ switch (res.op) {
+ case VFSOP_OPEN:
+ // TODO check path
+ c0_fs_respond(NULL, 0, 0);
+ break;
+
+ case VFSOP_WRITE:
+ if (res.flags) {
+ c0_fs_respond(NULL, -1, 0);
+ } else {
+ for (size_t i = 0; i < res.len; i++)
+ in_char(buf[i]);
+ dirty_flush(&dirty, &fb);
+ c0_fs_respond(NULL, res.len, 0);
+ }
+ break;
+
+ default:
+ c0_fs_respond(NULL, -1, 0);
+ break;
+ }
+ }
+
+ return 1;
+}
diff --git a/src/cmd/vterm/vterm.h b/src/cmd/vterm/vterm.h
new file mode 100644
index 0000000..026e71a
--- /dev/null
+++ b/src/cmd/vterm/vterm.h
@@ -0,0 +1,39 @@
+#pragma once
+#include <camellia/types.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <draw.h>
+
+#define eprintf(fmt, ...) fprintf(stderr, "vterm: "fmt"\n" __VA_OPT__(,) __VA_ARGS__)
+
+
+struct psf1 {
+ uint16_t magic;
+ uint8_t mode;
+ uint8_t h;
+} __attribute__((packed));
+struct psf2 {
+ uint32_t magic;
+ uint32_t version;
+ uint32_t glyph_offset;
+ uint32_t flags;
+ uint32_t glyph_amt;
+ uint32_t glyph_size;
+ uint32_t h;
+ uint32_t w;
+} __attribute__((packed));
+extern struct psf2 font;
+extern void *font_data;
+void font_load(const char *path);
+void font_blit(uint32_t glyph, int x, int y);
+
+extern struct framebuf fb;
+
+extern struct rect dirty;
+void vdirty_mark(uint32_t x, uint32_t y);
+void flush(void);
+void scroll(void);
+
+struct point {uint32_t x, y;};
+extern struct point cursor;
+void in_char(char c);