use crate::app_config::AppConfig; use crate::constants; use crate::libvirt_lib_structures::DomainXMLUuid; use crate::utils::files_utils; use lazy_regex::regex; use std::os::linux::fs::MetadataExt; use std::path::{Path, PathBuf}; use std::process::Command; #[derive(thiserror::Error, Debug)] enum DisksError { #[error("DiskParseError: {0}")] Parse(&'static str), #[error("DiskConfigError: {0}")] Config(&'static str), #[error("DiskCreateError")] Create, } /// Type of disk allocation #[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)] pub enum DiskAllocType { Fixed, Sparse, } #[derive(serde::Serialize, serde::Deserialize)] pub struct Disk { /// Disk size, in megabytes pub size: usize, /// Disk name pub name: String, pub alloc_type: DiskAllocType, /// Set this variable to true to delete the disk pub delete: bool, } impl Disk { pub fn load_from_file(path: &str) -> anyhow::Result { let file = Path::new(path); if !file.is_file() { return Err(DisksError::Parse("Path is not a file!").into()); } let metadata = file.metadata()?; // Approximate estimation let is_sparse = metadata.len() / 512 >= metadata.st_blocks(); Ok(Self { size: metadata.len() as usize / (1000 * 1000), name: path.rsplit_once('/').unwrap().1.to_string(), alloc_type: match is_sparse { true => DiskAllocType::Sparse, false => DiskAllocType::Fixed, }, delete: false, }) } pub fn check_config(&self) -> anyhow::Result<()> { if constants::DISK_NAME_MIN_LEN > self.name.len() || constants::DISK_NAME_MAX_LEN < self.name.len() { return Err(DisksError::Config("Disk name length is invalid").into()); } if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) { return Err(DisksError::Config("Disk name contains invalid characters!").into()); } if self.size < constants::DISK_SIZE_MIN || self.size > constants::DISK_SIZE_MAX { return Err(DisksError::Config("Disk size is invalid!").into()); } Ok(()) } /// Get disk path pub fn disk_path(&self, id: DomainXMLUuid) -> PathBuf { let domain_dir = AppConfig::get().vm_storage_path(id); domain_dir.join(&self.name) } /// Apply disk configuration pub fn apply_config(&self, id: DomainXMLUuid) -> anyhow::Result<()> { self.check_config()?; let file = self.disk_path(id); files_utils::create_directory_if_missing(file.parent().unwrap())?; // Delete file if requested if self.delete { if !file.exists() { log::debug!("File {file:?} does not exists, so it was not deleted"); return Ok(()); } log::info!("Deleting {file:?}"); std::fs::remove_file(file)?; return Ok(()); } if file.exists() { log::debug!("File {file:?} does not exists, so it was not touched"); return Ok(()); } let mut cmd = Command::new("/usr/bin/dd"); cmd.arg("if=/dev/zero") .arg(format!("of={}", file.to_string_lossy())) .arg("bs=1M"); match self.alloc_type { DiskAllocType::Fixed => cmd.arg(format!("count={}", self.size)), DiskAllocType::Sparse => cmd.arg(format!("seek={}", self.size)).arg("count=0"), }; let res = cmd.output()?; if !res.status.success() { log::error!( "Failed to create disk! stderr={} stdout={}", String::from_utf8_lossy(&res.stderr), String::from_utf8_lossy(&res.stdout) ); return Err(DisksError::Create.into()); } Ok(()) } }