VirtWeb/virtweb_backend/src/utils/disks_utils.rs

134 lines
3.8 KiB
Rust

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(())
}
}