From d56721a4cd08e16b66a5617510572a1336610b07 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:33:59 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`feat/we?= =?UTF-8?q?b-console-fs-api-examples`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @AndyTechnologies. * https://github.com/AndyTechnologies/wasm-apps/pull/20#issuecomment-5206853207 The following files were modified: * `examples/as-fs/src/main.wasm.ts` * `examples/cpp-saludo/src/main.wasm.cpp` * `examples/mounts-demo/src/main.wasm.cpp` * `examples/rust-fs/src/main.wasm.rs` * `examples/rust-hello/src/main.wasm.rs` * `packages/compiler/src/bindings/console.h` * `packages/compiler/src/bindings/console.rs` * `packages/compiler/src/bindings/console.ts` * `packages/compiler/src/bindings/fs.h` * `packages/compiler/src/bindings/fs.rs` * `packages/compiler/src/bindings/fs.ts` * `packages/compiler/src/bindings/rust/src/console.rs` * `packages/compiler/src/bindings/rust/src/fs.rs` * `packages/compiler/src/bindings/rust/src/wasi.rs` * `packages/compiler/src/bindings/wasi.h` * `packages/compiler/src/bindings/wasi.rs` * `packages/compiler/src/bindings/wasi.ts` * `packages/compiler/src/index.ts` * `packages/compiler/src/strategies/rust-strategy.ts` * `packages/linker/templates/console.as.ts` * `packages/linker/templates/console.h` * `packages/linker/templates/console.rs` * `packages/linker/templates/fs.as.ts` * `packages/linker/templates/fs.h` * `packages/linker/templates/fs.rs` * `packages/linker/templates/wasi.as.ts` * `packages/linker/templates/wasi.h` * `packages/linker/templates/wasi.rs` --- examples/as-fs/src/main.wasm.ts | 3 + examples/cpp-saludo/src/main.wasm.cpp | 7 +- examples/mounts-demo/src/main.wasm.cpp | 7 +- examples/rust-fs/src/main.wasm.rs | 10 +- examples/rust-hello/src/main.wasm.rs | 7 + packages/compiler/src/bindings/console.h | 20 + packages/compiler/src/bindings/console.rs | 27 ++ packages/compiler/src/bindings/console.ts | 21 + packages/compiler/src/bindings/fs.h | 8 + packages/compiler/src/bindings/fs.rs | 127 ++++++ packages/compiler/src/bindings/fs.ts | 38 +- .../compiler/src/bindings/rust/src/console.rs | 43 ++ packages/compiler/src/bindings/rust/src/fs.rs | 116 +++++ .../compiler/src/bindings/rust/src/wasi.rs | 413 +++++++++++++++++- packages/compiler/src/bindings/wasi.h | 152 +++++++ packages/compiler/src/bindings/wasi.rs | 413 +++++++++++++++++- packages/compiler/src/bindings/wasi.ts | 155 ++++++- packages/compiler/src/index.ts | 25 +- .../compiler/src/strategies/rust-strategy.ts | 20 +- packages/linker/templates/console.as.ts | 21 + packages/linker/templates/console.h | 20 + packages/linker/templates/console.rs | 45 ++ packages/linker/templates/fs.as.ts | 38 +- packages/linker/templates/fs.h | 7 + packages/linker/templates/fs.rs | 118 +++++ packages/linker/templates/wasi.as.ts | 155 ++++++- packages/linker/templates/wasi.h | 152 +++++++ packages/linker/templates/wasi.rs | 367 +++++++++++++++- 28 files changed, 2497 insertions(+), 38 deletions(-) diff --git a/examples/as-fs/src/main.wasm.ts b/examples/as-fs/src/main.wasm.ts index 646c9fe..cb2bbd8 100644 --- a/examples/as-fs/src/main.wasm.ts +++ b/examples/as-fs/src/main.wasm.ts @@ -2,6 +2,9 @@ import { log } from 'console'; import { readFile } from 'fs'; +/** + * Reads the greeting file and logs its UTF-8 contents when available. + */ export function _start(): void { log('Opening file...'); let data = readFile('/mnt/data/greeting.txt'); diff --git a/examples/cpp-saludo/src/main.wasm.cpp b/examples/cpp-saludo/src/main.wasm.cpp index 6452f21..951411f 100644 --- a/examples/cpp-saludo/src/main.wasm.cpp +++ b/examples/cpp-saludo/src/main.wasm.cpp @@ -1,7 +1,12 @@ // C++ WASM example using high-level Console API #include "console.h" -extern "C" int _start() { +extern "C" /** + * @brief Logs a greeting when the WebAssembly module starts. + * + * @return 0 to indicate successful completion. + */ +int _start() { console::log("Hola desde C++ WASM!"); return 0; } diff --git a/examples/mounts-demo/src/main.wasm.cpp b/examples/mounts-demo/src/main.wasm.cpp index 446e329..acf2c8d 100644 --- a/examples/mounts-demo/src/main.wasm.cpp +++ b/examples/mounts-demo/src/main.wasm.cpp @@ -2,7 +2,12 @@ #include "console.h" #include "fs.h" -extern "C" int _start() { +extern "C" /** + * @brief Reads the mounted greeting file and writes its contents to standard output. + * + * @return int 0 after processing the greeting file. + */ +int _start() { console::log("Opening file..."); char buf[512]; diff --git a/examples/rust-fs/src/main.wasm.rs b/examples/rust-fs/src/main.wasm.rs index 72b3242..4544d04 100644 --- a/examples/rust-fs/src/main.wasm.rs +++ b/examples/rust-fs/src/main.wasm.rs @@ -5,7 +5,15 @@ use wasm_apps_bindings::{console, fs, wasi, wasm_setup}; wasm_setup!(); -#[no_mangle] +/// Reads the greeting file from the mounted data directory and writes its contents to standard output. +/// +/// Logs a fallback message when the mounted directory or file is unavailable. +/// +/// # Examples +/// +/// ``` +/// _start(); +/// ```”】【 pub extern "C" fn _start() { console::log("Opening file..."); let data = fs::read_file(b"/mnt/data/greeting.txt"); diff --git a/examples/rust-hello/src/main.wasm.rs b/examples/rust-hello/src/main.wasm.rs index bcb690e..8c28035 100644 --- a/examples/rust-hello/src/main.wasm.rs +++ b/examples/rust-hello/src/main.wasm.rs @@ -4,6 +4,13 @@ use wasm_apps_bindings::{console, wasm_setup}; wasm_setup!(); +/// Logs a greeting message when the WebAssembly module starts. +/// +/// # Examples +/// +/// ``` +/// _start(); +/// ``` #[no_mangle] pub extern "C" fn _start() { console::log("Hola desde Rust WASM!"); diff --git a/packages/compiler/src/bindings/console.h b/packages/compiler/src/bindings/console.h index 670d156..3779de4 100644 --- a/packages/compiler/src/bindings/console.h +++ b/packages/compiler/src/bindings/console.h @@ -3,6 +3,26 @@ #include "wasi.h" +/** + * Writes a message followed by a newline to standard output. + * @param msg Message to write. + */ + +/** + * Writes a warning followed by a newline to standard error. + * @param msg Warning message to write. + */ + +/** + * Writes an error message followed by a newline to standard error. + * @param msg Error message to write. + */ + +/** + * Verifies a condition and terminates execution with an assertion message if it is false. + * @param cond Condition to verify. + * @param msg Message to include when the assertion fails. + */ namespace console { inline void log(const char* msg) { diff --git a/packages/compiler/src/bindings/console.rs b/packages/compiler/src/bindings/console.rs index 1f2617d..b3c58c0 100644 --- a/packages/compiler/src/bindings/console.rs +++ b/packages/compiler/src/bindings/console.rs @@ -3,16 +3,43 @@ use crate::wasi; +/// Writes a message followed by a newline to standard output. +/// +/// # Examples +/// +/// ``` +/// log("Build completed"); +/// ``` pub fn log(msg: &str) { let _ = wasi::stdout_str(msg); let _ = wasi::stdout_write(b"\n"); } +/// Writes a warning message followed by a newline to standard error. + +/// + +/// # Examples + +/// + +/// ``` + +/// warn("Configuration is missing"); + +/// ``` pub fn warn(msg: &str) { let _ = wasi::stderr_write(msg.as_bytes()); let _ = wasi::stderr_write(b"\n"); } +/// Writes an error message followed by a newline to standard error. +/// +/// # Examples +/// +/// ``` +/// error("Failed to load configuration"); +/// ``` pub fn error(msg: &str) { let _ = wasi::stderr_write(msg.as_bytes()); let _ = wasi::stderr_write(b"\n"); diff --git a/packages/compiler/src/bindings/console.ts b/packages/compiler/src/bindings/console.ts index fa25e62..1952940 100644 --- a/packages/compiler/src/bindings/console.ts +++ b/packages/compiler/src/bindings/console.ts @@ -2,24 +2,45 @@ import { stdoutWrite, stderrWrite } from "./wasi"; +/** + * Writes a newline-terminated message to standard output. + * + * @param msg - The message to write + */ export function log(msg: string): void { let buf = String.UTF8.encode(msg + "\n"); let arr = Uint8Array.wrap(buf); stdoutWrite(arr.dataStart as usize, arr.length); } +/** + * Writes a warning message to standard error. + * + * @param msg - The warning message to write + */ export function warn(msg: string): void { let buf = String.UTF8.encode(msg + "\n"); let arr = Uint8Array.wrap(buf); stderrWrite(arr.dataStart as usize, arr.length); } +/** + * Writes a newline-terminated error message to standard error. + * + * @param msg - The error message to write + */ export function error(msg: string): void { let buf = String.UTF8.encode(msg + "\n"); let arr = Uint8Array.wrap(buf); stderrWrite(arr.dataStart as usize, arr.length); } +/** + * Verifies a condition and terminates execution when it is false. + * + * @param cond - The condition to verify + * @param msg - The message written when the condition is false + */ export function assert(cond: bool, msg: string): void { if (!cond) { let full = "Assertion failed: " + msg + "\n"; diff --git a/packages/compiler/src/bindings/fs.h b/packages/compiler/src/bindings/fs.h index d63c508..847536d 100644 --- a/packages/compiler/src/bindings/fs.h +++ b/packages/compiler/src/bindings/fs.h @@ -5,6 +5,14 @@ #include #include +/** + * Resolves a path against a preopened directory. + * + * @param path Path to resolve. + * @param out_dir_fd Receives the matching directory descriptor. + * @param out_rel_offset Receives the offset of the path relative to that directory. + * @return 0 on success, or -1 if no preopened directory matches the path. + */ namespace fs { // ── Internal: resolve path to (dirFd, relative) ───── diff --git a/packages/compiler/src/bindings/fs.rs b/packages/compiler/src/bindings/fs.rs index 922d400..9a1d9d4 100644 --- a/packages/compiler/src/bindings/fs.rs +++ b/packages/compiler/src/bindings/fs.rs @@ -10,6 +10,20 @@ struct ResolvedPath { relative: Vec, } +/// Resolves a path against the available preopened directories. +/// +/// # Examples +/// +/// ```no_run +/// let resolved = resolve_path(b"/data/file.txt"); +/// assert!(resolved.is_some()); +/// ``` +/// +/// Returns the first matching preopened directory and the path relative to it. +/// +/// # Returns +/// +/// `Some(ResolvedPath)` for a matching preopened directory, or `None` if no directory matches. fn resolve_path(path: &[u8]) -> Option { for fd in 3..=63i32 { // Try to read prestat @@ -49,6 +63,21 @@ fn resolve_path(path: &[u8]) -> Option { // ── Public API ──────────────────────────────────── +/// Reads a file into a byte vector. +/// +/// The path is interpreted relative to a preopened directory. +/// +/// # Errors +/// +/// Returns a WASI error code if the path cannot be resolved or the file cannot +/// be opened or read. +/// +/// # Examples +/// +/// ```no_run +/// let contents = read_file(b"example.txt").expect("failed to read file"); +/// assert!(!contents.is_empty()); +/// ``` pub fn read_file(path: &[u8]) -> Result, i32> { let r = resolve_path(path).ok_or(-1)?; let fd = wasi::path_open(r.dir_fd, &r.relative, false, false)?; @@ -61,6 +90,27 @@ pub fn read_file(path: &[u8]) -> Result, i32> { Ok(buf) } +/// Reads a file into a caller-provided buffer. +/// +/// The returned byte count indicates how much data was written to `buf`. +/// +/// # Examples +/// +/// ``` +/// let mut buffer = [0u8; 1024]; +/// let result = read_file_to_buf(b"file.txt", &mut buffer); +/// assert!(result.is_ok() || result.is_err()); +/// ``` +/// +/// # Parameters +/// +/// * `path` — The path of the file to read. +/// * `buf` — The buffer receiving the file contents. +/// +/// # Returns +/// +/// The number of bytes read, or a WASI error code. +pub fn read_file_to_buf(path: &[u8], buf: &mut [u8]) -> Result { pub fn read_file_to_buf(path: &[u8], buf: &mut [u8]) -> Result { let r = resolve_path(path).ok_or(-1)?; let fd = wasi::path_open(r.dir_fd, &r.relative, false, false)?; @@ -69,6 +119,37 @@ pub fn read_file_to_buf(path: &[u8], buf: &mut [u8]) -> Result { Ok(n) } +/// Writes data to a file, creating it when necessary. + +/// + +/// # Examples + +/// + +/// ``` + +/// # fn example() -> Result<(), i32> { + +/// let bytes_written = write_file(b"output.txt", b"hello")?; + +/// assert_eq!(bytes_written, 5); + +/// # Ok(()) + +/// # } + +/// ``` + +/// + +/// # Returns + +/// + +/// The number of bytes written, or the WASI error code if the path cannot be + +/// resolved, the file cannot be opened, or the data cannot be written. pub fn write_file(path: &[u8], data: &[u8]) -> Result { let r = resolve_path(path).ok_or(-1)?; let fd = wasi::path_open(r.dir_fd, &r.relative, true, true)?; @@ -77,6 +158,23 @@ pub fn write_file(path: &[u8], data: &[u8]) -> Result { Ok(n) } +/// Checks whether a path refers to an existing file. + +/// + +/// Returns `false` when the path cannot be resolved. + +/// + +/// # Examples + +/// + +/// ``` + +/// assert!(!exists(b"/definitely-missing")); + +/// ``` pub fn exists(path: &[u8]) -> bool { let r = match resolve_path(path) { Some(v) => v, @@ -85,12 +183,41 @@ pub fn exists(path: &[u8]) -> bool { wasi::file_exists(r.dir_fd, &r.relative) } +/// Removes a file at the specified path. +/// +/// # Parameters +/// +/// * `path` - The path of the file to remove. +/// +/// # Returns +/// +/// `Ok(())` if the file is removed, or an error code if path resolution or removal fails. +/// +/// # Examples +/// +/// ``` +/// let result = unlink(b"temporary.txt"); +/// assert!(result.is_ok() || result.is_err()); +/// ``` pub fn unlink(path: &[u8]) -> Result<(), i32> { let r = resolve_path(path).ok_or(-1)?; wasi::path_unlink_file(r.dir_fd, &r.relative).map_err(|e| e.0)?; Ok(()) } +/// Creates a directory at the specified path. +/// +/// # Examples +/// +/// ``` +/// mkdir(b"cache").expect("failed to create directory"); +/// ``` +/// +/// # Returns +/// +/// `Ok(())` when the directory is created, or the WASI error code when creation fails. +/// +/// `path` is interpreted relative to a preopened directory. pub fn mkdir(path: &[u8]) -> Result<(), i32> { let r = resolve_path(path).ok_or(-1)?; wasi::path_create_directory(r.dir_fd, &r.relative).map_err(|e| e.0)?; diff --git a/packages/compiler/src/bindings/fs.ts b/packages/compiler/src/bindings/fs.ts index 158ae8b..4bd75e6 100644 --- a/packages/compiler/src/bindings/fs.ts +++ b/packages/compiler/src/bindings/fs.ts @@ -17,6 +17,12 @@ class ResolvedPath { } } +/** + * Resolves a path against an available preopened directory. + * + * @param path - The path to resolve. + * @returns A resolved directory descriptor and relative path, or `null` if no preopened directory matches. + */ function resolvePath(path: string): ResolvedPath | null { let pathBytes = String.UTF8.encode(path); let pathArr = Uint8Array.wrap(pathBytes); @@ -64,7 +70,12 @@ function resolvePath(path: string): ResolvedPath | null { return null; } -// ── Public API ──────────────────────────────────── +/** + * Reads the contents of a file. + * + * @param path - The path of the file to read + * @returns A buffer containing up to 64 KiB of file data, or `null` if the file cannot be resolved, opened, or read + */ export function readFile(path: string): ArrayBuffer | null { let rp = resolvePath(path); @@ -89,6 +100,13 @@ export function readFile(path: string): ArrayBuffer | null { return result; } +/** + * Writes data to a file, creating it if necessary. + * + * @param path - The path of the file to write + * @param data - The bytes to write + * @returns `true` if the write succeeds, `false` otherwise + */ export function writeFile(path: string, data: ArrayBuffer): bool { let rp = resolvePath(path); if (rp == null) return false; @@ -106,6 +124,12 @@ export function writeFile(path: string, data: ArrayBuffer): bool { return ret >= 0; } +/** + * Checks whether a filesystem path exists. + * + * @param path - The path to check + * @returns `true` if metadata can be retrieved for the path, `false` otherwise + */ export function exists(path: string): bool { let rp = resolvePath(path); if (rp == null) return false; @@ -117,6 +141,12 @@ export function exists(path: string): bool { return fs != null; } +/** + * Removes a file at the specified path. + * + * @param path - The path of the file to remove + * @returns `true` if the file was removed successfully, `false` otherwise. + */ export function unlink(path: string): bool { let rp = resolvePath(path); if (rp == null) return false; @@ -127,6 +157,12 @@ export function unlink(path: string): bool { return pathUnlinkFile(rp!.dirFd, relAddr, relLen) == 0; } +/** + * Creates a directory at the specified path. + * + * @param path - The path of the directory to create + * @returns `true` if the directory is created successfully, `false` otherwise. + */ export function mkdir(path: string): bool { let rp = resolvePath(path); if (rp == null) return false; diff --git a/packages/compiler/src/bindings/rust/src/console.rs b/packages/compiler/src/bindings/rust/src/console.rs index 1f2617d..de23363 100644 --- a/packages/compiler/src/bindings/rust/src/console.rs +++ b/packages/compiler/src/bindings/rust/src/console.rs @@ -3,16 +3,59 @@ use crate::wasi; +/// Writes a message followed by a newline to standard output. + +/// + +/// # Examples + +/// + +/// ``` + +/// log("Build complete"); + +/// ``` + +/// + +/// `msg` is the message to write. + +/// + +/// # Parameters + +/// + +/// * `msg` - The message to write. pub fn log(msg: &str) { let _ = wasi::stdout_str(msg); let _ = wasi::stdout_write(b"\n"); } +/// Writes a warning message followed by a newline to standard error. +/// +/// # Examples +/// +/// ``` +/// warn("This is a warning."); +/// ``` +/// +/// # Parameters +/// +/// * `msg` - The warning message to write. pub fn warn(msg: &str) { let _ = wasi::stderr_write(msg.as_bytes()); let _ = wasi::stderr_write(b"\n"); } +/// Writes a message followed by a newline to standard error. +/// +/// # Examples +/// +/// ``` +/// error("An error occurred"); +/// ``` pub fn error(msg: &str) { let _ = wasi::stderr_write(msg.as_bytes()); let _ = wasi::stderr_write(b"\n"); diff --git a/packages/compiler/src/bindings/rust/src/fs.rs b/packages/compiler/src/bindings/rust/src/fs.rs index d78fbaa..880db42 100644 --- a/packages/compiler/src/bindings/rust/src/fs.rs +++ b/packages/compiler/src/bindings/rust/src/fs.rs @@ -14,6 +14,22 @@ struct ResolvedPath { relative: Vec, } +/// Resolves a path against the available preopened directories. +/// +/// # Arguments +/// +/// * `path` - The path to resolve, represented as bytes. +/// +/// # Returns +/// +/// The matching preopened directory descriptor and path relative to it, or `None` if no directory matches. +/// +/// # Examples +/// +/// ``` +/// let result = resolve_path(b"/path/without/a/preopen"); +/// assert!(result.is_none()); +/// ``` fn resolve_path(path: &[u8]) -> Option { for fd in 3..=63i32 { // Try to read prestat @@ -53,6 +69,22 @@ fn resolve_path(path: &[u8]) -> Option { // ── Public API ──────────────────────────────────── +/// Reads up to 65,536 bytes from a file. +/// +/// The path is resolved relative to an available preopened directory. +/// +/// # Errors +/// +/// Returns `-1` when the path cannot be resolved, or a WASI error code when +/// the file cannot be opened or read. +/// +/// # Examples +/// +/// ``` +/// let contents = read_file(b"config.txt")?; +/// println!("Read {} bytes", contents.len()); +/// # Ok::<(), i32>(()) +/// ``` pub fn read_file(path: &[u8]) -> Result, i32> { let r = resolve_path(path).ok_or(-1)?; let fd = wasi::path_open(r.dir_fd, &r.relative, false, false)?; @@ -65,6 +97,26 @@ pub fn read_file(path: &[u8]) -> Result, i32> { Ok(buf) } +/// Reads a file into a caller-provided buffer. +/// +/// # Examples +/// +/// ``` +/// let mut buf = [0u8; 1024]; +/// let bytes_read = read_file_to_buf(b"file.txt", &mut buf)?; +/// let contents = &buf[..bytes_read]; +/// # let _ = contents; +/// # Ok::<(), i32>(()) +/// ``` +/// +/// # Parameters +/// +/// * `path` - The file path to read. +/// * `buf` - The buffer that receives the file contents. +/// +/// # Returns +/// +/// The number of bytes read, or a WASI error code. pub fn read_file_to_buf(path: &[u8], buf: &mut [u8]) -> Result { let r = resolve_path(path).ok_or(-1)?; let fd = wasi::path_open(r.dir_fd, &r.relative, false, false)?; @@ -73,6 +125,21 @@ pub fn read_file_to_buf(path: &[u8], buf: &mut [u8]) -> Result { Ok(n) } +/// Writes data to a file, creating it when necessary. +/// +/// # Examples +/// +/// ```no_run +/// let written = write_file(b"output.txt", b"file contents")?; +/// assert_eq!(written, 13); +/// # Ok::<(), i32>(()) +/// ``` +/// +/// # Errors +/// +/// Returns the underlying filesystem error code if the path cannot be resolved +/// or the file cannot be opened or written. +pub fn write_file(path: &[u8], data: &[u8]) -> Result { pub fn write_file(path: &[u8], data: &[u8]) -> Result { let r = resolve_path(path).ok_or(-1)?; let fd = wasi::path_open(r.dir_fd, &r.relative, true, true)?; @@ -81,6 +148,24 @@ pub fn write_file(path: &[u8], data: &[u8]) -> Result { Ok(n) } +/// Checks whether a file exists at the specified path. +/// +/// The path is resolved relative to the preopened directories available to the WASI module. +/// Returns `false` when the path cannot be resolved or the file does not exist. +/// +/// # Examples +/// +/// ``` +/// assert!(!exists(b"this-file-does-not-exist")); +/// ``` +/// +/// # Returns +/// +/// `true` if the file exists, `false` otherwise. +/// +/// # Parameters +/// +/// * `path` - Path to the file, relative to a preopened directory. pub fn exists(path: &[u8]) -> bool { let r = match resolve_path(path) { Some(v) => v, @@ -89,12 +174,43 @@ pub fn exists(path: &[u8]) -> bool { wasi::file_exists(r.dir_fd, &r.relative) } +/// Removes a file at the specified path. +/// +/// # Parameters +/// +/// * `path` - The file path as a byte slice. +/// +/// # Returns +/// +/// `Ok(())` if the file is removed successfully, or an `i32` error code if +/// path resolution or removal fails. +/// +/// # Examples +/// +/// ```no_run +/// unlink(b"temporary.txt").expect("failed to remove file"); +/// ``` pub fn unlink(path: &[u8]) -> Result<(), i32> { let r = resolve_path(path).ok_or(-1)?; wasi::path_unlink_file(r.dir_fd, &r.relative).map_err(|e| e.0)?; Ok(()) } +/// Creates a directory at the specified path. +/// +/// # Examples +/// +/// ``` +/// mkdir(b"example-directory").unwrap(); +/// ``` +/// +/// # Arguments +/// +/// * `path` - The directory path represented as bytes. +/// +/// # Returns +/// +/// `Ok(())` when the directory is created, or an error code if creation fails. pub fn mkdir(path: &[u8]) -> Result<(), i32> { let r = resolve_path(path).ok_or(-1)?; wasi::path_create_directory(r.dir_fd, &r.relative).map_err(|e| e.0)?; diff --git a/packages/compiler/src/bindings/rust/src/wasi.rs b/packages/compiler/src/bindings/rust/src/wasi.rs index 6535131..3f3acc7 100644 --- a/packages/compiler/src/bindings/rust/src/wasi.rs +++ b/packages/compiler/src/bindings/rust/src/wasi.rs @@ -85,6 +85,18 @@ impl core::fmt::Display for WasiError { // ── Stdout/stderr ───────────────────────────────── +/// Writes bytes to standard output. +/// +/// # Examples +/// +/// ``` +/// let written = stdout_write(b"Hello, world!\n").unwrap(); +/// assert_eq!(written, 14); +/// ``` +/// +/// # Returns +/// +/// The number of bytes written. pub fn stdout_write(data: &[u8]) -> Result { let iov = __wasi_iovec_t { buf: data.as_ptr(), buf_len: data.len() }; let mut written: usize = 0; @@ -93,10 +105,39 @@ pub fn stdout_write(data: &[u8]) -> Result { Ok(written) } +/// Writes a string to standard output. +/// +/// # Examples +/// +/// ``` +/// let written = stdout_str("Hello, world!").unwrap(); +/// assert_eq!(written, 13); +/// ``` +/// +/// # Returns +/// +/// The number of bytes written. pub fn stdout_str(s: &str) -> Result { stdout_write(s.as_bytes()) } +/// Writes bytes to standard error. +/// +/// # Examples +/// +/// ``` +/// let message = b"error message\n"; +/// let written = stderr_write(message).unwrap(); +/// assert_eq!(written, message.len()); +/// ``` +/// +/// # Returns +/// +/// The number of bytes written. +/// +/// # Errors +/// +/// Returns `WasiError` if the WASI write operation fails. pub fn stderr_write(data: &[u8]) -> Result { let iov = __wasi_iovec_t { buf: data.as_ptr(), buf_len: data.len() }; let mut written: usize = 0; @@ -107,6 +148,23 @@ pub fn stderr_write(data: &[u8]) -> Result { // ── Low-level I/O ───────────────────────────────── +/// Writes bytes to a file descriptor. +/// +/// # Parameters +/// +/// * `fd` - The file descriptor to write to. +/// * `data` - The bytes to write. +/// +/// # Returns +/// +/// The number of bytes written. +/// +/// # Examples +/// +/// ``` +/// let written = fd_write(1, b"hello").unwrap(); +/// assert_eq!(written, 5); +/// ``` pub fn fd_write(fd: i32, data: &[u8]) -> Result { let iov = __wasi_iovec_t { buf: data.as_ptr(), buf_len: data.len() }; let mut written: usize = 0; @@ -115,6 +173,25 @@ pub fn fd_write(fd: i32, data: &[u8]) -> Result { Ok(written) } +/// Reads bytes from a file descriptor into a buffer. +/// +/// # Examples +/// +/// ```no_run +/// let mut buffer = [0u8; 128]; +/// let bytes_read = fd_read(0, &mut buffer)?; +/// # Ok::<(), WasiError>(()) +/// ``` +/// +/// # Parameters +/// +/// * `fd` - The file descriptor to read from. +/// * `buf` - The buffer receiving the data. +/// +/// # Returns +/// +/// The number of bytes read. +pub fn fd_read(fd: i32, buf: &mut [u8]) -> Result { pub fn fd_read(fd: i32, buf: &mut [u8]) -> Result { let iov = __wasi_iovec_t { buf: buf.as_ptr(), buf_len: buf.len() }; let mut nread: usize = 0; @@ -123,12 +200,46 @@ pub fn fd_read(fd: i32, buf: &mut [u8]) -> Result { Ok(nread) } +/// Closes a WASI file descriptor. +/// +/// # Examples +/// +/// ```no_run +/// fd_close(fd)?; +/// # Ok::<(), WasiError>(()) +/// ``` +/// +/// # Errors +/// +/// Returns a [`WasiError`] if the descriptor cannot be closed. pub fn fd_close(fd: i32) -> Result<(), WasiError> { let ret = unsafe { __wasi_fd_close(fd) }; if ret != 0 { return Err(WasiError(-ret)); } Ok(()) } +/// Changes the file descriptor's current position. +/// +/// # Arguments +/// +/// * `fd` - The file descriptor to reposition. +/// * `offset` - The position adjustment. +/// * `whence` - The reference position used to apply the offset. +/// +/// # Returns +/// +/// The resulting file position. +/// +/// # Errors +/// +/// Returns a [`WasiError`] if the seek operation fails. +/// +/// # Examples +/// +/// ``` +/// let position = fd_seek(0, 0, 0); +/// assert!(position.is_ok()); +/// ``` pub fn fd_seek(fd: i32, offset: i64, whence: i32) -> Result { let mut newoffset: u64 = 0; let ret = unsafe { __wasi_fd_seek(fd, offset, whence, &mut newoffset) }; @@ -138,6 +249,22 @@ pub fn fd_seek(fd: i32, offset: i64, whence: i32) -> Result { // ── Preopen dir scanning ─────────────────────────── +/// Retrieves metadata for a preopened directory descriptor. +/// +/// # Examples +/// +/// ``` +/// let result = fd_prestat_get(3); +/// +/// if let Ok(prestat) = result { +/// assert!(prestat.pr_type != 0); +/// } +/// ``` +/// +/// # Returns +/// +/// The preopen metadata, or a [`WasiError`] if the descriptor is not a preopened directory. +pub fn fd_prestat_get(fd: i32) -> Result<__wasi_prestat_t, WasiError> { pub fn fd_prestat_get(fd: i32) -> Result<__wasi_prestat_t, WasiError> { let mut buf: __wasi_prestat_t = unsafe { core::mem::zeroed() }; let ret = unsafe { __wasi_fd_prestat_get(fd, &mut buf) }; @@ -145,6 +272,24 @@ pub fn fd_prestat_get(fd: i32) -> Result<__wasi_prestat_t, WasiError> { Ok(buf) } +/// Retrieves the path of a preopened directory. +/// +/// # Parameters +/// +/// * `fd` - File descriptor for the preopened directory. +/// +/// # Returns +/// +/// The preopened directory path as bytes. +/// +/// # Examples +/// +/// ```no_run +/// let name = fd_prestat_dir_name(3)?; +/// assert!(!name.is_empty()); +/// # Ok::<(), WasiError>(()) +/// ``` +pub fn fd_prestat_dir_name(fd: i32) -> Result, WasiError> { pub fn fd_prestat_dir_name(fd: i32) -> Result, WasiError> { let prestat = fd_prestat_get(fd)?; let mut name = vec![0u8; prestat.pr_name_len as usize]; @@ -157,6 +302,45 @@ pub fn fd_prestat_dir_name(fd: i32) -> Result, WasiError> { Ok(name) } +/// Reads directory entries from a file descriptor into a caller-provided buffer. + +/// + +/// `cookie` specifies the directory position from which reading begins. + +/// + +/// # Examples + +/// + +/// ```no_run + +/// let mut buffer = [0u8; 1024]; + +/// let bytes_read = fd_readdir(3, &mut buffer, 0)?; + +/// assert!(bytes_read <= buffer.len()); + +/// # Ok::<(), WasiError>(()) + +/// ``` + +/// + +/// # Errors + +/// + +/// Returns a [`WasiError`] if the directory entries cannot be read. + +/// + +/// # Returns + +/// + +/// The number of bytes written to `buf`. pub fn fd_readdir(fd: i32, buf: &mut [u8], cookie: u64) -> Result { let mut nread: usize = 0; let ret = unsafe { __wasi_fd_readdir(fd, buf.as_mut_ptr(), buf.len(), cookie, &mut nread) }; @@ -169,6 +353,28 @@ pub fn fd_readdir(fd: i32, buf: &mut [u8], cookie: u64) -> Result(()) +/// ``` +/// +/// # Arguments +/// +/// * `dir_fd` - File descriptor of the directory containing the path. +/// * `path` - Path to open, represented as bytes. +/// * `for_write` - Whether to request write access. +/// * `create` - Whether to create the file if it does not exist. +/// +/// # Returns +/// +/// The opened file descriptor on success, or the WASI error on failure. pub fn path_open(dir_fd: i32, path: &[u8], for_write: bool, create: bool) -> Result { let mut oflags: u32 = 0; let mut rights_base: u64 = RIGHTS_READ; @@ -188,6 +394,23 @@ pub fn path_open(dir_fd: i32, path: &[u8], for_write: bool, create: bool) -> Res Ok(opened_fd) } +/// Retrieves file metadata for a path relative to a directory descriptor. +/// +/// # Examples +/// +/// ```no_run +/// let metadata = path_filestat_get(3, b"file.txt")?; +/// # Ok::<(), WasiError>(()) +/// ``` +/// +/// # Parameters +/// +/// - `dir_fd`: Directory descriptor relative to which the path is resolved. +/// - `path`: Path whose metadata is requested. +/// +/// # Returns +/// +/// The file metadata, or a [`WasiError`] if the operation fails. pub fn path_filestat_get(dir_fd: i32, path: &[u8]) -> Result<__wasi_filestat_t, WasiError> { let mut buf: __wasi_filestat_t = unsafe { core::mem::zeroed() }; let ret = unsafe { __wasi_path_filestat_get(dir_fd, 0, path.as_ptr(), path.len(), &mut buf) }; @@ -195,12 +418,49 @@ pub fn path_filestat_get(dir_fd: i32, path: &[u8]) -> Result<__wasi_filestat_t, Ok(buf) } +/// Removes a file relative to a directory file descriptor. +/// +/// # Parameters +/// +/// * `dir_fd` - File descriptor of the directory containing the file. +/// * `path` - Path of the file to remove. +/// +/// # Returns +/// +/// `Ok(())` when the file is removed, or a `WasiError` when the operation fails. +/// +/// # Examples +/// +/// ``` +/// let result = path_unlink_file(3, b"temporary.txt"); +/// assert!(result.is_ok()); +/// ``` +pub fn path_unlink_file(dir_fd: i32, path: &[u8]) -> Result<(), WasiError> { pub fn path_unlink_file(dir_fd: i32, path: &[u8]) -> Result<(), WasiError> { let ret = unsafe { __wasi_path_unlink_file(dir_fd, path.as_ptr(), path.len()) }; if ret != 0 { return Err(WasiError(-ret)); } Ok(()) } +/// Creates a directory at a path relative to a directory descriptor. + +/// + +/// # Examples + +/// + +/// ```no_run + +/// path_create_directory(dir_fd, b"new-directory")?; + +/// # Ok::<(), WasiError>(()) + +/// ``` + +/// + +/// `path` is passed as a byte slice without a terminating NUL. pub fn path_create_directory(dir_fd: i32, path: &[u8]) -> Result<(), WasiError> { let ret = unsafe { __wasi_path_create_directory(dir_fd, path.as_ptr(), path.len()) }; if ret != 0 { return Err(WasiError(-ret)); } @@ -209,28 +469,146 @@ pub fn path_create_directory(dir_fd: i32, path: &[u8]) -> Result<(), WasiError> // ── Convenience ──────────────────────────────────── +/// Retrieves the file type for a path relative to a directory descriptor. + +/// + +/// # Examples + +/// + +/// ```no_run + +/// let file_type = stat_file(3, b"file.txt")?; + +/// println!("{file_type}"); + +/// # Ok::<(), WasiError>(()) + +/// ``` + +/// + +/// # Arguments + +/// + +/// * `dir_fd` - The directory descriptor relative to which the path is resolved. + +/// * `path` - The path to inspect as a byte slice. + +/// + +/// # Returns + +/// + +/// The WASI file type for the path. pub fn stat_file(dir_fd: i32, path: &[u8]) -> Result { let st = path_filestat_get(dir_fd, path)?; Ok(st.st_filetype) } +/// Retrieves the size of a file relative to a directory descriptor. +/// +/// # Examples +/// +/// ``` +/// # fn main() -> Result<(), WasiError> { +/// let size = file_size(3, b"file.txt")?; +/// assert!(size >= 0); +/// # Ok(()) +/// # } +/// ``` +/// +/// # Errors +/// +/// Returns a `WasiError` if the file metadata cannot be retrieved. +/// +/// # Returns +/// +/// The file size in bytes. pub fn file_size(dir_fd: i32, path: &[u8]) -> Result { let st = path_filestat_get(dir_fd, path)?; Ok(st.st_size) } +/// Determines whether a path can be retrieved relative to a directory descriptor. +/// +/// # Examples +/// +/// ```no_run +/// let exists = file_exists(3, b"config.toml"); +/// println!("File exists: {exists}"); +/// ``` +/// +/// # Parameters +/// +/// * `dir_fd` - Directory descriptor used as the path's base. +/// * `path` - Path to inspect. +/// +/// # Returns +/// +/// `true` if metadata retrieval succeeds, `false` otherwise. pub fn file_exists(dir_fd: i32, path: &[u8]) -> bool { path_filestat_get(dir_fd, path).is_ok() } // ── Environment ──────────────────────────────────── +/// Fills caller-provided memory with the process environment entries. + +/// + +/// # Errors + +/// + +/// Returns a [`WasiError`] if the environment cannot be written to the supplied + +/// buffers. + +/// + +/// # Examples + +/// + +/// ```no_run + +/// let mut environ = core::ptr::null_mut(); + +/// let mut environ_buf = core::ptr::null_mut(); + +/// + +/// environ_get(&mut environ, environ_buf); + +/// ``` pub fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Result<(), WasiError> { let ret = unsafe { __wasi_environ_get(environ, environ_buf) }; if ret != 0 { return Err(WasiError(-ret)); } Ok(()) } +/// Retrieves the number of environment entries and the buffer size required to store them. +/// +/// # Returns +/// +/// A tuple containing the environment entry count and required buffer size in bytes. +/// +/// # Errors +/// +/// Returns a [`WasiError`] if the WASI environment-size query fails. +/// +/// # Examples +/// +/// ``` +/// let (count, buffer_size) = environ_sizes_get().unwrap(); +/// assert!(count >= 0); +/// assert!(buffer_size >= 0); +/// ``` +pub fn environ_sizes_get() -> Result<(usize, usize), WasiError> { pub fn environ_sizes_get() -> Result<(usize, usize), WasiError> { let mut count: usize = 0; let mut buf_size: usize = 0; @@ -241,10 +619,35 @@ pub fn environ_sizes_get() -> Result<(usize, usize), WasiError> { // ── Utility ──────────────────────────────────────── +/// Terminates the process with the specified exit code. +/// +/// # Examples +/// +/// ``` +/// let terminate: fn(i32) -> ! = exit; +/// terminate(0); +/// ``` pub fn exit(code: i32) -> ! { unsafe { __wasi_proc_exit(code) } } +/// Retrieves the current value of a WASI clock. +/// +/// # Arguments +/// +/// * `id` - The WASI clock identifier. +/// * `precision` - The requested clock precision in nanoseconds. +/// +/// # Returns +/// +/// The clock value in nanoseconds, or a [`WasiError`] if the clock cannot be read. +/// +/// # Examples +/// +/// ``` +/// let time = clock_time(0, 0).expect("clock read failed"); +/// assert!(time > 0); +/// ``` pub fn clock_time(id: u32, precision: u64) -> Result { let mut time: u64 = 0; let ret = unsafe { __wasi_clock_time_get(id, precision, &mut time) }; @@ -255,5 +658,13 @@ pub fn clock_time(id: u32, precision: u64) -> Result { // ── WasiError as From helper ──────────── impl From for i32 { - fn from(e: WasiError) -> Self { e.0 } + /// Converts a [`WasiError`] into its numeric error code. +/// +/// # Examples +/// +/// ``` +/// let code: i32 = WasiError(-1).into(); +/// assert_eq!(code, -1); +/// ``` +fn from(e: WasiError) -> Self { e.0 } } diff --git a/packages/compiler/src/bindings/wasi.h b/packages/compiler/src/bindings/wasi.h index ea7a7da..c042f8c 100644 --- a/packages/compiler/src/bindings/wasi.h +++ b/packages/compiler/src/bindings/wasi.h @@ -41,6 +41,158 @@ typedef uint64_t __wasi_dircookie_t; #endif #ifdef __cplusplus +/** + * Writes raw data to standard output. + * @param data Data to write. + * @param len Number of bytes to write. + */ + +/** + * Writes raw data to standard error. + * @param data Data to write. + * @param len Number of bytes to write. + */ + +/** + * Writes a null-terminated string to standard output. + * @param s String to write. + */ + +/** + * Writes data to a file descriptor. + * @param fd File descriptor. + * @param data Data to write. + * @param len Number of bytes to write. + * @returns Number of bytes written, or a negative error code. + */ + +/** + * Reads data from a file descriptor. + * @param fd File descriptor. + * @param buf Buffer for the data. + * @param len Buffer capacity in bytes. + * @returns Number of bytes read, or a negative error code. + */ + +/** + * Closes a file descriptor. + * @param fd File descriptor. + * @returns Zero on success, or a negative error code. + */ + +/** + * Changes the position of a file descriptor. + * @param fd File descriptor. + * @param offset Position adjustment. + * @param whence Reference point for the adjustment. + * @returns New position, or a negative error code. + */ + +/** + * Retrieves metadata for a preopened file descriptor. + * @param fd Preopened file descriptor. + * @param buf Destination for the metadata. + * @returns Zero on success, or a negative error code. + */ + +/** + * Retrieves the path associated with a preopened file descriptor. + * @param fd Preopened file descriptor. + * @param buf Destination buffer. + * @param len Buffer capacity in bytes. + * @returns Zero on success, or a negative error code. + */ + +/** + * Reads directory entries from a file descriptor. + * @param fd Directory file descriptor. + * @param buf Destination buffer. + * @param len Buffer capacity in bytes. + * @param cookie Entry position at which to begin reading. + * @returns Number of bytes written to the buffer, or a negative error code. + */ + +/** + * Opens a path relative to a directory file descriptor. + * @param dir_fd Base directory file descriptor. + * @param path Path to open. + * @param path_len Path length in bytes. + * @param oflags POSIX-style open flags. + * @returns Opened file descriptor, or a negative error code. + */ + +/** + * Retrieves metadata for a path relative to a directory file descriptor. + * @param dir_fd Base directory file descriptor. + * @param flags Path lookup flags. + * @param path Path to inspect. + * @param path_len Path length in bytes. + * @param buf Destination for the metadata. + * @returns Zero on success, or a negative error code. + */ + +/** + * Removes a file relative to a directory file descriptor. + * @param dir_fd Base directory file descriptor. + * @param path Path to remove. + * @param path_len Path length in bytes. + * @returns Zero on success, or a negative error code. + */ + +/** + * Creates a directory relative to a directory file descriptor. + * @param dir_fd Base directory file descriptor. + * @param path Directory path to create. + * @param path_len Path length in bytes. + * @returns Zero on success, or a negative error code. + */ + +/** + * Retrieves the file type for a null-terminated path. + * @param dir_fd Base directory file descriptor. + * @param path Path to inspect. + * @returns File type, or a negative error code. + */ + +/** + * Retrieves the size of a null-terminated file path. + * @param dir_fd Base directory file descriptor. + * @param path Path to inspect. + * @returns File size in bytes, or a negative error code. + */ + +/** + * Determines whether a file exists at a null-terminated path. + * @param dir_fd Base directory file descriptor. + * @param path Path to inspect. + * @returns `true` if the path metadata is available, `false` otherwise. + */ + +/** + * Retrieves environment variables. + * @param environ Destination array for environment strings. + * @param environ_buf Buffer for environment strings. + * @returns Zero on success, or a negative error code. + */ + +/** + * Retrieves the number of environment variables and required buffer size. + * @param count Destination for the variable count. + * @param buf_size Destination for the required buffer size. + * @returns Zero on success, or a negative error code. + */ + +/** + * Terminates the process with an exit code. + * @param code Process exit code. + */ + +/** + * Retrieves the current value of a WASI clock. + * @param id Clock identifier. + * @param precision Requested clock precision. + * @returns Clock value. + */ namespace wasi { // ── Stdout/stderr ────────────────────────────────── diff --git a/packages/compiler/src/bindings/wasi.rs b/packages/compiler/src/bindings/wasi.rs index 2b6a28f..9cc4700 100644 --- a/packages/compiler/src/bindings/wasi.rs +++ b/packages/compiler/src/bindings/wasi.rs @@ -74,6 +74,14 @@ pub const FILETYPE_REGULAR_FILE: u32 = 4; pub struct WasiError(pub i32); impl core::fmt::Display for WasiError { + /// Formats the error as `WASI error `. + /// + /// # Examples + /// + /// ``` + /// let error = WasiError(2); + /// assert_eq!(error.to_string(), "WASI error 2"); + /// ``` fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "WASI error {}", self.0) } @@ -81,6 +89,21 @@ impl core::fmt::Display for WasiError { // ── Stdout/stderr ───────────────────────────────── +/// Writes bytes to standard output. +/// +/// # Examples +/// +/// ``` +/// let written = stdout_write(b"Hello, WASI!\n").unwrap(); +/// assert!(written <= b"Hello, WASI!\n".len()); +/// ``` +/// +/// # Errors +/// +/// Returns a [`WasiError`] if the write fails. +/// +/// @param data Bytes to write to standard output. +/// @returns The number of bytes written. pub fn stdout_write(data: &[u8]) -> Result { let iov = __wasi_iovec_t { buf: data.as_ptr(), buf_len: data.len() }; let mut written: usize = 0; @@ -89,10 +112,34 @@ pub fn stdout_write(data: &[u8]) -> Result { Ok(written) } +/// Writes a string to standard output. +/// +/// # Examples +/// +/// ``` +/// let bytes_written = stdout_str("Hello, WASI!\n").unwrap(); +/// assert_eq!(bytes_written, "Hello, WASI!\n".len()); +/// ``` +/// +/// # Returns +/// +/// The number of bytes written on success. pub fn stdout_str(s: &str) -> Result { stdout_write(s.as_bytes()) } +/// Writes bytes to standard error. +/// +/// # Returns +/// +/// The number of bytes written, or a [`WasiError`] if the write fails. +/// +/// # Examples +/// +/// ``` +/// let written = stderr_write(&[]).unwrap(); +/// assert_eq!(written, 0); +/// ``` pub fn stderr_write(data: &[u8]) -> Result { let iov = __wasi_iovec_t { buf: data.as_ptr(), buf_len: data.len() }; let mut written: usize = 0; @@ -103,6 +150,19 @@ pub fn stderr_write(data: &[u8]) -> Result { // ── Low-level I/O ───────────────────────────────── +/// Writes bytes to a file descriptor. +/// +/// # Examples +/// +/// ``` +/// let written = fd_write(1, b"Hello, WASI!\n").unwrap(); +/// assert_eq!(written, 13); +/// ``` +/// +/// # Errors +/// +/// Returns a [`WasiError`] when the WASI write operation fails. +pub fn fd_write(fd: i32, data: &[u8]) -> Result { pub fn fd_write(fd: i32, data: &[u8]) -> Result { let iov = __wasi_iovec_t { buf: data.as_ptr(), buf_len: data.len() }; let mut written: usize = 0; @@ -111,6 +171,25 @@ pub fn fd_write(fd: i32, data: &[u8]) -> Result { Ok(written) } +/// Reads bytes from a file descriptor into a buffer. +/// +/// # Arguments +/// +/// * `fd` - The file descriptor to read from. +/// * `buf` - The buffer that receives the bytes. +/// +/// # Returns +/// +/// The number of bytes read. +/// +/// # Examples +/// +/// ```no_run +/// let mut buffer = [0u8; 128]; +/// let bytes_read = fd_read(0, &mut buffer)?; +/// # let _ = bytes_read; +/// # Ok::<(), WasiError>(()) +/// ``` pub fn fd_read(fd: i32, buf: &mut [u8]) -> Result { let iov = __wasi_iovec_t { buf: buf.as_ptr(), buf_len: buf.len() }; let mut nread: usize = 0; @@ -119,12 +198,53 @@ pub fn fd_read(fd: i32, buf: &mut [u8]) -> Result { Ok(nread) } +/// Closes an open file descriptor. +/// +/// # Examples +/// +/// ``` +/// let result = fd_close(3); +/// assert!(result.is_ok() || result.is_err()); +/// ``` +/// +/// # Errors +/// +/// Returns a `WasiError` if the descriptor cannot be closed. pub fn fd_close(fd: i32) -> Result<(), WasiError> { let ret = unsafe { __wasi_fd_close(fd) }; if ret != 0 { return Err(WasiError(-ret)); } Ok(()) } +/// Moves the file descriptor's position and reports the resulting offset. + +/// + +/// `whence` specifies how `offset` is interpreted according to the WASI + +/// seek operation. + +/// + +/// # Examples + +/// + +/// ``` + +/// let result = fd_seek(0, 0, 0); + +/// let _ = result; + +/// ``` + +/// + +/// # Returns + +/// + +/// The new absolute file position on success. pub fn fd_seek(fd: i32, offset: i64, whence: i32) -> Result { let mut newoffset: u64 = 0; let ret = unsafe { __wasi_fd_seek(fd, offset, whence, &mut newoffset) }; @@ -134,6 +254,45 @@ pub fn fd_seek(fd: i32, offset: i64, whence: i32) -> Result { // ── Preopen dir scanning ─────────────────────────── +/// Retrieves preopen metadata for a file descriptor. + +/// + +/// # Examples + +/// + +/// ```rust,no_run + +/// let prestat = fd_prestat_get(3)?; + +/// # Ok::<(), WasiError>(()) + +/// ``` + +/// + +/// # Errors + +/// + +/// Returns a [`WasiError`] when the WASI syscall fails. + +/// + +/// # Returns + +/// + +/// The preopen metadata associated with `fd`. + +/// + +/// # Parameters + +/// + +/// * `fd` - The file descriptor to query. pub fn fd_prestat_get(fd: i32) -> Result<__wasi_prestat_t, WasiError> { let mut buf: __wasi_prestat_t = unsafe { core::mem::zeroed() }; let ret = unsafe { __wasi_fd_prestat_get(fd, &mut buf) }; @@ -141,6 +300,37 @@ pub fn fd_prestat_get(fd: i32) -> Result<__wasi_prestat_t, WasiError> { Ok(buf) } +/// Reads the name of a preopened directory associated with a file descriptor. + +/// + +/// The returned bytes exclude any trailing NUL terminator. + +/// + +/// # Examples + +/// + +/// ```no_run + +/// let name = fd_prestat_dir_name(3)?; + +/// println!("{}", String::from_utf8_lossy(&name)); + +/// # Ok::<(), WasiError>(()) + +/// ``` + +/// + +/// # Errors + +/// + +/// Returns a [`WasiError`] if the descriptor is not a preopened directory or + +/// if the WASI operation fails. pub fn fd_prestat_dir_name(fd: i32) -> Result, WasiError> { let prestat = fd_prestat_get(fd)?; let mut name = vec![0u8; prestat.pr_name_len as usize]; @@ -153,6 +343,31 @@ pub fn fd_prestat_dir_name(fd: i32) -> Result, WasiError> { Ok(name) } +/// Reads directory entries from a file descriptor into a byte buffer. +/// +/// `cookie` specifies the directory position from which to begin reading. The +/// returned length is the number of bytes written to `buf`. +/// +/// # Examples +/// +/// ``` +/// let mut buffer = [0u8; 1024]; +/// let _ = fd_readdir(3, &mut buffer, 0); +/// ``` +/// +/// # Errors +/// +/// Returns a [`WasiError`] when the directory read fails. +/// +/// # Parameters +/// +/// * `fd` — File descriptor for the directory. +/// * `buf` — Buffer that receives the directory entry data. +/// * `cookie` — Directory position at which to begin reading. +/// +/// # Returns +/// +/// The number of bytes written to `buf`. pub fn fd_readdir(fd: i32, buf: &mut [u8], cookie: u64) -> Result { let mut nread: usize = 0; let ret = unsafe { __wasi_fd_readdir(fd, buf.as_mut_ptr(), buf.len(), cookie, &mut nread) }; @@ -165,6 +380,30 @@ pub fn fd_readdir(fd: i32, buf: &mut [u8], cookie: u64) -> Result(()) +/// ``` pub fn path_open(dir_fd: i32, path: &[u8], for_write: bool, create: bool) -> Result { let mut oflags: u32 = 0; let mut rights_base: u64 = RIGHTS_READ; @@ -184,6 +423,22 @@ pub fn path_open(dir_fd: i32, path: &[u8], for_write: bool, create: bool) -> Res Ok(opened_fd) } +/// Retrieves metadata for a path relative to a directory descriptor. +/// +/// # Examples +/// +/// ```no_run +/// let metadata = path_filestat_get(3, b"file")?; +/// # Ok::<(), WasiError>(()) +/// ``` +/// +/// # Errors +/// +/// Returns a [`WasiError`] if the WASI metadata query fails. +/// +/// # Returns +/// +/// The file metadata for the specified path. pub fn path_filestat_get(dir_fd: i32, path: &[u8]) -> Result<__wasi_filestat_t, WasiError> { let mut buf: __wasi_filestat_t = unsafe { core::mem::zeroed() }; let ret = unsafe { __wasi_path_filestat_get(dir_fd, 0, path.as_ptr(), path.len(), &mut buf) }; @@ -191,12 +446,48 @@ pub fn path_filestat_get(dir_fd: i32, path: &[u8]) -> Result<__wasi_filestat_t, Ok(buf) } +/// Removes a file relative to a directory file descriptor. +/// +/// # Examples +/// +/// ```no_run +/// path_unlink_file(dir_fd, b"temporary.txt")?; +/// # Ok::<(), WasiError>(()) +/// ``` +/// +/// `path` must contain the file name or relative path to remove. +/// +/// # Errors +/// +/// Returns a [`WasiError`] when the file cannot be removed. +/// +/// # Parameters +/// +/// * `dir_fd` - File descriptor of the directory containing the file. +/// * `path` - File name or relative path of the file to remove. pub fn path_unlink_file(dir_fd: i32, path: &[u8]) -> Result<(), WasiError> { let ret = unsafe { __wasi_path_unlink_file(dir_fd, path.as_ptr(), path.len()) }; if ret != 0 { return Err(WasiError(-ret)); } Ok(()) } +/// Creates a directory at a path relative to a directory descriptor. +/// +/// # Parameters +/// +/// * `dir_fd` — File descriptor of the directory containing the new directory. +/// * `path` — Directory path represented as bytes. +/// +/// # Returns +/// +/// `Ok(())` when the directory is created, or a [`WasiError`] containing the WASI error code. +/// +/// # Examples +/// +/// ```no_run +/// let result = path_create_directory(dir_fd, b"new-directory"); +/// assert!(result.is_ok()); +/// ``` pub fn path_create_directory(dir_fd: i32, path: &[u8]) -> Result<(), WasiError> { let ret = unsafe { __wasi_path_create_directory(dir_fd, path.as_ptr(), path.len()) }; if ret != 0 { return Err(WasiError(-ret)); } @@ -205,28 +496,111 @@ pub fn path_create_directory(dir_fd: i32, path: &[u8]) -> Result<(), WasiError> // ── Convenience ──────────────────────────────────── +/// Retrieves the WASI file type for a path relative to a directory descriptor. +/// +/// # Examples +/// +/// ``` +/// let file_type = stat_file(3, b"file"); +/// assert!(file_type.is_ok()); +/// ``` +/// +/// # Returns +/// +/// The WASI file type for the specified path. pub fn stat_file(dir_fd: i32, path: &[u8]) -> Result { let st = path_filestat_get(dir_fd, path)?; Ok(st.st_filetype) } +/// Retrieves the size of a file relative to a directory descriptor. +/// +/// # Examples +/// +/// ```no_run +/// let size = file_size(3, b"example.txt")?; +/// println!("{size} bytes"); +/// # Ok::<(), WasiError>(()) +/// ``` +/// +/// # Errors +/// +/// Returns a [`WasiError`] if the file metadata cannot be retrieved. +/// +/// # Parameters +/// +/// * `dir_fd` - Directory descriptor containing the file. +/// * `path` - File path relative to `dir_fd`. +/// +/// # Returns +/// +/// The file size in bytes. pub fn file_size(dir_fd: i32, path: &[u8]) -> Result { let st = path_filestat_get(dir_fd, path)?; Ok(st.st_size) } +/// Determines whether a file or directory exists at a path relative to a directory descriptor. +/// +/// Returns `false` when the metadata query fails. +/// +/// # Examples +/// +/// ``` +/// assert!(!file_exists(i32::MAX, b"missing")); +/// ``` pub fn file_exists(dir_fd: i32, path: &[u8]) -> bool { path_filestat_get(dir_fd, path).is_ok() } // ── Environment ──────────────────────────────────── +/// Populates caller-provided storage with the process environment. +/// +/// `environ` must point to an array of pointers, and `environ_buf` must point +/// to a buffer sized according to `environ_sizes_get`. +/// +/// # Examples +/// +/// ```no_run +/// let mut pointers = [core::ptr::null_mut(); 1]; +/// let mut buffer = [0u8; 1]; +/// +/// environ_get(pointers.as_mut_ptr(), buffer.as_mut_ptr())?; +/// # Ok::<(), WasiError>(()) +/// ``` +/// +/// # Errors +/// +/// Returns the WASI error produced by the environment query. +/// +/// # Arguments +/// +/// * `environ` - Pointer to storage for environment-variable pointers. +/// * `environ_buf` - Pointer to storage for environment-variable data. +/// +/// # Returns +/// +/// `Ok(())` when the environment is written successfully. pub fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Result<(), WasiError> { let ret = unsafe { __wasi_environ_get(environ, environ_buf) }; if ret != 0 { return Err(WasiError(-ret)); } Ok(()) } +/// Retrieves the number of environment variables and the buffer size required to store them. +/// +/// # Examples +/// +/// ``` +/// let (count, buffer_size) = environ_sizes_get().unwrap(); +/// assert!(count >= 0); +/// assert!(buffer_size >= 0); +/// ``` +/// +/// # Returns +/// +/// A tuple containing the environment variable count and required buffer size in bytes. pub fn environ_sizes_get() -> Result<(usize, usize), WasiError> { let mut count: usize = 0; let mut buf_size: usize = 0; @@ -237,10 +611,36 @@ pub fn environ_sizes_get() -> Result<(usize, usize), WasiError> { // ── Utility ──────────────────────────────────────── +/// Terminates the current process with the specified exit code. +/// +/// # Examples +/// +/// ```no_run +/// exit(0); +/// ``` +pub fn exit(code: i32) -> ! pub fn exit(code: i32) -> ! { unsafe { __wasi_proc_exit(code) } } +/// Retrieves the current value of a WASI clock. +/// +/// # Parameters +/// +/// * `id` - WASI clock identifier. +/// * `precision` - Requested clock precision in nanoseconds. +/// +/// # Returns +/// +/// The clock value in nanoseconds. +/// +/// # Examples +/// +/// ``` +/// let time = clock_time(0, 0)?; +/// assert!(time > 0); +/// # Ok::<(), WasiError>(()) +/// ``` pub fn clock_time(id: u32, precision: u64) -> Result { let mut time: u64 = 0; let ret = unsafe { __wasi_clock_time_get(id, precision, &mut time) }; @@ -251,5 +651,16 @@ pub fn clock_time(id: u32, precision: u64) -> Result { // ── WasiError as From helper ──────────── impl From for i32 { - fn from(e: WasiError) -> Self { e.0 } + /// Converts a WASI error into its numeric error code. +/// +/// # Examples +/// +/// ``` +/// let error = WasiError(2); +/// let code: i32 = error.into(); +/// assert_eq!(code, 2); +/// ``` +/// +/// Returns the WASI error code. +fn from(e: WasiError) -> Self { e.0 } } diff --git a/packages/compiler/src/bindings/wasi.ts b/packages/compiler/src/bindings/wasi.ts index 6b64c7c..987df8b 100644 --- a/packages/compiler/src/bindings/wasi.ts +++ b/packages/compiler/src/bindings/wasi.ts @@ -64,7 +64,13 @@ export class Filestat { export const FILETYPE_DIRECTORY: i32 = 3; export const FILETYPE_REGULAR_FILE: i32 = 4; -// ── I/O vector helpers ──────────────────────────── +/** + * Creates a WASI I/O vector for a data buffer. + * + * @param data - The address of the data buffer + * @param len - The length of the data buffer in bytes + * @returns The address of the allocated I/O vector + */ function makeIovec(data: usize, len: i32): usize { let buf = heap.alloc(8); @@ -73,7 +79,13 @@ function makeIovec(data: usize, len: i32): usize { return buf; } -// ── Stdout/stderr ───────────────────────────────── +/** + * Writes a byte sequence to standard output. + * + * @param data - Pointer to the bytes to write + * @param len - Number of bytes to write + * @returns The WASI status code + */ export function stdoutWrite(data: usize, len: i32): i32 { let iov = makeIovec(data, len); @@ -82,12 +94,25 @@ export function stdoutWrite(data: usize, len: i32): i32 { return ret; } +/** + * Writes a UTF-8 encoded string to standard output. + * + * @param s - The string to write + * @returns The WASI write status + */ export function stdoutWriteStr(s: string): i32 { let buf = String.UTF8.encode(s); let arr = Uint8Array.wrap(buf); return stdoutWrite(arr.dataStart as usize, arr.length); } +/** + * Writes a byte sequence to standard error. + * + * @param data - The memory address of the bytes to write + * @param len - The number of bytes to write + * @returns The WASI status code + */ export function stderrWrite(data: usize, len: i32): i32 { let iov = makeIovec(data, len); let nwritten = heap.alloc(4); @@ -95,7 +120,14 @@ export function stderrWrite(data: usize, len: i32): i32 { return ret; } -// ── Low-level I/O ───────────────────────────────── +/** + * Writes a single buffer to a file descriptor. + * + * @param fd - The file descriptor to write to + * @param data - The memory address of the data buffer + * @param len - The number of bytes to write + * @returns The number of bytes written on success, or a negative WASI error code + */ export function fdWrite(fd: i32, data: usize, len: i32): i32 { let iov = makeIovec(data, len); @@ -104,6 +136,14 @@ export function fdWrite(fd: i32, data: usize, len: i32): i32 { return ret == 0 ? load(nwritten) : -ret; } +/** + * Reads data from a file descriptor into a buffer. + * + * @param fd - The file descriptor to read from + * @param buf - The destination buffer address + * @param len - The maximum number of bytes to read + * @returns The number of bytes read, or a negative WASI error code + */ export function fdRead(fd: i32, buf: usize, len: i32): i32 { let iov = makeIovec(buf, len); let nread = heap.alloc(4); @@ -111,17 +151,36 @@ export function fdRead(fd: i32, buf: usize, len: i32): i32 { return ret == 0 ? load(nread) : -ret; } +/** + * Closes a file descriptor. + * + * @param fd - The file descriptor to close + * @returns The negated WASI close status + */ export function fdClose(fd: i32): i32 { return -wasiFdClose(fd); } +/** + * Moves a file descriptor's offset. + * + * @param fd - The file descriptor to seek. + * @param offset - The offset adjustment. + * @param whence - The reference position for the adjustment. + * @returns The new offset on success, or a negative WASI error code on failure. + */ export function fdSeek(fd: i32, offset: i64, whence: i32): i64 { let newoffset = heap.alloc(8); let ret = wasiFdSeek(fd, offset, whence, newoffset); return ret == 0 ? load(newoffset) : -ret as i64; } -// ── Preopen dir scanning ─────────────────────────── +/** + * Retrieves metadata for a preopened directory file descriptor. + * + * @param fd - The file descriptor of the preopened directory + * @returns The preopen metadata, or `null` if retrieval fails + */ export function fdPrestatGet(fd: i32): Prestat | null { let buf = heap.alloc(8); @@ -132,6 +191,12 @@ export function fdPrestatGet(fd: i32): Prestat | null { return p; } +/** + * Retrieves the name of a preopened directory. + * + * @param fd - The file descriptor for the preopened directory + * @returns The directory name as UTF-8 bytes without trailing NUL bytes, or `null` if retrieval fails + */ export function fdPrestatDirName(fd: i32): Array | null { let prestat = fdPrestatGet(fd); if (prestat == null) return null; @@ -150,6 +215,16 @@ export function fdPrestatDirName(fd: i32): Array | null { const RIGHTS_READ: i64 = 0x0000000000000001; const RIGHTS_WRITE: i64 = 0x0000000000000002; +/** + * Opens a path relative to a directory file descriptor. + * + * @param dirFd - The file descriptor of the directory containing the path + * @param path - A pointer to the path bytes + * @param pathLen - The length of the path in bytes + * @param forWrite - Whether to request write access + * @param create - Whether to create the file if it does not exist + * @returns The opened file descriptor on success, or a negative WASI error code + */ export function pathOpen(dirFd: i32, path: usize, pathLen: i32, forWrite: bool, create: bool): i32 { let oflags: i32 = 0; let rightsBase: i64 = RIGHTS_READ; @@ -165,6 +240,14 @@ export function pathOpen(dirFd: i32, path: usize, pathLen: i32, forWrite: bool, return ret == 0 ? load(openedFd) : -ret; } +/** + * Retrieves metadata for a path relative to a directory descriptor. + * + * @param dirFd - The directory descriptor containing the path + * @param path - A pointer to the path + * @param pathLen - The path length in bytes + * @returns The file metadata, or `null` if the operation fails + */ export function pathFilestatGet(dirFd: i32, path: usize, pathLen: i32): Filestat | null { let buf = heap.alloc(56); // 8 * 7 = 56 bytes let ret = wasiPathFilestatGet(dirFd, 0, path, pathLen, buf); @@ -181,15 +264,37 @@ export function pathFilestatGet(dirFd: i32, path: usize, pathLen: i32): Filestat return fs; } +/** + * Removes a file relative to a directory file descriptor. + * + * @param dirFd - The file descriptor of the directory containing the file + * @param path - Pointer to the file path + * @param pathLen - Length of the file path + * @returns The negated WASI status code + */ export function pathUnlinkFile(dirFd: i32, path: usize, pathLen: i32): i32 { return -wasiPathUnlinkFile(dirFd, path, pathLen); } +/** + * Creates a directory at a path relative to a directory file descriptor. + * + * @param dirFd - The file descriptor of the directory containing the new directory + * @param path - A pointer to the path bytes + * @param pathLen - The length of the path in bytes + * @returns Zero on success or a negative WASI error code + */ export function pathCreateDirectory(dirFd: i32, path: usize, pathLen: i32): i32 { return -wasiPathCreateDirectory(dirFd, path, pathLen); } -// ── Convenience ──────────────────────────────────── +/** + * Retrieves the file type for a path relative to a directory descriptor. + * + * @param dirFd - The directory file descriptor used as the path reference. + * @param path - The path to inspect. + * @returns The file type, or `-1` if metadata cannot be retrieved. + */ export function statFile(dirFd: i32, path: string): i32 { let buf = String.UTF8.encode(path); @@ -198,6 +303,13 @@ export function statFile(dirFd: i32, path: string): i32 { return fs == null ? -1 : fs!.stFiletype; } +/** + * Retrieves the size of a file at the specified path. + * + * @param dirFd - The file descriptor of the directory containing the path + * @param path - The path to the file + * @returns The file size in bytes, or `-1` if the file metadata cannot be retrieved + */ export function fileSize(dirFd: i32, path: string): i64 { let buf = String.UTF8.encode(path); let arr = Uint8Array.wrap(buf); @@ -205,6 +317,13 @@ export function fileSize(dirFd: i32, path: string): i64 { return fs == null ? -1 : fs!.stSize; } +/** + * Checks whether a path can be inspected relative to a directory descriptor. + * + * @param dirFd - The directory file descriptor used as the path's base + * @param path - The path to inspect + * @returns `true` if file metadata can be retrieved, `false` otherwise + */ export function fileExists(dirFd: i32, path: string): bool { let buf = String.UTF8.encode(path); let arr = Uint8Array.wrap(buf); @@ -212,12 +331,23 @@ export function fileExists(dirFd: i32, path: string): bool { return fs != null; } -// ── Environment ──────────────────────────────────── +/** + * Retrieves the environment variables into the supplied buffers. + * + * @param environ - Pointer to the environment variable pointer array + * @param environBuf - Pointer to the environment variable buffer + * @returns The negated WASI status code + */ export function environGet(environ: usize, environBuf: usize): i32 { return -wasiEnvironGet(environ, environBuf); } +/** + * Retrieves the number of environment variables and the total size of their names and values. + * + * @returns A two-element array containing the environment variable count and buffer size, or `null` if retrieval fails. + */ export function environSizesGet(): StaticArray | null { let count = heap.alloc(4); let bufSize = heap.alloc(4); @@ -229,7 +359,13 @@ export function environSizesGet(): StaticArray | null { return result; } -// ── Utility ──────────────────────────────────────── +/** + * Retrieves the current time from a WASI clock. + * + * @param id - The WASI clock identifier + * @param precision - The requested clock precision + * @returns The clock time, or `-1` if retrieval fails + */ export function clockTime(id: i32, precision: i64): i64 { let time = heap.alloc(8); @@ -237,6 +373,11 @@ export function clockTime(id: i32, precision: i64): i64 { return ret == 0 ? load(time) : -1; } +/** + * Terminates the process with the specified exit code. + * + * @param code - The exit status to report. + */ export function exit(code: i32): void { wasiProcExit(code); } diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 413a95b..2a87bc4 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -66,11 +66,12 @@ export interface AscCoreResult { } /** - * Reescribe imports bare de bindings (`console`, `fs`, `wasi`) a rutas relativas - * al directorio del fuente. asc 0.28 NO resuelve bare imports vía `--path` - * (solo busca `~lib/` en la stdlib, que secuestra `console` y `wasi`), - * por lo que los nombres de bindings se convierten a imports relativos con - * extensión `.ts` desde `sourceDir`. + * Rewrites bare binding imports to relative paths for their TypeScript files. + * + * @param sourceCode - Source code containing binding imports + * @param bindingsDir - Directory containing the binding files + * @param sourceDir - Directory used as the base for relative paths + * @returns Source code with `console`, `fs`, and `wasi` imports rewritten */ export function rewriteBindingImports(sourceCode: string, bindingsDir: string, sourceDir: string): string { const importPathFor = (name: string): string => { @@ -86,11 +87,17 @@ export function rewriteBindingImports(sourceCode: string, bindingsDir: string, s } /** - * Ejecuta `asc` (AssemblyScript CLI) con los argumentos adecuados y devuelve - * los archivos generados. No aplica caché — es la capa pura de spawn + I/O. + * Compiles AssemblyScript source and returns the generated artifacts without using caches. * - * Usada tanto por `compileWasm()` (que añade caché encima) como por - * `AssemblyScriptToolchainStrategy.compile()` (que produce ToolchainResult). + * @param sourceCode - The AssemblyScript source to compile + * @param fileName - The source file name or path used for compilation and error reporting + * @param isDev - Whether to use development compilation settings + * @param runtime - The AssemblyScript runtime to use + * @param sourceMap - Whether to generate a source map in development mode + * @param optimizeLevel - The optimization level for release builds + * @param shrinkLevel - The code-shrinking level for release builds + * @returns The generated WebAssembly bytes, declarations, JavaScript bindings, optional source map, and source hash + * @throws CompilerError If compilation fails or required output files are missing */ export async function compileAssemblyScriptCore( sourceCode: string, diff --git a/packages/compiler/src/strategies/rust-strategy.ts b/packages/compiler/src/strategies/rust-strategy.ts index 45221b4..6485afd 100644 --- a/packages/compiler/src/strategies/rust-strategy.ts +++ b/packages/compiler/src/strategies/rust-strategy.ts @@ -12,20 +12,26 @@ const __dirname = path.dirname(__filename); const BINDINGS_RUST_DIR = path.resolve(__dirname, '..', 'bindings', 'rust'); /** - * Escapa un path para usarlo como valor TOML: `\` → `\\` y `"` → `\"` - * (necesario para rutas win32 dentro de `path = "..."`). + * Escapes a path for use as a TOML string value. + * + * @param p - The path to escape + * @returns The path with backslashes and double quotes escaped */ export function escapeTomlPathValue(p: string): string { return p.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); } /** - * Inyecta `wasm_apps_bindings = { path = "" }` en el manifest. + * Adds the `wasm_apps_bindings` path dependency to a TOML manifest. + * + * The dependency is appended to the existing `[dependencies]` section or added + * in a new section when none exists. Existing manifests that already contain + * the dependency are returned unchanged. * - * String-based (sin parser TOML): inserta la línea al final de la sección - * `[dependencies]` existente, o crea la sección al final del archivo si falta. - * Detecta EOL (CRLF-aware), preserva el contenido previo byte a byte y es - * idempotente (si la dep ya está, no toca nada). + * @param manifest - The TOML manifest content to update + * @param bindingsDir - The path to the `wasm_apps_bindings` crate + * @param isWin32 - Whether to escape the path for Windows TOML syntax + * @returns The manifest with the bindings dependency added */ export function injectBindingsDependency(manifest: string, bindingsDir: string, isWin32 = false): string { // Idempotence guard: ya inyectado → no-op diff --git a/packages/linker/templates/console.as.ts b/packages/linker/templates/console.as.ts index fa25e62..f21687a 100644 --- a/packages/linker/templates/console.as.ts +++ b/packages/linker/templates/console.as.ts @@ -2,24 +2,45 @@ import { stdoutWrite, stderrWrite } from "./wasi"; +/** + * Writes a message followed by a newline to standard output. + * + * @param msg - The message to write + */ export function log(msg: string): void { let buf = String.UTF8.encode(msg + "\n"); let arr = Uint8Array.wrap(buf); stdoutWrite(arr.dataStart as usize, arr.length); } +/** + * Writes a warning message to standard error. + * + * @param msg - The warning message to write + */ export function warn(msg: string): void { let buf = String.UTF8.encode(msg + "\n"); let arr = Uint8Array.wrap(buf); stderrWrite(arr.dataStart as usize, arr.length); } +/** + * Writes an error message to standard error. + * + * @param msg - The error message to write + */ export function error(msg: string): void { let buf = String.UTF8.encode(msg + "\n"); let arr = Uint8Array.wrap(buf); stderrWrite(arr.dataStart as usize, arr.length); } +/** + * Verifies a condition and terminates execution when it is false. + * + * @param cond - The condition to verify + * @param msg - The message written to standard error when the condition is false + */ export function assert(cond: bool, msg: string): void { if (!cond) { let full = "Assertion failed: " + msg + "\n"; diff --git a/packages/linker/templates/console.h b/packages/linker/templates/console.h index 670d156..bcac20c 100644 --- a/packages/linker/templates/console.h +++ b/packages/linker/templates/console.h @@ -3,6 +3,26 @@ #include "wasi.h" +/** + * Writes a message followed by a newline to standard output. + * @param msg Message to write. + */ + +/** + * Writes a warning message followed by a newline to standard error. + * @param msg Warning message to write. + */ + +/** + * Writes an error message followed by a newline to standard error. + * @param msg Error message to write. + */ + +/** + * Checks a condition and reports failed assertions to standard error. + * @param cond Condition to check. + * @param msg Message to include when the condition is false. + */ namespace console { inline void log(const char* msg) { diff --git a/packages/linker/templates/console.rs b/packages/linker/templates/console.rs index 1f2617d..696959c 100644 --- a/packages/linker/templates/console.rs +++ b/packages/linker/templates/console.rs @@ -3,16 +3,61 @@ use crate::wasi; +/// Writes a message followed by a newline to standard output. +/// +/// # Examples +/// +/// ``` +/// log("Hello, world!"); +/// ``` +/// +/// # Parameters +/// +/// * `msg` - The message to write. pub fn log(msg: &str) { let _ = wasi::stdout_str(msg); let _ = wasi::stdout_write(b"\n"); } +/// Writes a warning message followed by a newline to standard error. + +/// + +/// # Examples + +/// + +/// ``` + +/// warn("Configuration is incomplete"); + +/// ``` + +/// + +/// # Parameters + +/// + +/// * `msg` - The warning message to write. pub fn warn(msg: &str) { let _ = wasi::stderr_write(msg.as_bytes()); let _ = wasi::stderr_write(b"\n"); } +/// Writes a message followed by a newline to standard error. + +/// + +/// # Examples + +/// + +/// ``` + +/// error("An error occurred."); + +/// ``` pub fn error(msg: &str) { let _ = wasi::stderr_write(msg.as_bytes()); let _ = wasi::stderr_write(b"\n"); diff --git a/packages/linker/templates/fs.as.ts b/packages/linker/templates/fs.as.ts index 158ae8b..bd053e4 100644 --- a/packages/linker/templates/fs.as.ts +++ b/packages/linker/templates/fs.as.ts @@ -17,6 +17,12 @@ class ResolvedPath { } } +/** + * Resolves a path against an available preopened directory. + * + * @param path - The path to resolve. + * @returns The matching directory descriptor and relative path, or `null` if no preopened directory matches. + */ function resolvePath(path: string): ResolvedPath | null { let pathBytes = String.UTF8.encode(path); let pathArr = Uint8Array.wrap(pathBytes); @@ -64,7 +70,12 @@ function resolvePath(path: string): ResolvedPath | null { return null; } -// ── Public API ──────────────────────────────────── +/** + * Reads the contents of a file. + * + * @param path - The path of the file to read + * @returns The file contents, or `null` if the path cannot be resolved or the file cannot be opened or read + */ export function readFile(path: string): ArrayBuffer | null { let rp = resolvePath(path); @@ -89,6 +100,13 @@ export function readFile(path: string): ArrayBuffer | null { return result; } +/** + * Writes data to a file at the specified path. + * + * @param path - The path of the file to write + * @param data - The contents to write to the file + * @returns `true` if the file is opened and written successfully, `false` otherwise + */ export function writeFile(path: string, data: ArrayBuffer): bool { let rp = resolvePath(path); if (rp == null) return false; @@ -106,6 +124,12 @@ export function writeFile(path: string, data: ArrayBuffer): bool { return ret >= 0; } +/** + * Checks whether a filesystem entry exists at the specified path. + * + * @param path - The path to check + * @returns `true` if metadata is available for the path, `false` otherwise + */ export function exists(path: string): bool { let rp = resolvePath(path); if (rp == null) return false; @@ -117,6 +141,12 @@ export function exists(path: string): bool { return fs != null; } +/** + * Removes a file at the specified path. + * + * @param path - The path of the file to remove + * @returns `true` if the file was removed successfully, `false` otherwise + */ export function unlink(path: string): bool { let rp = resolvePath(path); if (rp == null) return false; @@ -127,6 +157,12 @@ export function unlink(path: string): bool { return pathUnlinkFile(rp!.dirFd, relAddr, relLen) == 0; } +/** + * Creates a directory at the specified path. + * + * @param path - The path of the directory to create + * @returns `true` if the directory is created successfully, `false` otherwise + */ export function mkdir(path: string): bool { let rp = resolvePath(path); if (rp == null) return false; diff --git a/packages/linker/templates/fs.h b/packages/linker/templates/fs.h index d63c508..9e01d75 100644 --- a/packages/linker/templates/fs.h +++ b/packages/linker/templates/fs.h @@ -5,6 +5,13 @@ #include #include +/** + * Resolves a path against a preopened directory. + * @param path Path to resolve. + * @param out_dir_fd Receives the matching directory descriptor. + * @param out_rel_offset Receives the offset of the path relative to that directory. + * @return 0 on success, or -1 if no preopened directory matches the path. + */ namespace fs { // ── Internal: resolve path to (dirFd, relative) ───── diff --git a/packages/linker/templates/fs.rs b/packages/linker/templates/fs.rs index 922d400..7aff289 100644 --- a/packages/linker/templates/fs.rs +++ b/packages/linker/templates/fs.rs @@ -10,6 +10,18 @@ struct ResolvedPath { relative: Vec, } +/// Resolves a path relative to the first matching preopened directory. +/// +/// # Examples +/// +/// ``` +/// if let Some(resolved) = resolve_path(b"/sandbox/file.txt") { +/// assert_eq!(resolved.relative, b"file.txt"); +/// } +/// ``` +/// +/// Returns `Some` with the matching directory descriptor and relative path, or +/// `None` when no preopened directory matches. fn resolve_path(path: &[u8]) -> Option { for fd in 3..=63i32 { // Try to read prestat @@ -49,6 +61,19 @@ fn resolve_path(path: &[u8]) -> Option { // ── Public API ──────────────────────────────────── +/// Reads up to 64 KiB from a file resolved against the configured WASI directories. +/// +/// # Errors +/// +/// Returns the WASI or path-resolution error code. +/// +/// # Examples +/// +/// ``` +/// let result = read_file(b"example.txt"); +/// assert!(result.is_ok() || result.is_err()); +/// ``` +pub fn read_file(path: &[u8]) -> Result, i32> pub fn read_file(path: &[u8]) -> Result, i32> { let r = resolve_path(path).ok_or(-1)?; let fd = wasi::path_open(r.dir_fd, &r.relative, false, false)?; @@ -61,6 +86,29 @@ pub fn read_file(path: &[u8]) -> Result, i32> { Ok(buf) } +/// Reads a file into the provided buffer. +/// +/// The buffer is filled from the beginning, up to its capacity. The returned +/// count indicates how many bytes were read. +/// +/// # Examples +/// +/// ``` +/// let mut buffer = [0u8; 1024]; +/// let bytes_read = read_file_to_buf(b"file.txt", &mut buffer)?; +/// let contents = &buffer[..bytes_read]; +/// # let _: &[u8] = contents; +/// # Ok::<(), i32>(()) +/// ``` +/// +/// # Parameters +/// +/// * `path` - Path of the file to read. +/// * `buf` - Buffer that receives the file contents. +/// +/// # Returns +/// +/// The number of bytes read, or a WASI error code. pub fn read_file_to_buf(path: &[u8], buf: &mut [u8]) -> Result { let r = resolve_path(path).ok_or(-1)?; let fd = wasi::path_open(r.dir_fd, &r.relative, false, false)?; @@ -69,6 +117,19 @@ pub fn read_file_to_buf(path: &[u8], buf: &mut [u8]) -> Result { Ok(n) } +/// Writes data to a file, creating the file when necessary. +/// +/// # Examples +/// +/// ``` +/// let bytes_written = write_file(b"example.txt", b"hello").unwrap(); +/// assert_eq!(bytes_written, 5); +/// ``` +/// +/// # Returns +/// +/// The number of bytes written, or a WASI error code. +pub fn write_file(path: &[u8], data: &[u8]) -> Result pub fn write_file(path: &[u8], data: &[u8]) -> Result { let r = resolve_path(path).ok_or(-1)?; let fd = wasi::path_open(r.dir_fd, &r.relative, true, true)?; @@ -77,6 +138,41 @@ pub fn write_file(path: &[u8], data: &[u8]) -> Result { Ok(n) } +/// Checks whether a file exists at the specified path. + +/// + +/// Paths are resolved against the available preopened directories. An unresolved + +/// path is treated as nonexistent. + +/// + +/// # Examples + +/// + +/// ``` + +/// let file_exists = exists(b"config.toml"); + +/// ``` + +/// + +/// # Arguments + +/// + +/// * `path` - The file path to check as bytes. + +/// + +/// # Returns + +/// + +/// `true` if the file exists, `false` otherwise. pub fn exists(path: &[u8]) -> bool { let r = match resolve_path(path) { Some(v) => v, @@ -85,12 +181,34 @@ pub fn exists(path: &[u8]) -> bool { wasi::file_exists(r.dir_fd, &r.relative) } +/// Removes the file at the specified path. +/// +/// # Errors +/// +/// Returns `-1` if the path cannot be resolved, or the WASI error code if +/// removing the file fails. +/// +/// # Examples +/// +/// ```no_run +/// unlink(b"temporary.txt").expect("failed to remove file"); +/// ``` pub fn unlink(path: &[u8]) -> Result<(), i32> { let r = resolve_path(path).ok_or(-1)?; wasi::path_unlink_file(r.dir_fd, &r.relative).map_err(|e| e.0)?; Ok(()) } +/// Creates a directory at the specified path. +/// +/// Returns an error code if the path cannot be resolved or the directory cannot be created. +/// +/// # Examples +/// +/// ```no_run +/// mkdir(b"new-directory").expect("failed to create directory"); +/// ``` +pub fn mkdir(path: &[u8]) -> Result<(), i32> { pub fn mkdir(path: &[u8]) -> Result<(), i32> { let r = resolve_path(path).ok_or(-1)?; wasi::path_create_directory(r.dir_fd, &r.relative).map_err(|e| e.0)?; diff --git a/packages/linker/templates/wasi.as.ts b/packages/linker/templates/wasi.as.ts index 6b64c7c..4e09586 100644 --- a/packages/linker/templates/wasi.as.ts +++ b/packages/linker/templates/wasi.as.ts @@ -64,7 +64,13 @@ export class Filestat { export const FILETYPE_DIRECTORY: i32 = 3; export const FILETYPE_REGULAR_FILE: i32 = 4; -// ── I/O vector helpers ──────────────────────────── +/** + * Creates a WASI I/O vector for a memory buffer. + * + * @param data - The buffer address + * @param len - The buffer length in bytes + * @returns The address of the allocated I/O vector + */ function makeIovec(data: usize, len: i32): usize { let buf = heap.alloc(8); @@ -73,7 +79,13 @@ function makeIovec(data: usize, len: i32): usize { return buf; } -// ── Stdout/stderr ───────────────────────────────── +/** + * Writes a memory buffer to standard output. + * + * @param data - Pointer to the buffer in linear memory + * @param len - Number of bytes to write + * @returns The WASI status code + */ export function stdoutWrite(data: usize, len: i32): i32 { let iov = makeIovec(data, len); @@ -82,12 +94,25 @@ export function stdoutWrite(data: usize, len: i32): i32 { return ret; } +/** + * Writes a string to standard output. + * + * @param s - The string to write + * @returns The WASI write status + */ export function stdoutWriteStr(s: string): i32 { let buf = String.UTF8.encode(s); let arr = Uint8Array.wrap(buf); return stdoutWrite(arr.dataStart as usize, arr.length); } +/** + * Writes a memory buffer to standard error. + * + * @param data - The address of the buffer to write + * @param len - The number of bytes to write + * @returns The WASI status code + */ export function stderrWrite(data: usize, len: i32): i32 { let iov = makeIovec(data, len); let nwritten = heap.alloc(4); @@ -95,7 +120,14 @@ export function stderrWrite(data: usize, len: i32): i32 { return ret; } -// ── Low-level I/O ───────────────────────────────── +/** + * Writes a buffer to a file descriptor. + * + * @param fd - The file descriptor to write to + * @param data - The address of the buffer in linear memory + * @param len - The number of bytes to write + * @returns The number of bytes written on success, or a negative WASI error code on failure + */ export function fdWrite(fd: i32, data: usize, len: i32): i32 { let iov = makeIovec(data, len); @@ -104,6 +136,14 @@ export function fdWrite(fd: i32, data: usize, len: i32): i32 { return ret == 0 ? load(nwritten) : -ret; } +/** + * Reads bytes from a file descriptor into a memory buffer. + * + * @param fd - The file descriptor to read from + * @param buf - The destination buffer address + * @param len - The maximum number of bytes to read + * @returns The number of bytes read on success, or a negative error code on failure + */ export function fdRead(fd: i32, buf: usize, len: i32): i32 { let iov = makeIovec(buf, len); let nread = heap.alloc(4); @@ -111,17 +151,36 @@ export function fdRead(fd: i32, buf: usize, len: i32): i32 { return ret == 0 ? load(nread) : -ret; } +/** + * Closes a file descriptor. + * + * @param fd - The file descriptor to close + * @returns The negated WASI status code + */ export function fdClose(fd: i32): i32 { return -wasiFdClose(fd); } +/** + * Moves the file descriptor's offset according to the specified position mode. + * + * @param fd - The file descriptor to seek. + * @param offset - The offset to apply. + * @param whence - The positioning mode that determines how the offset is applied. + * @returns The resulting file offset, or a negative error code if the operation fails. + */ export function fdSeek(fd: i32, offset: i64, whence: i32): i64 { let newoffset = heap.alloc(8); let ret = wasiFdSeek(fd, offset, whence, newoffset); return ret == 0 ? load(newoffset) : -ret as i64; } -// ── Preopen dir scanning ─────────────────────────── +/** + * Retrieves metadata for a preopened directory file descriptor. + * + * @param fd - The file descriptor of the preopened directory + * @returns The directory metadata, or `null` if retrieval fails + */ export function fdPrestatGet(fd: i32): Prestat | null { let buf = heap.alloc(8); @@ -132,6 +191,12 @@ export function fdPrestatGet(fd: i32): Prestat | null { return p; } +/** + * Retrieves the name of a preopened directory. + * + * @param fd - The file descriptor for the preopened directory + * @returns The directory name as UTF-8 bytes, or `null` if retrieval fails + */ export function fdPrestatDirName(fd: i32): Array | null { let prestat = fdPrestatGet(fd); if (prestat == null) return null; @@ -150,6 +215,16 @@ export function fdPrestatDirName(fd: i32): Array | null { const RIGHTS_READ: i64 = 0x0000000000000001; const RIGHTS_WRITE: i64 = 0x0000000000000002; +/** + * Opens a path relative to a directory file descriptor. + * + * @param dirFd - The file descriptor of the directory containing the path + * @param path - Pointer to the path bytes + * @param pathLen - The length of the path in bytes + * @param forWrite - Whether to grant read and write access instead of read-only access + * @param create - Whether to create the file if it does not exist + * @returns The opened file descriptor on success, or a negative WASI error code + */ export function pathOpen(dirFd: i32, path: usize, pathLen: i32, forWrite: bool, create: bool): i32 { let oflags: i32 = 0; let rightsBase: i64 = RIGHTS_READ; @@ -165,6 +240,14 @@ export function pathOpen(dirFd: i32, path: usize, pathLen: i32, forWrite: bool, return ret == 0 ? load(openedFd) : -ret; } +/** + * Retrieves metadata for a path relative to a directory file descriptor. + * + * @param dirFd - The file descriptor of the directory containing the path + * @param path - A pointer to the path bytes + * @param pathLen - The length of the path in bytes + * @returns The path metadata, or `null` if retrieval fails + */ export function pathFilestatGet(dirFd: i32, path: usize, pathLen: i32): Filestat | null { let buf = heap.alloc(56); // 8 * 7 = 56 bytes let ret = wasiPathFilestatGet(dirFd, 0, path, pathLen, buf); @@ -181,15 +264,37 @@ export function pathFilestatGet(dirFd: i32, path: usize, pathLen: i32): Filestat return fs; } +/** + * Removes a file relative to a directory file descriptor. + * + * @param dirFd - The file descriptor of the directory containing the file + * @param path - A pointer to the file path + * @param pathLen - The length of the file path in bytes + * @returns Zero on success or a negative WASI error code + */ export function pathUnlinkFile(dirFd: i32, path: usize, pathLen: i32): i32 { return -wasiPathUnlinkFile(dirFd, path, pathLen); } +/** + * Creates a directory at a path relative to a directory descriptor. + * + * @param dirFd - The file descriptor of the directory containing the new directory. + * @param path - A pointer to the path in memory. + * @param pathLen - The path length in bytes. + * @returns The negated WASI status code. + */ export function pathCreateDirectory(dirFd: i32, path: usize, pathLen: i32): i32 { return -wasiPathCreateDirectory(dirFd, path, pathLen); } -// ── Convenience ──────────────────────────────────── +/** + * Retrieves the file type for a path relative to a directory descriptor. + * + * @param dirFd - The directory file descriptor + * @param path - The path to inspect + * @returns The file type, or `-1` if metadata retrieval fails + */ export function statFile(dirFd: i32, path: string): i32 { let buf = String.UTF8.encode(path); @@ -198,6 +303,13 @@ export function statFile(dirFd: i32, path: string): i32 { return fs == null ? -1 : fs!.stFiletype; } +/** + * Retrieves the size of a file relative to a directory descriptor. + * + * @param dirFd - The directory file descriptor used as the path's base. + * @param path - The file path. + * @returns The file size in bytes, or `-1` if the metadata cannot be retrieved. + */ export function fileSize(dirFd: i32, path: string): i64 { let buf = String.UTF8.encode(path); let arr = Uint8Array.wrap(buf); @@ -205,6 +317,13 @@ export function fileSize(dirFd: i32, path: string): i64 { return fs == null ? -1 : fs!.stSize; } +/** + * Determines whether a path exists relative to a directory descriptor. + * + * @param dirFd - The directory file descriptor used as the path's base + * @param path - The path to check + * @returns `true` if metadata can be retrieved for the path, `false` otherwise + */ export function fileExists(dirFd: i32, path: string): bool { let buf = String.UTF8.encode(path); let arr = Uint8Array.wrap(buf); @@ -212,12 +331,23 @@ export function fileExists(dirFd: i32, path: string): bool { return fs != null; } -// ── Environment ──────────────────────────────────── +/** + * Retrieves the environment variables and their values into the provided buffers. + * + * @param environ - Pointer to the environment variable pointer array + * @param environBuf - Pointer to the environment variable buffer + * @returns The negated WASI status code + */ export function environGet(environ: usize, environBuf: usize): i32 { return -wasiEnvironGet(environ, environBuf); } +/** + * Retrieves the number of environment variables and the total buffer size required for their names and values. + * + * @returns A two-element array containing the environment variable count and buffer size, or `null` if the query fails. + */ export function environSizesGet(): StaticArray | null { let count = heap.alloc(4); let bufSize = heap.alloc(4); @@ -229,7 +359,13 @@ export function environSizesGet(): StaticArray | null { return result; } -// ── Utility ──────────────────────────────────────── +/** + * Retrieves the current time from a WASI clock. + * + * @param id - The WASI clock identifier + * @param precision - The requested clock precision + * @returns The clock time, or `-1` if retrieval fails + */ export function clockTime(id: i32, precision: i64): i64 { let time = heap.alloc(8); @@ -237,6 +373,11 @@ export function clockTime(id: i32, precision: i64): i64 { return ret == 0 ? load(time) : -1; } +/** + * Terminates the process with the specified exit code. + * + * @param code - The exit status to report. + */ export function exit(code: i32): void { wasiProcExit(code); } diff --git a/packages/linker/templates/wasi.h b/packages/linker/templates/wasi.h index ea7a7da..4d3a00e 100644 --- a/packages/linker/templates/wasi.h +++ b/packages/linker/templates/wasi.h @@ -41,6 +41,158 @@ typedef uint64_t __wasi_dircookie_t; #endif #ifdef __cplusplus +/** + * Writes bytes to standard output. + * @param data Buffer containing the bytes to write. + * @param len Number of bytes to write. + */ + +/** + * Writes bytes to standard error. + * @param data Buffer containing the bytes to write. + * @param len Number of bytes to write. + */ + +/** + * Writes a null-terminated string to standard output. + * @param s String to write. + */ + +/** + * Writes bytes to a file descriptor. + * @param fd File descriptor receiving the bytes. + * @param data Buffer containing the bytes to write. + * @param len Number of bytes to write. + * @return Number of bytes written, or a negative error code. + */ + +/** + * Reads bytes from a file descriptor. + * @param fd File descriptor to read from. + * @param buf Buffer receiving the bytes. + * @param len Capacity of the buffer. + * @return Number of bytes read, or a negative error code. + */ + +/** + * Closes a file descriptor. + * @param fd File descriptor to close. + * @return Zero on success, or a negative error code. + */ + +/** + * Changes the position of a file descriptor. + * @param fd File descriptor to seek. + * @param offset Position adjustment. + * @param whence Reference point for the adjustment. + * @return New file position, or a negative error code. + */ + +/** + * Retrieves metadata for a preopened file descriptor. + * @param fd Preopened file descriptor. + * @param buf Buffer receiving the preopen metadata. + * @return Zero on success, or a negative error code. + */ + +/** + * Retrieves the name of a preopened directory. + * @param fd Preopened directory descriptor. + * @param buf Buffer receiving the directory name. + * @param len Capacity of the buffer. + * @return Zero on success, or a negative error code. + */ + +/** + * Reads directory entries from a file descriptor. + * @param fd Directory file descriptor. + * @param buf Buffer receiving directory entries. + * @param len Capacity of the buffer. + * @param cookie Position from which to read entries. + * @return Number of bytes written to the buffer, or a negative error code. + */ + +/** + * Opens a path relative to a directory descriptor. + * @param dir_fd Directory descriptor containing the path. + * @param path Path to open. + * @param path_len Length of the path. + * @param oflags File opening flags. + * @return Opened file descriptor, or a negative error code. + */ + +/** + * Retrieves metadata for a path relative to a directory descriptor. + * @param dir_fd Directory descriptor containing the path. + * @param flags Path lookup flags. + * @param path Path to inspect. + * @param path_len Length of the path. + * @param buf Buffer receiving the file metadata. + * @return Zero on success, or a negative error code. + */ + +/** + * Removes a file relative to a directory descriptor. + * @param dir_fd Directory descriptor containing the file. + * @param path Path of the file to remove. + * @param path_len Length of the path. + * @return Zero on success, or a negative error code. + */ + +/** + * Creates a directory relative to a directory descriptor. + * @param dir_fd Directory descriptor in which to create the directory. + * @param path Directory path. + * @param path_len Length of the path. + * @return Zero on success, or a negative error code. + */ + +/** + * Retrieves the type of a file. + * @param dir_fd Directory descriptor containing the file. + * @param path File path. + * @return File type, or a negative error code. + */ + +/** + * Retrieves the size of a file. + * @param dir_fd Directory descriptor containing the file. + * @param path File path. + * @return File size, or a negative error code. + */ + +/** + * Determines whether a path exists. + * @param dir_fd Directory descriptor containing the path. + * @param path Path to check. + * @return `true` if the path exists, `false` otherwise. + */ + +/** + * Retrieves the process environment. + * @param environ Buffer receiving environment pointers. + * @param environ_buf Buffer receiving environment strings. + * @return Zero on success, or a negative error code. + */ + +/** + * Retrieves the number and total buffer size of environment variables. + * @param count Receives the number of environment variables. + * @param buf_size Receives the required buffer size. + * @return Zero on success, or a negative error code. + */ + +/** + * Terminates the process with an exit code. + * @param code Process exit code. + */ + +/** + * Retrieves the current clock time. + * @param id Clock identifier. + * @param precision Requested clock precision. + * @return Clock time. + */ namespace wasi { // ── Stdout/stderr ────────────────────────────────── diff --git a/packages/linker/templates/wasi.rs b/packages/linker/templates/wasi.rs index 2b6a28f..919af42 100644 --- a/packages/linker/templates/wasi.rs +++ b/packages/linker/templates/wasi.rs @@ -74,6 +74,14 @@ pub const FILETYPE_REGULAR_FILE: u32 = 4; pub struct WasiError(pub i32); impl core::fmt::Display for WasiError { + /// Formats the error as `WASI error `. + /// + /// # Examples + /// + /// ``` + /// let error = WasiError(2); + /// assert_eq!(error.to_string(), "WASI error 2"); + /// ``` fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "WASI error {}", self.0) } @@ -81,6 +89,26 @@ impl core::fmt::Display for WasiError { // ── Stdout/stderr ───────────────────────────────── +/// Writes bytes to the standard output stream. +/// +/// # Examples +/// +/// ``` +/// let written = stdout_write(b"Hello, world!\n").unwrap(); +/// assert_eq!(written, 15); +/// ``` +/// +/// # Errors +/// +/// Returns a [`WasiError`] if the write operation fails. +/// +/// # Parameters +/// +/// * `data` - The bytes to write. +/// +/// # Returns +/// +/// The number of bytes written. pub fn stdout_write(data: &[u8]) -> Result { let iov = __wasi_iovec_t { buf: data.as_ptr(), buf_len: data.len() }; let mut written: usize = 0; @@ -89,10 +117,38 @@ pub fn stdout_write(data: &[u8]) -> Result { Ok(written) } +/// Writes a UTF-8 string to standard output. +/// +/// # Examples +/// +/// ``` +/// let bytes_written = stdout_str("Hello, world!").unwrap(); +/// assert_eq!(bytes_written, 13); +/// ``` +/// +/// # Returns +/// +/// The number of bytes written, or a [`WasiError`] if the write fails. pub fn stdout_str(s: &str) -> Result { stdout_write(s.as_bytes()) } +/// Writes bytes to the standard error stream. +/// +/// # Returns +/// +/// The number of bytes written. +/// +/// # Errors +/// +/// Returns a [`WasiError`] if the WASI write operation fails. +/// +/// # Examples +/// +/// ``` +/// let written = stderr_write(b"error\n").unwrap(); +/// assert_eq!(written, 6); +/// ``` pub fn stderr_write(data: &[u8]) -> Result { let iov = __wasi_iovec_t { buf: data.as_ptr(), buf_len: data.len() }; let mut written: usize = 0; @@ -103,6 +159,19 @@ pub fn stderr_write(data: &[u8]) -> Result { // ── Low-level I/O ───────────────────────────────── +/// Writes bytes to a file descriptor. +/// +/// # Examples +/// +/// ``` +/// let written = fd_write(1, b"hello").unwrap(); +/// assert_eq!(written, 5); +/// ``` +/// +/// # Errors +/// +/// Returns a [`WasiError`] when the WASI write operation fails. +pub fn fd_write(fd: i32, data: &[u8]) -> Result { pub fn fd_write(fd: i32, data: &[u8]) -> Result { let iov = __wasi_iovec_t { buf: data.as_ptr(), buf_len: data.len() }; let mut written: usize = 0; @@ -111,6 +180,24 @@ pub fn fd_write(fd: i32, data: &[u8]) -> Result { Ok(written) } +/// Reads bytes from a file descriptor into a buffer. +/// +/// # Parameters +/// +/// * `buf` - Buffer that receives the bytes read. +/// +/// # Returns +/// +/// The number of bytes read, or a [`WasiError`] if the read fails. +/// +/// # Examples +/// +/// ``` +/// let mut buffer = [0u8; 16]; +/// let bytes_read = fd_read(0, &mut buffer)?; +/// assert!(bytes_read <= buffer.len()); +/// # Ok::<(), WasiError>(()) +/// ``` pub fn fd_read(fd: i32, buf: &mut [u8]) -> Result { let iov = __wasi_iovec_t { buf: buf.as_ptr(), buf_len: buf.len() }; let mut nread: usize = 0; @@ -119,12 +206,62 @@ pub fn fd_read(fd: i32, buf: &mut [u8]) -> Result { Ok(nread) } +/// Closes a WASI file descriptor. + +/// + +/// # Examples + +/// + +/// ``` + +/// let result = fd_close(3); + +/// assert!(result.is_ok() || result.is_err()); + +/// ``` + +/// + +/// # Returns + +/// + +/// `Ok(())` when the descriptor is closed successfully, or a `WasiError` + +/// containing the WASI error code. pub fn fd_close(fd: i32) -> Result<(), WasiError> { let ret = unsafe { __wasi_fd_close(fd) }; if ret != 0 { return Err(WasiError(-ret)); } Ok(()) } +/// Moves a file descriptor's position and reports the resulting absolute offset. +/// +/// `whence` selects the reference point for `offset`, such as the beginning, +/// current position, or end of the file. +/// +/// # Errors +/// +/// Returns a [`WasiError`] when the seek operation fails. +/// +/// # Examples +/// +/// ```no_run +/// let position = fd_seek(fd, 0, 0)?; +/// # let _: Result<(), WasiError> = Ok(()); +/// ``` +/// +/// # Arguments +/// +/// * `fd` - The file descriptor to seek. +/// * `offset` - The signed displacement from the position selected by `whence`. +/// * `whence` - The reference position for the displacement. +/// +/// # Returns +/// +/// The resulting absolute file offset. pub fn fd_seek(fd: i32, offset: i64, whence: i32) -> Result { let mut newoffset: u64 = 0; let ret = unsafe { __wasi_fd_seek(fd, offset, whence, &mut newoffset) }; @@ -134,6 +271,27 @@ pub fn fd_seek(fd: i32, offset: i64, whence: i32) -> Result { // ── Preopen dir scanning ─────────────────────────── +/// Retrieves metadata for a preopened directory associated with a file descriptor. +/// +/// # Examples +/// +/// ``` +/// let result = fd_prestat_get(3); +/// assert!(result.is_ok() || result.is_err()); +/// ``` +/// +/// # Errors +/// +/// Returns a [`WasiError`] if the WASI operation fails. +/// +/// # Arguments +/// +/// * `fd` - The file descriptor for the preopened directory. +/// +/// # Returns +/// +/// The preopened-directory metadata associated with `fd`. +pub fn fd_prestat_get(fd: i32) -> Result<__wasi_prestat_t, WasiError> pub fn fd_prestat_get(fd: i32) -> Result<__wasi_prestat_t, WasiError> { let mut buf: __wasi_prestat_t = unsafe { core::mem::zeroed() }; let ret = unsafe { __wasi_fd_prestat_get(fd, &mut buf) }; @@ -141,6 +299,22 @@ pub fn fd_prestat_get(fd: i32) -> Result<__wasi_prestat_t, WasiError> { Ok(buf) } +/// Retrieves the path associated with a preopened directory descriptor. +/// +/// # Examples +/// +/// ```no_run +/// let directory_name = fd_prestat_dir_name(3)?; +/// # Ok::<(), WasiError>(()) +/// ``` +/// +/// # Arguments +/// +/// * `fd` - The preopened directory file descriptor. +/// +/// # Returns +/// +/// The directory path as bytes without a trailing NUL byte. pub fn fd_prestat_dir_name(fd: i32) -> Result, WasiError> { let prestat = fd_prestat_get(fd)?; let mut name = vec![0u8; prestat.pr_name_len as usize]; @@ -153,6 +327,20 @@ pub fn fd_prestat_dir_name(fd: i32) -> Result, WasiError> { Ok(name) } +/// Reads directory entries from a file descriptor into a buffer, starting at the specified cookie. +/// +/// # Examples +/// +/// ```no_run +/// let mut buffer = [0u8; 4096]; +/// let bytes_read = fd_readdir(3, &mut buffer, 0)?; +/// # let _ = bytes_read; +/// # Ok::<(), WasiError>(()) +/// ``` +/// +/// The returned byte count indicates how much of the buffer was filled. Use the +/// cookie from a directory entry to continue reading subsequent entries. +pub fn fd_readdir(fd: i32, buf: &mut [u8], cookie: u64) -> Result { pub fn fd_readdir(fd: i32, buf: &mut [u8], cookie: u64) -> Result { let mut nread: usize = 0; let ret = unsafe { __wasi_fd_readdir(fd, buf.as_mut_ptr(), buf.len(), cookie, &mut nread) }; @@ -165,6 +353,25 @@ pub fn fd_readdir(fd: i32, buf: &mut [u8], cookie: u64) -> Result(()) +/// ``` +/// +/// # Arguments +/// +/// * `dir_fd` - File descriptor of the directory containing the path. +/// * `path` - Path to open, without a trailing NUL byte. +/// * `for_write` - Whether to request write access. +/// * `create` - Whether to create the file when it does not exist. +/// +/// # Returns +/// +/// The opened file descriptor, or the WASI error from the operation. pub fn path_open(dir_fd: i32, path: &[u8], for_write: bool, create: bool) -> Result { let mut oflags: u32 = 0; let mut rights_base: u64 = RIGHTS_READ; @@ -184,6 +391,23 @@ pub fn path_open(dir_fd: i32, path: &[u8], for_write: bool, create: bool) -> Res Ok(opened_fd) } +/// Retrieves file metadata for a path relative to a directory descriptor. +/// +/// # Examples +/// +/// ``` +/// let metadata = path_filestat_get(3, b"example.txt"); +/// assert!(metadata.is_ok()); +/// ``` +/// +/// # Arguments +/// +/// * `dir_fd` - Directory descriptor used as the path's base. +/// * `path` - Path whose metadata is requested. +/// +/// # Returns +/// +/// The file metadata, or a [`WasiError`] if the metadata cannot be retrieved. pub fn path_filestat_get(dir_fd: i32, path: &[u8]) -> Result<__wasi_filestat_t, WasiError> { let mut buf: __wasi_filestat_t = unsafe { core::mem::zeroed() }; let ret = unsafe { __wasi_path_filestat_get(dir_fd, 0, path.as_ptr(), path.len(), &mut buf) }; @@ -191,12 +415,51 @@ pub fn path_filestat_get(dir_fd: i32, path: &[u8]) -> Result<__wasi_filestat_t, Ok(buf) } +/// Removes a file relative to a directory file descriptor. + +/// + +/// # Examples + +/// + +/// ```no_run + +/// let result = path_unlink_file(3, b"temporary.txt"); + +/// assert!(result.is_ok()); + +/// ``` + +/// + +/// # Errors + +/// + +/// Returns the WASI error reported when the file cannot be removed. pub fn path_unlink_file(dir_fd: i32, path: &[u8]) -> Result<(), WasiError> { let ret = unsafe { __wasi_path_unlink_file(dir_fd, path.as_ptr(), path.len()) }; if ret != 0 { return Err(WasiError(-ret)); } Ok(()) } +/// Creates a directory at the specified path relative to a file descriptor. +/// +/// # Parameters +/// +/// * `dir_fd` — File descriptor of the directory containing the new directory. +/// * `path` — Directory path, excluding a trailing NUL byte. +/// +/// # Returns +/// +/// `Ok(())` when the directory is created, or a [`WasiError`] describing the failure. +/// +/// # Examples +/// +/// ```no_run +/// path_create_directory(3, b"new-directory").unwrap(); +/// ``` pub fn path_create_directory(dir_fd: i32, path: &[u8]) -> Result<(), WasiError> { let ret = unsafe { __wasi_path_create_directory(dir_fd, path.as_ptr(), path.len()) }; if ret != 0 { return Err(WasiError(-ret)); } @@ -205,28 +468,99 @@ pub fn path_create_directory(dir_fd: i32, path: &[u8]) -> Result<(), WasiError> // ── Convenience ──────────────────────────────────── +/// Retrieves the WASI file type for a path relative to a directory descriptor. +/// +/// # Returns +/// +/// The file type reported by WASI, such as [`FILETYPE_REGULAR_FILE`] or [`FILETYPE_DIRECTORY`]. +/// +/// # Examples +/// +/// ``` +/// let file_type = stat_file(3, b"file.txt")?; +/// assert_eq!(file_type, FILETYPE_REGULAR_FILE); +/// # Ok::<(), WasiError>(()) +/// ``` pub fn stat_file(dir_fd: i32, path: &[u8]) -> Result { let st = path_filestat_get(dir_fd, path)?; Ok(st.st_filetype) } +/// Retrieves the size of a file relative to a directory descriptor. +/// +/// # Examples +/// +/// ```no_run +/// let size = file_size(3, b"example.txt")?; +/// # Ok::<(), WasiError>(()) +/// ``` +/// +/// # Errors +/// +/// Returns the WASI error produced when the file metadata cannot be retrieved. +pub fn file_size(dir_fd: i32, path: &[u8]) -> Result { pub fn file_size(dir_fd: i32, path: &[u8]) -> Result { let st = path_filestat_get(dir_fd, path)?; Ok(st.st_size) } +/// Checks whether a path can be located relative to a directory descriptor. +/// +/// # Examples +/// +/// ``` +/// assert!(!file_exists(-1, b"missing")); +/// ``` +/// +/// Returns `true` when metadata lookup succeeds, or `false` when it fails. pub fn file_exists(dir_fd: i32, path: &[u8]) -> bool { path_filestat_get(dir_fd, path).is_ok() } // ── Environment ──────────────────────────────────── +/// Populates caller-provided storage with the process environment variables. +/// +/// `environ` receives pointers to each environment entry, and `environ_buf` +/// receives the corresponding NUL-terminated strings. Allocate the required +/// storage using [`environ_sizes_get`] before calling this function. +/// +/// # Examples +/// +/// ```no_run +/// let mut pointers = [core::ptr::null_mut(); 16]; +/// let mut buffer = [0u8; 1024]; +/// +/// environ_get(pointers.as_mut_ptr(), buffer.as_mut_ptr()).unwrap(); +/// ``` +/// +/// # Safety +/// +/// The pointers must reference writable storage large enough for all +/// environment pointers and strings. +pub fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Result<(), WasiError> pub fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Result<(), WasiError> { let ret = unsafe { __wasi_environ_get(environ, environ_buf) }; if ret != 0 { return Err(WasiError(-ret)); } Ok(()) } +/// Retrieves the number of environment variables and the buffer size required to store them. +/// +/// # Examples +/// +/// ``` +/// let (count, buffer_size) = environ_sizes_get().unwrap(); +/// assert!(buffer_size >= count); +/// ``` +/// +/// # Returns +/// +/// The number of environment variables and the required environment buffer size. +/// +/// # Errors +/// +/// Returns a [`WasiError`] if the WASI environment-size query fails. pub fn environ_sizes_get() -> Result<(usize, usize), WasiError> { let mut count: usize = 0; let mut buf_size: usize = 0; @@ -237,10 +571,32 @@ pub fn environ_sizes_get() -> Result<(usize, usize), WasiError> { // ── Utility ──────────────────────────────────────── +/// Terminates the process with the specified exit code. +/// +/// # Examples +/// +/// ```no_run +/// exit(0); +/// ``` pub fn exit(code: i32) -> ! { unsafe { __wasi_proc_exit(code) } } +/// Retrieves the current time from a WASI clock. +/// +/// `id` identifies the clock, and `precision` specifies the requested clock precision +/// in nanoseconds. +/// +/// # Examples +/// +/// ``` +/// let time = clock_time(0, 1)?; +/// # Ok::<(), WasiError>(()) +/// ``` +/// +/// # Returns +/// +/// The clock time, or a `WasiError` if the clock query fails. pub fn clock_time(id: u32, precision: u64) -> Result { let mut time: u64 = 0; let ret = unsafe { __wasi_clock_time_get(id, precision, &mut time) }; @@ -251,5 +607,14 @@ pub fn clock_time(id: u32, precision: u64) -> Result { // ── WasiError as From helper ──────────── impl From for i32 { - fn from(e: WasiError) -> Self { e.0 } + /// Converts a [`WasiError`] into its underlying WASI error code. +/// +/// # Examples +/// +/// ``` +/// let error = WasiError(5); +/// let code: i32 = error.into(); +/// assert_eq!(code, 5); +/// ``` +fn from(e: WasiError) -> Self { e.0 } }