Add qcow2.xz file format support

This commit is contained in:
2025-06-09 16:58:21 +02:00
parent b2529c250a
commit 759361d9f6
4 changed files with 56 additions and 19 deletions

View File

@ -137,6 +137,9 @@ pub const PROGRAM_COPY: &str = "/bin/cp";
/// Gzip program path /// Gzip program path
pub const PROGRAM_GZIP: &str = "/usr/bin/gzip"; pub const PROGRAM_GZIP: &str = "/usr/bin/gzip";
/// XZ program path
pub const PROGRAM_XZ: &str = "/usr/bin/xz";
/// Bash program /// Bash program
pub const PROGRAM_BASH: &str = "/usr/bin/bash"; pub const PROGRAM_BASH: &str = "/usr/bin/bash";

View File

@ -28,8 +28,10 @@ pub enum DiskFileFormat {
#[serde(default)] #[serde(default)]
virtual_size: FileSize, virtual_size: FileSize,
}, },
CompressedRaw, GzCompressedRaw,
CompressedQCow2, GzCompressedQCow2,
XzCompressedRaw,
XzCompressedQCow2,
} }
impl DiskFileFormat { impl DiskFileFormat {
@ -37,8 +39,10 @@ impl DiskFileFormat {
match self { match self {
DiskFileFormat::Raw { .. } => &["raw", ""], DiskFileFormat::Raw { .. } => &["raw", ""],
DiskFileFormat::QCow2 { .. } => &["qcow2"], DiskFileFormat::QCow2 { .. } => &["qcow2"],
DiskFileFormat::CompressedRaw => &["raw.gz"], DiskFileFormat::GzCompressedRaw => &["raw.gz"],
DiskFileFormat::CompressedQCow2 => &["qcow2.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") => { "gz" if name.ends_with(".qcow2") => {
name = name.strip_suffix(".qcow2").unwrap_or(&name).to_string(); 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}!"), _ => anyhow::bail!("Unsupported disk extension: {ext}!"),
}; };
@ -159,8 +168,8 @@ impl DiskFileInfo {
// Prepare the conversion // Prepare the conversion
let mut cmd = match (self.format, dest_format) { let mut cmd = match (self.format, dest_format) {
// Decompress QCow2 // Decompress QCow2 (GZIP)
(DiskFileFormat::CompressedQCow2, DiskFileFormat::QCow2 { .. }) => { (DiskFileFormat::GzCompressedQCow2, DiskFileFormat::QCow2 { .. }) => {
let mut cmd = Command::new(constants::PROGRAM_GZIP); let mut cmd = Command::new(constants::PROGRAM_GZIP);
cmd.arg("--keep") cmd.arg("--keep")
.arg("--decompress") .arg("--decompress")
@ -170,8 +179,19 @@ impl DiskFileInfo {
cmd cmd
} }
// Compress QCow2 // Decompress QCow2 (XZ)
(DiskFileFormat::QCow2 { .. }, DiskFileFormat::CompressedQCow2) => { (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); let mut cmd = Command::new(constants::PROGRAM_GZIP);
cmd.arg("--keep") cmd.arg("--keep")
.arg("--to-stdout") .arg("--to-stdout")
@ -180,6 +200,16 @@ impl DiskFileInfo {
cmd 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 // Convert QCow2 to Raw file
(DiskFileFormat::QCow2 { .. }, DiskFileFormat::Raw { is_sparse }) => { (DiskFileFormat::QCow2 { .. }, DiskFileFormat::Raw { is_sparse }) => {
let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE); let mut cmd = Command::new(constants::PROGRAM_QEMU_IMAGE);
@ -245,7 +275,7 @@ impl DiskFileInfo {
} }
// Compress Raw // Compress Raw
(DiskFileFormat::Raw { .. }, DiskFileFormat::CompressedRaw) => { (DiskFileFormat::Raw { .. }, DiskFileFormat::GzCompressedRaw) => {
let mut cmd = Command::new(constants::PROGRAM_GZIP); let mut cmd = Command::new(constants::PROGRAM_GZIP);
cmd.arg("--keep") cmd.arg("--keep")
.arg("--to-stdout") .arg("--to-stdout")
@ -255,7 +285,7 @@ impl DiskFileInfo {
} }
// Decompress Raw to not sparse file // 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); let mut cmd = Command::new(constants::PROGRAM_GZIP);
cmd.arg("--keep") cmd.arg("--keep")
.arg("--decompress") .arg("--decompress")
@ -267,7 +297,7 @@ impl DiskFileInfo {
// Decompress Raw to sparse file // Decompress Raw to sparse file
// https://benou.fr/www/ben/decompressing-sparse-files.html // 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); let mut cmd = Command::new(constants::PROGRAM_BASH);
cmd.arg("-c").arg(format!( cmd.arg("-c").arg(format!(
"{} -d -c {} | {} conv=sparse of={}", "{} -d -c {} | {} conv=sparse of={}",

View File

@ -4,8 +4,8 @@ import { VMFileDisk, VMInfo } from "./VMApi";
export type DiskImageFormat = export type DiskImageFormat =
| { format: "Raw"; is_sparse: boolean } | { format: "Raw"; is_sparse: boolean }
| { format: "QCow2"; virtual_size?: number } | { format: "QCow2"; virtual_size?: number }
| { format: "CompressedQCow2" } | { format: "GzCompressedQCow2" }
| { format: "CompressedRaw" }; | { format: "GzCompressedRaw" };
export type DiskImage = { export type DiskImage = {
file_size: number; file_size: number;

View File

@ -42,13 +42,15 @@ export function ConvertDiskImageDialog(
setFormat({ format: value ?? ("QCow2" as any) }); setFormat({ format: value ?? ("QCow2" as any) });
if (value === "QCow2") setFilename(`${origFilename}.qcow2`); 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") { if (value === "Raw") {
setFilename(`${origFilename}.raw`); setFilename(`${origFilename}.raw`);
// Check sparse checkbox by default // Check sparse checkbox by default
setFormat({ format: "Raw", is_sparse: true }); 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 () => { const handleSubmit = async () => {
@ -104,8 +106,10 @@ export function ConvertDiskImageDialog(
options={[ options={[
{ value: "QCow2" }, { value: "QCow2" },
{ value: "Raw" }, { value: "Raw" },
{ value: "CompressedRaw" }, { value: "GzCompressedRaw" },
{ value: "CompressedQCow2" }, { value: "XzCompressedRaw" },
{ value: "GzCompressedQCow2" },
{ value: "XzCompressedQCow2" },
]} ]}
/> />