From 869e2153b1cb3da4b6e53851063cc739cf97b6e9 Mon Sep 17 00:00:00 2001 From: Andy Wingo Date: Mon, 15 Sep 2025 16:38:29 +0200 Subject: [PATCH] wasi:filesystem@0.3.0-rc-2025-09-16: Add tests for symbolic links --- .../src/bin/filesystem-symbolic-links.json | 3 + .../src/bin/filesystem-symbolic-links.rs | 115 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 tests/rust/wasm32-wasip3/src/bin/filesystem-symbolic-links.json create mode 100644 tests/rust/wasm32-wasip3/src/bin/filesystem-symbolic-links.rs diff --git a/tests/rust/wasm32-wasip3/src/bin/filesystem-symbolic-links.json b/tests/rust/wasm32-wasip3/src/bin/filesystem-symbolic-links.json new file mode 100644 index 000000000..ff216a756 --- /dev/null +++ b/tests/rust/wasm32-wasip3/src/bin/filesystem-symbolic-links.json @@ -0,0 +1,3 @@ +{ + "dirs": ["fs-tests.dir"] +} diff --git a/tests/rust/wasm32-wasip3/src/bin/filesystem-symbolic-links.rs b/tests/rust/wasm32-wasip3/src/bin/filesystem-symbolic-links.rs new file mode 100644 index 000000000..1f04dc684 --- /dev/null +++ b/tests/rust/wasm32-wasip3/src/bin/filesystem-symbolic-links.rs @@ -0,0 +1,115 @@ +use std::process; +extern crate wit_bindgen; + +wit_bindgen::generate!({ + inline: r" + package test:test; + + world test { + include wasi:filesystem/imports@0.3.0-rc-2025-09-16; + include wasi:cli/command@0.3.0-rc-2025-09-16; + } +", + additional_derives: [PartialEq, Eq, Hash, Clone], + // Work around https://github.com/bytecodealliance/wasm-tools/issues/2285. + features:["clocks-timezone"], + generate_all +}); + +use wasi::filesystem::types::Descriptor; +use wasi::filesystem::types::{DescriptorFlags, ErrorCode, OpenFlags, PathFlags}; + +async fn test_symbolic_links(dir: &Descriptor) { + let ln_s = |from: &str, to: &str| -> _ { dir.symlink_at(from.to_string(), to.to_string()) }; + let readlink = |path: &str| dir.readlink_at(path.to_string()); + let stat_with_flags = |flags: PathFlags, path: &str| dir.stat_at(flags, path.to_string()); + let stat_follow = |path: &str| stat_with_flags(PathFlags::SYMLINK_FOLLOW, path); + let open_r_follow = |path: &str| -> _ { + dir.open_at( + PathFlags::SYMLINK_FOLLOW, + path.to_string(), + OpenFlags::empty(), + DescriptorFlags::READ, + ) + }; + let open_r = |path: &str| -> _ { + dir.open_at( + PathFlags::empty(), + path.to_string(), + OpenFlags::empty(), + DescriptorFlags::READ, + ) + }; + let rm = |path: &str| dir.unlink_file_at(path.to_string()); + + // readlink-at: async func(path: string) -> result; + assert_eq!(readlink("").await, Err(ErrorCode::NoEntry)); + assert_eq!(readlink(".").await, Err(ErrorCode::Invalid)); + assert_eq!(readlink("a.txt").await, Err(ErrorCode::Invalid)); + assert_eq!(readlink("z.txt").await, Err(ErrorCode::NoEntry)); + assert_eq!(readlink("./../").await, Err(ErrorCode::NotPermitted)); + assert_eq!(readlink("..").await, Err(ErrorCode::NotPermitted)); + assert_eq!(readlink("/").await, Err(ErrorCode::NotPermitted)); + assert_eq!(readlink("parent").await, Ok("..".to_string())); + + let afd = open_r("a.txt").await.unwrap(); + assert_eq!( + afd.readlink_at(".".to_string()).await, + Err(ErrorCode::NotDirectory) + ); + + // https://github.com/WebAssembly/wasi-filesystem/issues/186 + assert_eq!( + open_r("parent") + .await + .expect_err("open symlink with NOFOLLOW"), + ErrorCode::Loop + ); + + ln_s("parent", "parent-link").await.unwrap(); + assert_eq!(open_r("parent-link").await.unwrap_err(), ErrorCode::Loop); + assert_eq!( + open_r_follow("parent-link").await.unwrap_err(), + ErrorCode::NotPermitted + ); + assert_eq!( + stat_follow("parent-link").await.unwrap_err(), + ErrorCode::NotPermitted + ); + assert_eq!(open_r("parent-link").await.unwrap_err(), ErrorCode::Loop); + assert_eq!( + ln_s("a.txt", "parent-link/a.txt").await, + Err(ErrorCode::NotPermitted) + ); + rm("parent-link").await.unwrap(); + + ln_s("self", "self").await.unwrap(); + assert_eq!(open_r_follow("self").await.unwrap_err(), ErrorCode::Loop); + assert_eq!(stat_follow("self").await.unwrap_err(), ErrorCode::Loop); + rm("self").await.unwrap(); + + assert_eq!(ln_s("whatever", "").await, Err(ErrorCode::NoEntry)); + assert_eq!(ln_s("", "whatever").await, Err(ErrorCode::NoEntry)); + // symlink-at: async func(old-path: string, new-path: string) -> result<_, error-code>; +} + +struct Component; +export!(Component); +impl exports::wasi::cli::run::Guest for Component { + async fn run() -> Result<(), ()> { + match &wasi::filesystem::preopens::get_directories()[..] { + [(dir, dirname)] if dirname == "fs-tests.dir" => { + test_symbolic_links(dir).await; + } + [..] => { + eprintln!("usage: run with one open dir named 'fs-tests.dir'"); + process::exit(1) + } + }; + Ok(()) + } +} + +fn main() { + unreachable!("main is a stub"); +}