Skip to content

Commit

Permalink
Add statfs support (#3018)
Browse files Browse the repository at this point in the history
* Add statfs hook

* + changelog.d

* Fix test statfs_fstatfs.rs

* Fix statfs_fstatfs.c

* Fix statfs_fstatfs.c

* Fix statfs_fstatfs.rs

* Fix statfs_fstatfs.rs

* Fix statfs_fstatfs.rs

* Update file.rs

* PR comments
  • Loading branch information
facundopoblete authored Jan 23, 2025
1 parent 57bdab5 commit 0aa4f40
Show file tree
Hide file tree
Showing 18 changed files with 265 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions changelog.d/statfs.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add statfs support
20 changes: 18 additions & 2 deletions mirrord/agent/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,12 @@ impl FileManager {
Some(FileResponse::Xstat(xstat_result))
}
FileRequest::XstatFs(XstatFsRequest { fd }) => {
let xstat_result = self.xstatfs(fd);
Some(FileResponse::XstatFs(xstat_result))
let xstatfs_result = self.xstatfs(fd);
Some(FileResponse::XstatFs(xstatfs_result))
}
FileRequest::StatFs(StatFsRequest { path }) => {
let statfs_result = self.statfs(path);
Some(FileResponse::XstatFs(statfs_result))
}

// dir operations
Expand Down Expand Up @@ -769,6 +773,18 @@ impl FileManager {
})
}

#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn statfs(&mut self, path: PathBuf) -> RemoteResult<XstatFsResponse> {
let path = resolve_path(path, &self.root_path)?;

let statfs = nix::sys::statfs::statfs(&path)
.map_err(|err| std::io::Error::from_raw_os_error(err as i32))?;

Ok(XstatFsResponse {
metadata: statfs.into(),
})
}

#[tracing::instrument(level = Level::TRACE, skip(self), err(level = Level::DEBUG))]
pub(crate) fn fdopen_dir(&mut self, fd: u64) -> RemoteResult<OpenDirResponse> {
let path = match self
Expand Down
7 changes: 7 additions & 0 deletions mirrord/intproxy/protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,13 @@ impl_request!(
res_path = ProxyToLayerMessage::File => FileResponse::XstatFs,
);

impl_request!(
req = StatFsRequest,
res = RemoteResult<XstatFsResponse>,
req_path = LayerToProxyMessage::File => FileRequest::StatFs,
res_path = ProxyToLayerMessage::File => FileResponse::XstatFs,
);

impl_request!(
req = FdOpenDirRequest,
res = RemoteResult<OpenDirResponse>,
Expand Down
14 changes: 10 additions & 4 deletions mirrord/intproxy/src/proxies/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use mirrord_protocol::{
file::{
CloseDirRequest, CloseFileRequest, DirEntryInternal, ReadDirBatchRequest, ReadDirResponse,
ReadFileResponse, ReadLimitedFileRequest, SeekFromInternal, MKDIR_VERSION,
READDIR_BATCH_VERSION, READLINK_VERSION, RMDIR_VERSION,
READDIR_BATCH_VERSION, READLINK_VERSION, RMDIR_VERSION, STATFS_VERSION,
},
ClientMessage, DaemonMessage, ErrorKindInternal, FileRequest, FileResponse, RemoteIOError,
ResponseError,
Expand Down Expand Up @@ -259,21 +259,27 @@ impl FilesProxy {

match request {
FileRequest::ReadLink(..)
if protocol_version.is_some_and(|version| !READLINK_VERSION.matches(version)) =>
if protocol_version.is_none_or(|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)) =>
if protocol_version.is_none_or(|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)) =>
.is_none_or(|version: &Version| !RMDIR_VERSION.matches(version)) =>
{
Err(FileResponse::RemoveDir(Err(ResponseError::NotImplemented)))
}
FileRequest::StatFs(..)
if protocol_version
.is_none_or(|version: &Version| !STATFS_VERSION.matches(version)) =>
{
Err(FileResponse::XstatFs(Err(ResponseError::NotImplemented)))
}
_ => Ok(()),
}
}
Expand Down
31 changes: 30 additions & 1 deletion mirrord/layer/src/file/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -904,8 +904,9 @@ unsafe extern "C" fn fstatat_detour(
})
}

