summaryrefslogtreecommitdiff
path: root/src/libk
diff options
context:
space:
mode:
authordzwdz2023-08-14 18:51:07 +0200
committerdzwdz2023-08-14 18:51:07 +0200
commit642b5fb0007b64c77d186fcb018d571152ee1d47 (patch)
tree1c466461f3602d306be309a053edae558ef2568e /src/libk
parent8050069c57b729c18c19b1a03ab6e4bf63b4735e (diff)
reorganization: first steps
Diffstat (limited to 'src/libk')
-rw-r--r--src/libk/fsutil.c51
-rw-r--r--src/libk/include/assert.h7
-rw-r--r--src/libk/include/camellia/errno.h28
-rw-r--r--src/libk/include/camellia/execbuf.h9
-rw-r--r--src/libk/include/camellia/flags.h35
-rw-r--r--src/libk/include/camellia/fsutil.h12
-rw-r--r--src/libk/include/camellia/path.h17
-rw-r--r--src/libk/include/camellia/syscalls.h96
-rw-r--r--src/libk/include/camellia/types.h41
-rw-r--r--src/libk/include/shared/mem.h18
-rw-r--r--src/libk/include/shared/printf.h6
-rw-r--r--src/libk/include/shared/ring.h23
-rw-r--r--src/libk/mem.c82
-rw-r--r--src/libk/path.c53
-rw-r--r--src/libk/printf.c268
-rw-r--r--src/libk/ring.c60
16 files changed, 806 insertions, 0 deletions
diff --git a/src/libk/fsutil.c b/src/libk/fsutil.c
new file mode 100644
index 0000000..05ca44a
--- /dev/null
+++ b/src/libk/fsutil.c
@@ -0,0 +1,51 @@
+#include <assert.h>
+#include <camellia/fsutil.h>
+#include <limits.h>
+
+void fs_normslice(long *restrict offset, size_t *restrict length, size_t max, bool expand)
+{
+ assert(max <= (size_t)LONG_MAX);
+
+ if (*offset < 0) {
+ /* Negative offsets are relative to EOF + 1.
+ * Thus:
+ * write(-1) writes right after the file ends; atomic append
+ * write(-n) writes, overriding the last (n-1) bytes
+ * read(-n) reads the last (n-1) bytes
+ */
+ *offset += max + 1;
+ if (*offset < 0) {
+ /* cursor went before the file, EOF */
+ *length = 0;
+ *offset = max;
+ goto end;
+ }
+ }
+
+ if (expand) {
+ /* This is a write() to a file with a dynamic size.
+ * We don't care if it goes past the current size, because the
+ * driver can handle expanding it. */
+ } else {
+ /* This operation can't extend the file, it's either:
+ * - any read()
+ * - a write() to a file with a static size (e.g. a framebuffer)
+ * *offset and *length describe a slice of a buffer with length max,
+ * so their sum must not overflow it. */
+ if ((size_t)*offset <= max) {
+ size_t maxlen = max - (size_t)*offset;
+ if (*length > maxlen)
+ *length = maxlen;
+ } else {
+ /* regular EOF */
+ *length = 0;
+ *offset = max;
+ goto end;
+ }
+ }
+
+end:
+ assert(0 <= *offset);
+ if (!expand)
+ assert(*offset + *length <= max);
+}
diff --git a/src/libk/include/assert.h b/src/libk/include/assert.h
new file mode 100644
index 0000000..7520aa9
--- /dev/null
+++ b/src/libk/include/assert.h
@@ -0,0 +1,7 @@
+#ifdef NDEBUG
+#define assert(stmt) do {} while (0)
+#else
+#define assert(stmt) do { if (!(stmt)) __badassert(__func__, __FILE__, __LINE__); } while (0)
+#endif
+
+_Noreturn void __badassert(const char *func, const char *file, int line);
diff --git a/src/libk/include/camellia/errno.h b/src/libk/include/camellia/errno.h
new file mode 100644
index 0000000..1177f54
--- /dev/null
+++ b/src/libk/include/camellia/errno.h
@@ -0,0 +1,28 @@
+#pragma once
+/* the comments are directly pasted into user visible strings.
+ * keep them short, don't include " */
+
+#define EGENERIC 1 /* unknown error */
+#define EFAULT 2
+#define EBADF 3 /* bad file descriptor */
+#define EINVAL 4
+#define ENOSYS 5 /* unsupported */
+#define ERANGE 6
+#define ENOMEM 7
+#define ENOENT 8
+#define ENOTEMPTY 9
+#define EACCES 10
+#define EMFILE 11 /* all file descriptors taken */
+#define ECONNRESET 12
+#define EPIPE 13
+#define ECHILD 14
+
+#define EISDIR 200
+#define ENAMETOOLONG 201
+#define ENOTDIR 202
+#define ELOOP 203
+#define ENOEXEC 204
+#define EINTR 205
+#define EWOULDBLOCK 206
+#define EEXIST 207
+#define EAGAIN 208
diff --git a/src/libk/include/camellia/execbuf.h b/src/libk/include/camellia/execbuf.h
new file mode 100644
index 0000000..de7ae3b
--- /dev/null
+++ b/src/libk/include/camellia/execbuf.h
@@ -0,0 +1,9 @@
+#pragma once
+/* the instruction format is bound to change, atm it's extremely inefficient */
+
+#define EXECBUF_MAX_LEN 4096
+
+/* takes 6 arguments */
+#define EXECBUF_SYSCALL 0xF0000001
+/* takes 1 argument, changes %rip */
+#define EXECBUF_JMP 0xF0000002
diff --git a/src/libk/include/camellia/flags.h b/src/libk/include/camellia/flags.h
new file mode 100644
index 0000000..f4c54fe
--- /dev/null
+++ b/src/libk/include/camellia/flags.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#define MEMFLAG_PRESENT 1
+#define MEMFLAG_FINDFREE 2
+
+#define FORK_NOREAP 1
+#define FORK_NEWFS 2
+#define FORK_SHAREMEM 4
+#define FORK_SHAREHANDLE 8
+
+#define WRITE_TRUNCATE 1
+
+#define FSR_DELEGATE 1
+
+#define DUP_SEARCH 1
+
+#define OPEN_READ 1
+#define OPEN_WRITE 2
+#define OPEN_RW 3
+/* not setting OPEN_READ nor OPEN_WRITE works as if OPEN_READ was set, but it also checks the execute bit.
+ * same as in plan9. */
+#define OPEN_EXEC 0
+
+#define OPEN_READABLE(flags) ((flags & 3) != OPEN_WRITE)
+#define OPEN_WRITEABLE(flags) (flags & OPEN_WRITE)
+
+/* Requires OPEN_WRITE to be set, enforced by the kernel.
+ * The idea is that if all flags which allow modifying the filesystem state require
+ * OPEN_WRITE to be set, filesystem handlers could just check for the OPEN_WRITE flag. */
+#define OPEN_CREATE 4
+
+
+/* special handles */
+#define HANDLE_NULLFS -2
+#define HANDLE_PROCFS -3
diff --git a/src/libk/include/camellia/fsutil.h b/src/libk/include/camellia/fsutil.h
new file mode 100644
index 0000000..8b8c4fc
--- /dev/null
+++ b/src/libk/include/camellia/fsutil.h
@@ -0,0 +1,12 @@
+#pragma once
+#include <stdbool.h>
+#include <stddef.h>
+
+/** Normalizes the offset and length passed to a fs into safe values.
+ *
+ * @param expand Can this operation expand the target file?
+ * true if writing to a file with an adjustable size
+ * false if reading any sort of file
+ * or writing to a file with static size
+ */
+void fs_normslice(long *restrict offset, size_t *restrict length, size_t max, bool expand);
diff --git a/src/libk/include/camellia/path.h b/src/libk/include/camellia/path.h
new file mode 100644
index 0000000..b268595
--- /dev/null
+++ b/src/libk/include/camellia/path.h
@@ -0,0 +1,17 @@
+#pragma once
+#include <stddef.h>
+
+#define PATH_MAX 512
+
+/** Reduce a path to its simplest form.
+ * Kinds of invalid paths:
+ * - relative - "" "a" "./a"
+ * - going behind the root directory - "/../"
+ *
+ * @return On success, length of the string in *out, <= len. 0 if the path was invalid.
+ *
+ * returns an unsigned type because:
+ * 1. valid paths always return at least 1, for the initial slash
+ * 2. it makes it easier to assign the result to an unsigned variable and check for error
+ */
+size_t path_simplify(const char *in, char *out, size_t len);
diff --git a/src/libk/include/camellia/syscalls.h b/src/libk/include/camellia/syscalls.h
new file mode 100644
index 0000000..9a8fa94
--- /dev/null
+++ b/src/libk/include/camellia/syscalls.h
@@ -0,0 +1,96 @@
+#pragma once
+
+#define _SYS_EXIT 0
+#define _SYS_AWAIT 1
+#define _SYS_FORK 2
+#define _SYS_OPEN 3
+#define _SYS_MOUNT 4
+#define _SYS_DUP 5
+#define _SYS_READ 6
+#define _SYS_WRITE 7
+#define _SYS_GETSIZE 8
+#define _SYS_REMOVE 9
+#define _SYS_CLOSE 10
+#define _SYS_FS_WAIT 11
+#define _SYS_FS_RESPOND 12
+#define _SYS_MEMFLAG 13
+#define _SYS_PIPE 14
+#define _SYS_SLEEP 15
+#define _SYS_FILICIDE 16
+#define _SYS_INTR 17
+#define _SYS_INTR_SET 18
+#define _SYS_GETPID 19
+#define _SYS_GETPPID 20
+#define _SYS_WAIT2 21
+
+#define _SYS_EXECBUF 100
+#define _SYS_DEBUG_KLOG 101
+
+#ifndef ASM_FILE
+#include <camellia/types.h>
+#include <stddef.h>
+
+long _syscall(long, long, long, long, long, long);
+
+/** Kills the current process.
+ */
+_Noreturn void _sys_exit(long ret);
+
+/** Waits for a child to exit.
+ * @return the value the child passed to exit()
+ */
+long _sys_await(void);
+
+/** Creates a copy of the current process, and executes it.
+ * All user memory pages get copied too.
+ *
+ * @param flags FORK_NOREAP, FORK_NEWFS
+ * @param fs_front requires FORK_NEWFS. the front handle to the new fs is put there
+ *
+ * @return 0 in the child, the CID in the parent.
+ */
+long _sys_fork(int flags, hid_t __user *fs_front);
+
+hid_t _sys_open(const char __user *path, long len, int flags);
+long _sys_mount(hid_t h, const char __user *path, long len);
+hid_t _sys_dup(hid_t from, hid_t to, int flags);
+
+long _sys_read(hid_t h, void __user *buf, size_t len, long offset);
+long _sys_write(hid_t h, const void __user *buf, size_t len, long offset, int flags);
+long _sys_getsize(hid_t h);
+long _sys_remove(hid_t h);
+long _sys_close(hid_t h);
+
+hid_t _sys_fs_wait(char __user *buf, long max_len, struct ufs_request __user *res);
+long _sys_fs_respond(hid_t hid, const void __user *buf, long ret, int flags);
+
+/** Modifies the virtual address space.
+ *
+ * If the MEMFLAG_PRESENT flag is present - mark the memory region as allocated.
+ * Otherwise, free it.
+ *
+ * MEMFLAG_FINDFREE tries to find the first free region of length `len`.
+ *
+ * @return address of the first affected page (usually == addr)
+ */
+void __user *_sys_memflag(void __user *addr, size_t len, int flags);
+long _sys_pipe(hid_t __user user_ends[2], int flags);
+
+void _sys_sleep(long ms);
+
+void _sys_filicide(void);
+void _sys_intr(void);
+void _sys_intr_set(void __user *ip);
+
+uint32_t _sys_getpid(void);
+uint32_t _sys_getppid(void);
+
+// TODO deprecate await
+int _sys_wait2(int pid, int flags, struct sys_wait2 __user *out);
+
+/* see shared/execbuf.h */
+long _sys_execbuf(void __user *buf, size_t len);
+
+void _sys_debug_klog(const void __user *buf, size_t len);
+
+#endif
diff --git a/src/libk/include/camellia/types.h b/src/libk/include/camellia/types.h
new file mode 100644
index 0000000..65200d7
--- /dev/null
+++ b/src/libk/include/camellia/types.h
@@ -0,0 +1,41 @@
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __CHECKER__
+# define __user __attribute__((noderef, address_space(__user)))
+# define __force __attribute__((force))
+#else
+# define __user
+# define __force
+#endif
+
+typedef void __user * userptr_t;
+typedef int hid_t;
+
+enum vfs_op {
+ VFSOP_OPEN,
+ VFSOP_READ,
+ VFSOP_WRITE,
+ VFSOP_GETSIZE,
+ VFSOP_REMOVE,
+ VFSOP_CLOSE,
+};
+
+struct ufs_request {
+ enum vfs_op op;
+ size_t len; // how much was put in *buf
+ size_t capacity; // how much output can be accepted by the caller
+ void __user *id; // file id (returned by the open handler, passed to other calls)
+ long offset;
+ int flags;
+};
+
+struct intr_data {
+ void __user *ip;
+ void __user *sp; /* last for pop %rsp */
+};
+
+struct sys_wait2 {
+ long status;
+};
diff --git a/src/libk/include/shared/mem.h b/src/libk/include/shared/mem.h
new file mode 100644
index 0000000..3597edf
--- /dev/null
+++ b/src/libk/include/shared/mem.h
@@ -0,0 +1,18 @@
+#pragma once
+#include <stdarg.h>
+#include <stddef.h>
+
+/* note: (partially) tested in the userland tests */
+
+void *memchr(const void *s, int c, size_t n);
+int memcmp(const void *s1, const void *s2, size_t n);
+
+void *memcpy(void *dest, const void *src, size_t n);
+void *memmove(void *dest, const void *src, size_t n);
+void *memset(void *s, int c, size_t n);
+
+int strcmp(const char *s1, const char *s2);
+size_t strlen(const char *s);
+
+int snprintf(char *restrict str, size_t len, const char *restrict fmt, ...);
+int vsnprintf(char *restrict str, size_t len, const char *restrict fmt, va_list ap);
diff --git a/src/libk/include/shared/printf.h b/src/libk/include/shared/printf.h
new file mode 100644
index 0000000..45fd358
--- /dev/null
+++ b/src/libk/include/shared/printf.h
@@ -0,0 +1,6 @@
+#pragma once
+#include <stdarg.h>
+#include <stddef.h>
+
+int __printf_internal(const char *fmt, va_list argp,
+ void (*back)(void*, const char*, size_t), void *back_arg);
diff --git a/src/libk/include/shared/ring.h b/src/libk/include/shared/ring.h
new file mode 100644
index 0000000..dbaf331
--- /dev/null
+++ b/src/libk/include/shared/ring.h
@@ -0,0 +1,23 @@
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+
+typedef struct {
+ char *buf;
+ size_t capacity;
+ size_t _head, _tail;
+} ring_t;
+
+/** Returns amount of bytes stored in the buffer. */
+size_t ring_used(ring_t*);
+/** Returns amount of space left in the buffer. */
+size_t ring_avail(ring_t*);
+
+void ring_put(ring_t*, const void*, size_t);
+void ring_put1b(ring_t*, uint8_t);
+
+size_t ring_get(ring_t*, void*, size_t);
+
+/** Consumes up to `len` bytes, and returns a pointer to the buffer where it's stored.
+ * Not thread-safe. */
+void* ring_contig(ring_t*, size_t *len);
diff --git a/src/libk/mem.c b/src/libk/mem.c
new file mode 100644
index 0000000..14dd6bd
--- /dev/null
+++ b/src/libk/mem.c
@@ -0,0 +1,82 @@
+#include <assert.h>
+#include <shared/mem.h>
+#include <stdint.h>
+
+union dualptr_const { const uintptr_t *w; const char *c; uintptr_t u; };
+union dualptr { uintptr_t *w; char *c; uintptr_t u; };
+
+void *memchr(const void *s, int c, size_t n) {
+ const unsigned char *s2 = s;
+ for (size_t i = 0; i < n; i++) {
+ if (s2[i] == (unsigned char)c)
+ return (void*)&s2[i];
+ }
+ return NULL;
+}
+
+int memcmp(const void *s1, const void *s2, size_t n) {
+ const unsigned char *c1 = s1, *c2 = s2;
+ for (size_t i = 0; i < n; i++) {
+ if (c1[i] != c2[i]) {
+ if (c1[i] < c2[i]) return -1;
+ else return 1;
+ }
+ }
+ return 0;
+}
+
+void *memcpy(void *dest, const void *src, size_t n) {
+ // TODO erms, rep movsb
+ union dualptr_const s = {.c = src};
+ union dualptr d = {.c = dest};
+ if (dest == src) return dest;
+ // assert(src >= dest || src + n < dest);
+
+ for (; (d.u & 7) && n != 0; n--) {
+ *(d.c)++ = *(s.c)++;
+ }
+ while (n >= sizeof(uintptr_t)) {
+ *(d.w)++ = *(s.w)++;
+ n -= sizeof(uintptr_t);
+ }
+ while (n-- != 0) {
+ *(d.c)++ = *(s.c)++;
+ }
+ return dest;
+}
+
+void *memmove(void *dest, const void *src, size_t n) {
+ if (src >= dest || src + n < dest)
+ return memcpy(dest, src, n);
+ for (; n; n--) /* naive reverse copy */
+ ((uint8_t*)dest)[n-1] = ((uint8_t*)src)[n-1];
+ return dest;
+}
+
+void *memset(void *s, int c, size_t n) {
+ union dualptr d = {.c = s};
+
+ uintptr_t fill = (c & 0xff) * 0x0101010101010101;
+ while (n >= sizeof(uintptr_t)) {
+ *(d.w)++ = fill;
+ n -= sizeof(uintptr_t);
+ }
+ while (n-- != 0)
+ *(d.c)++ = c;
+ return s;
+}
+
+int strcmp(const char *s1, const char *s2) {
+ while (*s1 && *s1 == *s2) {
+ s1++; s2++;
+ }
+ if (*s1 == *s2) return 0;
+ if (*s1 < *s2) return -1;
+ else return 1;
+}
+
+size_t strlen(const char *s) {
+ size_t c = 0;
+ while (*s++) c++;
+ return c;
+}
diff --git a/src/libk/path.c b/src/libk/path.c
new file mode 100644
index 0000000..4e3077b
--- /dev/null
+++ b/src/libk/path.c
@@ -0,0 +1,53 @@
+#include <assert.h>
+#include <camellia/path.h>
+#include <shared/mem.h>
+#include <stdbool.h>
+
+size_t path_simplify(const char *in, char *out, size_t len) {
+ if (len == 0) return 0; /* empty paths are invalid */
+ if (in[0] != '/') return 0; /* so are relative paths */
+
+ int seg_len;
+ int out_pos = 0;
+ bool directory = false;
+
+ for (size_t i = 0; i < len; i += seg_len + 1) {
+ assert(in[i] == '/');
+
+ seg_len = 0;
+ directory = false;
+ for (size_t j = i + 1; j < len; j++) {
+ if (in[j] == '/') {
+ directory = true;
+ break;
+ }
+ seg_len++;
+ }
+
+ /* |i=5 |next i = i + seg_len + 1 = 10
+ * v v
+ * /some/path/asdf
+ * |--|
+ * seg_len = 4, segment starts at i+1 */
+
+ if (seg_len == 0 || (seg_len == 1 && in[i + 1] == '.')) {
+ /* // or /./ */
+ directory = true;
+ } else if (seg_len == 2 && in[i + 1] == '.' && in[i + 2] == '.') {
+ /* /../ */
+ directory = true;
+ /* try to backtrack to the last slash */
+ while (--out_pos >= 0 && out[out_pos] != '/');
+ if (out_pos < 0) return 0;
+ } else {
+ /* a normal segment, e.g. /asdf/ */
+ out[out_pos] = '/';
+ memcpy(&out[out_pos + 1], &in[i + 1], seg_len);
+ out_pos += seg_len + 1;
+ }
+ }
+
+ if (directory) out[out_pos++] = '/';
+ assert(0 < out_pos && (size_t)out_pos <= len);
+ return out_pos;
+}
diff --git a/src/libk/printf.c b/src/libk/printf.c
new file mode 100644
index 0000000..fffd801
--- /dev/null
+++ b/src/libk/printf.c
@@ -0,0 +1,268 @@
+#include <shared/mem.h>
+#include <shared/printf.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+enum lenmod {
+ LM_int,
+ LM_long,
+ LM_longlong,
+ LM_size,
+};
+
+struct out_state {
+ void (*back)(void *, const char *, size_t);
+ void *backarg;
+ int written;
+
+ char *cache;
+ size_t cpos, clen;
+};
+
+struct mods {
+ char fill_char;
+ size_t field_width;
+ size_t precision;
+};
+
+
+static int flush(struct out_state *os) {
+ if (os->cpos) {
+ os->back(os->backarg, os->cache, os->cpos);
+ os->written += os->cpos;
+ os->cpos = 0;
+ }
+ return os->written;
+}
+
+static void output(struct out_state *os, const char *buf, size_t len) {
+ if (os->cpos + len < os->clen) {
+ memcpy(os->cache + os->cpos, buf, len);
+ os->cpos += len;
+ return;
+ }
+ flush(os);
+ os->back(os->backarg, buf, len);
+ os->written += len;
+}
+
+static void output_c(struct out_state *os, char c, int amt) {
+ for (int i = 0; i < amt; i++)
+ output(os, &c, 1);
+}
+
+
+static void pad(struct out_state *os, struct mods *m, size_t len) {
+ output_c(os, m->fill_char, m->field_width - len);
+}
+
+static void padnum(struct out_state *os, struct mods *m, size_t len, char sign) {
+ if (len < m->precision) {
+ output_c(os, m->fill_char, m->field_width - m->precision - (sign ? 1 : 0));
+ if (sign) output_c(os, sign, 1);
+ output_c(os, '0', m->precision - len);
+ } else {
+ output_c(os, m->fill_char, m->field_width - len - (sign ? 1 : 0));
+ if (sign) output_c(os, sign, 1);
+ }
+}
+
+static void output_uint(struct out_state *os, struct mods *m, unsigned long long n, char sign) {
+ char buf[sizeof(unsigned long long) * 3];
+ size_t pos = sizeof(buf);
+
+ if (!n) {
+ buf[--pos] = '0';
+ } else while (n) {
+ unsigned long long q = n / 10, r = n % 10;
+ buf[--pos] = r + '0';
+ n = q;
+ }
+ size_t len = sizeof(buf) - pos;
+ padnum(os, m, len, sign);
+ output(os, buf + pos, len);
+}
+
+static void output_uint16(struct out_state *os, struct mods *m, unsigned long long n) {
+ size_t len = 1;
+ while (n >> (len * 4) && (len * 4) < (sizeof(n) * 8))
+ len++;
+ padnum(os, m, len, '\0');
+ while (len-- > 0) {
+ char h = '0' + ((n >> (len * 4)) & 0xf);
+ if (h > '9') h += 'a' - '9' - 1;
+ output_c(os, h, 1);
+ }
+}
+
+
+int __printf_internal(const char *fmt, va_list argp,
+ void (*back)(void*, const char*, size_t), void *backarg)
+{
+ const char *seg = fmt; /* beginning of the current non-modifier streak */
+ char cache[64];
+ struct out_state os = {
+ .back = back,
+ .backarg = backarg,
+ .cache = cache,
+ .cpos = 0,
+ .clen = sizeof(cache),
+ };
+
+ for (;;) {
+ char c = *fmt++;
+ if (c == '\0') {
+ output(&os, seg, fmt - seg - 1);
+ return flush(&os);
+ }
+ if (c != '%') continue;
+ output(&os, seg, fmt - seg - 1);
+
+ struct mods m = {
+ .fill_char = ' ',
+ .field_width = 0,
+ .precision = 0,
+ };
+
+ for (bool modifier = true; modifier;) {
+ c = *fmt++;
+ switch (c) {
+ case '0':
+ m.fill_char = '0';
+ break;
+ default:
+ modifier = false;
+ break;
+ }
+ }
+
+ while ('0' <= c && c <= '9') {
+ m.field_width *= 10;
+ m.field_width += c - '0';
+ c = *fmt++;
+ }
+
+ if (c == '.') {
+ c = *fmt++;
+ if (c == '*') {
+ // TODO handle negative precision
+ m.precision = va_arg(argp, int);
+ c = *fmt++;
+ } else while ('0' <= c && c <= '9') {
+ m.precision *= 10;
+ m.precision += c - '0';
+ c = *fmt++;
+ }
+ }
+
+ enum lenmod lm;
+ switch (c) {
+ case 'l':
+ lm = LM_long;
+ c = *fmt++;
+ if (c == 'l') {
+ lm = LM_longlong;
+ c = *fmt++;
+ }
+ break;
+ case 'z':
+ lm = LM_size;
+ c = *fmt++;
+ break;
+ default:
+ lm = LM_int;
+ break;
+ }
+
+ switch (c) {
+ unsigned long n, len;
+ long ns;
+ char sign;
+
+ case 'c':
+ output_c(&os, va_arg(argp, int), 1);
+ break;
+
+ case 's':
+ const char *s = va_arg(argp, char*);
+ if (s == NULL) s = "(null)";
+ // TODO can segfault even if precision caps the string
+ len = strlen(s);
+ if (len > m.precision && m.precision != 0)
+ len = m.precision;
+ pad(&os, &m, len);
+ output(&os, s, len);
+ break;
+
+ case 'p':
+ output(&os, "0x", 2);
+ output_uint16(&os, &m, (uintptr_t)va_arg(argp, void*));
+ break;
+
+ case 'x':
+ if (lm == LM_int) n = va_arg(argp, unsigned int);
+ else if (lm == LM_long) n = va_arg(argp, unsigned long);
+ else if (lm == LM_longlong) n = va_arg(argp, unsigned long long);
+ else if (lm == LM_size) n = va_arg(argp, size_t);
+ output_uint16(&os, &m, n);
+ break;
+
+ case 'u':
+ if (lm == LM_int) n = va_arg(argp, unsigned int);
+ else if (lm == LM_long) n = va_arg(argp, unsigned long);
+ else if (lm == LM_longlong) n = va_arg(argp, unsigned long long);
+ else if (lm == LM_size) n = va_arg(argp, size_t);
+ output_uint(&os, &m, n, '\0');
+ break;
+
+ case 'd':
+ case 'i':
+ if (lm == LM_int) ns = va_arg(argp, int);
+ else if (lm == LM_long) ns = va_arg(argp, long);
+ else if (lm == LM_longlong) ns = va_arg(argp, long long);
+ else if (lm == LM_size) ns = va_arg(argp, size_t);
+ sign = '\0';
+ if (ns < 0) {
+ ns = -ns;
+ sign = '-';
+ }
+ output_uint(&os, &m, (long)ns, sign);
+ break;
+
+ case '%':
+ output(&os, "%", 1);
+ break;
+ }
+ seg = fmt;
+ }
+}
+
+
+static void vsnprintf_backend(void *arg, const char *buf, size_t len) {
+ char **ptrs = arg;
+ size_t space = ptrs[1] - ptrs[0];
+ if (!ptrs[0]) return;
+ if (len > space) len = space;
+
+ memcpy(ptrs[0], buf, len);
+ ptrs[0] += len;
+ /* ptrs[1] is the last byte of the buffer, it must be 0.
+ * on overflow:
+ * ptrs[0] + (ptrs[1] - ptrs[0]) = ptrs[1] */
+ *ptrs[0] = '\0';
+}
+
+int vsnprintf(char *restrict str, size_t len, const char *restrict fmt, va_list ap) {
+ char *ptrs[2] = {str, str + len - 1};
+ return __printf_internal(fmt, ap, vsnprintf_backend, &ptrs);
+}
+
+int snprintf(char *restrict str, size_t len, const char *restrict fmt, ...) {
+ int ret;
+ va_list argp;
+ va_start(argp, fmt);
+ ret = vsnprintf(str, len, fmt, argp);
+ va_end(argp);
+ return ret;
+}
diff --git a/src/libk/ring.c b/src/libk/ring.c
new file mode 100644
index 0000000..44c73f6
--- /dev/null
+++ b/src/libk/ring.c
@@ -0,0 +1,60 @@
+#include <shared/ring.h>
+#include <shared/mem.h>
+#include <stdbool.h>
+
+static bool at_end(ring_t *r) {
+ return r->_head + 1 == r->_tail
+ || (r->_head + 1 == r->capacity && r->_tail == 0);
+}
+
+size_t ring_used(ring_t *r) {
+ if (r->_head >= r->_tail)
+ return r->_head - r->_tail;
+ else
+ return r->_head + r->capacity - r->_tail;
+}
+
+size_t ring_avail(ring_t *r) {
+ return r->capacity - ring_used(r);
+}
+
+void ring_put(ring_t *r, const void *buf, size_t len) {
+ // TODO do something similar to ring_get
+ for (size_t i = 0; i < len; i++)
+ ring_put1b(r, ((uint8_t*)buf)[i]);
+}
+
+void ring_put1b(ring_t *r, uint8_t byte) {
+ if (at_end(r)) return;
+ r->buf[r->_head++] = byte;
+ if (r->_head >= r->capacity) r->_head = 0;
+}
+
+size_t ring_get(ring_t *r, void *buf, size_t len) {
+ size_t read = 0;
+ size_t plen;
+ void *pbuf;
+ for (size_t i = 0; i < 2; i++) {
+ plen = len - read;
+ pbuf = ring_contig(r, &plen);
+ if (buf) memcpy(buf + read, pbuf, plen);
+ read += plen;
+ }
+ return read;
+}
+
+void *ring_contig(ring_t *r, size_t *len) {
+ void *ret = &r->buf[r->_tail];
+ size_t avail;
+ if (r->_head >= r->_tail)
+ avail = r->_head - r->_tail;
+ else
+ avail = r->capacity - r->_tail;
+
+ if (*len > avail)
+ *len = avail;
+
+ r->_tail += *len;
+ if (r->_tail >= r->capacity) r->_tail = 0;
+ return ret;
+}