Add QCow2 file format support on backend
This commit is contained in:
		@@ -4,7 +4,7 @@ use crate::libvirt_lib_structures::XMLUuid;
 | 
				
			|||||||
use crate::libvirt_lib_structures::domain::*;
 | 
					use crate::libvirt_lib_structures::domain::*;
 | 
				
			||||||
use crate::libvirt_rest_structures::LibVirtStructError;
 | 
					use crate::libvirt_rest_structures::LibVirtStructError;
 | 
				
			||||||
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
 | 
					use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
 | 
				
			||||||
use crate::utils::disks_utils::Disk;
 | 
					use crate::utils::file_disks_utils::{DiskFormat, FileDisk};
 | 
				
			||||||
use crate::utils::files_utils;
 | 
					use crate::utils::files_utils;
 | 
				
			||||||
use crate::utils::files_utils::convert_size_unit_to_mb;
 | 
					use crate::utils::files_utils::convert_size_unit_to_mb;
 | 
				
			||||||
use lazy_regex::regex;
 | 
					use lazy_regex::regex;
 | 
				
			||||||
@@ -78,7 +78,7 @@ pub struct VMInfo {
 | 
				
			|||||||
    /// Attach ISO file(s)
 | 
					    /// Attach ISO file(s)
 | 
				
			||||||
    pub iso_files: Vec<String>,
 | 
					    pub iso_files: Vec<String>,
 | 
				
			||||||
    /// Storage - https://access.redhat.com/documentation/fr-fr/red_hat_enterprise_linux/6/html/virtualization_administration_guide/sect-virtualization-virtualized_block_devices-adding_storage_devices_to_guests#sect-Virtualization-Adding_storage_devices_to_guests-Adding_file_based_storage_to_a_guest
 | 
					    /// Storage - https://access.redhat.com/documentation/fr-fr/red_hat_enterprise_linux/6/html/virtualization_administration_guide/sect-virtualization-virtualized_block_devices-adding_storage_devices_to_guests#sect-Virtualization-Adding_storage_devices_to_guests-Adding_file_based_storage_to_a_guest
 | 
				
			||||||
    pub disks: Vec<Disk>,
 | 
					    pub disks: Vec<FileDisk>,
 | 
				
			||||||
    /// Network cards
 | 
					    /// Network cards
 | 
				
			||||||
    pub networks: Vec<Network>,
 | 
					    pub networks: Vec<Network>,
 | 
				
			||||||
    /// Add a TPM v2.0 module
 | 
					    /// Add a TPM v2.0 module
 | 
				
			||||||
@@ -129,6 +129,7 @@ impl VMInfo {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        let mut disks = vec![];
 | 
					        let mut disks = vec![];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Add ISO files
 | 
				
			||||||
        for iso_file in &self.iso_files {
 | 
					        for iso_file in &self.iso_files {
 | 
				
			||||||
            if !files_utils::check_file_name(iso_file) {
 | 
					            if !files_utils::check_file_name(iso_file) {
 | 
				
			||||||
                return Err(StructureExtraction("ISO filename is invalid!").into());
 | 
					                return Err(StructureExtraction("ISO filename is invalid!").into());
 | 
				
			||||||
@@ -267,7 +268,10 @@ impl VMInfo {
 | 
				
			|||||||
                device: "disk".to_string(),
 | 
					                device: "disk".to_string(),
 | 
				
			||||||
                driver: DiskDriverXML {
 | 
					                driver: DiskDriverXML {
 | 
				
			||||||
                    name: "qemu".to_string(),
 | 
					                    name: "qemu".to_string(),
 | 
				
			||||||
                    r#type: "raw".to_string(),
 | 
					                    r#type: match disk.format {
 | 
				
			||||||
 | 
					                        DiskFormat::Raw { .. } => "raw".to_string(),
 | 
				
			||||||
 | 
					                        DiskFormat::QCow2 => "qcow2".to_string(),
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
                    cache: "none".to_string(),
 | 
					                    cache: "none".to_string(),
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                source: DiskSourceXML {
 | 
					                source: DiskSourceXML {
 | 
				
			||||||
@@ -429,7 +433,7 @@ impl VMInfo {
 | 
				
			|||||||
                .disks
 | 
					                .disks
 | 
				
			||||||
                .iter()
 | 
					                .iter()
 | 
				
			||||||
                .filter(|d| d.device == "disk")
 | 
					                .filter(|d| d.device == "disk")
 | 
				
			||||||
                .map(|d| Disk::load_from_file(&d.source.file).unwrap())
 | 
					                .map(|d| FileDisk:: load_from_file(&d.source.file).unwrap())
 | 
				
			||||||
                .collect(),
 | 
					                .collect(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            networks: domain
 | 
					            networks: domain
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,133 +0,0 @@
 | 
				
			|||||||
use crate::app_config::AppConfig;
 | 
					 | 
				
			||||||
use crate::constants;
 | 
					 | 
				
			||||||
use crate::libvirt_lib_structures::XMLUuid;
 | 
					 | 
				
			||||||
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<Self> {
 | 
					 | 
				
			||||||
        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: XMLUuid) -> PathBuf {
 | 
					 | 
				
			||||||
        let domain_dir = AppConfig::get().vm_storage_path(id);
 | 
					 | 
				
			||||||
        domain_dir.join(&self.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 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(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										208
									
								
								virtweb_backend/src/utils/file_disks_utils.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								virtweb_backend/src/utils/file_disks_utils.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,208 @@
 | 
				
			|||||||
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
 | 
					use crate::constants;
 | 
				
			||||||
 | 
					use crate::libvirt_lib_structures::XMLUuid;
 | 
				
			||||||
 | 
					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,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Disk allocation type
 | 
				
			||||||
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					#[serde(tag = "format")]
 | 
				
			||||||
 | 
					pub enum DiskFormat {
 | 
				
			||||||
 | 
					    Raw {
 | 
				
			||||||
 | 
					        /// Type of disk allocation
 | 
				
			||||||
 | 
					        alloc_type: DiskAllocType,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    QCow2,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct FileDisk {
 | 
				
			||||||
 | 
					    /// Disk name
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    /// Disk size, in megabytes
 | 
				
			||||||
 | 
					    size: usize,
 | 
				
			||||||
 | 
					    /// Disk format
 | 
				
			||||||
 | 
					    #[serde(flatten)]
 | 
				
			||||||
 | 
					    pub format: DiskFormat,
 | 
				
			||||||
 | 
					    /// Set this variable to true to delete the disk
 | 
				
			||||||
 | 
					    pub delete: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl FileDisk {
 | 
				
			||||||
 | 
					    pub fn load_from_file(path: &str) -> anyhow::Result<Self> {
 | 
				
			||||||
 | 
					        let file = Path::new(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if !file.is_file() {
 | 
				
			||||||
 | 
					            return Err(DisksError::Parse("Path is not a file!").into());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let metadata = file.metadata()?;
 | 
				
			||||||
 | 
					        let name = file.file_stem().and_then(|s| s.to_str()).unwrap_or("disk");
 | 
				
			||||||
 | 
					        let ext = file.extension().and_then(|s| s.to_str()).unwrap_or("raw");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Approximate raw file estimation
 | 
				
			||||||
 | 
					        let is_raw_sparse = metadata.len() / 512 >= metadata.st_blocks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let format = match ext {
 | 
				
			||||||
 | 
					            "qcow2" => DiskFormat::QCow2,
 | 
				
			||||||
 | 
					            "raw" => DiskFormat::Raw {
 | 
				
			||||||
 | 
					                alloc_type: match is_raw_sparse {
 | 
				
			||||||
 | 
					                    true => DiskAllocType::Sparse,
 | 
				
			||||||
 | 
					                    false => DiskAllocType::Fixed,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            _ => anyhow::bail!("Unsupported disk extension: {ext}!"),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(Self {
 | 
				
			||||||
 | 
					            name: name.to_string(),
 | 
				
			||||||
 | 
					            size: match format {
 | 
				
			||||||
 | 
					                DiskFormat::Raw { .. } => metadata.len() as usize / (1000 * 1000),
 | 
				
			||||||
 | 
					                DiskFormat::QCow2 => qcow_virt_size(path)? / (1000 * 1000),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            format,
 | 
				
			||||||
 | 
					            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());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check disk size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if !(constants::DISK_SIZE_MIN..=constants::DISK_SIZE_MAX).contains(&self.size) {
 | 
				
			||||||
 | 
					            return Err(DisksError::Config("Disk size is invalid!").into());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get disk path
 | 
				
			||||||
 | 
					    pub fn disk_path(&self, id: XMLUuid) -> PathBuf {
 | 
				
			||||||
 | 
					        let domain_dir = AppConfig::get().vm_storage_path(id);
 | 
				
			||||||
 | 
					        let file_name = match self.format {
 | 
				
			||||||
 | 
					            DiskFormat::Raw { .. } => self.name.to_string(),
 | 
				
			||||||
 | 
					            DiskFormat::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(());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Prepare command to create file
 | 
				
			||||||
 | 
					        let res = match self.format {
 | 
				
			||||||
 | 
					            DiskFormat::Raw { alloc_type } => {
 | 
				
			||||||
 | 
					                let mut cmd = Command::new("/usr/bin/dd");
 | 
				
			||||||
 | 
					                cmd.arg("if=/dev/zero")
 | 
				
			||||||
 | 
					                    .arg(format!("of={}", file.to_string_lossy()))
 | 
				
			||||||
 | 
					                    .arg("bs=1M");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                match alloc_type {
 | 
				
			||||||
 | 
					                    DiskAllocType::Fixed => cmd.arg(format!("count={}", self.size)),
 | 
				
			||||||
 | 
					                    DiskAllocType::Sparse => cmd.arg(format!("seek={}", self.size)).arg("count=0"),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                cmd.output()?
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            DiskFormat::QCow2 => {
 | 
				
			||||||
 | 
					                let mut cmd = Command::new("/usr/bin/qemu-img");
 | 
				
			||||||
 | 
					                cmd.arg("create")
 | 
				
			||||||
 | 
					                    .arg("-f")
 | 
				
			||||||
 | 
					                    .arg("qcow2")
 | 
				
			||||||
 | 
					                    .arg(file)
 | 
				
			||||||
 | 
					                    .arg(format!("{}M", self.size));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                cmd.output()?
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Execute Linux command
 | 
				
			||||||
 | 
					        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(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
 | 
					struct QCowInfoOutput {
 | 
				
			||||||
 | 
					    #[serde(rename = "virtual-size")]
 | 
				
			||||||
 | 
					    virtual_size: usize,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Get QCow2 virtual size
 | 
				
			||||||
 | 
					fn qcow_virt_size(path: &str) -> anyhow::Result<usize> {
 | 
				
			||||||
 | 
					    // Run qemu-img
 | 
				
			||||||
 | 
					    let mut cmd = Command::new("qemu-img");
 | 
				
			||||||
 | 
					    cmd.args(["info", path, "--output", "json", "--force-share"]);
 | 
				
			||||||
 | 
					    let output = cmd.output()?;
 | 
				
			||||||
 | 
					    if !output.status.success() {
 | 
				
			||||||
 | 
					        anyhow::bail!(
 | 
				
			||||||
 | 
					            "qemu-img info failed, status: {}, stderr: {}",
 | 
				
			||||||
 | 
					            output.status,
 | 
				
			||||||
 | 
					            String::from_utf8_lossy(&output.stderr)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let res_json = String::from_utf8(output.stdout)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Decode JSON
 | 
				
			||||||
 | 
					    let decoded: QCowInfoOutput = serde_json::from_str(&res_json)?;
 | 
				
			||||||
 | 
					    Ok(decoded.virtual_size)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
pub mod disks_utils;
 | 
					pub mod file_disks_utils;
 | 
				
			||||||
pub mod files_utils;
 | 
					pub mod files_utils;
 | 
				
			||||||
pub mod net_utils;
 | 
					pub mod net_utils;
 | 
				
			||||||
pub mod rand_utils;
 | 
					pub mod rand_utils;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								virtweb_docs/REFERENCE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								virtweb_docs/REFERENCE.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					## References
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### LibVirt XML documentation
 | 
				
			||||||
 | 
					* Online: https://libvirt.org/format.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Offline with Ubuntu:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					sudo apt install libvirt-doc
 | 
				
			||||||
 | 
					firefox /usr/share/doc/libvirt-doc/html/index.html
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
		Reference in New Issue
	
	Block a user