commit e5087df9d440f55ba3e9e8d0b7165593a6c99041
parent e461b84e8baeee4238c130f0cd23f09cd8b95a81
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 9 Jun 2026 16:07:31 -0700
wasm: WASI fd_seek/tell, fd_filestat_get, path_filestat_get, fd_readdir
Adds the remaining commonly-needed WASI Preview1 calls:
- fd_seek / fd_tell: in-memory position arithmetic on buffered files
- fd_filestat_get: synthesizes wasi_filestat_t for stdio, prestat, and
open fds; uses stat_path callback for accurate size/mtime when available
- path_filestat_get: stat by guest path via dirfd + relative path
- fd_readdir: snapshot-on-open directory listing via KitWasmOpenDirFn;
WASI cookie is a zero-based index into the snapshot
- path_open OFLAGS_DIRECTORY: opens a directory fd for fd_readdir
Public API (include/kit/wasm.h): adds KitWasmDirEntry, WASI filetype
constants, and four new KitWasmHostConfig callbacks (stat_path,
open_dir, read_dir_entry, close_dir).
OS layer (driver/env/posix.c + windows.c): implements driver_path_stat
and DriverDirHandle (snapshot of opendir/FindFirstFile entries) for all
four supported OS targets. driver/lib/wasm_run.c wires the callbacks.
Tests: four new driver-wasm cases covering seek/tell, fd_filestat_get,
path_filestat_get, and path_open(OFLAGS_DIRECTORY)+fd_readdir.
Diffstat:
7 files changed, 906 insertions(+), 28 deletions(-)
diff --git a/driver/env.h b/driver/env.h
@@ -168,6 +168,32 @@ int driver_path_exists(const char* path);
* Returns 0 on success, nonzero on stat failure. */
int driver_path_mtime_ns(const char* path, int64_t* out);
+/* Stat a host path. Fills *out_size, *out_mtime_ns, and *out_filetype using
+ * the KIT_WASM_FILETYPE_* constants (0=unknown 1=block 2=char 3=dir
+ * 4=regular 5=dgram 6=stream 7=symlink). Returns 0 on success, 1 if the
+ * path does not exist, 2 on any other error. Does not require a DriverEnv. */
+int driver_path_stat(const char* path, uint64_t* out_size,
+ uint64_t* out_mtime_ns, uint8_t* out_filetype);
+
+/* Opaque directory-enumeration handle. Holds a snapshot of all entries
+ * (excluding "." and "..") taken at driver_open_dir time. */
+typedef struct DriverDirHandle DriverDirHandle;
+
+/* Open a directory and snapshot its entries. Returns NULL on failure. */
+DriverDirHandle* driver_open_dir(DriverEnv*, const char* path);
+
+/* Read the entry at zero-based index. Sets *out_name to a pointer borrowed
+ * from the handle (valid until driver_close_dir), *out_name_len to its byte
+ * length (not NUL-terminated), and fills *out_ino, *out_size, *out_mtime_ns,
+ * *out_filetype. Returns 0 on success, 1 when index >= entry count. */
+int driver_read_dir_entry(DriverDirHandle*, uint64_t index,
+ const char** out_name, uint32_t* out_name_len,
+ uint64_t* out_ino, uint64_t* out_size,
+ uint64_t* out_mtime_ns, uint8_t* out_filetype);
+
+/* Free a directory handle. Safe to call with NULL. */
+void driver_close_dir(DriverEnv*, DriverDirHandle*);
+
/* Create a directory and any missing parents. Returns 0 on success. */
int driver_mkdir_p(DriverEnv*, const char* path);
diff --git a/driver/env/posix.c b/driver/env/posix.c
@@ -362,6 +362,161 @@ int driver_path_mtime_ns(const char* path, int64_t* out) {
return os_stat_mtime_ns(&sb, out);
}
+static uint8_t posix_mode_to_wasm_filetype(mode_t m) {
+ if (S_ISREG(m)) return 4;
+ if (S_ISDIR(m)) return 3;
+ if (S_ISLNK(m)) return 7;
+ if (S_ISBLK(m)) return 1;
+ if (S_ISCHR(m)) return 2;
+ if (S_ISSOCK(m)) return 6;
+ return 0;
+}
+
+int driver_path_stat(const char* path, uint64_t* out_size,
+ uint64_t* out_mtime_ns, uint8_t* out_filetype) {
+ struct stat sb;
+ int64_t mtime;
+ if (!path || stat(path, &sb) != 0)
+ return (errno == ENOENT || errno == ENOTDIR) ? 1 : 2;
+ *out_size = (uint64_t)sb.st_size;
+ *out_mtime_ns = os_stat_mtime_ns(&sb, &mtime) == 0 ? (uint64_t)mtime : 0u;
+ *out_filetype = posix_mode_to_wasm_filetype(sb.st_mode);
+ return 0;
+}
+
+typedef struct DriverDirEntryRec {
+ char* name;
+ size_t name_alloc;
+ uint32_t name_len;
+ uint64_t ino;
+ uint64_t size;
+ uint64_t mtime_ns;
+ uint8_t filetype;
+} DriverDirEntryRec;
+
+struct DriverDirHandle {
+ DriverEnv* env;
+ DriverDirEntryRec* entries;
+ size_t entries_alloc;
+ uint64_t count;
+};
+
+DriverDirHandle* driver_open_dir(DriverEnv* env, const char* path) {
+ DIR* d;
+ struct dirent* ent;
+ DriverDirHandle* h;
+ uint64_t cap = 0;
+ uint64_t count = 0;
+ size_t plen;
+ int slash;
+
+ if (!env || !path) return NULL;
+ d = opendir(path);
+ if (!d) return NULL;
+ plen = kit_slice_cstr(path).len;
+ slash = plen && path[plen - 1u] != '/';
+
+ h = (DriverDirHandle*)env->heap->alloc(env->heap, sizeof(*h),
+ _Alignof(DriverDirHandle));
+ if (!h) { closedir(d); return NULL; }
+ memset(h, 0, sizeof(*h));
+ h->env = env;
+
+ while ((ent = readdir(d)) != NULL) {
+ const char* name = ent->d_name;
+ size_t name_len;
+ DriverDirEntryRec* e;
+ size_t child_alloc;
+ char* child;
+ struct stat sb;
+ int64_t mtime;
+
+ if (driver_streq(name, ".") || driver_streq(name, "..")) continue;
+ name_len = kit_slice_cstr(name).len;
+
+ /* grow entry array */
+ if (count >= cap) {
+ uint64_t new_cap = cap ? cap * 2u : 8u;
+ size_t new_alloc = (size_t)new_cap * sizeof(DriverDirEntryRec);
+ DriverDirEntryRec* nv = (DriverDirEntryRec*)env->heap->alloc(
+ env->heap, new_alloc, _Alignof(DriverDirEntryRec));
+ if (!nv) goto fail;
+ if (h->entries) {
+ memcpy(nv, h->entries, (size_t)count * sizeof(DriverDirEntryRec));
+ env->heap->free(env->heap, h->entries, h->entries_alloc);
+ }
+ h->entries = nv;
+ h->entries_alloc = new_alloc;
+ cap = new_cap;
+ }
+
+ e = &h->entries[count];
+ memset(e, 0, sizeof(*e));
+ e->name_alloc = name_len + 1u;
+ e->name = (char*)env->heap->alloc(env->heap, e->name_alloc, 1u);
+ if (!e->name) goto fail;
+ memcpy(e->name, name, name_len);
+ e->name[name_len] = '\0';
+ e->name_len = (uint32_t)name_len;
+
+ /* lstat the entry to get metadata */
+ child_alloc = plen + (size_t)slash + name_len + 1u;
+ child = (char*)driver_alloc(env, child_alloc);
+ if (child) {
+ size_t off = 0;
+ memcpy(child, path, plen); off += plen;
+ if (slash) child[off++] = '/';
+ memcpy(child + off, name, name_len);
+ child[off + name_len] = '\0';
+ if (lstat(child, &sb) == 0) {
+ e->ino = (uint64_t)sb.st_ino;
+ e->size = (uint64_t)sb.st_size;
+ if (os_stat_mtime_ns(&sb, &mtime) == 0) e->mtime_ns = (uint64_t)mtime;
+ e->filetype = posix_mode_to_wasm_filetype(sb.st_mode);
+ }
+ driver_free(env, child, child_alloc);
+ }
+ ++count;
+ }
+
+ closedir(d);
+ h->count = count;
+ return h;
+
+fail:
+ closedir(d);
+ driver_close_dir(env, h);
+ return NULL;
+}
+
+int driver_read_dir_entry(DriverDirHandle* h, uint64_t index,
+ const char** out_name, uint32_t* out_name_len,
+ uint64_t* out_ino, uint64_t* out_size,
+ uint64_t* out_mtime_ns, uint8_t* out_filetype) {
+ DriverDirEntryRec* e;
+ if (!h || index >= h->count) return 1;
+ e = &h->entries[index];
+ *out_name = e->name;
+ *out_name_len = e->name_len;
+ *out_ino = e->ino;
+ *out_size = e->size;
+ *out_mtime_ns = e->mtime_ns;
+ *out_filetype = e->filetype;
+ return 0;
+}
+
+void driver_close_dir(DriverEnv* env, DriverDirHandle* h) {
+ uint64_t i;
+ if (!h) return;
+ if (!env) env = h->env;
+ for (i = 0; i < h->count; ++i) {
+ DriverDirEntryRec* e = &h->entries[i];
+ if (e->name) env->heap->free(env->heap, e->name, e->name_alloc);
+ }
+ if (h->entries) env->heap->free(env->heap, h->entries, h->entries_alloc);
+ env->heap->free(env->heap, h, sizeof(*h));
+}
+
int driver_mkdir_p(DriverEnv* env, const char* path) {
size_t len;
char* buf;
diff --git a/driver/env/windows.c b/driver/env/windows.c
@@ -545,6 +545,174 @@ int driver_path_mtime_ns(const char* path, int64_t* out) {
return 0;
}
+int driver_path_stat(const char* path, uint64_t* out_size,
+ uint64_t* out_mtime_ns, uint8_t* out_filetype) {
+ WIN32_FILE_ATTRIBUTE_DATA fad;
+ wchar_t* wpath;
+ BOOL ok;
+ DWORD err;
+ if (!path) return 2;
+ wpath = widen(path);
+ if (!wpath) return 2;
+ ok = GetFileAttributesExW(wpath, GetFileExInfoStandard, &fad);
+ err = ok ? 0 : GetLastError();
+ free(wpath);
+ if (!ok) {
+ return (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) ? 1 : 2;
+ }
+ *out_mtime_ns = (uint64_t)filetime_to_unix_ns(fad.ftLastWriteTime);
+ if (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ *out_size = 0u;
+ *out_filetype = 3; /* DIRECTORY */
+ } else if (fad.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+ *out_size = ((uint64_t)fad.nFileSizeHigh << 32) | fad.nFileSizeLow;
+ *out_filetype = 7; /* SYMBOLIC_LINK */
+ } else {
+ *out_size = ((uint64_t)fad.nFileSizeHigh << 32) | fad.nFileSizeLow;
+ *out_filetype = 4; /* REGULAR_FILE */
+ }
+ return 0;
+}
+
+typedef struct DriverDirEntryRec {
+ char* name;
+ size_t name_alloc;
+ uint32_t name_len;
+ uint64_t ino;
+ uint64_t size;
+ uint64_t mtime_ns;
+ uint8_t filetype;
+} DriverDirEntryRec;
+
+struct DriverDirHandle {
+ DriverEnv* env;
+ DriverDirEntryRec* entries;
+ size_t entries_alloc;
+ uint64_t count;
+};
+
+DriverDirHandle* driver_open_dir(DriverEnv* env, const char* path) {
+ char* pattern;
+ wchar_t* wpattern;
+ WIN32_FIND_DATAW fd;
+ HANDLE h;
+ DWORD last;
+ DriverDirHandle* dh;
+ uint64_t cap = 0;
+ uint64_t count = 0;
+
+ if (!env || !path) return NULL;
+ pattern = driver_join_path(env, path, "*");
+ if (!pattern) return NULL;
+ wpattern = widen(pattern);
+ driver_free(env, pattern, kit_slice_cstr(pattern).len + 1u);
+ if (!wpattern) return NULL;
+
+ h = FindFirstFileW(wpattern, &fd);
+ free(wpattern);
+ if (h == INVALID_HANDLE_VALUE) return NULL;
+
+ dh = (DriverDirHandle*)env->heap->alloc(env->heap, sizeof(*dh),
+ _Alignof(DriverDirHandle));
+ if (!dh) { FindClose(h); return NULL; }
+ memset(dh, 0, sizeof(*dh));
+ dh->env = env;
+
+ for (;;) {
+ char* name;
+ size_t name_len;
+ DriverDirEntryRec* e;
+
+ name = narrow(fd.cFileName);
+ if (!name) goto fail;
+ if (driver_streq(name, ".") || driver_streq(name, "..")) {
+ free(name);
+ goto loop_next;
+ }
+
+ name_len = kit_slice_cstr(name).len;
+
+ /* grow entry array */
+ if (count >= cap) {
+ uint64_t new_cap = cap ? cap * 2u : 8u;
+ size_t new_alloc = (size_t)new_cap * sizeof(DriverDirEntryRec);
+ DriverDirEntryRec* nv = (DriverDirEntryRec*)env->heap->alloc(
+ env->heap, new_alloc, _Alignof(DriverDirEntryRec));
+ if (!nv) { free(name); goto fail; }
+ if (dh->entries) {
+ memcpy(nv, dh->entries, (size_t)count * sizeof(DriverDirEntryRec));
+ env->heap->free(env->heap, dh->entries, dh->entries_alloc);
+ }
+ dh->entries = nv;
+ dh->entries_alloc = new_alloc;
+ cap = new_cap;
+ }
+
+ e = &dh->entries[count];
+ memset(e, 0, sizeof(*e));
+ e->name_alloc = name_len + 1u;
+ e->name = (char*)env->heap->alloc(env->heap, e->name_alloc, 1u);
+ if (!e->name) { free(name); goto fail; }
+ memcpy(e->name, name, name_len + 1u);
+ e->name_len = (uint32_t)name_len;
+ free(name);
+
+ e->size = ((uint64_t)fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
+ e->mtime_ns = (uint64_t)filetime_to_unix_ns(fd.ftLastWriteTime);
+ if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ e->filetype = 3;
+ else if (fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
+ e->filetype = 7;
+ else
+ e->filetype = 4;
+ ++count;
+
+ loop_next:
+ if (!FindNextFileW(h, &fd)) {
+ last = GetLastError();
+ if (last == ERROR_NO_MORE_FILES) break;
+ goto fail;
+ }
+ }
+
+ FindClose(h);
+ dh->count = count;
+ return dh;
+
+fail:
+ FindClose(h);
+ driver_close_dir(env, dh);
+ return NULL;
+}
+
+int driver_read_dir_entry(DriverDirHandle* h, uint64_t index,
+ const char** out_name, uint32_t* out_name_len,
+ uint64_t* out_ino, uint64_t* out_size,
+ uint64_t* out_mtime_ns, uint8_t* out_filetype) {
+ DriverDirEntryRec* e;
+ if (!h || index >= h->count) return 1;
+ e = &h->entries[index];
+ *out_name = e->name;
+ *out_name_len = e->name_len;
+ *out_ino = e->ino;
+ *out_size = e->size;
+ *out_mtime_ns = e->mtime_ns;
+ *out_filetype = e->filetype;
+ return 0;
+}
+
+void driver_close_dir(DriverEnv* env, DriverDirHandle* h) {
+ uint64_t i;
+ if (!h) return;
+ if (!env) env = h->env;
+ for (i = 0; i < h->count; ++i) {
+ DriverDirEntryRec* e = &h->entries[i];
+ if (e->name) env->heap->free(env->heap, e->name, e->name_alloc);
+ }
+ if (h->entries) env->heap->free(env->heap, h->entries, h->entries_alloc);
+ env->heap->free(env->heap, h, sizeof(*h));
+}
+
int driver_mkdir_p(DriverEnv* env, const char* path) {
size_t len;
char* buf;
diff --git a/driver/lib/wasm_run.c b/driver/lib/wasm_run.c
@@ -772,6 +772,55 @@ static KitStatus wasm_driver_random(void* user, uint8_t* dst, size_t n) {
return KIT_OK;
}
+static KitStatus wasm_driver_stat_path(void* user, const char* path,
+ uint64_t* out_size,
+ uint64_t* out_mtime_ns,
+ uint8_t* out_filetype) {
+ int r;
+ (void)user;
+ r = driver_path_stat(path, out_size, out_mtime_ns, out_filetype);
+ if (r == 0) return KIT_OK;
+ if (r == 1) return KIT_NOT_FOUND;
+ return KIT_IO;
+}
+
+static KitStatus wasm_driver_open_dir(void* user, const char* path,
+ void** out_handle) {
+ DriverWasmHostBuild* b = (DriverWasmHostBuild*)user;
+ DriverDirHandle* h = driver_open_dir(b->env, path);
+ if (!h) return KIT_IO;
+ *out_handle = h;
+ return KIT_OK;
+}
+
+static KitStatus wasm_driver_read_dir_entry(void* user, void* handle,
+ uint64_t index,
+ KitWasmDirEntry* out) {
+ const char* name = NULL;
+ uint32_t name_len = 0;
+ uint64_t ino = 0, size = 0, mtime_ns = 0;
+ uint8_t filetype = 0;
+ int r;
+ (void)user;
+ r = driver_read_dir_entry((DriverDirHandle*)handle, index,
+ &name, &name_len, &ino, &size, &mtime_ns,
+ &filetype);
+ if (r == 1) return KIT_NOT_FOUND;
+ if (r != 0) return KIT_IO;
+ out->ino = ino;
+ out->size = size;
+ out->mtime_ns = mtime_ns;
+ out->name = name;
+ out->name_len = name_len;
+ out->filetype = filetype;
+ return KIT_OK;
+}
+
+static void wasm_driver_close_dir(void* user, void* handle) {
+ DriverWasmHostBuild* b = (DriverWasmHostBuild*)user;
+ driver_close_dir(b->env, (DriverDirHandle*)handle);
+}
+
static void wasm_build_release(DriverWasmHostBuild* b) {
uint32_t i;
if (!b) return;
@@ -836,6 +885,10 @@ static int wasm_build_host(const DriverWasmRunOptions* opts, const char* tool,
cfg.random = wasm_driver_random;
b->random_state = wasm_seed_hash(opts->random_seed);
}
+ cfg.stat_path = wasm_driver_stat_path;
+ cfg.open_dir = wasm_driver_open_dir;
+ cfg.read_dir_entry = wasm_driver_read_dir_entry;
+ cfg.close_dir = wasm_driver_close_dir;
if (wasm_build_env(b, &cfg) != 0 || wasm_build_mounts(b, &cfg) != 0)
return 1;
st = kit_wasm_host_new(&cfg, &b->host);
diff --git a/include/kit/wasm.h b/include/kit/wasm.h
@@ -124,6 +124,30 @@ typedef enum KitWasmFsFlags {
KIT_WASM_FS_FILE = 1u << 2,
} KitWasmFsFlags;
+/* WASI filetype constants (wasi_filetype_t). */
+enum {
+ KIT_WASM_FILETYPE_UNKNOWN = 0,
+ KIT_WASM_FILETYPE_BLOCK_DEVICE = 1,
+ KIT_WASM_FILETYPE_CHARACTER_DEVICE = 2,
+ KIT_WASM_FILETYPE_DIRECTORY = 3,
+ KIT_WASM_FILETYPE_REGULAR_FILE = 4,
+ KIT_WASM_FILETYPE_SOCKET_DGRAM = 5,
+ KIT_WASM_FILETYPE_SOCKET_STREAM = 6,
+ KIT_WASM_FILETYPE_SYMBOLIC_LINK = 7,
+};
+
+/* Host-side directory entry returned by KitWasmReadDirEntryFn.
+ * name is borrowed from callback-owned storage; valid only until the next
+ * read_dir_entry call or close_dir on the same handle. */
+typedef struct KitWasmDirEntry {
+ uint64_t ino; /* inode or synthetic id; 0 if unavailable */
+ uint64_t size; /* file size in bytes; 0 for directories */
+ uint64_t mtime_ns; /* modification time ns since Unix epoch; 0 if unknown */
+ const char* name; /* borrowed; not NUL-terminated — use name_len */
+ uint32_t name_len;
+ uint8_t filetype; /* KIT_WASM_FILETYPE_* */
+} KitWasmDirEntry;
+
typedef struct KitWasmFsMount {
const char* host_path; /* host path exposed to the guest */
const char* guest_path; /* guest absolute path, e.g. "/work" */
@@ -137,6 +161,29 @@ typedef KitStatus (*KitWasmClockFn)(void* user, uint32_t clock_id,
uint64_t* ns_out);
typedef KitStatus (*KitWasmRandomFn)(void* user, uint8_t* dst, size_t n);
+/* Stat a host path. Fills *out_size, *out_mtime_ns, and *out_filetype
+ * (KIT_WASM_FILETYPE_*). Returns KIT_OK, KIT_NOT_FOUND, or KIT_IO. */
+typedef KitStatus (*KitWasmStatPathFn)(void* user, const char* path,
+ uint64_t* out_size,
+ uint64_t* out_mtime_ns,
+ uint8_t* out_filetype);
+
+/* Open a directory and snapshot its entries (excluding "." and "..").
+ * Returns KIT_OK with a non-NULL *out_handle on success. */
+typedef KitStatus (*KitWasmOpenDirFn)(void* user, const char* path,
+ void** out_handle);
+
+/* Return the directory entry at zero-based index from an open handle.
+ * Returns KIT_OK while entries remain, KIT_NOT_FOUND past the end.
+ * out->name is borrowed from the handle; valid until the next call or
+ * close_dir. */
+typedef KitStatus (*KitWasmReadDirEntryFn)(void* user, void* handle,
+ uint64_t index,
+ KitWasmDirEntry* out);
+
+/* Close and free a directory handle opened by KitWasmOpenDirFn. */
+typedef void (*KitWasmCloseDirFn)(void* user, void* handle);
+
typedef struct KitWasmHostConfig {
KitHeap* heap;
uint32_t flags; /* KIT_WASM_HOST_* */
@@ -158,6 +205,10 @@ typedef struct KitWasmHostConfig {
KitWasmWriteFn write;
KitWasmClockFn clock;
KitWasmRandomFn random;
+ KitWasmStatPathFn stat_path; /* NULL: fd_filestat_get synthesizes from buffered size */
+ KitWasmOpenDirFn open_dir; /* NULL: directory path_open returns ENOSYS */
+ KitWasmReadDirEntryFn read_dir_entry;
+ KitWasmCloseDirFn close_dir;
void* user;
} KitWasmHostConfig;
diff --git a/src/api/wasm_host.c b/src/api/wasm_host.c
@@ -14,9 +14,13 @@ typedef struct KitWasmMemoryRecord {
typedef struct KitWasmOpenFd {
uint32_t used;
+ uint32_t is_dir;
KitFileData data;
const KitFileIO* io;
uint64_t pos;
+ void* dir_handle;
+ char* host_path;
+ size_t host_path_size; /* 0=borrowed (KIT_WASM_FS_FILE), >0=heap-owned */
} KitWasmOpenFd;
struct KitWasmHost {
@@ -42,11 +46,13 @@ enum {
WASI_ESUCCESS = 0,
WASI_EACCES = 2,
WASI_EBADF = 8,
+ WASI_EISDIR = 31,
WASI_EINVAL = 28,
WASI_EIO = 29,
WASI_ENOMEM = 34,
WASI_ENOENT = 44,
WASI_ENOSYS = 52,
+ WASI_ENOTDIR = 54,
WASI_ENOTCAPABLE = 76,
};
@@ -187,6 +193,34 @@ static int wasm_write_u64(KitWasmInstance* inst, uint32_t addr, uint64_t v) {
return 1;
}
+static void wasm_put_u64_le(uint8_t* buf, uint32_t off, uint64_t v) {
+ buf[off+0] = (uint8_t)(v); buf[off+1] = (uint8_t)(v >> 8);
+ buf[off+2] = (uint8_t)(v >> 16); buf[off+3] = (uint8_t)(v >> 24);
+ buf[off+4] = (uint8_t)(v >> 32); buf[off+5] = (uint8_t)(v >> 40);
+ buf[off+6] = (uint8_t)(v >> 48); buf[off+7] = (uint8_t)(v >> 56);
+}
+
+static void wasm_put_u32_le(uint8_t* buf, uint32_t off, uint32_t v) {
+ buf[off+0] = (uint8_t)(v); buf[off+1] = (uint8_t)(v >> 8);
+ buf[off+2] = (uint8_t)(v >> 16); buf[off+3] = (uint8_t)(v >> 24);
+}
+
+/* wasi_filestat_t: dev(u64@0) ino(u64@8) filetype(u8@16)+7pad nlink(u64@24)
+ * size(u64@32) atim(u64@40) mtim(u64@48) ctim(u64@56) — 64 bytes total */
+static int wasm_write_filestat(KitWasmInstance* inst, uint32_t ptr,
+ uint8_t filetype, uint64_t size,
+ uint64_t mtime_ns) {
+ uint8_t* p;
+ if (!wasm_mem_ptr(inst, ptr, 64u, &p)) return 0;
+ memset(p, 0, 64u);
+ p[16] = filetype;
+ wasm_put_u64_le(p, 32u, size);
+ wasm_put_u64_le(p, 40u, mtime_ns);
+ wasm_put_u64_le(p, 48u, mtime_ns);
+ wasm_put_u64_le(p, 56u, mtime_ns);
+ return 1;
+}
+
static int wasm_status_errno(KitStatus st) {
switch (st) {
case KIT_OK:
@@ -466,6 +500,21 @@ static int wasm_find_free_fd(KitWasmRuntimeInstance* w, uint32_t* out) {
return 0;
}
+static void wasm_fd_close_slot(KitWasmRuntimeInstance* w, uint32_t fd) {
+ KitHeap* heap = w->host->config.heap;
+ if (!w->fds[fd].used) return;
+ if (w->fds[fd].is_dir) {
+ if (w->fds[fd].dir_handle && w->host->config.close_dir)
+ w->host->config.close_dir(w->host->config.user, w->fds[fd].dir_handle);
+ } else {
+ if (w->fds[fd].io && w->fds[fd].io->release)
+ w->fds[fd].io->release(w->fds[fd].io->user, &w->fds[fd].data);
+ }
+ if (w->fds[fd].host_path_size > 0)
+ heap->free(heap, w->fds[fd].host_path, w->fds[fd].host_path_size);
+ memset(&w->fds[fd], 0, sizeof w->fds[fd]);
+}
+
static int32_t wasi_path_open(KitWasmInstance* inst, int32_t dirfd_i,
int32_t dirflags_i, int32_t path_i,
int32_t path_len_i, int32_t oflags_i,
@@ -477,17 +526,20 @@ static int32_t wasi_path_open(KitWasmInstance* inst, int32_t dirfd_i,
uint8_t* guest_path;
char* host_path = NULL;
size_t host_path_size = 0;
- KitFileData data;
uint32_t fd;
KitStatus st;
+ int open_as_dir;
(void)dirflags_i;
(void)rights_base_i;
(void)rights_inheriting_i;
(void)fdflags_i;
if (!m) return WASI_EBADF;
if (!(m->flags & KIT_WASM_FS_READ)) return WASI_EACCES;
- if (oflags_i != 0) return WASI_ENOSYS;
- if (!w->host->config.file_io || !w->host->config.file_io->read_all)
+ /* OFLAGS_DIRECTORY=4; CREAT/EXCL/TRUNC not supported */
+ if (oflags_i & ~4) return WASI_ENOSYS;
+ open_as_dir = (oflags_i & 4) != 0;
+ if (!open_as_dir &&
+ (!w->host->config.file_io || !w->host->config.file_io->read_all))
return WASI_ENOSYS;
if (!wasm_mem_ptr(inst, (uint32_t)path_i, (uint32_t)path_len_i,
&guest_path))
@@ -499,30 +551,71 @@ static int32_t wasi_path_open(KitWasmInstance* inst, int32_t dirfd_i,
if (!base || !*base ||
!wasm_bytes_eq_cstr(guest_path, (uint32_t)path_len_i, base))
return WASI_ENOENT;
- host_path = (char*)m->host_path;
+ host_path = (char*)m->host_path; /* borrowed */
} else {
st = wasm_join_path(w->host->config.heap, m->host_path, guest_path,
(uint32_t)path_len_i, &host_path, &host_path_size);
if (st != KIT_OK) return wasm_status_errno(st);
}
- memset(&data, 0, sizeof data);
- st = w->host->config.file_io->read_all(w->host->config.file_io->user,
- host_path, &data);
- if (!(m->flags & KIT_WASM_FS_FILE))
- w->host->config.heap->free(w->host->config.heap, host_path,
- host_path_size);
- if (st != KIT_OK) return wasm_status_errno(st);
- if (!wasm_find_free_fd(w, &fd)) {
- if (w->host->config.file_io->release)
- w->host->config.file_io->release(w->host->config.file_io->user, &data);
- return WASI_ENOMEM;
+ if (open_as_dir) {
+ void* dir_handle = NULL;
+ if (!w->host->config.open_dir) {
+ if (host_path_size > 0)
+ w->host->config.heap->free(w->host->config.heap, host_path,
+ host_path_size);
+ return WASI_ENOSYS;
+ }
+ st = w->host->config.open_dir(w->host->config.user, host_path,
+ &dir_handle);
+ if (st != KIT_OK) {
+ if (host_path_size > 0)
+ w->host->config.heap->free(w->host->config.heap, host_path,
+ host_path_size);
+ return wasm_status_errno(st);
+ }
+ if (!wasm_find_free_fd(w, &fd)) {
+ if (w->host->config.close_dir)
+ w->host->config.close_dir(w->host->config.user, dir_handle);
+ if (host_path_size > 0)
+ w->host->config.heap->free(w->host->config.heap, host_path,
+ host_path_size);
+ return WASI_ENOMEM;
+ }
+ w->fds[fd].used = 1;
+ w->fds[fd].is_dir = 1;
+ w->fds[fd].dir_handle = dir_handle;
+ w->fds[fd].host_path = host_path;
+ w->fds[fd].host_path_size = host_path_size;
+ if (!wasm_write_u32(inst, (uint32_t)opened_fd_i, fd)) return WASI_EINVAL;
+ return WASI_ESUCCESS;
+ } else {
+ KitFileData data;
+ memset(&data, 0, sizeof data);
+ st = w->host->config.file_io->read_all(w->host->config.file_io->user,
+ host_path, &data);
+ if (st != KIT_OK) {
+ if (host_path_size > 0)
+ w->host->config.heap->free(w->host->config.heap, host_path,
+ host_path_size);
+ return wasm_status_errno(st);
+ }
+ if (!wasm_find_free_fd(w, &fd)) {
+ if (w->host->config.file_io->release)
+ w->host->config.file_io->release(w->host->config.file_io->user, &data);
+ if (host_path_size > 0)
+ w->host->config.heap->free(w->host->config.heap, host_path,
+ host_path_size);
+ return WASI_ENOMEM;
+ }
+ w->fds[fd].used = 1;
+ w->fds[fd].data = data;
+ w->fds[fd].io = w->host->config.file_io;
+ w->fds[fd].pos = 0;
+ w->fds[fd].host_path = host_path;
+ w->fds[fd].host_path_size = host_path_size;
+ if (!wasm_write_u32(inst, (uint32_t)opened_fd_i, fd)) return WASI_EINVAL;
+ return WASI_ESUCCESS;
}
- w->fds[fd].used = 1;
- w->fds[fd].data = data;
- w->fds[fd].io = w->host->config.file_io;
- w->fds[fd].pos = 0;
- if (!wasm_write_u32(inst, (uint32_t)opened_fd_i, fd)) return WASI_EINVAL;
- return WASI_ESUCCESS;
}
static int32_t wasi_fd_read(KitWasmInstance* inst, int32_t fd_i,
@@ -535,6 +628,7 @@ static int32_t wasi_fd_read(KitWasmInstance* inst, int32_t fd_i,
uint32_t total = 0;
uint32_t i;
if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF;
+ if (w->fds[fd].is_dir) return WASI_EISDIR;
for (i = 0; i < iovs_len; ++i) {
uint32_t ptr;
uint32_t len;
@@ -570,12 +664,165 @@ static int32_t wasi_fd_close(KitWasmInstance* inst, int32_t fd_i) {
KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
uint32_t fd = (uint32_t)fd_i;
if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF;
- if (w->fds[fd].io && w->fds[fd].io->release)
- w->fds[fd].io->release(w->fds[fd].io->user, &w->fds[fd].data);
- memset(&w->fds[fd], 0, sizeof w->fds[fd]);
+ wasm_fd_close_slot(w, fd);
return WASI_ESUCCESS;
}
+static int32_t wasi_fd_seek(KitWasmInstance* inst, int32_t fd_i,
+ int64_t offset, int32_t whence_i,
+ int32_t newoffset_i) {
+ KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
+ uint32_t fd = (uint32_t)fd_i;
+ int64_t base;
+ int64_t newpos;
+ if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF;
+ if (w->fds[fd].is_dir) return WASI_EISDIR;
+ switch (whence_i) {
+ case 0: base = 0; break;
+ case 1: base = (int64_t)w->fds[fd].pos; break;
+ case 2: base = (int64_t)(uint64_t)w->fds[fd].data.size; break;
+ default: return WASI_EINVAL;
+ }
+ if (offset < 0 && (uint64_t)(-offset) > (uint64_t)base) return WASI_EINVAL;
+ newpos = base + offset;
+ if (newpos < 0) return WASI_EINVAL;
+ if ((uint64_t)newpos > (uint64_t)w->fds[fd].data.size)
+ newpos = (int64_t)(uint64_t)w->fds[fd].data.size;
+ w->fds[fd].pos = (uint64_t)newpos;
+ return wasm_write_u64(inst, (uint32_t)newoffset_i, (uint64_t)newpos)
+ ? WASI_ESUCCESS
+ : WASI_EINVAL;
+}
+
+static int32_t wasi_fd_tell(KitWasmInstance* inst, int32_t fd_i,
+ int32_t offset_i) {
+ KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
+ uint32_t fd = (uint32_t)fd_i;
+ if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF;
+ if (w->fds[fd].is_dir) return WASI_EISDIR;
+ return wasm_write_u64(inst, (uint32_t)offset_i, w->fds[fd].pos)
+ ? WASI_ESUCCESS
+ : WASI_EINVAL;
+}
+
+static int32_t wasi_fd_filestat_get(KitWasmInstance* inst, int32_t fd_i,
+ int32_t filestat_i) {
+ KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
+ uint32_t fd = (uint32_t)fd_i;
+ uint32_t npreopen = wasm_preopen_count(w->host);
+ uint64_t size = 0, mtime_ns = 0;
+ uint8_t ft;
+ if (fd <= 2u)
+ return wasm_write_filestat(inst, (uint32_t)filestat_i,
+ KIT_WASM_FILETYPE_CHARACTER_DEVICE, 0u, 0u)
+ ? WASI_ESUCCESS
+ : WASI_EINVAL;
+ if (fd >= 3u && fd < 3u + npreopen) {
+ const KitWasmFsMount* m = wasm_preopen_for_fd(w, fd);
+ ft = (m && (m->flags & KIT_WASM_FS_FILE)) ? KIT_WASM_FILETYPE_REGULAR_FILE
+ : KIT_WASM_FILETYPE_DIRECTORY;
+ return wasm_write_filestat(inst, (uint32_t)filestat_i, ft, 0u, 0u)
+ ? WASI_ESUCCESS
+ : WASI_EINVAL;
+ }
+ if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF;
+ ft = w->fds[fd].is_dir ? KIT_WASM_FILETYPE_DIRECTORY
+ : KIT_WASM_FILETYPE_REGULAR_FILE;
+ if (w->host->config.stat_path && w->fds[fd].host_path) {
+ w->host->config.stat_path(w->host->config.user, w->fds[fd].host_path,
+ &size, &mtime_ns, &ft);
+ } else if (!w->fds[fd].is_dir) {
+ size = (uint64_t)w->fds[fd].data.size;
+ }
+ return wasm_write_filestat(inst, (uint32_t)filestat_i, ft, size, mtime_ns)
+ ? WASI_ESUCCESS
+ : WASI_EINVAL;
+}
+
+static int32_t wasi_path_filestat_get(KitWasmInstance* inst, int32_t dirfd_i,
+ int32_t flags_i, int32_t path_i,
+ int32_t path_len_i,
+ int32_t filestat_i) {
+ KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
+ const KitWasmFsMount* m = wasm_preopen_for_fd(w, (uint32_t)dirfd_i);
+ uint8_t* guest_path;
+ char* host_path = NULL;
+ size_t host_path_size = 0;
+ uint64_t size = 0, mtime_ns = 0;
+ uint8_t ft = KIT_WASM_FILETYPE_UNKNOWN;
+ KitStatus st;
+ (void)flags_i;
+ if (!m) return WASI_EBADF;
+ if (!w->host->config.stat_path) return WASI_ENOSYS;
+ if (!wasm_mem_ptr(inst, (uint32_t)path_i, (uint32_t)path_len_i, &guest_path))
+ return WASI_EINVAL;
+ if (!wasm_guest_path_safe(guest_path, (uint32_t)path_len_i))
+ return WASI_ENOTCAPABLE;
+ if (m->flags & KIT_WASM_FS_FILE) {
+ const char* base = wasm_guest_basename(m->guest_path);
+ if (!base || !*base ||
+ !wasm_bytes_eq_cstr(guest_path, (uint32_t)path_len_i, base))
+ return WASI_ENOENT;
+ host_path = (char*)m->host_path;
+ } else {
+ st = wasm_join_path(w->host->config.heap, m->host_path, guest_path,
+ (uint32_t)path_len_i, &host_path, &host_path_size);
+ if (st != KIT_OK) return wasm_status_errno(st);
+ }
+ st = w->host->config.stat_path(w->host->config.user, host_path,
+ &size, &mtime_ns, &ft);
+ if (host_path_size > 0)
+ w->host->config.heap->free(w->host->config.heap, host_path, host_path_size);
+ if (st != KIT_OK) return wasm_status_errno(st);
+ return wasm_write_filestat(inst, (uint32_t)filestat_i, ft, size, mtime_ns)
+ ? WASI_ESUCCESS
+ : WASI_EINVAL;
+}
+
+/* wasi_dirent_t: d_next(u64@0) ino(u64@8) namlen(u32@16) type(u8@20) 3pad
+ * then namlen bytes of name (no NUL). Cookie = zero-based entry index. */
+static int32_t wasi_fd_readdir(KitWasmInstance* inst, int32_t fd_i,
+ int32_t buf_i, int32_t buf_len_i,
+ int64_t cookie_i, int32_t bufused_i) {
+ KitWasmRuntimeInstance* w = wasm_wrap_from_instance(inst);
+ uint32_t fd = (uint32_t)fd_i;
+ uint32_t buf_len = (uint32_t)buf_len_i;
+ uint64_t cookie = (uint64_t)cookie_i;
+ uint8_t* buf;
+ uint32_t written = 0;
+ if (fd >= KIT_WASM_HOST_MAX_FDS || !w->fds[fd].used) return WASI_EBADF;
+ if (!w->fds[fd].is_dir) return WASI_ENOTDIR;
+ if (!w->host->config.read_dir_entry) return WASI_ENOSYS;
+ if (!wasm_mem_ptr(inst, (uint32_t)buf_i, buf_len, &buf)) return WASI_EINVAL;
+ while (written < buf_len) {
+ KitWasmDirEntry entry;
+ uint8_t hdr[24];
+ uint32_t remain = buf_len - written;
+ uint32_t to_write;
+ KitStatus st = w->host->config.read_dir_entry(
+ w->host->config.user, w->fds[fd].dir_handle, cookie, &entry);
+ if (st == KIT_NOT_FOUND) break;
+ if (st != KIT_OK) return wasm_status_errno(st);
+ memset(hdr, 0, 24u);
+ wasm_put_u64_le(hdr, 0u, cookie + 1u);
+ wasm_put_u64_le(hdr, 8u, entry.ino);
+ wasm_put_u32_le(hdr, 16u, entry.name_len);
+ hdr[20] = entry.filetype;
+ to_write = (remain < 24u) ? remain : 24u;
+ memcpy(buf + written, hdr, to_write);
+ written += to_write;
+ remain -= to_write;
+ if (remain > 0u && entry.name_len > 0u && entry.name) {
+ to_write = (remain < entry.name_len) ? remain : entry.name_len;
+ memcpy(buf + written, entry.name, to_write);
+ written += to_write;
+ }
+ ++cookie;
+ }
+ return wasm_write_u32(inst, (uint32_t)bufused_i, written) ? WASI_ESUCCESS
+ : WASI_EINVAL;
+}
+
static void* wasm_wasi_resolve(void* user, const char* module,
const char* field,
const KitWasmImportType* type) {
@@ -585,12 +832,20 @@ static void* wasm_wasi_resolve(void* user, const char* module,
KitWasmValType p2_i32[2] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I32};
KitWasmValType p4_i32[4] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I32,
KIT_WASM_VAL_I32, KIT_WASM_VAL_I32};
+ KitWasmValType p5_i32[5] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I32,
+ KIT_WASM_VAL_I32, KIT_WASM_VAL_I32,
+ KIT_WASM_VAL_I32};
KitWasmValType p_clock[3] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I64,
KIT_WASM_VAL_I32};
KitWasmValType p_path_open[9] = {
KIT_WASM_VAL_I32, KIT_WASM_VAL_I32, KIT_WASM_VAL_I32,
KIT_WASM_VAL_I32, KIT_WASM_VAL_I32, KIT_WASM_VAL_I64,
KIT_WASM_VAL_I64, KIT_WASM_VAL_I32, KIT_WASM_VAL_I32};
+ KitWasmValType p_fd_seek[4] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I64,
+ KIT_WASM_VAL_I32, KIT_WASM_VAL_I32};
+ KitWasmValType p_fd_readdir[5] = {KIT_WASM_VAL_I32, KIT_WASM_VAL_I32,
+ KIT_WASM_VAL_I32, KIT_WASM_VAL_I64,
+ KIT_WASM_VAL_I32};
if (!host || !(host->config.flags & KIT_WASM_HOST_WASI_PREVIEW1))
return NULL;
if (!wasm_streq(module, "wasi_snapshot_preview1")) return NULL;
@@ -633,6 +888,21 @@ static void* wasm_wasi_resolve(void* user, const char* module,
if (wasm_streq(field, "fd_close") &&
wasm_sig(type, p_proc_exit, 1u, r_i32, 1u))
return (void*)(uintptr_t)wasi_fd_close;
+ if (wasm_streq(field, "fd_seek") &&
+ wasm_sig(type, p_fd_seek, 4u, r_i32, 1u))
+ return (void*)(uintptr_t)wasi_fd_seek;
+ if (wasm_streq(field, "fd_tell") &&
+ wasm_sig(type, p2_i32, 2u, r_i32, 1u))
+ return (void*)(uintptr_t)wasi_fd_tell;
+ if (wasm_streq(field, "fd_filestat_get") &&
+ wasm_sig(type, p2_i32, 2u, r_i32, 1u))
+ return (void*)(uintptr_t)wasi_fd_filestat_get;
+ if (wasm_streq(field, "path_filestat_get") &&
+ wasm_sig(type, p5_i32, 5u, r_i32, 1u))
+ return (void*)(uintptr_t)wasi_path_filestat_get;
+ if (wasm_streq(field, "fd_readdir") &&
+ wasm_sig(type, p_fd_readdir, 5u, r_i32, 1u))
+ return (void*)(uintptr_t)wasi_fd_readdir;
return NULL;
}
@@ -778,10 +1048,8 @@ KIT_API void kit_wasm_instance_free(KitWasmInstance* inst) {
if (!inst) return;
w = wasm_wrap_from_instance(inst);
heap = w->host->config.heap;
- for (i = 0; i < KIT_WASM_HOST_MAX_FDS; ++i) {
- if (w->fds[i].used && w->fds[i].io && w->fds[i].io->release)
- w->fds[i].io->release(w->fds[i].io->user, &w->fds[i].data);
- }
+ for (i = 0; i < KIT_WASM_HOST_MAX_FDS; ++i)
+ wasm_fd_close_slot(w, i);
for (i = 0; i < w->nmemories; ++i) {
size_t mem_size;
if (w->memories && w->memories[i] &&
diff --git a/test/driver-wasm/run.sh b/test/driver-wasm/run.sh
@@ -198,6 +198,145 @@ cat > "$work/wasi_fs_read.wat" <<'WAT'
i32.const 0))
WAT
+cat > "$work/wasi_seek.wat" <<'WAT'
+(module
+ (import "wasi_snapshot_preview1" "path_open"
+ (func $path_open (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
+ (import "wasi_snapshot_preview1" "fd_read"
+ (func $fd_read (param i32 i32 i32 i32) (result i32)))
+ (import "wasi_snapshot_preview1" "fd_seek"
+ (func $fd_seek (param i32 i64 i32 i32) (result i32)))
+ (import "wasi_snapshot_preview1" "fd_tell"
+ (func $fd_tell (param i32 i32) (result i32)))
+ (import "wasi_snapshot_preview1" "fd_close"
+ (func $fd_close (param i32) (result i32)))
+ (memory 1)
+ (data (i32.const 64) "hello.txt")
+ (func (export "test_main") (result i32)
+ (local $fd i32) (local $err i32)
+ ;; path_open(dirfd=3, ..., "hello.txt", oflags=0, ..., &fd=16)
+ i32.const 3 i32.const 0 i32.const 64 i32.const 9
+ i32.const 0 i64.const 0 i64.const 0 i32.const 0 i32.const 16
+ call $path_open local.set $err
+ (if (i32.ne (local.get $err) (i32.const 0)) (then (return (i32.const 1))))
+ (i32.load (i32.const 16)) local.set $fd
+ ;; read 2 bytes into buf=128; iov at [0]=128 [4]=2; nread at 8
+ (i32.store (i32.const 0) (i32.const 128))
+ (i32.store (i32.const 4) (i32.const 2))
+ local.get $fd i32.const 0 i32.const 1 i32.const 8
+ call $fd_read local.set $err
+ (if (i32.ne (local.get $err) (i32.const 0)) (then (return (i32.const 2))))
+ (if (i32.ne (i32.load (i32.const 8)) (i32.const 2)) (then (return (i32.const 3))))
+ (if (i32.ne (i32.load8_u (i32.const 128)) (i32.const 104)) ;; 'h'
+ (then (return (i32.const 4))))
+ ;; fd_seek(fd, offset=3, whence=0/SET, &newpos=24)
+ local.get $fd i64.const 3 i32.const 0 i32.const 24
+ call $fd_seek local.set $err
+ (if (i32.ne (local.get $err) (i32.const 0)) (then (return (i32.const 5))))
+ (if (i64.ne (i64.load (i32.const 24)) (i64.const 3)) (then (return (i32.const 6))))
+ ;; fd_tell(fd, &pos=32)
+ local.get $fd i32.const 32
+ call $fd_tell local.set $err
+ (if (i32.ne (local.get $err) (i32.const 0)) (then (return (i32.const 7))))
+ (if (i64.ne (i64.load (i32.const 32)) (i64.const 3)) (then (return (i32.const 8))))
+ ;; read 1 byte from pos 3: expect 'l'=108
+ (i32.store (i32.const 4) (i32.const 1))
+ local.get $fd i32.const 0 i32.const 1 i32.const 8
+ call $fd_read local.set $err
+ (if (i32.ne (local.get $err) (i32.const 0)) (then (return (i32.const 9))))
+ (if (i32.ne (i32.load8_u (i32.const 128)) (i32.const 108)) ;; 'l'
+ (then (return (i32.const 10))))
+ local.get $fd call $fd_close drop
+ i32.const 0))
+WAT
+
+cat > "$work/wasi_filestat.wat" <<'WAT'
+(module
+ (import "wasi_snapshot_preview1" "path_open"
+ (func $path_open (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
+ (import "wasi_snapshot_preview1" "fd_filestat_get"
+ (func $fd_filestat_get (param i32 i32) (result i32)))
+ (import "wasi_snapshot_preview1" "fd_close"
+ (func $fd_close (param i32) (result i32)))
+ (memory 1)
+ (data (i32.const 256) "hello.txt")
+ (func (export "test_main") (result i32)
+ (local $fd i32) (local $err i32)
+ i32.const 3 i32.const 0 i32.const 256 i32.const 9
+ i32.const 0 i64.const 0 i64.const 0 i32.const 0 i32.const 16
+ call $path_open local.set $err
+ (if (i32.ne (local.get $err) (i32.const 0)) (then (return (i32.const 1))))
+ (i32.load (i32.const 16)) local.set $fd
+ ;; fd_filestat_get(fd, filestat=64): 64-byte struct
+ local.get $fd i32.const 64
+ call $fd_filestat_get local.set $err
+ (if (i32.ne (local.get $err) (i32.const 0)) (then (return (i32.const 2))))
+ ;; filetype @ 64+16=80: expect 4 (regular file)
+ (if (i32.ne (i32.load8_u (i32.const 80)) (i32.const 4))
+ (then (return (i32.const 3))))
+ ;; size @ 64+32=96 (u64 LE): expect 5
+ (if (i64.ne (i64.load (i32.const 96)) (i64.const 5))
+ (then (return (i32.const 4))))
+ local.get $fd call $fd_close drop
+ i32.const 0))
+WAT
+
+cat > "$work/wasi_path_filestat.wat" <<'WAT'
+(module
+ (import "wasi_snapshot_preview1" "path_filestat_get"
+ (func $path_filestat_get (param i32 i32 i32 i32 i32) (result i32)))
+ (memory 1)
+ (data (i32.const 256) "hello.txt")
+ (func (export "test_main") (result i32)
+ (local $err i32)
+ ;; path_filestat_get(dirfd=3, flags=0, path=256, pathlen=9, filestat=64)
+ i32.const 3 i32.const 0 i32.const 256 i32.const 9 i32.const 64
+ call $path_filestat_get local.set $err
+ (if (i32.ne (local.get $err) (i32.const 0)) (then (return (local.get $err))))
+ ;; filetype @ 64+16=80: expect 4 (regular file)
+ (if (i32.ne (i32.load8_u (i32.const 80)) (i32.const 4))
+ (then (return (i32.const 10))))
+ ;; size @ 64+32=96 (u64 LE): expect 5
+ (if (i64.ne (i64.load (i32.const 96)) (i64.const 5))
+ (then (return (i32.const 11))))
+ i32.const 0))
+WAT
+
+cat > "$work/wasi_opendir_readdir.wat" <<'WAT'
+(module
+ (import "wasi_snapshot_preview1" "path_open"
+ (func $path_open (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
+ (import "wasi_snapshot_preview1" "fd_readdir"
+ (func $fd_readdir (param i32 i32 i32 i64 i32) (result i32)))
+ (import "wasi_snapshot_preview1" "fd_close"
+ (func $fd_close (param i32) (result i32)))
+ (memory 1)
+ (data (i32.const 64) "subdir")
+ (func (export "test_main") (result i32)
+ (local $dir_fd i32) (local $err i32)
+ ;; path_open(3, 0, "subdir", oflags=4/OFLAGS_DIRECTORY, ..., &fd=16)
+ i32.const 3 i32.const 0 i32.const 64 i32.const 6
+ i32.const 4 i64.const 0 i64.const 0 i32.const 0 i32.const 16
+ call $path_open local.set $err
+ (if (i32.ne (local.get $err) (i32.const 0)) (then (return (i32.const 1))))
+ (i32.load (i32.const 16)) local.set $dir_fd
+ ;; fd_readdir(dir_fd, buf=128, buflen=256, cookie=0, &bufused=32)
+ local.get $dir_fd i32.const 128 i32.const 256 i64.const 0 i32.const 32
+ call $fd_readdir local.set $err
+ (if (i32.ne (local.get $err) (i32.const 0)) (then (return (i32.const 2))))
+ ;; expect exactly one entry: "world.txt" (9 bytes) → bufused=24+9=33
+ (if (i32.ne (i32.load (i32.const 32)) (i32.const 33))
+ (then (return (i32.const 3))))
+ ;; dirent namlen @ buf+16=144: expect 9
+ (if (i32.ne (i32.load (i32.const 144)) (i32.const 9))
+ (then (return (i32.const 4))))
+ ;; dirent filetype @ buf+20=148: expect 4 (regular)
+ (if (i32.ne (i32.load8_u (i32.const 148)) (i32.const 4))
+ (then (return (i32.const 5))))
+ local.get $dir_fd call $fd_close drop
+ i32.const 0))
+WAT
+
cat > "$work/wasi_unknown.wat" <<'WAT'
(module
(import "wasi_snapshot_preview1" "path_unlink_file"
@@ -215,6 +354,8 @@ int main(void) { return 0; }
SRC
printf 'hello' > "$work/hello.txt"
+mkdir -p "$work/subdir"
+printf 'world' > "$work/subdir/world.txt"
run_ok "wasm-default-runs-no-imports" \
"$KIT" run -e test_main "$work/return0.wat"
@@ -293,6 +434,22 @@ run_ok "wasm-wasi-fs-map-file-read" \
--wasm-map-file="$work/hello.txt=/sandbox/hello.txt:ro" \
-e test_main "$work/wasi_fs_read.wat"
+run_ok "wasm-wasi-seek-tell" \
+ "$KIT" run --wasm-wasi --wasm-map-dir="$work=/sandbox:ro" \
+ -e test_main "$work/wasi_seek.wat"
+
+run_ok "wasm-wasi-fd-filestat-get" \
+ "$KIT" run --wasm-wasi --wasm-map-dir="$work=/sandbox:ro" \
+ -e test_main "$work/wasi_filestat.wat"
+
+run_ok "wasm-wasi-path-filestat-get" \
+ "$KIT" run --wasm-wasi --wasm-map-dir="$work=/sandbox:ro" \
+ -e test_main "$work/wasi_path_filestat.wat"
+
+run_ok "wasm-wasi-opendir-readdir" \
+ "$KIT" run --wasm-wasi --wasm-map-dir="$work=/sandbox:ro" \
+ -e test_main "$work/wasi_opendir_readdir.wat"
+
run_fail "wasm-wasi-unknown-import-fails" \
"$KIT" run --wasm-wasi -e test_main "$work/wasi_unknown.wat"
contains "wasm-wasi-unknown-import-diag" \