From 196f81d00ca43654f07d0e926d568735753271fa Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Wed, 25 Dec 2024 09:18:15 +0000 Subject: [PATCH] Implement cross-FS file rename --- lib/virtual-fs/src/mem_fs/filesystem.rs | 35 +++++++--- tests/wasix/cross-fs-rename/main.c | 89 +++++++++++++++++++++++++ tests/wasix/cross-fs-rename/run.sh | 6 ++ 3 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 tests/wasix/cross-fs-rename/main.c create mode 100755 tests/wasix/cross-fs-rename/run.sh diff --git a/lib/virtual-fs/src/mem_fs/filesystem.rs b/lib/virtual-fs/src/mem_fs/filesystem.rs index 65b34ce542f..0e32d0b3ece 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -447,7 +447,7 @@ impl crate::FileSystem for FileSystem { } fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> { - Box::pin(async { + Box::pin(async move { let name_of_to; // Read lock. @@ -493,6 +493,7 @@ impl crate::FileSystem for FileSystem { }; match (inode_of_from_parent, inode_of_to_parent) { + // Rename within this MemFS instance (Either::Left(inode_of_from_parent), Either::Left(inode_of_to_parent)) => { let fs = self.inner.read().map_err(|_| FsError::Lock)?; @@ -572,16 +573,34 @@ impl crate::FileSystem for FileSystem { Ok(()) } - (Either::Right((from_fs, from_path)), Either::Right((to_fs, to_path))) => { - if Arc::ptr_eq(&from_fs, &to_fs) { - let same_fs = from_fs; - same_fs.rename(&from_path, &to_path).await - } else { - Err(FsError::InvalidInput) + // Rename within the same mounted FS instance + (Either::Right((from_fs, from_path)), Either::Right((to_fs, to_path))) + if Arc::ptr_eq(&from_fs, &to_fs) => + { + let same_fs = from_fs; + same_fs.rename(&from_path, &to_path).await + } + + // Rename across file systems; we need to do a create and a delete + _ => { + let mut from_file = self.new_open_options().read(true).open(from)?; + let mut to_file = self + .new_open_options() + .create_new(true) + .write(true) + .open(to)?; + tokio::io::copy(from_file.as_mut(), to_file.as_mut()).await?; + if let Err(error) = self.remove_file(from) { + tracing::warn!( + ?from, + ?to, + ?error, + "Failed to remove file after cross-FS rename" + ); } + Ok(()) } - _ => Err(FsError::InvalidInput), } }) } diff --git a/tests/wasix/cross-fs-rename/main.c b/tests/wasix/cross-fs-rename/main.c new file mode 100644 index 00000000000..31f01f52ff8 --- /dev/null +++ b/tests/wasix/cross-fs-rename/main.c @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include + +void error_exit(const char *desc, const char *call) +{ + const char *err_desc = strerror(errno); + printf("Test \"%s\" failed at %s: %s\n", desc, call, err_desc); + // Make it visible in the output log as well + fprintf(stderr, "Test \"%s\" failed at %s: %s\n", desc, call, err_desc); + exit(-1); +} + +void create_and_move_file(const char *from_path, const char *to_path) +{ + char test_description[1024] = {0}; + sprintf(test_description, "%s -> %s", from_path, to_path); + + FILE *f = fopen(from_path, "wb"); + if (!f) + { + error_exit(test_description, "fopen"); + } + + char *txt = "hello"; + if (!fwrite(txt, 1, 6, f)) + { + error_exit(test_description, "fwrite"); + } + + if (fclose(f)) + { + error_exit(test_description, "fclose"); + } + + // /home is a host FS mount + if (rename(from_path, to_path)) + { + error_exit(test_description, "rename"); + } + + f = fopen(to_path, "rb"); + if (!f) + { + error_exit(test_description, "fopen 2"); + } + + char buffer[7] = {0}; + if (!fread(buffer, 1, 7, f)) + { + error_exit(test_description, "fread"); + } + + if (strcmp(buffer, txt)) + { + fprintf(stderr, "Expected %s to be equal to %s", buffer, txt); + exit(-1); + } + + if (fclose(f)) + { + error_exit(test_description, "fclose 2"); + } +} + +int main() +{ + // /tmp is on the MemFS, /temp1 and /temp2 are on separate HostFS instances + + // Move file within MemFS + create_and_move_file("/tmp/old", "/tmp/new"); + + // Move file within single mounted FS + create_and_move_file("/temp1/old", "/temp1/new"); + + // Move file from MemFS to mounted FS + create_and_move_file("/tmp/file", "/temp1/file"); + + // Move file from mounted FS to MemFS + create_and_move_file("/temp1/file2", "/tmp/file2"); + + // Move file between different mounted FS's + create_and_move_file("/temp1/file3", "/temp2/file3"); + + printf("0"); + return 0; +} diff --git a/tests/wasix/cross-fs-rename/run.sh b/tests/wasix/cross-fs-rename/run.sh new file mode 100755 index 00000000000..cc783761c49 --- /dev/null +++ b/tests/wasix/cross-fs-rename/run.sh @@ -0,0 +1,6 @@ +TEMP_DIR1=$(mktemp -d) +TEMP_DIR2=$(mktemp -d) + +$WASMER -q run main.wasm --mapdir /temp1:$TEMP_DIR1 --mapdir /temp2:$TEMP_DIR2 > output + +printf "0" | diff -u output - 1>/dev/null