use crate::fuse::LcpfsFuse;
use crate::{Pool, register_device};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct MountOptions {
pub allow_other: bool,
pub allow_root: bool,
pub foreground: bool,
pub debug: bool,
pub readonly: bool,
}
impl Default for MountOptions {
fn default() -> Self {
Self {
allow_other: false,
allow_root: false,
foreground: false,
debug: false,
readonly: false,
}
}
}
pub fn mount(device: &str, mountpoint: &str, options: MountOptions) -> Result<(), String> {
let file = std::fs::OpenOptions::new()
.read(true)
.write(!options.readonly)
.open(device)
.map_err(|e| format!("Failed to open device {}: {}", device, e))?;
let block_dev = FileBlockDevice::new(file);
let dev_id = register_device(Box::new(block_dev));
let pool = Pool::import(dev_id).map_err(|e| format!("Failed to import pool: {:?}", e))?;
let fs = LcpfsFuse::new(pool);
let mut fuse_options = vec![
fuser::MountOption::FSName("lcpfs".to_string()),
fuser::MountOption::Subtype("lcpfs".to_string()),
];
if options.allow_other {
fuse_options.push(fuser::MountOption::AllowOther);
}
if options.allow_root {
fuse_options.push(fuser::MountOption::AllowRoot);
}
if options.readonly {
fuse_options.push(fuser::MountOption::RO);
}
if !Path::new(mountpoint).exists() {
return Err(format!("Mountpoint does not exist: {}", mountpoint));
}
eprintln!("Mounting LCPFS from {} at {}", device, mountpoint);
fuser::mount2(fs, mountpoint, &fuse_options)
.map_err(|e| format!("FUSE mount failed: {}", e))?;
Ok(())
}
struct FileBlockDevice {
file: std::fs::File,
}
impl FileBlockDevice {
fn new(file: std::fs::File) -> Self {
Self { file }
}
}
impl crate::BlockDevice for FileBlockDevice {
fn read_block(&mut self, block: usize, buf: &mut [u8]) -> Result<(), &'static str> {
use std::io::{Read, Seek, SeekFrom};
let offset = block as u64 * 512;
self.file
.seek(SeekFrom::Start(offset))
.map_err(|_| "seek failed")?;
self.file.read_exact(buf).map_err(|_| "read failed")?;
Ok(())
}
fn write_block(&mut self, block: usize, buf: &[u8]) -> Result<(), &'static str> {
use std::io::{Seek, SeekFrom, Write};
let offset = block as u64 * 512;
self.file
.seek(SeekFrom::Start(offset))
.map_err(|_| "seek failed")?;
self.file.write_all(buf).map_err(|_| "write failed")?;
Ok(())
}
fn block_count(&self) -> usize {
use std::io::{Seek, SeekFrom};
let mut file = &self.file;
if let Ok(size) = file.seek(SeekFrom::End(0)) {
(size / 512) as usize
} else {
0
}
}
fn block_size(&self) -> usize {
512
}
}
pub fn main() -> Result<(), String> {
let args: Vec<String> = std::env::args().collect();
if args.len() < 3 {
eprintln!("Usage: {} <device> <mountpoint> [options]", args[0]);
eprintln!();
eprintln!("Options:");
eprintln!(" -f, --foreground Run in foreground");
eprintln!(" -d, --debug Enable debug output");
eprintln!(" -o allow_other Allow other users");
eprintln!(" -o allow_root Allow root access");
eprintln!(" -o ro Read-only mount");
eprintln!();
eprintln!("Example:");
eprintln!(" {} /dev/sda1 /mnt/data", args[0]);
eprintln!(" {} /dev/nvme0n1p2 /mnt/pool -f", args[0]);
return Err("Invalid arguments".to_string());
}
let device = &args[1];
let mountpoint = &args[2];
let mut options = MountOptions::default();
let mut i = 3;
while i < args.len() {
match args[i].as_str() {
"-f" | "--foreground" => options.foreground = true,
"-d" | "--debug" => options.debug = true,
"-o" => {
i += 1;
if i < args.len() {
match args[i].as_str() {
"allow_other" => options.allow_other = true,
"allow_root" => options.allow_root = true,
"ro" => options.readonly = true,
other => eprintln!("Unknown option: {}", other),
}
}
}
other => eprintln!("Unknown argument: {}", other),
}
i += 1;
}
mount(device, mountpoint, options)
}