diff --git a/plugins/PLUGINS.md b/plugins/PLUGINS.md index 6d00dfd..24a0734 100644 --- a/plugins/PLUGINS.md +++ b/plugins/PLUGINS.md @@ -333,7 +333,7 @@ Current Plugin 2.0 samples: |--------|---------| | `sample-wasm` | C/WASI file bridge sample using `pluginData`, `downloads`, and `overlord_host_info`. | | `sample-wasm-hostinfo` | TinyGo sample that queries HostInfo from a universal WASM module. | -| `sample-wasm-platform-note` | Rust sample that branches by `os`/`arch` inside one WASM module and writes a platform-specific note. | +| `sample-wasm-platform-note` | Rust sample that branches by `os`/`arch`, writes a platform-specific note, and probes Desktop list/read access. | | `sample-ts-fullstack` | TypeScript UI plus TypeScript server runtime with shared local modules. | Legacy native samples remain available: diff --git a/plugins/sample-wasm-platform-note/README.md b/plugins/sample-wasm-platform-note/README.md index 6540877..cf4eec7 100644 --- a/plugins/sample-wasm-platform-note/README.md +++ b/plugins/sample-wasm-platform-note/README.md @@ -1,6 +1,8 @@ # Sample WASM Platform Note Plugin -This Plugin 2.0 sample is written in Rust. It reads HostInfo, branches on `os` and `arch`, and writes a small note into the approved `pluginData` bucket. +This Plugin 2.0 sample is written in Rust. It reads HostInfo, branches on `os` and `arch`, writes a small note into the approved `pluginData` bucket, then lists the user's Desktop and attempts to read a small sample from one regular file to prove the file bridge is working. + +The manifest requests `pluginData` write/mkdir access and `desktop` list/read access, so changing this sample's permissions will require approving the new needs hash before load. On Windows, install the Rust WASI target first: diff --git a/plugins/sample-wasm-platform-note/native/src/lib.rs b/plugins/sample-wasm-platform-note/native/src/lib.rs index b684f15..7c1c938 100644 --- a/plugins/sample-wasm-platform-note/native/src/lib.rs +++ b/plugins/sample-wasm-platform-note/native/src/lib.rs @@ -6,6 +6,22 @@ use std::str; extern "C" { fn overlord_emit(event: *const u8, event_len: i32, payload: *const u8, payload_len: i32) -> i32; fn overlord_host_info(out: *mut u8, out_len: i32) -> i32; + fn overlord_fs_list( + bucket: *const u8, + bucket_len: i32, + path: *const u8, + path_len: i32, + out: *mut u8, + out_len: i32, + ) -> i32; + fn overlord_fs_read( + bucket: *const u8, + bucket_len: i32, + path: *const u8, + path_len: i32, + out: *mut u8, + out_len: i32, + ) -> i32; fn overlord_fs_mkdir(bucket: *const u8, bucket_len: i32, path: *const u8, path_len: i32) -> i32; fn overlord_fs_write( bucket: *const u8, @@ -102,9 +118,14 @@ pub extern "C" fn overlord_on_event( note.len() as i32, ) }; + let desktop = probe_desktop(host); let response = format!( - r#"{{"os":"{os}","arch":"{arch}","mkdir":{mkdir_result},"write":{write_result}}}"# + r#"{{"os":"{os}","arch":"{arch}","mkdir":{mkdir_result},"write":{write_result},"desktopList":{},"desktopRead":{},"desktopFile":"{}","desktopSize":{}}}"#, + desktop.list_result, + desktop.read_result, + json_escape(&desktop.file), + desktop.size, ); emit("platform_note", response.as_bytes()); 0 @@ -124,6 +145,155 @@ fn emit(event: &str, payload: &[u8]) { } } +struct DesktopProbe { + file: String, + size: i64, + list_result: i32, + read_result: i32, +} + +fn probe_desktop(seed: &str) -> DesktopProbe { + let bucket = "desktop"; + let path = ""; + let mut listing = Vec::new(); + let mut list_result = -4; + for size in [8 * 1024, 64 * 1024, 256 * 1024, 1024 * 1024] { + listing.resize(size, 0); + list_result = unsafe { + overlord_fs_list( + bucket.as_ptr(), + bucket.len() as i32, + path.as_ptr(), + path.len() as i32, + listing.as_mut_ptr(), + listing.len() as i32, + ) + }; + if list_result != -4 { + break; + } + } + if list_result <= 0 { + return DesktopProbe { + file: String::new(), + size: -1, + list_result, + read_result: -3, + }; + } + + let listing = str::from_utf8(&listing[..list_result as usize]).unwrap_or(""); + let files = desktop_files_from_listing(listing); + if files.is_empty() { + return DesktopProbe { + file: String::new(), + size: -1, + list_result, + read_result: -3, + }; + } + + let seed_sum = seed.bytes().fold(0usize, |acc, b| acc.wrapping_add(b as usize)); + let selected = &files[seed_sum % files.len()]; + let mut buf = [0u8; 128]; + let read_result = unsafe { + overlord_fs_read( + bucket.as_ptr(), + bucket.len() as i32, + selected.path.as_ptr(), + selected.path.len() as i32, + buf.as_mut_ptr(), + buf.len() as i32, + ) + }; + + DesktopProbe { + file: selected.path.clone(), + size: selected.size, + list_result, + read_result, + } +} + +struct DesktopFile { + path: String, + size: i64, +} + +fn desktop_files_from_listing(listing: &str) -> Vec { + let mut files = Vec::new(); + let mut rest = listing; + while let Some(path_key) = rest.find(r#""path":"#) { + rest = &rest[path_key + 7..]; + let Some((path, after_path)) = parse_json_string(rest) else { + break; + }; + rest = after_path; + let next_path = rest.find(r#""path":"#).unwrap_or(rest.len()); + let entry = &rest[..next_path]; + if !entry.contains(r#""isDir":false"#) { + continue; + } + let size = parse_json_i64_after(entry, r#""size":"#).unwrap_or(-1); + files.push(DesktopFile { path, size }); + } + files +} + +fn parse_json_string(input: &str) -> Option<(String, &str)> { + let input = input.strip_prefix('"')?; + let mut out = String::new(); + let mut escaped = false; + for (idx, ch) in input.char_indices() { + if escaped { + match ch { + '"' => out.push('"'), + '\\' => out.push('\\'), + '/' => out.push('/'), + 'b' => out.push('\u{0008}'), + 'f' => out.push('\u{000c}'), + 'n' => out.push('\n'), + 'r' => out.push('\r'), + 't' => out.push('\t'), + other => out.push(other), + } + escaped = false; + continue; + } + match ch { + '\\' => escaped = true, + '"' => return Some((out, &input[idx + 1..])), + other => out.push(other), + } + } + None +} + +fn parse_json_i64_after(input: &str, key: &str) -> Option { + let start = input.find(key)? + key.len(); + let bytes = input.as_bytes(); + let mut end = start; + while end < bytes.len() && (bytes[end].is_ascii_digit() || bytes[end] == b'-') { + end += 1; + } + input[start..end].parse().ok() +} + +fn json_escape(input: &str) -> String { + let mut out = String::with_capacity(input.len()); + for ch in input.chars() { + match ch { + '"' => out.push_str("\\\""), + '\\' => out.push_str("\\\\"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + other => out.push(other), + } + } + out +} + #[no_mangle] pub extern "C" fn memset(dest: *mut u8, value: i32, len: usize) -> *mut u8 { unsafe { ptr::write_bytes(dest, value as u8, len) }; diff --git a/plugins/sample-wasm-platform-note/native/target/.rustc_info.json b/plugins/sample-wasm-platform-note/native/target/.rustc_info.json index b3d198b..19ad8dc 100644 --- a/plugins/sample-wasm-platform-note/native/target/.rustc_info.json +++ b/plugins/sample-wasm-platform-note/native/target/.rustc_info.json @@ -1 +1 @@ -{"rustc_fingerprint":2568544898921049753,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\this1\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"5946400966674136012":{"success":true,"status":"","code":0,"stdout":"___.wasm\nlib___.rlib\n___.wasm\nlib___.a\nC:\\Users\\this1\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\noff\n___\ndebug_assertions\npanic=\"abort\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"wasm32\"\ntarget_endian=\"little\"\ntarget_env=\"p1\"\ntarget_family=\"wasm\"\ntarget_feature=\"bulk-memory\"\ntarget_feature=\"multivalue\"\ntarget_feature=\"mutable-globals\"\ntarget_feature=\"nontrapping-fptoint\"\ntarget_feature=\"reference-types\"\ntarget_feature=\"sign-ext\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"wasi\"\ntarget_pointer_width=\"32\"\ntarget_vendor=\"unknown\"\n","stderr":"warning: dropping unsupported crate type `dylib` for target `wasm32-wasip1`\n\nwarning: dropping unsupported crate type `proc-macro` for target `wasm32-wasip1`\n\nwarning: 2 warnings emitted\n\n"},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.94.0 (4a4ef493e 2026-03-02)\nbinary: rustc\ncommit-hash: 4a4ef493e3a1488c6e321570238084b38948f6db\ncommit-date: 2026-03-02\nhost: x86_64-pc-windows-msvc\nrelease: 1.94.0\nLLVM version: 21.1.8\n","stderr":""}},"successes":{}} \ No newline at end of file +{"rustc_fingerprint":2568544898921049753,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.94.0 (4a4ef493e 2026-03-02)\nbinary: rustc\ncommit-hash: 4a4ef493e3a1488c6e321570238084b38948f6db\ncommit-date: 2026-03-02\nhost: x86_64-pc-windows-msvc\nrelease: 1.94.0\nLLVM version: 21.1.8\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\this1\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"5946400966674136012":{"success":true,"status":"","code":0,"stdout":"___.wasm\nlib___.rlib\n___.wasm\nlib___.a\nC:\\Users\\this1\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\noff\n___\ndebug_assertions\npanic=\"abort\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"wasm32\"\ntarget_endian=\"little\"\ntarget_env=\"p1\"\ntarget_family=\"wasm\"\ntarget_feature=\"bulk-memory\"\ntarget_feature=\"multivalue\"\ntarget_feature=\"mutable-globals\"\ntarget_feature=\"nontrapping-fptoint\"\ntarget_feature=\"reference-types\"\ntarget_feature=\"sign-ext\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"wasi\"\ntarget_pointer_width=\"32\"\ntarget_vendor=\"unknown\"\n","stderr":"warning: dropping unsupported crate type `dylib` for target `wasm32-wasip1`\n\nwarning: dropping unsupported crate type `proc-macro` for target `wasm32-wasip1`\n\nwarning: 2 warnings emitted\n\n"}},"successes":{}} \ No newline at end of file diff --git a/plugins/sample-wasm-platform-note/sample-wasm-platform-note.html b/plugins/sample-wasm-platform-note/sample-wasm-platform-note.html index b07572a..4a74db2 100644 --- a/plugins/sample-wasm-platform-note/sample-wasm-platform-note.html +++ b/plugins/sample-wasm-platform-note/sample-wasm-platform-note.html @@ -7,6 +7,10 @@
Arch-
mkdir-
write-
+
Desktop file-
+
Desktop size-
+
Desktop list-
+
Desktop read-

 
diff --git a/plugins/sample-wasm-platform-note/sample-wasm-platform-note.js b/plugins/sample-wasm-platform-note/sample-wasm-platform-note.js
index 7d9289b..d8188e0 100644
--- a/plugins/sample-wasm-platform-note/sample-wasm-platform-note.js
+++ b/plugins/sample-wasm-platform-note/sample-wasm-platform-note.js
@@ -7,6 +7,10 @@
     arch: document.getElementById("sample-wasm-platform-note-arch"),
     mkdir: document.getElementById("sample-wasm-platform-note-mkdir"),
     write: document.getElementById("sample-wasm-platform-note-write"),
