use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
pub const DEFAULT_BLOCK_SIZE: u64 = 128 * 1024;
pub const MIN_BLOCK_SIZE: u64 = 4 * 1024;
pub const MAX_BLOCK_SIZE: u64 = 1024 * 1024;
pub const DEFAULT_WARN_THRESHOLD: u8 = 80;
pub const DEFAULT_CRITICAL_THRESHOLD: u8 = 90;
pub const DEFAULT_EMERGENCY_THRESHOLD: u8 = 95;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum BlockState {
Unallocated = 0,
Allocated = 1,
Pending = 2,
Deallocated = 3,
Reserved = 4,
}
impl BlockState {
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(Self::Unallocated),
1 => Some(Self::Allocated),
2 => Some(Self::Pending),
3 => Some(Self::Deallocated),
4 => Some(Self::Reserved),
_ => None,
}
}
pub fn is_physical(&self) -> bool {
matches!(self, Self::Allocated | Self::Pending | Self::Reserved)
}
pub fn is_zero(&self) -> bool {
matches!(self, Self::Unallocated | Self::Deallocated)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VirtualBlock(pub u64);
impl VirtualBlock {
pub fn new(block: u64) -> Self {
Self(block)
}
pub fn block(&self) -> u64 {
self.0
}
pub fn offset(&self, block_size: u64) -> u64 {
self.0 * block_size
}
pub fn from_offset(offset: u64, block_size: u64) -> Self {
Self(offset / block_size)
}
}
impl From<u64> for VirtualBlock {
fn from(v: u64) -> Self {
Self(v)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PhysicalBlock(pub u64);
impl PhysicalBlock {
pub fn new(block: u64) -> Self {
Self(block)
}
pub fn block(&self) -> u64 {
self.0
}
pub fn offset(&self, block_size: u64) -> u64 {
self.0 * block_size
}
}
impl From<u64> for PhysicalBlock {
fn from(v: u64) -> Self {
Self(v)
}
}
#[derive(Debug, Clone, Copy)]
pub struct BlockMapping {
pub virtual_block: VirtualBlock,
pub physical_block: Option<PhysicalBlock>,
pub state: BlockState,
pub refcount: u16,
pub flags: MappingFlags,
}
impl BlockMapping {
pub fn unallocated(virtual_block: VirtualBlock) -> Self {
Self {
virtual_block,
physical_block: None,
state: BlockState::Unallocated,
refcount: 0,
flags: MappingFlags::empty(),
}
}
pub fn allocated(virtual_block: VirtualBlock, physical_block: PhysicalBlock) -> Self {
Self {
virtual_block,
physical_block: Some(physical_block),
state: BlockState::Allocated,
refcount: 1,
flags: MappingFlags::empty(),
}
}
pub fn is_allocated(&self) -> bool {
self.physical_block.is_some()
}
pub fn deallocate(&mut self) {
self.physical_block = None;
self.state = BlockState::Deallocated;
self.refcount = 0;
}
pub fn add_ref(&mut self) {
self.refcount = self.refcount.saturating_add(1);
}
pub fn release(&mut self) -> bool {
self.refcount = self.refcount.saturating_sub(1);
self.refcount == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct MappingFlags(pub u8);
impl MappingFlags {
pub const NONE: u8 = 0;
pub const SHARED: u8 = 0x01;
pub const DIRTY: u8 = 0x02;
pub const COMPRESSED: u8 = 0x04;
pub const ENCRYPTED: u8 = 0x08;
pub const MIGRATING: u8 = 0x10;
pub fn empty() -> Self {
Self(0)
}
pub fn shared() -> Self {
Self(Self::SHARED)
}
pub fn is_shared(&self) -> bool {
self.0 & Self::SHARED != 0
}
pub fn is_dirty(&self) -> bool {
self.0 & Self::DIRTY != 0
}
pub fn set_dirty(&mut self) {
self.0 |= Self::DIRTY;
}
pub fn clear_dirty(&mut self) {
self.0 &= !Self::DIRTY;
}
}
#[derive(Debug, Clone, Copy)]
pub struct Thresholds {
pub warning: u8,
pub critical: u8,
pub emergency: u8,
}
impl Default for Thresholds {
fn default() -> Self {
Self {
warning: DEFAULT_WARN_THRESHOLD,
critical: DEFAULT_CRITICAL_THRESHOLD,
emergency: DEFAULT_EMERGENCY_THRESHOLD,
}
}
}
impl Thresholds {
pub fn new(warning: u8, critical: u8, emergency: u8) -> Self {
Self {
warning: warning.min(100),
critical: critical.min(100),
emergency: emergency.min(100),
}
}
pub fn level(&self, percent: u8) -> ThresholdLevel {
if percent >= self.emergency {
ThresholdLevel::Emergency
} else if percent >= self.critical {
ThresholdLevel::Critical
} else if percent >= self.warning {
ThresholdLevel::Warning
} else {
ThresholdLevel::Normal
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum ThresholdLevel {
#[default]
Normal = 0,
Warning = 1,
Critical = 2,
Emergency = 3,
}
impl ThresholdLevel {
pub fn is_problem(&self) -> bool {
!matches!(self, Self::Normal)
}
pub fn name(&self) -> &'static str {
match self {
Self::Normal => "normal",
Self::Warning => "warning",
Self::Critical => "critical",
Self::Emergency => "emergency",
}
}
}
impl fmt::Display for ThresholdLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Default)]
pub struct VolumeStats {
pub virtual_size: u64,
pub physical_used: u64,
pub allocated_blocks: u64,
pub unallocated_blocks: u64,
pub deallocated_blocks: u64,
pub shared_blocks: u64,
pub bytes_written: u64,
pub bytes_read: u64,
pub alloc_ops: u64,
pub dealloc_ops: u64,
pub cow_ops: u64,
}
impl VolumeStats {
pub fn new(virtual_size: u64) -> Self {
Self {
virtual_size,
..Default::default()
}
}
pub fn usage_percent(&self) -> u8 {
if self.virtual_size == 0 {
return 0;
}
((self.physical_used as u128 * 100) / self.virtual_size as u128) as u8
}
pub fn allocation_ratio(&self) -> f64 {
if self.physical_used == 0 {
return 0.0;
}
self.virtual_size as f64 / self.physical_used as f64
}
pub fn total_blocks(&self) -> u64 {
self.allocated_blocks + self.unallocated_blocks + self.deallocated_blocks
}
}
#[derive(Debug, Clone, Default)]
pub struct PoolStats {
pub total_capacity: u64,
pub physical_used: u64,
pub physical_free: u64,
pub total_virtual: u64,
pub volume_count: u32,
pub snapshot_count: u32,
pub overcommit_ratio: f64,
pub threshold_level: ThresholdLevel,
}
impl PoolStats {
pub fn new(total_capacity: u64) -> Self {
Self {
total_capacity,
physical_free: total_capacity,
..Default::default()
}
}
pub fn usage_percent(&self) -> u8 {
if self.total_capacity == 0 {
return 0;
}
((self.physical_used as u128 * 100) / self.total_capacity as u128) as u8
}
pub fn update_overcommit(&mut self) {
if self.total_capacity > 0 {
self.overcommit_ratio = self.total_virtual as f64 / self.total_capacity as f64;
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AllocationPolicy {
#[default]
FirstFit,
BestFit,
NextFit,
Contiguous,
Striped,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OvercommitPolicy {
AllowFull,
StopAtWarning,
StopAtCritical,
#[default]
StopAtEmergency,
Never,
}
impl OvercommitPolicy {
pub fn allows_write(&self, level: ThresholdLevel) -> bool {
match self {
Self::AllowFull => true,
Self::Never => true,
Self::StopAtWarning => level < ThresholdLevel::Warning,
Self::StopAtCritical => level < ThresholdLevel::Critical,
Self::StopAtEmergency => level < ThresholdLevel::Emergency,
}
}
}
#[derive(Debug, Clone)]
pub struct VolumeConfig {
pub name: String,
pub virtual_size: u64,
pub block_size: u64,
pub allocation_policy: AllocationPolicy,
pub zero_detect: bool,
pub compression: bool,
pub dedup_key: Option<String>,
pub max_physical: Option<u64>,
pub reservation: u64,
}
impl VolumeConfig {
pub fn new(name: impl Into<String>, virtual_size: u64) -> Self {
Self {
name: name.into(),
virtual_size,
block_size: DEFAULT_BLOCK_SIZE,
allocation_policy: AllocationPolicy::default(),
zero_detect: true,
compression: false,
dedup_key: None,
max_physical: None,
reservation: 0,
}
}
pub fn with_block_size(mut self, size: u64) -> Self {
self.block_size = size.clamp(MIN_BLOCK_SIZE, MAX_BLOCK_SIZE);
self
}
pub fn with_compression(mut self) -> Self {
self.compression = true;
self
}
pub fn with_max_physical(mut self, max: u64) -> Self {
self.max_physical = Some(max);
self
}
pub fn with_reservation(mut self, reservation: u64) -> Self {
self.reservation = reservation;
self
}
pub fn virtual_blocks(&self) -> u64 {
self.virtual_size.div_ceil(self.block_size)
}
}
#[derive(Debug, Clone)]
pub struct PoolConfig {
pub name: String,
pub block_size: u64,
pub capacity: u64,
pub thresholds: Thresholds,
pub overcommit_policy: OvercommitPolicy,
pub allocation_policy: AllocationPolicy,
pub auto_trim: bool,
pub metadata_reserve_percent: u8,
}
impl PoolConfig {
pub fn new(name: impl Into<String>, capacity: u64) -> Self {
Self {
name: name.into(),
block_size: DEFAULT_BLOCK_SIZE,
capacity,
thresholds: Thresholds::default(),
overcommit_policy: OvercommitPolicy::default(),
allocation_policy: AllocationPolicy::default(),
auto_trim: true,
metadata_reserve_percent: 5,
}
}
pub fn with_block_size(mut self, size: u64) -> Self {
self.block_size = size.clamp(MIN_BLOCK_SIZE, MAX_BLOCK_SIZE);
self
}
pub fn with_thresholds(mut self, thresholds: Thresholds) -> Self {
self.thresholds = thresholds;
self
}
pub fn with_overcommit_policy(mut self, policy: OvercommitPolicy) -> Self {
self.overcommit_policy = policy;
self
}
pub fn usable_capacity(&self) -> u64 {
let reserved = (self.capacity as u128 * self.metadata_reserve_percent as u128 / 100) as u64;
self.capacity.saturating_sub(reserved)
}
pub fn physical_blocks(&self) -> u64 {
self.usable_capacity() / self.block_size
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlertType {
ThresholdCrossed,
PoolNearlyFull,
PoolFull,
ReservationExceeded,
AllocationFailed,
SnapshotSpaceIssue,
}
#[derive(Debug, Clone)]
pub struct Alert {
pub alert_type: AlertType,
pub level: ThresholdLevel,
pub pool_name: String,
pub volume_name: Option<String>,
pub usage_percent: u8,
pub message: String,
pub timestamp: u64,
}
impl Alert {
pub fn new(
alert_type: AlertType,
level: ThresholdLevel,
pool_name: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
alert_type,
level,
pool_name: pool_name.into(),
volume_name: None,
usage_percent: 0,
message: message.into(),
timestamp: 0,
}
}
pub fn with_volume(mut self, name: impl Into<String>) -> Self {
self.volume_name = Some(name.into());
self
}
pub fn with_usage(mut self, percent: u8) -> Self {
self.usage_percent = percent;
self
}
pub fn with_timestamp(mut self, ts: u64) -> Self {
self.timestamp = ts;
self
}
}
#[derive(Debug, Clone)]
pub enum ThinError {
PoolNotFound(String),
VolumeNotFound(String),
PoolFull,
VolumeLimitReached,
AllocationFailed,
InvalidBlockSize,
InvalidConfig(String),
VolumeExists(String),
SnapshotNotFound(String),
NotPermitted,
IoError,
}
impl fmt::Display for ThinError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PoolNotFound(name) => write!(f, "Pool not found: {}", name),
Self::VolumeNotFound(name) => write!(f, "Volume not found: {}", name),
Self::PoolFull => write!(f, "Pool is full"),
Self::VolumeLimitReached => write!(f, "Volume physical limit reached"),
Self::AllocationFailed => write!(f, "Block allocation failed"),
Self::InvalidBlockSize => write!(f, "Invalid block size"),
Self::InvalidConfig(msg) => write!(f, "Invalid configuration: {}", msg),
Self::VolumeExists(name) => write!(f, "Volume already exists: {}", name),
Self::SnapshotNotFound(name) => write!(f, "Snapshot not found: {}", name),
Self::NotPermitted => write!(f, "Operation not permitted"),
Self::IoError => write!(f, "I/O error"),
}
}
}
pub type ThinResult<T> = core::result::Result<T, ThinError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_block_state() {
assert!(BlockState::Unallocated.is_zero());
assert!(BlockState::Deallocated.is_zero());
assert!(!BlockState::Allocated.is_zero());
assert!(BlockState::Allocated.is_physical());
assert!(BlockState::Pending.is_physical());
assert!(!BlockState::Unallocated.is_physical());
}
#[test]
fn test_virtual_block() {
let vb = VirtualBlock::new(100);
assert_eq!(vb.block(), 100);
assert_eq!(vb.offset(4096), 100 * 4096);
let vb2 = VirtualBlock::from_offset(409600, 4096);
assert_eq!(vb2.block(), 100);
}
#[test]
fn test_block_mapping() {
let vb = VirtualBlock::new(50);
let pb = PhysicalBlock::new(1000);
let mut mapping = BlockMapping::unallocated(vb);
assert!(!mapping.is_allocated());
assert_eq!(mapping.state, BlockState::Unallocated);
let allocated = BlockMapping::allocated(vb, pb);
assert!(allocated.is_allocated());
assert_eq!(allocated.refcount, 1);
}
#[test]
fn test_block_mapping_refcount() {
let vb = VirtualBlock::new(50);
let pb = PhysicalBlock::new(1000);
let mut mapping = BlockMapping::allocated(vb, pb);
assert_eq!(mapping.refcount, 1);
mapping.add_ref();
assert_eq!(mapping.refcount, 2);
assert!(!mapping.release()); assert_eq!(mapping.refcount, 1);
assert!(mapping.release()); assert_eq!(mapping.refcount, 0);
}
#[test]
fn test_mapping_flags() {
let mut flags = MappingFlags::empty();
assert!(!flags.is_dirty());
flags.set_dirty();
assert!(flags.is_dirty());
flags.clear_dirty();
assert!(!flags.is_dirty());
let shared = MappingFlags::shared();
assert!(shared.is_shared());
}
#[test]
fn test_thresholds() {
let t = Thresholds::default();
assert_eq!(t.level(50), ThresholdLevel::Normal);
assert_eq!(t.level(80), ThresholdLevel::Warning);
assert_eq!(t.level(90), ThresholdLevel::Critical);
assert_eq!(t.level(95), ThresholdLevel::Emergency);
}
#[test]
fn test_threshold_level() {
assert!(!ThresholdLevel::Normal.is_problem());
assert!(ThresholdLevel::Warning.is_problem());
assert!(ThresholdLevel::Critical.is_problem());
assert!(ThresholdLevel::Emergency.is_problem());
}
#[test]
fn test_volume_stats() {
let mut stats = VolumeStats::new(1024 * 1024 * 1024); stats.physical_used = 256 * 1024 * 1024;
assert_eq!(stats.usage_percent(), 25);
assert_eq!(stats.allocation_ratio(), 4.0);
}
#[test]
fn test_pool_stats() {
let mut stats = PoolStats::new(10 * 1024 * 1024 * 1024); stats.physical_used = 5 * 1024 * 1024 * 1024; stats.total_virtual = 50 * 1024 * 1024 * 1024; stats.update_overcommit();
assert_eq!(stats.usage_percent(), 50);
assert_eq!(stats.overcommit_ratio, 5.0);
}
#[test]
fn test_overcommit_policy() {
let policy = OvercommitPolicy::StopAtCritical;
assert!(policy.allows_write(ThresholdLevel::Normal));
assert!(policy.allows_write(ThresholdLevel::Warning));
assert!(!policy.allows_write(ThresholdLevel::Critical));
assert!(!policy.allows_write(ThresholdLevel::Emergency));
}
#[test]
fn test_volume_config() {
let config = VolumeConfig::new("test-vol", 100 * 1024 * 1024 * 1024)
.with_block_size(256 * 1024)
.with_compression()
.with_reservation(10 * 1024 * 1024 * 1024);
assert_eq!(config.name, "test-vol");
assert_eq!(config.block_size, 256 * 1024);
assert!(config.compression);
assert_eq!(config.reservation, 10 * 1024 * 1024 * 1024);
}
#[test]
fn test_pool_config() {
let config = PoolConfig::new("test-pool", 100 * 1024 * 1024 * 1024);
let usable = config.usable_capacity();
assert_eq!(usable, 95 * 1024 * 1024 * 1024);
}
#[test]
fn test_alert() {
let alert = Alert::new(
AlertType::ThresholdCrossed,
ThresholdLevel::Warning,
"pool1",
"Pool usage at 80%",
)
.with_volume("vol1")
.with_usage(80);
assert_eq!(alert.alert_type, AlertType::ThresholdCrossed);
assert_eq!(alert.level, ThresholdLevel::Warning);
assert_eq!(alert.volume_name, Some("vol1".into()));
assert_eq!(alert.usage_percent, 80);
}
#[test]
fn test_thin_error_display() {
let err = ThinError::PoolFull;
assert_eq!(alloc::format!("{}", err), "Pool is full");
let err2 = ThinError::VolumeNotFound("vol1".into());
assert_eq!(alloc::format!("{}", err2), "Volume not found: vol1");
}
}