/// Hook for `libc::fstatfs`.
#[hook_guard_fn]
unsafe extern "C" fn fstatfs_detour(fd: c_int, out_stat: *mut statfs) -> c_int {
pub(crate) unsafe extern "C" fn fstatfs_detour(fd: c_int, out_stat: *mut statfs) -> c_int {
if out_stat.is_null() {
return HookError::BadPointer.into();
}
Expand All @@ -919,6 +920,25 @@ unsafe extern "C" fn fstatfs_detour(fd: c_int, out_stat: *mut statfs) -> c_int {
.unwrap_or_bypass_with(|_| FN_FSTATFS(fd, out_stat))
}

/// Hook for `libc::statfs`.
#[hook_guard_fn]
pub(crate) unsafe extern "C" fn statfs_detour(
raw_path: *const c_char,
out_stat: *mut statfs,
) -> c_int {
if out_stat.is_null() {
return HookError::BadPointer.into();
}

crate::file::ops::statfs(raw_path.checked_into())
.map(|res| {
let res = res.metadata;
fill_statfs(out_stat, &res);
0
})
.unwrap_or_bypass_with(|_| FN_STATFS(raw_path, out_stat))
}

unsafe fn realpath_logic(
source_path: *const c_char,
output_path: *mut c_char,
Expand Down Expand Up @@ -1333,6 +1353,8 @@ pub(crate) unsafe fn enable_file_hooks(hook_manager: &mut HookManager) {
FnFstatfs,
FN_FSTATFS
);
replace!(hook_manager, "statfs", statfs_detour, FnStatfs, FN_STATFS);

replace!(
hook_manager,
"fdopendir",
Expand Down Expand Up @@ -1415,6 +1437,13 @@ pub(crate) unsafe fn enable_file_hooks(hook_manager: &mut HookManager) {
FnFstatfs,
FN_FSTATFS
);
replace!(
hook_manager,
"statfs$INODE64",
statfs_detour,
FnStatfs,
FN_STATFS
);
replace!(
hook_manager,
"fdopendir$INODE64",
Expand Down
13 changes: 12 additions & 1 deletion mirrord/layer/src/file/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use mirrord_protocol::{
file::{
MakeDirAtRequest, MakeDirRequest, OpenFileRequest, OpenFileResponse, OpenOptionsInternal,
ReadFileResponse, ReadLinkFileRequest, ReadLinkFileResponse, RemoveDirRequest,
SeekFileResponse, UnlinkAtRequest, WriteFileResponse, XstatFsResponse, XstatResponse,
SeekFileResponse, StatFsRequest, UnlinkAtRequest, WriteFileResponse, XstatFsResponse,
XstatResponse,
},
ResponseError,
};
Expand Down Expand Up @@ -736,6 +737,16 @@ pub(crate) fn xstatfs(fd: RawFd) -> Detour<XstatFsResponse> {
Detour::Success(response)
}

#[mirrord_layer_macro::instrument(level = "trace")]
pub(crate) fn statfs(path: Detour<PathBuf>) -> Detour<XstatFsResponse> {
let path = path?;
let lstatfs = StatFsRequest { path };

let response = common::make_proxy_request_with_response(lstatfs)??;

Detour::Success(response)
}

#[cfg(target_os = "linux")]
#[mirrord_layer_macro::instrument(level = "trace")]
pub(crate) fn getdents64(fd: RawFd, buffer_size: u64) -> Detour<GetDEnts64Response> {
Expand Down
2 changes: 2 additions & 0 deletions mirrord/layer/src/go/linux_x64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ unsafe extern "C" fn c_abi_syscall_handler(
faccessat_detour(param1 as _, param2 as _, param3 as _, 0) as i64
}
libc::SYS_fstat => fstat_detour(param1 as _, param2 as _) as i64,
libc::SYS_statfs => statfs_detour(param1 as _, param2 as _) as i64,
libc::SYS_fstatfs => fstatfs_detour(param1 as _, param2 as _) as i64,
libc::SYS_getdents64 => getdents64_detour(param1 as _, param2 as _, param3 as _) as i64,
#[cfg(all(target_os = "linux", not(target_arch = "aarch64")))]
libc::SYS_mkdir => mkdir_detour(param1 as _, param2 as _) as i64,
Expand Down
2 changes: 2 additions & 0 deletions mirrord/layer/src/go/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ unsafe extern "C" fn c_abi_syscall6_handler(
.into()
}
libc::SYS_fstat => fstat_detour(param1 as _, param2 as _) as i64,
libc::SYS_statfs => statfs_detour(param1 as _, param2 as _) as i64,
libc::SYS_fstatfs => fstatfs_detour(param1 as _, param2 as _) as i64,
libc::SYS_fsync => fsync_detour(param1 as _) as i64,
libc::SYS_fdatasync => fsync_detour(param1 as _) as i64,
libc::SYS_openat => {
Expand Down
13 changes: 12 additions & 1 deletion mirrord/layer/tests/apps/fileops/go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,21 @@ import (

func main() {
tempFile := "/tmp/test_file.txt"
syscall.Open(tempFile, syscall.O_CREAT|syscall.O_WRONLY, 0644)
fd, _ := syscall.Open(tempFile, syscall.O_CREAT|syscall.O_WRONLY, 0644)
var stat syscall.Stat_t
err := syscall.Stat(tempFile, &stat)
if err != nil {
panic(err)
}

var statfs syscall.Statfs_t
err = syscall.Statfs(tempFile, &statfs)
if err != nil {
panic(err)
}

err = syscall.Fstatfs(fd, &statfs)
if err != nil {
panic(err)
}
}
52 changes: 52 additions & 0 deletions mirrord/layer/tests/apps/statfs_fstatfs/statfs_fstatfs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/fcntl.h>

#if defined(__APPLE__) && defined(__MACH__)
#include <sys/param.h>
#include <sys/mount.h>
#else
#include <sys/vfs.h>
#endif

/// Test `statfs / fstatfs`.
///
/// Gets information about a mounted filesystem
///
int main()
{
char *tmp_test_path = "/statfs_fstatfs_test_path";
mkdir(tmp_test_path, 0777);

// statfs
struct statfs statfs_buf;
if (statfs(tmp_test_path, &statfs_buf) == -1)
{
perror("statfs failed");
return EXIT_FAILURE;
}

// fstatfs
int fd = open(tmp_test_path, O_RDONLY);

if (fd == -1)
{
perror("Error opening tmp_test_path");
return 1;
}

struct statfs fstatfs_buf;
if (fstatfs(fd, &fstatfs_buf) == -1)
{
perror("fstatfs failed");
close(fd);
return EXIT_FAILURE;
}

close(fd);
return 0;
}
48 changes: 47 additions & 1 deletion mirrord/layer/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use mirrord_intproxy::{agent_conn::AgentConnection, IntProxy};
use mirrord_protocol::{
file::{
AccessFileRequest, AccessFileResponse, OpenFileRequest, OpenOptionsInternal,
ReadFileRequest, SeekFromInternal, XstatRequest, XstatResponse,
ReadFileRequest, SeekFromInternal, XstatFsResponse, XstatRequest, XstatResponse,
},
tcp::{DaemonTcp, LayerTcp, NewTcpConnection, TcpClose, TcpData},
ClientMessage, DaemonCodec, DaemonMessage, FileRequest, FileResponse,
Expand Down Expand Up @@ -489,6 +489,48 @@ impl TestIntProxy {
.unwrap();
}

/// Makes a [`FileRequest::Statefs`] and answers it.
pub async fn expect_statfs(&mut self, expected_path: &str) {
// Expecting `statfs` call with path.
assert_matches!(
self.recv().await,
ClientMessage::FileRequest(FileRequest::StatFs(
mirrord_protocol::file::StatFsRequest { path }
)) if path.to_str().unwrap() == expected_path
);

// Answer `statfs`.
self.codec
.send(DaemonMessage::File(FileResponse::XstatFs(Ok(
XstatFsResponse {
metadata: Default::default(),
},
))))
.await
.unwrap();
}

/// Makes a [`FileRequest::Xstatefs`] and answers it.
pub async fn expect_fstatfs(&mut self, expected_fd: u64) {
// Expecting `fstatfs` call with path.
assert_matches!(
self.recv().await,
ClientMessage::FileRequest(FileRequest::XstatFs(
mirrord_protocol::file::XstatFsRequest { fd }
)) if expected_fd == fd
);

// Answer `fstatfs`.
self.codec
.send(DaemonMessage::File(FileResponse::XstatFs(Ok(
XstatFsResponse {
metadata: Default::default(),
},
))))
.await
.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.
Expand Down Expand Up @@ -784,6 +826,7 @@ pub enum Application {
Fork,
ReadLink,
MakeDir,
StatfsFstatfs,
RemoveDir,
OpenFile,
CIssue2055,
Expand Down Expand Up @@ -841,6 +884,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::StatfsFstatfs => String::from("tests/apps/statfs_fstatfs/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 => {
Expand Down Expand Up @@ -1080,6 +1124,7 @@ impl Application {
| Application::Fork
| Application::ReadLink
| Application::MakeDir
| Application::StatfsFstatfs
| Application::RemoveDir
| Application::Realpath
| Application::RustFileOps
Expand Down Expand Up @@ -1159,6 +1204,7 @@ impl Application {
| Application::Fork
| Application::ReadLink
| Application::MakeDir
| Application::StatfsFstatfs
| Application::RemoveDir
| Application::Realpath
| Application::Go21Issue834
Expand Down
3 changes: 3 additions & 0 deletions mirrord/layer/tests/fileops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,9 @@ async fn go_stat(
))))
.await;

intproxy.expect_statfs("/tmp/test_file.txt").await;
intproxy.expect_fstatfs(fd).await;

test_process.wait_assert_success().await;
test_process.assert_no_error_in_stderr().await;
}
Expand Down
Loading

0 comments on commit 0aa4f40

Please sign in to comment.