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
//
// Pool Formatting
// Creates valid pool structure and hyperblock persistence.

use crate::fscore::structs::{Hyperblock, UBERBLOCK_SIZE, VDEV_LABEL_SIZE};
use crate::storage::vdev::POOL_TOPOLOGY;
use crate::util::alloc::METASLAB;
use crate::{BLOCK_DEVICES, BlockDevice};
use alloc::boxed::Box;
use alloc::vec;

const SECTOR_SIZE: usize = 512;

/// LCPFS Pool Formatter
pub struct LcpfsFormatter;

impl LcpfsFormatter {
    /// Format a drive with LCPFS pool structure.
    /// Creates valid VDEV labels at L0 and L1 positions with initial hyperblock.
    pub fn format_drive(dev_id: usize, pool_name: &str) -> Result<(), &'static str> {
        crate::lcpfs_println!(
            "[ LCPFS ] Formatting device {} as pool '{}'...",
            dev_id,
            pool_name
        );

        // Build the VDEV label (256KB)
        let label = Self::build_vdev_label(pool_name)?;

        // Write to device
        {
            let mut devices = BLOCK_DEVICES.lock();
            let dev = devices.get_mut(dev_id).ok_or("Device not found")?;

            let total_size = dev.size().unwrap_or(0);
            if total_size < (VDEV_LABEL_SIZE * 2) as u64 {
                return Err("Drive too small for LCPFS format");
            }

            // Write L0 at start of disk (sector 0)
            crate::lcpfs_println!("[ LCPFS ] Writing L0 label at sector 0...");
            Self::write_label_to_device(dev, 0, &label)?;

            // Write L1 after L0 (sector 512 = 256KB / 512)
            let l1_sector = VDEV_LABEL_SIZE / SECTOR_SIZE;
            crate::lcpfs_println!("[ LCPFS ] Writing L1 label at sector {}...", l1_sector);
            Self::write_label_to_device(dev, l1_sector, &label)?;
        }

        // Initialize the allocator with disk size
        let disk_size = {
            let devices = BLOCK_DEVICES.lock();
            devices
                .get(dev_id)
                .map(|d| d.size().unwrap_or(0))
                .unwrap_or(0)
        };

        if disk_size > 0 {
            // Initialize METASLAB (allocator)
            METASLAB.lock().init(disk_size);
            crate::lcpfs_println!(
                "[ LCPFS ] Allocator initialized ({} MB)",
                disk_size / 1024 / 1024
            );

            // Add device to pool topology
            if let Err(e) = POOL_TOPOLOGY.lock().add_vdev(dev_id) {
                crate::lcpfs_println!("[ LCPFS ] Warning: Could not add vdev to topology: {}", e);
            }
        }

