summaryrefslogtreecommitdiffstats
path: root/libc-bottom-half/sources/descriptor_table.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--libc-bottom-half/sources/descriptor_table.c255
1 files changed, 255 insertions, 0 deletions
diff --git a/libc-bottom-half/sources/descriptor_table.c b/libc-bottom-half/sources/descriptor_table.c
new file mode 100644
index 0000000..d45e7ce
--- /dev/null
+++ b/libc-bottom-half/sources/descriptor_table.c
@@ -0,0 +1,255 @@
+/*
+ * This file provides a global hashtable for tracking `wasi-libc`-managed file
+ * descriptors.
+ *
+ * WASI Preview 2 has no notion of file descriptors and instead uses unforgeable
+ * resource handles (which are currently represented as integers at the ABI
+ * level, used as indices into per-component tables managed by the host).
+ * Moreover, there's not necessarily a one-to-one correspondence between POSIX
+ * file descriptors and resource handles (e.g. a TCP connection may require
+ * separate handles for reading, writing, and polling the same connection). We
+ * use this table to map each POSIX descriptor to a set of one or more handles.
+ *
+ * As of this writing, we still rely on the WASI Preview 1 adapter
+ * (https://github.com/bytecodealliance/wasmtime/tree/main/crates/wasi-preview1-component-adapter)
+ * to manage non-socket descriptors, so currently this table only tracks TCP and
+ * UDP sockets. We use the adapter's `adapter_open_badfd` and
+ * `adapter_close_badfd` functions to reserve and later close descriptors to
+ * avoid confusion (e.g. if an application tries to use Preview 1 host functions
+ * directly for socket operations rather than go through `wasi-libc`).
+ * Eventually, we'll switch `wasi-libc` over to Preview 2 entirely, at which
+ * point we'll no longer need the adapter. At that point, all file descriptors
+ * will be managed exclusively in this table.
+ */
+
+#include <wasi/descriptor_table.h>
+
+__attribute__((__import_module__("wasi_snapshot_preview1"),
+ __import_name__("adapter_open_badfd"))) extern int32_t
+ __wasi_preview1_adapter_open_badfd(int32_t);
+
+static bool wasi_preview1_adapter_open_badfd(int *fd)
+{
+ return __wasi_preview1_adapter_open_badfd((int32_t)fd) == 0;
+}
+
+__attribute__((__import_module__("wasi_snapshot_preview1"),
+ __import_name__("adapter_close_badfd"))) extern int32_t
+ __wasi_preview1_adapter_close_badfd(int32_t);
+
+static bool wasi_preview1_adapter_close_badfd(int fd)
+{
+ return __wasi_preview1_adapter_close_badfd(fd) == 0;
+}
+
+/*
+ * This hash table is based on the one in musl/src/search/hsearch.c, but uses
+ * integer keys and supports a `remove` operation. Note that I've switched from
+ * quadratic to linear probing in order to make `remove` simple and efficient,
+ * with the tradeoff that clustering is more likely. See also
+ * https://en.wikipedia.org/wiki/Open_addressing.
+ */
+
+#define MINSIZE 8
+#define MAXSIZE ((size_t)-1 / 2 + 1)
+
+typedef struct {
+ bool occupied;
+ int key;
+ descriptor_table_entry_t entry;
+} descriptor_table_item_t;
+
+typedef struct {
+ descriptor_table_item_t *entries;
+ size_t mask;
+ size_t used;
+} descriptor_table_t;
+
+static descriptor_table_t global_table = { .entries = NULL,
+ .mask = 0,
+ .used = 0 };
+
+static size_t keyhash(int key)
+{
+ // TODO: use a hash function here
+ return key;
+}
+
+static int resize(size_t nel, descriptor_table_t *table)
+{
+ size_t newsize;
+ size_t i;
+ descriptor_table_item_t *e, *newe;
+ descriptor_table_item_t *oldtab = table->entries;
+ descriptor_table_item_t *oldend = table->entries + table->mask + 1;
+
+ if (nel > MAXSIZE)
+ nel = MAXSIZE;
+ for (newsize = MINSIZE; newsize < nel; newsize *= 2)
+ ;
+ table->entries = calloc(newsize, sizeof *table->entries);
+ if (!table->entries) {
+ table->entries = oldtab;
+ return 0;
+ }
+ table->mask = newsize - 1;
+ if (!oldtab)
+ return 1;
+ for (e = oldtab; e < oldend; e++)
+ if (e->occupied) {
+ for (i = keyhash(e->key);; ++i) {
+ newe = table->entries + (i & table->mask);
+ if (!newe->occupied)
+ break;
+ }
+ *newe = *e;
+ }
+ free(oldtab);
+ return 1;
+}
+
+static descriptor_table_item_t *lookup(int key, size_t hash,
+ descriptor_table_t *table)
+{
+ size_t i;
+ descriptor_table_item_t *e;
+
+ for (i = hash;; ++i) {
+ e = table->entries + (i & table->mask);
+ if (!e->occupied || e->key == key)
+ break;
+ }
+ return e;
+}
+
+static bool insert(descriptor_table_entry_t entry, int fd,
+ descriptor_table_t *table)
+{
+ if (!table->entries) {
+ if (!resize(MINSIZE, table)) {
+ return false;
+ }
+ }
+
+ size_t hash = keyhash(fd);
+ descriptor_table_item_t *e = lookup(fd, hash, table);
+
+ e->entry = entry;
+ if (!e->occupied) {
+ e->key = fd;
+ e->occupied = true;
+ if (++table->used > table->mask - table->mask / 4) {
+ if (!resize(2 * table->used, table)) {
+ table->used--;
+ e->occupied = false;
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static bool get(int fd, descriptor_table_entry_t **entry,
+ descriptor_table_t *table)
+{
+ if (!table->entries) {
+ return false;
+ }
+
+ size_t hash = keyhash(fd);
+ descriptor_table_item_t *e = lookup(fd, hash, table);
+ if (e->occupied) {
+ *entry = &e->entry;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static bool remove(int fd, descriptor_table_entry_t *entry,
+ descriptor_table_t *table)
+{
+ if (!table->entries) {
+ return false;
+ }
+
+ size_t hash = keyhash(fd);
+ size_t i;
+ descriptor_table_item_t *e;
+ for (i = hash;; ++i) {
+ e = table->entries + (i & table->mask);
+ if (!e->occupied || e->key == fd)
+ break;
+ }
+
+ if (e->occupied) {
+ *entry = e->entry;
+ e->occupied = false;
+
+ // Search for any occupied entries which would be lost (due to
+ // an interrupted linear probe) if we left this one unoccupied
+ // and move them as necessary.
+ i = i & table->mask;
+ size_t j = i;
+ while (true) {
+ j = (j + 1) & table->mask;
+ e = table->entries + j;
+ if (!e->occupied)
+ break;
+ size_t k = keyhash(e->key) & table->mask;
+ if (i <= j) {
+ if ((i < k) && (k <= j))
+ continue;
+ } else if ((i < k) || (k <= j)) {
+ continue;
+ }
+ table->entries[i] = *e;
+ e->occupied = false;
+ i = j;
+ }
+
+ // If the load factor has dropped below 25%, shrink the table to
+ // reduce memory footprint.
+ if (--table->used < table->mask / 4) {
+ resize(table->mask / 2, table);
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool descriptor_table_insert(descriptor_table_entry_t entry, int *fd)
+{
+ if (wasi_preview1_adapter_open_badfd(fd)) {
+ if (insert(entry, *fd, &global_table)) {
+ return true;
+ } else {
+ if (!wasi_preview1_adapter_close_badfd(*fd)) {
+ abort();
+ }
+ *fd = -1;
+ return false;
+ }
+ } else {
+ return false;
+ }
+}
+
+bool descriptor_table_get_ref(int fd, descriptor_table_entry_t **entry)
+{
+ return get(fd, entry, &global_table);
+}
+
+bool descriptor_table_remove(int fd, descriptor_table_entry_t *entry)
+{
+ if (remove(fd, entry, &global_table)) {
+ if (!wasi_preview1_adapter_close_badfd(fd)) {
+ abort();
+ }
+ return true;
+ } else {
+ return false;
+ }
+}