diff --git a/tests/rust/src/bin/dangling_fd.rs b/tests/rust/src/bin/dangling_fd.rs index e7480ff2..ad0cafcf 100644 --- a/tests/rust/src/bin/dangling_fd.rs +++ b/tests/rust/src/bin/dangling_fd.rs @@ -17,6 +17,7 @@ unsafe fn test_dangling_fd(dir_fd: wasi::Fd) { wasi::path_unlink_file(dir_fd, FILE_NAME).expect("failed to unlink"); let fd = wasi::path_open(dir_fd, 0, FILE_NAME, wasi::OFLAGS_CREAT, 0, 0, 0).unwrap(); wasi::fd_close(fd).unwrap(); + wasi::path_unlink_file(dir_fd, FILE_NAME).expect("failed to unlink"); // Now, repeat the same process but for a directory wasi::path_create_directory(dir_fd, DIR_NAME).expect("failed to create dir"); diff --git a/tests/rust/src/bin/fd_readdir.rs b/tests/rust/src/bin/fd_readdir.rs index 8ff8dad4..47658552 100644 --- a/tests/rust/src/bin/fd_readdir.rs +++ b/tests/rust/src/bin/fd_readdir.rs @@ -1,6 +1,5 @@ use std::{env, mem, process, slice, str}; -use wasi::path_create_directory; -use wasi_tests::open_scratch_directory; +use wasi_tests::{create_tmp_dir, open_scratch_directory}; const BUF_LEN: usize = 256; @@ -53,28 +52,6 @@ impl<'a> Iterator for ReadDir<'a> { } } -unsafe fn create_tmp_dir(dir_fd: wasi::Fd, name: &str) -> wasi::Fd { - path_create_directory(dir_fd, name).expect("failed to create dir"); - wasi::path_open( - dir_fd, - 0, - name, - wasi::OFLAGS_DIRECTORY, - wasi::RIGHTS_FD_FILESTAT_GET - | wasi::RIGHTS_FD_READDIR - | wasi::RIGHTS_PATH_CREATE_FILE - | wasi::RIGHTS_PATH_OPEN - | wasi::RIGHTS_PATH_UNLINK_FILE, - wasi::RIGHTS_FD_READ - | wasi::RIGHTS_FD_WRITE - | wasi::RIGHTS_FD_READDIR - | wasi::RIGHTS_FD_FILESTAT_GET - | wasi::RIGHTS_FD_SEEK, - 0, - ) - .expect("failed to open dir") -} - /// Return the entries plus a bool indicating EOF. unsafe fn exec_fd_readdir(fd: wasi::Fd, cookie: wasi::Dircookie) -> (Vec, bool) { let mut buf: [u8; BUF_LEN] = [0; BUF_LEN]; diff --git a/tests/rust/src/bin/file_allocate.rs b/tests/rust/src/bin/file_allocate.rs new file mode 100644 index 00000000..b527e89b --- /dev/null +++ b/tests/rust/src/bin/file_allocate.rs @@ -0,0 +1,77 @@ +use std::{env, process}; +use wasi_tests::{create_tmp_dir, open_scratch_directory, TESTCONFIG}; + +unsafe fn test_file_allocate(dir_fd: wasi::Fd) { + // Create a file in the scratch directory. + let file_fd = wasi::path_open( + dir_fd, + 0, + "file", + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ + | wasi::RIGHTS_FD_WRITE + | wasi::RIGHTS_FD_ALLOCATE + | wasi::RIGHTS_FD_FILESTAT_GET, + 0, + 0, + ) + .expect("opening a file"); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + // Check file size + let mut stat = wasi::fd_filestat_get(file_fd).expect("reading file stats"); + assert_eq!(stat.size, 0, "file size should be 0"); + + if TESTCONFIG.support_fd_allocate() { + // Allocate some size + wasi::fd_allocate(file_fd, 0, 100).expect("allocating size"); + stat = wasi::fd_filestat_get(file_fd).expect("reading file stats"); + assert_eq!(stat.size, 100, "file size should be 100"); + + // Allocate should not modify if less than current size + wasi::fd_allocate(file_fd, 10, 10).expect("allocating size less than current size"); + stat = wasi::fd_filestat_get(file_fd).expect("reading file stats"); + assert_eq!(stat.size, 100, "file size should remain unchanged at 100"); + + // Allocate should modify if offset+len > current_len + wasi::fd_allocate(file_fd, 90, 20).expect("allocating size larger than current size"); + stat = wasi::fd_filestat_get(file_fd).expect("reading file stats"); + assert_eq!(stat.size, 110, "file size should increase from 100 to 110"); + } + wasi::fd_close(file_fd).expect("closing a file"); + wasi::path_unlink_file(dir_fd, "file").expect("removing a file"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "file_allocate_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_file_allocate(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/file_pread_pwrite.rs b/tests/rust/src/bin/file_pread_pwrite.rs new file mode 100644 index 00000000..30a35e48 --- /dev/null +++ b/tests/rust/src/bin/file_pread_pwrite.rs @@ -0,0 +1,160 @@ +use std::convert::TryInto; +use std::{env, process}; +use wasi_tests::{create_tmp_dir, open_scratch_directory}; + +unsafe fn test_file_pread_pwrite(dir_fd: wasi::Fd) { + // Create a file in the scratch directory. + let file_fd = wasi::path_open( + dir_fd, + 0, + "file", + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_SEEK | wasi::RIGHTS_FD_WRITE, + 0, + 0, + ) + .expect("opening a file"); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + let contents = &[0u8, 1, 2, 3]; + let ciovec = wasi::Ciovec { + buf: contents.as_ptr() as *const _, + buf_len: contents.len(), + }; + let mut nwritten = + wasi::fd_pwrite(file_fd, &mut [ciovec], 0).expect("writing bytes at offset 0"); + assert_eq!(nwritten, 4, "nwritten bytes check"); + + let contents = &mut [0u8; 4]; + let iovec = wasi::Iovec { + buf: contents.as_mut_ptr() as *mut _, + buf_len: contents.len(), + }; + let mut nread = wasi::fd_pread(file_fd, &[iovec], 0).expect("reading bytes at offset 0"); + assert_eq!(nread, 4, "nread bytes check"); + assert_eq!(contents, &[0u8, 1, 2, 3], "written bytes equal read bytes"); + + // Write all the data through multiple iovecs. + // + // Note that this needs to be done with a loop, because some + // platforms do not support writing multiple iovecs at once. + // See https://github.com/rust-lang/rust/issues/74825. + let contents = &[0u8, 1, 2, 3]; + let mut offset = 0usize; + loop { + let mut ciovecs: Vec = Vec::new(); + let mut remaining = contents.len() - offset; + if remaining > 2 { + ciovecs.push(wasi::Ciovec { + buf: contents[offset..].as_ptr() as *const _, + buf_len: 2, + }); + remaining -= 2; + } + ciovecs.push(wasi::Ciovec { + buf: contents[contents.len() - remaining..].as_ptr() as *const _, + buf_len: remaining, + }); + + nwritten = wasi::fd_pwrite(file_fd, ciovecs.as_slice(), offset.try_into().unwrap()) + .expect("writing bytes at offset 0"); + + offset += nwritten; + if offset == contents.len() { + break; + } + } + assert_eq!(offset, 4, "nread bytes check"); + + // Read all the data through multiple iovecs. + // + // Note that this needs to be done with a loop, because some + // platforms do not support reading multiple iovecs at once. + // See https://github.com/rust-lang/rust/issues/74825. + let contents = &mut [0u8; 4]; + let mut offset = 0usize; + loop { + let buffer = &mut [0u8; 4]; + let iovecs = &[ + wasi::Iovec { + buf: buffer.as_mut_ptr() as *mut _, + buf_len: 2, + }, + wasi::Iovec { + buf: buffer[2..].as_mut_ptr() as *mut _, + buf_len: 2, + }, + ]; + nread = wasi::fd_pread(file_fd, iovecs, offset as _).expect("reading bytes at offset 0"); + if nread == 0 { + break; + } + contents[offset..offset + nread].copy_from_slice(&buffer[0..nread]); + offset += nread; + } + assert_eq!(offset, 4, "nread bytes check"); + assert_eq!(contents, &[0u8, 1, 2, 3], "file cursor was overwritten"); + + let contents = &mut [0u8; 4]; + let iovec = wasi::Iovec { + buf: contents.as_mut_ptr() as *mut _, + buf_len: contents.len(), + }; + nread = wasi::fd_pread(file_fd, &[iovec], 2).expect("reading bytes at offset 2"); + assert_eq!(nread, 2, "nread bytes check"); + assert_eq!(contents, &[2u8, 3, 0, 0], "file cursor was overwritten"); + + let contents = &[1u8, 0]; + let ciovec = wasi::Ciovec { + buf: contents.as_ptr() as *const _, + buf_len: contents.len(), + }; + nwritten = wasi::fd_pwrite(file_fd, &mut [ciovec], 2).expect("writing bytes at offset 2"); + assert_eq!(nwritten, 2, "nwritten bytes check"); + + let contents = &mut [0u8; 4]; + let iovec = wasi::Iovec { + buf: contents.as_mut_ptr() as *mut _, + buf_len: contents.len(), + }; + nread = wasi::fd_pread(file_fd, &[iovec], 0).expect("reading bytes at offset 0"); + assert_eq!(nread, 4, "nread bytes check"); + assert_eq!(contents, &[0u8, 1, 1, 0], "file cursor was overwritten"); + + wasi::fd_close(file_fd).expect("closing a file"); + wasi::path_unlink_file(dir_fd, "file").expect("removing a file"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "file_pread_pwrite_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_file_pread_pwrite(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/file_seek_tell.rs b/tests/rust/src/bin/file_seek_tell.rs new file mode 100644 index 00000000..3675cfa3 --- /dev/null +++ b/tests/rust/src/bin/file_seek_tell.rs @@ -0,0 +1,111 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_tmp_dir, open_scratch_directory}; + +unsafe fn test_file_seek_tell(dir_fd: wasi::Fd) { + // Create a file in the scratch directory. + let file_fd = wasi::path_open( + dir_fd, + 0, + "file", + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE | wasi::RIGHTS_FD_SEEK | wasi::RIGHTS_FD_TELL, + 0, + 0, + ) + .expect("opening a file"); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + // Check current offset + let mut offset = wasi::fd_tell(file_fd).expect("getting initial file offset"); + assert_eq!(offset, 0, "current offset should be 0"); + + // Write to file + let data = &[0u8; 100]; + let iov = wasi::Ciovec { + buf: data.as_ptr() as *const _, + buf_len: data.len(), + }; + let nwritten = wasi::fd_write(file_fd, &[iov]).expect("writing to a file"); + assert_eq!(nwritten, 100, "should write 100 bytes to file"); + + // Check current offset + offset = wasi::fd_tell(file_fd).expect("getting file offset after writing"); + assert_eq!(offset, 100, "offset after writing should be 100"); + + // Seek to middle of the file + let mut newoffset = + wasi::fd_seek(file_fd, -50, wasi::WHENCE_CUR).expect("seeking to the middle of a file"); + assert_eq!( + newoffset, 50, + "offset after seeking to the middle should be at 50" + ); + + // Seek to the beginning of the file + newoffset = + wasi::fd_seek(file_fd, 0, wasi::WHENCE_SET).expect("seeking to the beginning of the file"); + assert_eq!( + newoffset, 0, + "offset after seeking to the beginning of the file should be at 0" + ); + + // Seek beyond the file should be possible + wasi::fd_seek(file_fd, 1000, wasi::WHENCE_CUR).expect("seeking beyond the end of the file"); + + // Seek before byte 0 is an error though + assert_errno!( + wasi::fd_seek(file_fd, -2000, wasi::WHENCE_CUR) + .expect_err("seeking before byte 0 should be an error"), + wasi::ERRNO_INVAL + ); + + // Check that fd_read properly updates the file offset + wasi::fd_seek(file_fd, 0, wasi::WHENCE_SET) + .expect("seeking to the beginning of the file again"); + + let buffer = &mut [0u8; 100]; + let iovec = wasi::Iovec { + buf: buffer.as_mut_ptr(), + buf_len: buffer.len(), + }; + let nread = wasi::fd_read(file_fd, &[iovec]).expect("reading file"); + assert_eq!(nread, buffer.len(), "should read {} bytes", buffer.len()); + + offset = wasi::fd_tell(file_fd).expect("getting file offset after reading"); + assert_eq!(offset, 100, "offset after reading should be 100"); + + wasi::fd_close(file_fd).expect("closing a file"); + wasi::path_unlink_file(dir_fd, "file").expect("deleting a file"); +} +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "file_seek_tell_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_file_seek_tell(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/file_truncation.rs b/tests/rust/src/bin/file_truncation.rs new file mode 100644 index 00000000..5be776cd --- /dev/null +++ b/tests/rust/src/bin/file_truncation.rs @@ -0,0 +1,93 @@ +use std::{env, process}; +use wasi_tests::{create_tmp_dir, open_scratch_directory}; + +unsafe fn test_file_truncation(dir_fd: wasi::Fd) { + const FILENAME: &str = "test.txt"; + + // Open a file for writing + let file_fd = wasi::path_open( + dir_fd, + 0, + FILENAME, + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_WRITE, + 0, + 0, + ) + .expect("creating a file for writing"); + + // Write to the file + let content = b"this content will be truncated!"; + let nwritten = wasi::fd_write( + file_fd, + &[wasi::Ciovec { + buf: content.as_ptr() as *const _, + buf_len: content.len(), + }], + ) + .expect("writing file content"); + assert_eq!(nwritten, content.len(), "nwritten bytes check"); + + wasi::fd_close(file_fd).expect("closing the file"); + + // Open the file for truncation + let file_fd = wasi::path_open( + dir_fd, + 0, + FILENAME, + wasi::OFLAGS_CREAT | wasi::OFLAGS_TRUNC, + wasi::RIGHTS_FD_WRITE | wasi::RIGHTS_FD_READ, + 0, + 0, + ) + .expect("creating a truncated file for reading"); + + // Read the file's contents + let buffer = &mut [0u8; 100]; + let nread = wasi::fd_read( + file_fd, + &[wasi::Iovec { + buf: buffer.as_mut_ptr(), + buf_len: buffer.len(), + }], + ) + .expect("reading file content"); + + // The file should be empty due to truncation + assert_eq!(nread, 0, "expected an empty file after truncation"); + + wasi::fd_close(file_fd).expect("closing the file"); + + wasi::path_unlink_file(dir_fd, FILENAME).expect("removing FILENAME"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "file_truncation_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_file_truncation(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/file_unbuffered_write.rs b/tests/rust/src/bin/file_unbuffered_write.rs new file mode 100644 index 00000000..5688da2a --- /dev/null +++ b/tests/rust/src/bin/file_unbuffered_write.rs @@ -0,0 +1,82 @@ +use std::{env, process}; +use wasi_tests::{create_tmp_dir, open_scratch_directory}; + +unsafe fn test_file_unbuffered_write(dir_fd: wasi::Fd) { + // Create and open file for reading + let fd_read = wasi::path_open( + dir_fd, + 0, + "file", + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ, + 0, + 0, + ) + .expect("create and open file for reading"); + assert!( + fd_read > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + // Open the same file but for writing + let fd_write = wasi::path_open(dir_fd, 0, "file", 0, wasi::RIGHTS_FD_WRITE, 0, 0) + .expect("opening file for writing"); + assert!( + fd_write > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + // Write to file + let contents = &[1u8]; + let ciovec = wasi::Ciovec { + buf: contents.as_ptr() as *const _, + buf_len: contents.len(), + }; + let nwritten = wasi::fd_write(fd_write, &[ciovec]).expect("writing byte to file"); + assert_eq!(nwritten, 1, "nwritten bytes check"); + + // Read from file + let contents = &mut [0u8; 1]; + let iovec = wasi::Iovec { + buf: contents.as_mut_ptr() as *mut _, + buf_len: contents.len(), + }; + let nread = wasi::fd_read(fd_read, &[iovec]).expect("reading bytes from file"); + assert_eq!(nread, 1, "nread bytes check"); + assert_eq!(contents, &[1u8], "written bytes equal read bytes"); + + // Clean up + wasi::fd_close(fd_write).expect("closing a file"); + wasi::fd_close(fd_read).expect("closing a file"); + wasi::path_unlink_file(dir_fd, "file").expect("removing a file"); +} +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "file_unbuffered_write_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_file_unbuffered_write(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/isatty.rs b/tests/rust/src/bin/isatty.rs new file mode 100644 index 00000000..96f84411 --- /dev/null +++ b/tests/rust/src/bin/isatty.rs @@ -0,0 +1,50 @@ +use std::{env, process}; +use wasi_tests::{create_tmp_dir, open_scratch_directory}; + +unsafe fn test_isatty(dir_fd: wasi::Fd) { + // Create a file in the scratch directory and test if it's a tty. + let file_fd = + wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).expect("opening a file"); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + assert_eq!( + libc::isatty(file_fd as std::os::raw::c_int), + 0, + "file is a tty" + ); + wasi::fd_close(file_fd).expect("closing a file"); + wasi::path_unlink_file(dir_fd, "file").expect("removing a file"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "isatty_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_isatty(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/nofollow_errors.rs b/tests/rust/src/bin/nofollow_errors.rs new file mode 100644 index 00000000..fc6083d6 --- /dev/null +++ b/tests/rust/src/bin/nofollow_errors.rs @@ -0,0 +1,118 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_tmp_dir, open_scratch_directory}; + +unsafe fn test_nofollow_errors(dir_fd: wasi::Fd) { + // Create a directory for the symlink to point to. + wasi::path_create_directory(dir_fd, "target").expect("creating a dir"); + + // Create a symlink. + wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink"); + + // Try to open it as a directory with O_NOFOLLOW again. + assert_errno!( + wasi::path_open(dir_fd, 0, "symlink", wasi::OFLAGS_DIRECTORY, 0, 0, 0) + .expect_err("opening a directory symlink as a directory should fail"), + wasi::ERRNO_LOOP, + wasi::ERRNO_NOTDIR + ); + + // Try to open it with just O_NOFOLLOW. + assert_errno!( + wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0) + .expect_err("opening a symlink with O_NOFOLLOW should fail"), + wasi::ERRNO_LOOP, + wasi::ERRNO_ACCES + ); + + // Try to open it as a directory without O_NOFOLLOW. + let file_fd = wasi::path_open( + dir_fd, + wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, + "symlink", + wasi::OFLAGS_DIRECTORY, + 0, + 0, + 0, + ) + .expect("opening a symlink as a directory"); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + wasi::fd_close(file_fd).expect("closing a file"); + + // Replace the target directory with a file. + wasi::path_unlink_file(dir_fd, "symlink").expect("removing a file"); + wasi::path_remove_directory(dir_fd, "target") + .expect("remove_directory on a directory should succeed"); + + let file_fd = + wasi::path_open(dir_fd, 0, "target", wasi::OFLAGS_CREAT, 0, 0, 0).expect("creating a file"); + wasi::fd_close(file_fd).expect("closing a file"); + wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink"); + + // Try to open it as a directory with O_NOFOLLOW again. + assert_errno!( + wasi::path_open(dir_fd, 0, "symlink", wasi::OFLAGS_DIRECTORY, 0, 0, 0) + .expect_err("opening a directory symlink as a directory should fail"), + wasi::ERRNO_LOOP, + wasi::ERRNO_NOTDIR + ); + + // Try to open it with just O_NOFOLLOW. + assert_errno!( + wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0) + .expect_err("opening a symlink with NOFOLLOW should fail"), + wasi::ERRNO_LOOP + ); + + // Try to open it as a directory without O_NOFOLLOW. + assert_errno!( + wasi::path_open( + dir_fd, + wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, + "symlink", + wasi::OFLAGS_DIRECTORY, + 0, + 0, + 0, + ) + .expect_err("opening a symlink to a file as a directory"), + wasi::ERRNO_NOTDIR + ); + + // Clean up. + wasi::path_unlink_file(dir_fd, "target").expect("removing a file"); + wasi::path_unlink_file(dir_fd, "symlink").expect("removing a file"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "nofollow_errors_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_nofollow_errors(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/overwrite_preopen.rs b/tests/rust/src/bin/overwrite_preopen.rs new file mode 100644 index 00000000..169ae6d8 --- /dev/null +++ b/tests/rust/src/bin/overwrite_preopen.rs @@ -0,0 +1,57 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_tmp_dir, open_scratch_directory}; + +unsafe fn test_overwrite_preopen(dir_fd: wasi::Fd) { + let pre_fd: wasi::Fd = (libc::STDERR_FILENO + 1) as wasi::Fd; + + assert!(dir_fd > pre_fd, "dir_fd number"); + + let old_dir_filestat = wasi::fd_filestat_get(dir_fd).expect("failed fd_filestat_get"); + + // Try to renumber over a preopened directory handle. + wasi::fd_renumber(dir_fd, pre_fd).expect("renumbering over a preopened file descriptor"); + + // Ensure that pre_fd is still open. + let new_dir_filestat = wasi::fd_filestat_get(pre_fd).expect("failed fd_filestat_get"); + + // Ensure that we renumbered. + assert_eq!(old_dir_filestat.dev, new_dir_filestat.dev); + assert_eq!(old_dir_filestat.ino, new_dir_filestat.ino); + + // Ensure that dir_fd is closed. + assert_errno!( + wasi::fd_fdstat_get(dir_fd).expect_err("failed fd_fdstat_get"), + wasi::ERRNO_BADF + ); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "overwrite_preopen_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_overwrite_preopen(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/path_exists.rs b/tests/rust/src/bin/path_exists.rs new file mode 100644 index 00000000..1b77eeaa --- /dev/null +++ b/tests/rust/src/bin/path_exists.rs @@ -0,0 +1,86 @@ +use std::{env, process}; +use wasi_tests::{create_file, create_tmp_dir, open_scratch_directory}; + +unsafe fn test_path_exists(dir_fd: wasi::Fd) { + // Create a temporary directory + wasi::path_create_directory(dir_fd, "subdir").expect("create directory"); + + // Check directory exists: + let file_stat = wasi::path_filestat_get(dir_fd, 0, "subdir").expect("reading file stats"); + assert_eq!(file_stat.filetype, wasi::FILETYPE_DIRECTORY); + + // Should still exist with symlink follow flag: + let file_stat = wasi::path_filestat_get(dir_fd, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, "subdir") + .expect("reading file stats"); + assert_eq!(file_stat.filetype, wasi::FILETYPE_DIRECTORY); + + // Create a file: + create_file(dir_fd, "subdir/file"); + // Check directory exists: + let file_stat = wasi::path_filestat_get(dir_fd, 0, "subdir/file").expect("reading file stats"); + assert_eq!(file_stat.filetype, wasi::FILETYPE_REGULAR_FILE); + + // Should still exist with symlink follow flag: + let file_stat = + wasi::path_filestat_get(dir_fd, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, "subdir/file") + .expect("reading file stats"); + assert_eq!(file_stat.filetype, wasi::FILETYPE_REGULAR_FILE); + + // Create a symlink to a file: + wasi::path_symlink("subdir/file", dir_fd, "link1").expect("create symlink"); + // Check symlink exists: + let file_stat = wasi::path_filestat_get(dir_fd, 0, "link1").expect("reading file stats"); + assert_eq!(file_stat.filetype, wasi::FILETYPE_SYMBOLIC_LINK); + + // Should still exist with symlink follow flag, pointing to regular file + let file_stat = wasi::path_filestat_get(dir_fd, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, "link1") + .expect("reading file stats"); + assert_eq!(file_stat.filetype, wasi::FILETYPE_REGULAR_FILE); + + // Create a symlink to a dir: + wasi::path_symlink("subdir", dir_fd, "link2").expect("create symlink"); + // Check symlink exists: + let file_stat = wasi::path_filestat_get(dir_fd, 0, "link2").expect("reading file stats"); + assert_eq!(file_stat.filetype, wasi::FILETYPE_SYMBOLIC_LINK); + + // Should still exist with symlink follow flag, pointing to directory + let file_stat = wasi::path_filestat_get(dir_fd, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, "link2") + .expect("reading file stats"); + assert_eq!(file_stat.filetype, wasi::FILETYPE_DIRECTORY); + + wasi::path_unlink_file(dir_fd, "link1").expect("clean up"); + wasi::path_unlink_file(dir_fd, "link2").expect("clean up"); + wasi::path_unlink_file(dir_fd, "subdir/file").expect("clean up"); + wasi::path_remove_directory(dir_fd, "subdir").expect("clean up"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "path_exists_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_path_exists(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/path_filestat.rs b/tests/rust/src/bin/path_filestat.rs new file mode 100644 index 00000000..82a14054 --- /dev/null +++ b/tests/rust/src/bin/path_filestat.rs @@ -0,0 +1,155 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_tmp_dir, open_scratch_directory, TESTCONFIG}; + +unsafe fn test_path_filestat(dir_fd: wasi::Fd) { + let mut fdstat = wasi::fd_fdstat_get(dir_fd).expect("fd_fdstat_get"); + assert_ne!( + fdstat.fs_rights_base & wasi::RIGHTS_PATH_FILESTAT_GET, + 0, + "the scratch directory should have RIGHT_PATH_FILESTAT_GET as base right", + ); + + let fdflags = if TESTCONFIG.support_fdflags_sync() { + wasi::FDFLAGS_APPEND | wasi::FDFLAGS_SYNC + } else { + wasi::FDFLAGS_APPEND + }; + + // Create a file in the scratch directory. + let file_fd = wasi::path_open( + dir_fd, + 0, + "file", + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE | wasi::RIGHTS_PATH_FILESTAT_GET, + 0, + // Pass some flags for later retrieval + fdflags, + ) + .expect("opening a file"); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + fdstat = wasi::fd_fdstat_get(file_fd).expect("fd_fdstat_get"); + assert_eq!( + fdstat.fs_rights_base & wasi::RIGHTS_PATH_FILESTAT_GET, + 0, + "files shouldn't have rights for path_* syscalls even if manually given", + ); + assert_eq!( + fdstat.fs_rights_inheriting & wasi::RIGHTS_PATH_FILESTAT_GET, + 0, + "files shouldn't have rights for path_* syscalls even if manually given", + ); + assert_eq!( + fdstat.fs_flags & wasi::FDFLAGS_APPEND, + wasi::FDFLAGS_APPEND, + "file should have the APPEND fdflag used to create the file" + ); + if TESTCONFIG.support_fdflags_sync() { + assert_eq!( + fdstat.fs_flags & wasi::FDFLAGS_SYNC, + wasi::FDFLAGS_SYNC, + "file should have the SYNC fdflag used to create the file" + ); + } + + if !TESTCONFIG.support_fdflags_sync() { + assert_errno!( + wasi::path_open( + dir_fd, + 0, + "file", + 0, + wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE | wasi::RIGHTS_PATH_FILESTAT_GET, + 0, + wasi::FDFLAGS_SYNC, + ) + .expect_err("FDFLAGS_SYNC not supported by platform"), + wasi::ERRNO_NOTSUP + ); + } + + // Check file size + let file_stat = wasi::path_filestat_get(dir_fd, 0, "file").expect("reading file stats"); + assert_eq!(file_stat.size, 0, "file size should be 0"); + + // Check path_filestat_set_times + let new_mtim = file_stat.mtim - 100; + wasi::path_filestat_set_times(dir_fd, 0, "file", 0, new_mtim, wasi::FSTFLAGS_MTIM) + .expect("path_filestat_set_times should succeed"); + + let modified_file_stat = wasi::path_filestat_get(dir_fd, 0, "file") + .expect("reading file stats after path_filestat_set_times"); + assert_eq!(modified_file_stat.mtim, new_mtim, "mtim should change"); + + assert_errno!( + wasi::path_filestat_set_times( + dir_fd, + 0, + "file", + 0, + new_mtim, + wasi::FSTFLAGS_MTIM | wasi::FSTFLAGS_MTIM_NOW, + ) + .expect_err("MTIM and MTIM_NOW can't both be set"), + wasi::ERRNO_INVAL + ); + + // check if the times were untouched + let unmodified_file_stat = wasi::path_filestat_get(dir_fd, 0, "file") + .expect("reading file stats after ERRNO_INVAL fd_filestat_set_times"); + assert_eq!( + unmodified_file_stat.mtim, new_mtim, + "mtim should not change" + ); + + // Invalid arguments to set_times: + assert_errno!( + wasi::path_filestat_set_times( + dir_fd, + 0, + "file", + 0, + 0, + wasi::FSTFLAGS_ATIM | wasi::FSTFLAGS_ATIM_NOW, + ) + .expect_err("ATIM & ATIM_NOW can't both be set"), + wasi::ERRNO_INVAL + ); + + wasi::fd_close(file_fd).expect("closing a file"); + wasi::path_unlink_file(dir_fd, "file").expect("removing a file"); +} +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "path_filestat_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_path_filestat(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/path_link.rs b/tests/rust/src/bin/path_link.rs new file mode 100644 index 00000000..94c5e974 --- /dev/null +++ b/tests/rust/src/bin/path_link.rs @@ -0,0 +1,226 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_file, create_tmp_dir, open_scratch_directory, TESTCONFIG}; + +const TEST_RIGHTS: wasi::Rights = wasi::RIGHTS_FD_READ + | wasi::RIGHTS_PATH_LINK_SOURCE + | wasi::RIGHTS_PATH_LINK_TARGET + | wasi::RIGHTS_FD_FILESTAT_GET + | wasi::RIGHTS_PATH_OPEN + | wasi::RIGHTS_PATH_UNLINK_FILE; + +unsafe fn create_or_open(dir_fd: wasi::Fd, name: &str, flags: wasi::Oflags) -> wasi::Fd { + let file_fd = wasi::path_open(dir_fd, 0, name, flags, TEST_RIGHTS, TEST_RIGHTS, 0) + .unwrap_or_else(|_| panic!("opening '{}'", name)); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + file_fd +} + +unsafe fn open_link(dir_fd: wasi::Fd, name: &str) -> wasi::Fd { + let file_fd = wasi::path_open(dir_fd, 0, name, 0, TEST_RIGHTS, TEST_RIGHTS, 0) + .unwrap_or_else(|_| panic!("opening a link '{}'", name)); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + file_fd +} + +// This is temporary until `wasi` implements `Debug` and `PartialEq` for +// `wasi::Filestat`. +fn filestats_assert_eq(left: wasi::Filestat, right: wasi::Filestat) { + assert_eq!(left.dev, right.dev, "dev should be equal"); + assert_eq!(left.ino, right.ino, "ino should be equal"); + assert_eq!(left.atim, right.atim, "atim should be equal"); + assert_eq!(left.ctim, right.ctim, "ctim should be equal"); + assert_eq!(left.mtim, right.mtim, "mtim should be equal"); + assert_eq!(left.size, right.size, "size should be equal"); + assert_eq!(left.nlink, right.nlink, "nlink should be equal"); + assert_eq!(left.filetype, right.filetype, "filetype should be equal"); +} + +// This is temporary until `wasi` implements `Debug` and `PartialEq` for +// `wasi::Fdstat`. +fn fdstats_assert_eq(left: wasi::Fdstat, right: wasi::Fdstat) { + assert_eq!(left.fs_flags, right.fs_flags, "fs_flags should be equal"); + assert_eq!( + left.fs_filetype, right.fs_filetype, + "fs_filetype should be equal" + ); + assert_eq!( + left.fs_rights_base, right.fs_rights_base, + "fs_rights_base should be equal" + ); + assert_eq!( + left.fs_rights_inheriting, right.fs_rights_inheriting, + "fs_rights_inheriting should be equal" + ); +} + +unsafe fn check_rights(orig_fd: wasi::Fd, link_fd: wasi::Fd) { + // Compare Filestats + let orig_filestat = wasi::fd_filestat_get(orig_fd).expect("reading filestat of the source"); + let link_filestat = wasi::fd_filestat_get(link_fd).expect("reading filestat of the link"); + filestats_assert_eq(orig_filestat, link_filestat); + + // Compare Fdstats + let orig_fdstat = wasi::fd_fdstat_get(orig_fd).expect("reading fdstat of the source"); + let link_fdstat = wasi::fd_fdstat_get(link_fd).expect("reading fdstat of the link"); + fdstats_assert_eq(orig_fdstat, link_fdstat); +} + +// Extra calls of fd_close are needed for Windows, which will not remove +// the directory until all handles are closed. +unsafe fn test_path_link(dir_fd: wasi::Fd) { + // Create a file + let file_fd = create_or_open(dir_fd, "file", wasi::OFLAGS_CREAT); + + // Create a link in the same directory and compare rights + wasi::path_link(dir_fd, 0, "file", dir_fd, "link") + .expect("creating a link in the same directory"); + let link_fd = open_link(dir_fd, "link"); + check_rights(file_fd, link_fd); + wasi::fd_close(link_fd).expect("Closing link_fd"); // needed for Windows + wasi::path_unlink_file(dir_fd, "link").expect("removing a link"); + + // Create a link in a different directory and compare rights + wasi::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory"); + let subdir_fd = create_or_open(dir_fd, "subdir", wasi::OFLAGS_DIRECTORY); + wasi::path_link(dir_fd, 0, "file", subdir_fd, "link").expect("creating a link in subdirectory"); + let link_fd = open_link(subdir_fd, "link"); + check_rights(file_fd, link_fd); + wasi::path_unlink_file(subdir_fd, "link").expect("removing a link"); + wasi::fd_close(subdir_fd).expect("Closing subdir_fd"); // needed for Windows + wasi::fd_close(link_fd).expect("Closing link_fd"); // needed for Windows + wasi::path_remove_directory(dir_fd, "subdir").expect("removing a subdirectory"); + + // Create a link to a path that already exists + create_file(dir_fd, "link"); + + assert_errno!( + wasi::path_link(dir_fd, 0, "file", dir_fd, "link") + .expect_err("creating a link to existing path should fail"), + wasi::ERRNO_EXIST + ); + wasi::path_unlink_file(dir_fd, "link").expect("removing a file"); + + // Create a link to itself + assert_errno!( + wasi::path_link(dir_fd, 0, "file", dir_fd, "file") + .expect_err("creating a link to itself should fail"), + wasi::ERRNO_EXIST + ); + + // Create a link where target is a directory + wasi::path_create_directory(dir_fd, "link").expect("creating a dir"); + + assert_errno!( + wasi::path_link(dir_fd, 0, "file", dir_fd, "link") + .expect_err("creating a link where target is a directory should fail"), + wasi::ERRNO_EXIST + ); + wasi::path_remove_directory(dir_fd, "link").expect("removing a dir"); + + // Create a link to a directory + wasi::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory"); + let subdir_fd = create_or_open(dir_fd, "subdir", wasi::OFLAGS_DIRECTORY); + + assert_errno!( + wasi::path_link(dir_fd, 0, "subdir", dir_fd, "link") + .expect_err("creating a link to a directory should fail"), + wasi::ERRNO_PERM, + wasi::ERRNO_ACCES + ); + wasi::fd_close(subdir_fd).expect("close subdir before deleting it"); + wasi::path_remove_directory(dir_fd, "subdir").expect("removing a subdirectory"); + + // Create a link to a file with trailing slash + assert_errno!( + wasi::path_link(dir_fd, 0, "file", dir_fd, "link/") + .expect_err("creating a link to a file with trailing slash should fail"), + wasi::ERRNO_NOENT + ); + + if TESTCONFIG.support_dangling_filesystem() { + // Create a link to a dangling symlink + wasi::path_symlink("target", dir_fd, "symlink").expect("creating a dangling symlink"); + + // This should succeed, because we're not following symlinks + wasi::path_link(dir_fd, 0, "symlink", dir_fd, "link") + .expect("creating a link to a dangling symlink should succeed"); + wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink"); + wasi::path_unlink_file(dir_fd, "link").expect("removing a hardlink"); + + // Create a link to a symlink loop + wasi::path_symlink("symlink", dir_fd, "symlink").expect("creating a symlink loop"); + + wasi::path_link(dir_fd, 0, "symlink", dir_fd, "link") + .expect("creating a link to a symlink loop should succeed"); + wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink"); + wasi::path_unlink_file(dir_fd, "link").expect("removing a hardlink"); + + // Create a link where target is a dangling symlink + wasi::path_symlink("target", dir_fd, "symlink").expect("creating a dangling symlink"); + + assert_errno!( + wasi::path_link(dir_fd, 0, "file", dir_fd, "symlink") + .expect_err("creating a link where target is a dangling symlink"), + wasi::ERRNO_EXIST + ); + wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink"); + + // Create a link where target is a dangling symlink following symlinks + wasi::path_symlink("target", dir_fd, "symlink").expect("creating a dangling symlink"); + + // Symlink following with path_link is rejected + assert_errno!( + wasi::path_link( + dir_fd, + wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, + "symlink", + dir_fd, + "link", + ) + .expect_err("calling path_link with LOOKUPFLAGS_SYMLINK_FOLLOW should fail"), + wasi::ERRNO_INVAL + ); + + wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink"); + } + + // Clean up. + wasi::path_unlink_file(dir_fd, "file").expect("removing a file"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "path_link_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_path_link(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/path_open_create_existing.rs b/tests/rust/src/bin/path_open_create_existing.rs new file mode 100644 index 00000000..3cb9f946 --- /dev/null +++ b/tests/rust/src/bin/path_open_create_existing.rs @@ -0,0 +1,51 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_file, create_tmp_dir, open_scratch_directory}; + +unsafe fn test_path_open_create_existing(dir_fd: wasi::Fd) { + create_file(dir_fd, "file"); + assert_errno!( + wasi::path_open( + dir_fd, + 0, + "file", + wasi::OFLAGS_CREAT | wasi::OFLAGS_EXCL, + 0, + 0, + 0, + ) + .expect_err("trying to create a file that already exists"), + wasi::ERRNO_EXIST + ); + wasi::path_unlink_file(dir_fd, "file").expect("removing a file"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "path_open_create_existing_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_path_open_create_existing(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/path_open_dirfd_not_dir.rs b/tests/rust/src/bin/path_open_dirfd_not_dir.rs new file mode 100644 index 00000000..e7ba87be --- /dev/null +++ b/tests/rust/src/bin/path_open_dirfd_not_dir.rs @@ -0,0 +1,47 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_tmp_dir, open_scratch_directory}; + +unsafe fn test_dirfd_not_dir(dir_fd: wasi::Fd) { + // Open a file. + let file_fd = + wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).expect("opening a file"); + // Now try to open a file underneath it as if it were a directory. + assert_errno!( + wasi::path_open(file_fd, 0, "foo", wasi::OFLAGS_CREAT, 0, 0, 0) + .expect_err("non-directory base fd should get ERRNO_NOTDIR"), + wasi::ERRNO_NOTDIR + ); + wasi::fd_close(file_fd).expect("closing a file"); + wasi::path_unlink_file(dir_fd, "file").expect("removing a file"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "path_open_dirfd_not_dir_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_dirfd_not_dir(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/path_open_missing.rs b/tests/rust/src/bin/path_open_missing.rs new file mode 100644 index 00000000..a1a251ef --- /dev/null +++ b/tests/rust/src/bin/path_open_missing.rs @@ -0,0 +1,44 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_tmp_dir, open_scratch_directory}; + +unsafe fn test_path_open_missing(dir_fd: wasi::Fd) { + assert_errno!( + wasi::path_open( + dir_fd, 0, "file", 0, // not passing O_CREAT here + 0, 0, 0, + ) + .expect_err("trying to open a file that doesn't exist"), + wasi::ERRNO_NOENT + ); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "path_open_missing_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_path_open_missing(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/path_open_read_without_rights.rs b/tests/rust/src/bin/path_open_read_without_rights.rs new file mode 100644 index 00000000..6b774fbd --- /dev/null +++ b/tests/rust/src/bin/path_open_read_without_rights.rs @@ -0,0 +1,87 @@ +use std::{env, process}; +use wasi_tests::{ + assert_errno, create_file, create_tmp_dir, drop_rights, fd_get_rights, open_scratch_directory, +}; + +const TEST_FILENAME: &'static str = "file"; + +unsafe fn try_read_file(dir_fd: wasi::Fd) { + let fd = wasi::path_open(dir_fd, 0, TEST_FILENAME, 0, 0, 0, 0).expect("opening the file"); + + // Check that we don't have the right to exeucute fd_read + let (rbase, rinher) = fd_get_rights(fd); + assert_eq!( + rbase & wasi::RIGHTS_FD_READ, + 0, + "should not have base RIGHTS_FD_READ" + ); + assert_eq!( + rinher & wasi::RIGHTS_FD_READ, + 0, + "should not have inheriting RIGHTS_FD_READ" + ); + + let contents = &mut [0u8; 1]; + let iovec = wasi::Iovec { + buf: contents.as_mut_ptr() as *mut _, + buf_len: contents.len(), + }; + // Since we no longer have the right to fd_read, trying to read a file + // should be an error. + assert_errno!( + wasi::fd_read(fd, &[iovec]).expect_err("reading bytes from file should fail"), + wasi::ERRNO_BADF + ); +} + +unsafe fn test_read_rights(dir_fd: wasi::Fd) { + create_file(dir_fd, TEST_FILENAME); + drop_rights(dir_fd, wasi::RIGHTS_FD_READ, wasi::RIGHTS_FD_READ); + + let (rbase, rinher) = fd_get_rights(dir_fd); + assert_eq!( + rbase & wasi::RIGHTS_FD_READ, + 0, + "dir should not have base RIGHTS_FD_READ" + ); + assert_eq!( + rinher & wasi::RIGHTS_FD_READ, + 0, + "dir should not have inheriting RIGHTS_FD_READ" + ); + + try_read_file(dir_fd); + + wasi::path_unlink_file(dir_fd, TEST_FILENAME).expect("removing readable_file"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "path_open_read_without_rights_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_read_rights(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/path_rename.rs b/tests/rust/src/bin/path_rename.rs new file mode 100644 index 00000000..9532ce31 --- /dev/null +++ b/tests/rust/src/bin/path_rename.rs @@ -0,0 +1,187 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_file, create_tmp_dir, open_scratch_directory, TESTCONFIG}; + +unsafe fn test_path_rename(dir_fd: wasi::Fd) { + // First, try renaming a dir to nonexistent path + // Create source directory + wasi::path_create_directory(dir_fd, "source").expect("creating a directory"); + + // Try renaming the directory + wasi::path_rename(dir_fd, "source", dir_fd, "target").expect("renaming a directory"); + + // Check that source directory doesn't exist anymore + assert_errno!( + wasi::path_open(dir_fd, 0, "source", wasi::OFLAGS_DIRECTORY, 0, 0, 0) + .expect_err("opening a nonexistent path as a directory should fail"), + wasi::ERRNO_NOENT + ); + + // Check that target directory exists + let mut fd = wasi::path_open(dir_fd, 0, "target", wasi::OFLAGS_DIRECTORY, 0, 0, 0) + .expect("opening renamed path as a directory"); + assert!( + fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + wasi::fd_close(fd).expect("closing a file"); + wasi::path_remove_directory(dir_fd, "target").expect("removing a directory"); + + // Yes, renaming a dir to an empty dir is a property guaranteed by rename(2) + // and its fairly important that it is atomic. + // But, we haven't found a way to emulate it on windows. So, sometimes this + // behavior is just hosed. Sorry. + if TESTCONFIG.support_rename_dir_to_empty_dir() { + // Now, try renaming renaming a dir to existing empty dir + wasi::path_create_directory(dir_fd, "source").expect("creating a directory"); + wasi::path_create_directory(dir_fd, "target").expect("creating a directory"); + wasi::path_rename(dir_fd, "source", dir_fd, "target").expect("renaming a directory"); + + // Check that source directory doesn't exist anymore + assert_errno!( + wasi::path_open(dir_fd, 0, "source", wasi::OFLAGS_DIRECTORY, 0, 0, 0) + .expect_err("opening a nonexistent path as a directory"), + wasi::ERRNO_NOENT + ); + + // Check that target directory exists + fd = wasi::path_open(dir_fd, 0, "target", wasi::OFLAGS_DIRECTORY, 0, 0, 0) + .expect("opening renamed path as a directory"); + assert!( + fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + wasi::fd_close(fd).expect("closing a file"); + wasi::path_remove_directory(dir_fd, "target").expect("removing a directory"); + } else { + wasi::path_create_directory(dir_fd, "source").expect("creating a directory"); + wasi::path_create_directory(dir_fd, "target").expect("creating a directory"); + wasi::path_rename(dir_fd, "source", dir_fd, "target") + .expect_err("windows does not support renaming a directory to an empty directory"); + wasi::path_remove_directory(dir_fd, "target").expect("removing a directory"); + wasi::path_remove_directory(dir_fd, "source").expect("removing a directory"); + } + + // Now, try renaming a dir to existing non-empty dir + wasi::path_create_directory(dir_fd, "source").expect("creating a directory"); + wasi::path_create_directory(dir_fd, "target").expect("creating a directory"); + create_file(dir_fd, "target/file"); + + assert_errno!( + wasi::path_rename(dir_fd, "source", dir_fd, "target") + .expect_err("renaming directory to a nonempty directory"), + windows => wasi::ERRNO_ACCES, + unix => wasi::ERRNO_NOTEMPTY + ); + + // This is technically a different property, but the root of these divergent behaviors is in + // the semantics that windows gives us around renaming directories. So, it lives under the same + // flag. + if TESTCONFIG.support_rename_dir_to_empty_dir() { + // Try renaming dir to a file + assert_errno!( + wasi::path_rename(dir_fd, "source", dir_fd, "target/file") + .expect_err("renaming a directory to a file"), + wasi::ERRNO_NOTDIR + ); + wasi::path_unlink_file(dir_fd, "target/file").expect("removing a file"); + wasi::path_remove_directory(dir_fd, "source").expect("removing a directory"); + } else { + // Windows will let you erase a file by renaming a directory to it. + // WASI users can't depend on this error getting caught to prevent data loss. + wasi::path_rename(dir_fd, "source", dir_fd, "target/file") + .expect("windows happens to support renaming a directory to a file"); + wasi::path_remove_directory(dir_fd, "target/file").expect("removing a file"); + } + wasi::path_remove_directory(dir_fd, "target").expect("removing a directory"); + + // Now, try renaming a file to a nonexistent path + create_file(dir_fd, "source"); + wasi::path_rename(dir_fd, "source", dir_fd, "target").expect("renaming a file"); + + // Check that source file doesn't exist anymore + assert_errno!( + wasi::path_open(dir_fd, 0, "source", 0, 0, 0, 0) + .expect_err("opening a nonexistent path should fail"), + wasi::ERRNO_NOENT + ); + + // Check that target file exists + fd = wasi::path_open(dir_fd, 0, "target", 0, 0, 0, 0).expect("opening renamed path"); + assert!( + fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + wasi::fd_close(fd).expect("closing a file"); + wasi::path_unlink_file(dir_fd, "target").expect("removing a file"); + + // Now, try renaming file to an existing file + create_file(dir_fd, "source"); + create_file(dir_fd, "target"); + + wasi::path_rename(dir_fd, "source", dir_fd, "target") + .expect("renaming file to another existing file"); + + // Check that source file doesn't exist anymore + assert_errno!( + wasi::path_open(dir_fd, 0, "source", 0, 0, 0, 0).expect_err("opening a nonexistent path"), + wasi::ERRNO_NOENT + ); + + // Check that target file exists + fd = wasi::path_open(dir_fd, 0, "target", 0, 0, 0, 0).expect("opening renamed path"); + assert!( + fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + wasi::fd_close(fd).expect("closing a file"); + wasi::path_unlink_file(dir_fd, "target").expect("removing a file"); + + // Try renaming to an (empty) directory instead + create_file(dir_fd, "source"); + wasi::path_create_directory(dir_fd, "target").expect("creating a directory"); + + assert_errno!( + wasi::path_rename(dir_fd, "source", dir_fd, "target") + .expect_err("renaming a file to existing directory should fail"), + windows => wasi::ERRNO_ACCES, + unix => wasi::ERRNO_ISDIR + ); + + wasi::path_remove_directory(dir_fd, "target").expect("removing a directory"); + wasi::path_unlink_file(dir_fd, "source").expect("removing a file"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "path_rename_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_path_rename(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/path_rename_dir_trailing_slashes.rs b/tests/rust/src/bin/path_rename_dir_trailing_slashes.rs new file mode 100644 index 00000000..129de743 --- /dev/null +++ b/tests/rust/src/bin/path_rename_dir_trailing_slashes.rs @@ -0,0 +1,47 @@ +use std::{env, process}; +use wasi_tests::{create_tmp_dir, open_scratch_directory}; + +unsafe fn test_path_rename_trailing_slashes(dir_fd: wasi::Fd) { + // Test renaming a directory with a trailing slash in the name. + wasi::path_create_directory(dir_fd, "source").expect("creating a directory"); + wasi::path_rename(dir_fd, "source/", dir_fd, "target") + .expect("renaming a directory with a trailing slash in the source name"); + wasi::path_rename(dir_fd, "target", dir_fd, "source/") + .expect("renaming a directory with a trailing slash in the destination name"); + wasi::path_rename(dir_fd, "source/", dir_fd, "target/") + .expect("renaming a directory with a trailing slash in the source and destination names"); + wasi::path_rename(dir_fd, "target", dir_fd, "source") + .expect("renaming a directory with no trailing slashes at all should work"); + wasi::path_remove_directory(dir_fd, "source").expect("removing the directory"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "path_rename_dir_trailing_slashes_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_path_rename_trailing_slashes(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/path_symlink_trailing_slashes.rs b/tests/rust/src/bin/path_symlink_trailing_slashes.rs new file mode 100644 index 00000000..5e0628ba --- /dev/null +++ b/tests/rust/src/bin/path_symlink_trailing_slashes.rs @@ -0,0 +1,91 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_file, create_tmp_dir, open_scratch_directory, TESTCONFIG}; + +unsafe fn test_path_symlink_trailing_slashes(dir_fd: wasi::Fd) { + if TESTCONFIG.support_dangling_filesystem() { + // Dangling symlink: Link destination shouldn't end with a slash. + assert_errno!( + wasi::path_symlink("source", dir_fd, "target/") + .expect_err("link destination ending with a slash should fail"), + wasi::ERRNO_NOENT + ); + + // Dangling symlink: Without the trailing slash, this should succeed. + wasi::path_symlink("source", dir_fd, "target") + .expect("link destination ending with a slash"); + wasi::path_unlink_file(dir_fd, "target").expect("removing a file"); + } + + // Link destination already exists, target has trailing slash. + wasi::path_create_directory(dir_fd, "target").expect("creating a directory"); + assert_errno!( + wasi::path_symlink("source", dir_fd, "target/") + .expect_err("link destination already exists"), + unix => wasi::ERRNO_EXIST, + windows => wasi::ERRNO_NOENT + ); + wasi::path_remove_directory(dir_fd, "target").expect("removing a directory"); + + // Link destination already exists, target has no trailing slash. + wasi::path_create_directory(dir_fd, "target").expect("creating a directory"); + assert_errno!( + wasi::path_symlink("source", dir_fd, "target") + .expect_err("link destination already exists"), + unix => wasi::ERRNO_EXIST, + windows => wasi::ERRNO_NOENT + ); + wasi::path_remove_directory(dir_fd, "target").expect("removing a directory"); + + // Link destination already exists, target has trailing slash. + create_file(dir_fd, "target"); + + assert_errno!( + wasi::path_symlink("source", dir_fd, "target/") + .expect_err("link destination already exists"), + unix => wasi::ERRNO_NOTDIR, + windows => wasi::ERRNO_NOENT + ); + wasi::path_unlink_file(dir_fd, "target").expect("removing a file"); + + // Link destination already exists, target has no trailing slash. + create_file(dir_fd, "target"); + + assert_errno!( + wasi::path_symlink("source", dir_fd, "target") + .expect_err("link destination already exists"), + unix => wasi::ERRNO_EXIST, + windows => wasi::ERRNO_NOENT + ); + wasi::path_unlink_file(dir_fd, "target").expect("removing a file"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "path_symlink_trailing_slashes_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_path_symlink_trailing_slashes(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/poll_oneoff_stdio.rs b/tests/rust/src/bin/poll_oneoff_stdio.rs new file mode 100644 index 00000000..d3b39c95 --- /dev/null +++ b/tests/rust/src/bin/poll_oneoff_stdio.rs @@ -0,0 +1,137 @@ +use std::collections::HashMap; +use std::mem::MaybeUninit; +use wasi_tests::{assert_errno, STDERR_FD, STDIN_FD, STDOUT_FD}; + +const TIMEOUT: u64 = 200_000_000u64; // 200 milliseconds, required to satisfy slow execution in CI +const CLOCK_ID: wasi::Userdata = 0x0123_45678; +const STDIN_ID: wasi::Userdata = 0x8765_43210; + +unsafe fn poll_oneoff_impl(r#in: &[wasi::Subscription]) -> Result, wasi::Errno> { + let mut out: Vec = Vec::new(); + out.resize_with(r#in.len(), || { + MaybeUninit::::zeroed().assume_init() + }); + let size = wasi::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())?; + out.truncate(size); + Ok(out) +} + +unsafe fn test_stdin_read() { + let clock = wasi::SubscriptionClock { + id: wasi::CLOCKID_MONOTONIC, + timeout: TIMEOUT, + precision: 0, + flags: 0, + }; + let fd_readwrite = wasi::SubscriptionFdReadwrite { + file_descriptor: STDIN_FD, + }; + // Either stdin can be ready for reading, or this poll can timeout. + let r#in = [ + wasi::Subscription { + userdata: CLOCK_ID, + u: wasi::SubscriptionU { + tag: wasi::EVENTTYPE_CLOCK.raw(), + u: wasi::SubscriptionUU { clock }, + }, + }, + wasi::Subscription { + userdata: STDIN_ID, + u: wasi::SubscriptionU { + tag: wasi::EVENTTYPE_FD_READ.raw(), + u: wasi::SubscriptionUU { + fd_read: fd_readwrite, + }, + }, + }, + ]; + let out = poll_oneoff_impl(&r#in).unwrap(); + // The result should be either a timeout, or that stdin is ready for reading. + // Both are valid behaviors that depend on the test environment. + assert!(out.len() >= 1, "stdin read should return at least 1 event"); + for event in out { + if event.type_ == wasi::EVENTTYPE_CLOCK { + assert_errno!(event.error, wasi::ERRNO_SUCCESS); + assert_eq!( + event.userdata, CLOCK_ID, + "the event.userdata should contain CLOCK_ID", + ); + } else if event.type_ == wasi::EVENTTYPE_FD_READ { + assert_errno!(event.error, wasi::ERRNO_SUCCESS); + assert_eq!( + event.userdata, STDIN_ID, + "the event.userdata should contain STDIN_ID", + ); + } else { + panic!("unexpected event type {}", event.type_.raw()); + } + } +} + +fn writable_subs(h: &HashMap) -> Vec { + println!("writable subs: {:?}", h); + h.iter() + .map(|(ud, fd)| wasi::Subscription { + userdata: *ud, + u: wasi::SubscriptionU { + tag: wasi::EVENTTYPE_FD_WRITE.raw(), + u: wasi::SubscriptionUU { + fd_write: wasi::SubscriptionFdReadwrite { + file_descriptor: *fd, + }, + }, + }, + }) + .collect() +} + +unsafe fn test_stdout_stderr_write() { + let mut writable: HashMap = + vec![(1, STDOUT_FD), (2, STDERR_FD)].into_iter().collect(); + + let clock = wasi::Subscription { + userdata: CLOCK_ID, + u: wasi::SubscriptionU { + tag: wasi::EVENTTYPE_CLOCK.raw(), + u: wasi::SubscriptionUU { + clock: wasi::SubscriptionClock { + id: wasi::CLOCKID_MONOTONIC, + timeout: TIMEOUT, + precision: 0, + flags: 0, + }, + }, + }, + }; + while !writable.is_empty() { + let mut subs = writable_subs(&writable); + subs.push(clock.clone()); + let out = poll_oneoff_impl(&subs).unwrap(); + for event in out { + match event.userdata { + CLOCK_ID => { + panic!("timed out with the following pending subs: {:?}", writable) + } + ud => { + if let Some(_) = writable.remove(&ud) { + assert_eq!(event.type_, wasi::EVENTTYPE_FD_WRITE); + assert_errno!(event.error, wasi::ERRNO_SUCCESS); + } else { + panic!("Unknown userdata {}, pending sub: {:?}", ud, writable) + } + } + } + } + } +} + +unsafe fn test_poll_oneoff() { + // NB we assume that stdin/stdout/stderr are valid and open + // for the duration of the test case + test_stdin_read(); + test_stdout_stderr_write(); +} +fn main() { + // Run the tests. + unsafe { test_poll_oneoff() } +} diff --git a/tests/rust/src/bin/readlink.rs b/tests/rust/src/bin/readlink.rs new file mode 100644 index 00000000..d6b9cb43 --- /dev/null +++ b/tests/rust/src/bin/readlink.rs @@ -0,0 +1,64 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_file, create_tmp_dir, open_scratch_directory}; + +unsafe fn test_readlink(dir_fd: wasi::Fd) { + // Create a file in the scratch directory. + create_file(dir_fd, "target"); + + // Create a symlink + wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink"); + + // Read link into the buffer + let buf = &mut [0u8; 10]; + let bufused = wasi::path_readlink(dir_fd, "symlink", buf.as_mut_ptr(), buf.len()) + .expect("readlink should succeed"); + assert_eq!(bufused, 6, "should use 6 bytes of the buffer"); + assert_eq!(&buf[..6], b"target", "buffer should contain 'target'"); + assert_eq!( + &buf[6..], + &[0u8; 4], + "the remaining bytes should be untouched" + ); + + // Read link into smaller buffer than the actual link's length + let buf = &mut [0u8; 4]; + let err = wasi::path_readlink(dir_fd, "symlink", buf.as_mut_ptr(), buf.len()) + .err() + .expect("readlink with too-small buffer should fail"); + assert_errno!(err, wasi::ERRNO_RANGE); + + // Clean up. + wasi::path_unlink_file(dir_fd, "target").expect("removing a file"); + wasi::path_unlink_file(dir_fd, "symlink").expect("removing a file"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "readlink_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_readlink(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/remove_nonempty_directory.rs b/tests/rust/src/bin/remove_nonempty_directory.rs new file mode 100644 index 00000000..fa3e21d8 --- /dev/null +++ b/tests/rust/src/bin/remove_nonempty_directory.rs @@ -0,0 +1,53 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_tmp_dir, open_scratch_directory}; + +unsafe fn test_remove_nonempty_directory(dir_fd: wasi::Fd) { + // Create a directory in the scratch directory. + wasi::path_create_directory(dir_fd, "dir").expect("creating a directory"); + + // Create a directory in the directory we just created. + wasi::path_create_directory(dir_fd, "dir/nested").expect("creating a subdirectory"); + + // Test that attempting to unlink the first directory returns the expected error code. + assert_errno!( + wasi::path_remove_directory(dir_fd, "dir") + .expect_err("remove_directory on a directory should return ENOTEMPTY"), + wasi::ERRNO_NOTEMPTY + ); + + // Removing the directories. + wasi::path_remove_directory(dir_fd, "dir/nested") + .expect("remove_directory on a nested directory should succeed"); + wasi::path_remove_directory(dir_fd, "dir").expect("removing a directory"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "remove_nonempty_directory_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_remove_nonempty_directory(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/renumber.rs b/tests/rust/src/bin/renumber.rs new file mode 100644 index 00000000..67f94497 --- /dev/null +++ b/tests/rust/src/bin/renumber.rs @@ -0,0 +1,109 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_tmp_dir, open_scratch_directory}; + +unsafe fn test_renumber(dir_fd: wasi::Fd) { + let pre_fd: wasi::Fd = (libc::STDERR_FILENO + 1) as wasi::Fd; + + assert!(dir_fd > pre_fd, "dir_fd number"); + + // Create a file in the scratch directory. + let fd_from = wasi::path_open( + dir_fd, + 0, + "file1", + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE, + 0, + 0, + ) + .expect("opening a file"); + assert!( + fd_from > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + // Get fd_from fdstat attributes + let fdstat_from = + wasi::fd_fdstat_get(fd_from).expect("calling fd_fdstat on the open file descriptor"); + + // Create another file in the scratch directory. + let fd_to = wasi::path_open( + dir_fd, + 0, + "file2", + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE, + 0, + 0, + ) + .expect("opening a file"); + assert!( + fd_to > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + // Renumber fd of file1 into fd of file2 + wasi::fd_renumber(fd_from, fd_to).expect("renumbering two descriptors"); + + // Ensure that fd_from is closed + assert_errno!( + wasi::fd_close(fd_from).expect_err("closing already closed file descriptor"), + wasi::ERRNO_BADF + ); + + // Ensure that fd_to is still open. + let fdstat_to = + wasi::fd_fdstat_get(fd_to).expect("calling fd_fdstat on the open file descriptor"); + assert_eq!( + fdstat_from.fs_filetype, fdstat_to.fs_filetype, + "expected fd_to have the same fdstat as fd_from" + ); + assert_eq!( + fdstat_from.fs_flags, fdstat_to.fs_flags, + "expected fd_to have the same fdstat as fd_from" + ); + assert_eq!( + fdstat_from.fs_rights_base, fdstat_to.fs_rights_base, + "expected fd_to have the same fdstat as fd_from" + ); + assert_eq!( + fdstat_from.fs_rights_inheriting, fdstat_to.fs_rights_inheriting, + "expected fd_to have the same fdstat as fd_from" + ); + + wasi::fd_close(fd_to).expect("closing a file"); + + wasi::path_unlink_file(dir_fd, "file1").expect("removing a file"); + wasi::path_unlink_file(dir_fd, "file2").expect("removing a file"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "renumber_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_renumber(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/sched_yield.rs b/tests/rust/src/bin/sched_yield.rs new file mode 100644 index 00000000..a00ab299 --- /dev/null +++ b/tests/rust/src/bin/sched_yield.rs @@ -0,0 +1,8 @@ +unsafe fn test_sched_yield() { + wasi::sched_yield().expect("sched_yield"); +} + +fn main() { + // Run tests + unsafe { test_sched_yield() } +} diff --git a/tests/rust/src/bin/stdio.rs b/tests/rust/src/bin/stdio.rs new file mode 100644 index 00000000..31b0a25b --- /dev/null +++ b/tests/rust/src/bin/stdio.rs @@ -0,0 +1,13 @@ +use wasi_tests::{STDERR_FD, STDIN_FD, STDOUT_FD}; + +unsafe fn test_stdio() { + for fd in &[STDIN_FD, STDOUT_FD, STDERR_FD] { + wasi::fd_fdstat_get(*fd).expect("fd_fdstat_get on stdio"); + wasi::fd_renumber(*fd, *fd + 100).expect("renumbering stdio"); + } +} + +fn main() { + // Run the tests. + unsafe { test_stdio() } +} diff --git a/tests/rust/src/bin/symlink_create.rs b/tests/rust/src/bin/symlink_create.rs new file mode 100644 index 00000000..e7ad968a --- /dev/null +++ b/tests/rust/src/bin/symlink_create.rs @@ -0,0 +1,103 @@ +use std::{env, process}; +use wasi_tests::{create_tmp_dir, open_scratch_directory}; + +unsafe fn create_symlink_to_file(dir_fd: wasi::Fd) { + // Create a directory for the symlink to point to. + let target_fd = + wasi::path_open(dir_fd, 0, "target", wasi::OFLAGS_CREAT, 0, 0, 0).expect("creating a file"); + wasi::fd_close(target_fd).expect("closing file"); + + // Create a symlink. + wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink"); + + // Try to open it as a directory without O_NOFOLLOW. + let target_file_via_symlink = wasi::path_open( + dir_fd, + wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, + "symlink", + 0, + 0, + 0, + 0, + ) + .expect("opening a symlink as a directory"); + assert!( + target_file_via_symlink > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + wasi::fd_close(target_file_via_symlink).expect("close the symlink file"); + + // Replace the target directory with a file. + wasi::path_unlink_file(dir_fd, "symlink").expect("removing the symlink"); + wasi::path_unlink_file(dir_fd, "target").expect("removing the target file"); +} + +unsafe fn create_symlink_to_directory(dir_fd: wasi::Fd) { + // Create a directory for the symlink to point to. + wasi::path_create_directory(dir_fd, "target").expect("creating a dir"); + + // Create a symlink. + wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink"); + + // Try to open it as a directory without O_NOFOLLOW. + let target_dir_via_symlink = wasi::path_open( + dir_fd, + wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, + "symlink", + wasi::OFLAGS_DIRECTORY, + 0, + 0, + 0, + ) + .expect("opening a symlink as a directory"); + assert!( + target_dir_via_symlink > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + wasi::fd_close(target_dir_via_symlink).expect("closing a file"); + + // Replace the target directory with a file. + wasi::path_unlink_file(dir_fd, "symlink").expect("remove symlink to directory"); + wasi::path_remove_directory(dir_fd, "target") + .expect("remove_directory on a directory should succeed"); +} + +unsafe fn create_symlink_to_root(dir_fd: wasi::Fd) { + // Create a symlink. + wasi::path_symlink("/", dir_fd, "symlink").expect_err("creating a symlink to an absolute path"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "symlink_create_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { + create_symlink_to_file(dir_fd); + create_symlink_to_directory(dir_fd); + create_symlink_to_root(dir_fd); + } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/symlink_filestat.rs b/tests/rust/src/bin/symlink_filestat.rs new file mode 100644 index 00000000..502a30ef --- /dev/null +++ b/tests/rust/src/bin/symlink_filestat.rs @@ -0,0 +1,116 @@ +use std::{env, process}; +use wasi_tests::{create_tmp_dir, open_scratch_directory}; + +unsafe fn test_path_filestat(dir_fd: wasi::Fd) { + let fdstat = wasi::fd_fdstat_get(dir_fd).expect("fd_fdstat_get"); + assert_ne!( + fdstat.fs_rights_base & wasi::RIGHTS_PATH_FILESTAT_GET, + 0, + "the scratch directory should have RIGHT_PATH_FILESTAT_GET as base right", + ); + + // Create a file in the scratch directory. + let file_fd = wasi::path_open( + dir_fd, + 0, + "file", + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE | wasi::RIGHTS_PATH_FILESTAT_GET, + 0, + 0, + ) + .expect("opening a file"); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + // Check file size + let file_stat = wasi::path_filestat_get(dir_fd, 0, "file").expect("reading file stats"); + assert_eq!(file_stat.size, 0, "file size should be 0"); + + // Create a symlink + wasi::path_symlink("file", dir_fd, "symlink").expect("creating symlink to a file"); + + // Check path_filestat_set_times on the symlink itself + let sym_stat = wasi::path_filestat_get(dir_fd, 0, "symlink").expect("reading symlink stats"); + + // Modify mtim of symlink + let sym_new_mtim = sym_stat.mtim - 200; + wasi::path_filestat_set_times(dir_fd, 0, "symlink", 0, sym_new_mtim, wasi::FSTFLAGS_MTIM) + .expect("path_filestat_set_times should succeed on symlink"); + + // Check that symlink mtim motification worked + let modified_sym_stat = wasi::path_filestat_get(dir_fd, 0, "symlink") + .expect("reading file stats after path_filestat_set_times"); + assert_eq!( + modified_sym_stat.mtim, sym_new_mtim, + "symlink mtim should change" + ); + + // Check that pointee mtim is not modified + let unmodified_file_stat = wasi::path_filestat_get(dir_fd, 0, "file") + .expect("reading file stats after path_filestat_set_times"); + assert_eq!( + unmodified_file_stat.mtim, file_stat.mtim, + "file mtim should not change" + ); + + // Now, dereference the symlink + let deref_sym_stat = + wasi::path_filestat_get(dir_fd, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, "symlink") + .expect("reading file stats on the dereferenced symlink"); + assert_eq!( + deref_sym_stat.mtim, file_stat.mtim, + "symlink mtim should be equal to pointee's when dereferenced" + ); + + // Finally, change stat of the original file by dereferencing the symlink + wasi::path_filestat_set_times( + dir_fd, + wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, + "symlink", + 0, + sym_stat.mtim, + wasi::FSTFLAGS_MTIM, + ) + .expect("path_filestat_set_times should succeed on setting stat on original file"); + + let new_file_stat = wasi::path_filestat_get(dir_fd, 0, "file") + .expect("reading file stats after path_filestat_set_times"); + assert_eq!(new_file_stat.mtim, sym_stat.mtim, "mtim should change"); + + wasi::fd_close(file_fd).expect("closing a file"); + wasi::path_unlink_file(dir_fd, "symlink").expect("removing a symlink"); + wasi::path_unlink_file(dir_fd, "file").expect("removing a file"); +} +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "symlink_filestat_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_path_filestat(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/symlink_loop.rs b/tests/rust/src/bin/symlink_loop.rs new file mode 100644 index 00000000..9a55eecc --- /dev/null +++ b/tests/rust/src/bin/symlink_loop.rs @@ -0,0 +1,50 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_tmp_dir, open_scratch_directory, TESTCONFIG}; + +unsafe fn test_symlink_loop(dir_fd: wasi::Fd) { + if TESTCONFIG.support_dangling_filesystem() { + // Create a self-referencing symlink. + wasi::path_symlink("symlink", dir_fd, "symlink").expect("creating a symlink"); + + // Try to open it. + assert_errno!( + wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0) + .expect_err("opening a self-referencing symlink"), + wasi::ERRNO_LOOP + ); + + // Clean up. + wasi::path_unlink_file(dir_fd, "symlink").expect("removing a file"); + } +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "symlink_loop_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_symlink_loop(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/truncation_rights.rs b/tests/rust/src/bin/truncation_rights.rs new file mode 100644 index 00000000..4cc9f0a7 --- /dev/null +++ b/tests/rust/src/bin/truncation_rights.rs @@ -0,0 +1,106 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_file, create_tmp_dir, open_scratch_directory}; + +unsafe fn test_truncation_rights(dir_fd: wasi::Fd) { + // Create a file in the scratch directory. + create_file(dir_fd, "file"); + + // Get the rights for the scratch directory. + let mut dir_fdstat = + wasi::fd_fdstat_get(dir_fd).expect("calling fd_fdstat on the scratch directory"); + assert_eq!( + dir_fdstat.fs_filetype, + wasi::FILETYPE_DIRECTORY, + "expected the scratch directory to be a directory", + ); + assert_eq!( + dir_fdstat.fs_flags, 0, + "expected the scratch directory to have no special flags", + ); + assert_eq!( + dir_fdstat.fs_rights_base & wasi::RIGHTS_FD_FILESTAT_SET_SIZE, + 0, + "directories shouldn't have the fd_filestat_set_size right", + ); + + // If we have the right to set sizes from paths, test that it works. + if (dir_fdstat.fs_rights_base & wasi::RIGHTS_PATH_FILESTAT_SET_SIZE) == 0 { + eprintln!("implementation doesn't support setting file sizes, skipping"); + } else { + // Test that we can truncate the file. + let mut file_fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_TRUNC, 0, 0, 0) + .expect("truncating a file"); + wasi::fd_close(file_fd).expect("closing a file"); + + let mut rights_base: wasi::Rights = dir_fdstat.fs_rights_base; + let mut rights_inheriting: wasi::Rights = dir_fdstat.fs_rights_inheriting; + + if (rights_inheriting & wasi::RIGHTS_FD_FILESTAT_SET_SIZE) == 0 { + eprintln!("implementation doesn't support setting file sizes through file descriptors, skipping"); + } else { + rights_inheriting &= !wasi::RIGHTS_FD_FILESTAT_SET_SIZE; + wasi::fd_fdstat_set_rights(dir_fd, rights_base, rights_inheriting) + .expect("droping fd_filestat_set_size inheriting right on a directory"); + } + + // Test that we can truncate the file without the + // wasi_unstable::RIGHT_FD_FILESTAT_SET_SIZE right. + file_fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_TRUNC, 0, 0, 0) + .expect("truncating a file without fd_filestat_set_size right"); + wasi::fd_close(file_fd).expect("closing a file"); + + rights_base &= !wasi::RIGHTS_PATH_FILESTAT_SET_SIZE; + wasi::fd_fdstat_set_rights(dir_fd, rights_base, rights_inheriting) + .expect("droping path_filestat_set_size base right on a directory"); + + // Test that clearing wasi_unstable::RIGHT_PATH_FILESTAT_SET_SIZE actually + // took effect. + dir_fdstat = wasi::fd_fdstat_get(dir_fd).expect("reading the fdstat from a directory"); + assert_eq!( + (dir_fdstat.fs_rights_base & wasi::RIGHTS_PATH_FILESTAT_SET_SIZE), + 0, + "reading the fdstat from a directory", + ); + + // Test that we can't truncate the file without the + // wasi_unstable::RIGHT_PATH_FILESTAT_SET_SIZE right. + assert_errno!( + wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_TRUNC, 0, 0, 0) + .expect_err("truncating a file without path_filestat_set_size right"), + wasi::ERRNO_PERM + ); + } + + wasi::path_unlink_file(dir_fd, "file").expect("removing a file"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "truncation_rights_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_truncation_rights(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/bin/unlink_file_trailing_slashes.rs b/tests/rust/src/bin/unlink_file_trailing_slashes.rs new file mode 100644 index 00000000..77fba081 --- /dev/null +++ b/tests/rust/src/bin/unlink_file_trailing_slashes.rs @@ -0,0 +1,74 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, create_file, create_tmp_dir, open_scratch_directory}; + +unsafe fn test_unlink_file_trailing_slashes(dir_fd: wasi::Fd) { + // Create a directory in the scratch directory. + wasi::path_create_directory(dir_fd, "dir").expect("creating a directory"); + + // Test that unlinking it fails. + assert_errno!( + wasi::path_unlink_file(dir_fd, "dir") + .expect_err("unlink_file on a directory should fail"), + macos => wasi::ERRNO_PERM, + unix => wasi::ERRNO_ISDIR, + windows => wasi::ERRNO_ACCES + ); + + // Test that unlinking it with a trailing flash fails. + assert_errno!( + wasi::path_unlink_file(dir_fd, "dir/") + .expect_err("unlink_file on a directory should fail"), + macos => wasi::ERRNO_PERM, + unix => wasi::ERRNO_ISDIR, + windows => wasi::ERRNO_ACCES + ); + + // Clean up. + wasi::path_remove_directory(dir_fd, "dir").expect("removing a directory"); + + // Create a temporary file. + create_file(dir_fd, "file"); + + // Test that unlinking it with a trailing flash fails. + assert_errno!( + wasi::path_unlink_file(dir_fd, "file/") + .expect_err("unlink_file with a trailing slash should fail"), + unix => wasi::ERRNO_NOTDIR, + windows => wasi::ERRNO_NOENT + ); + + // Test that unlinking it with no trailing flash succeeds. + wasi::path_unlink_file(dir_fd, "file") + .expect("unlink_file with no trailing slash should succeed"); +} + +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 scratch directory + let base_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + const DIR_NAME: &str = "unlink_file_trailing_slashes_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + + // Run the tests. + unsafe { test_unlink_file_trailing_slashes(dir_fd) } + + unsafe { wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") } +} diff --git a/tests/rust/src/config.rs b/tests/rust/src/config.rs index b2d095c5..95c04ac8 100644 --- a/tests/rust/src/config.rs +++ b/tests/rust/src/config.rs @@ -27,7 +27,12 @@ impl TestConfig { let no_dangling_filesystem = std::env::var("NO_DANGLING_FILESYSTEM").is_ok(); let no_fd_allocate = std::env::var("NO_FD_ALLOCATE").is_ok(); let no_rename_dir_to_empty_dir = std::env::var("NO_RENAME_DIR_TO_EMPTY_DIR").is_ok(); - let no_fdflags_sync_support = std::env::var("NO_FDFLAGS_SYNC_SUPPORT").is_ok(); + + // Disable the sync tests while we determine whether these will be + // included in the standard: + //let no_fdflags_sync_support = std::env::var("NO_FDFLAGS_SYNC_SUPPORT").is_ok(); + let no_fdflags_sync_support = true; + TestConfig { errno_mode, no_dangling_filesystem, diff --git a/tests/rust/src/lib.rs b/tests/rust/src/lib.rs index f14b95db..7700664f 100644 --- a/tests/rust/src/lib.rs +++ b/tests/rust/src/lib.rs @@ -1,5 +1,6 @@ pub mod config; use once_cell::sync::Lazy; +use wasi::path_create_directory; pub static TESTCONFIG: Lazy = Lazy::new(config::TestConfig::from_env); @@ -9,6 +10,47 @@ pub const STDIN_FD: wasi::Fd = 0x0; pub const STDOUT_FD: wasi::Fd = 0x1; pub const STDERR_FD: wasi::Fd = 0x2; +pub unsafe fn create_tmp_dir(dir_fd: wasi::Fd, name: &str) -> wasi::Fd { + path_create_directory(dir_fd, name).expect("failed to create dir"); + wasi::path_open( + dir_fd, + 0, + name, + wasi::OFLAGS_DIRECTORY, + wasi::RIGHTS_FD_FILESTAT_GET + | wasi::RIGHTS_FD_READDIR + | wasi::RIGHTS_PATH_CREATE_FILE + | wasi::RIGHTS_PATH_CREATE_DIRECTORY + | wasi::RIGHTS_PATH_REMOVE_DIRECTORY + | wasi::RIGHTS_PATH_OPEN + | wasi::RIGHTS_PATH_UNLINK_FILE + | wasi::RIGHTS_PATH_LINK_SOURCE + | wasi::RIGHTS_PATH_LINK_TARGET + | wasi::RIGHTS_PATH_READLINK + | wasi::RIGHTS_PATH_RENAME_SOURCE + | wasi::RIGHTS_PATH_RENAME_TARGET + | wasi::RIGHTS_PATH_FILESTAT_GET + | wasi::RIGHTS_PATH_FILESTAT_SET_SIZE + | wasi::RIGHTS_PATH_FILESTAT_SET_TIMES + | wasi::RIGHTS_PATH_SYMLINK + | wasi::RIGHTS_PATH_REMOVE_DIRECTORY, + wasi::RIGHTS_FD_READ + | wasi::RIGHTS_FD_WRITE + | wasi::RIGHTS_FD_READDIR + | wasi::RIGHTS_FD_FILESTAT_GET + | wasi::RIGHTS_FD_SEEK + | wasi::RIGHTS_FD_FDSTAT_SET_FLAGS + | wasi::RIGHTS_FD_SYNC + | wasi::RIGHTS_FD_TELL + | wasi::RIGHTS_FD_ADVISE + | wasi::RIGHTS_FD_ALLOCATE + | wasi::RIGHTS_FD_FILESTAT_SET_SIZE + | wasi::RIGHTS_FD_FILESTAT_SET_TIMES, + 0, + ) + .expect("failed to open dir") +} + /// Opens a fresh file descriptor for `path` where `path` should be a preopened /// directory. pub fn open_scratch_directory(path: &str) -> Result { diff --git a/tests/rust/testsuite/file_allocate.json b/tests/rust/testsuite/file_allocate.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/file_allocate.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/file_pread_pwrite.json b/tests/rust/testsuite/file_pread_pwrite.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/file_pread_pwrite.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/file_seek_tell.json b/tests/rust/testsuite/file_seek_tell.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/file_seek_tell.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/file_truncation.json b/tests/rust/testsuite/file_truncation.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/file_truncation.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/file_unbuffered_write.json b/tests/rust/testsuite/file_unbuffered_write.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/file_unbuffered_write.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/interesting_paths.json b/tests/rust/testsuite/interesting_paths.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/interesting_paths.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/isatty.json b/tests/rust/testsuite/isatty.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/isatty.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/nofollow_errors.json b/tests/rust/testsuite/nofollow_errors.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/nofollow_errors.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/overwrite_preopen.json b/tests/rust/testsuite/overwrite_preopen.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/overwrite_preopen.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/path_exists.json b/tests/rust/testsuite/path_exists.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/path_exists.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/path_filestat.json b/tests/rust/testsuite/path_filestat.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/path_filestat.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/path_link.json b/tests/rust/testsuite/path_link.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/path_link.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/path_open_create_existing.json b/tests/rust/testsuite/path_open_create_existing.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/path_open_create_existing.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/path_open_dirfd_not_dir.json b/tests/rust/testsuite/path_open_dirfd_not_dir.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/path_open_dirfd_not_dir.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/path_open_missing.json b/tests/rust/testsuite/path_open_missing.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/path_open_missing.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/path_open_read_without_rights.json b/tests/rust/testsuite/path_open_read_without_rights.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/path_open_read_without_rights.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/path_rename.json b/tests/rust/testsuite/path_rename.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/path_rename.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/path_rename_dir_trailing_slashes.json b/tests/rust/testsuite/path_rename_dir_trailing_slashes.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/path_rename_dir_trailing_slashes.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/path_symlink_trailing_slashes.json b/tests/rust/testsuite/path_symlink_trailing_slashes.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/path_symlink_trailing_slashes.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/readlink.json b/tests/rust/testsuite/readlink.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/readlink.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/remove_nonempty_directory.json b/tests/rust/testsuite/remove_nonempty_directory.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/remove_nonempty_directory.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/renumber.json b/tests/rust/testsuite/renumber.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/renumber.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/symlink_create.json b/tests/rust/testsuite/symlink_create.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/symlink_create.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/symlink_filestat.json b/tests/rust/testsuite/symlink_filestat.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/symlink_filestat.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/symlink_loop.json b/tests/rust/testsuite/symlink_loop.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/symlink_loop.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/truncation_rights.json b/tests/rust/testsuite/truncation_rights.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/truncation_rights.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/unlink_file_trailing_slashes.json b/tests/rust/testsuite/unlink_file_trailing_slashes.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/unlink_file_trailing_slashes.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file