kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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:
Mdriver/env.h | 26++++++++++++++++++++++++++
Mdriver/env/posix.c | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/env/windows.c | 168+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/lib/wasm_run.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/kit/wasm.h | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/api/wasm_host.c | 324++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mtest/driver-wasm/run.sh | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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" \