diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java index 751a16a15541..608ad3c69cba 100644 --- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java +++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.regex.Pattern; +import org.apache.cloudstack.storage.formatinspector.Qcow2Inspector; import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang3.StringUtils; import org.libvirt.LibvirtException; @@ -53,6 +54,8 @@ public class QemuImg { public static final long QEMU_2_10 = 2010000; public static final long QEMU_5_10 = 5010000; + public static final int MIN_BITMAP_VERSION = 3; + /* The qemu-img binary. We expect this to be in $PATH */ public String _qemuImgPath = "qemu-img"; private String cloudQemuImgPath = "cloud-qemu-img"; @@ -466,7 +469,7 @@ public void convert(final QemuImgFile srcFile, final QemuImgFile destFile, script.add(srcFile.getFileName()); } - if (this.version >= QEMU_5_10 && keepBitmaps) { + if (this.version >= QEMU_5_10 && keepBitmaps && Qcow2Inspector.validateQcow2Version(srcFile.getFileName(), MIN_BITMAP_VERSION)) { script.add("--bitmaps"); } diff --git a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/formatinspector/Qcow2Inspector.java b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/formatinspector/Qcow2Inspector.java index 9d80064d292e..bc06d7490787 100644 --- a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/formatinspector/Qcow2Inspector.java +++ b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/formatinspector/Qcow2Inspector.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.storage.formatinspector; import com.cloud.utils.NumbersUtil; +import com.cloud.utils.exception.CloudRuntimeException; import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -25,6 +26,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.math.BigInteger; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -108,6 +110,32 @@ public static Map unravelQcow2Header(InputStream qcow2InputStrea return result; } + + /** + * Validates if the file has a minimum version. + * @param filePath Path of the file to be validated. + * @param minVersion the minimum version that it should contain. + * @throws RuntimeException If a IOException is thrown. + * @return true if file version is >= minVersion, false otherwise. + */ + public static boolean validateQcow2Version(String filePath, int minVersion) { + try (InputStream inputStream = new FileInputStream(filePath)) { + for (Qcow2HeaderField qcow2Header : Qcow2HeaderField.values()) { + if (qcow2Header != Qcow2HeaderField.VERSION) { + skipHeader(inputStream, qcow2Header, filePath); + continue; + } + + byte[] headerValue = readHeader(inputStream, qcow2Header, filePath); + int version = new BigInteger(headerValue).intValue(); + return version >= minVersion; + } + } catch (IOException ex) { + throw new CloudRuntimeException(String.format("Unable to validate file [%s] due to: ", filePath), ex); + } + return false; + } + /** * Skips the field's length in the InputStream. * @param qcow2InputStream InputStream of the QCOW2 being unraveled.