use crate::app_config::AppConfig; use crate::constants; use crate::controllers::{HttpResult, LibVirtReq}; use crate::libvirt_lib_structures::XMLUuid; use crate::libvirt_rest_structures::vm::VMInfo; use crate::utils::file_disks_utils::{DiskFileFormat, DiskFileInfo}; use crate::utils::files_utils; use actix_files::NamedFile; use actix_multipart::form::MultipartForm; use actix_multipart::form::tempfile::TempFile; use actix_web::{HttpRequest, HttpResponse, web}; #[derive(Debug, MultipartForm)] pub struct UploadDiskImageForm { #[multipart(rename = "file")] files: Vec, } /// Upload disk image file pub async fn upload(MultipartForm(mut form): MultipartForm) -> HttpResult { if form.files.is_empty() { log::error!("Missing uploaded disk file!"); return Ok(HttpResponse::BadRequest().json("Missing file!")); } let file = form.files.remove(0); // Check uploaded file size if file.size > constants::DISK_IMAGE_MAX_SIZE.as_bytes() { return Ok(HttpResponse::BadRequest().json("Disk image max size exceeded!")); } // Check file mime type if let Some(mime_type) = file.content_type { if !constants::ALLOWED_DISK_IMAGES_MIME_TYPES.contains(&mime_type.as_ref()) { return Ok(HttpResponse::BadRequest().json(format!( "Unsupported file type for disk upload: {}", mime_type ))); } } // Extract and check file name let Some(file_name) = file.file_name else { return Ok(HttpResponse::BadRequest().json("Missing file name of uploaded file!")); }; if !files_utils::check_file_name(&file_name) { return Ok(HttpResponse::BadRequest().json("Invalid uploaded file name!")); } // Check if a file with the same name already exists let dest_path = AppConfig::get().disk_images_file_path(&file_name); if dest_path.is_file() { return Ok(HttpResponse::Conflict().json("A file with the same name already exists!")); } // Copy the file to the destination file.file.persist(&dest_path)?; // Check if file information can be loaded if let Err(e) = DiskFileInfo::load_file(&dest_path) { log::error!("Failed to get information about uploaded disk file! {e}"); std::fs::remove_file(&dest_path)?; return Ok(HttpResponse::InternalServerError() .json(format!("Unable to process uploaded file! {e}"))); } Ok(HttpResponse::Ok().json("Successfully uploaded disk image!")) } /// Get disk images list pub async fn get_list() -> HttpResult { let mut list = vec![]; for entry in AppConfig::get().disk_images_storage_path().read_dir()? { let entry = entry?; list.push(DiskFileInfo::load_file(&entry.path())?); } Ok(HttpResponse::Ok().json(list)) } #[derive(serde::Deserialize)] pub struct DiskFilePath { filename: String, } /// Download disk image pub async fn download(p: web::Path, req: HttpRequest) -> HttpResult { if !files_utils::check_file_name(&p.filename) { return Ok(HttpResponse::BadRequest().json("Invalid file name!")); } let file_path = AppConfig::get().disk_images_file_path(&p.filename); if !file_path.exists() { return Ok(HttpResponse::NotFound().json("Disk image does not exists!")); } Ok(NamedFile::open(file_path)?.into_response(&req)) } #[derive(serde::Deserialize)] pub struct ConvertDiskImageRequest { dest_file_name: String, #[serde(flatten)] format: DiskFileFormat, } /// Convert disk image into a new format pub async fn convert( p: web::Path, req: web::Json, ) -> HttpResult { if !files_utils::check_file_name(&p.filename) { return Ok(HttpResponse::BadRequest().json("Invalid src file name!")); } let src_file_path = AppConfig::get().disk_images_file_path(&p.filename); let src = DiskFileInfo::load_file(&src_file_path)?; handle_convert_request(src, &req).await } #[derive(serde::Deserialize)] pub struct BackupVMDiskPath { uid: XMLUuid, diskid: String, } /// Perform disk backup pub async fn backup_disk( client: LibVirtReq, path: web::Path, req: web::Json, ) -> HttpResult { // Get the VM information let info = match client.get_single_domain(path.uid).await { Ok(i) => i, Err(e) => { log::error!("Failed to get domain info! {e}"); return Ok(HttpResponse::InternalServerError().json(e.to_string())); } }; let vm = VMInfo::from_domain(info)?; // Load disk information let Some(disk) = vm .file_disks .into_iter() .find(|disk| disk.name == path.diskid) else { return Ok(HttpResponse::NotFound() .json(format!("Disk {} not found for vm {}", path.diskid, vm.name))); }; let src_path = disk.disk_path(vm.uuid.expect("Missing VM uuid!")); let src_disk = DiskFileInfo::load_file(&src_path)?; // Perform conversion handle_convert_request(src_disk, &req).await } /// Generic controller code that performs image conversion to create a disk image file pub async fn handle_convert_request( src: DiskFileInfo, req: &ConvertDiskImageRequest, ) -> HttpResult { // Check destination file if !files_utils::check_file_name(&req.dest_file_name) { return Ok(HttpResponse::BadRequest().json("Invalid destination file name!")); } if !req .format .ext() .iter() .any(|e| req.dest_file_name.ends_with(e)) { return Ok(HttpResponse::BadRequest().json("Invalid destination file extension!")); } let dst_file_path = AppConfig::get().disk_images_file_path(&req.dest_file_name); if dst_file_path.exists() { return Ok(HttpResponse::Conflict().json("Specified destination file already exists!")); } // Perform conversion if let Err(e) = src.convert(&dst_file_path, req.format) { log::error!("Disk file conversion error: {e}"); return Ok( HttpResponse::InternalServerError().json(format!("Disk file conversion error: {e}")) ); } Ok(HttpResponse::Accepted().json("Successfully converted disk file")) } #[derive(serde::Deserialize)] pub struct RenameDiskImageRequest { name: String, } /// Rename disk image pub async fn rename( p: web::Path, req: web::Json, ) -> HttpResult { // Check source if !files_utils::check_file_name(&p.filename) { return Ok(HttpResponse::BadRequest().json("Invalid src file name!")); } let src_path = AppConfig::get().disk_images_file_path(&p.filename); if !src_path.exists() { return Ok(HttpResponse::NotFound().json("Disk image does not exists!")); } // Check destination if !files_utils::check_file_name(&req.name) { return Ok(HttpResponse::BadRequest().json("Invalid dst file name!")); } let dst_path = AppConfig::get().disk_images_file_path(&req.name); if dst_path.exists() { return Ok(HttpResponse::Conflict().json("Destination name already exists!")); } // Check extension let disk = DiskFileInfo::load_file(&src_path)?; if !disk.format.ext().iter().any(|e| req.name.ends_with(e)) { return Ok(HttpResponse::BadRequest().json("Invalid destination file extension!")); } // Perform rename std::fs::rename(&src_path, &dst_path)?; Ok(HttpResponse::Accepted().finish()) } /// Delete a disk image pub async fn delete(p: web::Path) -> HttpResult { if !files_utils::check_file_name(&p.filename) { return Ok(HttpResponse::BadRequest().json("Invalid file name!")); } let file_path = AppConfig::get().disk_images_file_path(&p.filename); if !file_path.exists() { return Ok(HttpResponse::NotFound().json("Disk image does not exists!")); } std::fs::remove_file(file_path)?; Ok(HttpResponse::Accepted().finish()) }