diff --git a/crates/test-programs/src/bin/p1_file_hardlink_across_perms.rs b/crates/test-programs/src/bin/p1_file_hardlink_across_perms.rs new file mode 100644 index 000000000000..ae94e40349c2 --- /dev/null +++ b/crates/test-programs/src/bin/p1_file_hardlink_across_perms.rs @@ -0,0 +1,95 @@ +#![expect(unsafe_op_in_unsafe_fn, reason = "old code, not worth updating yet")] + +use std::env; +use std::process; +use test_programs::preview1::open_scratch_directory; + +const RW_ALIAS_FILENAME: &str = "alias.txt"; +const RO_TEST_FILENAME: &str = "test.txt"; +const RO_EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; + +unsafe fn test_ro_file_has_expected_contents(dir_fd: wasip1::Fd) { + // Open a file for reading + let file_fd = wasip1::path_open(dir_fd, 0, RO_TEST_FILENAME, 0, wasip1::RIGHTS_FD_READ, 0, 0) + .expect("opening test.txt for reading"); + + // Read the file's contents + let buffer = &mut [0u8; 100]; + let nread = wasip1::fd_read( + file_fd, + &[wasip1::Iovec { + buf: buffer.as_mut_ptr(), + buf_len: buffer.len(), + }], + ) + .expect("reading file content"); + + // The file should be as created by the test harness + assert_eq!(nread, RO_EXPECTED_CONTENTS.len(), "expected untouched file"); + assert_eq!( + &buffer[..nread], + RO_EXPECTED_CONTENTS, + "expected untouched file contents" + ); + + wasip1::fd_close(file_fd).expect("closing the file"); +} + +unsafe fn test_file_hardlink_across_perms(rw_dir_fd: wasip1::Fd, ro_dir_fd: wasip1::Fd) { + // Check test preconditions. + test_ro_file_has_expected_contents(ro_dir_fd); + + // Creating a hard link of the read-only file into a Descriptor under + // which files are read-writable would allow the read-only file to be + // written to. So, this must fail with perm: + let err = wasip1::path_link(ro_dir_fd, 0, RO_TEST_FILENAME, rw_dir_fd, RW_ALIAS_FILENAME); + assert!( + err.is_err(), + "path_link should fail because link source readonly, dest is readwrite" + ); + assert_eq!( + err.err().unwrap(), + wasip1::ERRNO_PERM, + "path_link should fail with ERRNO_PERM" + ); + + // Check that contents of link dest did not change + test_ro_file_has_expected_contents(ro_dir_fd); +} + +fn main() { + let mut args = env::args(); + let prog = args.next().unwrap(); + let arg = if let Some(arg) = args.next() { + arg + } else { + eprintln!("usage: {prog} "); + process::exit(1); + }; + + // Open read-write scratch directory + let rw_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("failed to open scratch directory: {err}"); + process::exit(1) + } + }; + + // This test program requires a special preopen at the path "readonly", + // which the host enforces as read-only. Unlike other test programs, this + // directory's path not passed in as an argument, because modifications to + // the testing harness would be too invasive. + let ro_dir_fd = match open_scratch_directory("readonly") { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("failed to open readonly preopen: {err}"); + process::exit(1) + } + }; + + // Run the tests. + unsafe { + test_file_hardlink_across_perms(rw_dir_fd, ro_dir_fd); + } +} diff --git a/crates/test-programs/src/bin/p1_file_rename_across_perms.rs b/crates/test-programs/src/bin/p1_file_rename_across_perms.rs new file mode 100644 index 000000000000..d8b931c6b902 --- /dev/null +++ b/crates/test-programs/src/bin/p1_file_rename_across_perms.rs @@ -0,0 +1,104 @@ +#![expect(unsafe_op_in_unsafe_fn, reason = "old code, not worth updating yet")] + +use std::env; +use std::process; +use test_programs::preview1::open_scratch_directory; + +const RW_ALIAS_FILENAME: &str = "alias.txt"; +const RO_TEST_FILENAME: &str = "test.txt"; +const RO_EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; + +unsafe fn test_ro_file_has_expected_contents(dir_fd: wasip1::Fd) { + // Open a file for reading + let file_fd = wasip1::path_open(dir_fd, 0, RO_TEST_FILENAME, 0, wasip1::RIGHTS_FD_READ, 0, 0) + .expect("opening test.txt for reading"); + + // Read the file's contents + let buffer = &mut [0u8; 100]; + let nread = wasip1::fd_read( + file_fd, + &[wasip1::Iovec { + buf: buffer.as_mut_ptr(), + buf_len: buffer.len(), + }], + ) + .expect("reading file content"); + + // The file should be as created by the test harness + assert_eq!(nread, RO_EXPECTED_CONTENTS.len(), "expected untouched file"); + assert_eq!( + &buffer[..nread], + RO_EXPECTED_CONTENTS, + "expected untouched file contents" + ); + + wasip1::fd_close(file_fd).expect("closing the file"); +} + +unsafe fn test_file_rename_across_perms(rw_dir_fd: wasip1::Fd, ro_dir_fd: wasip1::Fd) { + // Check test preconditions. + test_ro_file_has_expected_contents(ro_dir_fd); + + // Create a hardlink inside the file ro dir so there are two files pointing to + // the read-only file. + match wasip1::path_link(ro_dir_fd, 0, RO_TEST_FILENAME, ro_dir_fd, RW_ALIAS_FILENAME) { + // The readonly dir isnt recreated fresh per test mode in the p2 + // runner, so just allow this to fail with exists because its very + // tedious to restructure everything to fix this properly + Ok(()) | Err(wasip1::ERRNO_EXIST) => {} + _ => panic!("should be possible to create link inside ro file domain"), + } + + // Renaming that file into the file rw dir should fail with permissions + // error, otherwise it would permit opening the ro file as rw + let err = wasip1::path_rename(ro_dir_fd, RW_ALIAS_FILENAME, rw_dir_fd, RW_ALIAS_FILENAME); + assert!( + err.is_err(), + "path_rename should fail because link source readonly, dest is readwrite" + ); + assert_eq!( + err.err().unwrap(), + wasip1::ERRNO_PERM, + "path_rename should fail with ERRNO_PERM" + ); + + // Check that contents of link dest did not change + test_ro_file_has_expected_contents(ro_dir_fd); +} + +fn main() { + let mut args = env::args(); + let prog = args.next().unwrap(); + let arg = if let Some(arg) = args.next() { + arg + } else { + eprintln!("usage: {prog} "); + process::exit(1); + }; + + // Open read-write scratch directory + let rw_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("failed to open scratch directory: {err}"); + process::exit(1) + } + }; + + // This test program requires a special preopen at the path "readonly", + // which the host enforces as read-only. Unlike other test programs, this + // directory's path not passed in as an argument, because modifications to + // the testing harness would be too invasive. + let ro_dir_fd = match open_scratch_directory("readonly") { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("failed to open readonly preopen: {err}"); + process::exit(1) + } + }; + + // Run the tests. + unsafe { + test_file_rename_across_perms(rw_dir_fd, ro_dir_fd); + } +} diff --git a/crates/test-programs/src/bin/p1_file_truncation_readonly.rs b/crates/test-programs/src/bin/p1_file_truncation_readonly.rs index 59407539b469..3124805a26dc 100644 --- a/crates/test-programs/src/bin/p1_file_truncation_readonly.rs +++ b/crates/test-programs/src/bin/p1_file_truncation_readonly.rs @@ -29,7 +29,7 @@ unsafe fn test_file_has_expected_contents(dir_fd: wasip1::Fd, blocking_mode: &Bl ) .expect("reading file content"); - const EXPECTED_CONTENTS: &[u8] = b"truncation test file\n"; + const EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; // The file should be as created by the test harness, not truncated. assert_eq!(nread, EXPECTED_CONTENTS.len(), "expected untouched file"); assert_eq!( diff --git a/crates/test-programs/src/bin/p2_file_hardlink_across_perms.rs b/crates/test-programs/src/bin/p2_file_hardlink_across_perms.rs new file mode 100644 index 000000000000..9087c3c926a8 --- /dev/null +++ b/crates/test-programs/src/bin/p2_file_hardlink_across_perms.rs @@ -0,0 +1,86 @@ +#![expect(unsafe_op_in_unsafe_fn, reason = "old code, not worth updating yet")] +use test_programs::wasi::filesystem::preopens; +use test_programs::wasi::filesystem::types::{ + Descriptor, DescriptorFlags, ErrorCode, OpenFlags, PathFlags, +}; + +const RW_ALIAS_FILENAME: &str = "alias.txt"; +const RO_TEST_FILENAME: &str = "test.txt"; +const RO_EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; + +unsafe fn test_ro_file_has_expected_contents(dir: &Descriptor) { + // Open a file for reading + let file = dir + .open_at( + PathFlags::empty(), + RO_TEST_FILENAME, + OpenFlags::empty(), + DescriptorFlags::READ, + ) + .expect("open test.txt for reading"); + + // Read the file's contents + let stream = file.read_via_stream(0).unwrap(); + let read = stream.blocking_read(100).expect("reading test.txt content"); + + drop(stream); + drop(file); + assert_eq!( + read, RO_EXPECTED_CONTENTS, + "expected untouched file contents" + ); +} + +unsafe fn test_file_hardlink_across_perms(rw_dir: &Descriptor, ro_dir: &Descriptor) { + // Check test preconditions. + test_ro_file_has_expected_contents(ro_dir); + + // Creating a hard link of the read-only file into a Descriptor under + // which files are read-writable would allow the read-only file to be + // written to. So, this must fail with perm: + let err = ro_dir.link_at( + PathFlags::empty(), + RO_TEST_FILENAME, + rw_dir, + RW_ALIAS_FILENAME, + ); + assert!( + err.is_err(), + "link_at should fail because link source is readonly, and dest is readwrite" + ); + assert_eq!( + err.err().unwrap(), + ErrorCode::NotPermitted, + "link_at should fail with NotPermitted" + ); + + // Check that contents of link dest did not change + test_ro_file_has_expected_contents(ro_dir); +} + +fn main() { + let args = wasip2::cli::environment::get_arguments(); + if args.len() != 2 { + panic!("usage: scratch directory argument required"); + } + let preopens = preopens::get_directories(); + let rw_path = &args[1]; + let (rw_dir, _) = preopens + .iter() + .find(|(_, path)| path == rw_path) + .expect("find preopen specified by argument"); + + // This test program requires a special preopen at the path "readonly", + // which the host enforces as read-only. Unlike other test programs, this + // directory's path not passed in as an argument, because modifications to + // the testing harness would be too invasive. + let (ro_dir, _) = preopens + .iter() + .find(|(_, path)| path == "readonly") + .expect("find preopen named readonly"); + + // Run the tests. + unsafe { + test_file_hardlink_across_perms(rw_dir, ro_dir); + } +} diff --git a/crates/test-programs/src/bin/p2_file_rename_across_perms.rs b/crates/test-programs/src/bin/p2_file_rename_across_perms.rs new file mode 100644 index 000000000000..1ff8d7873b38 --- /dev/null +++ b/crates/test-programs/src/bin/p2_file_rename_across_perms.rs @@ -0,0 +1,95 @@ +#![expect(unsafe_op_in_unsafe_fn, reason = "old code, not worth updating yet")] +use test_programs::wasi::filesystem::preopens; +use test_programs::wasi::filesystem::types::{ + Descriptor, DescriptorFlags, ErrorCode, OpenFlags, PathFlags, +}; + +const RW_ALIAS_FILENAME: &str = "alias.txt"; +const RO_TEST_FILENAME: &str = "test.txt"; +const RO_EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; + +unsafe fn test_ro_file_has_expected_contents(dir: &Descriptor) { + // Open a file for reading + let file = dir + .open_at( + PathFlags::empty(), + RO_TEST_FILENAME, + OpenFlags::empty(), + DescriptorFlags::READ, + ) + .expect("open test.txt for reading"); + + // Read the file's contents + let stream = file.read_via_stream(0).unwrap(); + let read = stream.blocking_read(100).expect("reading test.txt content"); + + drop(stream); + drop(file); + assert_eq!( + read, RO_EXPECTED_CONTENTS, + "expected untouched file contents" + ); +} + +unsafe fn test_file_hardlink_across_perms(rw_dir: &Descriptor, ro_dir: &Descriptor) { + // Check test preconditions. + test_ro_file_has_expected_contents(ro_dir); + + // Create a hardlink inside the file ro dir so there are two files pointing to + // the read-only file. + match ro_dir.link_at( + PathFlags::empty(), + RO_TEST_FILENAME, + ro_dir, + RW_ALIAS_FILENAME, + ) { + // The readonly dir isnt recreated fresh per test mode in the p2 + // runner, so just allow this to fail with exists because its very + // tedious to restructure everything to fix this properly + Ok(()) | Err(ErrorCode::Exist) => {} + _ => panic!("should be possible to create link inside ro file domain"), + } + + // Renaming that file into the file rw dir should fail with permissions + // error, otherwise it would permit opening the ro file as rw + let err = ro_dir.rename_at(RW_ALIAS_FILENAME, rw_dir, RW_ALIAS_FILENAME); + assert!( + err.is_err(), + "rename_at should fail because link source is file readonly, and dest is file readwrite" + ); + assert_eq!( + err.err().unwrap(), + ErrorCode::NotPermitted, + "rename_at should fail with NotPermitted" + ); + + // Check that contents of link dest did not change + test_ro_file_has_expected_contents(ro_dir); +} + +fn main() { + let args = wasip2::cli::environment::get_arguments(); + if args.len() != 2 { + panic!("usage: scratch directory argument required"); + } + let preopens = preopens::get_directories(); + let rw_path = &args[1]; + let (rw_dir, _) = preopens + .iter() + .find(|(_, path)| path == rw_path) + .expect("find preopen specified by argument"); + + // This test program requires a special preopen at the path "readonly", + // which the host enforces as read-only. Unlike other test programs, this + // directory's path not passed in as an argument, because modifications to + // the testing harness would be too invasive. + let (ro_dir, _) = preopens + .iter() + .find(|(_, path)| path == "readonly") + .expect("find preopen named readonly"); + + // Run the tests. + unsafe { + test_file_hardlink_across_perms(rw_dir, ro_dir); + } +} diff --git a/crates/test-programs/src/bin/p2_file_truncation_readonly.rs b/crates/test-programs/src/bin/p2_file_truncation_readonly.rs index d51a16edeb42..38865a8a997a 100644 --- a/crates/test-programs/src/bin/p2_file_truncation_readonly.rs +++ b/crates/test-programs/src/bin/p2_file_truncation_readonly.rs @@ -21,7 +21,7 @@ fn test_file_has_expected_contents(dir: &Descriptor) { drop(stream); drop(file); - const EXPECTED_CONTENTS: &[u8] = b"truncation test file\n"; + const EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; // The file should not be empty due to truncation assert_eq!(read, EXPECTED_CONTENTS, "expected untouched file contents"); } diff --git a/crates/test-programs/src/bin/p3_file_hardlink_across_perms.rs b/crates/test-programs/src/bin/p3_file_hardlink_across_perms.rs new file mode 100644 index 000000000000..197852d7ccd2 --- /dev/null +++ b/crates/test-programs/src/bin/p3_file_hardlink_across_perms.rs @@ -0,0 +1,97 @@ +use test_programs::p3::wasi::filesystem::types::{ + Descriptor, DescriptorFlags, ErrorCode, OpenFlags, PathFlags, +}; +use test_programs::p3::{self, wasi}; + +struct Component; + +p3::export!(Component); + +const RW_ALIAS_FILENAME: &str = "alias.txt"; +const RO_TEST_FILENAME: &str = "test.txt"; +const RO_EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; + +async fn test_ro_file_has_expected_contents(dir: &Descriptor) { + // Open a file for reading + let file = dir + .open_at( + PathFlags::empty(), + RO_TEST_FILENAME.to_string(), + OpenFlags::empty(), + DescriptorFlags::READ, + ) + .await + .expect("open test.txt for reading"); + + // Read the file's contents + let (read, result) = file.read_via_stream(0); + let read = read.collect().await; + result.await.expect("reading test.txt content"); + drop(file); + + assert_eq!( + read, RO_EXPECTED_CONTENTS, + "expected untouched file contents" + ); +} + +async fn test_file_hardlink_across_perms(rw_dir: &Descriptor, ro_dir: &Descriptor) { + // Check test preconditions. + test_ro_file_has_expected_contents(ro_dir).await; + + // Creating a hard link of the read-only file into a Descriptor under + // which files are read-writable would allow the read-only file to be + // written to. So, this must fail with perm: + let err = ro_dir + .link_at( + PathFlags::empty(), + RO_TEST_FILENAME.to_string(), + rw_dir, + RW_ALIAS_FILENAME.to_string(), + ) + .await; + assert!( + err.is_err(), + "link_at should fail because link source is readonly, dest is readwrite" + ); + assert!( + matches!(err.err().unwrap(), ErrorCode::NotPermitted,), + "link_at should fail with NotPermitted" + ); + + // Check that contents of link dest did not change + test_ro_file_has_expected_contents(ro_dir).await; +} + +impl p3::exports::wasi::cli::run::Guest for Component { + async fn run() -> Result<(), ()> { + let args = wasi::cli::environment::get_arguments(); + if args.len() != 2 { + panic!("usage: scratch directory argument required"); + } + let preopens = wasi::filesystem::preopens::get_directories(); + let rw_path = &args[1]; + let (rw_dir, _) = preopens + .iter() + .find(|(_, path)| path == rw_path) + .expect("find preopen specified by argument"); + + // This test program requires a special preopen at the path "readonly", + // which the host enforces as read-only. Unlike other test programs, this + // directory's path not passed in as an argument, because modifications to + // the testing harness would be too invasive. + let (ro_dir, _) = preopens + .iter() + .find(|(_, path)| path == "readonly") + .expect("find preopen named readonly"); + + // Run the tests. + test_file_hardlink_across_perms(rw_dir, ro_dir).await; + + Ok(()) + } +} + +fn main() { + unreachable!() +} diff --git a/crates/test-programs/src/bin/p3_file_rename_across_perms.rs b/crates/test-programs/src/bin/p3_file_rename_across_perms.rs new file mode 100644 index 000000000000..a3d4aab0963e --- /dev/null +++ b/crates/test-programs/src/bin/p3_file_rename_across_perms.rs @@ -0,0 +1,106 @@ +use test_programs::p3::wasi::filesystem::types::{ + Descriptor, DescriptorFlags, ErrorCode, OpenFlags, PathFlags, +}; +use test_programs::p3::{self, wasi}; + +struct Component; + +p3::export!(Component); + +const RW_ALIAS_FILENAME: &str = "alias.txt"; +const RO_TEST_FILENAME: &str = "test.txt"; +const RO_EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; + +async fn test_ro_file_has_expected_contents(dir: &Descriptor) { + // Open a file for reading + let file = dir + .open_at( + PathFlags::empty(), + RO_TEST_FILENAME.to_string(), + OpenFlags::empty(), + DescriptorFlags::READ, + ) + .await + .expect("open test.txt for reading"); + + // Read the file's contents + let (read, result) = file.read_via_stream(0); + let read = read.collect().await; + result.await.expect("reading test.txt content"); + drop(file); + + assert_eq!( + read, RO_EXPECTED_CONTENTS, + "expected untouched file contents" + ); +} + +async fn test_file_rename_across_perms(rw_dir: &Descriptor, ro_dir: &Descriptor) { + // Check test preconditions. + test_ro_file_has_expected_contents(ro_dir).await; + + // Create a hardlink inside the file ro dir so there are two files pointing to + // the read-only file. + ro_dir + .link_at( + PathFlags::empty(), + RO_TEST_FILENAME.to_owned(), + ro_dir, + RW_ALIAS_FILENAME.to_owned(), + ) + .await + .expect("should be possible to create link inside ro file domain"); + + // Renaming that file into the file rw dir should fail with permissions + // error, otherwise it would permit opening the ro file as rw + let err = ro_dir + .rename_at( + RW_ALIAS_FILENAME.to_owned(), + rw_dir, + RW_ALIAS_FILENAME.to_owned(), + ) + .await; + assert!( + err.is_err(), + "rename_at should fail because link source is file readonly, and dest is file readwrite" + ); + assert!( + matches!(err.err().unwrap(), ErrorCode::NotPermitted,), + "rename_at should fail with NotPermitted" + ); + // Check that contents of link dest did not change + test_ro_file_has_expected_contents(ro_dir).await; +} + +impl p3::exports::wasi::cli::run::Guest for Component { + async fn run() -> Result<(), ()> { + let args = wasi::cli::environment::get_arguments(); + if args.len() != 2 { + panic!("usage: scratch directory argument required"); + } + let preopens = wasi::filesystem::preopens::get_directories(); + let rw_path = &args[1]; + let (rw_dir, _) = preopens + .iter() + .find(|(_, path)| path == rw_path) + .expect("find preopen specified by argument"); + + // This test program requires a special preopen at the path "readonly", + // which the host enforces as read-only. Unlike other test programs, this + // directory's path not passed in as an argument, because modifications to + // the testing harness would be too invasive. + let (ro_dir, _) = preopens + .iter() + .find(|(_, path)| path == "readonly") + .expect("find preopen named readonly"); + + // Run the tests. + test_file_rename_across_perms(rw_dir, ro_dir).await; + + Ok(()) + } +} + +fn main() { + unreachable!() +} diff --git a/crates/test-programs/src/bin/p3_file_truncation_readonly.rs b/crates/test-programs/src/bin/p3_file_truncation_readonly.rs index d4b053a32324..096c1cdd146d 100644 --- a/crates/test-programs/src/bin/p3_file_truncation_readonly.rs +++ b/crates/test-programs/src/bin/p3_file_truncation_readonly.rs @@ -8,7 +8,7 @@ struct Component; p3::export!(Component); const FILENAME: &str = "test.txt"; -const EXPECTED_CONTENTS: &[u8] = b"truncation test file\n"; +const EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; impl p3::exports::wasi::cli::run::Guest for Component { async fn run() -> Result<(), ()> { diff --git a/crates/wasi/src/filesystem.rs b/crates/wasi/src/filesystem.rs index 282b4a275896..3f6b657cbcba 100644 --- a/crates/wasi/src/filesystem.rs +++ b/crates/wasi/src/filesystem.rs @@ -915,6 +915,9 @@ impl Dir { if old_path_flags.contains(PathFlags::SYMLINK_FOLLOW) { return Err(ErrorCode::Invalid); } + if self.perms != new_dir.perms || self.file_perms != new_dir.file_perms { + return Err(ErrorCode::NotPermitted); + } let new_dir_handle = Arc::clone(&new_dir.dir); self.run_blocking(move |d| d.hard_link(&old_path, &new_dir_handle, &new_path)) .await?; @@ -1094,6 +1097,9 @@ impl Dir { if !new_dir.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted); } + if self.perms != new_dir.perms || self.file_perms != new_dir.file_perms { + return Err(ErrorCode::NotPermitted); + } let new_dir_handle = Arc::clone(&new_dir.dir); self.run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path)) .await?; diff --git a/crates/wasi/tests/all/p1.rs b/crates/wasi/tests/all/p1.rs index a8118f07f289..cd7077afe8bf 100644 --- a/crates/wasi/tests/all/p1.rs +++ b/crates/wasi/tests/all/p1.rs @@ -276,21 +276,25 @@ async fn p1_sleep_quickly_but_lots() { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn p1_file_truncation_readonly() { + run_with_readonly_testfile(P1_FILE_TRUNCATION_READONLY).await +} + +async fn run_with_readonly_testfile(component_path: &str) { use std::path::PathBuf; use wasmtime_wasi::{DirPerms, FilePerms}; - let prefix = format!("wasi_components_truncation_readonly_ro_"); + let prefix = format!("wasi_components_ro_"); let tempdir = tempfile::Builder::new() .prefix(&prefix) .tempdir() .expect("create readonly tempdir"); const FILENAME: &str = "test.txt"; - const EXPECTED_CONTENTS: &[u8] = b"truncation test file\n"; + const EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; let mut file: PathBuf = PathBuf::from(tempdir.path()); file.push(FILENAME); std::fs::write(&file, EXPECTED_CONTENTS).expect("write truncation test file"); - run(P1_FILE_TRUNCATION_READONLY, |b| { + run(component_path, |b| { b.preopened_dir( tempdir.path(), "readonly", @@ -300,8 +304,17 @@ async fn p1_file_truncation_readonly() { .unwrap(); }) .await - .expect("run p1_file_truncation_readonly guest"); + .expect("run guest"); let contents = std::fs::read(&file).expect("read truncation test file"); assert_eq!(EXPECTED_CONTENTS, contents); } + +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn p1_file_hardlink_across_perms() { + run_with_readonly_testfile(P1_FILE_HARDLINK_ACROSS_PERMS).await +} +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn p1_file_rename_across_perms() { + run_with_readonly_testfile(P1_FILE_RENAME_ACROSS_PERMS).await +} diff --git a/crates/wasi/tests/all/p2/async_.rs b/crates/wasi/tests/all/p2/async_.rs index 9d1699f56c46..9b87382b7341 100644 --- a/crates/wasi/tests/all/p2/async_.rs +++ b/crates/wasi/tests/all/p2/async_.rs @@ -398,27 +398,27 @@ async fn p2_udp_send_too_much() { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn p1_file_truncation_readonly() { - file_truncation_readonly(P1_FILE_TRUNCATION_READONLY_COMPONENT).await + run_with_readonly_testfile(P1_FILE_TRUNCATION_READONLY_COMPONENT).await } #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn p2_file_truncation_readonly() { - file_truncation_readonly(P2_FILE_TRUNCATION_READONLY_COMPONENT).await + run_with_readonly_testfile(P2_FILE_TRUNCATION_READONLY_COMPONENT).await } -async fn file_truncation_readonly(component_path: &str) { +async fn run_with_readonly_testfile(component_path: &str) { use std::path::PathBuf; use wasmtime_wasi::{DirPerms, FilePerms}; - let prefix = "wasi_components_truncation_readonly_ro_"; + let prefix = "wasi_components_ro_"; let tempdir = tempfile::Builder::new() .prefix(prefix) .tempdir() .expect("create readonly tempdir"); const FILENAME: &str = "test.txt"; - const EXPECTED_CONTENTS: &[u8] = b"truncation test file\n"; + const EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; let mut file: PathBuf = PathBuf::from(tempdir.path()); file.push(FILENAME); - std::fs::write(&file, EXPECTED_CONTENTS).expect("write truncation test file"); + std::fs::write(&file, EXPECTED_CONTENTS).expect("write read only test file"); run(component_path, |b| { b.preopened_dir( @@ -430,12 +430,29 @@ async fn file_truncation_readonly(component_path: &str) { .unwrap(); }) .await - .expect("run p1_file_truncation_readonly guest"); + .expect("run guest"); - let contents = std::fs::read(&file).expect("read truncation test file"); + let contents = std::fs::read(&file).expect("read read only test file"); assert_eq!(EXPECTED_CONTENTS, contents); } +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn p1_file_hardlink_across_perms() { + run_with_readonly_testfile(P1_FILE_HARDLINK_ACROSS_PERMS_COMPONENT).await +} +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn p2_file_hardlink_across_perms() { + run_with_readonly_testfile(P2_FILE_HARDLINK_ACROSS_PERMS_COMPONENT).await +} +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn p1_file_rename_across_perms() { + run_with_readonly_testfile(P1_FILE_RENAME_ACROSS_PERMS_COMPONENT).await +} +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn p2_file_rename_across_perms() { + run_with_readonly_testfile(P2_FILE_RENAME_ACROSS_PERMS_COMPONENT).await +} + #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn p2_clocks_zero_wait() { run(P2_CLOCKS_ZERO_WAIT_COMPONENT, |_| {}).await.unwrap() diff --git a/crates/wasi/tests/all/p2/sync.rs b/crates/wasi/tests/all/p2/sync.rs index ada68d478e51..46cb1e4a439a 100644 --- a/crates/wasi/tests/all/p2/sync.rs +++ b/crates/wasi/tests/all/p2/sync.rs @@ -367,27 +367,27 @@ fn p2_udp_send_too_much() { #[test_log::test] fn p1_file_truncation_readonly() { - file_truncation_readonly(P1_FILE_TRUNCATION_READONLY_COMPONENT) + run_with_readonly_testfile(P1_FILE_TRUNCATION_READONLY_COMPONENT) } #[test_log::test] fn p2_file_truncation_readonly() { - file_truncation_readonly(P2_FILE_TRUNCATION_READONLY_COMPONENT) + run_with_readonly_testfile(P2_FILE_TRUNCATION_READONLY_COMPONENT) } -fn file_truncation_readonly(component_path: &str) { +fn run_with_readonly_testfile(component_path: &str) { use std::path::PathBuf; use wasmtime_wasi::{DirPerms, FilePerms}; - let prefix = "wasi_components_truncation_readonly_ro_"; + let prefix = "wasi_components_ro_"; let tempdir = tempfile::Builder::new() .prefix(prefix) .tempdir() .expect("create readonly tempdir"); const FILENAME: &str = "test.txt"; - const EXPECTED_CONTENTS: &[u8] = b"truncation test file\n"; + const EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; let mut file: PathBuf = PathBuf::from(tempdir.path()); file.push(FILENAME); - std::fs::write(&file, EXPECTED_CONTENTS).expect("write truncation test file"); + std::fs::write(&file, EXPECTED_CONTENTS).expect("write read only test file"); run(component_path, |b| { b.preopened_dir( @@ -398,12 +398,29 @@ fn file_truncation_readonly(component_path: &str) { ) .unwrap(); }) - .expect("run p1_file_truncation_readonly guest"); + .expect("run guest"); - let contents = std::fs::read(&file).expect("read truncation test file"); + let contents = std::fs::read(&file).expect("read read only test file"); assert_eq!(EXPECTED_CONTENTS, contents); } +#[test_log::test] +fn p1_file_hardlink_across_perms() { + run_with_readonly_testfile(P1_FILE_HARDLINK_ACROSS_PERMS_COMPONENT) +} +#[test_log::test] +fn p2_file_hardlink_across_perms() { + run_with_readonly_testfile(P2_FILE_HARDLINK_ACROSS_PERMS_COMPONENT) +} +#[test_log::test] +fn p1_file_rename_across_perms() { + run_with_readonly_testfile(P1_FILE_RENAME_ACROSS_PERMS_COMPONENT) +} +#[test_log::test] +fn p2_file_rename_across_perms() { + run_with_readonly_testfile(P2_FILE_RENAME_ACROSS_PERMS_COMPONENT) +} + #[test_log::test] fn p2_clocks_zero_wait() { run(P2_CLOCKS_ZERO_WAIT_COMPONENT, |_| {}).unwrap() diff --git a/crates/wasi/tests/all/p3/mod.rs b/crates/wasi/tests/all/p3/mod.rs index 11e77cf95ed3..18156e399b12 100644 --- a/crates/wasi/tests/all/p3/mod.rs +++ b/crates/wasi/tests/all/p3/mod.rs @@ -187,18 +187,21 @@ async fn p3_file_write_blocking() -> wasmtime::Result<()> { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn p3_file_truncation_readonly() -> wasmtime::Result<()> { - let prefix = "wasi_components_p3_truncation_readonly_ro_"; + run_with_readonly_testfile(P3_FILE_TRUNCATION_READONLY_COMPONENT).await +} +async fn run_with_readonly_testfile(component_path: &str) -> wasmtime::Result<()> { + let prefix = "wasi_components_p3_ro_"; let tempdir = tempfile::Builder::new() .prefix(prefix) .tempdir() .expect("create readonly tempdir"); const FILENAME: &str = "test.txt"; - const EXPECTED_CONTENTS: &[u8] = b"truncation test file\n"; + const EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; let mut file = PathBuf::from(tempdir.path()); file.push(FILENAME); - std::fs::write(&file, EXPECTED_CONTENTS).expect("write truncation test file"); + std::fs::write(&file, EXPECTED_CONTENTS).expect("write read only test file"); - run_with_builder(P3_FILE_TRUNCATION_READONLY_COMPONENT, false, |builder| { + run_with_builder(component_path, false, |builder| { builder .preopened_dir( tempdir.path(), @@ -210,7 +213,16 @@ async fn p3_file_truncation_readonly() -> wasmtime::Result<()> { }) .await?; - let contents = std::fs::read(&file).expect("read truncation test file"); + let contents = std::fs::read(&file).expect("read read only test file"); assert_eq!(EXPECTED_CONTENTS, contents); Ok(()) } + +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn p3_file_hardlink_across_perms() -> wasmtime::Result<()> { + run_with_readonly_testfile(P3_FILE_HARDLINK_ACROSS_PERMS_COMPONENT).await +} +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn p3_file_rename_across_perms() -> wasmtime::Result<()> { + run_with_readonly_testfile(P3_FILE_RENAME_ACROSS_PERMS_COMPONENT).await +}