use anyhow::{Context, Result};
use clap::Subcommand;
#[derive(Subcommand)]
pub enum DiskCommands {
Usage,
Compact,
}
pub async fn execute(cmd: DiskCommands) -> Result<()> {
match cmd {
DiskCommands::Usage => execute_usage().await,
DiskCommands::Compact => execute_compact().await,
}
}
const BYTES_PER_GIB: f64 = 1024.0 * 1024.0 * 1024.0;
#[derive(Debug, Clone, Copy, PartialEq)]
struct DiskUsage {
logical_bytes: u64,
physical_bytes: u64,
}
impl DiskUsage {
fn logical_gib(self) -> f64 {
self.logical_bytes as f64 / BYTES_PER_GIB
}
fn physical_gib(self) -> f64 {
self.physical_bytes as f64 / BYTES_PER_GIB
}
fn reclaimable_gib(self) -> f64 {
self.logical_bytes.saturating_sub(self.physical_bytes) as f64 / BYTES_PER_GIB
}
fn usage_pct(self) -> f64 {
if self.logical_bytes == 0 {
return 0.0;
}
let used = self.physical_bytes.min(self.logical_bytes) as f64;
(used / self.logical_bytes as f64) * 100.0
}
}
fn read_disk_usage(path: &std::path::Path) -> Result<DiskUsage> {
let metadata =
std::fs::metadata(path).with_context(|| format!("failed to stat {}", path.display()))?;
let logical_bytes = metadata.len();
#[cfg(unix)]
let physical_bytes = {
use std::os::unix::fs::MetadataExt;
metadata.blocks() * 512
};
#[cfg(not(unix))]
let physical_bytes = logical_bytes;
Ok(DiskUsage {
logical_bytes,
physical_bytes,
})
}
async fn execute_usage() -> Result<()> {
let config = arcbox_core::Config::load().unwrap_or_default();
let img_path = config.docker_img_path();
if !img_path.exists() {
println!("Docker data disk not found at {}", img_path.display());
println!("The disk will be created when a machine is first started.");
return Ok(());
}
let usage = read_disk_usage(&img_path)?;
println!("Docker data disk:");
println!(" Path: {}", img_path.display());
println!(" Logical: {:.1} GiB", usage.logical_gib());
println!(
" Physical: {:.1} GiB ({:.1}%)",
usage.physical_gib(),
usage.usage_pct()
);
println!(" Reclaimable: {:.1} GiB", usage.reclaimable_gib());
Ok(())
}
async fn execute_compact() -> Result<()> {
let config = arcbox_core::Config::load().unwrap_or_default();
let img_path = config.docker_img_path();
if !img_path.exists() {
println!("Docker data disk not found at {}", img_path.display());
return Ok(());
}
println!("On-demand compaction via this command is not yet implemented.");
println!("The guest VM runs fstrim hourly and the host disk uses sparse allocation, so freed");
println!("blocks are reclaimed automatically — but this command itself triggers no trim.");
println!();
println!("To reclaim space inside the VM, run:");
println!(" docker system prune --all --volumes");
println!();
let usage = read_disk_usage(&img_path)?;
println!(
"Current: {:.1} GiB physical / {:.1} GiB logical",
usage.physical_gib(),
usage.logical_gib(),
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::DiskUsage;
#[test]
fn usage_pct_clamped_when_physical_exceeds_logical() {
let usage = DiskUsage {
logical_bytes: 100,
physical_bytes: 200,
};
assert!((usage.usage_pct() - 100.0).abs() < f64::EPSILON);
}
#[test]
fn usage_pct_zero_when_logical_zero() {
let usage = DiskUsage {
logical_bytes: 0,
physical_bytes: 0,
};
assert!(usage.usage_pct().abs() < f64::EPSILON);
}
#[test]
fn reclaimable_gib_saturates_to_zero_when_physical_exceeds_logical() {
let usage = DiskUsage {
logical_bytes: 100,
physical_bytes: 200,
};
assert!(usage.reclaimable_gib().abs() < f64::EPSILON);
}
#[test]
fn usage_pct_typical_sparse_image() {
let usage = DiskUsage {
logical_bytes: 10 * 1024 * 1024 * 1024,
physical_bytes: 5 * 1024 * 1024 * 1024,
};
assert!((usage.usage_pct() - 50.0).abs() < f64::EPSILON);
}
}