#![cfg(any(feature = "serial", feature = "parallel"))]
#[cfg(all(feature = "parallel", not(feature = "serial")))]
use easy_fuser::fuse_parallel::prelude::*;
use easy_fuser::fuse_presets::DefaultFuseHandler;
use easy_fuser::fuse_presets::mirror_fs::*;
#[cfg(feature = "serial")]
use easy_fuser::fuse_serial::prelude::*;
use easy_fuser_macro::delegate_fs;
use std::fs::{self, File};
use std::io::Write;
use std::os::unix::fs::MetadataExt;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::time::Duration;
use tempfile::TempDir;
struct MyFs {
mirror_fs: MirrorFs,
default_fs: DefaultFuseHandler<PathBuf>,
}
impl FuseHandler for MyFs {
type TId = PathBuf;
delegate_fs! { mirror_fs, [ flush, fsync, lseek, read, release,
access, getattr, getxattr, listxattr, lookup, open, readdir, readlink,
]
}
delegate_fs! { mirror_fs, [ copy_file_range, fallocate, write,
create, mkdir, mknod, removexattr, rename, rmdir, setattr, setxattr, symlink, unlink
]
}
delegate_fs! {default_fs, [ bmap, forget, fsyncdir, getlk, ioctl, link, opendir, releasedir, setlk, statfs ]}
}
struct MyFsReadOnly {
mirror_fs: MirrorFsReadOnly,
default_fs: DefaultFuseHandler<PathBuf>,
}
impl FuseHandler for MyFsReadOnly {
type TId = PathBuf;
delegate_fs! { mirror_fs, [
flush, fsync, lseek, read, release,
access, getattr, getxattr, listxattr, lookup, open, readdir, readlink
]}
delegate_fs! { default_fs, [
copy_file_range, fallocate, write,
create, mkdir, mknod, removexattr, rename, rmdir, setattr, setxattr, symlink, unlink,
bmap, forget, fsyncdir, getlk, ioctl, link, opendir, releasedir, setlk, statfs
]}
}
static MOUNT_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn test_mirror_fs_operations() {
let _lock = MOUNT_LOCK.lock().unwrap();
let mount_dir = TempDir::new().unwrap();
let source_dir = TempDir::new().unwrap();
let mntpoint = mount_dir.path().to_path_buf();
let source_path = source_dir.path().to_path_buf();
let mntpoint_clone = mntpoint.clone();
let source_path_clone = source_path.clone();
let sentinel = source_path.join("sentinel.txt");
fs::write(&sentinel, "").unwrap();
let handle = std::thread::spawn(move || {
let fs = MyFs {
mirror_fs: MirrorFs::new(source_path_clone),
default_fs: DefaultFuseHandler::new(),
};
mount(fs, &mntpoint_clone, &[], Some(4)).unwrap();
});
let mnt_sentinel = mntpoint.join("sentinel.txt");
let mut mounted = false;
for _ in 0..100 {
if mnt_sentinel.exists() {
mounted = true;
break;
}
std::thread::sleep(Duration::from_millis(50));
}
assert!(mounted, "Mount timed out");
{
let test_file = mntpoint.join("test_file.txt");
File::create(&test_file).unwrap();
assert!(test_file.exists());
let test_dir = mntpoint.join("test_dir");
fs::create_dir(&test_dir).unwrap();
assert!(test_dir.exists());
let content = "Hello, MirrorFs!";
fs::write(&test_file, content).unwrap();
let read_content = fs::read_to_string(&test_file).unwrap();
assert_eq!(content, read_content);
fs::remove_file(&test_file).unwrap();
assert!(!test_file.exists());
fs::remove_dir(&test_dir).unwrap();
assert!(!test_dir.exists());
let new_file = mntpoint.join("attribute_test.txt");
File::create(&new_file).unwrap();
let metadata = fs::metadata(&new_file).unwrap();
println!("Initial permissions: {:o}", metadata.permissions().mode());
println!("Initial owner: {}:{}", metadata.uid(), metadata.gid());
let new_permissions = 0o644;
fs::set_permissions(&new_file, fs::Permissions::from_mode(new_permissions)).unwrap();
let updated_metadata = fs::metadata(&new_file).unwrap();
println!(
"Updated permissions: {:o}",
updated_metadata.permissions().mode()
);
assert_eq!(
updated_metadata.permissions().mode() & 0o777,
new_permissions
);
let mut file = fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(&new_file)
.unwrap();
file.write_all(b"Initial content").unwrap();
drop(file);
let initial_len = fs::metadata(&new_file).unwrap().len();
assert_eq!(initial_len, 15);
let truncate_len = 5;
let file = fs::OpenOptions::new().write(true).open(&new_file).unwrap();
file.set_len(truncate_len).unwrap();
drop(file);
let truncated_len = fs::metadata(&new_file).unwrap().len();
assert_eq!(truncated_len, truncate_len);
}
eprintln!("Unmounting filesystem...");
let mut unmounted = false;
for cmd_name in &["fusermount3", "fusermount", "umount"] {
let mut cmd = std::process::Command::new(cmd_name);
if cmd_name == &"umount" {
cmd.arg(&mntpoint);
} else {
cmd.arg("-u").arg(&mntpoint);
}
if let Ok(status) = cmd.status() {
if status.success() {
eprintln!("Unmounted successfully using {}", cmd_name);
unmounted = true;
break;
}
}
}
assert!(
unmounted,
"Failed to unmount using fusermount3, fusermount, or umount"
);
eprintln!("Joining thread...");
#[cfg(not(any(target_os = "freebsd")))]
handle.join().unwrap();
#[cfg(target_os = "freebsd")]
let _ = handle.join();
eprintln!("Thread joined successfully!");
drop(mntpoint);
}
#[test]
fn test_mirror_fs_readonly_operations() {
let _lock = MOUNT_LOCK.lock().unwrap();
let mount_dir = TempDir::new().unwrap();
let source_dir = TempDir::new().unwrap();
let mntpoint = mount_dir.path().to_path_buf();
let source_path = source_dir.path().to_path_buf();
let test_file_name = "readonly_test.txt";
let source_file = source_path.join(test_file_name);
fs::write(&source_file, "Read-only test content").unwrap();
let mntpoint_clone = mntpoint.clone();
let source_path_clone = source_path.clone();
let handle = std::thread::spawn(move || {
let fs = MyFsReadOnly {
mirror_fs: MirrorFsReadOnly::new(source_path_clone),
default_fs: DefaultFuseHandler::new(),
};
mount(fs, &mntpoint_clone, &[], Some(4)).unwrap();
});
let mnt_file = mntpoint.join(test_file_name);
let mut mounted = false;
for _ in 0..100 {
if mnt_file.exists() {
mounted = true;
break;
}
std::thread::sleep(Duration::from_millis(50));
}
assert!(mounted, "Mount timed out");
{
let mnt_file = mntpoint.join(test_file_name);
assert!(mnt_file.exists());
let content = fs::read_to_string(&mnt_file).unwrap();
assert_eq!(content, "Read-only test content");
let new_file = mntpoint.join("new_file.txt");
let write_result = fs::write(&new_file, "This should fail");
assert!(write_result.is_err());
let err = write_result.unwrap_err();
assert_eq!(err.raw_os_error(), Some(libc::ENOSYS));
}
eprintln!("Unmounting filesystem...");
let mut unmounted = false;
for cmd_name in &["fusermount3", "fusermount", "umount"] {
let mut cmd = std::process::Command::new(cmd_name);
if cmd_name == &"umount" {
cmd.arg(&mntpoint);
} else {
cmd.arg("-u").arg(&mntpoint);
}
if let Ok(status) = cmd.status() {
if status.success() {
eprintln!("Unmounted successfully using {}", cmd_name);
unmounted = true;
break;
}
}
}
assert!(
unmounted,
"Failed to unmount using fusermount3, fusermount, or umount"
);
eprintln!("Joining thread...");
#[cfg(not(any(target_os = "freebsd")))]
handle.join().unwrap();
#[cfg(target_os = "freebsd")]
let _ = handle.join();
eprintln!("Thread joined successfully!");
drop(mntpoint);
}