        crate::lcpfs_println!(
            "[ LCPFS ] Format complete. Pool '{}' created with TXG=1.",
            pool_name
        );
        Ok(())
    }

    /// Build a complete VDEV label with valid hyperblock at slot 0.
    fn build_vdev_label(pool_name: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
        let mut label = vec![0u8; VDEV_LABEL_SIZE];

        // VDEV Label Layout (256KB total):
        // [0..8KB]       = Blank space
        // [8KB..16KB]    = Boot header
        // [16KB..128KB]  = Name-value pairs (112KB)
        // [128KB..256KB] = Hyperblock ring buffer (128 x 1KB slots)

        // 1. Write boot header magic at offset 8KB
        let boot_offset = 8 * 1024;
        label[boot_offset..boot_offset + 8].copy_from_slice(b"LCPFBOOT");

        // 2. Write pool name in NV pairs area at offset 16KB
        let nv_offset = 16 * 1024;
        let name_bytes = pool_name.as_bytes();
        let copy_len = name_bytes.len().min(64);
        label[nv_offset..nv_offset + copy_len].copy_from_slice(&name_bytes[..copy_len]);

        // 3. Write initial hyperblock at slot 0 (offset 128KB)
        let hyperblock_offset = 128 * 1024;
        let hb = Hyperblock::new(1); // TXG = 1 (first transaction group)

        // Serialize hyperblock to bytes
        // SAFETY INVARIANTS:
        // 1. Hyperblock is #[repr(C)] with stable layout
        // 2. All fields are primitive types (u64, u32, arrays - no Drop)
        // 3. hb is valid, initialized Hyperblock on stack
        // 4. Slice lifetime ≤ hb lifetime (does not escape function)
        // 5. size_of::<Hyperblock>() matches actual struct size
        //
        // VERIFICATION: TODO - Prove Hyperblock matches on-disk format spec
        //
        // JUSTIFICATION:
        // Pool format requires Hyperblock in each uberblock slot.
        // Binary serialization for on-disk uberblock array (256 copies).
        let hb_bytes = unsafe {
            core::slice::from_raw_parts(
                &hb as *const Hyperblock as *const u8,
                core::mem::size_of::<Hyperblock>(),
            )
        };
        label[hyperblock_offset..hyperblock_offset + hb_bytes.len()].copy_from_slice(hb_bytes);

        Ok(label)
    }

    /// Write a 256KB label to device starting at given sector.
    fn write_label_to_device(
        dev: &mut Box<dyn BlockDevice + Send>,
        start_sector: usize,
        label: &[u8],
    ) -> Result<(), &'static str> {
        let sectors_to_write = VDEV_LABEL_SIZE / SECTOR_SIZE;

        for i in 0..sectors_to_write {
            let offset = i * SECTOR_SIZE;
            let sector_data = &label[offset..offset + SECTOR_SIZE];
            dev.write_block(start_sector + i, sector_data)?;
        }

        Ok(())
    }

    /// Write updated hyperblock to disk (for persistence after file operations).
    /// Uses rotating ring buffer - writes to slot (txg % 128).
    pub fn write_hyperblock(dev_id: usize, hb: &Hyperblock) -> Result<(), &'static str> {
        let mut devices = BLOCK_DEVICES.lock();
        let dev = devices.get_mut(dev_id).ok_or("Device not found")?;

        // Calculate slot in ring buffer (TXG mod 128)
        let slot = (hb.txg % 128) as usize;

        // Hyperblock ring starts at 128KB into the label
        // Each slot is 1KB (UBERBLOCK_SIZE)
        let label_offset = 128 * 1024 + (slot * UBERBLOCK_SIZE);

        // Convert to sector number (512-byte sectors)
        let start_sector = label_offset / SECTOR_SIZE;

        // Serialize hyperblock
        // SAFETY INVARIANTS:
        // 1. Hyperblock is #[repr(C)] with stable layout
        // 2. All fields are primitive types (no pointers, no Drop)
        // 3. hb reference valid for function duration
        // 4. Slice lifetime ≤ hb reference lifetime
        // 5. size_of::<Hyperblock>() matches struct size
        //
        // VERIFICATION: TODO - Same as format_pool (Hyperblock stability)
        //
        // JUSTIFICATION:
        // Uberblock updates require serializing Hyperblock to specific disk slot.
        // Binary format for atomicity (sector-aligned write).
        let hb_bytes = unsafe {
            core::slice::from_raw_parts(
                hb as *const Hyperblock as *const u8,
                core::mem::size_of::<Hyperblock>(),
            )
        };

        // Write hyperblock (fits in 2 sectors since it's < 1KB, but slot is 1KB)
        let mut sector_buf = [0u8; SECTOR_SIZE];

        // First sector of the slot
        let copy_len = hb_bytes.len().min(SECTOR_SIZE);
        sector_buf[..copy_len].copy_from_slice(&hb_bytes[..copy_len]);
        dev.write_block(start_sector, &sector_buf)?;

        // Second sector if needed (hyperblock struct is ~168 bytes, so this is padding)
        sector_buf.fill(0);
        if hb_bytes.len() > SECTOR_SIZE {
            sector_buf[..hb_bytes.len() - SECTOR_SIZE].copy_from_slice(&hb_bytes[SECTOR_SIZE..]);
        }
        dev.write_block(start_sector + 1, &sector_buf)?;

        Ok(())
    }
}

/// Convenience function for shell command
pub fn format_pool(pool_name: &str) -> Result<(), &'static str> {
    LcpfsFormatter::format_drive(0, pool_name)
}