use crate::fscore::structs::Blkptr;
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use lazy_static::lazy_static;
use spin::Mutex;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct DatasetPhys {
pub guid: u64,
pub parent_dataset_obj: u64,
pub parent_dataset_txg: u64,
pub root_dnode_bp: Blkptr,
pub root_dir_obj: u64,
pub snapshot_list_obj: u64,
pub quota: u64, pub reservation: u64, pub compression: u8, pub checksum: u8, pub readonly: u8, pub dedup: u8, pub copies: u8, pub encryption: u8, pub txg_birth: u64, pub used_bytes: u64, pub pad: [u64; 4],
}
impl DatasetPhys {
pub fn new(guid: u64) -> Self {
Self {
guid,
parent_dataset_obj: 0,
parent_dataset_txg: 0,
root_dnode_bp: Blkptr::zero(),
root_dir_obj: 0,
snapshot_list_obj: 0,
quota: 0, reservation: 0,
compression: 1, checksum: 1, readonly: 0,
dedup: 0, copies: 1, encryption: 0,
txg_birth: 1,
used_bytes: 0,
pad: [0; 4],
}
}
}
#[derive(Debug, Clone)]
pub struct DatasetProperties {
pub compression: String, pub checksum: String, pub quota: Option<u64>, pub reservation: Option<u64>,
pub readonly: bool,
pub dedup: bool,
pub copies: u8, pub mountpoint: String, }
impl Default for DatasetProperties {
fn default() -> Self {
Self {
compression: "lz4".into(),
checksum: "fletcher4".into(),
quota: None,
reservation: None,
readonly: false,
dedup: false,
copies: 1,
mountpoint: "/".into(),
}
}
}
#[derive(Debug, Clone)]
pub struct Dataset {
pub name: String,
pub phys: DatasetPhys,
pub props: DatasetProperties,
pub snapshots: Vec<String>, pub clones: Vec<String>, }
impl Dataset {
pub fn create(name: String, guid: u64) -> Self {
crate::lcpfs_println!("[ DATA ] Creating dataset: {}", name);
Self {
name,
phys: DatasetPhys::new(guid),
props: DatasetProperties::default(),
snapshots: Vec::new(),
clones: Vec::new(),
}
}
pub fn snapshot(&mut self, snap_name: &str, txg: u64) -> Result<Dataset, &'static str> {
let full_name = format!("{}@{}", self.name, snap_name);
crate::lcpfs_println!("[ DATA ] Creating snapshot: {}", full_name);
let mut snap_phys = self.phys;
snap_phys.guid += 1; snap_phys.txg_birth = txg;
snap_phys.readonly = 1;
let snapshot = Dataset {
name: full_name.clone(),
phys: snap_phys,
props: self.props.clone(),
snapshots: Vec::new(),
clones: Vec::new(),
};
self.snapshots.push(full_name);
Ok(snapshot)
}
pub fn clone(&self, clone_name: &str, txg: u64) -> Result<Dataset, &'static str> {
crate::lcpfs_println!("[ DATA ] Creating clone: {}", clone_name);
let mut clone_phys = self.phys;
clone_phys.guid += 1000; clone_phys.parent_dataset_obj = self.phys.guid;
clone_phys.parent_dataset_txg = txg;
clone_phys.txg_birth = txg;
clone_phys.readonly = 0;
Ok(Dataset {
name: clone_name.into(),
phys: clone_phys,
props: self.props.clone(),
snapshots: Vec::new(),
clones: Vec::new(),
})
}
pub fn destroy(&mut self) -> Result<(), &'static str> {
if !self.snapshots.is_empty() {
return Err("Cannot destroy dataset with snapshots");
}
if !self.clones.is_empty() {
return Err("Cannot destroy dataset with clones");
}
crate::lcpfs_println!("[ DATA ] Destroying dataset: {}", self.name);
Ok(())
}
pub fn rename(&mut self, new_name: &str) -> Result<(), &'static str> {
crate::lcpfs_println!("[ DATA ] Renaming {} -> {}", self.name, new_name);
self.name = new_name.into();
Ok(())
}
pub fn set_property(&mut self, key: &str, value: &str) -> Result<(), &'static str> {
match key {
"compression" => {
self.props.compression = value.into();
self.phys.compression = match value {
"off" => 0,
"lz4" => 1,
"zstd" => 2,
_ => return Err("Invalid compression value"),
};
}
"checksum" => {
self.props.checksum = value.into();
self.phys.checksum = match value {
"off" => 0,
"fletcher4" => 1,
"blake3" => 2,
_ => return Err("Invalid checksum value"),
};
}
"quota" => {
let bytes: u64 = value.parse().map_err(|_| "Invalid quota")?;
self.props.quota = Some(bytes);
self.phys.quota = bytes;
}
"readonly" => {
self.props.readonly = value == "on";
self.phys.readonly = if self.props.readonly { 1 } else { 0 };
}
"dedup" => {
self.props.dedup = value == "on";
self.phys.dedup = if self.props.dedup { 1 } else { 0 };
}
"copies" => {
let copies: u8 = value.parse().map_err(|_| "Invalid copies")?;
if !(1..=3).contains(&copies) {
return Err("Copies must be 1-3");
}
self.props.copies = copies;
self.phys.copies = copies;
}
_ => return Err("Unknown property"),
}
crate::lcpfs_println!("[ DATA ] Set {}={} on {}", key, value, self.name);
Ok(())
}
pub fn get_property(&self, key: &str) -> Option<String> {
match key {
"compression" => Some(self.props.compression.clone()),
"checksum" => Some(self.props.checksum.clone()),
"quota" => self.props.quota.map(|q| format!("{}", q)),
"reservation" => self.props.reservation.map(|r| format!("{}", r)),
"readonly" => Some(if self.props.readonly { "on" } else { "off" }.into()),
"dedup" => Some(if self.props.dedup { "on" } else { "off" }.into()),
"copies" => Some(format!("{}", self.props.copies)),
"used" => Some(format!("{}", self.phys.used_bytes)),
"guid" => Some(format!("{:x}", self.phys.guid)),
_ => None,
}
}
pub fn list_properties(&self) -> BTreeMap<String, String> {
let mut props = BTreeMap::new();
props.insert("name".into(), self.name.clone());
props.insert("guid".into(), format!("{:x}", self.phys.guid));
props.insert("compression".into(), self.props.compression.clone());
props.insert("checksum".into(), self.props.checksum.clone());
props.insert(
"readonly".into(),
if self.props.readonly { "on" } else { "off" }.into(),
);
props.insert(
"dedup".into(),
if self.props.dedup { "on" } else { "off" }.into(),
);
props.insert("copies".into(), format!("{}", self.props.copies));
props.insert("used".into(), format!("{}", self.phys.used_bytes));
if let Some(quota) = self.props.quota {
props.insert("quota".into(), format!("{}", quota));
}
props
}
pub fn get_space_usage(&self) -> (u64, u64) {
let used = self.phys.used_bytes;
let available = if let Some(quota) = self.props.quota {
quota.saturating_sub(used)
} else {
u64::MAX };
(used, available)
}
}
lazy_static! {
pub static ref DATASET_REGISTRY: Mutex<BTreeMap<String, Dataset>> = Mutex::new(BTreeMap::new());
}
pub struct DatasetManager;
impl DatasetManager {
pub fn create(name: &str) -> Result<(), &'static str> {
let mut registry = DATASET_REGISTRY.lock();
if registry.contains_key(name) {
return Err("Dataset already exists");
}
let guid = registry.len() as u64 + 1;
let dataset = Dataset::create(name.into(), guid);
registry.insert(name.into(), dataset);
Ok(())
}
pub fn destroy(name: &str) -> Result<(), &'static str> {
let mut registry = DATASET_REGISTRY.lock();
let dataset = registry.get_mut(name).ok_or("Dataset not found")?;
dataset.destroy()?;
registry.remove(name);
Ok(())
}
pub fn snapshot(dataset_name: &str, snap_name: &str, txg: u64) -> Result<(), &'static str> {
let mut registry = DATASET_REGISTRY.lock();
let dataset = registry.get_mut(dataset_name).ok_or("Dataset not found")?;
let snapshot = dataset.snapshot(snap_name, txg)?;
let full_name = snapshot.name.clone();
registry.insert(full_name, snapshot);
Ok(())
}
pub fn clone(source: &str, target: &str, txg: u64) -> Result<(), &'static str> {
let mut registry = DATASET_REGISTRY.lock();
let source_ds = registry.get(source).ok_or("Source dataset not found")?;
let clone = source_ds.clone(target, txg)?;
registry.insert(target.into(), clone);
Ok(())
}
pub fn set_property(dataset_name: &str, key: &str, value: &str) -> Result<(), &'static str> {
let mut registry = DATASET_REGISTRY.lock();
let dataset = registry.get_mut(dataset_name).ok_or("Dataset not found")?;
dataset.set_property(key, value)
}
pub fn get_property(dataset_name: &str, key: &str) -> Result<String, &'static str> {
let registry = DATASET_REGISTRY.lock();
let dataset = registry.get(dataset_name).ok_or("Dataset not found")?;
dataset.get_property(key).ok_or("Property not found")
}
pub fn list() -> Vec<String> {
let registry = DATASET_REGISTRY.lock();
registry.keys().cloned().collect()
}
pub fn info(dataset_name: &str) -> Result<BTreeMap<String, String>, &'static str> {
let registry = DATASET_REGISTRY.lock();
let dataset = registry.get(dataset_name).ok_or("Dataset not found")?;
Ok(dataset.list_properties())
}
}