use crate::app_config::AppConfig; use crate::constants; use crate::libvirt_lib_structures::XMLUuid; use crate::utils::file_disks_utils::{DiskFileFormat, DiskFileInfo}; use crate::utils::file_size_utils::FileSize; use crate::utils::files_utils; use lazy_regex::regex; use std::path::{Path, PathBuf}; #[derive(thiserror::Error, Debug)] enum VMDisksError { #[error("DiskConfigError: {0}")] Config(&'static str), } #[derive(serde::Serialize, serde::Deserialize)] pub enum VMDiskBus { Virtio, SATA, } /// Disk allocation type #[derive(serde::Serialize, serde::Deserialize)] #[serde(tag = "format")] pub enum VMDiskFormat { Raw { /// Is raw file a sparse file? is_sparse: bool, }, QCow2, } #[derive(serde::Serialize, serde::Deserialize)] pub struct VMFileDisk { /// Disk name pub name: String, /// Disk size, in bytes pub size: FileSize, /// Disk bus pub bus: VMDiskBus, /// Disk format #[serde(flatten)] pub format: VMDiskFormat, /// When creating a new disk, specify the disk image template to use #[serde(skip_serializing_if = "Option::is_none")] pub from_image: Option, /// Set this variable to true to delete the disk pub delete: bool, } impl VMFileDisk { pub fn load_from_file(path: &str, bus: &str) -> anyhow::Result { let file = Path::new(path); let info = DiskFileInfo::load_file(file)?; Ok(Self { name: info.name, // Get only the virtual size of the file size: match info.format { DiskFileFormat::Raw { .. } => info.file_size, DiskFileFormat::QCow2 { virtual_size } => virtual_size, _ => anyhow::bail!("Unsupported image format: {:?}", info.format), }, format: match info.format { DiskFileFormat::Raw { is_sparse } => VMDiskFormat::Raw { is_sparse }, DiskFileFormat::QCow2 { .. } => VMDiskFormat::QCow2, _ => anyhow::bail!("Unsupported image format: {:?}", info.format), }, bus: match bus { "virtio" => VMDiskBus::Virtio, "sata" => VMDiskBus::SATA, _ => anyhow::bail!("Unsupported disk bus type: {bus}"), }, delete: false, from_image: None, }) } 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(VMDisksError::Config("Disk name length is invalid").into()); } if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) { return Err(VMDisksError::Config("Disk name contains invalid characters!").into()); } // Check disk size if !(constants::DISK_SIZE_MIN..=constants::DISK_SIZE_MAX).contains(&self.size) { return Err(VMDisksError::Config("Disk size is invalid!").into()); } // Check specified disk image template if let Some(disk_image) = &self.from_image { if !files_utils::check_file_name(disk_image) { return Err(VMDisksError::Config("Disk image template name is not valid!").into()); } if !AppConfig::get().disk_images_file_path(disk_image).is_file() { return Err( VMDisksError::Config("Specified disk image file does not exist!").into(), ); } } Ok(()) } /// Get disk path on file system pub fn disk_path(&self, id: XMLUuid) -> PathBuf { let domain_dir = AppConfig::get().vm_storage_path(id); let file_name = match self.format { VMDiskFormat::Raw { .. } => self.name.to_string(), VMDiskFormat::QCow2 => format!("{}.qcow2", self.name), }; domain_dir.join(&file_name) } /// Apply disk configuration pub fn apply_config(&self, id: XMLUuid) -> 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 format = match self.format { VMDiskFormat::Raw { is_sparse } => DiskFileFormat::Raw { is_sparse }, VMDiskFormat::QCow2 => DiskFileFormat::QCow2 { virtual_size: self.size, }, }; // Create / Restore disk file match &self.from_image { // Create disk file None => { DiskFileInfo::create(&file, format, self.size)?; } // Restore disk image template Some(disk_img) => { let src_file = DiskFileInfo::load_file(&AppConfig::get().disk_images_file_path(disk_img))?; src_file.convert(&file, format)?; } } Ok(()) } }