diff --git a/rust/src/tests/errors.rs b/rust/src/tests/errors.rs index 45a714e3..c150c574 100644 --- a/rust/src/tests/errors.rs +++ b/rust/src/tests/errors.rs @@ -1,3 +1,4 @@ +pub(super) mod eexist; pub(super) mod efault; pub(super) mod eloop; pub(super) mod enametoolong; diff --git a/rust/src/tests/errors/eexist.rs b/rust/src/tests/errors/eexist.rs new file mode 100644 index 00000000..ba41dd44 --- /dev/null +++ b/rust/src/tests/errors/eexist.rs @@ -0,0 +1,49 @@ +/// Create a test case which asserts that the sycall +/// returns EEXIST if the named file exists. +/// There are multiple forms for this macro: +/// +/// - A basic form which takes the syscall, and optionally a `~path` argument +/// to indicate where the `path` argument should be substituted if the path +/// is not the only argument taken by the syscall. +/// +/// ``` +/// // `unlink` accepts only a path as argument. +/// eloop_comp_test_case!(unlink); +/// // `chflags` takes a path and the flags to set as arguments. +/// // We need to add `~path` where the path argument should normally be taken. +/// eloop_comp_test_case!(chflags(~path, FileFlags::empty())); +/// ``` +/// +/// - A more complex form which takes multiple functions +/// with the context and the path as arguments, for syscalls +/// requiring to compute other arguments. +/// +/// ``` +/// eloop_comp_test_case!(chown, |ctx: &mut TestContext, path: &Path| { +/// let user = ctx.get_new_user(); +/// chown(path, Some(user.uid), None) +/// }) +/// ```` +macro_rules! eexist_file_exists_test_case { + ($syscall: ident, $($f: expr),+ $(; $attrs:tt )?) => { + crate::test_case! { + #[doc = concat!(stringify!($syscall), + " returns EEXIST if the named file exists")] + eexist_file_exists $(, $attrs )? => [Regular, Dir, Fifo, Block, Char, Socket, Symlink(None)] + } + fn eexist_file_exists(ctx: &mut crate::context::TestContext, + ft: crate::context::FileType) { + let path = ctx.create(ft).unwrap(); + $( assert_eq!($f(ctx, &path), Err(nix::errno::Errno::EEXIST)); )+ + } + }; + + ($syscall: ident $( ($( $($before:expr),* ,)? ~path $(, $($after:expr),*)?) )?) => { + eexist_file_exists_test_case!($syscall, |_: &mut $crate::context::TestContext, + path: &std::path::Path| { + $syscall($( $($($before),* ,)? )? path $( $(, $($after),*)? )?) + }); + }; +} + +pub(crate) use eexist_file_exists_test_case; diff --git a/rust/src/tests/link.rs b/rust/src/tests/link.rs index 2647d1c5..e4c483cd 100644 --- a/rust/src/tests/link.rs +++ b/rust/src/tests/link.rs @@ -224,6 +224,17 @@ fn enoent_source_not_exists(ctx: &mut TestContext) { assert_eq!(link(&source, &dest), Err(Errno::ENOENT)); } +crate::test_case! { + /// link returns EEXIST if the destination file exists + eexist_dest_exists => [Regular, Dir, Fifo, Block, Char, Socket, Symlink(None)] +} +fn eexist_dest_exists(ctx: &mut TestContext, ft: FileType) { + let path = ctx.create(ft).unwrap(); + let regular_file = ctx.create(FileType::Regular).unwrap(); + + assert_eq!(link(®ular_file, &path), Err(nix::errno::Errno::EEXIST)); +} + // link/14.t exdev_target_test_case!(link); diff --git a/rust/src/tests/mkdir.rs b/rust/src/tests/mkdir.rs index 99b45ac5..b9e8e2d3 100644 --- a/rust/src/tests/mkdir.rs +++ b/rust/src/tests/mkdir.rs @@ -4,6 +4,7 @@ use nix::{sys::stat::Mode, unistd::mkdir}; use crate::context::{SerializedTestContext, TestContext}; +use super::errors::eexist::eexist_file_exists_test_case; use super::errors::efault::efault_path_test_case; use super::errors::eloop::eloop_comp_test_case; use super::errors::enametoolong::{enametoolong_comp_test_case, enametoolong_path_test_case}; @@ -63,5 +64,8 @@ enoent_comp_test_case!(mkdir(~path, Mode::empty())); // mkdir/07.t eloop_comp_test_case!(mkdir(~path, Mode::empty())); +// mkdir/10.t +eexist_file_exists_test_case!(mkdir(~path, Mode::empty())); + // mkdir/12.t efault_path_test_case!(mkdir, |ptr| nix::libc::mkdir(ptr, 0o755)); diff --git a/rust/src/tests/mkfifo.rs b/rust/src/tests/mkfifo.rs index fb2f5509..2f0bad89 100644 --- a/rust/src/tests/mkfifo.rs +++ b/rust/src/tests/mkfifo.rs @@ -4,6 +4,7 @@ use nix::{sys::stat::Mode, unistd::mkfifo}; use crate::context::{SerializedTestContext, TestContext}; +use super::errors::eexist::eexist_file_exists_test_case; use super::errors::efault::efault_path_test_case; use super::errors::eloop::eloop_comp_test_case; use super::errors::enametoolong::{enametoolong_comp_test_case, enametoolong_path_test_case}; @@ -65,5 +66,8 @@ enoent_comp_test_case!(mkfifo(~path, Mode::empty())); // mkfifo/07.t eloop_comp_test_case!(mkfifo(~path, Mode::empty())); +// mkfifo/09.t +eexist_file_exists_test_case!(mkfifo(~path, Mode::empty())); + // mkfifo/12.t efault_path_test_case!(mkfifo, |ptr| nix::libc::mkfifo(ptr, 0o644)); diff --git a/rust/src/tests/mknod.rs b/rust/src/tests/mknod.rs index 8fe92a34..a1b8dfe1 100644 --- a/rust/src/tests/mknod.rs +++ b/rust/src/tests/mknod.rs @@ -5,6 +5,7 @@ use nix::sys::stat::{mknod, Mode, SFlag}; use crate::context::{FileType, SerializedTestContext, TestContext}; +use super::errors::eexist::eexist_file_exists_test_case; use super::errors::efault::efault_path_test_case; use super::errors::eloop::eloop_comp_test_case; use super::errors::enametoolong::{enametoolong_comp_test_case, enametoolong_path_test_case}; @@ -203,6 +204,9 @@ enoent_comp_test_case!(mknod(~path, SFlag::S_IFIFO, Mode::empty(), 0)); // mknod/07.t eloop_comp_test_case!(mknod(~path, SFlag::S_IFIFO, Mode::empty(), 0)); +// mknod/08.t +eexist_file_exists_test_case!(mknod(~path, SFlag::S_IFIFO, Mode::empty(), 0)); + // mknod/10.t efault_path_test_case!(mknod, |ptr| nix::libc::mknod( ptr, @@ -226,4 +230,7 @@ mod privileged { // mknod/03.t enametoolong_path_test_case!(mknod, mknod_block_wrapper, mknod_char_wrapper; root); + + // mknod/08.t + eexist_file_exists_test_case!(mknod, mknod_block_wrapper, mknod_char_wrapper; root); } diff --git a/rust/src/tests/open.rs b/rust/src/tests/open.rs index 27520bfe..20346ee4 100644 --- a/rust/src/tests/open.rs +++ b/rust/src/tests/open.rs @@ -10,6 +10,7 @@ use nix::unistd::close; use crate::context::{FileType, SerializedTestContext, TestContext}; +use super::errors::eexist::eexist_file_exists_test_case; use super::errors::efault::efault_path_test_case; use super::errors::eloop::eloop_comp_test_case; use super::errors::enametoolong::{enametoolong_comp_test_case, enametoolong_path_test_case}; @@ -297,6 +298,9 @@ etxtbsy_test_case!( open_flag_wrapper(OFlag::O_RDONLY | OFlag::O_TRUNC) ); +// open/22.t +eexist_file_exists_test_case!(open(~path, OFlag::O_CREAT | OFlag::O_EXCL, Mode::empty())); + // open/21.t efault_path_test_case!(open, |ptr| nix::libc::open(ptr, nix::libc::O_RDONLY)); diff --git a/rust/src/tests/rename.rs b/rust/src/tests/rename.rs index 1ca87d89..0eef8db0 100644 --- a/rust/src/tests/rename.rs +++ b/rust/src/tests/rename.rs @@ -315,6 +315,23 @@ fn eisdir_to_dir_from_not_dir(ctx: &mut TestContext, ft: FileType) { // rename/17.t efault_either_test_case!(rename, nix::libc::rename); +crate::test_case! { + /// rename returns EINVAL when the 'from' argument is a parent directory of 'to' + // rename/18.t + einval_parent_from_subdir_to +} +fn einval_parent_from_subdir_to(ctx: &mut TestContext) { + let subdir = ctx.create(FileType::Dir).unwrap(); + let nested_subdir = ctx + .new_file(FileType::Dir) + .name(subdir.join("subsubdir")) + .create() + .unwrap(); + + assert_eq!(rename(ctx.base_path(), &subdir), Err(Errno::EINVAL)); + assert_eq!(rename(ctx.base_path(), &nested_subdir), Err(Errno::EINVAL)); +} + crate::test_case! { /// rename returns EINVAL/EBUSY when an attempt is made to rename '.' or '..' // rename/19.t @@ -334,20 +351,19 @@ fn einval_ebusy_dot_dotdot(ctx: &mut TestContext) { } crate::test_case! { - /// rename returns EINVAL when the 'from' argument is a parent directory of 'to' - // rename/18.t - einval_parent_from_subdir_to + /// rename returns EEXIST or ENOTEMPTY if the 'to' argument is a directory and is not empty + // rename/20.t + eexist_enotempty_to_non_empty => [Regular, Dir, Fifo, Block, Char, Socket, Symlink(None)] } -fn einval_parent_from_subdir_to(ctx: &mut TestContext) { - let subdir = ctx.create(FileType::Dir).unwrap(); - let nested_subdir = ctx - .new_file(FileType::Dir) - .name(subdir.join("subsubdir")) - .create() - .unwrap(); +fn eexist_enotempty_to_non_empty(ctx: &mut TestContext, ft: FileType) { + let from_dir = ctx.create(FileType::Dir).unwrap(); + let to_dir = ctx.create(FileType::Dir).unwrap(); + ctx.new_file(ft).name(to_dir.join("test")).create().unwrap(); - assert_eq!(rename(ctx.base_path(), &subdir), Err(Errno::EINVAL)); - assert_eq!(rename(ctx.base_path(), &nested_subdir), Err(Errno::EINVAL)); + assert!(matches!( + rename(&from_dir, &to_dir), + Err(Errno::EEXIST | Errno::ENOTEMPTY) + )); } // rename/15.t diff --git a/rust/src/tests/rmdir.rs b/rust/src/tests/rmdir.rs index b009e18e..2c9c839a 100644 --- a/rust/src/tests/rmdir.rs +++ b/rust/src/tests/rmdir.rs @@ -113,15 +113,6 @@ fn has_mount_cap(_: &Config, _: &Path) -> anyhow::Result<()> { Ok(()) } -crate::test_case! { - /// rmdir return EBUSY if the directory to be removed is the mount point for a mounted file system - ebusy; has_mount_cap -} -fn ebusy(ctx: &mut TestContext) { - let dummy_mount = DummyMnt::new(ctx).unwrap(); - assert_eq!(rmdir(&dummy_mount.path), Err(Errno::EBUSY)); -} - // rmdir/02.t enametoolong_comp_test_case!(rmdir); @@ -134,6 +125,24 @@ enoent_named_file_test_case!(rmdir); // rmdir/05.t eloop_comp_test_case!(rmdir); +crate::test_case! { + /// rmdir returns EEXIST or ENOTEMPTY if the named directory + /// contains files other than '.' and '..' in it + // rmdir/06.t + eexist_enotempty_non_empty_dir => [Regular, Dir, Fifo, Block, Char, Socket, Symlink(None)] +} +fn eexist_enotempty_non_empty_dir(ctx: &mut TestContext, ft: crate::context::FileType) { + ctx.new_file(ft) + .name(ctx.base_path().join("file")) + .create() + .unwrap(); + + assert!(matches!( + rmdir(ctx.base_path()), + Err(Errno::EEXIST | Errno::ENOTEMPTY) + )); +} + crate::test_case! { /// rmdir returns EINVAL if the last component of the path is '.' // rmdir/12.t @@ -143,5 +152,35 @@ fn einval_dot(ctx: &mut TestContext) { assert_eq!(rmdir(&ctx.base_path().join(".")), Err(Errno::EINVAL)); } +crate::test_case! { + /// rmdir returns EEXIST or ENOTEMPTY if the last component of the path is '..' + // rmdir/12.t + eexist_enotempty_dotdot +} +fn eexist_enotempty_dotdot(ctx: &mut TestContext) { + // TODO: Not conforming to POSIX on FreeBSD + // According to POSIX: EEXIST or ENOTEMPTY: + // The path argument names a directory that is + // not an empty directory, + // or there are hard links to the directory other than dot or a single entry in dot-dot. + #[cfg(not(target_os = "freebsd"))] + { + assert!(matches!( + rmdir(&ctx.base_path().join("..")), + Err(Errno::ENOTEMPTY | Errno::EEXIST) + )); + } +} + +crate::test_case! { + /// rmdir return EBUSY if the directory to be removed is the mount point for a mounted file system + // rmdir/13.t + ebusy; has_mount_cap +} +fn ebusy(ctx: &mut TestContext) { + let dummy_mount = DummyMnt::new(ctx).unwrap(); + assert_eq!(rmdir(&dummy_mount.path), Err(Errno::EBUSY)); +} + // rmdir/15.t efault_path_test_case!(rmdir, nix::libc::rmdir); diff --git a/rust/src/tests/symlink.rs b/rust/src/tests/symlink.rs index 1b890483..6206433c 100644 --- a/rust/src/tests/symlink.rs +++ b/rust/src/tests/symlink.rs @@ -11,6 +11,7 @@ use crate::{ }; use super::errors::{ + eexist::eexist_file_exists_test_case, efault::efault_either_test_case, enametoolong::{enametoolong_comp_test_case, enametoolong_either_path_test_case}, enotdir::enotdir_comp_test_case, @@ -95,5 +96,8 @@ enametoolong_either_path_test_case!(symlink); // symlink/04.t enoent_comp_test_case!(symlink(Path::new("test"), ~path)); +// symlink/08.t +eexist_file_exists_test_case!(symlink(Path::new("test"), ~path)); + // symlink/13.t efault_either_test_case!(symlink, nix::libc::symlink);