lcpfs 2026.1.102

LCP File System - A ZFS-inspired copy-on-write filesystem for Rust
//! Command-line interface for LCPFS FUSE mount.

use crate::fuse::LcpfsFuse;
use crate::{Pool, register_device};
use std::path::Path;

/// Mount options for LCPFS FUSE.
#[derive(Debug, Clone)]
pub struct MountOptions {
    /// Allow other users to access the mount.
    pub allow_other: bool,
    /// Allow root to access the mount.
    pub allow_root: bool,
    /// Run in foreground (don't daemonize).
    pub foreground: bool,
    /// Enable debug output.
    pub debug: bool,
    /// Read-only mount.
    pub readonly: bool,
}

impl Default for MountOptions {
    fn default() -> Self {
        Self {
            allow_other: false,
            allow_root: false,
            foreground: false,
            debug: false,
            readonly: false,
        }
    }
}

/// Mount an LCPFS pool at the specified mountpoint.
///
/// # Arguments
///
/// * `device` - Path to block device (e.g., "/dev/sda1")
/// * `mountpoint` - Directory to mount at (e.g., "/mnt/data")
/// * `options` - Mount options
///
/// # Errors
///
/// Returns an error if:
/// - Device cannot be opened
/// - Pool import fails
/// - Mount fails
pub fn mount(device: &str, mountpoint: &str, options: MountOptions) -> Result<(), String> {
    // Open the block device
    let file = std::fs::OpenOptions::new()
        .read(true)
        .write(!options.readonly)
        .open(device)
        .map_err(|e| format!("Failed to open device {}: {}", device, e))?;

    // Create a block device wrapper
    let block_dev = FileBlockDevice::new(file);
    let dev_id = register_device(Box::new(block_dev));

    // Import the pool
    let pool = Pool::import(dev_id).map_err(|e| format!("Failed to import pool: {:?}", e))?;

    // Create FUSE adapter
    let fs = LcpfsFuse::new(pool);

    // Build FUSE mount options
    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);
    }

    // Ensure mountpoint exists
    if !Path::new(mountpoint).exists() {
        return Err(format!("Mountpoint does not exist: {}", mountpoint));
    }

    eprintln!("Mounting LCPFS from {} at {}", device, mountpoint);

    // Mount the filesystem
    fuser::mount2(fs, mountpoint, &fuse_options)
        .map_err(|e| format!("FUSE mount failed: {}", e))?;

    Ok(())
}

/// Block device wrapper for std::fs::File.
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};

        // Get file size and calculate block count
        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
    }
}

/// Parse command line arguments and run mount.
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();

    // Parse options
    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)
}