diff options
author | dzwdz | 2023-08-14 18:51:07 +0200 |
---|---|---|
committer | dzwdz | 2023-08-14 18:51:07 +0200 |
commit | 642b5fb0007b64c77d186fcb018d571152ee1d47 (patch) | |
tree | 1c466461f3602d306be309a053edae558ef2568e /src/cmd | |
parent | 8050069c57b729c18c19b1a03ab6e4bf63b4735e (diff) |
reorganization: first steps
Diffstat (limited to 'src/cmd')
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 = ðer_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); |