From d7b93e1c384eea86fde0e949119c5fce49338cdc Mon Sep 17 00:00:00 2001 From: Joinhack Date: Mon, 19 May 2025 17:26:55 +0800 Subject: [PATCH 01/45] wasi open api implements --- Cargo.toml | 3 +- src/lib.rs | 27 ++++++++ src/wasi/mod.rs | 142 ++++++++++++++++++++++++++++++++++++++++++ src/wasi/preview_1.js | 137 ++++++++++++++++++++++++++++++++++++++++ src/wasi/preview_1.rs | 42 +++++++++++++ 5 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 src/wasi/mod.rs create mode 100644 src/wasi/preview_1.js create mode 100644 src/wasi/preview_1.rs diff --git a/Cargo.toml b/Cargo.toml index 4afdb36..c606098 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,4 +29,5 @@ opt-level = 3 crypto = [] fetch = [] llm = [] -default = ["crypto", "fetch"] +wasi = [] +default = ["crypto", "fetch", "wasi"] diff --git a/src/lib.rs b/src/lib.rs index 7de945f..1aeaaf2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,8 @@ use javy_plugin_api::{ pub mod crypto; #[cfg(feature = "fetch")] pub mod fetch; +#[cfg(feature = "wasi")] +pub mod wasi; #[cfg(feature = "llm")] pub mod llm; @@ -19,6 +21,7 @@ pub mod llm; use crypto::bless_get_random_values; #[cfg(feature = "fetch")] use fetch::bless_fetch_request; + #[cfg(feature = "llm")] use llm::bless_llm_plugin; @@ -61,6 +64,28 @@ pub extern "C" fn initialize_runtime() { )?, )?; + #[cfg(feature = "wasi")] + { + macro_rules! bind { + ($l: ident) => { + let name = concat!("__javy_", stringify!($l)); + ctx.globals().set( + name, + Function::new( + ctx.clone(), + MutFn::new(move |cx, args| { + let (cx, args) = hold_and_release!(cx, args); + wasi::$l(hold!(cx.clone(), args)) + .map_err(|e| to_js_error(cx, e)) + }), + )?, + )?; + }; + } + bind!(wasi_preview1_open); + bind!(wasi_preview1_fd_prestat_dir_name); + } + #[cfg(feature = "llm")] ctx.globals().set( "BlessLLM", @@ -87,6 +112,8 @@ pub extern "C" fn initialize_runtime() { ctx.eval::<(), _>(include_str!("crypto/crypto.js"))?; #[cfg(feature = "fetch")] ctx.eval::<(), _>(include_str!("fetch/fetch.js"))?; + #[cfg(feature = "wasi")] + ctx.eval::<(), _>(include_str!("wasi/preview_1.js"))?; Ok::<_, anyhow::Error>(()) }) .unwrap(); diff --git a/src/wasi/mod.rs b/src/wasi/mod.rs new file mode 100644 index 0000000..e563219 --- /dev/null +++ b/src/wasi/mod.rs @@ -0,0 +1,142 @@ +use javy_plugin_api::javy::{ + quickjs::{Object as JObject, Value}, + Args +}; +use anyhow::{anyhow, bail, Result}; + +mod preview_1; + +pub fn wasi_preview1_open<'a>(args: Args<'a>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let mut opened_fd: i32 = 0; + let [ + dirfd, + fd_lookup_flags, + path, + fd_oflags, + fd_rights, + fd_rights_inherited, + fd_flags, + .. + ] = args_pat else { + bail!( + "open expects 7 parameters: the path and the dirfd, fd_lookup_flags, path, fd_oflags, fd_rights ... Got: {} parameters.", + args.len() + ); + }; + let dirfd = dirfd.as_int() + .ok_or_else(|| anyhow!("dirfd must be a number"))?; + let fd_lookup_flags = fd_lookup_flags.as_int() + .ok_or_else(|| anyhow!("fd_lookup_flags must be a number"))?; + let oflags = fd_oflags.as_int() + .ok_or_else(|| anyhow!("oflags must be a number"))?; + let fs_rights_base = if fd_rights.is_int() { + fd_rights.as_int() + .map(|i| i as i64) + .ok_or_else(|| anyhow!("fd_rights must be a number"))? + } else { + fd_rights.as_big_int() + .map(|x| x.clone()) + .ok_or_else(|| anyhow!("fd_rights must be a number"))? + .to_i64()? + }; + let fd_rights_inherited = if fd_rights_inherited.is_int() { + fd_rights_inherited.as_int() + .map(|i| i as i64) + .ok_or_else(|| anyhow!("fd_rights_inherited must be a number"))? + } else { + fd_rights_inherited.as_big_int() + .map(|x| x.clone()) + .ok_or_else(|| anyhow!("fd_rights_inherited must be a number"))? + .to_i64()? + }; + let fdflags = fd_flags.as_int() + .ok_or_else(|| anyhow!("fdflags must be a number"))?; + let opened_fd_ptr = (&mut opened_fd as *mut i32) as i32; + let path = path.as_string() + .ok_or_else(|| anyhow!("path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + let path_ptr = path.as_ptr() as i32; + let path_len = path.len() as i32; + let mut rs = unsafe { + preview_1::path_open( + dirfd, + fd_lookup_flags, + path_ptr, + path_len, + oflags, + fs_rights_base, + fd_rights_inherited, + fdflags, + opened_fd_ptr) + }; + + if rs == 0 { + rs = opened_fd; + } else { + rs = -rs; + } + + Ok(Value::new_int(cx, rs)) +} + +pub fn wasi_preview1_close<'a>(args: Args<'a>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let [ + fd, + .. + ] = args_pat else { + bail!( + "close expects 1 parameter: the fd, Got: {} parameters.", + args.len() + ); + }; + let fd = fd.as_int() + .ok_or_else(|| anyhow!("fd must be a number"))?; + let rs = unsafe { preview_1::fd_close(fd) }; + Ok(Value::new_int(cx, -rs)) +} + +pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let [ + fd, + .. + ] = args_pat else { + bail!( + "fd_prestat_dir_name expects 1 parameters: the fd, path_ptr and path_len, Got: {} parameters.", + args.len() + ); + }; + let mut path_len: i32 = 0; + let fd = fd.as_int() + .ok_or_else(|| anyhow!("fd must be a number"))?; + let path_len_ptr = (&mut path_len as *mut i32) as i32; + let rs = unsafe { preview_1::fd_prestat_get(fd, path_len_ptr) }; + let obj = JObject::new(cx)?; + if rs != 0 { + obj.set("code", -rs)?; + return Ok(Value::from_object(obj)) + } + let mut path_buf = vec![0u8; path_len as usize]; + let rs = unsafe { + preview_1::fd_prestat_dir_name( + fd, + path_buf.as_mut_ptr() as i32, + path_len + ) + }; + obj.set("code", -rs)?; + if rs == 0 { + let path = String::from_utf8(path_buf)?; + obj.set("dir_name", path)?; + } + Ok(Value::from_object(obj)) +} + + + diff --git a/src/wasi/preview_1.js b/src/wasi/preview_1.js new file mode 100644 index 0000000..bef56e8 --- /dev/null +++ b/src/wasi/preview_1.js @@ -0,0 +1,137 @@ + +// Wrap everything in an anonymous function to avoid leaking local variables into the global scope. +(function () { + // Get a reference to the function before we delete it from `globalThis`. + const __javy_wasi_preview1_open = globalThis.__javy_wasi_preview1_open; + const __javy_wasi_preview1_fd_prestat_dir_name = globalThis.__javy_wasi_preview1_fd_prestat_dir_name; + const Rights = { + FD_DATASYNC: 0x1, + FD_READ: 0x2, + FD_SEEK: 0x4, + FD_FDSTAT_SET_FLAGS: 0x8, + FD_SYNC: 0x10, + FD_TELL: 0x20, + FD_WRITE: 0x40, + FD_ADVISE: 0x80, + FD_ALLOCATE: 0x100, + PATH_CREATE_DIRECTORY: 0x200, + PATH_CREATE_FILE: 0x400 , + PATH_LINK_SOURCE: 0x800, + PATH_LINK_TARGET: 0x1000, + PATH_OPEN: 0x2000, + FD_READDIR: 0x4000, + PATH_READLINK: 0x8000, + PATH_RENAME_SOURCE: 0x10000, + PATH_RENAME_TARGET: 0x20000, + PATH_FILESTAT_GET: 40000, + PATH_FILESTAT_SET_SIZE: 0x80000, + PATH_FILESTAT_SET_TIMES: 0x100000, + FD_FILESTAT_GET: 0x200000, + FD_FILESTAT_SET_SIZE: 0x400000, + FD_FILESTAT_SET_TIMES: 0x800000, + PATH_SYMLINK: 0x1000000, + PATH_REMOVE_DIRECTORY: 0x2000000, + PATH_UNLINK_FILE: 0x4000000, + POLL_FD_READWRITE: 0x8000000, + SOCK_SHUTDOWN: 0x10000000, + SOCK_ACCEPT: 0x20000000, + } + + const Lookupflags = { + SYMLINK_FOLLOW: 0x1, + } + + const Oflags = { + CREAT: 0x1, + DIRECTORY: 0x2, + EXCL: 0x4, + TRUNC: 0x8, + } + + function open(path, flags = "r") { + if (path == null) { + throw new Error("Open error: Path is required"); + } + let dirfd_rs = dirfdForPath(path); + let dirfd = dirfd_rs.fd; + let fd_lookup_flags = Lookupflags.SYMLINK_FOLLOW;; + let fd_oflags = 0; + let fd_rights = 0; + if (flags == "r") { + fd_rights = + Rights.FD_READ | Rights.FD_SEEK | Rights.FD_TELL | Rights.FD_FILESTAT_GET | + Rights.FD_READDIR; + } else if (flags == "r+") { + fd_rights = + Rights.FD_WRITE | + Rights.FD_READ | Rights.FD_SEEK | Rights.FD_TELL | Rights.FD_FILESTAT_GET | + Rights.PATH_CREATE_FILE; + } else if (flags == "w") { + fd_oflags = Oflags.CREAT | Oflags.TRUNC; + fd_rights = + Rights.FD_WRITE | Rights.FD_SEEK | Rights.FD_TELL | Rights.FD_FILESTAT_GET | + Rights.PATH_CREATE_FILE; + } else if (flags == "wx") { + fd_oflags = Oflags.CREAT | Oflags.TRUNC | Oflags.EXCL; + fd_rights = + Rights.FD_WRITE | Rights.FD_SEEK | Rights.FD_TELL | Rights.FD_FILESTAT_GET | + Rights.PATH_CREATE_FILE; + } else if (flags == "w+") { + fd_oflags = Oflags.CREAT | Oflags.TRUNC; + fd_rights = + Rights.FD_WRITE | + Rights.FD_READ | Rights.FD_SEEK | Rights.FD_TELL | Rights.FD_FILESTAT_GET | + Rights.PATH_CREATE_FILE; + } else if (flags == "xw+") { + fd_oflags = Oflags.CREAT | Oflags.TRUNC | Oflags.EXCL; + fd_rights = + Rights.FD_WRITE | + Rights.FD_READ | Rights.FD_SEEK | Rights.FD_TELL | Rights.FD_FILESTAT_GET | + Rights.PATH_CREATE_FILE; + } else { + return null; + } + path = path.substring(dir_name_rs.dir_name.length, path.length); + let fd_rights_inherited = fd_rights; + let fd_flags = 0; + let rs = __javy_wasi_preview1_open( + dirfd, + fd_lookup_flags, + path, + fd_oflags, + fd_rights, + fd_rights_inherited, + fd_flags, + ) + if (rs < 0) { + throw new Error("Open error: " + rs); + } + return rs; + } + + function dirfdForPath(path, fd = 3) { + let dir_name_rs = __javy_wasi_preview1_fd_prestat_dir_name(fd); + if (dir_name_rs.code == 0) { + if (path.startsWith(dir_name_rs.dir_name)) { + dir_name_rs.fd = fd; + return dir_name_rs; + } else { + return dirfdForPath(path, fd + 1); + } + } else { + throw new Error("wasi_preview1_fd_prestat_dir_name error: " + dir_name_rs.code); + } + return null; + } + + globalThis.wasi_preview1 = function () { + return { + open, + }; + }(); + + // Delete the function from `globalThis` so it doesn't leak. + Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_open"); + + Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_fd_prestat_dir_name"); +})(); \ No newline at end of file diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs new file mode 100644 index 0000000..ccd886c --- /dev/null +++ b/src/wasi/preview_1.rs @@ -0,0 +1,42 @@ +#[link(wasm_import_module = "wasi_snapshot_preview1")] +unsafe extern "C" { + #[link_name = "path_open"] + pub unsafe fn path_open( + dirfd: i32, + dirflags: i32, + path: i32, + path_len: i32, + oflags: i32, + fs_rights_base: i64, + _fs_rights_inheriting: i64, + fdflags: i32, + fd_ptr: i32, + ) -> i32; + + #[link_name = "fd_close"] + pub unsafe fn fd_close( + fd: i32, + ) -> i32; + + #[link_name = "fd_read"] + pub unsafe fn fd_read( + fd: i32, + iovs: i32, + iovs_len: i32, + nread: i32, + ) -> i32; + + #[link_name = "fd_prestat_get"] + pub unsafe fn fd_prestat_get( + fd: i32, + path_len: i32, + ) -> i32; + + #[link_name = "fd_prestat_dir_name"] + pub unsafe fn fd_prestat_dir_name( + fd: i32, + path_ptr: i32, + path_len: i32, + ) -> i32; + +} From 1b497fd8ed735377cb7f2e20dc68975597ebf2e4 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Mon, 19 May 2025 22:42:33 +0800 Subject: [PATCH 02/45] wasi open api implements --- src/wasi/mod.rs | 10 ++++++---- src/wasi/preview_1.js | 13 ++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/wasi/mod.rs b/src/wasi/mod.rs index e563219..a4f969a 100644 --- a/src/wasi/mod.rs +++ b/src/wasi/mod.rs @@ -112,11 +112,13 @@ pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { args.len() ); }; - let mut path_len: i32 = 0; + let mut path_len_buf = [0u8; 8]; let fd = fd.as_int() .ok_or_else(|| anyhow!("fd must be a number"))?; - let path_len_ptr = (&mut path_len as *mut i32) as i32; + let path_len_ptr: i32 = path_len_buf.as_mut_ptr() as i32; let rs = unsafe { preview_1::fd_prestat_get(fd, path_len_ptr) }; + let path_len_buf: [u8; 4] = path_len_buf[4..].try_into()?; + let path_len = i32::from_le_bytes(path_len_buf); let obj = JObject::new(cx)?; if rs != 0 { obj.set("code", -rs)?; @@ -126,8 +128,8 @@ pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { let rs = unsafe { preview_1::fd_prestat_dir_name( fd, - path_buf.as_mut_ptr() as i32, - path_len + path_buf.as_mut_ptr() as *const i32 as i32, + path_len as _ ) }; obj.set("code", -rs)?; diff --git a/src/wasi/preview_1.js b/src/wasi/preview_1.js index bef56e8..12ca697 100644 --- a/src/wasi/preview_1.js +++ b/src/wasi/preview_1.js @@ -48,12 +48,14 @@ TRUNC: 0x8, } + // This function is used to open a file with the specified path and flags. + // It first checks if the path is valid and then determines the directory file descriptor (dirfd) for the path. + // It sets the appropriate flags and rights based on the specified mode (read, write, etc.). function open(path, flags = "r") { if (path == null) { throw new Error("Open error: Path is required"); } - let dirfd_rs = dirfdForPath(path); - let dirfd = dirfd_rs.fd; + let {dirpath, dirfd} = dirfdForPath(path); let fd_lookup_flags = Lookupflags.SYMLINK_FOLLOW;; let fd_oflags = 0; let fd_rights = 0; @@ -91,7 +93,7 @@ } else { return null; } - path = path.substring(dir_name_rs.dir_name.length, path.length); + path = path.substring(dirpath.length, path.length); let fd_rights_inherited = fd_rights; let fd_flags = 0; let rs = __javy_wasi_preview1_open( @@ -109,19 +111,20 @@ return rs; } + // This function is used to get the directory name for a given file descriptor. + // It recursively calls itself with an incremented file descriptor until it finds a valid directory name. function dirfdForPath(path, fd = 3) { let dir_name_rs = __javy_wasi_preview1_fd_prestat_dir_name(fd); if (dir_name_rs.code == 0) { if (path.startsWith(dir_name_rs.dir_name)) { dir_name_rs.fd = fd; - return dir_name_rs; + return {dirpath: dir_name_rs.dir_name, dirfd: fd}; } else { return dirfdForPath(path, fd + 1); } } else { throw new Error("wasi_preview1_fd_prestat_dir_name error: " + dir_name_rs.code); } - return null; } globalThis.wasi_preview1 = function () { From a8ec53d7e3dd45ef750f8d6cf0b403b545951e69 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Tue, 20 May 2025 10:47:54 +0800 Subject: [PATCH 03/45] support error message. --- Cargo.lock | 21 +++++++++++++++++++++ Cargo.toml | 1 + src/lib.rs | 6 +++--- src/wasi/mod.rs | 36 ++++++++++++++++++++++++++---------- src/wasi/preview_1.js | 16 ++++++++-------- 5 files changed, 59 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b993cb..9e8de65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,6 +303,7 @@ dependencies = [ "rand", "serde", "serde_json", + "thiserror", ] [[package]] @@ -737,6 +738,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "toml_datetime" version = "0.6.8" diff --git a/Cargo.toml b/Cargo.toml index c606098..0dc67b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ javy-plugin-api = { version = "3.0.0", features = ["json"] } rand = "0.8.5" serde_json = "1.0.120" serde = { version = "1.0.215", features = ["derive"] } +thiserror = "2.0.12" [profile.release] lto = true diff --git a/src/lib.rs b/src/lib.rs index 1aeaaf2..7987f7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,7 +67,7 @@ pub extern "C" fn initialize_runtime() { #[cfg(feature = "wasi")] { macro_rules! bind { - ($l: ident) => { + (function, $l: ident) => { let name = concat!("__javy_", stringify!($l)); ctx.globals().set( name, @@ -82,8 +82,8 @@ pub extern "C" fn initialize_runtime() { )?; }; } - bind!(wasi_preview1_open); - bind!(wasi_preview1_fd_prestat_dir_name); + bind!(function, wasi_preview1_open); + bind!(function, wasi_preview1_fd_prestat_dir_name); } #[cfg(feature = "llm")] diff --git a/src/wasi/mod.rs b/src/wasi/mod.rs index a4f969a..0cfd6b6 100644 --- a/src/wasi/mod.rs +++ b/src/wasi/mod.rs @@ -1,3 +1,4 @@ +use error::WasiError; use javy_plugin_api::javy::{ quickjs::{Object as JObject, Value}, Args @@ -5,6 +6,8 @@ use javy_plugin_api::javy::{ use anyhow::{anyhow, bail, Result}; mod preview_1; +mod error; + pub fn wasi_preview1_open<'a>(args: Args<'a>) -> Result> { let (cx, args) = args.release(); @@ -60,7 +63,7 @@ pub fn wasi_preview1_open<'a>(args: Args<'a>) -> Result> { .map_err(|_| anyhow!("invalid UTF-8 in path"))?; let path_ptr = path.as_ptr() as i32; let path_len = path.len() as i32; - let mut rs = unsafe { + let rs = unsafe { preview_1::path_open( dirfd, fd_lookup_flags, @@ -73,13 +76,23 @@ pub fn wasi_preview1_open<'a>(args: Args<'a>) -> Result> { opened_fd_ptr) }; - if rs == 0 { - rs = opened_fd; + let rs_obj = JObject::new(cx.clone())?; + rs_obj.set("fd", opened_fd)?; + set_error(&rs_obj, rs)?; + Ok(Value::from_object(rs_obj)) +} + +#[inline] +fn set_error(obj: &JObject, rs: i32) -> Result<()> { + let error_messgae = if rs != 0 { + let error: WasiError = rs.into(); + error.to_string() } else { - rs = -rs; - } - - Ok(Value::new_int(cx, rs)) + "Success".to_string() + }; + obj.set("errno", rs)?; + obj.set("error", error_messgae)?; + Ok(()) } pub fn wasi_preview1_close<'a>(args: Args<'a>) -> Result> { @@ -97,7 +110,9 @@ pub fn wasi_preview1_close<'a>(args: Args<'a>) -> Result> { let fd = fd.as_int() .ok_or_else(|| anyhow!("fd must be a number"))?; let rs = unsafe { preview_1::fd_close(fd) }; - Ok(Value::new_int(cx, -rs)) + let rs_obj = JObject::new(cx.clone())?; + set_error(&rs_obj, rs)?; + Ok(Value::from_object(rs_obj)) } pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { @@ -121,7 +136,7 @@ pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { let path_len = i32::from_le_bytes(path_len_buf); let obj = JObject::new(cx)?; if rs != 0 { - obj.set("code", -rs)?; + set_error(&obj, rs)?; return Ok(Value::from_object(obj)) } let mut path_buf = vec![0u8; path_len as usize]; @@ -132,11 +147,12 @@ pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { path_len as _ ) }; - obj.set("code", -rs)?; if rs == 0 { let path = String::from_utf8(path_buf)?; obj.set("dir_name", path)?; } + set_error(&obj, rs)?; + println!("fd_prestat_dir_name -"); Ok(Value::from_object(obj)) } diff --git a/src/wasi/preview_1.js b/src/wasi/preview_1.js index 12ca697..ec60699 100644 --- a/src/wasi/preview_1.js +++ b/src/wasi/preview_1.js @@ -105,8 +105,8 @@ fd_rights_inherited, fd_flags, ) - if (rs < 0) { - throw new Error("Open error: " + rs); + if (rs.errno != 0) { + throw new Error("Open error: " + rs.error); } return rs; } @@ -114,16 +114,16 @@ // This function is used to get the directory name for a given file descriptor. // It recursively calls itself with an incremented file descriptor until it finds a valid directory name. function dirfdForPath(path, fd = 3) { - let dir_name_rs = __javy_wasi_preview1_fd_prestat_dir_name(fd); - if (dir_name_rs.code == 0) { - if (path.startsWith(dir_name_rs.dir_name)) { - dir_name_rs.fd = fd; - return {dirpath: dir_name_rs.dir_name, dirfd: fd}; + let rs = __javy_wasi_preview1_fd_prestat_dir_name(fd); + if (rs.errno == 0) { + if (path.startsWith(rs.dir_name)) { + rs.fd = fd; + return {dirpath: rs.dir_name, dirfd: fd}; } else { return dirfdForPath(path, fd + 1); } } else { - throw new Error("wasi_preview1_fd_prestat_dir_name error: " + dir_name_rs.code); + throw new Error("wasi_preview1_fd_prestat_dir_name error: " + rs.error); } } From de3476a7f24af1bfe0265a3f5fca4e387b4b8e2f Mon Sep 17 00:00:00 2001 From: Joinhack Date: Tue, 20 May 2025 13:47:20 +0800 Subject: [PATCH 04/45] error message. --- src/wasi/error.rs | 242 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 src/wasi/error.rs diff --git a/src/wasi/error.rs b/src/wasi/error.rs new file mode 100644 index 0000000..353cdf5 --- /dev/null +++ b/src/wasi/error.rs @@ -0,0 +1,242 @@ +use thiserror::Error; + + +#[derive(Error, Debug)] +pub enum WasiError { + #[error("Argument list too long.")] + TooBig = 1, + #[error("Permission denied.")] + Access, + #[error("Address not available.")] + AddrNotUse, + #[error("Address not available.")] + AddrNotAvail, + #[error("Address family not supported..")] + AfNoSupport, + #[error("Resource unavailable, or operation would block.")] + Again, + #[error("Connection already in progress.")] + Already, + #[error("Bad file descriptor.")] + Badf, + #[error("Bad message.")] + Badmsg, + #[error("Device or resource busy.")] + Busy, + #[error("Operation canceled.")] + Canceled, + #[error("No child processes.")] + Child, + #[error("Connection aborted.")] + Connaborted, + #[error("Connection refused.")] + ConnRefused, + #[error("Connection reset.")] + ConnReset, + #[error("Resource deadlock would occur.")] + Deadlk, + #[error("Destination address required.")] + Destaddrreq, + #[error("Mathematics argument out of domain of function.")] + Dom, + #[error("Reserved.")] + Dquot, + #[error("File exists.")] + Exist, + #[error("Bad address.")] + Fault, + #[error("File too large.")] + Fbig, + #[error("Host is unreachable.")] + Hostunreach, + #[error("Identifier removed.")] + Idrm, + #[error("Illegal byte sequence.")] + Ilseq, + #[error("Operation in progress.")] + Inprogress, + #[error("Interrupted function.")] + Intr, + #[error("Invalid argument.")] + Inval, + #[error("I/O error.")] + Io, + #[error("Socket is connected.")] + Isconn, + #[error("Is a directory.")] + Isdir, + #[error("Too many levels of symbolic links.")] + Loop, + #[error("File descriptor value too large.")] + Mfile, + #[error("Too many links.")] + Mlink, + #[error("Message too large.")] + Msgsize, + #[error("Reserved.")] + Multihop, + #[error("Filename too long.")] + Nametoolong, + #[error("Network is down.")] + Netdown, + #[error("Connection aborted by network.")] + Netreset, + #[error("Network unreachable.")] + Netunreach, + #[error("Too many files open in system.")] + Nfile, + #[error("No buffer space available.")] + Nobufs, + #[error("No such device.")] + Nodev, + #[error("No such file or directory.")] + Noent, + #[error("Executable file format error.")] + Noexec, + #[error("No locks available.")] + Nolck, + #[error("Reserved.")] + Nolink, + #[error("Not enough space.")] + Nomem, + #[error("No message of the desired type.")] + Nomsg, + #[error("Protocol not available.")] + Noprotoopt, + #[error("No space left on device.")] + Nospc, + #[error("Function not supported.")] + Nosys, + #[error("The socket is not connected.")] + Notconn, + #[error("Not a directory or a symbolic link to a directory.")] + Notdir, + #[error("Directory not empty.")] + Notempty, + #[error("State not recoverable.")] + Notrecoverable, + #[error("Not a socket.")] + Notsock, + #[error("Not supported, or operation not supported on socket.")] + Notsup, + #[error("Inappropriate I/O control operation.")] + Notty, + #[error("No such device or address.")] + Nxio, + #[error("Value too large to be stored in data type.")] + Overflow, + #[error("Previous owner died.")] + Ownerdead, + #[error("Operation not permitted.")] + Perm, + #[error("Broken pipe.")] + Pipe, + #[error("Protocol error.")] + Proto, + #[error("Protocol not supported.")] + Protonosupport, + #[error("Protocol wrong type for socket.")] + Prototype, + #[error("Result too large.")] + Range, + #[error("Read-only file system.")] + Rofs, + #[error("Invalid seek.")] + Spipe, + #[error("No such process.")] + Srch, + #[error("Reserved.")] + Stale, + #[error("Connection timed out.")] + Timedout, + #[error("Text file busy.")] + Txtbsy, + #[error("Cross-device link.")] + Xdev, + #[error("Extension: Capabilities insufficient.")] + Notcapable, +} + +impl From for WasiError { + fn from(code: i32) -> Self { + match code { + 1 => WasiError::TooBig, + 2 => WasiError::Access, + 3 => WasiError::AddrNotUse, + 4 => WasiError::AddrNotAvail, + 5 => WasiError::AfNoSupport, + 6 => WasiError::Again, + 7 => WasiError::Already, + 8 => WasiError::Badf, + 9 => WasiError::Badmsg, + 10 => WasiError::Busy, + 11 => WasiError::Canceled, + 12 => WasiError::Child, + 13 => WasiError::Connaborted, + 14 => WasiError::ConnRefused, + 15 => WasiError::ConnReset, + 16 => WasiError::Deadlk, + 17 => WasiError::Destaddrreq, + 18 => WasiError::Dom, + 19 => WasiError::Dquot, + 20 => WasiError::Exist, + 21 => WasiError::Fault, + 22 => WasiError::Fbig, + 23 => WasiError::Hostunreach, + 24 => WasiError::Idrm, + 25 => WasiError::Ilseq, + 26 => WasiError::Inprogress, + 27 => WasiError::Intr, + 28 => WasiError::Inval, + 29 => WasiError::Io, + 30 => WasiError::Isconn, + 31 => WasiError::Isdir, + 32 => WasiError::Loop, + 33 => WasiError::Mfile, + 34 => WasiError::Mlink, + 35 => WasiError::Msgsize, + 36 => WasiError::Multihop, + 37 => WasiError::Nametoolong, + 38 => WasiError::Netdown, + 39 => WasiError::Netreset, + 40 => WasiError::Netunreach, + 41 => WasiError::Nfile, + 42 => WasiError::Nobufs, + 43 => WasiError::Nodev, + 44 => WasiError::Noent, + 45 => WasiError::Noexec, + 46 => WasiError::Nolck, + 47 => WasiError::Nolink, + 48 => WasiError::Nomem, + 49 => WasiError::Nomsg, + 50 => WasiError::Noprotoopt, + 51 => WasiError::Nospc, + 52 => WasiError::Nosys, + 53 => WasiError::Notconn, + 54 => WasiError::Notdir, + 55 => WasiError::Notempty, + 56 => WasiError::Notrecoverable, + 57 => WasiError::Notsock, + 58 => WasiError::Notsup, + 59 => WasiError::Notty, + 60 => WasiError::Nxio, + 61 => WasiError::Overflow, + 62 => WasiError::Ownerdead, + 63 => WasiError::Perm, + 64 => WasiError::Pipe, + 65 => WasiError::Proto, + 66 => WasiError::Protonosupport, + 67 => WasiError::Prototype, + 68 => WasiError::Range, + 69 => WasiError::Rofs, + 70 => WasiError::Spipe, + 71 => WasiError::Srch, + 72 => WasiError::Stale, + 73 => WasiError::Timedout, + 74 => WasiError::Txtbsy, + 75 => WasiError::Xdev, + 76 => WasiError::Notcapable, + _ => unimplemented!("WasiError code: {}", code), + } + } +} From 8b5916aaba7328d32fad131bf33a5ddc3ecadc71 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Tue, 20 May 2025 14:22:42 +0800 Subject: [PATCH 05/45] implement the mkdir rmdir unlink. --- src/lib.rs | 3 ++ src/wasi/mod.rs | 100 ++++++++++++++++++++++++++++++++++++++- src/wasi/preview_1.js | 106 ++++++++++++++++++++++++++++++++++++++---- src/wasi/preview_1.rs | 41 ++++++++++++---- 4 files changed, 231 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7987f7b..7d2d976 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,6 +84,9 @@ pub extern "C" fn initialize_runtime() { } bind!(function, wasi_preview1_open); bind!(function, wasi_preview1_fd_prestat_dir_name); + bind!(function, wasi_preview1_path_create_directory); + bind!(function, wasi_preview1_path_remove_directory); + bind!(function, wasi_preview1_path_unlink_file); } #[cfg(feature = "llm")] diff --git a/src/wasi/mod.rs b/src/wasi/mod.rs index 0cfd6b6..8833917 100644 --- a/src/wasi/mod.rs +++ b/src/wasi/mod.rs @@ -8,7 +8,8 @@ use anyhow::{anyhow, bail, Result}; mod preview_1; mod error; - +/// This function is used to open a file at the given path. +/// It is used to open a file at the given path. pub fn wasi_preview1_open<'a>(args: Args<'a>) -> Result> { let (cx, args) = args.release(); let args_pat: &[Value<'_>]= &args.0; @@ -95,6 +96,7 @@ fn set_error(obj: &JObject, rs: i32) -> Result<()> { Ok(()) } +/// This function is used to close a file descriptor. pub fn wasi_preview1_close<'a>(args: Args<'a>) -> Result> { let (cx, args) = args.release(); let args_pat: &[Value<'_>]= &args.0; @@ -115,6 +117,101 @@ pub fn wasi_preview1_close<'a>(args: Args<'a>) -> Result> { Ok(Value::from_object(rs_obj)) } +/// This function is used to create a directory at the given path. +/// It is used to create a directory at the given path. +/// The directory must not exist. +pub fn wasi_preview1_path_create_directory(args: Args<'_>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let [ + dirfd, + path, + .. + ] = args_pat else { + bail!( + "path_create_directory expects 2 parameters: the dirfd and path, Got: {} parameters.", + args.len() + ); + }; + let dirfd = dirfd.as_int() + .ok_or_else(|| anyhow!("dirfd must be a number"))?; + let path = path.as_string() + .ok_or_else(|| anyhow!("path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + let path_ptr = path.as_ptr() as i32; + let path_len = path.len() as i32; + let rs = unsafe { preview_1::path_create_directory(dirfd, path_ptr, path_len) }; + let rs_obj = JObject::new(cx.clone())?; + set_error(&rs_obj, rs)?; + Ok(Value::from_object(rs_obj)) +} + +/// Remove a directory at the given path. +/// This function is used to remove a directory at the given path. +/// It is used to remove a directory at the given path. +pub fn wasi_preview1_path_remove_directory(args: Args<'_>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let [ + dirfd, + path, + .. + ] = args_pat else { + bail!( + "path_remove_directory expects 2 parameters: the dirfd and path, Got: {} parameters.", + args.len() + ); + }; + let dirfd = dirfd.as_int() + .ok_or_else(|| anyhow!("dirfd must be a number"))?; + let path = path.as_string() + .ok_or_else(|| anyhow!("path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + let path_ptr = path.as_ptr() as i32; + let path_len = path.len() as i32; + let rs = unsafe { preview_1::path_remove_directory(dirfd, path_ptr, path_len) }; + let rs_obj = JObject::new(cx.clone())?; + set_error(&rs_obj, rs)?; + Ok(Value::from_object(rs_obj)) +} + +/// Unlink a file at the given path. +pub fn wasi_preview1_path_unlink_file(args: Args<'_>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let [ + dirfd, + path, + .. + ] = args_pat else { + bail!( + "path_unlink_file expects 2 parameters: the dirfd and path, Got: {} parameters.", + args.len() + ); + }; + + // dirfd is the file descriptor of the directory + let dirfd = dirfd.as_int() + .ok_or_else(|| anyhow!("dirfd must be a number"))?; + // path is the path to the file + let path = path.as_string() + .ok_or_else(|| anyhow!("path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + let path_ptr = path.as_ptr() as i32; + let path_len = path.len() as i32; + let rs = unsafe { preview_1::path_unlink_file(dirfd, path_ptr, path_len) }; + let rs_obj = JObject::new(cx.clone())?; + set_error(&rs_obj, rs)?; + Ok(Value::from_object(rs_obj)) +} + + +/// This function is used to get the directory name of a file descriptor. +/// It is used to get the directory name of a file descriptor. +/// The file descriptor must be a directory. pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { let (cx, args) = args.release(); let args_pat: &[Value<'_>]= &args.0; @@ -152,7 +249,6 @@ pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { obj.set("dir_name", path)?; } set_error(&obj, rs)?; - println!("fd_prestat_dir_name -"); Ok(Value::from_object(obj)) } diff --git a/src/wasi/preview_1.js b/src/wasi/preview_1.js index ec60699..3ad50cc 100644 --- a/src/wasi/preview_1.js +++ b/src/wasi/preview_1.js @@ -1,9 +1,19 @@ // Wrap everything in an anonymous function to avoid leaking local variables into the global scope. (function () { + + let lastErr = { + errno: 0, + error: "", + } // Get a reference to the function before we delete it from `globalThis`. const __javy_wasi_preview1_open = globalThis.__javy_wasi_preview1_open; const __javy_wasi_preview1_fd_prestat_dir_name = globalThis.__javy_wasi_preview1_fd_prestat_dir_name; + const __javy_wasi_preview1_path_create_directory = globalThis.__javy_wasi_preview1_path_create_directory; + const __javy_wasi_preview1_path_remove_directory = globalThis.__javy_wasi_preview1_path_remove_directory; + const __javy_wasi_preview1_path_unlink_file = globalThis.__javy_wasi_preview1_path_unlink_file; + + const InvalParameter = 0x1C const Rights = { FD_DATASYNC: 0x1, FD_READ: 0x2, @@ -53,9 +63,15 @@ // It sets the appropriate flags and rights based on the specified mode (read, write, etc.). function open(path, flags = "r") { if (path == null) { - throw new Error("Open error: Path is required"); + lastErr.errno = InvalParameter; + lastErr.error = "Path is required"; + return null; + } + const dirpathObj = dirfdForPath(path); + if (dirpathObj == null) { + return false; } - let {dirpath, dirfd} = dirfdForPath(path); + const {dirpath, dirfd} = dirpathObj; let fd_lookup_flags = Lookupflags.SYMLINK_FOLLOW;; let fd_oflags = 0; let fd_rights = 0; @@ -96,7 +112,7 @@ path = path.substring(dirpath.length, path.length); let fd_rights_inherited = fd_rights; let fd_flags = 0; - let rs = __javy_wasi_preview1_open( + const {errno, fd, error} = __javy_wasi_preview1_open( dirfd, fd_lookup_flags, path, @@ -105,10 +121,74 @@ fd_rights_inherited, fd_flags, ) - if (rs.errno != 0) { - throw new Error("Open error: " + rs.error); + lastErr = {errno, error} + if (errno != 0) { + return + } + return fd; + } + + // This function is used to create a new directory with the specified path. + // It first checks if the path is valid and then determines the directory file descriptor (dirfd) for the path. + function mkdir(path) { + if (path == null) { + lastErr.errno = InvalParameter; + lastErr.error = "Path is required"; + return false; + } + const dirpathObj = dirfdForPath(path); + if (dirpathObj == null) { + return false; + } + const {dirpath, dirfd} = dirpathObj; + path = path.substring(dirpath.length, path.length); + lastErr = __javy_wasi_preview1_path_create_directory(dirfd, path) + if (lastErr.errno != 0) { + return false; + } + return true; + } + + // This function is used to remove a directory with the specified path. + // It first checks if the path is valid and then determines the directory file descriptor (dirfd) for the path. + function rmdir(path) { + if (path == null) { + lastErr.errno = InvalParameter; + lastErr.error = "Path is required"; + return false; + } + const dirpathObj = dirfdForPath(path); + if (dirpathObj == null) { + return false; + } + const {dirpath, dirfd} = dirpathObj; + path = path.substring(dirpath.length, path.length); + lastErr = __javy_wasi_preview1_path_remove_directory(dirfd, path) + if (lastErr.errno != 0) { + return false; } - return rs; + return true; + } + + // This function is used to unlink (delete) a file with the specified path. + // It first checks if the path is valid and then determines the directory file descriptor (dirfd) for the path. + function unlink(path) { + if (path == null) { + lastErr.errno = InvalParameter; + lastErr.error = "Path is required"; + return false; + } + const dirpathObj = dirfdForPath(path); + if (dirpathObj == null) { + return false; + } + const {dirpath, dirfd} = dirpathObj; + path = path.substring(dirpath.length, path.length); + lastErr = __javy_wasi_preview1_path_unlink_file(dirfd, path) + if (lastErr.errno != 0) { + return false; + } + return true; } // This function is used to get the directory name for a given file descriptor. @@ -123,18 +203,26 @@ return dirfdForPath(path, fd + 1); } } else { - throw new Error("wasi_preview1_fd_prestat_dir_name error: " + rs.error); + lastErr = rs + return null; } } - globalThis.wasi_preview1 = function () { + globalThis.wasi_fs = function () { return { open, + mkdir, + rmdir, + unlink, + errno: () => lastErr.errno, + error: () => lastErr.error, }; }(); // Delete the function from `globalThis` so it doesn't leak. Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_open"); - Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_fd_prestat_dir_name"); + Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_path_create_directory"); + Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_path_remove_directory"); + Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_path_unlink_file"); })(); \ No newline at end of file diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index ccd886c..74a80bd 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -13,19 +13,42 @@ unsafe extern "C" { fd_ptr: i32, ) -> i32; + #[link_name = "path_create_directory"] + pub unsafe fn path_create_directory( + dirfd: i32, + path_ptr: i32, + path_len: i32, + ) -> i32; + + #[link_name = "path_remove_directory"] + pub unsafe fn path_remove_directory( + dirfd: i32, + path_ptr: i32, + path_len: i32, + ) -> i32; + + + #[link_name = "path_unlink_file"] + pub unsafe fn path_unlink_file( + dirfd: i32, + path_ptr: i32, + path_len: i32, + ) -> i32; + + #[link_name = "path_symlink"] + pub unsafe fn path_symlink( + old_path_ptr: i32, + old_path_len: i32, + dirfd: i32, + new_path_ptr: i32, + new_path_len: i32, + ) -> i32; + #[link_name = "fd_close"] pub unsafe fn fd_close( fd: i32, ) -> i32; - #[link_name = "fd_read"] - pub unsafe fn fd_read( - fd: i32, - iovs: i32, - iovs_len: i32, - nread: i32, - ) -> i32; - #[link_name = "fd_prestat_get"] pub unsafe fn fd_prestat_get( fd: i32, @@ -39,4 +62,6 @@ unsafe extern "C" { path_len: i32, ) -> i32; + + } From f398563cb2d7bf4faf1042e8f1b5dfed24bd079e Mon Sep 17 00:00:00 2001 From: Joinhack Date: Tue, 20 May 2025 14:37:07 +0800 Subject: [PATCH 06/45] close support --- src/wasi/close.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/wasi/close.rs diff --git a/src/wasi/close.rs b/src/wasi/close.rs new file mode 100644 index 0000000..c68a9c4 --- /dev/null +++ b/src/wasi/close.rs @@ -0,0 +1,28 @@ +use javy_plugin_api::javy::{ + quickjs::{Object as JObject, Value}, + Args +}; +use anyhow::{anyhow, bail, Result}; + +use super::{preview_1, set_error}; + +/// This function is used to close a file descriptor. +pub fn wasi_preview1_close<'a>(args: Args<'a>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let [ + fd, + .. + ] = args_pat else { + bail!( + "close expects 1 parameter: the fd, Got: {} parameters.", + args.len() + ); + }; + let fd = fd.as_int() + .ok_or_else(|| anyhow!("fd must be a number"))?; + let rs = unsafe { preview_1::fd_close(fd) }; + let rs_obj = JObject::new(cx.clone())?; + set_error(&rs_obj, rs)?; + Ok(Value::from_object(rs_obj)) +} \ No newline at end of file From 202f277289049ad0dcf6869cb60f2ead3810946b Mon Sep 17 00:00:00 2001 From: Joinhack Date: Tue, 20 May 2025 14:37:18 +0800 Subject: [PATCH 07/45] mkdir support --- src/wasi/mkdir.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/wasi/mkdir.rs diff --git a/src/wasi/mkdir.rs b/src/wasi/mkdir.rs new file mode 100644 index 0000000..feec90a --- /dev/null +++ b/src/wasi/mkdir.rs @@ -0,0 +1,37 @@ +use javy_plugin_api::javy::{ + quickjs::{Object as JObject, Value}, + Args +}; +use anyhow::{anyhow, bail, Result}; + +use super::{preview_1, set_error}; + +/// This function is used to create a directory at the given path. +/// It is used to create a directory at the given path. +/// The directory must not exist. +pub fn wasi_preview1_path_create_directory(args: Args<'_>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let [ + dirfd, + path, + .. + ] = args_pat else { + bail!( + "path_create_directory expects 2 parameters: the dirfd and path, Got: {} parameters.", + args.len() + ); + }; + let dirfd = dirfd.as_int() + .ok_or_else(|| anyhow!("dirfd must be a number"))?; + let path = path.as_string() + .ok_or_else(|| anyhow!("path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + let path_ptr = path.as_ptr() as i32; + let path_len = path.len() as i32; + let rs = unsafe { preview_1::path_create_directory(dirfd, path_ptr, path_len) }; + let rs_obj = JObject::new(cx.clone())?; + set_error(&rs_obj, rs)?; + Ok(Value::from_object(rs_obj)) +} \ No newline at end of file From f9b716569c3b972c74840b0baec1d8524ceb040b Mon Sep 17 00:00:00 2001 From: Joinhack Date: Tue, 20 May 2025 14:37:26 +0800 Subject: [PATCH 08/45] open support --- src/wasi/open.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/wasi/open.rs diff --git a/src/wasi/open.rs b/src/wasi/open.rs new file mode 100644 index 0000000..8df989c --- /dev/null +++ b/src/wasi/open.rs @@ -0,0 +1,82 @@ +use javy_plugin_api::javy::{ + quickjs::{Object as JObject, Value}, + Args +}; +use anyhow::{anyhow, bail, Result}; + +use super::{preview_1, set_error}; + +/// This function is used to open a file at the given path. +/// It is used to open a file at the given path. +pub fn wasi_preview1_open<'a>(args: Args<'a>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let mut opened_fd: i32 = 0; + let [ + dirfd, + fd_lookup_flags, + path, + fd_oflags, + fd_rights, + fd_rights_inherited, + fd_flags, + .. + ] = args_pat else { + bail!( + "open expects 7 parameters: the path and the dirfd, fd_lookup_flags, path, fd_oflags, fd_rights ... Got: {} parameters.", + args.len() + ); + }; + let dirfd = dirfd.as_int() + .ok_or_else(|| anyhow!("dirfd must be a number"))?; + let fd_lookup_flags = fd_lookup_flags.as_int() + .ok_or_else(|| anyhow!("fd_lookup_flags must be a number"))?; + let oflags = fd_oflags.as_int() + .ok_or_else(|| anyhow!("oflags must be a number"))?; + let fs_rights_base = if fd_rights.is_int() { + fd_rights.as_int() + .map(|i| i as i64) + .ok_or_else(|| anyhow!("fd_rights must be a number"))? + } else { + fd_rights.as_big_int() + .map(|x| x.clone()) + .ok_or_else(|| anyhow!("fd_rights must be a number"))? + .to_i64()? + }; + let fd_rights_inherited = if fd_rights_inherited.is_int() { + fd_rights_inherited.as_int() + .map(|i| i as i64) + .ok_or_else(|| anyhow!("fd_rights_inherited must be a number"))? + } else { + fd_rights_inherited.as_big_int() + .map(|x| x.clone()) + .ok_or_else(|| anyhow!("fd_rights_inherited must be a number"))? + .to_i64()? + }; + let fdflags = fd_flags.as_int() + .ok_or_else(|| anyhow!("fdflags must be a number"))?; + let opened_fd_ptr = (&mut opened_fd as *mut i32) as i32; + let path = path.as_string() + .ok_or_else(|| anyhow!("path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + let path_ptr = path.as_ptr() as i32; + let path_len = path.len() as i32; + let rs = unsafe { + preview_1::path_open( + dirfd, + fd_lookup_flags, + path_ptr, + path_len, + oflags, + fs_rights_base, + fd_rights_inherited, + fdflags, + opened_fd_ptr) + }; + + let rs_obj = JObject::new(cx.clone())?; + rs_obj.set("fd", opened_fd)?; + set_error(&rs_obj, rs)?; + Ok(Value::from_object(rs_obj)) +} From a467c3f708eaef6ca24e2392a33d4087c9f667f5 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Tue, 20 May 2025 14:37:41 +0800 Subject: [PATCH 09/45] rmdir support --- src/wasi/rmdir.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/wasi/rmdir.rs diff --git a/src/wasi/rmdir.rs b/src/wasi/rmdir.rs new file mode 100644 index 0000000..35b1d4d --- /dev/null +++ b/src/wasi/rmdir.rs @@ -0,0 +1,38 @@ +use javy_plugin_api::javy::{ + quickjs::{Object as JObject, Value}, + Args +}; +use anyhow::{anyhow, bail, Result}; + +use super::{preview_1, set_error}; + + +/// Remove a directory at the given path. +/// This function is used to remove a directory at the given path. +/// It is used to remove a directory at the given path. +pub fn wasi_preview1_path_remove_directory(args: Args<'_>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let [ + dirfd, + path, + .. + ] = args_pat else { + bail!( + "path_remove_directory expects 2 parameters: the dirfd and path, Got: {} parameters.", + args.len() + ); + }; + let dirfd = dirfd.as_int() + .ok_or_else(|| anyhow!("dirfd must be a number"))?; + let path = path.as_string() + .ok_or_else(|| anyhow!("path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + let path_ptr = path.as_ptr() as i32; + let path_len = path.len() as i32; + let rs = unsafe { preview_1::path_remove_directory(dirfd, path_ptr, path_len) }; + let rs_obj = JObject::new(cx.clone())?; + set_error(&rs_obj, rs)?; + Ok(Value::from_object(rs_obj)) +} From dce19e651b33543363d3358da382830b7c7f36a1 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Tue, 20 May 2025 14:38:00 +0800 Subject: [PATCH 10/45] unlink support --- src/wasi/unlink.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/wasi/unlink.rs diff --git a/src/wasi/unlink.rs b/src/wasi/unlink.rs new file mode 100644 index 0000000..98157b2 --- /dev/null +++ b/src/wasi/unlink.rs @@ -0,0 +1,39 @@ +use javy_plugin_api::javy::{ + quickjs::{Object as JObject, Value}, + Args +}; +use anyhow::{anyhow, bail, Result}; + +use super::{preview_1, set_error}; + + +/// Unlink a file at the given path. +pub fn wasi_preview1_path_unlink_file(args: Args<'_>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let [ + dirfd, + path, + .. + ] = args_pat else { + bail!( + "path_unlink_file expects 2 parameters: the dirfd and path, Got: {} parameters.", + args.len() + ); + }; + + // dirfd is the file descriptor of the directory + let dirfd = dirfd.as_int() + .ok_or_else(|| anyhow!("dirfd must be a number"))?; + // path is the path to the file + let path = path.as_string() + .ok_or_else(|| anyhow!("path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + let path_ptr = path.as_ptr() as i32; + let path_len = path.len() as i32; + let rs = unsafe { preview_1::path_unlink_file(dirfd, path_ptr, path_len) }; + let rs_obj = JObject::new(cx.clone())?; + set_error(&rs_obj, rs)?; + Ok(Value::from_object(rs_obj)) +} From 37226e0d533883611fbe549b9823499d36296579 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Tue, 20 May 2025 14:38:30 +0800 Subject: [PATCH 11/45] refactor: use the single file for each api --- src/lib.rs | 1 + src/wasi/mod.rs | 202 ++++-------------------------------------------- 2 files changed, 14 insertions(+), 189 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7d2d976..d030aea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,6 +87,7 @@ pub extern "C" fn initialize_runtime() { bind!(function, wasi_preview1_path_create_directory); bind!(function, wasi_preview1_path_remove_directory); bind!(function, wasi_preview1_path_unlink_file); + bind!(function, wasi_preview1_close); } #[cfg(feature = "llm")] diff --git a/src/wasi/mod.rs b/src/wasi/mod.rs index 8833917..4e71ae0 100644 --- a/src/wasi/mod.rs +++ b/src/wasi/mod.rs @@ -1,4 +1,4 @@ -use error::WasiError; + use javy_plugin_api::javy::{ quickjs::{Object as JObject, Value}, Args @@ -7,84 +7,21 @@ use anyhow::{anyhow, bail, Result}; mod preview_1; mod error; +mod open; +mod mkdir; +mod rmdir; +mod unlink; +mod close; +pub(crate) use open::wasi_preview1_open; +pub(crate) use mkdir::wasi_preview1_path_create_directory; +pub(crate) use rmdir::wasi_preview1_path_remove_directory; +pub(crate) use unlink::wasi_preview1_path_unlink_file; +pub(crate) use close::wasi_preview1_close; +pub use error::WasiError; -/// This function is used to open a file at the given path. -/// It is used to open a file at the given path. -pub fn wasi_preview1_open<'a>(args: Args<'a>) -> Result> { - let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let mut opened_fd: i32 = 0; - let [ - dirfd, - fd_lookup_flags, - path, - fd_oflags, - fd_rights, - fd_rights_inherited, - fd_flags, - .. - ] = args_pat else { - bail!( - "open expects 7 parameters: the path and the dirfd, fd_lookup_flags, path, fd_oflags, fd_rights ... Got: {} parameters.", - args.len() - ); - }; - let dirfd = dirfd.as_int() - .ok_or_else(|| anyhow!("dirfd must be a number"))?; - let fd_lookup_flags = fd_lookup_flags.as_int() - .ok_or_else(|| anyhow!("fd_lookup_flags must be a number"))?; - let oflags = fd_oflags.as_int() - .ok_or_else(|| anyhow!("oflags must be a number"))?; - let fs_rights_base = if fd_rights.is_int() { - fd_rights.as_int() - .map(|i| i as i64) - .ok_or_else(|| anyhow!("fd_rights must be a number"))? - } else { - fd_rights.as_big_int() - .map(|x| x.clone()) - .ok_or_else(|| anyhow!("fd_rights must be a number"))? - .to_i64()? - }; - let fd_rights_inherited = if fd_rights_inherited.is_int() { - fd_rights_inherited.as_int() - .map(|i| i as i64) - .ok_or_else(|| anyhow!("fd_rights_inherited must be a number"))? - } else { - fd_rights_inherited.as_big_int() - .map(|x| x.clone()) - .ok_or_else(|| anyhow!("fd_rights_inherited must be a number"))? - .to_i64()? - }; - let fdflags = fd_flags.as_int() - .ok_or_else(|| anyhow!("fdflags must be a number"))?; - let opened_fd_ptr = (&mut opened_fd as *mut i32) as i32; - let path = path.as_string() - .ok_or_else(|| anyhow!("path must be a string"))? - .to_string() - .map_err(|_| anyhow!("invalid UTF-8 in path"))?; - let path_ptr = path.as_ptr() as i32; - let path_len = path.len() as i32; - let rs = unsafe { - preview_1::path_open( - dirfd, - fd_lookup_flags, - path_ptr, - path_len, - oflags, - fs_rights_base, - fd_rights_inherited, - fdflags, - opened_fd_ptr) - }; - - let rs_obj = JObject::new(cx.clone())?; - rs_obj.set("fd", opened_fd)?; - set_error(&rs_obj, rs)?; - Ok(Value::from_object(rs_obj)) -} #[inline] -fn set_error(obj: &JObject, rs: i32) -> Result<()> { +pub fn set_error(obj: &JObject, rs: i32) -> Result<()> { let error_messgae = if rs != 0 { let error: WasiError = rs.into(); error.to_string() @@ -96,119 +33,6 @@ fn set_error(obj: &JObject, rs: i32) -> Result<()> { Ok(()) } -/// This function is used to close a file descriptor. -pub fn wasi_preview1_close<'a>(args: Args<'a>) -> Result> { - let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let [ - fd, - .. - ] = args_pat else { - bail!( - "close expects 1 parameter: the fd, Got: {} parameters.", - args.len() - ); - }; - let fd = fd.as_int() - .ok_or_else(|| anyhow!("fd must be a number"))?; - let rs = unsafe { preview_1::fd_close(fd) }; - let rs_obj = JObject::new(cx.clone())?; - set_error(&rs_obj, rs)?; - Ok(Value::from_object(rs_obj)) -} - -/// This function is used to create a directory at the given path. -/// It is used to create a directory at the given path. -/// The directory must not exist. -pub fn wasi_preview1_path_create_directory(args: Args<'_>) -> Result> { - let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let [ - dirfd, - path, - .. - ] = args_pat else { - bail!( - "path_create_directory expects 2 parameters: the dirfd and path, Got: {} parameters.", - args.len() - ); - }; - let dirfd = dirfd.as_int() - .ok_or_else(|| anyhow!("dirfd must be a number"))?; - let path = path.as_string() - .ok_or_else(|| anyhow!("path must be a string"))? - .to_string() - .map_err(|_| anyhow!("invalid UTF-8 in path"))?; - let path_ptr = path.as_ptr() as i32; - let path_len = path.len() as i32; - let rs = unsafe { preview_1::path_create_directory(dirfd, path_ptr, path_len) }; - let rs_obj = JObject::new(cx.clone())?; - set_error(&rs_obj, rs)?; - Ok(Value::from_object(rs_obj)) -} - -/// Remove a directory at the given path. -/// This function is used to remove a directory at the given path. -/// It is used to remove a directory at the given path. -pub fn wasi_preview1_path_remove_directory(args: Args<'_>) -> Result> { - let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let [ - dirfd, - path, - .. - ] = args_pat else { - bail!( - "path_remove_directory expects 2 parameters: the dirfd and path, Got: {} parameters.", - args.len() - ); - }; - let dirfd = dirfd.as_int() - .ok_or_else(|| anyhow!("dirfd must be a number"))?; - let path = path.as_string() - .ok_or_else(|| anyhow!("path must be a string"))? - .to_string() - .map_err(|_| anyhow!("invalid UTF-8 in path"))?; - let path_ptr = path.as_ptr() as i32; - let path_len = path.len() as i32; - let rs = unsafe { preview_1::path_remove_directory(dirfd, path_ptr, path_len) }; - let rs_obj = JObject::new(cx.clone())?; - set_error(&rs_obj, rs)?; - Ok(Value::from_object(rs_obj)) -} - -/// Unlink a file at the given path. -pub fn wasi_preview1_path_unlink_file(args: Args<'_>) -> Result> { - let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let [ - dirfd, - path, - .. - ] = args_pat else { - bail!( - "path_unlink_file expects 2 parameters: the dirfd and path, Got: {} parameters.", - args.len() - ); - }; - - // dirfd is the file descriptor of the directory - let dirfd = dirfd.as_int() - .ok_or_else(|| anyhow!("dirfd must be a number"))?; - // path is the path to the file - let path = path.as_string() - .ok_or_else(|| anyhow!("path must be a string"))? - .to_string() - .map_err(|_| anyhow!("invalid UTF-8 in path"))?; - let path_ptr = path.as_ptr() as i32; - let path_len = path.len() as i32; - let rs = unsafe { preview_1::path_unlink_file(dirfd, path_ptr, path_len) }; - let rs_obj = JObject::new(cx.clone())?; - set_error(&rs_obj, rs)?; - Ok(Value::from_object(rs_obj)) -} - - /// This function is used to get the directory name of a file descriptor. /// It is used to get the directory name of a file descriptor. /// The file descriptor must be a directory. From a5519caa0c3ecec175fd172a5555ca1b4b0df189 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Wed, 21 May 2025 22:30:06 +0800 Subject: [PATCH 12/45] refactor:close mkdir open rmdir unlink --- src/lib.rs | 2 ++ src/wasi/close.rs | 4 +-- src/wasi/link.rs | 58 +++++++++++++++++++++++++++++++++++++++++++ src/wasi/mkdir.rs | 4 +-- src/wasi/mod.rs | 10 +++++--- src/wasi/open.rs | 4 +-- src/wasi/preview_1.js | 56 +++++++++++++++++++++++++++++++++++++++++ src/wasi/preview_1.rs | 11 +++++++- src/wasi/rmdir.rs | 4 +-- src/wasi/symlink.rs | 46 ++++++++++++++++++++++++++++++++++ src/wasi/unlink.rs | 4 +-- 11 files changed, 189 insertions(+), 14 deletions(-) create mode 100644 src/wasi/link.rs create mode 100644 src/wasi/symlink.rs diff --git a/src/lib.rs b/src/lib.rs index d030aea..4628835 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,6 +88,8 @@ pub extern "C" fn initialize_runtime() { bind!(function, wasi_preview1_path_remove_directory); bind!(function, wasi_preview1_path_unlink_file); bind!(function, wasi_preview1_close); + bind!(function, wasi_preview1_path_symlink); + bind!(function, wasi_preview1_path_link); } #[cfg(feature = "llm")] diff --git a/src/wasi/close.rs b/src/wasi/close.rs index c68a9c4..fd110fb 100644 --- a/src/wasi/close.rs +++ b/src/wasi/close.rs @@ -4,7 +4,7 @@ use javy_plugin_api::javy::{ }; use anyhow::{anyhow, bail, Result}; -use super::{preview_1, set_error}; +use super::{preview_1, process_error}; /// This function is used to close a file descriptor. pub fn wasi_preview1_close<'a>(args: Args<'a>) -> Result> { @@ -23,6 +23,6 @@ pub fn wasi_preview1_close<'a>(args: Args<'a>) -> Result> { .ok_or_else(|| anyhow!("fd must be a number"))?; let rs = unsafe { preview_1::fd_close(fd) }; let rs_obj = JObject::new(cx.clone())?; - set_error(&rs_obj, rs)?; + process_error(&rs_obj, rs)?; Ok(Value::from_object(rs_obj)) } \ No newline at end of file diff --git a/src/wasi/link.rs b/src/wasi/link.rs new file mode 100644 index 0000000..ab6487e --- /dev/null +++ b/src/wasi/link.rs @@ -0,0 +1,58 @@ +use javy_plugin_api::javy::{ + quickjs::{Object as JObject, Value}, + Args +}; +use anyhow::{anyhow, bail, Result}; + +use super::{preview_1, process_error}; + + +pub fn wasi_preview1_path_link(args: Args<'_>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let [ + old_dirfd, + fd_lookup_flags, + old_path, + new_dirfd, + new_path, + .. + ] = args_pat else { + bail!( + "path_remove_directory expects 5 parameters: the dirfd and path, Got: {} parameters.", + args.len() + ); + }; + let dirfd = old_dirfd.as_int() + .ok_or_else(|| anyhow!("old_dirfd must be a number"))?; + let fd_lookup_flags = fd_lookup_flags.as_int() + .ok_or_else(|| anyhow!("fd_lookup_flags must be a number"))?; + let old_path = old_path.as_string() + .ok_or_else(|| anyhow!("old_path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + let new_dirfd = new_dirfd.as_int() + .ok_or_else(|| anyhow!("new_dirfd must be a number"))?; + let new_path = new_path.as_string() + .ok_or_else(|| anyhow!("new_path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + let new_path_ptr = new_path.as_ptr() as i32; + let new_path_len = new_path.len() as i32; + let old_path_ptr = old_path.as_ptr() as i32; + let old_path_len = old_path.len() as i32; + let rs = unsafe { + preview_1::path_link( + dirfd, + fd_lookup_flags, + old_path_ptr, + old_path_len, + new_dirfd, + new_path_ptr, + new_path_len + ) + }; + let rs_obj = JObject::new(cx.clone())?; + process_error(&rs_obj, rs)?; + Ok(Value::from_object(rs_obj)) +} diff --git a/src/wasi/mkdir.rs b/src/wasi/mkdir.rs index feec90a..cbbadef 100644 --- a/src/wasi/mkdir.rs +++ b/src/wasi/mkdir.rs @@ -4,7 +4,7 @@ use javy_plugin_api::javy::{ }; use anyhow::{anyhow, bail, Result}; -use super::{preview_1, set_error}; +use super::{preview_1, process_error}; /// This function is used to create a directory at the given path. /// It is used to create a directory at the given path. @@ -32,6 +32,6 @@ pub fn wasi_preview1_path_create_directory(args: Args<'_>) -> Result> let path_len = path.len() as i32; let rs = unsafe { preview_1::path_create_directory(dirfd, path_ptr, path_len) }; let rs_obj = JObject::new(cx.clone())?; - set_error(&rs_obj, rs)?; + process_error(&rs_obj, rs)?; Ok(Value::from_object(rs_obj)) } \ No newline at end of file diff --git a/src/wasi/mod.rs b/src/wasi/mod.rs index 4e71ae0..b821eb9 100644 --- a/src/wasi/mod.rs +++ b/src/wasi/mod.rs @@ -12,16 +12,20 @@ mod mkdir; mod rmdir; mod unlink; mod close; +mod symlink; +mod link; pub(crate) use open::wasi_preview1_open; pub(crate) use mkdir::wasi_preview1_path_create_directory; pub(crate) use rmdir::wasi_preview1_path_remove_directory; pub(crate) use unlink::wasi_preview1_path_unlink_file; pub(crate) use close::wasi_preview1_close; +pub(crate) use symlink::wasi_preview1_path_symlink; +pub(crate) use link::wasi_preview1_path_link; pub use error::WasiError; #[inline] -pub fn set_error(obj: &JObject, rs: i32) -> Result<()> { +pub fn process_error(obj: &JObject, rs: i32) -> Result<()> { let error_messgae = if rs != 0 { let error: WasiError = rs.into(); error.to_string() @@ -57,7 +61,7 @@ pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { let path_len = i32::from_le_bytes(path_len_buf); let obj = JObject::new(cx)?; if rs != 0 { - set_error(&obj, rs)?; + process_error(&obj, rs)?; return Ok(Value::from_object(obj)) } let mut path_buf = vec![0u8; path_len as usize]; @@ -72,7 +76,7 @@ pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { let path = String::from_utf8(path_buf)?; obj.set("dir_name", path)?; } - set_error(&obj, rs)?; + process_error(&obj, rs)?; Ok(Value::from_object(obj)) } diff --git a/src/wasi/open.rs b/src/wasi/open.rs index 8df989c..3b738a4 100644 --- a/src/wasi/open.rs +++ b/src/wasi/open.rs @@ -4,7 +4,7 @@ use javy_plugin_api::javy::{ }; use anyhow::{anyhow, bail, Result}; -use super::{preview_1, set_error}; +use super::{preview_1, process_error}; /// This function is used to open a file at the given path. /// It is used to open a file at the given path. @@ -77,6 +77,6 @@ pub fn wasi_preview1_open<'a>(args: Args<'a>) -> Result> { let rs_obj = JObject::new(cx.clone())?; rs_obj.set("fd", opened_fd)?; - set_error(&rs_obj, rs)?; + process_error(&rs_obj, rs)?; Ok(Value::from_object(rs_obj)) } diff --git a/src/wasi/preview_1.js b/src/wasi/preview_1.js index 3ad50cc..4b2b30e 100644 --- a/src/wasi/preview_1.js +++ b/src/wasi/preview_1.js @@ -12,6 +12,8 @@ const __javy_wasi_preview1_path_create_directory = globalThis.__javy_wasi_preview1_path_create_directory; const __javy_wasi_preview1_path_remove_directory = globalThis.__javy_wasi_preview1_path_remove_directory; const __javy_wasi_preview1_path_unlink_file = globalThis.__javy_wasi_preview1_path_unlink_file; + const __javy_wasi_preview1_path_symlink = globalThis.__javy_wasi_preview1_path_symlink; + const __javy_wasi_preview1_path_link = globalThis.__javy_wasi_preview1_path_link; const InvalParameter = 0x1C const Rights = { @@ -190,6 +192,57 @@ } return true; } + + // This function is used to create a symbolic link from oldpath to newpath. + // It first checks if the newpath is valid and then determines the directory file descriptor (dirfd) for the newpath. + function symlink(oldpath, newpath) { + if (oldpath == null || newpath == null) { + lastErr.errno = InvalParameter; + lastErr.error = "Path is required"; + return false; + } + const dirpathObj = dirfdForPath(newpath); + if (dirpathObj == null) { + return false; + } + const {dirpath, dirfd} = dirpathObj; + newpath = newpath.substring(dirpath.length, newpath.length); + lastErr = __javy_wasi_preview1_path_symlink(oldpath, dirfd, newpath) + if (lastErr.errno != 0) { + return false; + } + return true; + } + + // + function link(oldpath, newpath) { + if (oldpath == null || newpath == null) { + lastErr.errno = InvalParameter; + lastErr.error = "Path is required"; + return false; + } + const old_dirpath_rs = dirfdForPath(oldpath); + if (old_dirpath_rs == null) { + return false; + } + + const new_dirpath_rs = dirfdForPath(newpath); + if (new_dirpath_rs == null) { + return false; + } + const old_dirpath = old_dirpath_rs.dirpath; + const old_dirfd = old_dirpath_rs.dirfd; + + const new_dirpath = new_dirpath_rs.dirpath; + const new_dirfd = new_dirpath_rs.dirfd; + newpath = newpath.substring(new_dirpath.length, newpath.length); + oldpath = oldpath.substring(old_dirpath.length, oldpath.length); + lastErr = __javy_wasi_preview1_path_link(old_dirfd, 0, oldpath, new_dirfd, newpath); + if (lastErr.errno != 0) { + return false; + } + return true; + } // This function is used to get the directory name for a given file descriptor. // It recursively calls itself with an incremented file descriptor until it finds a valid directory name. @@ -214,6 +267,8 @@ mkdir, rmdir, unlink, + symlink, + link, errno: () => lastErr.errno, error: () => lastErr.error, }; @@ -225,4 +280,5 @@ Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_path_create_directory"); Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_path_remove_directory"); Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_path_unlink_file"); + Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_path_symlink"); })(); \ No newline at end of file diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index 74a80bd..85dcee2 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -62,6 +62,15 @@ unsafe extern "C" { path_len: i32, ) -> i32; - + #[link_name = "path_link"] + pub unsafe fn path_link( + old_dirfd: i32, + fd_lookup_flags: i32, + old_path: i32, + old_path_len: i32, + new_dirfd: i32, + new_path: i32, + new_path_len: i32, + ) -> i32; } diff --git a/src/wasi/rmdir.rs b/src/wasi/rmdir.rs index 35b1d4d..533fd04 100644 --- a/src/wasi/rmdir.rs +++ b/src/wasi/rmdir.rs @@ -4,7 +4,7 @@ use javy_plugin_api::javy::{ }; use anyhow::{anyhow, bail, Result}; -use super::{preview_1, set_error}; +use super::{preview_1, process_error}; /// Remove a directory at the given path. @@ -33,6 +33,6 @@ pub fn wasi_preview1_path_remove_directory(args: Args<'_>) -> Result> let path_len = path.len() as i32; let rs = unsafe { preview_1::path_remove_directory(dirfd, path_ptr, path_len) }; let rs_obj = JObject::new(cx.clone())?; - set_error(&rs_obj, rs)?; + process_error(&rs_obj, rs)?; Ok(Value::from_object(rs_obj)) } diff --git a/src/wasi/symlink.rs b/src/wasi/symlink.rs new file mode 100644 index 0000000..2404355 --- /dev/null +++ b/src/wasi/symlink.rs @@ -0,0 +1,46 @@ +use javy_plugin_api::javy::{ + quickjs::{Object as JObject, Value}, + Args +}; +use anyhow::{anyhow, bail, Result}; + +use super::{preview_1, process_error}; + +/// This function creates a symbolic link to a file or directory. +/// It takes the following parameters: +/// - `old_path`: The path to the file or directory to be linked. +/// - `dirfd`: The file descriptor of the directory where the link will be created. +/// - `new_path`: The name of the new symbolic link. +pub fn wasi_preview1_path_symlink(args: Args<'_>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let [ + old_path, + dirfd, + new_path, + .. + ] = args_pat else { + bail!( + "path_symlink expects 3 parameters: the old_path, fd and new_path, Got: {} parameters.", + args.len() + ); + }; + let old_path = old_path.as_string() + .ok_or_else(|| anyhow!("old_path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in old_path"))?; + let dirfd = dirfd.as_int() + .ok_or_else(|| anyhow!("fd must be a number"))?; + let new_path = new_path.as_string() + .ok_or_else(|| anyhow!("new_path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in new_path"))?; + let old_path_ptr = old_path.as_ptr() as i32; + let old_path_len = old_path.len() as i32; + let new_path_ptr = new_path.as_ptr() as i32; + let new_path_len = new_path.len() as i32; + let obj = JObject::new(cx)?; + let rs = unsafe { preview_1::path_symlink(old_path_ptr, old_path_len, dirfd, new_path_ptr, new_path_len) }; + process_error(&obj, rs)?; + Ok(Value::from_object(obj)) +} \ No newline at end of file diff --git a/src/wasi/unlink.rs b/src/wasi/unlink.rs index 98157b2..27c09b0 100644 --- a/src/wasi/unlink.rs +++ b/src/wasi/unlink.rs @@ -4,7 +4,7 @@ use javy_plugin_api::javy::{ }; use anyhow::{anyhow, bail, Result}; -use super::{preview_1, set_error}; +use super::{preview_1, process_error}; /// Unlink a file at the given path. @@ -34,6 +34,6 @@ pub fn wasi_preview1_path_unlink_file(args: Args<'_>) -> Result> { let path_len = path.len() as i32; let rs = unsafe { preview_1::path_unlink_file(dirfd, path_ptr, path_len) }; let rs_obj = JObject::new(cx.clone())?; - set_error(&rs_obj, rs)?; + process_error(&rs_obj, rs)?; Ok(Value::from_object(rs_obj)) } From b8a61a20d90318283acaa71d5416324125f21d5e Mon Sep 17 00:00:00 2001 From: Joinhack Date: Thu, 22 May 2025 09:45:43 +0800 Subject: [PATCH 13/45] rename support --- src/wasi/rename.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/wasi/rename.rs diff --git a/src/wasi/rename.rs b/src/wasi/rename.rs new file mode 100644 index 0000000..a73f8fc --- /dev/null +++ b/src/wasi/rename.rs @@ -0,0 +1,52 @@ +use javy_plugin_api::javy::{ + quickjs::{Object as JObject, Value}, + Args +}; +use anyhow::{anyhow, bail, Result}; + +use super::{preview_1, process_error}; + + +pub fn wasi_preview1_path_rename(args: Args<'_>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let [ + old_dirfd, + old_path, + new_dirfd, + new_path, + .. + ] = args_pat else { + bail!( + "path_remove_directory expects 4 parameters: the dirfd and path, Got: {} parameters.", + args.len() + ); + }; + let dirfd = old_dirfd.as_int() + .ok_or_else(|| anyhow!("old_dirfd must be a number"))?; + let old_path = old_path.as_string() + .ok_or_else(|| anyhow!("old_path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + let new_dirfd = new_dirfd.as_int() + .ok_or_else(|| anyhow!("new_dirfd must be a number"))?; + let new_path = new_path.as_string() + .ok_or_else(|| anyhow!("new_path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + let new_path_ptr = new_path.as_ptr() as i32; + let new_path_len = new_path.len() as i32; + let old_path_ptr = old_path.as_ptr() as i32; + let old_path_len = old_path.len() as i32; + let rs = unsafe { + preview_1::path_rename( + dirfd, + old_path_ptr, old_path_len, + new_dirfd, + new_path_ptr, new_path_len + ) + }; + let rs_obj = JObject::new(cx.clone())?; + process_error(&rs_obj, rs)?; + Ok(Value::from_object(rs_obj)) +} From 90ece1eb4d94318b538b03f2aea00efb380e94f5 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Thu, 22 May 2025 09:45:56 +0800 Subject: [PATCH 14/45] stat support --- src/wasi/stat.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/wasi/stat.rs diff --git a/src/wasi/stat.rs b/src/wasi/stat.rs new file mode 100644 index 0000000..b518cd4 --- /dev/null +++ b/src/wasi/stat.rs @@ -0,0 +1,58 @@ +use javy_plugin_api::javy::{ + quickjs::{Object as JObject, Value}, + Args +}; +use anyhow::{anyhow, bail, Result}; + +use super::{preview_1, process_error, FileType, Filestat}; + +pub fn wasi_preview1_path_filestat_get(args: Args<'_>) -> Result> { + let (cx, args) = args.release(); + let args_pat: &[Value<'_>]= &args.0; + let [ + dirfd, + lookup_flags, + path, + .. + ] = args_pat else { + bail!( + "wasi_preview1_path_filestat_get expects 3 parameters: the dirfd and path, Got: {} parameters.", + args.len() + ); + }; + let dirfd = dirfd.as_int() + .ok_or_else(|| anyhow!("dirfd must be a number"))?; + let lookup_flags = lookup_flags.as_int() + .ok_or_else(|| anyhow!("lookup_flags must be a number"))?; + let path = path.as_string() + .ok_or_else(|| anyhow!("path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + let path_ptr = path.as_ptr() as i32; + let path_len = path.len() as i32; + let mut fd_stat: Filestat = Default::default(); + let fd_stat_ptr = &mut fd_stat as *mut _ as i32; + let rs = unsafe { + preview_1::path_filestat_get( + dirfd, + lookup_flags, + path_ptr, path_len, + fd_stat_ptr, + ) + }; + let rs_obj = JObject::new(cx.clone())?; + if rs == 0 { + println!("fd_stat: {:?}", fd_stat); + let stat = JObject::new(cx.clone())?; + stat.set("filetype", fd_stat.filetype)?; + let filetype: &str = FileType(fd_stat.filetype).into(); + stat.set("filetype_desc", filetype)?; + stat.set("filesize", fd_stat.size)?; + stat.set("access_time", fd_stat.atim)?; + stat.set("modification_time", fd_stat.mtim)?; + stat.set("creation_time", fd_stat.ctim)?; + rs_obj.set("stat", stat)?; + } + process_error(&rs_obj, rs)?; + Ok(Value::from_object(rs_obj)) +} \ No newline at end of file From 0d1b1f6bee90a12685da5cc6e8234797fef534cc Mon Sep 17 00:00:00 2001 From: Joinhack Date: Thu, 22 May 2025 09:46:20 +0800 Subject: [PATCH 15/45] js wrapper rename stat --- src/lib.rs | 2 ++ src/wasi/mod.rs | 31 +++++++++++++++++++++++ src/wasi/preview_1.js | 59 ++++++++++++++++++++++++++++++++++++++++++- src/wasi/preview_1.rs | 20 +++++++++++++++ 4 files changed, 111 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4628835..95802f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,6 +90,8 @@ pub extern "C" fn initialize_runtime() { bind!(function, wasi_preview1_close); bind!(function, wasi_preview1_path_symlink); bind!(function, wasi_preview1_path_link); + bind!(function, wasi_preview1_path_rename); + bind!(function, wasi_preview1_path_filestat_get); } #[cfg(feature = "llm")] diff --git a/src/wasi/mod.rs b/src/wasi/mod.rs index b821eb9..cec3bcd 100644 --- a/src/wasi/mod.rs +++ b/src/wasi/mod.rs @@ -14,6 +14,8 @@ mod unlink; mod close; mod symlink; mod link; +mod rename; +mod stat; pub(crate) use open::wasi_preview1_open; pub(crate) use mkdir::wasi_preview1_path_create_directory; pub(crate) use rmdir::wasi_preview1_path_remove_directory; @@ -21,6 +23,8 @@ pub(crate) use unlink::wasi_preview1_path_unlink_file; pub(crate) use close::wasi_preview1_close; pub(crate) use symlink::wasi_preview1_path_symlink; pub(crate) use link::wasi_preview1_path_link; +pub(crate) use rename::wasi_preview1_path_rename; +pub(crate) use stat::wasi_preview1_path_filestat_get; pub use error::WasiError; @@ -80,5 +84,32 @@ pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { Ok(Value::from_object(obj)) } +#[derive(Default, Debug)] +pub struct Filestat { + pub device_id: u64, + pub inode: u64, + pub filetype: u8, + pub nlink: u64, + pub size: u64, // this is a read field, the rest are file fields + pub atim: u64, + pub mtim: u64, + pub ctim: u64, +} +pub struct FileType(u8); +impl Into<&str> for FileType { + fn into(self) -> &'static str { + match self.0 { + 0 => "unknown", + 1 => "block device", + 2 => "character device", + 3 => "directory", + 4 => "regular file", + 5 => "socket dgram", + 6 => "socket stream", + 7 => "symbolic link", + _ => unimplemented!("FileType not implemented"), + } + } +} \ No newline at end of file diff --git a/src/wasi/preview_1.js b/src/wasi/preview_1.js index 4b2b30e..9be2014 100644 --- a/src/wasi/preview_1.js +++ b/src/wasi/preview_1.js @@ -14,6 +14,8 @@ const __javy_wasi_preview1_path_unlink_file = globalThis.__javy_wasi_preview1_path_unlink_file; const __javy_wasi_preview1_path_symlink = globalThis.__javy_wasi_preview1_path_symlink; const __javy_wasi_preview1_path_link = globalThis.__javy_wasi_preview1_path_link; + const __javy_wasi_preview1_path_rename = globalThis.__javy_wasi_preview1_path_rename; + const __javy_wasi_preview1_path_filestat_get = globalThis.__javy_wasi_preview1_path_filestat_get; const InvalParameter = 0x1C const Rights = { @@ -214,7 +216,7 @@ return true; } - // + // This function is used to create a hard link from oldpath to newpath. function link(oldpath, newpath) { if (oldpath == null || newpath == null) { lastErr.errno = InvalParameter; @@ -244,6 +246,59 @@ return true; } + function rename(oldpath, newpath) { + if (oldpath == null || newpath == null) { + lastErr.errno = InvalParameter; + lastErr.error = "Path is required"; + return false; + } + const old_dirpath_rs = dirfdForPath(oldpath); + if (old_dirpath_rs == null) { + return false; + } + + const new_dirpath_rs = dirfdForPath(newpath); + if (new_dirpath_rs == null) { + return false; + } + const old_dirpath = old_dirpath_rs.dirpath; + const old_dirfd = old_dirpath_rs.dirfd; + + const new_dirpath = new_dirpath_rs.dirpath; + const new_dirfd = new_dirpath_rs.dirfd; + newpath = newpath.substring(new_dirpath.length, newpath.length); + oldpath = oldpath.substring(old_dirpath.length, oldpath.length); + lastErr = __javy_wasi_preview1_path_rename(old_dirfd, oldpath, new_dirfd, newpath); + if (lastErr.errno != 0) { + return false; + } + return true; + } + + function stat(path) { + if (path == null) { + lastErr.errno = InvalParameter; + lastErr.error = "Path is required"; + return false; + } + const dirpath_rs = dirfdForPath(path); + if (dirpath_rs == null) { + return false; + } + + + const dirpath = dirpath_rs.dirpath; + const dirfd = dirpath_rs.dirfd; + + path = path.substring(dirpath.length, path.length); + let {errno, error, stat} = __javy_wasi_preview1_path_filestat_get(dirfd, Lookupflags.SYMLINK_FOLLOW, path); + if (rs.errno != 0) { + lastErr = {errno, error}; + return null; + } + return stat; + } + // This function is used to get the directory name for a given file descriptor. // It recursively calls itself with an incremented file descriptor until it finds a valid directory name. function dirfdForPath(path, fd = 3) { @@ -269,6 +324,8 @@ unlink, symlink, link, + rename, + stat, errno: () => lastErr.errno, error: () => lastErr.error, }; diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index 85dcee2..d18ca86 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -73,4 +73,24 @@ unsafe extern "C" { new_path_len: i32, ) -> i32; + #[link_name = "path_rename"] + pub unsafe fn path_rename( + old_dirfd: i32, + old_path: i32, + old_path_len: i32, + new_dirfd: i32, + new_path: i32, + new_path_len: i32, + ) -> i32; + + + + #[link_name = "path_filestat_get"] + pub unsafe fn path_filestat_get( + dirfd: i32, + flags: i32, + path: i32, + path_len: i32, + stat_ptr: i32, + ) -> i32; } From 7be31f17fda1fbd19678e29ec35ef861b5bebfee Mon Sep 17 00:00:00 2001 From: Joinhack Date: Fri, 23 May 2025 09:50:32 +0800 Subject: [PATCH 16/45] fix the repr for stat --- src/wasi/mod.rs | 1 + src/wasi/preview_1.js | 2 +- src/wasi/stat.rs | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wasi/mod.rs b/src/wasi/mod.rs index cec3bcd..e852136 100644 --- a/src/wasi/mod.rs +++ b/src/wasi/mod.rs @@ -85,6 +85,7 @@ pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { } #[derive(Default, Debug)] +#[repr(C)] pub struct Filestat { pub device_id: u64, pub inode: u64, diff --git a/src/wasi/preview_1.js b/src/wasi/preview_1.js index 9be2014..b528d9e 100644 --- a/src/wasi/preview_1.js +++ b/src/wasi/preview_1.js @@ -292,7 +292,7 @@ path = path.substring(dirpath.length, path.length); let {errno, error, stat} = __javy_wasi_preview1_path_filestat_get(dirfd, Lookupflags.SYMLINK_FOLLOW, path); - if (rs.errno != 0) { + if (errno != 0) { lastErr = {errno, error}; return null; } diff --git a/src/wasi/stat.rs b/src/wasi/stat.rs index b518cd4..6f201fc 100644 --- a/src/wasi/stat.rs +++ b/src/wasi/stat.rs @@ -42,7 +42,6 @@ pub fn wasi_preview1_path_filestat_get(args: Args<'_>) -> Result> { }; let rs_obj = JObject::new(cx.clone())?; if rs == 0 { - println!("fd_stat: {:?}", fd_stat); let stat = JObject::new(cx.clone())?; stat.set("filetype", fd_stat.filetype)?; let filetype: &str = FileType(fd_stat.filetype).into(); From bf2ac488a390ffdb4a444720da7935b60339de38 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Sat, 24 May 2025 13:56:45 +0800 Subject: [PATCH 17/45] refactor: use global for last error --- src/wasi/close.rs | 7 +++---- src/wasi/error.rs | 1 - src/wasi/link.rs | 5 ++--- src/wasi/mkdir.rs | 5 ++--- src/wasi/mod.rs | 14 ++++++++----- src/wasi/open.rs | 16 ++++++++------ src/wasi/preview_1.js | 49 +++++++++++++++++++------------------------ src/wasi/preview_1.rs | 8 +++++++ src/wasi/rename.rs | 5 ++--- src/wasi/rmdir.rs | 7 +++---- src/wasi/stat.rs | 6 ++++-- src/wasi/symlink.rs | 11 ++++++---- src/wasi/unlink.rs | 10 +++++---- 13 files changed, 78 insertions(+), 66 deletions(-) diff --git a/src/wasi/close.rs b/src/wasi/close.rs index fd110fb..3b7bc93 100644 --- a/src/wasi/close.rs +++ b/src/wasi/close.rs @@ -1,5 +1,5 @@ use javy_plugin_api::javy::{ - quickjs::{Object as JObject, Value}, + quickjs::{Value}, Args }; use anyhow::{anyhow, bail, Result}; @@ -22,7 +22,6 @@ pub fn wasi_preview1_close<'a>(args: Args<'a>) -> Result> { let fd = fd.as_int() .ok_or_else(|| anyhow!("fd must be a number"))?; let rs = unsafe { preview_1::fd_close(fd) }; - let rs_obj = JObject::new(cx.clone())?; - process_error(&rs_obj, rs)?; - Ok(Value::from_object(rs_obj)) + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx.clone(), rs)) } \ No newline at end of file diff --git a/src/wasi/error.rs b/src/wasi/error.rs index 353cdf5..062f34d 100644 --- a/src/wasi/error.rs +++ b/src/wasi/error.rs @@ -1,6 +1,5 @@ use thiserror::Error; - #[derive(Error, Debug)] pub enum WasiError { #[error("Argument list too long.")] diff --git a/src/wasi/link.rs b/src/wasi/link.rs index ab6487e..4b69c87 100644 --- a/src/wasi/link.rs +++ b/src/wasi/link.rs @@ -52,7 +52,6 @@ pub fn wasi_preview1_path_link(args: Args<'_>) -> Result> { new_path_len ) }; - let rs_obj = JObject::new(cx.clone())?; - process_error(&rs_obj, rs)?; - Ok(Value::from_object(rs_obj)) + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx.clone(), rs)) } diff --git a/src/wasi/mkdir.rs b/src/wasi/mkdir.rs index cbbadef..ac710d6 100644 --- a/src/wasi/mkdir.rs +++ b/src/wasi/mkdir.rs @@ -31,7 +31,6 @@ pub fn wasi_preview1_path_create_directory(args: Args<'_>) -> Result> let path_ptr = path.as_ptr() as i32; let path_len = path.len() as i32; let rs = unsafe { preview_1::path_create_directory(dirfd, path_ptr, path_len) }; - let rs_obj = JObject::new(cx.clone())?; - process_error(&rs_obj, rs)?; - Ok(Value::from_object(rs_obj)) + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx.clone(), rs)) } \ No newline at end of file diff --git a/src/wasi/mod.rs b/src/wasi/mod.rs index e852136..17adebb 100644 --- a/src/wasi/mod.rs +++ b/src/wasi/mod.rs @@ -1,6 +1,6 @@ use javy_plugin_api::javy::{ - quickjs::{Object as JObject, Value}, + quickjs::{Ctx, Object as JObject, Value}, Args }; use anyhow::{anyhow, bail, Result}; @@ -16,6 +16,7 @@ mod symlink; mod link; mod rename; mod stat; +mod descriptor; pub(crate) use open::wasi_preview1_open; pub(crate) use mkdir::wasi_preview1_path_create_directory; pub(crate) use rmdir::wasi_preview1_path_remove_directory; @@ -29,7 +30,8 @@ pub use error::WasiError; #[inline] -pub fn process_error(obj: &JObject, rs: i32) -> Result<()> { +pub fn process_error(ctx: Ctx<'_>, rs: i32) -> Result<()> { + let obj = JObject::new(ctx.clone())?; let error_messgae = if rs != 0 { let error: WasiError = rs.into(); error.to_string() @@ -38,6 +40,7 @@ pub fn process_error(obj: &JObject, rs: i32) -> Result<()> { }; obj.set("errno", rs)?; obj.set("error", error_messgae)?; + ctx.globals().set("lastErr", obj)?; Ok(()) } @@ -63,9 +66,9 @@ pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { let rs = unsafe { preview_1::fd_prestat_get(fd, path_len_ptr) }; let path_len_buf: [u8; 4] = path_len_buf[4..].try_into()?; let path_len = i32::from_le_bytes(path_len_buf); - let obj = JObject::new(cx)?; + let obj = JObject::new(cx.clone())?; if rs != 0 { - process_error(&obj, rs)?; + process_error(cx.clone(), rs)?; return Ok(Value::from_object(obj)) } let mut path_buf = vec![0u8; path_len as usize]; @@ -80,7 +83,8 @@ pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { let path = String::from_utf8(path_buf)?; obj.set("dir_name", path)?; } - process_error(&obj, rs)?; + obj.set("code", rs)?; + process_error(cx.clone(), rs)?; Ok(Value::from_object(obj)) } diff --git a/src/wasi/open.rs b/src/wasi/open.rs index 3b738a4..c930297 100644 --- a/src/wasi/open.rs +++ b/src/wasi/open.rs @@ -1,9 +1,10 @@ use javy_plugin_api::javy::{ - quickjs::{Object as JObject, Value}, + quickjs::Value, Args }; use anyhow::{anyhow, bail, Result}; +use super::descriptor::Descriptor; use super::{preview_1, process_error}; /// This function is used to open a file at the given path. @@ -74,9 +75,12 @@ pub fn wasi_preview1_open<'a>(args: Args<'a>) -> Result> { fdflags, opened_fd_ptr) }; - - let rs_obj = JObject::new(cx.clone())?; - rs_obj.set("fd", opened_fd)?; - process_error(&rs_obj, rs)?; - Ok(Value::from_object(rs_obj)) + if rs == 0 { + Ok(Descriptor::new(cx.clone(), opened_fd)?) + } else { + process_error(cx.clone(), rs)?; + Ok(Value::new_null(cx.clone())) + } + + } diff --git a/src/wasi/preview_1.js b/src/wasi/preview_1.js index b528d9e..997b14b 100644 --- a/src/wasi/preview_1.js +++ b/src/wasi/preview_1.js @@ -6,6 +6,7 @@ errno: 0, error: "", } + globalThis.lastErr = lastErr; // Get a reference to the function before we delete it from `globalThis`. const __javy_wasi_preview1_open = globalThis.__javy_wasi_preview1_open; const __javy_wasi_preview1_fd_prestat_dir_name = globalThis.__javy_wasi_preview1_fd_prestat_dir_name; @@ -116,7 +117,7 @@ path = path.substring(dirpath.length, path.length); let fd_rights_inherited = fd_rights; let fd_flags = 0; - const {errno, fd, error} = __javy_wasi_preview1_open( + const file = __javy_wasi_preview1_open( dirfd, fd_lookup_flags, path, @@ -125,11 +126,7 @@ fd_rights_inherited, fd_flags, ) - lastErr = {errno, error} - if (errno != 0) { - return - } - return fd; + return file; } // This function is used to create a new directory with the specified path. @@ -146,8 +143,8 @@ } const {dirpath, dirfd} = dirpathObj; path = path.substring(dirpath.length, path.length); - lastErr = __javy_wasi_preview1_path_create_directory(dirfd, path) - if (lastErr.errno != 0) { + let rs = __javy_wasi_preview1_path_create_directory(dirfd, path) + if (rs != 0) { return false; } return true; @@ -167,8 +164,8 @@ } const {dirpath, dirfd} = dirpathObj; path = path.substring(dirpath.length, path.length); - lastErr = __javy_wasi_preview1_path_remove_directory(dirfd, path) - if (lastErr.errno != 0) { + let rs = __javy_wasi_preview1_path_remove_directory(dirfd, path) + if (rs != 0) { return false; } return true; @@ -188,8 +185,8 @@ } const {dirpath, dirfd} = dirpathObj; path = path.substring(dirpath.length, path.length); - lastErr = __javy_wasi_preview1_path_unlink_file(dirfd, path) - if (lastErr.errno != 0) { + let rs = __javy_wasi_preview1_path_unlink_file(dirfd, path) + if (rs != 0) { return false; } return true; @@ -209,8 +206,8 @@ } const {dirpath, dirfd} = dirpathObj; newpath = newpath.substring(dirpath.length, newpath.length); - lastErr = __javy_wasi_preview1_path_symlink(oldpath, dirfd, newpath) - if (lastErr.errno != 0) { + let rs = __javy_wasi_preview1_path_symlink(oldpath, dirfd, newpath) + if (rs != 0) { return false; } return true; @@ -239,8 +236,8 @@ const new_dirfd = new_dirpath_rs.dirfd; newpath = newpath.substring(new_dirpath.length, newpath.length); oldpath = oldpath.substring(old_dirpath.length, oldpath.length); - lastErr = __javy_wasi_preview1_path_link(old_dirfd, 0, oldpath, new_dirfd, newpath); - if (lastErr.errno != 0) { + let rs = __javy_wasi_preview1_path_link(old_dirfd, 0, oldpath, new_dirfd, newpath); + if (rs != 0) { return false; } return true; @@ -268,8 +265,8 @@ const new_dirfd = new_dirpath_rs.dirfd; newpath = newpath.substring(new_dirpath.length, newpath.length); oldpath = oldpath.substring(old_dirpath.length, oldpath.length); - lastErr = __javy_wasi_preview1_path_rename(old_dirfd, oldpath, new_dirfd, newpath); - if (lastErr.errno != 0) { + let rs = __javy_wasi_preview1_path_rename(old_dirfd, oldpath, new_dirfd, newpath); + if (rs != 0) { return false; } return true; @@ -291,11 +288,7 @@ const dirfd = dirpath_rs.dirfd; path = path.substring(dirpath.length, path.length); - let {errno, error, stat} = __javy_wasi_preview1_path_filestat_get(dirfd, Lookupflags.SYMLINK_FOLLOW, path); - if (errno != 0) { - lastErr = {errno, error}; - return null; - } + let stat = __javy_wasi_preview1_path_filestat_get(dirfd, Lookupflags.SYMLINK_FOLLOW, path); return stat; } @@ -303,7 +296,7 @@ // It recursively calls itself with an incremented file descriptor until it finds a valid directory name. function dirfdForPath(path, fd = 3) { let rs = __javy_wasi_preview1_fd_prestat_dir_name(fd); - if (rs.errno == 0) { + if (rs.code == 0) { if (path.startsWith(rs.dir_name)) { rs.fd = fd; return {dirpath: rs.dir_name, dirfd: fd}; @@ -311,7 +304,6 @@ return dirfdForPath(path, fd + 1); } } else { - lastErr = rs return null; } } @@ -326,8 +318,8 @@ link, rename, stat, - errno: () => lastErr.errno, - error: () => lastErr.error, + errno: () => globalThis.lastErr.errno, + error: () => globalThis.lastErr.error, }; }(); @@ -338,4 +330,7 @@ Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_path_remove_directory"); Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_path_unlink_file"); Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_path_symlink"); + Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_path_link"); + Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_path_rename"); + Reflect.deleteProperty(globalThis, "__javy_wasi_preview1_path_filestat_get"); })(); \ No newline at end of file diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index d18ca86..61520ba 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -55,6 +55,14 @@ unsafe extern "C" { path_len: i32, ) -> i32; + #[link_name = "fd_read"] + pub unsafe fn fd_read( + fd: i32, + iovec_slice: i32, + iovec_len: i32, + readn_ptr: i32, + ) -> i32; + #[link_name = "fd_prestat_dir_name"] pub unsafe fn fd_prestat_dir_name( fd: i32, diff --git a/src/wasi/rename.rs b/src/wasi/rename.rs index a73f8fc..e34713b 100644 --- a/src/wasi/rename.rs +++ b/src/wasi/rename.rs @@ -46,7 +46,6 @@ pub fn wasi_preview1_path_rename(args: Args<'_>) -> Result> { new_path_ptr, new_path_len ) }; - let rs_obj = JObject::new(cx.clone())?; - process_error(&rs_obj, rs)?; - Ok(Value::from_object(rs_obj)) + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx.clone(), rs)) } diff --git a/src/wasi/rmdir.rs b/src/wasi/rmdir.rs index 533fd04..c22ef07 100644 --- a/src/wasi/rmdir.rs +++ b/src/wasi/rmdir.rs @@ -1,5 +1,5 @@ use javy_plugin_api::javy::{ - quickjs::{Object as JObject, Value}, + quickjs::Value, Args }; use anyhow::{anyhow, bail, Result}; @@ -32,7 +32,6 @@ pub fn wasi_preview1_path_remove_directory(args: Args<'_>) -> Result> let path_ptr = path.as_ptr() as i32; let path_len = path.len() as i32; let rs = unsafe { preview_1::path_remove_directory(dirfd, path_ptr, path_len) }; - let rs_obj = JObject::new(cx.clone())?; - process_error(&rs_obj, rs)?; - Ok(Value::from_object(rs_obj)) + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx.clone(), rs)) } diff --git a/src/wasi/stat.rs b/src/wasi/stat.rs index 6f201fc..52a8eb1 100644 --- a/src/wasi/stat.rs +++ b/src/wasi/stat.rs @@ -51,7 +51,9 @@ pub fn wasi_preview1_path_filestat_get(args: Args<'_>) -> Result> { stat.set("modification_time", fd_stat.mtim)?; stat.set("creation_time", fd_stat.ctim)?; rs_obj.set("stat", stat)?; + Ok(Value::from_object(rs_obj)) + } else { + process_error(cx.clone(), rs)?; + Ok(Value::new_null(cx.clone())) } - process_error(&rs_obj, rs)?; - Ok(Value::from_object(rs_obj)) } \ No newline at end of file diff --git a/src/wasi/symlink.rs b/src/wasi/symlink.rs index 2404355..352da54 100644 --- a/src/wasi/symlink.rs +++ b/src/wasi/symlink.rs @@ -39,8 +39,11 @@ pub fn wasi_preview1_path_symlink(args: Args<'_>) -> Result> { let old_path_len = old_path.len() as i32; let new_path_ptr = new_path.as_ptr() as i32; let new_path_len = new_path.len() as i32; - let obj = JObject::new(cx)?; - let rs = unsafe { preview_1::path_symlink(old_path_ptr, old_path_len, dirfd, new_path_ptr, new_path_len) }; - process_error(&obj, rs)?; - Ok(Value::from_object(obj)) + let rs = unsafe { preview_1::path_symlink( + old_path_ptr, old_path_len, + dirfd, + new_path_ptr, new_path_len + )}; + process_error(cx.clone(), rs)?; + Ok(Value::new_null(cx.clone())) } \ No newline at end of file diff --git a/src/wasi/unlink.rs b/src/wasi/unlink.rs index 27c09b0..122a097 100644 --- a/src/wasi/unlink.rs +++ b/src/wasi/unlink.rs @@ -32,8 +32,10 @@ pub fn wasi_preview1_path_unlink_file(args: Args<'_>) -> Result> { .map_err(|_| anyhow!("invalid UTF-8 in path"))?; let path_ptr = path.as_ptr() as i32; let path_len = path.len() as i32; - let rs = unsafe { preview_1::path_unlink_file(dirfd, path_ptr, path_len) }; - let rs_obj = JObject::new(cx.clone())?; - process_error(&rs_obj, rs)?; - Ok(Value::from_object(rs_obj)) + let rs = unsafe { preview_1::path_unlink_file( + dirfd, + path_ptr, path_len + )}; + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx.clone(), rs)) } From 0bdf98cf7fb18e36bd72657f0fa80ae15ebd036f Mon Sep 17 00:00:00 2001 From: Joinhack Date: Sat, 24 May 2025 13:57:14 +0800 Subject: [PATCH 18/45] descriptor for file --- src/wasi/descriptor.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/wasi/descriptor.rs diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs new file mode 100644 index 0000000..fab8f86 --- /dev/null +++ b/src/wasi/descriptor.rs @@ -0,0 +1,83 @@ +use std::sync::Arc; +use javy_plugin_api::javy::{ + quickjs::{ + prelude::{MutFn, Rest}, + Ctx, + Function, + Object as JObject, + TypedArray, + Value + }, to_js_error +}; +use anyhow::{anyhow, bail, Ok, Result}; + +use super::{preview_1, process_error}; + +pub struct Descriptor { + fd: i32, + errno: i32, + error: Option, +} + +pub struct Iovec { + pub buf: i32, + pub buf_len: u32, +} + +impl Descriptor { + pub fn new<'js>(cx: Ctx<'js>, fd: i32) -> Result> { + let descriptor = Arc::new(Descriptor { + fd, + errno: 0, + error: None, + }); + let desc = JObject::new(cx.clone())?; + desc.set("fd", fd)?; + desc.set("read", Function::new( + cx.clone(), + MutFn::new(move |cx: Ctx<'js>, args: Rest>| { + descriptor.clone().read(cx.clone(), args) + .map_err(|e| to_js_error(cx.clone(), e)) + }), + )?)?; + Ok(Value::from_object(desc)) + } + + fn read<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { + let args_pat: &[Value<'_>]= &args.0; + let [ + buffer, + size, + .. + ] = args_pat else { + bail!( + "read expects 2 parameters: the buffer and size, Got: {} parameters.", + args.len() + ); + }; + let mut readn: i32 = 0; + let readn_ptr: i32 = &mut readn as *mut i32 as i32; + let array: &TypedArray<'_, u8> = buffer.as_array() + .ok_or_else(|| anyhow!("buffer must be a array"))? + .as_typed_array() + .ok_or_else(|| anyhow!("buffer must be a Uint8Array"))?; + let mut array_raw = array.as_raw() + .ok_or_else(|| anyhow!("buffer get raw ptr error."))?; + let ioslice = vec![ + Iovec { + buf: unsafe {array_raw.ptr.as_mut() as *mut u8 as i32}, + buf_len: size.as_int().ok_or_else(|| anyhow!("size must be a int"))? as u32, + } + ]; + let rs = unsafe { + preview_1::fd_read( + self.fd, + ioslice.as_ptr() as i32, + ioslice.len() as i32, + readn_ptr) + }; + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx, readn)) + } +} + From 8708e6283d2eb958c36bee26cb635e1ec042e2a5 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Sat, 24 May 2025 15:58:22 +0800 Subject: [PATCH 19/45] support file descriptor close method. --- src/wasi/close.rs | 2 +- src/wasi/descriptor.rs | 140 +++++++++++++++++++++++++++++++++++------ src/wasi/link.rs | 2 +- src/wasi/mkdir.rs | 2 +- src/wasi/preview_1.rs | 8 +++ src/wasi/rename.rs | 2 +- src/wasi/symlink.rs | 2 +- src/wasi/unlink.rs | 2 +- 8 files changed, 134 insertions(+), 26 deletions(-) diff --git a/src/wasi/close.rs b/src/wasi/close.rs index 3b7bc93..3bf77d8 100644 --- a/src/wasi/close.rs +++ b/src/wasi/close.rs @@ -1,5 +1,5 @@ use javy_plugin_api::javy::{ - quickjs::{Value}, + quickjs::Value, Args }; use anyhow::{anyhow, bail, Result}; diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index fab8f86..cd927c7 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -1,12 +1,7 @@ use std::sync::Arc; use javy_plugin_api::javy::{ quickjs::{ - prelude::{MutFn, Rest}, - Ctx, - Function, - Object as JObject, - TypedArray, - Value + prelude::{MutFn, Rest}, Ctx, Function, Object as JObject, TypedArray, Value }, to_js_error }; use anyhow::{anyhow, bail, Ok, Result}; @@ -33,40 +28,74 @@ impl Descriptor { }); let desc = JObject::new(cx.clone())?; desc.set("fd", fd)?; + // Set the read method + let descriptor_clone = descriptor.clone(); desc.set("read", Function::new( cx.clone(), MutFn::new(move |cx: Ctx<'js>, args: Rest>| { - descriptor.clone().read(cx.clone(), args) + descriptor_clone.clone().read(cx.clone(), args) + .map_err(|e| to_js_error(cx.clone(), e)) + }), + )?)?; + // Set the write method + let descriptor_clone = descriptor.clone(); + desc.set("write", Function::new( + cx.clone(), + MutFn::new(move |cx: Ctx<'js>, args: Rest>| { + descriptor_clone.clone().write(cx.clone(), args) + .map_err(|e| to_js_error(cx.clone(), e)) + }), + )?)?; + // Set the close method + let descriptor_clone = descriptor.clone(); + desc.set("close", Function::new( + cx.clone(), + MutFn::new(move |cx: Ctx<'js>, args: Rest>| { + descriptor_clone.clone().write(cx.clone(), args) .map_err(|e| to_js_error(cx.clone(), e)) }), )?)?; Ok(Value::from_object(desc)) } + /// The read method + /// # Arguments fn read<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { - let args_pat: &[Value<'_>]= &args.0; - let [ - buffer, - size, - .. - ] = args_pat else { + if args.0.len() < 1 { bail!( - "read expects 2 parameters: the buffer and size, Got: {} parameters.", + "read expects 1 parameters: the buffer and size[option], Got: {} parameters.", args.len() ); - }; + } + let buffer = &args.0[0]; + let null = Value::new_null(cx.clone()); + let mut size = &null; + if args.0.len() > 2 { + size = &args.0[1]; + } + let mut readn: i32 = 0; let readn_ptr: i32 = &mut readn as *mut i32 as i32; - let array: &TypedArray<'_, u8> = buffer.as_array() - .ok_or_else(|| anyhow!("buffer must be a array"))? - .as_typed_array() - .ok_or_else(|| anyhow!("buffer must be a Uint8Array"))?; + let array = buffer.as_object() + .ok_or_else(|| anyhow!("buffer must be a array"))?; + let array: &TypedArray<'_, u8> = array.as_typed_array() + .ok_or_else(|| anyhow!("buffer must be a typed array"))?; let mut array_raw = array.as_raw() .ok_or_else(|| anyhow!("buffer get raw ptr error."))?; + let size = size.as_int(); + let size = if let Some(size) = size { + if size > array_raw.len as i32 { + array_raw.len as u32 + } else { + size as u32 + } + } else { + array_raw.len as u32 + }; let ioslice = vec![ Iovec { buf: unsafe {array_raw.ptr.as_mut() as *mut u8 as i32}, - buf_len: size.as_int().ok_or_else(|| anyhow!("size must be a int"))? as u32, + buf_len: size, } ]; let rs = unsafe { @@ -77,7 +106,78 @@ impl Descriptor { readn_ptr) }; process_error(cx.clone(), rs)?; + if rs != 0 { + readn = -rs; + } Ok(Value::new_int(cx, readn)) } + + /// The write method + /// Uint8Array as the buffer the first parameter + /// size as the second parameter, it's optional, default is the length of the buffer + fn write<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { + if args.0.len() < 1 { + bail!( + "write expects 1 parameters: the buffer and size[option], Got: {} parameters.", + args.len() + ); + } + let buffer = &args.0[0]; + let null = Value::new_null(cx.clone()); + let mut size = &null; + if args.0.len() > 2 { + size = &args.0[1]; + } + + let mut writen: i32 = 0; + let writen_ptr: i32 = &mut writen as *mut i32 as i32; + let array = buffer.as_object() + .ok_or_else(|| anyhow!("buffer must be a array"))?; + let array: &TypedArray<'_, u8> = array.as_typed_array() + .ok_or_else(|| anyhow!("buffer must be a typed array"))?; + let mut array_raw = array.as_raw() + .ok_or_else(|| anyhow!("buffer get raw ptr error."))?; + let size = size.as_int(); + let size = if let Some(size) = size { + if size > array_raw.len as i32 { + array_raw.len as u32 + } else { + size as u32 + } + } else { + array_raw.len as u32 + }; + let ioslice = vec![ + Iovec { + buf: unsafe {array_raw.ptr.as_mut() as *mut u8 as i32}, + buf_len: size, + } + ]; + let rs = unsafe { + preview_1::fd_write( + self.fd, + ioslice.as_ptr() as i32, + ioslice.len() as i32, + writen_ptr) + }; + process_error(cx.clone(), rs)?; + if rs != 0 { + writen = -rs; + } + Ok(Value::new_int(cx, writen)) + } + + /// The close method + /// Uint8Array as the buffer the first parameter + /// size as the second parameter, it's optional, default is the length of the buffer + fn close<'js>(self: Arc, cx: Ctx<'js>, _: Rest>) -> Result> { + let rs = unsafe { + preview_1::fd_close( + self.fd + ) + }; + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx, rs)) + } } diff --git a/src/wasi/link.rs b/src/wasi/link.rs index 4b69c87..d36f908 100644 --- a/src/wasi/link.rs +++ b/src/wasi/link.rs @@ -1,5 +1,5 @@ use javy_plugin_api::javy::{ - quickjs::{Object as JObject, Value}, + quickjs::Value, Args }; use anyhow::{anyhow, bail, Result}; diff --git a/src/wasi/mkdir.rs b/src/wasi/mkdir.rs index ac710d6..bdec3ba 100644 --- a/src/wasi/mkdir.rs +++ b/src/wasi/mkdir.rs @@ -1,5 +1,5 @@ use javy_plugin_api::javy::{ - quickjs::{Object as JObject, Value}, + quickjs::Value, Args }; use anyhow::{anyhow, bail, Result}; diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index 61520ba..b54bcf5 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -63,6 +63,14 @@ unsafe extern "C" { readn_ptr: i32, ) -> i32; + #[link_name = "fd_write"] + pub unsafe fn fd_write( + fd: i32, + iovec_slice: i32, + iovec_len: i32, + writen_ptr: i32, + ) -> i32; + #[link_name = "fd_prestat_dir_name"] pub unsafe fn fd_prestat_dir_name( fd: i32, diff --git a/src/wasi/rename.rs b/src/wasi/rename.rs index e34713b..712e787 100644 --- a/src/wasi/rename.rs +++ b/src/wasi/rename.rs @@ -1,5 +1,5 @@ use javy_plugin_api::javy::{ - quickjs::{Object as JObject, Value}, + quickjs::Value, Args }; use anyhow::{anyhow, bail, Result}; diff --git a/src/wasi/symlink.rs b/src/wasi/symlink.rs index 352da54..34a25e4 100644 --- a/src/wasi/symlink.rs +++ b/src/wasi/symlink.rs @@ -1,5 +1,5 @@ use javy_plugin_api::javy::{ - quickjs::{Object as JObject, Value}, + quickjs::Value, Args }; use anyhow::{anyhow, bail, Result}; diff --git a/src/wasi/unlink.rs b/src/wasi/unlink.rs index 122a097..9f9de52 100644 --- a/src/wasi/unlink.rs +++ b/src/wasi/unlink.rs @@ -1,5 +1,5 @@ use javy_plugin_api::javy::{ - quickjs::{Object as JObject, Value}, + quickjs::Value, Args }; use anyhow::{anyhow, bail, Result}; From 7a3268bdbc9d95c6236127479a54e7ccbe558d63 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Sun, 25 May 2025 15:36:44 +0800 Subject: [PATCH 20/45] support seek and advise --- src/wasi/descriptor.rs | 100 ++++++++++++++++++++++++++++++++++++++++- src/wasi/preview_1.js | 16 +++++++ src/wasi/preview_1.rs | 20 ++++++++- 3 files changed, 133 insertions(+), 3 deletions(-) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index cd927c7..6005732 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -51,7 +51,27 @@ impl Descriptor { desc.set("close", Function::new( cx.clone(), MutFn::new(move |cx: Ctx<'js>, args: Rest>| { - descriptor_clone.clone().write(cx.clone(), args) + descriptor_clone.clone().close(cx.clone(), args) + .map_err(|e| to_js_error(cx.clone(), e)) + }), + )?)?; + + // Set the seek method + let descriptor_clone = descriptor.clone(); + desc.set("seek", Function::new( + cx.clone(), + MutFn::new(move |cx: Ctx<'js>, args: Rest>| { + descriptor_clone.clone().seek(cx.clone(), args) + .map_err(|e| to_js_error(cx.clone(), e)) + }), + )?)?; + + // Set the advise method + let descriptor_clone = descriptor.clone(); + desc.set("advise", Function::new( + cx.clone(), + MutFn::new(move |cx: Ctx<'js>, args: Rest>| { + descriptor_clone.clone().advise(cx.clone(), args) .map_err(|e| to_js_error(cx.clone(), e)) }), )?)?; @@ -167,6 +187,84 @@ impl Descriptor { Ok(Value::new_int(cx, writen)) } + fn advise<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { + let args_pat: &[Value<'_>]= &args.0; + let [ + offset, + len, + advice, + .. + ] = args_pat else { + bail!( + "advice expects 3 parameters: the offset, len and advice, Got: {} parameters.", + args.len() + ); + }; + let offset: u64 = if offset.is_int() { + offset.as_int().ok_or_else(|| anyhow!("offset must be a int"))? as _ + } else { + offset.as_big_int() + .map(|o| o.clone()) + .ok_or_else(|| anyhow!("offset must be a int"))? + .to_i64()? as _ + }; + let len: u64 = if len.is_int() { + len.as_int().ok_or_else(|| anyhow!("len must be a int"))? as _ + } else { + len.as_big_int() + .map(|o| o.clone()) + .ok_or_else(|| anyhow!("len must be a int"))? + .to_i64()? as _ + }; + let advice: i32 = advice.as_int().ok_or_else(|| anyhow!("advice must be a int"))?; + let rs = unsafe { + preview_1::fd_advise( + self.fd, + offset, + len, + advice + ) + }; + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx, rs)) + } + + fn seek<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { + let args_pat: &[Value<'_>]= &args.0; + let [ + offset, + whence, + .. + ] = args_pat else { + bail!( + "advice expects 2 parameters: the offset and whence, Got: {} parameters.", + args.len() + ); + }; + let offset: u64 = if offset.is_int() { + offset.as_int().ok_or_else(|| anyhow!("offset must be a int"))? as _ + } else { + offset.as_big_int() + .map(|o| o.clone()) + .ok_or_else(|| anyhow!("offset must be a int"))? + .to_i64()? as _ + }; + + let whence: i32 = whence.as_int().ok_or_else(|| anyhow!("advice must be a int"))?; + let mut fsize: i64 = 0; + let fsize_ptr: i32 = &mut fsize as *mut i64 as i32; + let rs = unsafe { + preview_1::fd_seek( + self.fd, + offset, + whence, + fsize_ptr + ) + }; + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx, rs)) + } + /// The close method /// Uint8Array as the buffer the first parameter /// size as the second parameter, it's optional, default is the length of the buffer diff --git a/src/wasi/preview_1.js b/src/wasi/preview_1.js index 997b14b..5750618 100644 --- a/src/wasi/preview_1.js +++ b/src/wasi/preview_1.js @@ -52,6 +52,20 @@ SOCK_ACCEPT: 0x20000000, } + const Advise = { + Normal: 0x0, + Sequential: 0x1, + Random: 0x2, + Willneed: 0x3, + Dontneed: 0x4, + } + + const Whence = { + SeekSet: 0x0, + SeekCur: 0x1, + SeekEnd: 0x2, + } + const Lookupflags = { SYMLINK_FOLLOW: 0x1, } @@ -318,6 +332,8 @@ link, rename, stat, + Advise, + Whence, errno: () => globalThis.lastErr.errno, error: () => globalThis.lastErr.error, }; diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index b54bcf5..e449c30 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -99,8 +99,6 @@ unsafe extern "C" { new_path_len: i32, ) -> i32; - - #[link_name = "path_filestat_get"] pub unsafe fn path_filestat_get( dirfd: i32, @@ -109,4 +107,22 @@ unsafe extern "C" { path_len: i32, stat_ptr: i32, ) -> i32; + + #[link_name = "fd_advise"] + pub unsafe fn fd_advise( + fd: i32, + offset: u64, + len: u64, + advice: i32, + ) -> i32; + + #[link_name = "fd_seek"] + pub unsafe fn fd_seek( + fd: i32, + offset: u64, + whence: i32, + fsize: i32, + ) -> i32; + + } From aa573f028acdd5cc446d4466cb47eb958357ae15 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Sun, 25 May 2025 21:25:58 +0800 Subject: [PATCH 21/45] refactor: use macro for extract i64 from jsvalue --- src/wasi/macros.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/wasi/macros.rs diff --git a/src/wasi/macros.rs b/src/wasi/macros.rs new file mode 100644 index 0000000..7f5e935 --- /dev/null +++ b/src/wasi/macros.rs @@ -0,0 +1,13 @@ + +macro_rules! jsvalue2int64 { + ($i: ident) => { + if $i.is_int() { + $i.as_int().ok_or_else(|| anyhow!("{} must be a int", stringify!($i)))? as _ + } else { + $i.as_big_int() + .map(|o| o.clone()) + .ok_or_else(|| anyhow!("{} must be a int", stringify!($i)))? + .to_i64()? as _ + }; + }; +} \ No newline at end of file From a31d148dcc6a8fa9484cd01156aa223f7432bcbe Mon Sep 17 00:00:00 2001 From: Joinhack Date: Sun, 25 May 2025 21:27:05 +0800 Subject: [PATCH 22/45] support sync and fdatasync for descriptor --- src/wasi/descriptor.rs | 121 +++++++++++++++++------------------------ src/wasi/mod.rs | 6 +- src/wasi/open.rs | 22 +------- src/wasi/preview_1.rs | 18 +++++- 4 files changed, 73 insertions(+), 94 deletions(-) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index 6005732..3cb3f1d 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -10,8 +10,6 @@ use super::{preview_1, process_error}; pub struct Descriptor { fd: i32, - errno: i32, - error: Option, } pub struct Iovec { @@ -23,63 +21,41 @@ impl Descriptor { pub fn new<'js>(cx: Ctx<'js>, fd: i32) -> Result> { let descriptor = Arc::new(Descriptor { fd, - errno: 0, - error: None, }); let desc = JObject::new(cx.clone())?; desc.set("fd", fd)?; + macro_rules! bind_method { + ($name:ident) => { + let descriptor_clone = descriptor.clone(); + desc.set(stringify!($name), Function::new( + cx.clone(), + MutFn::new(move |cx: Ctx<'js>, args: Rest>| { + descriptor_clone.clone().$name(cx.clone(), args) + .map_err(|e| to_js_error(cx.clone(), e)) + }), + )?)?; + }; + } // Set the read method - let descriptor_clone = descriptor.clone(); - desc.set("read", Function::new( - cx.clone(), - MutFn::new(move |cx: Ctx<'js>, args: Rest>| { - descriptor_clone.clone().read(cx.clone(), args) - .map_err(|e| to_js_error(cx.clone(), e)) - }), - )?)?; + bind_method!(read); // Set the write method - let descriptor_clone = descriptor.clone(); - desc.set("write", Function::new( - cx.clone(), - MutFn::new(move |cx: Ctx<'js>, args: Rest>| { - descriptor_clone.clone().write(cx.clone(), args) - .map_err(|e| to_js_error(cx.clone(), e)) - }), - )?)?; + bind_method!(write); // Set the close method - let descriptor_clone = descriptor.clone(); - desc.set("close", Function::new( - cx.clone(), - MutFn::new(move |cx: Ctx<'js>, args: Rest>| { - descriptor_clone.clone().close(cx.clone(), args) - .map_err(|e| to_js_error(cx.clone(), e)) - }), - )?)?; - + bind_method!(close); + // Set the fsync method + bind_method!(fsync); + // Set the fdatsync method + bind_method!(fdatasync); // Set the seek method - let descriptor_clone = descriptor.clone(); - desc.set("seek", Function::new( - cx.clone(), - MutFn::new(move |cx: Ctx<'js>, args: Rest>| { - descriptor_clone.clone().seek(cx.clone(), args) - .map_err(|e| to_js_error(cx.clone(), e)) - }), - )?)?; - + bind_method!(seek); // Set the advise method - let descriptor_clone = descriptor.clone(); - desc.set("advise", Function::new( - cx.clone(), - MutFn::new(move |cx: Ctx<'js>, args: Rest>| { - descriptor_clone.clone().advise(cx.clone(), args) - .map_err(|e| to_js_error(cx.clone(), e)) - }), - )?)?; + bind_method!(advise); Ok(Value::from_object(desc)) } /// The read method - /// # Arguments + /// Uint8Array as the buffer the first parameter + /// size as the second parameter, it's optional, default is the length of the buffer fn read<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { if args.0.len() < 1 { bail!( @@ -200,22 +176,8 @@ impl Descriptor { args.len() ); }; - let offset: u64 = if offset.is_int() { - offset.as_int().ok_or_else(|| anyhow!("offset must be a int"))? as _ - } else { - offset.as_big_int() - .map(|o| o.clone()) - .ok_or_else(|| anyhow!("offset must be a int"))? - .to_i64()? as _ - }; - let len: u64 = if len.is_int() { - len.as_int().ok_or_else(|| anyhow!("len must be a int"))? as _ - } else { - len.as_big_int() - .map(|o| o.clone()) - .ok_or_else(|| anyhow!("len must be a int"))? - .to_i64()? as _ - }; + let offset: u64 = jsvalue2int64!(offset); + let len: u64 = jsvalue2int64!(len); let advice: i32 = advice.as_int().ok_or_else(|| anyhow!("advice must be a int"))?; let rs = unsafe { preview_1::fd_advise( @@ -241,14 +203,7 @@ impl Descriptor { args.len() ); }; - let offset: u64 = if offset.is_int() { - offset.as_int().ok_or_else(|| anyhow!("offset must be a int"))? as _ - } else { - offset.as_big_int() - .map(|o| o.clone()) - .ok_or_else(|| anyhow!("offset must be a int"))? - .to_i64()? as _ - }; + let offset: u64 = jsvalue2int64!(offset); let whence: i32 = whence.as_int().ok_or_else(|| anyhow!("advice must be a int"))?; let mut fsize: i64 = 0; @@ -277,5 +232,29 @@ impl Descriptor { process_error(cx.clone(), rs)?; Ok(Value::new_int(cx, rs)) } + + /// The fsync method + /// Wait for the data and metadata to be written + fn fsync<'js>(self: Arc, cx: Ctx<'js>, _: Rest>) -> Result> { + let rs = unsafe { + preview_1::fd_sync( + self.fd + ) + }; + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx, rs)) + } + + /// The fdatasync method + /// Wait for the data to be written + fn fdatasync<'js>(self: Arc, cx: Ctx<'js>, _: Rest>) -> Result> { + let rs = unsafe { + preview_1::fd_datasync( + self.fd + ) + }; + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx, rs)) + } } diff --git a/src/wasi/mod.rs b/src/wasi/mod.rs index 17adebb..6c76530 100644 --- a/src/wasi/mod.rs +++ b/src/wasi/mod.rs @@ -1,10 +1,11 @@ - use javy_plugin_api::javy::{ quickjs::{Ctx, Object as JObject, Value}, Args }; use anyhow::{anyhow, bail, Result}; +#[macro_use] +mod macros; mod preview_1; mod error; mod open; @@ -117,4 +118,5 @@ impl Into<&str> for FileType { _ => unimplemented!("FileType not implemented"), } } -} \ No newline at end of file +} + diff --git a/src/wasi/open.rs b/src/wasi/open.rs index c930297..db4f4d8 100644 --- a/src/wasi/open.rs +++ b/src/wasi/open.rs @@ -34,26 +34,8 @@ pub fn wasi_preview1_open<'a>(args: Args<'a>) -> Result> { .ok_or_else(|| anyhow!("fd_lookup_flags must be a number"))?; let oflags = fd_oflags.as_int() .ok_or_else(|| anyhow!("oflags must be a number"))?; - let fs_rights_base = if fd_rights.is_int() { - fd_rights.as_int() - .map(|i| i as i64) - .ok_or_else(|| anyhow!("fd_rights must be a number"))? - } else { - fd_rights.as_big_int() - .map(|x| x.clone()) - .ok_or_else(|| anyhow!("fd_rights must be a number"))? - .to_i64()? - }; - let fd_rights_inherited = if fd_rights_inherited.is_int() { - fd_rights_inherited.as_int() - .map(|i| i as i64) - .ok_or_else(|| anyhow!("fd_rights_inherited must be a number"))? - } else { - fd_rights_inherited.as_big_int() - .map(|x| x.clone()) - .ok_or_else(|| anyhow!("fd_rights_inherited must be a number"))? - .to_i64()? - }; + let fs_rights_base = jsvalue2int64!(fd_rights); + let fd_rights_inherited = jsvalue2int64!(fd_rights_inherited); let fdflags = fd_flags.as_int() .ok_or_else(|| anyhow!("fdflags must be a number"))?; let opened_fd_ptr = (&mut opened_fd as *mut i32) as i32; diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index e449c30..d075063 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -49,6 +49,16 @@ unsafe extern "C" { fd: i32, ) -> i32; + #[link_name = "fd_sync"] + pub unsafe fn fd_sync( + fd: i32, + ) -> i32; + + #[link_name = "fd_datasync"] + pub unsafe fn fd_datasync( + fd: i32, + ) -> i32; + #[link_name = "fd_prestat_get"] pub unsafe fn fd_prestat_get( fd: i32, @@ -116,7 +126,7 @@ unsafe extern "C" { advice: i32, ) -> i32; - #[link_name = "fd_seek"] + #[link_name = "fd_seek"] pub unsafe fn fd_seek( fd: i32, offset: u64, @@ -124,5 +134,11 @@ unsafe extern "C" { fsize: i32, ) -> i32; + #[link_name = "fd_allocate"] + pub unsafe fn fd_allocate( + fd: i32, + offset: u64, + len: u64, + ) -> i32; } From 67c9af8f6ab0bfe75b98b6e35eea610602fc29ce Mon Sep 17 00:00:00 2001 From: Joinhack Date: Mon, 26 May 2025 11:46:42 +0800 Subject: [PATCH 23/45] support stat for descriptor --- src/wasi/descriptor.rs | 51 +++++++++++++++++++++++++++++++++++++++++- src/wasi/preview_1.rs | 6 +++++ src/wasi/stat.rs | 30 +++++++++++++++---------- 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index 3cb3f1d..58ef5b9 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -6,7 +6,7 @@ use javy_plugin_api::javy::{ }; use anyhow::{anyhow, bail, Ok, Result}; -use super::{preview_1, process_error}; +use super::{preview_1, process_error, stat::filestate_to_jsobject, Filestat}; pub struct Descriptor { fd: i32, @@ -50,6 +50,8 @@ impl Descriptor { bind_method!(seek); // Set the advise method bind_method!(advise); + // Set the stat method + bind_method!(stat); Ok(Value::from_object(desc)) } @@ -163,6 +165,18 @@ impl Descriptor { Ok(Value::new_int(cx, writen)) } + /// The advise method + /// This method is used to give advice to the file descriptor. + /// The first parameter is the offset, the second parameter is the length, + /// and the third parameter is the advice. + /// The advice can be one of the following values: + /// - `0`: Normal access. + /// - `1`: Random access. + /// - `2`: Sequential access. + /// - `3`: Will need to read the data. + /// - `4`: Will need to write the data. + /// The offset is the number of bytes to offset from the beginning of the file, + /// and the length is the number of bytes to advise. fn advise<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { let args_pat: &[Value<'_>]= &args.0; let [ @@ -191,6 +205,14 @@ impl Descriptor { Ok(Value::new_int(cx, rs)) } + /// The seek method + /// This method is used to change the current position of the file descriptor. + /// The first parameter is the offset, the second parameter is the whence. + /// The whence can be one of the following values: + /// - `0`: Seek from the beginning of the file. + /// - `1`: Seek from the current position of the file. + /// - `2`: Seek from the end of the file. + /// The offset is the number of bytes to seek. fn seek<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { let args_pat: &[Value<'_>]= &args.0; let [ @@ -256,5 +278,32 @@ impl Descriptor { process_error(cx.clone(), rs)?; Ok(Value::new_int(cx, rs)) } + + /// The stat method + /// This method is used to get the file status of the file descriptor. + /// It returns a JavaScript object with the following properties: + /// - `filetype`: The type of the file. + /// - `filetype_desc`: The description of the file type. + /// - `filesize`: The size of the file in bytes. + /// - `atime`: The last access time of the file. + /// - `mtime`: The last modification time of the file. + /// - `ctime`: The last change time of the file. + fn stat<'js>(self: Arc, cx: Ctx<'js>, _: Rest>) -> Result> { + let mut fd_stat: Filestat = Default::default(); + let fd_stat_ptr = &mut fd_stat as *mut _ as i32; + let rs = unsafe { + preview_1::fd_filestat_get( + self.fd, + fd_stat_ptr, + ) + }; + if rs == 0 { + let stat = filestate_to_jsobject(cx.clone(), &fd_stat)?; + Ok(Value::from_object(stat)) + } else { + process_error(cx.clone(), rs)?; + Ok(Value::new_null(cx.clone())) + } + } } diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index d075063..65b41bc 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -140,5 +140,11 @@ unsafe extern "C" { offset: u64, len: u64, ) -> i32; + + #[link_name = "fd_filestat_get"] + pub unsafe fn fd_filestat_get( + fd: i32, + stat_ptr: i32 + ) -> i32; } diff --git a/src/wasi/stat.rs b/src/wasi/stat.rs index 52a8eb1..5f21ac2 100644 --- a/src/wasi/stat.rs +++ b/src/wasi/stat.rs @@ -1,5 +1,5 @@ use javy_plugin_api::javy::{ - quickjs::{Object as JObject, Value}, + quickjs::{Object as JObject, Value, Ctx}, Args }; use anyhow::{anyhow, bail, Result}; @@ -40,20 +40,26 @@ pub fn wasi_preview1_path_filestat_get(args: Args<'_>) -> Result> { fd_stat_ptr, ) }; - let rs_obj = JObject::new(cx.clone())?; if rs == 0 { - let stat = JObject::new(cx.clone())?; - stat.set("filetype", fd_stat.filetype)?; - let filetype: &str = FileType(fd_stat.filetype).into(); - stat.set("filetype_desc", filetype)?; - stat.set("filesize", fd_stat.size)?; - stat.set("access_time", fd_stat.atim)?; - stat.set("modification_time", fd_stat.mtim)?; - stat.set("creation_time", fd_stat.ctim)?; - rs_obj.set("stat", stat)?; - Ok(Value::from_object(rs_obj)) + let stat = filestate_to_jsobject(cx.clone(), &fd_stat)?; + Ok(Value::from_object(stat)) } else { process_error(cx.clone(), rs)?; Ok(Value::new_null(cx.clone())) } +} + +pub fn filestate_to_jsobject<'js>( + cx: Ctx<'js>, + fd_stat: &Filestat, +) -> Result> { + let stat = JObject::new(cx.clone())?; + stat.set("filetype", fd_stat.filetype)?; + let filetype: &str = FileType(fd_stat.filetype).into(); + stat.set("filetype_desc", filetype)?; + stat.set("filesize", fd_stat.size)?; + stat.set("access_time", fd_stat.atim)?; + stat.set("modification_time", fd_stat.mtim)?; + stat.set("creation_time", fd_stat.ctim)?; + Ok(stat) } \ No newline at end of file From 7d83d2f4690ca08459aec914073c67d704c51821 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Mon, 26 May 2025 11:54:00 +0800 Subject: [PATCH 24/45] support ftruncate for descriptor --- src/wasi/descriptor.rs | 28 ++++++++++++++++++++++++++++ src/wasi/preview_1.rs | 6 ++++++ 2 files changed, 34 insertions(+) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index 58ef5b9..94351db 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -12,6 +12,7 @@ pub struct Descriptor { fd: i32, } +#[allow(dead_code)] pub struct Iovec { pub buf: i32, pub buf_len: u32, @@ -52,6 +53,8 @@ impl Descriptor { bind_method!(advise); // Set the stat method bind_method!(stat); + // Set the ftruncate method + bind_method!(ftruncate); Ok(Value::from_object(desc)) } @@ -305,5 +308,30 @@ impl Descriptor { Ok(Value::new_null(cx.clone())) } } + + /// The ftruncate method + /// This method is used to truncate the file descriptor to the given length. + /// The first parameter is the length to truncate the file to. + fn ftruncate<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { + let args_pat: &[Value<'_>]= &args.0; + let [ + len, + .. + ] = args_pat else { + bail!( + "ftruncate expects 1 parameters: the offset and whence, Got: {} parameters.", + args.len() + ); + }; + let len = jsvalue2int64!(len); + let rs = unsafe { + preview_1::fd_filestat_set_size( + self.fd, + len + ) + }; + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx, rs)) + } } diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index 65b41bc..6cf22cd 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -146,5 +146,11 @@ unsafe extern "C" { fd: i32, stat_ptr: i32 ) -> i32; + + #[link_name = "fd_filestat_set_size"] + pub unsafe fn fd_filestat_set_size( + fd: i32, + stat_ptr: i32 + ) -> i32; } From 6b31b6a8cc887c7903ad3853f5e93b7f406e01ce Mon Sep 17 00:00:00 2001 From: Joinhack Date: Mon, 26 May 2025 12:03:13 +0800 Subject: [PATCH 25/45] support allocate for descriptor --- src/wasi/descriptor.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index 94351db..784d6e8 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -55,6 +55,8 @@ impl Descriptor { bind_method!(stat); // Set the ftruncate method bind_method!(ftruncate); + // Set the allocate method + bind_method!(allocate); Ok(Value::from_object(desc)) } @@ -309,6 +311,36 @@ impl Descriptor { } } + /// The allocate method + /// This method is used to allocate space in the file descriptor. + /// The first parameter is the offset, the second parameter is the length. + /// The offset is the number of bytes to offset from the beginning of the file, + /// and the length is the number of bytes to allocate. + fn allocate<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { + let args_pat: &[Value<'_>]= &args.0; + let [ + offset, + len, + .. + ] = args_pat else { + bail!( + "allocate expects 2 parameters: the offset and length, Got: {} parameters.", + args.len() + ); + }; + let offset: u64 = jsvalue2int64!(offset); + let len: u64 = jsvalue2int64!(len); + let rs = unsafe { + preview_1::fd_allocate( + self.fd, + offset, + len + ) + }; + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx, rs)) + } + /// The ftruncate method /// This method is used to truncate the file descriptor to the given length. /// The first parameter is the length to truncate the file to. From 0cfc76c0b1a83738b769554fb095bed47ca38128 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Mon, 26 May 2025 12:04:14 +0800 Subject: [PATCH 26/45] rename fd --- src/wasi/descriptor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index 784d6e8..abba0a6 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -24,7 +24,7 @@ impl Descriptor { fd, }); let desc = JObject::new(cx.clone())?; - desc.set("fd", fd)?; + desc.set("rawfd", fd)?; macro_rules! bind_method { ($name:ident) => { let descriptor_clone = descriptor.clone(); From 82c6a05945836b78a7a9dff401534b37fcd1e202 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Mon, 26 May 2025 12:10:16 +0800 Subject: [PATCH 27/45] comment for function --- src/wasi/close.rs | 1 + src/wasi/link.rs | 8 +++++++- src/wasi/rename.rs | 7 ++++++- src/wasi/rmdir.rs | 2 ++ src/wasi/stat.rs | 5 +++++ src/wasi/unlink.rs | 2 ++ 6 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/wasi/close.rs b/src/wasi/close.rs index 3bf77d8..ed00bd8 100644 --- a/src/wasi/close.rs +++ b/src/wasi/close.rs @@ -7,6 +7,7 @@ use anyhow::{anyhow, bail, Result}; use super::{preview_1, process_error}; /// This function is used to close a file descriptor. +/// `fd`: The file descriptor to close. pub fn wasi_preview1_close<'a>(args: Args<'a>) -> Result> { let (cx, args) = args.release(); let args_pat: &[Value<'_>]= &args.0; diff --git a/src/wasi/link.rs b/src/wasi/link.rs index d36f908..d93dff8 100644 --- a/src/wasi/link.rs +++ b/src/wasi/link.rs @@ -6,7 +6,13 @@ use anyhow::{anyhow, bail, Result}; use super::{preview_1, process_error}; - +/// This function is used to link a file at the given path to a new path. +/// It is used to create a hard link from one file to another. +/// - `old_dirfd`: The directory file descriptor of the old file. +/// - `fd_lookup_flags`: Flags for looking up the file descriptor. +/// - `old_path`: The path of the old file. +/// - `new_dirfd`: The directory file descriptor of the new file. +/// - `new_path`: The path of the new file. pub fn wasi_preview1_path_link(args: Args<'_>) -> Result> { let (cx, args) = args.release(); let args_pat: &[Value<'_>]= &args.0; diff --git a/src/wasi/rename.rs b/src/wasi/rename.rs index 712e787..5069cb8 100644 --- a/src/wasi/rename.rs +++ b/src/wasi/rename.rs @@ -6,7 +6,12 @@ use anyhow::{anyhow, bail, Result}; use super::{preview_1, process_error}; - +/// This function is used to link a file at the given path to a new path. +/// It is used to rename a file from one path to another. +/// - `old_dirfd`: The directory file descriptor of the old file. +/// - `old_path`: The path of the old file. +/// - `new_dirfd`: The directory file descriptor of the new file. +/// - `new_path`: The path of the new file. pub fn wasi_preview1_path_rename(args: Args<'_>) -> Result> { let (cx, args) = args.release(); let args_pat: &[Value<'_>]= &args.0; diff --git a/src/wasi/rmdir.rs b/src/wasi/rmdir.rs index c22ef07..822f923 100644 --- a/src/wasi/rmdir.rs +++ b/src/wasi/rmdir.rs @@ -10,6 +10,8 @@ use super::{preview_1, process_error}; /// Remove a directory at the given path. /// This function is used to remove a directory at the given path. /// It is used to remove a directory at the given path. +/// - `old_dirfd`: The directory file descriptor of the old directory. +/// - `path`: The path of the directory to remove. pub fn wasi_preview1_path_remove_directory(args: Args<'_>) -> Result> { let (cx, args) = args.release(); let args_pat: &[Value<'_>]= &args.0; diff --git a/src/wasi/stat.rs b/src/wasi/stat.rs index 5f21ac2..cf2f960 100644 --- a/src/wasi/stat.rs +++ b/src/wasi/stat.rs @@ -6,6 +6,11 @@ use anyhow::{anyhow, bail, Result}; use super::{preview_1, process_error, FileType, Filestat}; +/// Get the file status of a file at the given path. +/// This function is used to get the file status of a file at the given path. +/// - `dirfd`: The directory file descriptor of the file. +/// - `lookup_flags`: Flags for looking up the file descriptor. +/// - `path`: The path of the file to get the status of. pub fn wasi_preview1_path_filestat_get(args: Args<'_>) -> Result> { let (cx, args) = args.release(); let args_pat: &[Value<'_>]= &args.0; diff --git a/src/wasi/unlink.rs b/src/wasi/unlink.rs index 9f9de52..1ce1a6b 100644 --- a/src/wasi/unlink.rs +++ b/src/wasi/unlink.rs @@ -8,6 +8,8 @@ use super::{preview_1, process_error}; /// Unlink a file at the given path. +/// - `dirfd`: The directory file descriptor of the directory containing the file. +/// - `path`: The path of the file to unlink. pub fn wasi_preview1_path_unlink_file(args: Args<'_>) -> Result> { let (cx, args) = args.release(); let args_pat: &[Value<'_>]= &args.0; From f97321343710845a23503ebb16692542fde80818 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Mon, 26 May 2025 12:11:44 +0800 Subject: [PATCH 28/45] refactor descriptor struct --- src/wasi/descriptor.rs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index abba0a6..e0dac66 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -8,10 +8,10 @@ use anyhow::{anyhow, bail, Ok, Result}; use super::{preview_1, process_error, stat::filestate_to_jsobject, Filestat}; -pub struct Descriptor { - fd: i32, -} +pub struct Descriptor(i32); + +/// This struct is used to represent an I/O vector. #[allow(dead_code)] pub struct Iovec { pub buf: i32, @@ -19,10 +19,11 @@ pub struct Iovec { } impl Descriptor { + /// Create a new file descriptor object. + /// This function creates a new file descriptor object with the given file descriptor. + /// The file descriptor is used to perform operations on the file. pub fn new<'js>(cx: Ctx<'js>, fd: i32) -> Result> { - let descriptor = Arc::new(Descriptor { - fd, - }); + let descriptor = Arc::new(Descriptor(fd)); let desc = JObject::new(cx.clone())?; desc.set("rawfd", fd)?; macro_rules! bind_method { @@ -103,7 +104,7 @@ impl Descriptor { ]; let rs = unsafe { preview_1::fd_read( - self.fd, + self.0, ioslice.as_ptr() as i32, ioslice.len() as i32, readn_ptr) @@ -158,7 +159,7 @@ impl Descriptor { ]; let rs = unsafe { preview_1::fd_write( - self.fd, + self.0, ioslice.as_ptr() as i32, ioslice.len() as i32, writen_ptr) @@ -200,7 +201,7 @@ impl Descriptor { let advice: i32 = advice.as_int().ok_or_else(|| anyhow!("advice must be a int"))?; let rs = unsafe { preview_1::fd_advise( - self.fd, + self.0, offset, len, advice @@ -237,7 +238,7 @@ impl Descriptor { let fsize_ptr: i32 = &mut fsize as *mut i64 as i32; let rs = unsafe { preview_1::fd_seek( - self.fd, + self.0, offset, whence, fsize_ptr @@ -253,7 +254,7 @@ impl Descriptor { fn close<'js>(self: Arc, cx: Ctx<'js>, _: Rest>) -> Result> { let rs = unsafe { preview_1::fd_close( - self.fd + self.0 ) }; process_error(cx.clone(), rs)?; @@ -265,7 +266,7 @@ impl Descriptor { fn fsync<'js>(self: Arc, cx: Ctx<'js>, _: Rest>) -> Result> { let rs = unsafe { preview_1::fd_sync( - self.fd + self.0 ) }; process_error(cx.clone(), rs)?; @@ -277,7 +278,7 @@ impl Descriptor { fn fdatasync<'js>(self: Arc, cx: Ctx<'js>, _: Rest>) -> Result> { let rs = unsafe { preview_1::fd_datasync( - self.fd + self.0 ) }; process_error(cx.clone(), rs)?; @@ -298,7 +299,7 @@ impl Descriptor { let fd_stat_ptr = &mut fd_stat as *mut _ as i32; let rs = unsafe { preview_1::fd_filestat_get( - self.fd, + self.0, fd_stat_ptr, ) }; @@ -332,7 +333,7 @@ impl Descriptor { let len: u64 = jsvalue2int64!(len); let rs = unsafe { preview_1::fd_allocate( - self.fd, + self.0, offset, len ) @@ -358,7 +359,7 @@ impl Descriptor { let len = jsvalue2int64!(len); let rs = unsafe { preview_1::fd_filestat_set_size( - self.fd, + self.0, len ) }; From 0ecdc7ac7fed08bbc5a2d2d9eb4b45385900f6e9 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Mon, 26 May 2025 14:39:29 +0800 Subject: [PATCH 29/45] support tell for descriptor --- src/wasi/descriptor.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index e0dac66..3ac853d 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use javy_plugin_api::javy::{ quickjs::{ - prelude::{MutFn, Rest}, Ctx, Function, Object as JObject, TypedArray, Value + prelude::{MutFn, Rest}, BigInt, Ctx, Function, Object as JObject, TypedArray, Value }, to_js_error }; use anyhow::{anyhow, bail, Ok, Result}; @@ -58,6 +58,8 @@ impl Descriptor { bind_method!(ftruncate); // Set the allocate method bind_method!(allocate); + // Set the tell method + bind_method!(tell); Ok(Value::from_object(desc)) } @@ -366,5 +368,22 @@ impl Descriptor { process_error(cx.clone(), rs)?; Ok(Value::new_int(cx, rs)) } + + /// The tell method + /// This method is used to get the current position of the file descriptor. + /// It returns a BigInt representing the current position in the file. + fn tell<'js>(self: Arc, cx: Ctx<'js>, _: Rest>) -> Result> { + let mut pos: u64 = 0; + let pos_ptr: i32 = &mut pos as *mut u64 as i32; + let rs = unsafe { + preview_1::fd_tell( + self.0, + pos_ptr + ) + }; + process_error(cx.clone(), rs)?; + Ok(Value::from_big_int(BigInt::from_u64(cx, pos)?)) + } + } From 6c0bcf66622a6874dcba5219b144f3fba9d0ee13 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Mon, 26 May 2025 17:37:29 +0800 Subject: [PATCH 30/45] support touch for descriptor --- src/wasi/descriptor.rs | 19 ++++++++++++++++++- src/wasi/macros.rs | 2 +- src/wasi/mod.rs | 7 +++++++ src/wasi/preview_1.rs | 16 +++++++++++++++- 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index 3ac853d..fa11689 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -6,7 +6,7 @@ use javy_plugin_api::javy::{ }; use anyhow::{anyhow, bail, Ok, Result}; -use super::{preview_1, process_error, stat::filestate_to_jsobject, Filestat}; +use super::{preview_1, process_error, stat::filestate_to_jsobject, Filestat, Fstflags}; pub struct Descriptor(i32); @@ -60,6 +60,8 @@ impl Descriptor { bind_method!(allocate); // Set the tell method bind_method!(tell); + // Set the touch method + bind_method!(touch); Ok(Value::from_object(desc)) } @@ -385,5 +387,20 @@ impl Descriptor { Ok(Value::from_big_int(BigInt::from_u64(cx, pos)?)) } + /// The touch method + /// This method is used to update the access and modification times of the file descriptor. + fn touch<'js>(self: Arc, cx: Ctx<'js>, _args: Rest>) -> Result> { + let rs = unsafe { + preview_1::fd_filestat_set_times( + self.0, + 0, + 0, + Fstflags::AtmNow as u16|Fstflags::MtimNow as u16 + ) + }; + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx, rs)) + } + } diff --git a/src/wasi/macros.rs b/src/wasi/macros.rs index 7f5e935..5945ee6 100644 --- a/src/wasi/macros.rs +++ b/src/wasi/macros.rs @@ -8,6 +8,6 @@ macro_rules! jsvalue2int64 { .map(|o| o.clone()) .ok_or_else(|| anyhow!("{} must be a int", stringify!($i)))? .to_i64()? as _ - }; + } }; } \ No newline at end of file diff --git a/src/wasi/mod.rs b/src/wasi/mod.rs index 6c76530..ee0d0f3 100644 --- a/src/wasi/mod.rs +++ b/src/wasi/mod.rs @@ -120,3 +120,10 @@ impl Into<&str> for FileType { } } +pub enum Fstflags { + Atm = 1 << 0, + AtmNow = 1 << 1, + Mtim = 1 << 2, + MtimNow = 1 << 3, +} + diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index 6cf22cd..1f224dc 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -150,7 +150,21 @@ unsafe extern "C" { #[link_name = "fd_filestat_set_size"] pub unsafe fn fd_filestat_set_size( fd: i32, - stat_ptr: i32 + stat: u64 + ) -> i32; + + #[link_name = "fd_tell"] + pub unsafe fn fd_tell( + fd: i32, + pos_ptr: i32 + ) -> i32; + + #[link_name = "fd_filestat_set_times"] + pub unsafe fn fd_filestat_set_times( + fd: i32, + atim: i64, + mtim: i64, + fst_flags: u16, ) -> i32; } From a28ff7bcdbd27190b38b4eb12dbd60129b84cc52 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Tue, 27 May 2025 22:47:37 +0800 Subject: [PATCH 31/45] support setflags for descriptor --- src/wasi/descriptor.rs | 33 +++++++++++++++++++++++++++++++-- src/wasi/preview_1.rs | 6 ++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index fa11689..47ea829 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -28,11 +28,14 @@ impl Descriptor { desc.set("rawfd", fd)?; macro_rules! bind_method { ($name:ident) => { + bind_method!(stringify!($name), $name); + }; + ($name: expr, $method: ident) => { let descriptor_clone = descriptor.clone(); - desc.set(stringify!($name), Function::new( + desc.set($name, Function::new( cx.clone(), MutFn::new(move |cx: Ctx<'js>, args: Rest>| { - descriptor_clone.clone().$name(cx.clone(), args) + descriptor_clone.clone().$method(cx.clone(), args) .map_err(|e| to_js_error(cx.clone(), e)) }), )?)?; @@ -62,6 +65,8 @@ impl Descriptor { bind_method!(tell); // Set the touch method bind_method!(touch); + // Set the set_flags method + bind_method!("setFlags", set_flags); Ok(Value::from_object(desc)) } @@ -402,5 +407,29 @@ impl Descriptor { Ok(Value::new_int(cx, rs)) } + /// The fd_fdstat_set_flags method + /// This method is used to set the flags of the file descriptor. + fn set_flags<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { + let args_pat: &[Value<'_>]= &args.0; + let [ + flags, + .. + ] = args_pat else { + bail!( + "set_flags expects 1 parameters: the fd_flags, Got: {} parameters.", + args.len() + ); + }; + let fd_flags = flags.as_int().ok_or_else(|| anyhow!("fd_flags must be a int"))?; + let rs = unsafe { + preview_1::fd_fdstat_set_flags( + self.0, + fd_flags as u16 + ) + }; + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx, rs)) + } + } diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index 1f224dc..0de3a36 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -166,5 +166,11 @@ unsafe extern "C" { mtim: i64, fst_flags: u16, ) -> i32; + + #[link_name = "fd_fdstat_set_flags"] + pub unsafe fn fd_fdstat_set_flags( + fd: i32, + fd_flags: u16, + ) -> i32; } From 506649583cf83a1182545c98f3591d0e0dea60e9 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Wed, 28 May 2025 10:33:12 +0800 Subject: [PATCH 32/45] support readstring readall for descriptor --- src/wasi/descriptor.rs | 54 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index 47ea829..597baac 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -1,7 +1,9 @@ -use std::sync::Arc; +use std::{sync::Arc, vec}; use javy_plugin_api::javy::{ quickjs::{ - prelude::{MutFn, Rest}, BigInt, Ctx, Function, Object as JObject, TypedArray, Value + prelude::{MutFn, Rest}, BigInt, Ctx, + Function, Object as JObject, TypedArray, Value, + String as JString }, to_js_error }; use anyhow::{anyhow, bail, Ok, Result}; @@ -67,6 +69,10 @@ impl Descriptor { bind_method!(touch); // Set the set_flags method bind_method!("setFlags", set_flags); + // Set the set_all method + bind_method!("readAll", read_all); + // Set the set_string method + bind_method!("readString", read_string); Ok(Value::from_object(desc)) } @@ -180,6 +186,50 @@ impl Descriptor { Ok(Value::new_int(cx, writen)) } + fn read_all_data<'js>(cx: Ctx<'js>, fd: i32) -> Result> { + let mut data = Vec::new(); + let mut readn: i32 = 0; + let mut rs; + let mut buf = vec![0u8; 1024 * 4]; // Buffer to read data into + loop { + let mut ioslice = vec![Iovec { + buf: buf.as_mut_ptr() as *const u8 as i32, // This will be set to the actual buffer later + buf_len: 1024*4, // Read in chunks of 1024*4 bytes + }]; + rs = unsafe { + preview_1::fd_read( + fd, + ioslice.as_mut_ptr() as i32, + ioslice.len() as i32, + &mut readn as *mut i32 as i32, + ) + }; + if rs != 0 || readn <= 0 { + break; // Stop reading on error or no more data + } + // Extend the data with the newly read bytes + data.extend_from_slice(&buf[0..readn as usize]); + } + process_error(cx.clone(), rs)?; + Ok(data) + } + + /// The read_all method + /// This method reads all data from the file descriptor and returns it as a Uint8Array. + fn read_all<'js>(self: Arc, cx: Ctx<'js>, _args: Rest>) -> Result> { + let data = Self::read_all_data(cx.clone(), self.0)?; + let arr: TypedArray<'js, u8> = TypedArray::new(cx.clone(), data)?; + Ok(Value::from_object(arr.into_object())) + } + + /// The read_string method + /// This method reads all data from the file descriptor and returns it as a string. + fn read_string<'js>(self: Arc, cx: Ctx<'js>, _args: Rest>) -> Result> { + let data = Self::read_all_data(cx.clone(), self.0)?; + let string: JString<'js> = JString::from_str(cx.clone(), &String::from_utf8(data)?)?; + Ok(Value::from_string(string)) + } + /// The advise method /// This method is used to give advice to the file descriptor. /// The first parameter is the offset, the second parameter is the length, From f2bf362988dc258449329760532298c2ea98a5da Mon Sep 17 00:00:00 2001 From: Joinhack Date: Wed, 28 May 2025 16:32:04 +0800 Subject: [PATCH 33/45] support fatime fmtime for descriptor --- src/wasi/descriptor.rs | 60 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index 597baac..b60600e 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -73,6 +73,10 @@ impl Descriptor { bind_method!("readAll", read_all); // Set the set_string method bind_method!("readString", read_string); + // Set the fatime method + bind_method!(fatime); + // Set the fmtime method + bind_method!(fmtime); Ok(Value::from_object(desc)) } @@ -401,6 +405,62 @@ impl Descriptor { Ok(Value::new_int(cx, rs)) } + /// The fatime method + /// This method is used to set the access time of the file descriptor. + /// The first parameter is the timestamp to set the access time to. + /// The timestamp is a BigInt representing the number of milliseconds since the epoch. + fn fatime<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { + let args_pat: &[Value<'_>]= &args.0; + let [ + ts, + .. + ] = args_pat else { + bail!( + "fatime expects 1 parameters: the ts, Got: {} parameters.", + args.len() + ); + }; + let ts: i64 = jsvalue2int64!(ts); + let rs = unsafe { + preview_1::fd_filestat_set_times( + self.0, + ts, + 0, + Fstflags::Atm as u16 + ) + }; + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx, rs)) + } + + /// The fmtime method + /// This method is used to set the modification time of the file descriptor. + /// The first parameter is the timestamp to set the modification time to. + /// The timestamp is a BigInt representing the number of milliseconds since the epoch. + fn fmtime<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { + let args_pat: &[Value<'_>]= &args.0; + let [ + ts, + .. + ] = args_pat else { + bail!( + "fmtime expects 1 parameters: the ts, Got: {} parameters.", + args.len() + ); + }; + let ts: i64 = jsvalue2int64!(ts); + let rs = unsafe { + preview_1::fd_filestat_set_times( + self.0, + 0, + ts, + Fstflags::Mtim as u16 + ) + }; + process_error(cx.clone(), rs)?; + Ok(Value::new_int(cx, rs)) + } + /// The ftruncate method /// This method is used to truncate the file descriptor to the given length. /// The first parameter is the length to truncate the file to. From 68bb4b701fe1d58044e22ebb878070073d5b14a7 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Wed, 28 May 2025 17:32:48 +0800 Subject: [PATCH 34/45] support fatime fmtime for descriptor --- src/wasi/descriptor.rs | 62 +++++++++++++++++++++++++++++++++++++++--- src/wasi/preview_1.rs | 11 ++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index b60600e..fe52446 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -1,14 +1,15 @@ use std::{sync::Arc, vec}; use javy_plugin_api::javy::{ quickjs::{ - prelude::{MutFn, Rest}, BigInt, Ctx, - Function, Object as JObject, TypedArray, Value, - String as JString + prelude::{MutFn, Rest}, Array, BigInt, Ctx, FromIteratorJs, + Function, Object as JObject, String as JString, TypedArray, Value }, to_js_error }; use anyhow::{anyhow, bail, Ok, Result}; -use super::{preview_1, process_error, stat::filestate_to_jsobject, Filestat, Fstflags}; +use super::{ + preview_1, process_error, stat::filestate_to_jsobject, Filestat, Fstflags +}; pub struct Descriptor(i32); @@ -77,9 +78,62 @@ impl Descriptor { bind_method!(fatime); // Set the fmtime method bind_method!(fmtime); + // Set the readdir method + bind_method!("readdir", read_dir); Ok(Value::from_object(desc)) } + /// todo: implement the readdir method + /// This method reads the directory entries from the file descriptor. + /// It returns an array of strings representing the names of the entries in the directory. + fn read_dir<'js>(self: Arc, cx: Ctx<'js>, _args: Rest>) -> Result> { + let mut dir_buff = vec![]; + let mut r_buff = vec![0u8; 1024 * 4]; // Buffer to read directory entries into + let mut readn: i64 = 0; + let mut rs; + loop { + rs = unsafe { + preview_1::fd_readdir( + self.0, + r_buff.as_mut_ptr() as i32, + r_buff.len() as i32, + 0, + &mut readn as *mut i64 as i32, + ) + }; + if rs != 0 { + process_error(cx.clone(), rs)?; + return Ok(Value::new_null(cx.clone())); + } + + if readn > 0 { + dir_buff.extend_from_slice(&r_buff[0..readn as usize]); + } + if readn < r_buff.len() as i64 { + break; // No more entries to read + } + } + let mut off = 0; + let mut name_jsarray = vec![]; + while off < dir_buff.len() { + off += 16; + let len: i32 = unsafe { + *(dir_buff.as_ptr().wrapping_add(off) as *const i32) + }; + off += 8; // Move past the length field + if off + len as usize > dir_buff.len() { + return Ok(Value::new_null(cx.clone())); + } + let name_str = unsafe { + std::str::from_utf8_unchecked(&dir_buff[off..off + len as usize]) + }; + off += len as usize; // Move past the name + name_jsarray.push(Value::from_string(JString::from_str(cx.clone(), name_str)?)); + } + let name_jsarray = Array::from_iter_js(&cx, name_jsarray.iter())?; + return Ok(Value::from_array(name_jsarray)); + } + /// The read method /// Uint8Array as the buffer the first parameter /// size as the second parameter, it's optional, default is the length of the buffer diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index 0de3a36..8346399 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -172,5 +172,16 @@ unsafe extern "C" { fd: i32, fd_flags: u16, ) -> i32; + + /// Reads directory entries from a file descriptor + #[allow(dead_code)] + #[link_name = "fd_readdir"] + pub unsafe fn fd_readdir( + fd: i32, + buf: i32, + buf_len: i32, + cookie: u64, + readn_ptr: i32, + ) -> i32; } From 5ff79acfc3b911c72fd2572686de9942077a1342 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Wed, 28 May 2025 17:42:04 +0800 Subject: [PATCH 35/45] fmt --- src/lib.rs | 4 +- src/wasi/close.rs | 17 +-- src/wasi/descriptor.rs | 268 +++++++++++++++-------------------------- src/wasi/link.rs | 49 ++++---- src/wasi/macros.rs | 6 +- src/wasi/mkdir.rs | 21 ++-- src/wasi/mod.rs | 62 +++++----- src/wasi/open.rs | 56 ++++----- src/wasi/preview_1.rs | 131 +++++--------------- src/wasi/rename.rs | 41 +++---- src/wasi/rmdir.rs | 20 ++- src/wasi/stat.rs | 40 +++--- src/wasi/symlink.rs | 39 +++--- src/wasi/unlink.rs | 25 ++-- 14 files changed, 285 insertions(+), 494 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 95802f9..63b9df4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,10 +12,10 @@ use javy_plugin_api::{ pub mod crypto; #[cfg(feature = "fetch")] pub mod fetch; -#[cfg(feature = "wasi")] -pub mod wasi; #[cfg(feature = "llm")] pub mod llm; +#[cfg(feature = "wasi")] +pub mod wasi; #[cfg(feature = "crypto")] use crypto::bless_get_random_values; diff --git a/src/wasi/close.rs b/src/wasi/close.rs index ed00bd8..6a03362 100644 --- a/src/wasi/close.rs +++ b/src/wasi/close.rs @@ -1,8 +1,5 @@ -use javy_plugin_api::javy::{ - quickjs::Value, - Args -}; use anyhow::{anyhow, bail, Result}; +use javy_plugin_api::javy::{quickjs::Value, Args}; use super::{preview_1, process_error}; @@ -10,19 +7,15 @@ use super::{preview_1, process_error}; /// `fd`: The file descriptor to close. pub fn wasi_preview1_close<'a>(args: Args<'a>) -> Result> { let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let [ - fd, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [fd, ..] = args_pat else { bail!( "close expects 1 parameter: the fd, Got: {} parameters.", args.len() ); }; - let fd = fd.as_int() - .ok_or_else(|| anyhow!("fd must be a number"))?; + let fd = fd.as_int().ok_or_else(|| anyhow!("fd must be a number"))?; let rs = unsafe { preview_1::fd_close(fd) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx.clone(), rs)) -} \ No newline at end of file +} diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index fe52446..63f0bb3 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -1,16 +1,15 @@ -use std::{sync::Arc, vec}; +use anyhow::{anyhow, bail, Ok, Result}; use javy_plugin_api::javy::{ quickjs::{ - prelude::{MutFn, Rest}, Array, BigInt, Ctx, FromIteratorJs, - Function, Object as JObject, String as JString, TypedArray, Value - }, to_js_error -}; -use anyhow::{anyhow, bail, Ok, Result}; - -use super::{ - preview_1, process_error, stat::filestate_to_jsobject, Filestat, Fstflags + prelude::{MutFn, Rest}, + Array, BigInt, Ctx, FromIteratorJs, Function, Object as JObject, String as JString, + TypedArray, Value, + }, + to_js_error, }; +use std::{sync::Arc, vec}; +use super::{preview_1, process_error, stat::filestate_to_jsobject, Filestat, Fstflags}; pub struct Descriptor(i32); @@ -35,13 +34,18 @@ impl Descriptor { }; ($name: expr, $method: ident) => { let descriptor_clone = descriptor.clone(); - desc.set($name, Function::new( - cx.clone(), - MutFn::new(move |cx: Ctx<'js>, args: Rest>| { - descriptor_clone.clone().$method(cx.clone(), args) - .map_err(|e| to_js_error(cx.clone(), e)) - }), - )?)?; + desc.set( + $name, + Function::new( + cx.clone(), + MutFn::new(move |cx: Ctx<'js>, args: Rest>| { + descriptor_clone + .clone() + .$method(cx.clone(), args) + .map_err(|e| to_js_error(cx.clone(), e)) + }), + )?, + )?; }; } // Set the read method @@ -105,10 +109,10 @@ impl Descriptor { process_error(cx.clone(), rs)?; return Ok(Value::new_null(cx.clone())); } - + if readn > 0 { dir_buff.extend_from_slice(&r_buff[0..readn as usize]); - } + } if readn < r_buff.len() as i64 { break; // No more entries to read } @@ -117,16 +121,13 @@ impl Descriptor { let mut name_jsarray = vec![]; while off < dir_buff.len() { off += 16; - let len: i32 = unsafe { - *(dir_buff.as_ptr().wrapping_add(off) as *const i32) - }; + let len: i32 = unsafe { *(dir_buff.as_ptr().wrapping_add(off) as *const i32) }; off += 8; // Move past the length field if off + len as usize > dir_buff.len() { return Ok(Value::new_null(cx.clone())); } - let name_str = unsafe { - std::str::from_utf8_unchecked(&dir_buff[off..off + len as usize]) - }; + let name_str = + unsafe { std::str::from_utf8_unchecked(&dir_buff[off..off + len as usize]) }; off += len as usize; // Move past the name name_jsarray.push(Value::from_string(JString::from_str(cx.clone(), name_str)?)); } @@ -144,7 +145,7 @@ impl Descriptor { args.len() ); } - let buffer = &args.0[0]; + let buffer = &args.0[0]; let null = Value::new_null(cx.clone()); let mut size = &null; if args.0.len() > 2 { @@ -153,11 +154,14 @@ impl Descriptor { let mut readn: i32 = 0; let readn_ptr: i32 = &mut readn as *mut i32 as i32; - let array = buffer.as_object() + let array = buffer + .as_object() .ok_or_else(|| anyhow!("buffer must be a array"))?; - let array: &TypedArray<'_, u8> = array.as_typed_array() + let array: &TypedArray<'_, u8> = array + .as_typed_array() .ok_or_else(|| anyhow!("buffer must be a typed array"))?; - let mut array_raw = array.as_raw() + let mut array_raw = array + .as_raw() .ok_or_else(|| anyhow!("buffer get raw ptr error."))?; let size = size.as_int(); let size = if let Some(size) = size { @@ -169,18 +173,17 @@ impl Descriptor { } else { array_raw.len as u32 }; - let ioslice = vec![ - Iovec { - buf: unsafe {array_raw.ptr.as_mut() as *mut u8 as i32}, - buf_len: size, - } - ]; + let ioslice = vec![Iovec { + buf: unsafe { array_raw.ptr.as_mut() as *mut u8 as i32 }, + buf_len: size, + }]; let rs = unsafe { preview_1::fd_read( self.0, ioslice.as_ptr() as i32, ioslice.len() as i32, - readn_ptr) + readn_ptr, + ) }; process_error(cx.clone(), rs)?; if rs != 0 { @@ -199,7 +202,7 @@ impl Descriptor { args.len() ); } - let buffer = &args.0[0]; + let buffer = &args.0[0]; let null = Value::new_null(cx.clone()); let mut size = &null; if args.0.len() > 2 { @@ -208,11 +211,14 @@ impl Descriptor { let mut writen: i32 = 0; let writen_ptr: i32 = &mut writen as *mut i32 as i32; - let array = buffer.as_object() + let array = buffer + .as_object() .ok_or_else(|| anyhow!("buffer must be a array"))?; - let array: &TypedArray<'_, u8> = array.as_typed_array() + let array: &TypedArray<'_, u8> = array + .as_typed_array() .ok_or_else(|| anyhow!("buffer must be a typed array"))?; - let mut array_raw = array.as_raw() + let mut array_raw = array + .as_raw() .ok_or_else(|| anyhow!("buffer get raw ptr error."))?; let size = size.as_int(); let size = if let Some(size) = size { @@ -224,18 +230,17 @@ impl Descriptor { } else { array_raw.len as u32 }; - let ioslice = vec![ - Iovec { - buf: unsafe {array_raw.ptr.as_mut() as *mut u8 as i32}, - buf_len: size, - } - ]; + let ioslice = vec![Iovec { + buf: unsafe { array_raw.ptr.as_mut() as *mut u8 as i32 }, + buf_len: size, + }]; let rs = unsafe { preview_1::fd_write( self.0, ioslice.as_ptr() as i32, ioslice.len() as i32, - writen_ptr) + writen_ptr, + ) }; process_error(cx.clone(), rs)?; if rs != 0 { @@ -252,7 +257,7 @@ impl Descriptor { loop { let mut ioslice = vec![Iovec { buf: buf.as_mut_ptr() as *const u8 as i32, // This will be set to the actual buffer later - buf_len: 1024*4, // Read in chunks of 1024*4 bytes + buf_len: 1024 * 4, // Read in chunks of 1024*4 bytes }]; rs = unsafe { preview_1::fd_read( @@ -282,7 +287,11 @@ impl Descriptor { /// The read_string method /// This method reads all data from the file descriptor and returns it as a string. - fn read_string<'js>(self: Arc, cx: Ctx<'js>, _args: Rest>) -> Result> { + fn read_string<'js>( + self: Arc, + cx: Ctx<'js>, + _args: Rest>, + ) -> Result> { let data = Self::read_all_data(cx.clone(), self.0)?; let string: JString<'js> = JString::from_str(cx.clone(), &String::from_utf8(data)?)?; Ok(Value::from_string(string)) @@ -294,20 +303,15 @@ impl Descriptor { /// and the third parameter is the advice. /// The advice can be one of the following values: /// - `0`: Normal access. - /// - `1`: Random access. + /// - `1`: Random access. /// - `2`: Sequential access. /// - `3`: Will need to read the data. /// - `4`: Will need to write the data. /// The offset is the number of bytes to offset from the beginning of the file, /// and the length is the number of bytes to advise. fn advise<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { - let args_pat: &[Value<'_>]= &args.0; - let [ - offset, - len, - advice, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [offset, len, advice, ..] = args_pat else { bail!( "advice expects 3 parameters: the offset, len and advice, Got: {} parameters.", args.len() @@ -315,15 +319,10 @@ impl Descriptor { }; let offset: u64 = jsvalue2int64!(offset); let len: u64 = jsvalue2int64!(len); - let advice: i32 = advice.as_int().ok_or_else(|| anyhow!("advice must be a int"))?; - let rs = unsafe { - preview_1::fd_advise( - self.0, - offset, - len, - advice - ) - }; + let advice: i32 = advice + .as_int() + .ok_or_else(|| anyhow!("advice must be a int"))?; + let rs = unsafe { preview_1::fd_advise(self.0, offset, len, advice) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx, rs)) } @@ -337,30 +336,21 @@ impl Descriptor { /// - `2`: Seek from the end of the file. /// The offset is the number of bytes to seek. fn seek<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { - let args_pat: &[Value<'_>]= &args.0; - let [ - offset, - whence, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [offset, whence, ..] = args_pat else { bail!( "advice expects 2 parameters: the offset and whence, Got: {} parameters.", args.len() ); }; let offset: u64 = jsvalue2int64!(offset); - - let whence: i32 = whence.as_int().ok_or_else(|| anyhow!("advice must be a int"))?; + + let whence: i32 = whence + .as_int() + .ok_or_else(|| anyhow!("advice must be a int"))?; let mut fsize: i64 = 0; let fsize_ptr: i32 = &mut fsize as *mut i64 as i32; - let rs = unsafe { - preview_1::fd_seek( - self.0, - offset, - whence, - fsize_ptr - ) - }; + let rs = unsafe { preview_1::fd_seek(self.0, offset, whence, fsize_ptr) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx, rs)) } @@ -369,11 +359,7 @@ impl Descriptor { /// Uint8Array as the buffer the first parameter /// size as the second parameter, it's optional, default is the length of the buffer fn close<'js>(self: Arc, cx: Ctx<'js>, _: Rest>) -> Result> { - let rs = unsafe { - preview_1::fd_close( - self.0 - ) - }; + let rs = unsafe { preview_1::fd_close(self.0) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx, rs)) } @@ -381,11 +367,7 @@ impl Descriptor { /// The fsync method /// Wait for the data and metadata to be written fn fsync<'js>(self: Arc, cx: Ctx<'js>, _: Rest>) -> Result> { - let rs = unsafe { - preview_1::fd_sync( - self.0 - ) - }; + let rs = unsafe { preview_1::fd_sync(self.0) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx, rs)) } @@ -393,11 +375,7 @@ impl Descriptor { /// The fdatasync method /// Wait for the data to be written fn fdatasync<'js>(self: Arc, cx: Ctx<'js>, _: Rest>) -> Result> { - let rs = unsafe { - preview_1::fd_datasync( - self.0 - ) - }; + let rs = unsafe { preview_1::fd_datasync(self.0) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx, rs)) } @@ -414,12 +392,7 @@ impl Descriptor { fn stat<'js>(self: Arc, cx: Ctx<'js>, _: Rest>) -> Result> { let mut fd_stat: Filestat = Default::default(); let fd_stat_ptr = &mut fd_stat as *mut _ as i32; - let rs = unsafe { - preview_1::fd_filestat_get( - self.0, - fd_stat_ptr, - ) - }; + let rs = unsafe { preview_1::fd_filestat_get(self.0, fd_stat_ptr) }; if rs == 0 { let stat = filestate_to_jsobject(cx.clone(), &fd_stat)?; Ok(Value::from_object(stat)) @@ -435,12 +408,8 @@ impl Descriptor { /// The offset is the number of bytes to offset from the beginning of the file, /// and the length is the number of bytes to allocate. fn allocate<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { - let args_pat: &[Value<'_>]= &args.0; - let [ - offset, - len, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [offset, len, ..] = args_pat else { bail!( "allocate expects 2 parameters: the offset and length, Got: {} parameters.", args.len() @@ -448,13 +417,7 @@ impl Descriptor { }; let offset: u64 = jsvalue2int64!(offset); let len: u64 = jsvalue2int64!(len); - let rs = unsafe { - preview_1::fd_allocate( - self.0, - offset, - len - ) - }; + let rs = unsafe { preview_1::fd_allocate(self.0, offset, len) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx, rs)) } @@ -464,25 +427,15 @@ impl Descriptor { /// The first parameter is the timestamp to set the access time to. /// The timestamp is a BigInt representing the number of milliseconds since the epoch. fn fatime<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { - let args_pat: &[Value<'_>]= &args.0; - let [ - ts, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [ts, ..] = args_pat else { bail!( "fatime expects 1 parameters: the ts, Got: {} parameters.", args.len() ); }; let ts: i64 = jsvalue2int64!(ts); - let rs = unsafe { - preview_1::fd_filestat_set_times( - self.0, - ts, - 0, - Fstflags::Atm as u16 - ) - }; + let rs = unsafe { preview_1::fd_filestat_set_times(self.0, ts, 0, Fstflags::Atm as u16) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx, rs)) } @@ -492,25 +445,15 @@ impl Descriptor { /// The first parameter is the timestamp to set the modification time to. /// The timestamp is a BigInt representing the number of milliseconds since the epoch. fn fmtime<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { - let args_pat: &[Value<'_>]= &args.0; - let [ - ts, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [ts, ..] = args_pat else { bail!( "fmtime expects 1 parameters: the ts, Got: {} parameters.", args.len() ); }; let ts: i64 = jsvalue2int64!(ts); - let rs = unsafe { - preview_1::fd_filestat_set_times( - self.0, - 0, - ts, - Fstflags::Mtim as u16 - ) - }; + let rs = unsafe { preview_1::fd_filestat_set_times(self.0, 0, ts, Fstflags::Mtim as u16) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx, rs)) } @@ -519,23 +462,15 @@ impl Descriptor { /// This method is used to truncate the file descriptor to the given length. /// The first parameter is the length to truncate the file to. fn ftruncate<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { - let args_pat: &[Value<'_>]= &args.0; - let [ - len, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [len, ..] = args_pat else { bail!( "ftruncate expects 1 parameters: the offset and whence, Got: {} parameters.", args.len() ); }; let len = jsvalue2int64!(len); - let rs = unsafe { - preview_1::fd_filestat_set_size( - self.0, - len - ) - }; + let rs = unsafe { preview_1::fd_filestat_set_size(self.0, len) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx, rs)) } @@ -546,12 +481,7 @@ impl Descriptor { fn tell<'js>(self: Arc, cx: Ctx<'js>, _: Rest>) -> Result> { let mut pos: u64 = 0; let pos_ptr: i32 = &mut pos as *mut u64 as i32; - let rs = unsafe { - preview_1::fd_tell( - self.0, - pos_ptr - ) - }; + let rs = unsafe { preview_1::fd_tell(self.0, pos_ptr) }; process_error(cx.clone(), rs)?; Ok(Value::from_big_int(BigInt::from_u64(cx, pos)?)) } @@ -564,7 +494,7 @@ impl Descriptor { self.0, 0, 0, - Fstflags::AtmNow as u16|Fstflags::MtimNow as u16 + Fstflags::AtmNow as u16 | Fstflags::MtimNow as u16, ) }; process_error(cx.clone(), rs)?; @@ -574,26 +504,18 @@ impl Descriptor { /// The fd_fdstat_set_flags method /// This method is used to set the flags of the file descriptor. fn set_flags<'js>(self: Arc, cx: Ctx<'js>, args: Rest>) -> Result> { - let args_pat: &[Value<'_>]= &args.0; - let [ - flags, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [flags, ..] = args_pat else { bail!( "set_flags expects 1 parameters: the fd_flags, Got: {} parameters.", args.len() ); }; - let fd_flags = flags.as_int().ok_or_else(|| anyhow!("fd_flags must be a int"))?; - let rs = unsafe { - preview_1::fd_fdstat_set_flags( - self.0, - fd_flags as u16 - ) - }; + let fd_flags = flags + .as_int() + .ok_or_else(|| anyhow!("fd_flags must be a int"))?; + let rs = unsafe { preview_1::fd_fdstat_set_flags(self.0, fd_flags as u16) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx, rs)) } - } - diff --git a/src/wasi/link.rs b/src/wasi/link.rs index d93dff8..a39b4a6 100644 --- a/src/wasi/link.rs +++ b/src/wasi/link.rs @@ -1,8 +1,5 @@ -use javy_plugin_api::javy::{ - quickjs::Value, - Args -}; use anyhow::{anyhow, bail, Result}; +use javy_plugin_api::javy::{quickjs::Value, Args}; use super::{preview_1, process_error}; @@ -15,31 +12,29 @@ use super::{preview_1, process_error}; /// - `new_path`: The path of the new file. pub fn wasi_preview1_path_link(args: Args<'_>) -> Result> { let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let [ - old_dirfd, - fd_lookup_flags, - old_path, - new_dirfd, - new_path, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [old_dirfd, fd_lookup_flags, old_path, new_dirfd, new_path, ..] = args_pat else { bail!( "path_remove_directory expects 5 parameters: the dirfd and path, Got: {} parameters.", args.len() ); }; - let dirfd = old_dirfd.as_int() + let dirfd = old_dirfd + .as_int() .ok_or_else(|| anyhow!("old_dirfd must be a number"))?; - let fd_lookup_flags = fd_lookup_flags.as_int() + let fd_lookup_flags = fd_lookup_flags + .as_int() .ok_or_else(|| anyhow!("fd_lookup_flags must be a number"))?; - let old_path = old_path.as_string() + let old_path = old_path + .as_string() .ok_or_else(|| anyhow!("old_path must be a string"))? .to_string() .map_err(|_| anyhow!("invalid UTF-8 in path"))?; - let new_dirfd = new_dirfd.as_int() + let new_dirfd = new_dirfd + .as_int() .ok_or_else(|| anyhow!("new_dirfd must be a number"))?; - let new_path = new_path.as_string() + let new_path = new_path + .as_string() .ok_or_else(|| anyhow!("new_path must be a string"))? .to_string() .map_err(|_| anyhow!("invalid UTF-8 in path"))?; @@ -47,16 +42,16 @@ pub fn wasi_preview1_path_link(args: Args<'_>) -> Result> { let new_path_len = new_path.len() as i32; let old_path_ptr = old_path.as_ptr() as i32; let old_path_len = old_path.len() as i32; - let rs = unsafe { + let rs = unsafe { preview_1::path_link( - dirfd, - fd_lookup_flags, - old_path_ptr, - old_path_len, - new_dirfd, - new_path_ptr, - new_path_len - ) + dirfd, + fd_lookup_flags, + old_path_ptr, + old_path_len, + new_dirfd, + new_path_ptr, + new_path_len, + ) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx.clone(), rs)) diff --git a/src/wasi/macros.rs b/src/wasi/macros.rs index 5945ee6..8944368 100644 --- a/src/wasi/macros.rs +++ b/src/wasi/macros.rs @@ -1,8 +1,8 @@ - macro_rules! jsvalue2int64 { ($i: ident) => { if $i.is_int() { - $i.as_int().ok_or_else(|| anyhow!("{} must be a int", stringify!($i)))? as _ + $i.as_int() + .ok_or_else(|| anyhow!("{} must be a int", stringify!($i)))? as _ } else { $i.as_big_int() .map(|o| o.clone()) @@ -10,4 +10,4 @@ macro_rules! jsvalue2int64 { .to_i64()? as _ } }; -} \ No newline at end of file +} diff --git a/src/wasi/mkdir.rs b/src/wasi/mkdir.rs index bdec3ba..ebdc82f 100644 --- a/src/wasi/mkdir.rs +++ b/src/wasi/mkdir.rs @@ -1,8 +1,5 @@ -use javy_plugin_api::javy::{ - quickjs::Value, - Args -}; use anyhow::{anyhow, bail, Result}; +use javy_plugin_api::javy::{quickjs::Value, Args}; use super::{preview_1, process_error}; @@ -11,20 +8,18 @@ use super::{preview_1, process_error}; /// The directory must not exist. pub fn wasi_preview1_path_create_directory(args: Args<'_>) -> Result> { let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let [ - dirfd, - path, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [dirfd, path, ..] = args_pat else { bail!( "path_create_directory expects 2 parameters: the dirfd and path, Got: {} parameters.", args.len() ); }; - let dirfd = dirfd.as_int() + let dirfd = dirfd + .as_int() .ok_or_else(|| anyhow!("dirfd must be a number"))?; - let path = path.as_string() + let path = path + .as_string() .ok_or_else(|| anyhow!("path must be a string"))? .to_string() .map_err(|_| anyhow!("invalid UTF-8 in path"))?; @@ -33,4 +28,4 @@ pub fn wasi_preview1_path_create_directory(args: Args<'_>) -> Result> let rs = unsafe { preview_1::path_create_directory(dirfd, path_ptr, path_len) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx.clone(), rs)) -} \ No newline at end of file +} diff --git a/src/wasi/mod.rs b/src/wasi/mod.rs index ee0d0f3..74bb659 100644 --- a/src/wasi/mod.rs +++ b/src/wasi/mod.rs @@ -1,34 +1,33 @@ +use anyhow::{anyhow, bail, Result}; use javy_plugin_api::javy::{ - quickjs::{Ctx, Object as JObject, Value}, - Args + quickjs::{Ctx, Object as JObject, Value}, + Args, }; -use anyhow::{anyhow, bail, Result}; #[macro_use] mod macros; -mod preview_1; -mod error; -mod open; -mod mkdir; -mod rmdir; -mod unlink; mod close; -mod symlink; +mod descriptor; +mod error; mod link; +mod mkdir; +mod open; +mod preview_1; mod rename; +mod rmdir; mod stat; -mod descriptor; -pub(crate) use open::wasi_preview1_open; -pub(crate) use mkdir::wasi_preview1_path_create_directory; -pub(crate) use rmdir::wasi_preview1_path_remove_directory; -pub(crate) use unlink::wasi_preview1_path_unlink_file; +mod symlink; +mod unlink; pub(crate) use close::wasi_preview1_close; -pub(crate) use symlink::wasi_preview1_path_symlink; +pub use error::WasiError; pub(crate) use link::wasi_preview1_path_link; +pub(crate) use mkdir::wasi_preview1_path_create_directory; +pub(crate) use open::wasi_preview1_open; pub(crate) use rename::wasi_preview1_path_rename; +pub(crate) use rmdir::wasi_preview1_path_remove_directory; pub(crate) use stat::wasi_preview1_path_filestat_get; -pub use error::WasiError; - +pub(crate) use symlink::wasi_preview1_path_symlink; +pub(crate) use unlink::wasi_preview1_path_unlink_file; #[inline] pub fn process_error(ctx: Ctx<'_>, rs: i32) -> Result<()> { @@ -50,37 +49,33 @@ pub fn process_error(ctx: Ctx<'_>, rs: i32) -> Result<()> { /// The file descriptor must be a directory. pub fn wasi_preview1_fd_prestat_dir_name(args: Args<'_>) -> Result> { let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let [ - fd, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [fd, ..] = args_pat else { bail!( "fd_prestat_dir_name expects 1 parameters: the fd, path_ptr and path_len, Got: {} parameters.", args.len() ); }; let mut path_len_buf = [0u8; 8]; - let fd = fd.as_int() - .ok_or_else(|| anyhow!("fd must be a number"))?; + let fd = fd.as_int().ok_or_else(|| anyhow!("fd must be a number"))?; let path_len_ptr: i32 = path_len_buf.as_mut_ptr() as i32; let rs = unsafe { preview_1::fd_prestat_get(fd, path_len_ptr) }; let path_len_buf: [u8; 4] = path_len_buf[4..].try_into()?; let path_len = i32::from_le_bytes(path_len_buf); let obj = JObject::new(cx.clone())?; - if rs != 0 { + if rs != 0 { process_error(cx.clone(), rs)?; - return Ok(Value::from_object(obj)) + return Ok(Value::from_object(obj)); } let mut path_buf = vec![0u8; path_len as usize]; - let rs = unsafe { + let rs = unsafe { preview_1::fd_prestat_dir_name( - fd, - path_buf.as_mut_ptr() as *const i32 as i32, - path_len as _ - ) + fd, + path_buf.as_mut_ptr() as *const i32 as i32, + path_len as _, + ) }; - if rs == 0 { + if rs == 0 { let path = String::from_utf8(path_buf)?; obj.set("dir_name", path)?; } @@ -126,4 +121,3 @@ pub enum Fstflags { Mtim = 1 << 2, MtimNow = 1 << 3, } - diff --git a/src/wasi/open.rs b/src/wasi/open.rs index db4f4d8..d85c2c4 100644 --- a/src/wasi/open.rs +++ b/src/wasi/open.rs @@ -1,8 +1,5 @@ -use javy_plugin_api::javy::{ - quickjs::Value, - Args -}; use anyhow::{anyhow, bail, Result}; +use javy_plugin_api::javy::{quickjs::Value, Args}; use super::descriptor::Descriptor; use super::{preview_1, process_error}; @@ -11,35 +8,33 @@ use super::{preview_1, process_error}; /// It is used to open a file at the given path. pub fn wasi_preview1_open<'a>(args: Args<'a>) -> Result> { let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; + let args_pat: &[Value<'_>] = &args.0; let mut opened_fd: i32 = 0; - let [ - dirfd, - fd_lookup_flags, - path, - fd_oflags, - fd_rights, - fd_rights_inherited, - fd_flags, - .. - ] = args_pat else { + let [dirfd, fd_lookup_flags, path, fd_oflags, fd_rights, fd_rights_inherited, fd_flags, ..] = + args_pat + else { bail!( "open expects 7 parameters: the path and the dirfd, fd_lookup_flags, path, fd_oflags, fd_rights ... Got: {} parameters.", args.len() ); }; - let dirfd = dirfd.as_int() + let dirfd = dirfd + .as_int() .ok_or_else(|| anyhow!("dirfd must be a number"))?; - let fd_lookup_flags = fd_lookup_flags.as_int() + let fd_lookup_flags = fd_lookup_flags + .as_int() .ok_or_else(|| anyhow!("fd_lookup_flags must be a number"))?; - let oflags = fd_oflags.as_int() + let oflags = fd_oflags + .as_int() .ok_or_else(|| anyhow!("oflags must be a number"))?; let fs_rights_base = jsvalue2int64!(fd_rights); let fd_rights_inherited = jsvalue2int64!(fd_rights_inherited); - let fdflags = fd_flags.as_int() + let fdflags = fd_flags + .as_int() .ok_or_else(|| anyhow!("fdflags must be a number"))?; let opened_fd_ptr = (&mut opened_fd as *mut i32) as i32; - let path = path.as_string() + let path = path + .as_string() .ok_or_else(|| anyhow!("path must be a string"))? .to_string() .map_err(|_| anyhow!("invalid UTF-8 in path"))?; @@ -47,15 +42,16 @@ pub fn wasi_preview1_open<'a>(args: Args<'a>) -> Result> { let path_len = path.len() as i32; let rs = unsafe { preview_1::path_open( - dirfd, - fd_lookup_flags, - path_ptr, - path_len, - oflags, - fs_rights_base, - fd_rights_inherited, - fdflags, - opened_fd_ptr) + dirfd, + fd_lookup_flags, + path_ptr, + path_len, + oflags, + fs_rights_base, + fd_rights_inherited, + fdflags, + opened_fd_ptr, + ) }; if rs == 0 { Ok(Descriptor::new(cx.clone(), opened_fd)?) @@ -63,6 +59,4 @@ pub fn wasi_preview1_open<'a>(args: Args<'a>) -> Result> { process_error(cx.clone(), rs)?; Ok(Value::new_null(cx.clone())) } - - } diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index 8346399..418746a 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -11,31 +11,18 @@ unsafe extern "C" { _fs_rights_inheriting: i64, fdflags: i32, fd_ptr: i32, - ) -> i32; + ) -> i32; - #[link_name = "path_create_directory"] - pub unsafe fn path_create_directory( - dirfd: i32, - path_ptr: i32, - path_len: i32, - ) -> i32; + #[link_name = "path_create_directory"] + pub unsafe fn path_create_directory(dirfd: i32, path_ptr: i32, path_len: i32) -> i32; - #[link_name = "path_remove_directory"] - pub unsafe fn path_remove_directory( - dirfd: i32, - path_ptr: i32, - path_len: i32, - ) -> i32; + #[link_name = "path_remove_directory"] + pub unsafe fn path_remove_directory(dirfd: i32, path_ptr: i32, path_len: i32) -> i32; + #[link_name = "path_unlink_file"] + pub unsafe fn path_unlink_file(dirfd: i32, path_ptr: i32, path_len: i32) -> i32; - #[link_name = "path_unlink_file"] - pub unsafe fn path_unlink_file( - dirfd: i32, - path_ptr: i32, - path_len: i32, - ) -> i32; - - #[link_name = "path_symlink"] + #[link_name = "path_symlink"] pub unsafe fn path_symlink( old_path_ptr: i32, old_path_len: i32, @@ -44,49 +31,26 @@ unsafe extern "C" { new_path_len: i32, ) -> i32; - #[link_name = "fd_close"] - pub unsafe fn fd_close( - fd: i32, - ) -> i32; + #[link_name = "fd_close"] + pub unsafe fn fd_close(fd: i32) -> i32; - #[link_name = "fd_sync"] - pub unsafe fn fd_sync( - fd: i32, - ) -> i32; + #[link_name = "fd_sync"] + pub unsafe fn fd_sync(fd: i32) -> i32; - #[link_name = "fd_datasync"] - pub unsafe fn fd_datasync( - fd: i32, - ) -> i32; + #[link_name = "fd_datasync"] + pub unsafe fn fd_datasync(fd: i32) -> i32; #[link_name = "fd_prestat_get"] - pub unsafe fn fd_prestat_get( - fd: i32, - path_len: i32, - ) -> i32; + pub unsafe fn fd_prestat_get(fd: i32, path_len: i32) -> i32; #[link_name = "fd_read"] - pub unsafe fn fd_read( - fd: i32, - iovec_slice: i32, - iovec_len: i32, - readn_ptr: i32, - ) -> i32; + pub unsafe fn fd_read(fd: i32, iovec_slice: i32, iovec_len: i32, readn_ptr: i32) -> i32; #[link_name = "fd_write"] - pub unsafe fn fd_write( - fd: i32, - iovec_slice: i32, - iovec_len: i32, - writen_ptr: i32, - ) -> i32; + pub unsafe fn fd_write(fd: i32, iovec_slice: i32, iovec_len: i32, writen_ptr: i32) -> i32; #[link_name = "fd_prestat_dir_name"] - pub unsafe fn fd_prestat_dir_name( - fd: i32, - path_ptr: i32, - path_len: i32, - ) -> i32; + pub unsafe fn fd_prestat_dir_name(fd: i32, path_ptr: i32, path_len: i32) -> i32; #[link_name = "path_link"] pub unsafe fn path_link( @@ -119,69 +83,32 @@ unsafe extern "C" { ) -> i32; #[link_name = "fd_advise"] - pub unsafe fn fd_advise( - fd: i32, - offset: u64, - len: u64, - advice: i32, - ) -> i32; + pub unsafe fn fd_advise(fd: i32, offset: u64, len: u64, advice: i32) -> i32; #[link_name = "fd_seek"] - pub unsafe fn fd_seek( - fd: i32, - offset: u64, - whence: i32, - fsize: i32, - ) -> i32; + pub unsafe fn fd_seek(fd: i32, offset: u64, whence: i32, fsize: i32) -> i32; #[link_name = "fd_allocate"] - pub unsafe fn fd_allocate( - fd: i32, - offset: u64, - len: u64, - ) -> i32; + pub unsafe fn fd_allocate(fd: i32, offset: u64, len: u64) -> i32; #[link_name = "fd_filestat_get"] - pub unsafe fn fd_filestat_get( - fd: i32, - stat_ptr: i32 - ) -> i32; + pub unsafe fn fd_filestat_get(fd: i32, stat_ptr: i32) -> i32; #[link_name = "fd_filestat_set_size"] - pub unsafe fn fd_filestat_set_size( - fd: i32, - stat: u64 - ) -> i32; - + pub unsafe fn fd_filestat_set_size(fd: i32, stat: u64) -> i32; + #[link_name = "fd_tell"] - pub unsafe fn fd_tell( - fd: i32, - pos_ptr: i32 - ) -> i32; + pub unsafe fn fd_tell(fd: i32, pos_ptr: i32) -> i32; #[link_name = "fd_filestat_set_times"] - pub unsafe fn fd_filestat_set_times( - fd: i32, - atim: i64, - mtim: i64, - fst_flags: u16, - ) -> i32; + pub unsafe fn fd_filestat_set_times(fd: i32, atim: i64, mtim: i64, fst_flags: u16) -> i32; #[link_name = "fd_fdstat_set_flags"] - pub unsafe fn fd_fdstat_set_flags( - fd: i32, - fd_flags: u16, - ) -> i32; + pub unsafe fn fd_fdstat_set_flags(fd: i32, fd_flags: u16) -> i32; /// Reads directory entries from a file descriptor #[allow(dead_code)] #[link_name = "fd_readdir"] - pub unsafe fn fd_readdir( - fd: i32, - buf: i32, - buf_len: i32, - cookie: u64, - readn_ptr: i32, - ) -> i32; - + pub unsafe fn fd_readdir(fd: i32, buf: i32, buf_len: i32, cookie: u64, readn_ptr: i32) -> i32; + } diff --git a/src/wasi/rename.rs b/src/wasi/rename.rs index 5069cb8..2dee5eb 100644 --- a/src/wasi/rename.rs +++ b/src/wasi/rename.rs @@ -1,8 +1,5 @@ -use javy_plugin_api::javy::{ - quickjs::Value, - Args -}; use anyhow::{anyhow, bail, Result}; +use javy_plugin_api::javy::{quickjs::Value, Args}; use super::{preview_1, process_error}; @@ -14,28 +11,26 @@ use super::{preview_1, process_error}; /// - `new_path`: The path of the new file. pub fn wasi_preview1_path_rename(args: Args<'_>) -> Result> { let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let [ - old_dirfd, - old_path, - new_dirfd, - new_path, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [old_dirfd, old_path, new_dirfd, new_path, ..] = args_pat else { bail!( "path_remove_directory expects 4 parameters: the dirfd and path, Got: {} parameters.", args.len() ); }; - let dirfd = old_dirfd.as_int() + let dirfd = old_dirfd + .as_int() .ok_or_else(|| anyhow!("old_dirfd must be a number"))?; - let old_path = old_path.as_string() + let old_path = old_path + .as_string() .ok_or_else(|| anyhow!("old_path must be a string"))? .to_string() .map_err(|_| anyhow!("invalid UTF-8 in path"))?; - let new_dirfd = new_dirfd.as_int() + let new_dirfd = new_dirfd + .as_int() .ok_or_else(|| anyhow!("new_dirfd must be a number"))?; - let new_path = new_path.as_string() + let new_path = new_path + .as_string() .ok_or_else(|| anyhow!("new_path must be a string"))? .to_string() .map_err(|_| anyhow!("invalid UTF-8 in path"))?; @@ -43,13 +38,15 @@ pub fn wasi_preview1_path_rename(args: Args<'_>) -> Result> { let new_path_len = new_path.len() as i32; let old_path_ptr = old_path.as_ptr() as i32; let old_path_len = old_path.len() as i32; - let rs = unsafe { + let rs = unsafe { preview_1::path_rename( - dirfd, - old_path_ptr, old_path_len, - new_dirfd, - new_path_ptr, new_path_len - ) + dirfd, + old_path_ptr, + old_path_len, + new_dirfd, + new_path_ptr, + new_path_len, + ) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx.clone(), rs)) diff --git a/src/wasi/rmdir.rs b/src/wasi/rmdir.rs index 822f923..48c55aa 100644 --- a/src/wasi/rmdir.rs +++ b/src/wasi/rmdir.rs @@ -1,12 +1,8 @@ -use javy_plugin_api::javy::{ - quickjs::Value, - Args -}; use anyhow::{anyhow, bail, Result}; +use javy_plugin_api::javy::{quickjs::Value, Args}; use super::{preview_1, process_error}; - /// Remove a directory at the given path. /// This function is used to remove a directory at the given path. /// It is used to remove a directory at the given path. @@ -14,20 +10,18 @@ use super::{preview_1, process_error}; /// - `path`: The path of the directory to remove. pub fn wasi_preview1_path_remove_directory(args: Args<'_>) -> Result> { let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let [ - dirfd, - path, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [dirfd, path, ..] = args_pat else { bail!( "path_remove_directory expects 2 parameters: the dirfd and path, Got: {} parameters.", args.len() ); }; - let dirfd = dirfd.as_int() + let dirfd = dirfd + .as_int() .ok_or_else(|| anyhow!("dirfd must be a number"))?; - let path = path.as_string() + let path = path + .as_string() .ok_or_else(|| anyhow!("path must be a string"))? .to_string() .map_err(|_| anyhow!("invalid UTF-8 in path"))?; diff --git a/src/wasi/stat.rs b/src/wasi/stat.rs index cf2f960..ab5cc5d 100644 --- a/src/wasi/stat.rs +++ b/src/wasi/stat.rs @@ -1,8 +1,8 @@ +use anyhow::{anyhow, bail, Result}; use javy_plugin_api::javy::{ - quickjs::{Object as JObject, Value, Ctx}, - Args + quickjs::{Ctx, Object as JObject, Value}, + Args, }; -use anyhow::{anyhow, bail, Result}; use super::{preview_1, process_error, FileType, Filestat}; @@ -13,23 +13,21 @@ use super::{preview_1, process_error, FileType, Filestat}; /// - `path`: The path of the file to get the status of. pub fn wasi_preview1_path_filestat_get(args: Args<'_>) -> Result> { let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let [ - dirfd, - lookup_flags, - path, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [dirfd, lookup_flags, path, ..] = args_pat else { bail!( "wasi_preview1_path_filestat_get expects 3 parameters: the dirfd and path, Got: {} parameters.", args.len() ); }; - let dirfd = dirfd.as_int() + let dirfd = dirfd + .as_int() .ok_or_else(|| anyhow!("dirfd must be a number"))?; - let lookup_flags = lookup_flags.as_int() + let lookup_flags = lookup_flags + .as_int() .ok_or_else(|| anyhow!("lookup_flags must be a number"))?; - let path = path.as_string() + let path = path + .as_string() .ok_or_else(|| anyhow!("path must be a string"))? .to_string() .map_err(|_| anyhow!("invalid UTF-8 in path"))?; @@ -37,13 +35,8 @@ pub fn wasi_preview1_path_filestat_get(args: Args<'_>) -> Result> { let path_len = path.len() as i32; let mut fd_stat: Filestat = Default::default(); let fd_stat_ptr = &mut fd_stat as *mut _ as i32; - let rs = unsafe { - preview_1::path_filestat_get( - dirfd, - lookup_flags, - path_ptr, path_len, - fd_stat_ptr, - ) + let rs = unsafe { + preview_1::path_filestat_get(dirfd, lookup_flags, path_ptr, path_len, fd_stat_ptr) }; if rs == 0 { let stat = filestate_to_jsobject(cx.clone(), &fd_stat)?; @@ -54,10 +47,7 @@ pub fn wasi_preview1_path_filestat_get(args: Args<'_>) -> Result> { } } -pub fn filestate_to_jsobject<'js>( - cx: Ctx<'js>, - fd_stat: &Filestat, -) -> Result> { +pub fn filestate_to_jsobject<'js>(cx: Ctx<'js>, fd_stat: &Filestat) -> Result> { let stat = JObject::new(cx.clone())?; stat.set("filetype", fd_stat.filetype)?; let filetype: &str = FileType(fd_stat.filetype).into(); @@ -67,4 +57,4 @@ pub fn filestate_to_jsobject<'js>( stat.set("modification_time", fd_stat.mtim)?; stat.set("creation_time", fd_stat.ctim)?; Ok(stat) -} \ No newline at end of file +} diff --git a/src/wasi/symlink.rs b/src/wasi/symlink.rs index 34a25e4..4926ac3 100644 --- a/src/wasi/symlink.rs +++ b/src/wasi/symlink.rs @@ -1,8 +1,5 @@ -use javy_plugin_api::javy::{ - quickjs::Value, - Args -}; use anyhow::{anyhow, bail, Result}; +use javy_plugin_api::javy::{quickjs::Value, Args}; use super::{preview_1, process_error}; @@ -13,25 +10,23 @@ use super::{preview_1, process_error}; /// - `new_path`: The name of the new symbolic link. pub fn wasi_preview1_path_symlink(args: Args<'_>) -> Result> { let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let [ - old_path, - dirfd, - new_path, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [old_path, dirfd, new_path, ..] = args_pat else { bail!( "path_symlink expects 3 parameters: the old_path, fd and new_path, Got: {} parameters.", args.len() ); }; - let old_path = old_path.as_string() + let old_path = old_path + .as_string() .ok_or_else(|| anyhow!("old_path must be a string"))? .to_string() .map_err(|_| anyhow!("invalid UTF-8 in old_path"))?; - let dirfd = dirfd.as_int() + let dirfd = dirfd + .as_int() .ok_or_else(|| anyhow!("fd must be a number"))?; - let new_path = new_path.as_string() + let new_path = new_path + .as_string() .ok_or_else(|| anyhow!("new_path must be a string"))? .to_string() .map_err(|_| anyhow!("invalid UTF-8 in new_path"))?; @@ -39,11 +34,15 @@ pub fn wasi_preview1_path_symlink(args: Args<'_>) -> Result> { let old_path_len = old_path.len() as i32; let new_path_ptr = new_path.as_ptr() as i32; let new_path_len = new_path.len() as i32; - let rs = unsafe { preview_1::path_symlink( - old_path_ptr, old_path_len, - dirfd, - new_path_ptr, new_path_len - )}; + let rs = unsafe { + preview_1::path_symlink( + old_path_ptr, + old_path_len, + dirfd, + new_path_ptr, + new_path_len, + ) + }; process_error(cx.clone(), rs)?; Ok(Value::new_null(cx.clone())) -} \ No newline at end of file +} diff --git a/src/wasi/unlink.rs b/src/wasi/unlink.rs index 1ce1a6b..4c0bcd0 100644 --- a/src/wasi/unlink.rs +++ b/src/wasi/unlink.rs @@ -1,23 +1,15 @@ -use javy_plugin_api::javy::{ - quickjs::Value, - Args -}; use anyhow::{anyhow, bail, Result}; +use javy_plugin_api::javy::{quickjs::Value, Args}; use super::{preview_1, process_error}; - /// Unlink a file at the given path. /// - `dirfd`: The directory file descriptor of the directory containing the file. /// - `path`: The path of the file to unlink. pub fn wasi_preview1_path_unlink_file(args: Args<'_>) -> Result> { let (cx, args) = args.release(); - let args_pat: &[Value<'_>]= &args.0; - let [ - dirfd, - path, - .. - ] = args_pat else { + let args_pat: &[Value<'_>] = &args.0; + let [dirfd, path, ..] = args_pat else { bail!( "path_unlink_file expects 2 parameters: the dirfd and path, Got: {} parameters.", args.len() @@ -25,19 +17,18 @@ pub fn wasi_preview1_path_unlink_file(args: Args<'_>) -> Result> { }; // dirfd is the file descriptor of the directory - let dirfd = dirfd.as_int() + let dirfd = dirfd + .as_int() .ok_or_else(|| anyhow!("dirfd must be a number"))?; // path is the path to the file - let path = path.as_string() + let path = path + .as_string() .ok_or_else(|| anyhow!("path must be a string"))? .to_string() .map_err(|_| anyhow!("invalid UTF-8 in path"))?; let path_ptr = path.as_ptr() as i32; let path_len = path.len() as i32; - let rs = unsafe { preview_1::path_unlink_file( - dirfd, - path_ptr, path_len - )}; + let rs = unsafe { preview_1::path_unlink_file(dirfd, path_ptr, path_len) }; process_error(cx.clone(), rs)?; Ok(Value::new_int(cx.clone(), rs)) } From c75adbebf02d7d05a350aa73f118dad45db764b3 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Thu, 29 May 2025 10:41:43 +0800 Subject: [PATCH 36/45] fix the issue --- src/wasi/descriptor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wasi/descriptor.rs b/src/wasi/descriptor.rs index 63f0bb3..dc6b549 100644 --- a/src/wasi/descriptor.rs +++ b/src/wasi/descriptor.rs @@ -148,7 +148,7 @@ impl Descriptor { let buffer = &args.0[0]; let null = Value::new_null(cx.clone()); let mut size = &null; - if args.0.len() > 2 { + if args.0.len() > 1 { size = &args.0[1]; } @@ -205,7 +205,7 @@ impl Descriptor { let buffer = &args.0[0]; let null = Value::new_null(cx.clone()); let mut size = &null; - if args.0.len() > 2 { + if args.0.len() > 1 { size = &args.0[1]; } From 97c544d15fd0e0a82cff684e933be0ff8cb0bd16 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Thu, 29 May 2025 10:50:05 +0800 Subject: [PATCH 37/45] rename parameter name path_len to the prestat_ptr --- src/wasi/preview_1.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasi/preview_1.rs b/src/wasi/preview_1.rs index 418746a..5b9bbbf 100644 --- a/src/wasi/preview_1.rs +++ b/src/wasi/preview_1.rs @@ -41,7 +41,7 @@ unsafe extern "C" { pub unsafe fn fd_datasync(fd: i32) -> i32; #[link_name = "fd_prestat_get"] - pub unsafe fn fd_prestat_get(fd: i32, path_len: i32) -> i32; + pub unsafe fn fd_prestat_get(fd: i32, path_len_ptr: i32) -> i32; #[link_name = "fd_read"] pub unsafe fn fd_read(fd: i32, iovec_slice: i32, iovec_len: i32, readn_ptr: i32) -> i32; From bf1180a502038c95dee8d24d5a29e2465279d9d1 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Fri, 30 May 2025 12:23:43 +0800 Subject: [PATCH 38/45] replace wasi with wasip1 --- Cargo.toml | 4 ++-- src/lib.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0dc67b2..21768f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,5 +30,5 @@ opt-level = 3 crypto = [] fetch = [] llm = [] -wasi = [] -default = ["crypto", "fetch", "wasi"] +wasip1 = [] +default = ["crypto", "fetch", "wasip1"] diff --git a/src/lib.rs b/src/lib.rs index 63b9df4..a5eeeaf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ pub mod crypto; pub mod fetch; #[cfg(feature = "llm")] pub mod llm; -#[cfg(feature = "wasi")] +#[cfg(feature = "wasip1")] pub mod wasi; #[cfg(feature = "crypto")] @@ -64,7 +64,7 @@ pub extern "C" fn initialize_runtime() { )?, )?; - #[cfg(feature = "wasi")] + #[cfg(feature = "wasip1")] { macro_rules! bind { (function, $l: ident) => { @@ -120,7 +120,7 @@ pub extern "C" fn initialize_runtime() { ctx.eval::<(), _>(include_str!("crypto/crypto.js"))?; #[cfg(feature = "fetch")] ctx.eval::<(), _>(include_str!("fetch/fetch.js"))?; - #[cfg(feature = "wasi")] + #[cfg(feature = "wasip1")] ctx.eval::<(), _>(include_str!("wasi/preview_1.js"))?; Ok::<_, anyhow::Error>(()) }) From e51f134922db894f9912dd13fb4d89a8b12aee6a Mon Sep 17 00:00:00 2001 From: "Join.Gong" Date: Sat, 7 Jun 2025 07:21:40 +0800 Subject: [PATCH 39/45] Apply suggestions from code review Co-authored-by: zees-dev <63374656+zees-dev@users.noreply.github.com> --- src/wasi/stat.rs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/wasi/stat.rs b/src/wasi/stat.rs index ab5cc5d..c125ee3 100644 --- a/src/wasi/stat.rs +++ b/src/wasi/stat.rs @@ -14,23 +14,16 @@ use super::{preview_1, process_error, FileType, Filestat}; pub fn wasi_preview1_path_filestat_get(args: Args<'_>) -> Result> { let (cx, args) = args.release(); let args_pat: &[Value<'_>] = &args.0; - let [dirfd, lookup_flags, path, ..] = args_pat else { + if args.len() != 3 { bail!( - "wasi_preview1_path_filestat_get expects 3 parameters: the dirfd and path, Got: {} parameters.", + "wasi_preview1_path_filestat_get expects exactly 3 parameters \ + (dirfd, lookup_flags, path); got {}.", args.len() ); - }; - let dirfd = dirfd - .as_int() - .ok_or_else(|| anyhow!("dirfd must be a number"))?; - let lookup_flags = lookup_flags - .as_int() - .ok_or_else(|| anyhow!("lookup_flags must be a number"))?; - let path = path - .as_string() - .ok_or_else(|| anyhow!("path must be a string"))? - .to_string() - .map_err(|_| anyhow!("invalid UTF-8 in path"))?; + } + let dirfd = &args_pat[0]; + let lookup_flags = &args_pat[1]; + let path = &args_pat[2]; let path_ptr = path.as_ptr() as i32; let path_len = path.len() as i32; let mut fd_stat: Filestat = Default::default(); From 7ee24c9162079f9f5ff01eb012b8c28d374bdf95 Mon Sep 17 00:00:00 2001 From: "Join.Gong" Date: Sat, 7 Jun 2025 07:22:16 +0800 Subject: [PATCH 40/45] Apply suggestions from code review Co-authored-by: zees-dev <63374656+zees-dev@users.noreply.github.com> --- src/wasi/link.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasi/link.rs b/src/wasi/link.rs index a39b4a6..b0cd378 100644 --- a/src/wasi/link.rs +++ b/src/wasi/link.rs @@ -15,7 +15,7 @@ pub fn wasi_preview1_path_link(args: Args<'_>) -> Result> { let args_pat: &[Value<'_>] = &args.0; let [old_dirfd, fd_lookup_flags, old_path, new_dirfd, new_path, ..] = args_pat else { bail!( - "path_remove_directory expects 5 parameters: the dirfd and path, Got: {} parameters.", + "path_link expects 5 parameters: old_dirfd, fd_lookup_flags, old_path, new_dirfd, new_path. Got: {} parameters.", args.len() ); }; From 78bd0cbacf3b4f949a6964f79f1d2586b061c59e Mon Sep 17 00:00:00 2001 From: "Join.Gong" Date: Sat, 7 Jun 2025 07:22:44 +0800 Subject: [PATCH 41/45] Apply suggestions from code review Co-authored-by: zees-dev <63374656+zees-dev@users.noreply.github.com> --- src/wasi/preview_1.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasi/preview_1.js b/src/wasi/preview_1.js index 5750618..4d0e140 100644 --- a/src/wasi/preview_1.js +++ b/src/wasi/preview_1.js @@ -38,7 +38,7 @@ PATH_READLINK: 0x8000, PATH_RENAME_SOURCE: 0x10000, PATH_RENAME_TARGET: 0x20000, - PATH_FILESTAT_GET: 40000, + PATH_FILESTAT_GET: 0x40000, PATH_FILESTAT_SET_SIZE: 0x80000, PATH_FILESTAT_SET_TIMES: 0x100000, FD_FILESTAT_GET: 0x200000, From e3ae7f350aa22c9c695edc4ed0470feee9f35ad7 Mon Sep 17 00:00:00 2001 From: "Join.Gong" Date: Sat, 7 Jun 2025 07:23:24 +0800 Subject: [PATCH 42/45] Apply suggestions from code review Co-authored-by: zees-dev <63374656+zees-dev@users.noreply.github.com> --- src/wasi/rename.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wasi/rename.rs b/src/wasi/rename.rs index 2dee5eb..4f34911 100644 --- a/src/wasi/rename.rs +++ b/src/wasi/rename.rs @@ -3,8 +3,7 @@ use javy_plugin_api::javy::{quickjs::Value, Args}; use super::{preview_1, process_error}; -/// This function is used to link a file at the given path to a new path. -/// It is used to rename a file from one path to another. +/// This function is used to rename a file or directory from one path to another. /// - `old_dirfd`: The directory file descriptor of the old file. /// - `old_path`: The path of the old file. /// - `new_dirfd`: The directory file descriptor of the new file. From 0cc0319d92c7afab6f6346e352168287378888a1 Mon Sep 17 00:00:00 2001 From: "Join.Gong" Date: Sat, 7 Jun 2025 07:24:09 +0800 Subject: [PATCH 43/45] Apply suggestions from code review Co-authored-by: zees-dev <63374656+zees-dev@users.noreply.github.com> --- src/wasi/rename.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasi/rename.rs b/src/wasi/rename.rs index 4f34911..bf28c06 100644 --- a/src/wasi/rename.rs +++ b/src/wasi/rename.rs @@ -13,7 +13,7 @@ pub fn wasi_preview1_path_rename(args: Args<'_>) -> Result> { let args_pat: &[Value<'_>] = &args.0; let [old_dirfd, old_path, new_dirfd, new_path, ..] = args_pat else { bail!( - "path_remove_directory expects 4 parameters: the dirfd and path, Got: {} parameters.", + "path_rename expects 4 parameters: old_dirfd, old_path, new_dirfd, new_path. Got: {} parameters.", args.len() ); }; From 5cb2f8ff85378511590f126144ec56d9f3639f17 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Sat, 7 Jun 2025 07:34:58 +0800 Subject: [PATCH 44/45] fmt --- src/wasi/stat.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wasi/stat.rs b/src/wasi/stat.rs index c125ee3..3275b48 100644 --- a/src/wasi/stat.rs +++ b/src/wasi/stat.rs @@ -21,9 +21,9 @@ pub fn wasi_preview1_path_filestat_get(args: Args<'_>) -> Result> { args.len() ); } - let dirfd = &args_pat[0]; + let dirfd = &args_pat[0]; let lookup_flags = &args_pat[1]; - let path = &args_pat[2]; + let path = &args_pat[2]; let path_ptr = path.as_ptr() as i32; let path_len = path.len() as i32; let mut fd_stat: Filestat = Default::default(); From 2c408c4abd6baff092cfec08d5e8a60f2338fa28 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Sat, 7 Jun 2025 07:42:39 +0800 Subject: [PATCH 45/45] fix the compile issue --- src/wasi/stat.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/wasi/stat.rs b/src/wasi/stat.rs index 3275b48..ab5cc5d 100644 --- a/src/wasi/stat.rs +++ b/src/wasi/stat.rs @@ -14,16 +14,23 @@ use super::{preview_1, process_error, FileType, Filestat}; pub fn wasi_preview1_path_filestat_get(args: Args<'_>) -> Result> { let (cx, args) = args.release(); let args_pat: &[Value<'_>] = &args.0; - if args.len() != 3 { + let [dirfd, lookup_flags, path, ..] = args_pat else { bail!( - "wasi_preview1_path_filestat_get expects exactly 3 parameters \ - (dirfd, lookup_flags, path); got {}.", + "wasi_preview1_path_filestat_get expects 3 parameters: the dirfd and path, Got: {} parameters.", args.len() ); - } - let dirfd = &args_pat[0]; - let lookup_flags = &args_pat[1]; - let path = &args_pat[2]; + }; + let dirfd = dirfd + .as_int() + .ok_or_else(|| anyhow!("dirfd must be a number"))?; + let lookup_flags = lookup_flags + .as_int() + .ok_or_else(|| anyhow!("lookup_flags must be a number"))?; + let path = path + .as_string() + .ok_or_else(|| anyhow!("path must be a string"))? + .to_string() + .map_err(|_| anyhow!("invalid UTF-8 in path"))?; let path_ptr = path.as_ptr() as i32; let path_len = path.len() as i32; let mut fd_stat: Filestat = Default::default();