diff --git a/virtweb_backend/src/constants.rs b/virtweb_backend/src/constants.rs index 214ad6b..1822d6d 100644 --- a/virtweb_backend/src/constants.rs +++ b/virtweb_backend/src/constants.rs @@ -137,6 +137,9 @@ pub const PROGRAM_COPY: &str = "/bin/cp"; /// Gzip program path pub const PROGRAM_GZIP: &str = "/usr/bin/gzip"; +/// XZ program path +pub const PROGRAM_XZ: &str = "/usr/bin/xz"; + /// Bash program pub const PROGRAM_BASH: &str = "/usr/bin/bash"; diff --git a/virtweb_backend/src/utils/file_disks_utils.rs b/virtweb_backend/src/utils/file_disks_utils.rs index db1d40f..012f894 100644 --- a/virtweb_backend/src/utils/file_disks_utils.rs +++ b/virtweb_backend/src/utils/file_disks_utils.rs @@ -28,8 +28,10 @@ pub enum DiskFileFormat { #[serde(default)] virtual_size: FileSize, }, - CompressedRaw, - CompressedQCow2, + GzCompressedRaw, + GzCompressedQCow2, + XzCompressedRaw, + XzCompressedQCow2, } impl DiskFileFormat { @@ -37,8 +39,10 @@ impl DiskFileFormat { match self { DiskFileFormat::Raw { .. } => &["raw", ""], DiskFileFormat::QCow2 { .. } => &["qcow2"], - DiskFileFormat::CompressedRaw => &["raw.gz"], - DiskFileFormat::CompressedQCow2 => &["qcow2.gz"], + DiskFileFormat::GzCompressedRaw => &["raw.gz"], + DiskFileFormat::GzCompressedQCow2 => &["qcow2.gz"], + DiskFileFormat::XzCompressedRaw => &["raw.xz"], + DiskFileFormat::XzCompressedQCow2 => &["qcow2.xz"], } } } @@ -81,9 +85,14 @@ impl DiskFileInfo { }, "gz" if name.ends_with(".qcow2") => { name = name.strip_suffix(".qcow2").unwrap_or(&name).to_string(); - DiskFileFormat::CompressedQCow2 + DiskFileFormat::GzCompressedQCow2 } - "gz" => DiskFileFormat::CompressedRaw, + "gz" => DiskFileFormat::GzCompressedRaw, + "xz" if name.ends_with(".qcow2") => { + name = name.strip_suffix(".qcow2").unwrap_or(&name).to_string(); + DiskFileFormat::XzCompressedQCow2 + } + "xz" => DiskFileFormat::XzCompressedRaw, _ => anyhow::bail!("Unsupported disk extension: {ext}!"), }; @@ -159,8 +168,8 @@ impl DiskFileInfo { // Prepare the conversion let mut cmd = match (self.format, dest_format) { - // Decompress QCow2 - (DiskFileFormat::CompressedQCow2, DiskFileFormat::QCow2 { .. }) => { + // Decompress QCow2 (GZIP) + (DiskFileFormat::GzCompressedQCow2, DiskFileFormat::QCow2 { .. }) => { let mut cmd = Command::new(constants::PROGRAM_GZIP); cmd.arg("--keep") .arg("--decompress") @@ -170,8 +179,19 @@ impl DiskFileInfo { cmd } - // Compress QCow2 - (DiskFileFormat::QCow2 { .. }, DiskFileFormat::CompressedQCow2) => { + // Decompress QCow2 (XZ) + (DiskFileFormat::XzCompressedQCow2, DiskFileFormat::QCow2 { .. }) => { + let mut cmd = Command::new(constants::PROGRAM_XZ); + cmd.arg("--stdout") + .arg("--keep") + .arg("--decompress") + .arg(&self.file_path) + .stdout(File::create(&temp_file)?); + cmd + } + + // Compress QCow2 (Gzip) + (DiskFileFormat::QCow2 { .. }, DiskFileFormat::GzCompressedQCow2) => { let mut cmd = Command::new(constants::PROGRAM_GZIP); cmd.arg("--keep") .arg("--to-stdout") @@ -180,6 +200,16 @@ impl DiskFileInfo { cmd } + // Compress QCow2 (Xz) + (DiskFileFormat::QCow2 { .. }, DiskFileFormat::XzCompressedQCow2) => { + let mut cmd = Command::new(constants::PROGRAM_XZ); + cmd.arg("--keep") + .arg("--to-stdout") + .arg(&self.file_path) + .stdout(File::create(&temp_file)?); + cmd + } + // Convert QCow2 to Raw file (DiskFileFormat::QCow2 { .. }, DiskFileFormat::Raw { is_sparse }) => { let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE); @@ -245,7 +275,7 @@ impl DiskFileInfo { } // Compress Raw - (DiskFileFormat::Raw { .. }, DiskFileFormat::CompressedRaw) => { + (DiskFileFormat::Raw { .. }, DiskFileFormat::GzCompressedRaw) => { let mut cmd = Command::new(constants::PROGRAM_GZIP); cmd.arg("--keep") .arg("--to-stdout") @@ -255,7 +285,7 @@ impl DiskFileInfo { } // Decompress Raw to not sparse file - (DiskFileFormat::CompressedRaw, DiskFileFormat::Raw { is_sparse: false }) => { + (DiskFileFormat::GzCompressedRaw, DiskFileFormat::Raw { is_sparse: false }) => { let mut cmd = Command::new(constants::PROGRAM_GZIP); cmd.arg("--keep") .arg("--decompress") @@ -267,7 +297,7 @@ impl DiskFileInfo { // Decompress Raw to sparse file // https://benou.fr/www/ben/decompressing-sparse-files.html - (DiskFileFormat::CompressedRaw, DiskFileFormat::Raw { is_sparse: true }) => { + (DiskFileFormat::GzCompressedRaw, DiskFileFormat::Raw { is_sparse: true }) => { let mut cmd = Command::new(constants::PROGRAM_BASH); cmd.arg("-c").arg(format!( "{} -d -c {} | {} conv=sparse of={}", diff --git a/virtweb_frontend/src/api/DiskImageApi.ts b/virtweb_frontend/src/api/DiskImageApi.ts index 3db7bbe..bcca6c4 100644 --- a/virtweb_frontend/src/api/DiskImageApi.ts +++ b/virtweb_frontend/src/api/DiskImageApi.ts @@ -4,8 +4,8 @@ import { VMFileDisk, VMInfo } from "./VMApi"; export type DiskImageFormat = | { format: "Raw"; is_sparse: boolean } | { format: "QCow2"; virtual_size?: number } - | { format: "CompressedQCow2" } - | { format: "CompressedRaw" }; + | { format: "GzCompressedQCow2" } + | { format: "GzCompressedRaw" }; export type DiskImage = { file_size: number; diff --git a/virtweb_frontend/src/dialogs/ConvertDiskImageDialog.tsx b/virtweb_frontend/src/dialogs/ConvertDiskImageDialog.tsx index f64e229..830d8b3 100644 --- a/virtweb_frontend/src/dialogs/ConvertDiskImageDialog.tsx +++ b/virtweb_frontend/src/dialogs/ConvertDiskImageDialog.tsx @@ -42,13 +42,15 @@ export function ConvertDiskImageDialog( setFormat({ format: value ?? ("QCow2" as any) }); if (value === "QCow2") setFilename(`${origFilename}.qcow2`); - if (value === "CompressedQCow2") setFilename(`${origFilename}.qcow2.gz`); + if (value === "GzCompressedQCow2") setFilename(`${origFilename}.qcow2.gz`); + if (value === "XzCompressedQCow2") setFilename(`${origFilename}.qcow2.xz`); if (value === "Raw") { setFilename(`${origFilename}.raw`); // Check sparse checkbox by default setFormat({ format: "Raw", is_sparse: true }); } - if (value === "CompressedRaw") setFilename(`${origFilename}.raw.gz`); + if (value === "GzCompressedRaw") setFilename(`${origFilename}.raw.gz`); + if (value === "XzCompressedRaw") setFilename(`${origFilename}.raw.xz`); }; const handleSubmit = async () => { @@ -104,8 +106,10 @@ export function ConvertDiskImageDialog( options={[ { value: "QCow2" }, { value: "Raw" }, - { value: "CompressedRaw" }, - { value: "CompressedQCow2" }, + { value: "GzCompressedRaw" }, + { value: "XzCompressedRaw" }, + { value: "GzCompressedQCow2" }, + { value: "XzCompressedQCow2" }, ]} />