lcpfs 2026.1.102

LCP File System - A ZFS-inspired copy-on-write filesystem for Rust
// Copyright 2025 LunaOS Contributors
// SPDX-License-Identifier: Apache-2.0

//! # Virtual Device Manager (VDEV)
//!
//! This module manages the mapping between logical pool addresses and
//! physical storage devices, enabling dynamic pool expansion.
//!
//! ## Overview
//!
//! VDEVs abstract physical storage devices into a unified address space.
//! Multiple disks can be added to a pool, with LCPFS automatically
//! managing the logical-to-physical address translation.
//!
//! ## Address Translation
//!
//! ```text
//! Pool Topology:
//!   Logical 0x0000_0000 - 0x3FFF_FFFF --> VDEV 0 (16 GB SSD)
//!   Logical 0x4000_0000 - 0x7FFF_FFFF --> VDEV 1 (16 GB SSD)
//!   Logical 0x8000_0000 - 0xBFFF_FFFF --> VDEV 2 (16 GB NVMe)
//! ```
//!
//! ## Dynamic Expansion
//!
//! Use `add_vdev()` to grow the pool at runtime:
//! 1. New device registered with unique offset
//! 2. Metaslab allocator notified of expanded capacity
//! 3. New space immediately available for allocation
//!
//! ## Usage
//!
//! ```rust,ignore
//! use lcpfs::lcpfs_vdev::POOL_TOPOLOGY;
//!
//! // Add a new disk to the pool
//! POOL_TOPOLOGY.lock().add_vdev(device_id)?;
//! ```

use crate::util::alloc::METASLAB;
use crate::{BlockDevice, get_block_device};
use alloc::boxed::Box;
use alloc::vec::Vec;
use lazy_static::lazy_static;
use spin::Mutex;

/// Virtual device representing a physical disk in the pool
pub struct Vdev {
    /// Device identifier
    pub id: usize,
    /// Block device interface
    pub device: Box<dyn BlockDevice + Send>,
    /// Logical start offset in pool address space
    pub offset_start: u64, // Logical start in the pool address space
    /// Size of this vdev in bytes
    pub size: u64,
}

/// Pool topology managing multiple vdevs
pub struct PoolTopology {
    /// List of virtual devices
    pub vdevs: Vec<Vdev>,
    /// Total pool size in bytes
    pub total_size: u64,
}

lazy_static! {
    /// Global pool topology instance
    pub static ref POOL_TOPOLOGY: Mutex<PoolTopology> =
        Mutex::new(PoolTopology::new());
}

impl Default for PoolTopology {
    fn default() -> Self {
        Self::new()
    }
}

impl PoolTopology {
    /// Create a new empty pool topology
    pub const fn new() -> Self {
        Self {
            vdevs: Vec::new(),
            total_size: 0,
        }
    }

    /// Reset the topology for testing purposes.
    ///
    /// Clears all vdevs and resets total_size to 0.
    #[cfg(test)]
    pub fn reset_for_testing(&mut self) {
        self.vdevs.clear();
        self.total_size = 0;
    }

    /// Adds a physical device to the pool (Dynamic Expansion).
    pub fn add_vdev(&mut self, dev_id: usize) -> Result<(), &'static str> {
        let dev = get_block_device(dev_id).ok_or("Device not found")?;
        let size = dev.size().unwrap_or(0);

        if size == 0 {
            return Err("Empty Device");
        }

        let vdev = Vdev {
            id: dev_id,
            device: dev,
            offset_start: self.total_size,
            size,
        };

        crate::lcpfs_println!(
            "[ TOPO ] Added VDEV {} ({} GB) at logical offset 0x{:x}",
            dev_id,
            size / 1024 / 1024 / 1024,
            self.total_size
        );

        self.vdevs.push(vdev);
        self.total_size += size;

        // Automatically notify allocator of new space
        let mut alloc = METASLAB.lock();
        alloc.grow_pool(self.total_size);

        Ok(())
    }

    /// Translates a Logical DVA Offset -> Physical Device + Offset.
    pub fn translate(
        &mut self,
        logical_offset: u64,
    ) -> Option<(&mut Box<dyn BlockDevice + Send>, u64)> {
        // Binary search could be faster, but linear scan is fine for < 100 disks.
        for vdev in &mut self.vdevs {
            if logical_offset >= vdev.offset_start
                && logical_offset < (vdev.offset_start + vdev.size)
            {
                let phys_offset = logical_offset - vdev.offset_start;
                return Some((&mut vdev.device, phys_offset));
            }
        }
        None
    }
}