diff --git a/Cargo.lock b/Cargo.lock index 548a59c3242..8125fcff456 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4480,7 +4480,7 @@ dependencies = [ [[package]] name = "mirrord-protocol" -version = "1.13.4" +version = "1.14.0" dependencies = [ "actix-codec", "bincode", diff --git a/changelog.d/2221.added.md b/changelog.d/2221.added.md new file mode 100644 index 00000000000..fe396a1ac03 --- /dev/null +++ b/changelog.d/2221.added.md @@ -0,0 +1 @@ +Add rmdir / unlink / unlinkat support diff --git a/mirrord/agent/src/file.rs b/mirrord/agent/src/file.rs index 54432a9779b..0bc30afb151 100644 --- a/mirrord/agent/src/file.rs +++ b/mirrord/agent/src/file.rs @@ -5,13 +5,17 @@ use std::{ io::{self, prelude::*, BufReader, SeekFrom}, iter::{Enumerate, Peekable}, ops::RangeInclusive, - os::unix::{fs::MetadataExt, prelude::FileExt}, + os::{ + fd::RawFd, + unix::{fs::MetadataExt, prelude::FileExt}, + }, path::{Path, PathBuf}, }; use faccess::{AccessMode, PathExt}; use libc::DT_DIR; use mirrord_protocol::{file::*, FileRequest, FileResponse, RemoteResult, ResponseError}; +use nix::unistd::UnlinkatFlags; use tracing::{error, trace, Level}; use crate::error::Result; @@ -258,6 +262,17 @@ impl FileManager { pathname, mode, }) => Some(FileResponse::MakeDir(self.mkdirat(dirfd, &pathname, mode))), + FileRequest::RemoveDir(RemoveDirRequest { pathname }) => { + Some(FileResponse::RemoveDir(self.rmdir(&pathname))) + } + FileRequest::Unlink(UnlinkRequest { pathname }) => { + Some(FileResponse::Unlink(self.unlink(&pathname))) + } + FileRequest::UnlinkAt(UnlinkAtRequest { + dirfd, + pathname, + flags, + }) => Some(FileResponse::Unlink(self.unlinkat(dirfd, &pathname, flags))), }) } @@ -520,6 +535,55 @@ impl FileManager { } } + #[tracing::instrument(level = Level::TRACE, skip(self))] + pub(crate) fn rmdir(&mut self, path: &Path) -> RemoteResult<()> { + let path = resolve_path(path, &self.root_path)?; + + std::fs::remove_dir(path.as_path()).map_err(ResponseError::from) + } + + #[tracing::instrument(level = Level::TRACE, skip(self))] + pub(crate) fn unlink(&mut self, path: &Path) -> RemoteResult<()> { + let path = resolve_path(path, &self.root_path)?; + + nix::unistd::unlink(path.as_path()) + .map_err(|error| ResponseError::from(std::io::Error::from_raw_os_error(error as i32))) + } + + #[tracing::instrument(level = Level::TRACE, skip(self))] + pub(crate) fn unlinkat( + &mut self, + dirfd: Option, + path: &Path, + flags: u32, + ) -> RemoteResult<()> { + let path = match dirfd { + Some(dirfd) => { + let relative_dir = self + .open_files + .get(&dirfd) + .ok_or(ResponseError::NotFound(dirfd))?; + + if let RemoteFile::Directory(relative_dir) = relative_dir { + relative_dir.join(path) + } else { + return Err(ResponseError::NotDirectory(dirfd)); + } + } + None => resolve_path(path, &self.root_path)?, + }; + + let flags = match flags { + 0 => UnlinkatFlags::RemoveDir, + _ => UnlinkatFlags::NoRemoveDir, + }; + + let fd: Option = dirfd.map(|fd| fd as RawFd); + + nix::unistd::unlinkat(fd, path.as_path(), flags) + .map_err(|error| ResponseError::from(std::io::Error::from_raw_os_error(error as i32))) + } + pub(crate) fn seek(&mut self, fd: u64, seek_from: SeekFrom) -> RemoteResult { trace!( "FileManager::seek -> fd {:#?} | seek_from {:#?}", diff --git a/mirrord/intproxy/protocol/src/lib.rs b/mirrord/intproxy/protocol/src/lib.rs index 623020718b6..69a551b50bc 100644 --- a/mirrord/intproxy/protocol/src/lib.rs +++ b/mirrord/intproxy/protocol/src/lib.rs @@ -324,6 +324,27 @@ impl_request!( res_path = ProxyToLayerMessage::File => FileResponse::MakeDir, ); +impl_request!( + req = RemoveDirRequest, + res = RemoteResult<()>, + req_path = LayerToProxyMessage::File => FileRequest::RemoveDir, + res_path = ProxyToLayerMessage::File => FileResponse::RemoveDir, +); + +impl_request!( + req = UnlinkRequest, + res = RemoteResult<()>, + req_path = LayerToProxyMessage::File => FileRequest::Unlink, + res_path = ProxyToLayerMessage::File => FileResponse::Unlink, +); + +impl_request!( + req = UnlinkAtRequest, + res = RemoteResult<()>, + req_path = LayerToProxyMessage::File => FileRequest::UnlinkAt, + res_path = ProxyToLayerMessage::File => FileResponse::Unlink, +); + impl_request!( req = SeekFileRequest, res = RemoteResult, diff --git a/mirrord/intproxy/src/proxies/files.rs b/mirrord/intproxy/src/proxies/files.rs index 79ac6695575..517c743ce12 100644 --- a/mirrord/intproxy/src/proxies/files.rs +++ b/mirrord/intproxy/src/proxies/files.rs @@ -6,7 +6,7 @@ use mirrord_protocol::{ file::{ CloseDirRequest, CloseFileRequest, DirEntryInternal, ReadDirBatchRequest, ReadDirResponse, ReadFileResponse, ReadLimitedFileRequest, SeekFromInternal, MKDIR_VERSION, - READDIR_BATCH_VERSION, READLINK_VERSION, + READDIR_BATCH_VERSION, READLINK_VERSION, RMDIR_VERSION, }, ClientMessage, DaemonMessage, ErrorKindInternal, FileRequest, FileResponse, RemoteIOError, ResponseError, @@ -253,6 +253,31 @@ impl FilesProxy { self.protocol_version.replace(version); } + /// Checks if the mirrord protocol version supports this [`FileRequest`]. + fn is_request_supported(&self, request: &FileRequest) -> Result<(), FileResponse> { + let protocol_version = self.protocol_version.as_ref(); + + match request { + FileRequest::ReadLink(..) + if protocol_version.is_some_and(|version| !READLINK_VERSION.matches(version)) => + { + Err(FileResponse::ReadLink(Err(ResponseError::NotImplemented))) + } + FileRequest::MakeDir(..) | FileRequest::MakeDirAt(..) + if protocol_version.is_some_and(|version| !MKDIR_VERSION.matches(version)) => + { + Err(FileResponse::MakeDir(Err(ResponseError::NotImplemented))) + } + FileRequest::RemoveDir(..) | FileRequest::Unlink(..) | FileRequest::UnlinkAt(..) + if protocol_version + .is_some_and(|version: &Version| !RMDIR_VERSION.matches(version)) => + { + Err(FileResponse::RemoveDir(Err(ResponseError::NotImplemented))) + } + _ => Ok(()), + } + } + // #[tracing::instrument(level = Level::TRACE, skip(message_bus))] async fn file_request( &mut self, @@ -261,6 +286,18 @@ impl FilesProxy { message_id: MessageId, message_bus: &mut MessageBus, ) { + // Not supported in old `mirrord-protocol` versions. + if let Err(response) = self.is_request_supported(&request) { + message_bus + .send(ToLayer { + message_id, + layer_id, + message: ProxyToLayerMessage::File(response), + }) + .await; + return; + } + match request { // Should trigger remote close only when the fd is closed in all layer instances. FileRequest::Close(close) => { @@ -454,31 +491,6 @@ impl FilesProxy { } }, - // Not supported in old `mirrord-protocol` versions. - req @ FileRequest::ReadLink(..) => { - let supported = self - .protocol_version - .as_ref() - .is_some_and(|version| READLINK_VERSION.matches(version)); - - if supported { - self.request_queue.push_back(message_id, layer_id); - message_bus - .send(ProxyMessage::ToAgent(ClientMessage::FileRequest(req))) - .await; - } else { - message_bus - .send(ToLayer { - message_id, - message: ProxyToLayerMessage::File(FileResponse::ReadLink(Err( - ResponseError::NotImplemented, - ))), - layer_id, - }) - .await; - } - } - // Should only be sent from intproxy, not from the layer. FileRequest::ReadDirBatch(..) => { unreachable!("ReadDirBatch request is never sent from the layer"); @@ -522,30 +534,6 @@ impl FilesProxy { .await; } - FileRequest::MakeDir(_) | FileRequest::MakeDirAt(_) => { - let supported = self - .protocol_version - .as_ref() - .is_some_and(|version| MKDIR_VERSION.matches(version)); - - if supported { - self.request_queue.push_back(message_id, layer_id); - message_bus - .send(ProxyMessage::ToAgent(ClientMessage::FileRequest(request))) - .await; - } else { - let file_response = FileResponse::MakeDir(Err(ResponseError::NotImplemented)); - - message_bus - .send(ToLayer { - message_id, - message: ProxyToLayerMessage::File(file_response), - layer_id, - }) - .await; - } - } - // Doesn't require any special logic. other => { self.request_queue.push_back(message_id, layer_id); diff --git a/mirrord/layer/src/file/hooks.rs b/mirrord/layer/src/file/hooks.rs index 4de1e73577f..7c46165b37a 100644 --- a/mirrord/layer/src/file/hooks.rs +++ b/mirrord/layer/src/file/hooks.rs @@ -1088,6 +1088,43 @@ pub(crate) unsafe extern "C" fn mkdirat_detour( }) } +/// Hook for `libc::rmdir`. +#[hook_guard_fn] +pub(crate) unsafe extern "C" fn rmdir_detour(pathname: *const c_char) -> c_int { + rmdir(pathname.checked_into()) + .map(|()| 0) + .unwrap_or_bypass_with(|bypass| { + let raw_path = update_ptr_from_bypass(pathname, &bypass); + FN_RMDIR(raw_path) + }) +} + +/// Hook for `libc::unlink`. +#[hook_guard_fn] +pub(crate) unsafe extern "C" fn unlink_detour(pathname: *const c_char) -> c_int { + unlink(pathname.checked_into()) + .map(|()| 0) + .unwrap_or_bypass_with(|bypass| { + let raw_path = update_ptr_from_bypass(pathname, &bypass); + FN_UNLINK(raw_path) + }) +} + +/// Hook for `libc::unlinkat`. +#[hook_guard_fn] +pub(crate) unsafe extern "C" fn unlinkat_detour( + dirfd: c_int, + pathname: *const c_char, + flags: u32, +) -> c_int { + unlinkat(dirfd, pathname.checked_into(), flags) + .map(|()| 0) + .unwrap_or_bypass_with(|bypass| { + let raw_path = update_ptr_from_bypass(pathname, &bypass); + FN_UNLINKAT(dirfd, raw_path, flags) + }) +} + /// Convenience function to setup file hooks (`x_detour`) with `frida_gum`. pub(crate) unsafe fn enable_file_hooks(hook_manager: &mut HookManager) { replace!(hook_manager, "open", open_detour, FnOpen, FN_OPEN); @@ -1163,7 +1200,6 @@ pub(crate) unsafe fn enable_file_hooks(hook_manager: &mut HookManager) { ); replace!(hook_manager, "mkdir", mkdir_detour, FnMkdir, FN_MKDIR); - replace!( hook_manager, "mkdirat", @@ -1172,6 +1208,17 @@ pub(crate) unsafe fn enable_file_hooks(hook_manager: &mut HookManager) { FN_MKDIRAT ); + replace!(hook_manager, "rmdir", rmdir_detour, FnRmdir, FN_RMDIR); + + replace!(hook_manager, "unlink", unlink_detour, FnUnlink, FN_UNLINK); + replace!( + hook_manager, + "unlinkat", + unlinkat_detour, + FnUnlinkat, + FN_UNLINKAT + ); + replace!(hook_manager, "lseek", lseek_detour, FnLseek, FN_LSEEK); replace!(hook_manager, "write", write_detour, FnWrite, FN_WRITE); diff --git a/mirrord/layer/src/file/ops.rs b/mirrord/layer/src/file/ops.rs index ca9dd10f951..bac20ad7cd9 100644 --- a/mirrord/layer/src/file/ops.rs +++ b/mirrord/layer/src/file/ops.rs @@ -4,12 +4,12 @@ use std::{env, ffi::CString, io::SeekFrom, os::unix::io::RawFd, path::PathBuf}; #[cfg(target_os = "linux")] use libc::{c_char, statx, statx_timestamp}; -use libc::{c_int, iovec, unlink, AT_FDCWD}; +use libc::{c_int, iovec, AT_FDCWD}; use mirrord_protocol::{ file::{ MakeDirAtRequest, MakeDirRequest, OpenFileRequest, OpenFileResponse, OpenOptionsInternal, - ReadFileResponse, ReadLinkFileRequest, ReadLinkFileResponse, SeekFileResponse, - WriteFileResponse, XstatFsResponse, XstatResponse, + ReadFileResponse, ReadLinkFileRequest, ReadLinkFileResponse, RemoveDirRequest, + SeekFileResponse, UnlinkAtRequest, WriteFileResponse, XstatFsResponse, XstatResponse, }, ResponseError, }; @@ -157,7 +157,7 @@ fn create_local_fake_file(remote_fd: u64) -> Detour { close_remote_file_on_failure(remote_fd)?; Detour::Error(HookError::LocalFileCreation(remote_fd, error.0)) } else { - unsafe { unlink(file_path_ptr) }; + unsafe { libc::unlink(file_path_ptr) }; Detour::Success(local_file_fd) } } @@ -392,6 +392,69 @@ pub(crate) fn mkdirat(dirfd: RawFd, pathname: Detour, mode: u32) -> Det } } +#[mirrord_layer_macro::instrument(level = Level::TRACE, ret)] +pub(crate) fn rmdir(pathname: Detour) -> Detour<()> { + let pathname = pathname?; + + check_relative_paths!(pathname); + + let path = remap_path!(pathname); + + ensure_not_ignored!(path, false); + + let rmdir = RemoveDirRequest { pathname: path }; + + // `NotImplemented` error here means that the protocol doesn't support it. + match common::make_proxy_request_with_response(rmdir)? { + Ok(response) => Detour::Success(response), + Err(ResponseError::NotImplemented) => Detour::Bypass(Bypass::NotImplemented), + Err(fail) => Detour::Error(fail.into()), + } +} + +#[mirrord_layer_macro::instrument(level = Level::TRACE, ret)] +pub(crate) fn unlink(pathname: Detour) -> Detour<()> { + let pathname = pathname?; + + check_relative_paths!(pathname); + + let path = remap_path!(pathname); + + ensure_not_ignored!(path, false); + + let unlink = RemoveDirRequest { pathname: path }; + + // `NotImplemented` error here means that the protocol doesn't support it. + match common::make_proxy_request_with_response(unlink)? { + Ok(response) => Detour::Success(response), + Err(ResponseError::NotImplemented) => Detour::Bypass(Bypass::NotImplemented), + Err(fail) => Detour::Error(fail.into()), + } +} + +#[mirrord_layer_macro::instrument(level = Level::TRACE, ret)] +pub(crate) fn unlinkat(dirfd: RawFd, pathname: Detour, flags: u32) -> Detour<()> { + let pathname = pathname?; + + let optional_dirfd = match pathname.is_absolute() { + true => None, + false => Some(get_remote_fd(dirfd)?), + }; + + let unlink = UnlinkAtRequest { + dirfd: optional_dirfd, + pathname: pathname.clone(), + flags, + }; + + // `NotImplemented` error here means that the protocol doesn't support it. + match common::make_proxy_request_with_response(unlink)? { + Ok(response) => Detour::Success(response), + Err(ResponseError::NotImplemented) => Detour::Bypass(Bypass::NotImplemented), + Err(fail) => Detour::Error(fail.into()), + } +} + pub(crate) fn pwrite(local_fd: RawFd, buffer: &[u8], offset: u64) -> Detour { let remote_fd = get_remote_fd(local_fd)?; trace!("pwrite: local_fd {local_fd}"); diff --git a/mirrord/layer/src/go/linux_x64.rs b/mirrord/layer/src/go/linux_x64.rs index 5acaceb13b2..18b36700cbe 100644 --- a/mirrord/layer/src/go/linux_x64.rs +++ b/mirrord/layer/src/go/linux_x64.rs @@ -344,6 +344,11 @@ unsafe extern "C" fn c_abi_syscall_handler( #[cfg(all(target_os = "linux", not(target_arch = "aarch64")))] libc::SYS_mkdir => mkdir_detour(param1 as _, param2 as _) as i64, libc::SYS_mkdirat => mkdirat_detour(param1 as _, param2 as _, param3 as _) as i64, + #[cfg(all(target_os = "linux", not(target_arch = "aarch64")))] + libc::SYS_rmdir => rmdir_detour(param1 as _) as i64, + #[cfg(all(target_os = "linux", not(target_arch = "aarch64")))] + libc::SYS_unlink => unlink_detour(param1 as _) as i64, + libc::SYS_unlinkat => unlinkat_detour(param1 as _, param2 as _, param3 as _) as i64, _ => { let (Ok(result) | Err(result)) = syscalls::syscall!( syscalls::Sysno::from(syscall as i32), diff --git a/mirrord/layer/src/go/mod.rs b/mirrord/layer/src/go/mod.rs index 003eed8692c..df810bbdcf9 100644 --- a/mirrord/layer/src/go/mod.rs +++ b/mirrord/layer/src/go/mod.rs @@ -113,6 +113,11 @@ unsafe extern "C" fn c_abi_syscall6_handler( #[cfg(all(target_os = "linux", not(target_arch = "aarch64")))] libc::SYS_mkdir => mkdir_detour(param1 as _, param2 as _) as i64, libc::SYS_mkdirat => mkdirat_detour(param1 as _, param2 as _, param3 as _) as i64, + #[cfg(all(target_os = "linux", not(target_arch = "aarch64")))] + libc::SYS_rmdir => rmdir_detour(param1 as _) as i64, + #[cfg(all(target_os = "linux", not(target_arch = "aarch64")))] + libc::SYS_unlink => unlink_detour(param1 as _) as i64, + libc::SYS_unlinkat => unlinkat_detour(param1 as _, param2 as _, param3 as _) as i64, _ => { let (Ok(result) | Err(result)) = syscalls::syscall!( syscalls::Sysno::from(syscall as i32), diff --git a/mirrord/layer/tests/apps/mkdir/mkdir.c b/mirrord/layer/tests/apps/mkdir/mkdir.c index 4253b6c089d..8631ae7a26a 100644 --- a/mirrord/layer/tests/apps/mkdir/mkdir.c +++ b/mirrord/layer/tests/apps/mkdir/mkdir.c @@ -1,6 +1,5 @@ #include #include -#include #include #include diff --git a/mirrord/layer/tests/apps/rmdir/rmdir.c b/mirrord/layer/tests/apps/rmdir/rmdir.c new file mode 100644 index 00000000000..f839e5256e7 --- /dev/null +++ b/mirrord/layer/tests/apps/rmdir/rmdir.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include + +/// Test `rmdir`. +/// +/// Creates a folder and then removes it. +/// +int main() +{ + char *test_dir = "/test_dir"; + int mkdir_result = mkdir(test_dir, 0777); + assert(mkdir_result == 0); + + int rmdir_result = rmdir(test_dir); + assert(rmdir_result == 0); + + return 0; +} diff --git a/mirrord/layer/tests/common/mod.rs b/mirrord/layer/tests/common/mod.rs index 22945790e4b..639b39427d9 100644 --- a/mirrord/layer/tests/common/mod.rs +++ b/mirrord/layer/tests/common/mod.rs @@ -487,6 +487,25 @@ impl TestIntProxy { .unwrap(); } + /// Makes a [`FileRequest::RemoveDir`] and answers it. + pub async fn expect_remove_dir(&mut self, expected_dir_name: &str) { + // Expecting `rmdir` call with path. + assert_matches!( + self.recv().await, + ClientMessage::FileRequest(FileRequest::RemoveDir( + mirrord_protocol::file::RemoveDirRequest { pathname } + )) if pathname.to_str().unwrap() == expected_dir_name + ); + + // Answer `rmdir`. + self.codec + .send(DaemonMessage::File( + mirrord_protocol::FileResponse::RemoveDir(Ok(())), + )) + .await + .unwrap(); + } + /// Verify that the passed message (not the next message from self.codec!) is a file read. /// Return buffer size. pub async fn expect_message_file_read(message: ClientMessage, expected_fd: u64) -> u64 { @@ -763,6 +782,7 @@ pub enum Application { Fork, ReadLink, MakeDir, + RemoveDir, OpenFile, CIssue2055, CIssue2178, @@ -819,6 +839,7 @@ impl Application { Application::Fork => String::from("tests/apps/fork/out.c_test_app"), Application::ReadLink => String::from("tests/apps/readlink/out.c_test_app"), Application::MakeDir => String::from("tests/apps/mkdir/out.c_test_app"), + Application::RemoveDir => String::from("tests/apps/rmdir/out.c_test_app"), Application::Realpath => String::from("tests/apps/realpath/out.c_test_app"), Application::NodeHTTP | Application::NodeIssue2283 | Application::NodeIssue2807 => { String::from("node") @@ -1057,6 +1078,7 @@ impl Application { | Application::Fork | Application::ReadLink | Application::MakeDir + | Application::RemoveDir | Application::Realpath | Application::RustFileOps | Application::RustIssue1123 @@ -1135,6 +1157,7 @@ impl Application { | Application::Fork | Application::ReadLink | Application::MakeDir + | Application::RemoveDir | Application::Realpath | Application::Go21Issue834 | Application::Go22Issue834 diff --git a/mirrord/layer/tests/mkdir.rs b/mirrord/layer/tests/mkdir.rs index 5f0fe3301b3..76fa3e8936b 100644 --- a/mirrord/layer/tests/mkdir.rs +++ b/mirrord/layer/tests/mkdir.rs @@ -17,10 +17,10 @@ async fn mkdir(dylib_path: &Path) { .start_process_with_layer(dylib_path, Default::default(), None) .await; - println!("waiting for file request."); + println!("waiting for MakeDirRequest."); intproxy.expect_make_dir("/mkdir_test_path", 0o777).await; - println!("waiting for file request."); + println!("waiting for MakeDirRequest."); intproxy.expect_make_dir("/mkdirat_test_path", 0o777).await; assert_eq!(intproxy.try_recv().await, None); diff --git a/mirrord/layer/tests/rmdir.rs b/mirrord/layer/tests/rmdir.rs new file mode 100644 index 00000000000..910e3d18ff9 --- /dev/null +++ b/mirrord/layer/tests/rmdir.rs @@ -0,0 +1,31 @@ +#![feature(assert_matches)] +use std::{path::Path, time::Duration}; + +use rstest::rstest; + +mod common; +pub use common::*; + +/// Test for the [`libc::rmdir`] function. +#[rstest] +#[tokio::test] +#[timeout(Duration::from_secs(60))] +async fn rmdir(dylib_path: &Path) { + let application = Application::RemoveDir; + + let (mut test_process, mut intproxy) = application + .start_process_with_layer(dylib_path, Default::default(), None) + .await; + + println!("waiting for MakeDirRequest."); + intproxy.expect_make_dir("/test_dir", 0o777).await; + + println!("waiting for RemoveDirRequest."); + intproxy.expect_remove_dir("/test_dir").await; + + assert_eq!(intproxy.try_recv().await, None); + + test_process.wait_assert_success().await; + test_process.assert_no_error_in_stderr().await; + test_process.assert_no_error_in_stdout().await; +} diff --git a/mirrord/protocol/Cargo.toml b/mirrord/protocol/Cargo.toml index e6c9980c15b..fd33bba8e6b 100644 --- a/mirrord/protocol/Cargo.toml +++ b/mirrord/protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mirrord-protocol" -version = "1.13.4" +version = "1.14.0" authors.workspace = true description.workspace = true documentation.workspace = true diff --git a/mirrord/protocol/src/codec.rs b/mirrord/protocol/src/codec.rs index 81e79bf5db7..1b0975d350d 100644 --- a/mirrord/protocol/src/codec.rs +++ b/mirrord/protocol/src/codec.rs @@ -91,6 +91,9 @@ pub enum FileRequest { ReadDirBatch(ReadDirBatchRequest), MakeDir(MakeDirRequest), MakeDirAt(MakeDirAtRequest), + RemoveDir(RemoveDirRequest), + Unlink(UnlinkRequest), + UnlinkAt(UnlinkAtRequest), } /// Minimal mirrord-protocol version that allows `ClientMessage::ReadyForLogs` message. @@ -136,6 +139,8 @@ pub enum FileResponse { ReadLink(RemoteResult), ReadDirBatch(RemoteResult), MakeDir(RemoteResult<()>), + RemoveDir(RemoteResult<()>), + Unlink(RemoteResult<()>), } /// `-agent` --> `-layer` messages. diff --git a/mirrord/protocol/src/file.rs b/mirrord/protocol/src/file.rs index c0b4cfe2f18..9a8622731fb 100644 --- a/mirrord/protocol/src/file.rs +++ b/mirrord/protocol/src/file.rs @@ -22,9 +22,15 @@ pub static READLINK_VERSION: LazyLock = pub static READDIR_BATCH_VERSION: LazyLock = LazyLock::new(|| ">=1.9.0".parse().expect("Bad Identifier")); +/// Minimal mirrord-protocol version that allows [`MakeDirRequest`] and [`MakeDirAtRequest`]. pub static MKDIR_VERSION: LazyLock = LazyLock::new(|| ">=1.13.0".parse().expect("Bad Identifier")); +/// Minimal mirrord-protocol version that allows [`RemoveDirRequest`], [`UnlinkRequest`] and +/// [`UnlinkAtRequest`].. +pub static RMDIR_VERSION: LazyLock = + LazyLock::new(|| ">=1.14.0".parse().expect("Bad Identifier")); + pub static OPEN_LOCAL_VERSION: LazyLock = LazyLock::new(|| ">=1.13.3".parse().expect("Bad Identifier")); @@ -285,6 +291,23 @@ pub struct MakeDirAtRequest { pub mode: u32, } +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)] +pub struct RemoveDirRequest { + pub pathname: PathBuf, +} + +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)] +pub struct UnlinkRequest { + pub pathname: PathBuf, +} + +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)] +pub struct UnlinkAtRequest { + pub dirfd: Option, + pub pathname: PathBuf, + pub flags: u32, +} + #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)] pub struct ReadLimitedFileRequest { pub remote_fd: u64, diff --git a/tests/go-e2e-dir/main.go b/tests/go-e2e-dir/main.go index 80006ce58c5..4a505a4dcc8 100644 --- a/tests/go-e2e-dir/main.go +++ b/tests/go-e2e-dir/main.go @@ -37,6 +37,12 @@ func main() { os.Exit(-1) } + err = os.Remove("/app/test_mkdir") + if err != nil { + fmt.Printf("Rmdir error: %s\n", err) + os.Exit(-1) + } + // let close requests be sent for test time.Sleep(1 * time.Second) os.Exit(0) diff --git a/tests/python-e2e/ops.py b/tests/python-e2e/ops.py index 9f94425921e..c107ebb2375 100644 --- a/tests/python-e2e/ops.py +++ b/tests/python-e2e/ops.py @@ -87,7 +87,16 @@ def test_mkdir_errors(self): os.mkdir("test_mkdir_error_already_exists", dir_fd=dir) os.close(dir) - + + def test_rmdir(self): + """ + Creates a new directory in "/tmp" and removes it using rmdir. + """ + os.mkdir("/tmp/test_rmdir") + self.assertTrue(os.path.isdir("/tmp/test_rmdir")) + os.rmdir("/tmp/test_rmdir") + self.assertFalse(os.path.isdir("/tmp/test_rmdir")) + def _create_new_tmp_file(self): """ Creates a new file in /tmp and returns the path and name of the file.