+    desktopFile: document.getElementById("sample-wasm-platform-note-desktop-file"),
+    desktopSize: document.getElementById("sample-wasm-platform-note-desktop-size"),
+    desktopList: document.getElementById("sample-wasm-platform-note-desktop-list"),
+    desktopRead: document.getElementById("sample-wasm-platform-note-desktop-read"),
   };
   const clientId = new URLSearchParams(window.location.search).get("clientId");
   const pluginId = "sample-wasm-platform-note";
@@ -34,6 +38,10 @@
     if (fields.arch) fields.arch.textContent = info.arch || "-";
     if (fields.mkdir) fields.mkdir.textContent = String(info.mkdir ?? "-");
     if (fields.write) fields.write.textContent = String(info.write ?? "-");
+    if (fields.desktopFile) fields.desktopFile.textContent = info.desktopFile || "(no readable file found)";
+    if (fields.desktopSize) fields.desktopSize.textContent = Number.isFinite(info.desktopSize) && info.desktopSize >= 0 ? `${info.desktopSize} bytes` : "-";
+    if (fields.desktopList) fields.desktopList.textContent = String(info.desktopList ?? "-");
+    if (fields.desktopRead) fields.desktopRead.textContent = String(info.desktopRead ?? "-");
     setStatus(info.write === 0 ? "Platform note written" : "Platform note result received");
     log(JSON.stringify(info, null, 2));
   }