use clap::Parser;
use libfuse_fs::passthrough::{PassthroughArgs, new_passthroughfs_layer};
#[cfg(target_os = "macos")]
use libfuse_fs::passthrough::{PassthroughFs, config::Config};
use libfuse_fs::util::bind_mount::{BindMount, BindMountManager};
use rfuse3::raw::logfs::LoggingFileSystem;
use rfuse3::{MountOptions, raw::Session};
use std::ffi::OsString;
#[cfg(target_os = "macos")]
use std::path::PathBuf;
#[cfg(target_os = "macos")]
use std::time::Duration;
use tokio::signal;
use tracing::{debug, error, info};
#[derive(Parser, Debug)]
#[command(
author,
version,
about = "Passthrough FS example for integration tests"
)]
struct Args {
#[arg(long)]
mountpoint: String,
#[arg(long)]
rootdir: String,
#[arg(long, default_value_t = false)]
privileged: bool,
#[arg(long, short)]
options: Option<String>,
#[arg(long)]
allow_other: bool,
#[arg(long = "bind")]
bind_mounts: Vec<String>,
#[arg(long, default_value_t = false)]
macos_eager: bool,
}
fn set_log() {
let log_level = "trace";
let filter_str = format!("libfuse_fs={}", log_level);
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(filter_str));
tracing_subscriber::fmt().with_env_filter(filter).init();
}
#[tokio::main]
async fn main() {
let args = Args::parse();
set_log();
debug!("Starting passthrough filesystem with args: {:?}", args);
let bind_specs: Result<Vec<BindMount>, _> = args
.bind_mounts
.iter()
.map(|s| BindMount::parse(s))
.collect();
let bind_specs = match bind_specs {
Ok(specs) => specs,
Err(e) => {
error!("Failed to parse bind mount specifications: {}", e);
std::process::exit(1);
}
};
let bind_manager = BindMountManager::new(&args.mountpoint);
#[cfg(target_os = "macos")]
let fs = if args.macos_eager {
debug!("macOS eager-fd mode (A/B benchmark)");
let mut cfg = Config {
root_dir: PathBuf::from(&args.rootdir),
xattr: true,
do_import: true,
macos_lazy_inode_fd: false,
..Default::default()
};
cfg.entry_timeout = Duration::ZERO;
cfg.attr_timeout = Duration::ZERO;
cfg.dir_entry_timeout = Some(Duration::ZERO);
cfg.dir_attr_timeout = Some(Duration::ZERO);
if let Some(m) = args.options.as_deref() {
cfg.mapping = m.parse().unwrap_or_else(|e| {
error!("invalid mapping {m}: {e}; using empty mapping");
Default::default()
});
}
let fs = PassthroughFs::<()>::new(cfg).expect("Failed to init passthrough fs (eager)");
fs.import().await.expect("Failed to import root inode");
fs
} else {
new_passthroughfs_layer(PassthroughArgs {
root_dir: args.rootdir,
mapping: args.options,
})
.await
.expect("Failed to init passthrough fs")
};
#[cfg(not(target_os = "macos"))]
let fs = new_passthroughfs_layer(PassthroughArgs {
root_dir: args.rootdir,
mapping: args.options,
})
.await
.expect("Failed to init passthrough fs");
let fs = LoggingFileSystem::new(fs);
let mount_path = OsString::from(&args.mountpoint);
let uid = unsafe { libc::getuid() };
let gid = unsafe { libc::getgid() };
let mut mount_options = MountOptions::default();
#[cfg(target_os = "linux")]
mount_options.force_readdir_plus(true);
mount_options
.uid(uid)
.gid(gid)
.allow_other(args.allow_other);
let mut mount_handle = if !args.privileged {
debug!("Mounting passthrough (unprivileged)");
Session::new(mount_options)
.mount_with_unprivileged(fs, mount_path)
.await
.expect("Unprivileged mount failed")
} else {
debug!("Mounting passthrough (privileged)");
Session::new(mount_options)
.mount(fs, mount_path)
.await
.expect("Privileged mount failed")
};
if !bind_specs.is_empty() {
info!("Mounting {} bind mount(s)", bind_specs.len());
if let Err(e) = bind_manager.mount_all(&bind_specs).await {
error!("Failed to mount bind mounts: {}", e);
mount_handle.unmount().await.unwrap();
std::process::exit(1);
}
}
tokio::select! {
res = &mut mount_handle => {
if let Err(e) = res {
error!("Passthrough filesystem error: {:?}", e);
}
info!("Cleaning up...");
if let Err(e) = bind_manager.unmount_all().await {
error!("Failed to unmount bind mounts: {}", e);
}
},
_ = signal::ctrl_c() => {
info!("Received SIGINT signal, cleaning up...");
if let Err(e) = bind_manager.unmount_all().await {
error!("Failed to unmount bind mounts: {}", e);
}
if let Err(e) = mount_handle.unmount().await {
error!("Failed to unmount passthrough filesystem: {}", e);
}
}
_ = async {
use tokio::signal::unix::{signal, SignalKind};
let mut term = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM handler");
term.recv().await
} => {
info!("Received SIGTERM signal, cleaning up...");
if let Err(e) = bind_manager.unmount_all().await {
error!("Failed to unmount bind mounts: {}", e);
}
if let Err(e) = mount_handle.unmount().await {
error!("Failed to unmount passthrough filesystem: {}", e);
}
}
}
}