[PATCH v3 00/15] Implement support for QCOW2 data files

Hello everyone! With help of Peter's comprehensive review I finally present 3rd version of this series. Changes since last revision: - minor code improvements including memory leaks fixes - splitted and regrouped some patches for more logical structure - fixed issue with erroneous disk with data file detatch - completely rewritten tests - added some piece of documentation Nikolai Barybin (15): conf: add data-file feature and related fields to virStorageSource Add VIR_STORAGE_FILE_FEATURE_DATA_FILE to virStorageFileFeature enum conf: schemas: add data-file store to domain rng schema conf: implement XML parsing/formating for dataFileStore storage file: add getDataFile function to FileTypeInfo storage file: add qcow2 data-file path parsing from header storage file: fill in src->dataFileStore during file probe security: DAC: handle qcow2 data-file on image label set/restore security: selinux: handle qcow2 data-file on image label set/restore security: apparmor: handle qcow2 data-file qemu: put data-file path to VM's cgroup and namespace qemu: factor out qemuDomainPrepareStorageSource() qemu: enable basic qcow2 data-file feature support tests: add qcow2 data-file tests docs: formatdomain: describe dataFileStore element of disk docs/formatdomain.rst | 45 +++++++- src/conf/domain_conf.c | 100 ++++++++++++++++++ src/conf/domain_conf.h | 13 +++ src/conf/schemas/domaincommon.rng | 15 +++ src/conf/storage_source_conf.c | 11 ++ src/conf/storage_source_conf.h | 5 + src/qemu/qemu_block.c | 14 +++ src/qemu/qemu_cgroup.c | 13 ++- src/qemu/qemu_command.c | 5 + src/qemu/qemu_domain.c | 50 ++++++--- src/qemu/qemu_namespace.c | 7 ++ src/security/security_dac.c | 27 ++++- src/security/security_selinux.c | 27 ++++- src/security/virt-aa-helper.c | 4 + src/storage_file/storage_file_probe.c | 78 ++++++++++---- src/storage_file/storage_source.c | 39 +++++++ src/storage_file/storage_source.h | 4 + .../qcow2-data-file-in.xml | 86 +++++++++++++++ .../qcow2-data-file-out.xml | 86 +++++++++++++++ tests/qemuxmlactivetest.c | 2 + ...sk-qcow2-datafile-store.x86_64-latest.args | 51 +++++++++ ...isk-qcow2-datafile-store.x86_64-latest.xml | 81 ++++++++++++++ .../disk-qcow2-datafile-store.xml | 67 ++++++++++++ tests/qemuxmlconftest.c | 1 + tests/virstoragetest.c | 98 +++++++++++++++++ tests/virstoragetestdata/out/qcow2-data_file | 20 ++++ .../out/qcow2-qcow2_qcow2-qcow2-data_file | 31 ++++++ 27 files changed, 941 insertions(+), 39 deletions(-) create mode 100644 tests/qemustatusxml2xmldata/qcow2-data-file-in.xml create mode 100644 tests/qemustatusxml2xmldata/qcow2-data-file-out.xml create mode 100644 tests/qemuxmlconfdata/disk-qcow2-datafile-store.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/disk-qcow2-datafile-store.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/disk-qcow2-datafile-store.xml create mode 100644 tests/virstoragetestdata/out/qcow2-data_file create mode 100644 tests/virstoragetestdata/out/qcow2-qcow2_qcow2-qcow2-data_file -- 2.43.5

Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/conf/storage_source_conf.c | 10 ++++++++++ src/conf/storage_source_conf.h | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/src/conf/storage_source_conf.c b/src/conf/storage_source_conf.c index 5b9a80f100..d4e39b9b57 100644 --- a/src/conf/storage_source_conf.c +++ b/src/conf/storage_source_conf.c @@ -829,6 +829,7 @@ virStorageSourceCopy(const virStorageSource *src, def->relPath = g_strdup(src->relPath); def->backingStoreRaw = g_strdup(src->backingStoreRaw); def->backingStoreRawFormat = src->backingStoreRawFormat; + def->dataFileRaw = g_strdup(src->dataFileRaw); def->snapshot = g_strdup(src->snapshot); def->configFile = g_strdup(src->configFile); def->nodenameformat = g_strdup(src->nodenameformat); @@ -894,6 +895,12 @@ virStorageSourceCopy(const virStorageSource *src, return NULL; } + if (src->dataFileStore) { + if (!(def->dataFileStore = virStorageSourceCopy(src->dataFileStore, + false))) + return NULL; + } + if (src->fdtuple) def->fdtuple = g_object_ref(src->fdtuple); @@ -1174,6 +1181,9 @@ virStorageSourceClear(virStorageSource *def) VIR_FREE(def->nodenamestorage); VIR_FREE(def->nodenameformat); + VIR_FREE(def->dataFileRaw); + g_clear_pointer(&def->dataFileStore, virObjectUnref); + virStorageSourceBackingStoreClear(def); VIR_FREE(def->tlsAlias); diff --git a/src/conf/storage_source_conf.h b/src/conf/storage_source_conf.h index a507116007..aa2aa680de 100644 --- a/src/conf/storage_source_conf.h +++ b/src/conf/storage_source_conf.h @@ -359,6 +359,9 @@ struct _virStorageSource { /* backing chain of the storage source */ virStorageSource *backingStore; + /* qcow2 data file source */ + virStorageSource *dataFileStore; + /* metadata for storage driver access to remote and local volumes */ void *drv; @@ -369,6 +372,7 @@ struct _virStorageSource { /* Name of the child backing store recorded in metadata of the * current file. */ char *backingStoreRaw; + char *dataFileRaw; virStorageFileFormat backingStoreRawFormat; /* metadata that allows identifying given storage source */ -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:36 +0300, Nikolai Barybin via Devel wrote: I'll be adding a commit message: The 'data-file' is a qcow2 feature which allows storing the actual data outside of the qcow2 image.
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/conf/storage_source_conf.c | 10 ++++++++++ src/conf/storage_source_conf.h | 4 ++++ 2 files changed, 14 insertions(+)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/conf/storage_source_conf.c | 1 + src/conf/storage_source_conf.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/conf/storage_source_conf.c b/src/conf/storage_source_conf.c index d4e39b9b57..2b658dd485 100644 --- a/src/conf/storage_source_conf.c +++ b/src/conf/storage_source_conf.c @@ -69,6 +69,7 @@ VIR_ENUM_IMPL(virStorageFileFeature, VIR_STORAGE_FILE_FEATURE_LAST, "lazy_refcounts", "extended_l2", + "data_file", ); diff --git a/src/conf/storage_source_conf.h b/src/conf/storage_source_conf.h index aa2aa680de..9463722518 100644 --- a/src/conf/storage_source_conf.h +++ b/src/conf/storage_source_conf.h @@ -88,6 +88,7 @@ VIR_ENUM_DECL(virStorageFileFormat); typedef enum { VIR_STORAGE_FILE_FEATURE_LAZY_REFCOUNTS = 0, VIR_STORAGE_FILE_FEATURE_EXTENDED_L2, + VIR_STORAGE_FILE_FEATURE_DATA_FILE, VIR_STORAGE_FILE_FEATURE_LAST } virStorageFileFeature; -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:37 +0300, Nikolai Barybin via Devel wrote:
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/conf/storage_source_conf.c | 1 + src/conf/storage_source_conf.h | 1 + 2 files changed, 2 insertions(+)
I'll be considering squashing this one into 6/15 as it's not used anywhere else except that, or I'll add a commit message if I decide to keep it separate. Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/conf/schemas/domaincommon.rng | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng index bfd0044805..e70896b7ef 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -1783,6 +1783,9 @@ <ref name="diskBackingChain"/> <ref name="privateDataDeviceDisk"/> </interleave> + <optional> + <ref name="diskDataFile"/> + </optional> </define> <define name="diskBackingChain"> @@ -1803,6 +1806,18 @@ <ref name="diskSource"/> <ref name="diskBackingChain"/> <ref name="diskFormat"/> + <optional> + <ref name="diskDataFile"/> + </optional> + </interleave> + </element> + </define> + + <define name="diskDataFile"> + <element name="dataFileStore"> + <interleave> + <ref name="diskFormat"/> + <ref name="diskSource"/> </interleave> </element> </define> -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:38 +0300, Nikolai Barybin via Devel wrote:
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/conf/schemas/domaincommon.rng | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng index bfd0044805..e70896b7ef 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -1783,6 +1783,9 @@ <ref name="diskBackingChain"/> <ref name="privateDataDeviceDisk"/> </interleave> + <optional> + <ref name="diskDataFile"/> + </optional> </define>
<define name="diskBackingChain"> @@ -1803,6 +1806,18 @@ <ref name="diskSource"/> <ref name="diskBackingChain"/> <ref name="diskFormat"/> + <optional> + <ref name="diskDataFile"/> + </optional> + </interleave> + </element> + </define> + + <define name="diskDataFile"> + <element name="dataFileStore">
I'm going to rename this to 'dataStore' since it can be e.g also a block device. I'm also strongly considering making this a child of <source> instead of placing it in the disk like <backingStore>. I'll have a look how the parser/formatter changes and then I'll decide.
+ <interleave> + <ref name="diskFormat"/> + <ref name="diskSource"/> </interleave> </element> </define> -- 2.43.5

Data files are simple raw images. Thus, we don't need to parse too much. The main objectives are: - allow only RAW format - forbid storage slices - include this parsing/formatting into backing chain parse/format as well as into top storage source parse/format because data file can belong to backing image Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/conf/domain_conf.c | 100 +++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 13 ++++++ 2 files changed, 113 insertions(+) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 295707ec1f..9348c12725 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -7670,6 +7670,62 @@ virDomainStorageSourceParse(xmlNodePtr node, } +int +virDomainDiskDataFileStoreParse(xmlXPathContextPtr ctxt, + virStorageSource *src, + unsigned int flags, + virDomainXMLOption *xmlopt) +{ + VIR_XPATH_NODE_AUTORESTORE(ctxt) + xmlNodePtr source; + g_autofree char *type = NULL; + g_autofree char *format = NULL; + g_autoptr(virStorageSource) dataFileStore = NULL; + + if (!(ctxt->node = virXPathNode("./dataFileStore", ctxt))) + return 0; + + if (!(type = virXMLPropStringRequired(ctxt->node, "type"))) + return -1; + + if (!(format = virXPathString("string(./format/@type)", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing data file store format")); + return -1; + } + + if (!(source = virXPathNode("./source", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing disk data file store source")); + return -1; + } + + if (virStorageFileFormatTypeFromString(format) != VIR_STORAGE_FILE_RAW) { + virReportError(VIR_ERR_XML_ERROR, + _("unexpected disk data file format '%1$s', only raw format is supported"), + format); + return -1; + } + + if (!(dataFileStore = virDomainStorageSourceParseBase(type, format, NULL))) + return -1; + + if (virDomainStorageSourceParse(source, ctxt, dataFileStore, flags, xmlopt) < 0) + return -1; + + if (dataFileStore->sliceStorage) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("slices are not supported for fata file store source")); + return -1; + } + + dataFileStore->readonly = src->readonly; + src->dataFileStore = g_steal_pointer(&dataFileStore); + + return 0; +} + + int virDomainDiskBackingStoreParse(xmlXPathContextPtr ctxt, virStorageSource *src, @@ -7720,6 +7776,7 @@ virDomainDiskBackingStoreParse(xmlXPathContextPtr ctxt, backingStore->readonly = true; if (virDomainStorageSourceParse(source, ctxt, backingStore, flags, xmlopt) < 0 || + virDomainDiskDataFileStoreParse(ctxt, backingStore, flags, xmlopt) < 0 || virDomainDiskBackingStoreParse(ctxt, backingStore, flags, xmlopt) < 0) return -1; @@ -8112,6 +8169,9 @@ virDomainDiskDefParseSourceXML(virDomainXMLOption *xmlopt, return NULL; } + if (virDomainDiskDataFileStoreParse(ctxt, src, flags, xmlopt) < 0) + return NULL; + if (virDomainDiskBackingStoreParse(ctxt, src, flags, xmlopt) < 0) return NULL; @@ -22885,6 +22945,38 @@ virDomainDiskSourceFormat(virBuffer *buf, return 0; } +int +virDomainDiskDataFileStoreFormat(virBuffer *buf, + virStorageSource *src, + virDomainXMLOption *xmlopt, + unsigned int flags) +{ + g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER; + g_auto(virBuffer) formatAttrBuf = VIR_BUFFER_INITIALIZER; + g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf); + virStorageSource *dataFileStore = src->dataFileStore; + bool inactive = flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE; + + if (!dataFileStore) + return 0; + + /* don't write detected data file member to inactive xml */ + if (inactive && dataFileStore->detected) + return 0; + + virBufferAsprintf(&attrBuf, " type='%s'", + virStorageTypeToString(dataFileStore->type)); + + virBufferAsprintf(&formatAttrBuf, " type='%s'", "raw"); + virXMLFormatElement(&childBuf, "format", &formatAttrBuf, NULL); + + if (virDomainDiskSourceFormat(&childBuf, dataFileStore, "source", 0, false, + flags, false, false, xmlopt) < 0) + return -1; + + virXMLFormatElement(buf, "dataFileStore", &attrBuf, &childBuf); + return 0; +} int virDomainDiskBackingStoreFormat(virBuffer *buf, @@ -22943,6 +23035,10 @@ virDomainDiskBackingStoreFormat(virBuffer *buf, flags, false, false, xmlopt) < 0) return -1; + if (backingStore->dataFileStore && + virDomainDiskDataFileStoreFormat(&childBuf, backingStore, xmlopt, flags) < 0) + return -1; + if (virDomainDiskBackingStoreFormat(&childBuf, backingStore, xmlopt, flags) < 0) return -1; @@ -23261,6 +23357,10 @@ virDomainDiskDefFormat(virBuffer *buf, if (virDomainDiskBackingStoreFormat(&childBuf, def->src, xmlopt, flags) < 0) return -1; + if (def->src->dataFileStore && + virDomainDiskDataFileStoreFormat(&childBuf, def->src, xmlopt, flags) < 0) + return -1; + virBufferEscapeString(&childBuf, "<backenddomain name='%s'/>\n", def->domain_name); virDomainDiskGeometryDefFormat(&childBuf, def); diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index a187ab4083..4d1939aa1b 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3909,6 +3909,12 @@ int virDomainDiskSourceFormat(virBuffer *buf, bool skipEnc, virDomainXMLOption *xmlopt); +int +virDomainDiskDataFileStoreFormat(virBuffer *buf, + virStorageSource *src, + virDomainXMLOption *xmlopt, + unsigned int flags); + int virDomainDiskBackingStoreFormat(virBuffer *buf, virStorageSource *src, @@ -4426,6 +4432,13 @@ int virDomainStorageSourceParse(xmlNodePtr node, virDomainXMLOption *xmlopt) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); +int +virDomainDiskDataFileStoreParse(xmlXPathContextPtr ctxt, + virStorageSource *src, + unsigned int flags, + virDomainXMLOption *xmlopt) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) G_GNUC_WARN_UNUSED_RESULT; + int virDomainDiskBackingStoreParse(xmlXPathContextPtr ctxt, virStorageSource *src, -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:39 +0300, Nikolai Barybin via Devel wrote:
Data files are simple raw images. Thus, we don't need to parse too much. The main objectives are:
- allow only RAW format - forbid storage slices - include this parsing/formatting into backing chain parse/format as well as into top storage source parse/format because data file can belong to backing image
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/conf/domain_conf.c | 100 +++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 13 ++++++ 2 files changed, 113 insertions(+)
As I've already mentioned before I think that it'll make much more sense if the element is called <dataStore> (as it doesn't necessarily have to be a file) and also that it's nested under: <disk ...> <source file=...> <dataStore type='file'> <source ...> </dataStore> </source> </disk> The data-file qcow2 feature ties the data very closely to the source so I don't think the same treatment as we have with <backingStore> would model this relationship correctly. In fact I consider that <backingStore> should have been also nested as above. I'll do the appropriate adjustment.
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 295707ec1f..9348c12725 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -7670,6 +7670,62 @@ virDomainStorageSourceParse(xmlNodePtr node, }
+int +virDomainDiskDataFileStoreParse(xmlXPathContextPtr ctxt, + virStorageSource *src, + unsigned int flags, + virDomainXMLOption *xmlopt) +{ + VIR_XPATH_NODE_AUTORESTORE(ctxt) + xmlNodePtr source; + g_autofree char *type = NULL; + g_autofree char *format = NULL; + g_autoptr(virStorageSource) dataFileStore = NULL; + + if (!(ctxt->node = virXPathNode("./dataFileStore", ctxt))) + return 0; + + if (!(type = virXMLPropStringRequired(ctxt->node, "type"))) + return -1; + + if (!(format = virXPathString("string(./format/@type)", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing data file store format")); + return -1; + } + + if (!(source = virXPathNode("./source", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing disk data file store source")); + return -1; + } + + if (virStorageFileFormatTypeFromString(format) != VIR_STORAGE_FILE_RAW) { + virReportError(VIR_ERR_XML_ERROR, + _("unexpected disk data file format '%1$s', only raw format is supported"), + format); + return -1; + } + + if (!(dataFileStore = virDomainStorageSourceParseBase(type, format, NULL))) + return -1; + + if (virDomainStorageSourceParse(source, ctxt, dataFileStore, flags, xmlopt) < 0) + return -1; + + if (dataFileStore->sliceStorage) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("slices are not supported for fata file store source")); + return -1; + }
Validation should be done in src/conf/domain_validate.c or the hypervisor-driver specific code. I'll move this there. This is also lacking validation that the disk source is QCOW2 as no other source supports this. I'll add that there as well, as move the format check above. [...]
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index a187ab4083..4d1939aa1b 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3909,6 +3909,12 @@ int virDomainDiskSourceFormat(virBuffer *buf, bool skipEnc, virDomainXMLOption *xmlopt);
+int +virDomainDiskDataFileStoreFormat(virBuffer *buf, + virStorageSource *src, + virDomainXMLOption *xmlopt, + unsigned int flags); + int virDomainDiskBackingStoreFormat(virBuffer *buf, virStorageSource *src, @@ -4426,6 +4432,13 @@ int virDomainStorageSourceParse(xmlNodePtr node, virDomainXMLOption *xmlopt) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
+int +virDomainDiskDataFileStoreParse(xmlXPathContextPtr ctxt, + virStorageSource *src, + unsigned int flags, + virDomainXMLOption *xmlopt) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) G_GNUC_WARN_UNUSED_RESULT; + int virDomainDiskBackingStoreParse(xmlXPathContextPtr ctxt, virStorageSource *src,
Please do not unnecessarily export functions. Neither of these is being used outside of the 'domain_conf.c' module. Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/storage_file/storage_file_probe.c | 34 ++++++++++++++------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/storage_file/storage_file_probe.c b/src/storage_file/storage_file_probe.c index 243927d50a..ad14350edc 100644 --- a/src/storage_file/storage_file_probe.c +++ b/src/storage_file/storage_file_probe.c @@ -93,6 +93,7 @@ struct FileTypeInfo { size_t buf_size); int (*getBackingStore)(char **res, int *format, const char *buf, size_t buf_size); + int (*getDataFile)(char **res, virBitmap *features, char *buf, size_t buf_size); int (*getFeatures)(virBitmap **features, int format, char *buf, ssize_t len); }; @@ -238,18 +239,18 @@ static struct FileEncryptionInfo const qcow2EncryptionInfo[] = { static struct FileTypeInfo const fileTypeInfo[] = { [VIR_STORAGE_FILE_NONE] = { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL, NULL }, + -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL, NULL, NULL }, [VIR_STORAGE_FILE_RAW] = { 0, NULL, LV_LITTLE_ENDIAN, -1, 0, {0}, 0, 0, 0, luksEncryptionInfo, - NULL, NULL, NULL }, + NULL, NULL, NULL, NULL }, [VIR_STORAGE_FILE_DIR] = { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL, NULL }, + -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL, NULL, NULL }, [VIR_STORAGE_FILE_BOCHS] = { /*"Bochs Virtual HD Image", */ /* Untested */ 0, NULL, LV_LITTLE_ENDIAN, 64, 4, {0x20000}, - 32+16+16+4+4+4+4+4, 8, 1, NULL, NULL, NULL, NULL + 32+16+16+4+4+4+4+4, 8, 1, NULL, NULL, NULL, NULL, NULL }, [VIR_STORAGE_FILE_CLOOP] = { /* #!/bin/sh @@ -258,7 +259,7 @@ static struct FileTypeInfo const fileTypeInfo[] = { */ /* Untested */ 0, NULL, LV_LITTLE_ENDIAN, -1, 0, {0}, - -1, 0, 0, NULL, NULL, NULL, NULL + -1, 0, 0, NULL, NULL, NULL, NULL, NULL }, [VIR_STORAGE_FILE_DMG] = { /* XXX QEMU says there's no magic for dmg, @@ -266,45 +267,45 @@ static struct FileTypeInfo const fileTypeInfo[] = { * would have to match) but then disables that check. */ 0, NULL, 0, -1, 0, {0}, - -1, 0, 0, NULL, NULL, NULL, NULL + -1, 0, 0, NULL, NULL, NULL, NULL, NULL }, [VIR_STORAGE_FILE_ISO] = { 32769, "CD001", LV_LITTLE_ENDIAN, -2, 0, {0}, - -1, 0, 0, NULL, NULL, NULL, NULL + -1, 0, 0, NULL, NULL, NULL, NULL, NULL }, [VIR_STORAGE_FILE_VPC] = { 0, "conectix", LV_BIG_ENDIAN, 12, 4, {0x10000}, - 8 + 4 + 4 + 8 + 4 + 4 + 2 + 2 + 4, 8, 1, NULL, NULL, NULL, NULL + 8 + 4 + 4 + 8 + 4 + 4 + 2 + 2 + 4, 8, 1, NULL, NULL, NULL, NULL, NULL }, /* TODO: add getBackingStore function */ [VIR_STORAGE_FILE_VDI] = { 64, "\x7f\x10\xda\xbe", LV_LITTLE_ENDIAN, 68, 4, {0x00010001}, - 64 + 5 * 4 + 256 + 7 * 4, 8, 1, NULL, NULL, NULL, NULL}, + 64 + 5 * 4 + 256 + 7 * 4, 8, 1, NULL, NULL, NULL, NULL, NULL}, /* Not direct file formats, but used for various drivers */ [VIR_STORAGE_FILE_FAT] = { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL, NULL }, + -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL, NULL, NULL }, [VIR_STORAGE_FILE_VHD] = { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL, NULL }, + -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL, NULL, NULL }, [VIR_STORAGE_FILE_PLOOP] = { 0, "WithouFreSpacExt", LV_LITTLE_ENDIAN, -2, 0, {0}, PLOOP_IMAGE_SIZE_OFFSET, 8, - PLOOP_SIZE_MULTIPLIER, NULL, NULL, NULL, NULL }, + PLOOP_SIZE_MULTIPLIER, NULL, NULL, NULL, NULL, NULL }, /* All formats with a backing store probe below here */ [VIR_STORAGE_FILE_COW] = { 0, "OOOM", LV_BIG_ENDIAN, 4, 4, {2}, - 4+4+1024+4, 8, 1, NULL, NULL, cowGetBackingStore, NULL + 4+4+1024+4, 8, 1, NULL, NULL, cowGetBackingStore, NULL, NULL }, [VIR_STORAGE_FILE_QCOW] = { 0, "QFI", LV_BIG_ENDIAN, 4, 4, {1}, QCOWX_HDR_IMAGE_SIZE, 8, 1, qcow1EncryptionInfo, - NULL, qcowXGetBackingStore, NULL + NULL, qcowXGetBackingStore, NULL, NULL }, [VIR_STORAGE_FILE_QCOW2] = { 0, "QFI", @@ -313,18 +314,19 @@ static struct FileTypeInfo const fileTypeInfo[] = { qcow2EncryptionInfo, qcow2GetClusterSize, qcowXGetBackingStore, + NULL, qcow2GetFeatures }, [VIR_STORAGE_FILE_QED] = { /* https://wiki.qemu.org/Features/QED */ 0, "QED", LV_LITTLE_ENDIAN, -2, 0, {0}, - QED_HDR_IMAGE_SIZE, 8, 1, NULL, NULL, qedGetBackingStore, NULL + QED_HDR_IMAGE_SIZE, 8, 1, NULL, NULL, qedGetBackingStore, NULL, NULL }, [VIR_STORAGE_FILE_VMDK] = { 0, "KDMV", LV_LITTLE_ENDIAN, 4, 4, {1, 2, 3}, - 4+4+4, 8, 512, NULL, NULL, vmdk4GetBackingStore, NULL + 4+4+4, 8, 512, NULL, NULL, vmdk4GetBackingStore, NULL, NULL }, }; G_STATIC_ASSERT(G_N_ELEMENTS(fileTypeInfo) == VIR_STORAGE_FILE_LAST); -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:40 +0300, Nikolai Barybin via Devel wrote:
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/storage_file/storage_file_probe.c | 34 ++++++++++++++------------- 1 file changed, 18 insertions(+), 16 deletions(-)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

In qcow2 header data file is represented by incompitible feature bit and its path is saved to header extension table. Thus, we implement here the logic similar to backing file probing. Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/storage_file/storage_file_probe.c | 46 ++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/src/storage_file/storage_file_probe.c b/src/storage_file/storage_file_probe.c index ad14350edc..06868f5355 100644 --- a/src/storage_file/storage_file_probe.c +++ b/src/storage_file/storage_file_probe.c @@ -106,6 +106,7 @@ qcow2GetClusterSize(const char *buf, size_t buf_size); static int qcowXGetBackingStore(char **, int *, const char *, size_t); +static int qcow2GetDataFile(char **, virBitmap *, char *, size_t); static int qcow2GetFeatures(virBitmap **features, int format, char *buf, ssize_t len); static int vmdk4GetBackingStore(char **, int *, @@ -127,6 +128,7 @@ qedGetBackingStore(char **, int *, const char *, size_t); #define QCOW2_HDR_EXTENSION_END 0 #define QCOW2_HDR_EXTENSION_BACKING_FORMAT 0xE2792ACA +#define QCOW2_HDR_EXTENSION_DATA_FILE_NAME 0x44415441 #define QCOW2v3_HDR_FEATURES_INCOMPATIBLE (QCOW2_HDR_TOTAL_SIZE) #define QCOW2v3_HDR_FEATURES_COMPATIBLE (QCOW2v3_HDR_FEATURES_INCOMPATIBLE+8) @@ -314,7 +316,7 @@ static struct FileTypeInfo const fileTypeInfo[] = { qcow2EncryptionInfo, qcow2GetClusterSize, qcowXGetBackingStore, - NULL, + qcow2GetDataFile, qcow2GetFeatures }, [VIR_STORAGE_FILE_QED] = { @@ -361,7 +363,7 @@ enum qcow2IncompatibleFeature { static const virStorageFileFeature qcow2IncompatibleFeatureArray[] = { VIR_STORAGE_FILE_FEATURE_LAST, /* QCOW2_INCOMPATIBLE_FEATURE_DIRTY */ VIR_STORAGE_FILE_FEATURE_LAST, /* QCOW2_INCOMPATIBLE_FEATURE_CORRUPT */ - VIR_STORAGE_FILE_FEATURE_LAST, /* QCOW2_INCOMPATIBLE_FEATURE_DATA_FILE */ + VIR_STORAGE_FILE_FEATURE_DATA_FILE, /* QCOW2_INCOMPATIBLE_FEATURE_DATA_FILE */ VIR_STORAGE_FILE_FEATURE_LAST, /* QCOW2_INCOMPATIBLE_FEATURE_COMPRESSION */ VIR_STORAGE_FILE_FEATURE_EXTENDED_L2, /* QCOW2_INCOMPATIBLE_FEATURE_EXTL2 */ }; @@ -393,7 +395,8 @@ cowGetBackingStore(char **res, static int qcow2GetExtensions(const char *buf, size_t buf_size, - int *backingFormat) + int *backingFormat, + char **dataFilePath) { size_t offset; size_t extension_start; @@ -488,6 +491,15 @@ qcow2GetExtensions(const char *buf, break; } + case QCOW2_HDR_EXTENSION_DATA_FILE_NAME: { + if (!dataFilePath) + break; + + *dataFilePath = g_new0(char, len + 1); + memcpy(*dataFilePath, buf + offset, len); + break; + } + case QCOW2_HDR_EXTENSION_END: return 0; } @@ -554,13 +566,33 @@ qcowXGetBackingStore(char **res, memcpy(*res, buf + offset, size); (*res)[size] = '\0'; - if (qcow2GetExtensions(buf, buf_size, format) < 0) + if (qcow2GetExtensions(buf, buf_size, format, NULL) < 0) return 0; return 0; } +static int +qcow2GetDataFile(char **res, + virBitmap *features, + char *buf, + size_t buf_size) +{ + *res = NULL; + + if (buf_size < QCOW2v3_HDR_FEATURES_INCOMPATIBLE + 8) + return 0; + + if (features && virBitmapIsBitSet(features, VIR_STORAGE_FILE_FEATURE_DATA_FILE)) { + if (qcow2GetExtensions(buf, buf_size, NULL, res) < 0) + return 0; + } + + return 0; +} + + static int vmdk4GetBackingStore(char **res, int *format, @@ -968,6 +1000,12 @@ virStorageFileProbeGetMetadata(virStorageSource *meta, fileTypeInfo[meta->format].getFeatures(&meta->features, meta->format, buf, len) < 0) return -1; + VIR_FREE(meta->dataFileRaw); + if (fileTypeInfo[meta->format].getDataFile != NULL) { + fileTypeInfo[meta->format].getDataFile(&meta->dataFileRaw, meta->features, + buf, len); + } + VIR_FREE(meta->compat); if (meta->format == VIR_STORAGE_FILE_QCOW2 && meta->features) meta->compat = g_strdup("1.1"); -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:41 +0300, Nikolai Barybin via Devel wrote:
In qcow2 header data file is represented by incompitible feature bit and its path is saved to header extension table. Thus, we implement here the logic similar to backing file probing.
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/storage_file/storage_file_probe.c | 46 ++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 4 deletions(-)
[...]
+static int +qcow2GetDataFile(char **res, + virBitmap *features, + char *buf, + size_t buf_size) +{ + *res = NULL; + + if (buf_size < QCOW2v3_HDR_FEATURES_INCOMPATIBLE + 8) + return 0; + + if (features && virBitmapIsBitSet(features, VIR_STORAGE_FILE_FEATURE_DATA_FILE)) { + if (qcow2GetExtensions(buf, buf_size, NULL, res) < 0) + return 0;
This should be 'return -1'
+ } + + return 0; +} + + static int vmdk4GetBackingStore(char **res, int *format,
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/storage_file/storage_source.c | 39 +++++++++++++++++++++++++++++++ src/storage_file/storage_source.h | 4 ++++ 2 files changed, 43 insertions(+) diff --git a/src/storage_file/storage_source.c b/src/storage_file/storage_source.c index 2cfe3bc325..b9d2d71aea 100644 --- a/src/storage_file/storage_source.c +++ b/src/storage_file/storage_source.c @@ -543,6 +543,33 @@ virStorageSourceNewFromBacking(virStorageSource *parent, } +/** + * virStorageSourceNewFromDataFile: + * @parent: storage source parent + * @dataFileSrc: returned data file source definition + * + * Creates a storage source which describes the data file image of @parent. + * Returned storage source format is VIR_STORAGE_FILE_RAW, and, unlike + * backing storage creation, readonly flag is copied from @parent. + * + * Return codes are the same as in virStorageSourceNewFromChild. + */ +int +virStorageSourceNewFromDataFile(virStorageSource *parent, + virStorageSource **dataFileSrc) +{ + if (virStorageSourceNewFromChild(parent, + parent->dataFileRaw, + dataFileSrc) < 0) + return -1; + + (*dataFileSrc)->format = VIR_STORAGE_FILE_RAW; + (*dataFileSrc)->readonly = parent->readonly; + + return 0; +} + + /** * @src: disk source definition structure * @fd: file descriptor @@ -1391,6 +1418,18 @@ virStorageSourceGetMetadataRecurse(virStorageSource *src, } } + if (src->dataFileRaw) { + g_autoptr(virStorageSource) dataFileStore = NULL; + if ((rv = virStorageSourceNewFromDataFile(src, &dataFileStore)) < 0) + return -1; + + /* the data file would not be usable for VM usage */ + if (rv == 1) + return 0; + + src->dataFileStore = g_steal_pointer(&dataFileStore); + } + if (src->backingStoreRaw) { if ((rv = virStorageSourceNewFromBacking(src, &backingStore)) < 0) return -1; diff --git a/src/storage_file/storage_source.h b/src/storage_file/storage_source.h index 63fefb6919..78b31e405a 100644 --- a/src/storage_file/storage_source.h +++ b/src/storage_file/storage_source.h @@ -72,6 +72,10 @@ int virStorageSourceNewFromBacking(virStorageSource *parent, virStorageSource **backing); +int +virStorageSourceNewFromDataFile(virStorageSource *parent, + virStorageSource **dataFileSrc); + int virStorageSourceGetRelativeBackingPath(virStorageSource *top, virStorageSource *base, -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:42 +0300, Nikolai Barybin via Devel wrote:
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/storage_file/storage_source.c | 39 +++++++++++++++++++++++++++++++ src/storage_file/storage_source.h | 4 ++++ 2 files changed, 43 insertions(+)
diff --git a/src/storage_file/storage_source.c b/src/storage_file/storage_source.c index 2cfe3bc325..b9d2d71aea 100644 --- a/src/storage_file/storage_source.c +++ b/src/storage_file/storage_source.c @@ -543,6 +543,33 @@ virStorageSourceNewFromBacking(virStorageSource *parent, }
+/** + * virStorageSourceNewFromDataFile: + * @parent: storage source parent + * @dataFileSrc: returned data file source definition + * + * Creates a storage source which describes the data file image of @parent. + * Returned storage source format is VIR_STORAGE_FILE_RAW, and, unlike + * backing storage creation, readonly flag is copied from @parent. + * + * Return codes are the same as in virStorageSourceNewFromChild. + */ +int
This function is never used outside of this module thus should not be exported.
+virStorageSourceNewFromDataFile(virStorageSource *parent, + virStorageSource **dataFileSrc) +{ + if (virStorageSourceNewFromChild(parent, + parent->dataFileRaw, + dataFileSrc) < 0) + return -1;
This strips the possibility to return '1' which you've explicitly documented above. Thinking about it a bit; the logic which is for backing store regarding the skipping of detection if libvirt can't parse it is a stop-gap measure for pre-existing configs when the blockdev code was being introduced. It meant to simply skip the backing store definition via -blockdev in case libvirt can't do that and offloaded it to qemu. For data files I don't think we need this kind of logic, and can fail instead.
+ + (*dataFileSrc)->format = VIR_STORAGE_FILE_RAW; + (*dataFileSrc)->readonly = parent->readonly; + + return 0; +} + + /** * @src: disk source definition structure * @fd: file descriptor @@ -1391,6 +1418,18 @@ virStorageSourceGetMetadataRecurse(virStorageSource *src, } }
+ if (src->dataFileRaw) { + g_autoptr(virStorageSource) dataFileStore = NULL; + if ((rv = virStorageSourceNewFromDataFile(src, &dataFileStore)) < 0) + return -1; + + /* the data file would not be usable for VM usage */ + if (rv == 1) + return 0;
Apart from what I wrote above, this would be wrong here as this would also skip the backing store parsing below. Now with the change to report error if 'data-store' can't be fully processed by libvirt the location and logich here will be correct, but the above return statement will be removed.
+
I'll be changing this to: diff --git a/src/storage_file/storage_source.c b/src/storage_file/storage_source.c index 2cfe3bc325..df73f44699 100644 --- a/src/storage_file/storage_source.c +++ b/src/storage_file/storage_source.c @@ -543,6 +543,39 @@ virStorageSourceNewFromBacking(virStorageSource *parent, } +/** + * virStorageSourceNewFromDataFile: + * @parent: storage source parent + * + * Creates a storage source which describes the data file image of @parent. + * Returned storage source format is VIR_STORAGE_FILE_RAW, and, unlike + * backing storage creation, readonly flag is copied from @parent. + */ +static virStorageSource * +virStorageSourceNewFromDataFile(virStorageSource *parent) +{ + g_autoptr(virStorageSource) dataFile = NULL; + int rc; + + if ((rc = virStorageSourceNewFromChild(parent, + parent->dataFileRaw, + &dataFile)) < 0) + return NULL; + + if (rc == 1) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("can't use data file definition '%1$s'"), + parent->dataFileRaw); + return NULL; + } + + dataFile->format = VIR_STORAGE_FILE_RAW; + dataFile->readonly = parent->readonly; + + return g_steal_pointer(&dataFile); +} + + /** * @src: disk source definition structure * @fd: file descriptor @@ -1391,6 +1424,11 @@ virStorageSourceGetMetadataRecurse(virStorageSource *src, } } + if (src->dataFileRaw) { + if (!(src->dataFileStore = virStorageSourceNewFromDataFile(src))) + return -1; + } + if (src->backingStoreRaw) { if ((rv = virStorageSourceNewFromBacking(src, &backingStore)) < 0) return -1;

On Thu, Nov 21, 2024 at 15:11:46 +0100, Peter Krempa wrote:
On Wed, Nov 20, 2024 at 18:48:42 +0300, Nikolai Barybin via Devel wrote:
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/storage_file/storage_source.c | 39 +++++++++++++++++++++++++++++++ src/storage_file/storage_source.h | 4 ++++ 2 files changed, 43 insertions(+)
@@ -1391,6 +1424,11 @@ virStorageSourceGetMetadataRecurse(virStorageSource *src, } }
+ if (src->dataFileRaw) {
In addition to this change we must also not fill this if the current 'src' already has the 'dataFileStore' member populated. It can happen that you have the last image in chain with a <dataStore> but without <backingStore> that situation invokes probing of the image and here we'd overwrite the user-configured <dataStore>.

Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/security/security_dac.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/security/security_dac.c b/src/security/security_dac.c index a179378a78..0505f4e4a3 100644 --- a/src/security/security_dac.c +++ b/src/security/security_dac.c @@ -969,6 +969,13 @@ virSecurityDACSetImageLabel(virSecurityManager *mgr, def, n, parent, isChainTop) < 0) return -1; + /* Unlike backing images, data files are not designed to be shared by + * anyone. Thus, we always consider them as chain top. */ + if (n->dataFileStore && + virSecurityDACSetImageLabelInternal(mgr, sharedFilesystems, def, + n->dataFileStore, n, true) < 0) + return -1; + if (!(flags & VIR_SECURITY_DOMAIN_IMAGE_LABEL_BACKING_CHAIN)) break; @@ -1065,8 +1072,16 @@ virSecurityDACRestoreImageLabel(virSecurityManager *mgr, virStorageSource *src, virSecurityDomainImageLabelFlags flags G_GNUC_UNUSED) { - return virSecurityDACRestoreImageLabelInt(mgr, sharedFilesystems, - def, src, false); + if (virSecurityDACRestoreImageLabelInt(mgr, sharedFilesystems, + def, src, false) < 0) + return -1; + + if (src->dataFileStore && + virSecurityDACRestoreImageLabelInt(mgr, sharedFilesystems, + def, src->dataFileStore, false) < 0) + return -1; + + return 0; } @@ -1946,6 +1961,14 @@ virSecurityDACRestoreAllLabel(virSecurityManager *mgr, def->disks[i]->src, migrated) < 0) rc = -1; + + if (def->disks[i]->src->dataFileStore && + virSecurityDACRestoreImageLabelInt(mgr, + sharedFilesystems, + def, + def->disks[i]->src->dataFileStore, + migrated) < 0) + rc = -1; } for (i = 0; i < def->ngraphics; i++) { -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:43 +0300, Nikolai Barybin via Devel wrote:
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/security/security_dac.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/security/security_selinux.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/security/security_selinux.c b/src/security/security_selinux.c index 18daa521d1..05e24ff11b 100644 --- a/src/security/security_selinux.c +++ b/src/security/security_selinux.c @@ -1934,8 +1934,16 @@ virSecuritySELinuxRestoreImageLabel(virSecurityManager *mgr, virStorageSource *src, virSecurityDomainImageLabelFlags flags G_GNUC_UNUSED) { - return virSecuritySELinuxRestoreImageLabelInt(mgr, sharedFilesystems, - def, src, false); + if (virSecuritySELinuxRestoreImageLabelInt(mgr, sharedFilesystems, + def, src, false) < 0) + return -1; + + if (src->dataFileStore && + virSecuritySELinuxRestoreImageLabelInt(mgr, sharedFilesystems, + def, src->dataFileStore, false) < 0) + return -1; + + return 0; } @@ -2067,6 +2075,14 @@ virSecuritySELinuxSetImageLabel(virSecurityManager *mgr, isChainTop) < 0) return -1; + /* Unlike backing images, data files are not designed to be shared by + * anyone. Thus, we always consider them as chain top. */ + if (n->dataFileStore && + virSecuritySELinuxSetImageLabelInternal(mgr, sharedFilesystems, + def, n->dataFileStore, parent, + true) < 0) + return -1; + if (!(flags & VIR_SECURITY_DOMAIN_IMAGE_LABEL_BACKING_CHAIN)) break; @@ -2929,6 +2945,13 @@ virSecuritySELinuxRestoreAllLabel(virSecurityManager *mgr, def, disk->src, migrated) < 0) rc = -1; + + if (disk->src->dataFileStore && + virSecuritySELinuxRestoreImageLabelInt(mgr, sharedFilesystems, + def, disk->src->dataFileStore, + migrated) < 0) + rc = -1; + } for (i = 0; i < def->nhostdevs; i++) { -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:44 +0300, Nikolai Barybin via Devel wrote:
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/security/security_selinux.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

On Wed, Nov 20, 2024 at 18:48:44 +0300, Nikolai Barybin via Devel wrote:
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/security/security_selinux.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-)
[...]
@@ -2067,6 +2075,14 @@ virSecuritySELinuxSetImageLabel(virSecurityManager *mgr, isChainTop) < 0) return -1;
+ /* Unlike backing images, data files are not designed to be shared by + * anyone. Thus, we always consider them as chain top. */ + if (n->dataFileStore && + virSecuritySELinuxSetImageLabelInternal(mgr, sharedFilesystems, + def, n->dataFileStore, parent, + true) < 0)
Inside this function there's code which picks which label gets applied, the code applies RW labels if the active element is equal to 'parent'. This needs to be extended to also consider 'parent->dataFileStore' so that the data-file images are labelled RW.

Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/security/virt-aa-helper.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/security/virt-aa-helper.c b/src/security/virt-aa-helper.c index 067a17f331..a2914f22b9 100644 --- a/src/security/virt-aa-helper.c +++ b/src/security/virt-aa-helper.c @@ -877,6 +877,10 @@ storage_source_add_files(virStorageSource *src, if (add_file_path(tmp, depth, buf) < 0) return -1; + if (src->dataFileStore && + add_file_path(src->dataFileStore, buf, 0) < 0) + return -1; + depth++; } -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:45 +0300, Nikolai Barybin via Devel wrote:
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/security/virt-aa-helper.c | 4 ++++ 1 file changed, 4 insertions(+)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

On Wed, Nov 20, 2024 at 18:48:45 +0300, Nikolai Barybin via Devel wrote:
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/security/virt-aa-helper.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/src/security/virt-aa-helper.c b/src/security/virt-aa-helper.c index 067a17f331..a2914f22b9 100644 --- a/src/security/virt-aa-helper.c +++ b/src/security/virt-aa-helper.c @@ -877,6 +877,10 @@ storage_source_add_files(virStorageSource *src, if (add_file_path(tmp, depth, buf) < 0) return -1;
+ if (src->dataFileStore && + add_file_path(src->dataFileStore, buf, 0) < 0)
This failed to compile, as add_file_path has a different prototype as seen above. Also you use 'src->dataFileStore' which is always the top node rather than 'tmp' which is used for iteration.
+ return -1; + depth++; }
-- 2.43.5

Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/qemu/qemu_cgroup.c | 13 ++++++++++++- src/qemu/qemu_namespace.c | 7 +++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/qemu/qemu_cgroup.c b/src/qemu/qemu_cgroup.c index be4b9a38ff..f3c85d65e8 100644 --- a/src/qemu/qemu_cgroup.c +++ b/src/qemu/qemu_cgroup.c @@ -232,7 +232,14 @@ int qemuSetupImageCgroup(virDomainObj *vm, virStorageSource *src) { - return qemuSetupImageCgroupInternal(vm, src, false); + if (qemuSetupImageCgroupInternal(vm, src, false) < 0) + return -1; + + if (src->dataFileStore && + qemuSetupImageCgroupInternal(vm, src->dataFileStore, false) < 0) + return -1; + + return 0; } @@ -321,6 +328,10 @@ qemuSetupImageChainCgroup(virDomainObj *vm, if (qemuSetupImageCgroupInternal(vm, next, forceReadonly) < 0) return -1; + if (next->dataFileStore && + qemuSetupImageCgroupInternal(vm, next->dataFileStore, forceReadonly) < 0) + return -1; + /* setup only the top level image for read-write */ forceReadonly = true; } diff --git a/src/qemu/qemu_namespace.c b/src/qemu/qemu_namespace.c index 0ebc115524..9fc13ee759 100644 --- a/src/qemu/qemu_namespace.c +++ b/src/qemu/qemu_namespace.c @@ -272,6 +272,13 @@ qemuDomainSetupDisk(virStorageSource *src, } else { GSList *targetPaths = NULL; + if (next->dataFileStore && + !virStorageSourceIsEmpty(next->dataFileStore) && + virStorageSourceIsLocalStorage(next->dataFileStore)) { + g_autofree char *dataFilePath = g_strdup(next->dataFileStore->path); + *paths = g_slist_prepend(*paths, g_steal_pointer(&dataFilePath)); + } + if (virStorageSourceIsEmpty(next) || !virStorageSourceIsLocalStorage(next)) { /* Not creating device. Just continue. */ -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:46 +0300, Nikolai Barybin via Devel wrote:
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/qemu/qemu_cgroup.c | 13 ++++++++++++- src/qemu/qemu_namespace.c | 7 +++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/qemu/qemu_namespace.c b/src/qemu/qemu_namespace.c index 0ebc115524..9fc13ee759 100644 --- a/src/qemu/qemu_namespace.c +++ b/src/qemu/qemu_namespace.c @@ -272,6 +272,13 @@ qemuDomainSetupDisk(virStorageSource *src, } else { GSList *targetPaths = NULL;
+ if (next->dataFileStore && + !virStorageSourceIsEmpty(next->dataFileStore) && + virStorageSourceIsLocalStorage(next->dataFileStore)) { + g_autofree char *dataFilePath = g_strdup(next->dataFileStore->path); + *paths = g_slist_prepend(*paths, g_steal_pointer(&dataFilePath)); + } +
This can be simplified: if (next->dataFileStore && virStorageSourceIsLocalStorage(next->dataFileStore)) *paths = g_slist_prepend(*paths, g_strdup(next->dataFileStore->path)); virStorageSourceIsEmpty is pointless because the data file can't be empty and there's no need for a temporary variable.

On Thu, Nov 21, 2024 at 18:03:30 +0100, Peter Krempa wrote:
On Wed, Nov 20, 2024 at 18:48:46 +0300, Nikolai Barybin via Devel wrote:
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/qemu/qemu_cgroup.c | 13 ++++++++++++- src/qemu/qemu_namespace.c | 7 +++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/qemu/qemu_namespace.c b/src/qemu/qemu_namespace.c index 0ebc115524..9fc13ee759 100644 --- a/src/qemu/qemu_namespace.c +++ b/src/qemu/qemu_namespace.c @@ -272,6 +272,13 @@ qemuDomainSetupDisk(virStorageSource *src, } else { GSList *targetPaths = NULL;
+ if (next->dataFileStore && + !virStorageSourceIsEmpty(next->dataFileStore) && + virStorageSourceIsLocalStorage(next->dataFileStore)) { + g_autofree char *dataFilePath = g_strdup(next->dataFileStore->path); + *paths = g_slist_prepend(*paths, g_steal_pointer(&dataFilePath)); + } +
This can be simplified:
if (next->dataFileStore && virStorageSourceIsLocalStorage(next->dataFileStore)) *paths = g_slist_prepend(*paths, g_strdup(next->dataFileStore->path));
virStorageSourceIsEmpty is pointless because the data file can't be empty and there's no need for a temporary variable.
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

This refactoring will simplify next changes. Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/qemu/qemu_domain.c | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 66ab4baa8b..393c7dcac1 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -6114,6 +6114,32 @@ qemuDomainPrepareStorageSourceConfig(virStorageSource *src, } +static int +qemuDomainPrepareStorageSource(virStorageSource *src, + virDomainObj *vm, + virDomainDiskDef *disk, + virQEMUDriverConfig *cfg) +{ + qemuDomainObjPrivate *priv = vm->privateData; + + /* convert detected ISO format to 'raw' as qemu would not understand it */ + if (src->format == VIR_STORAGE_FILE_ISO) + src->format = VIR_STORAGE_FILE_RAW; + + if (qemuDomainValidateStorageSource(src, priv->qemuCaps) < 0) + return -1; + + qemuDomainPrepareStorageSourceConfig(src, cfg); + qemuDomainPrepareDiskSourceData(disk, src); + + if (!qemuDiskBusIsSD(disk->bus) && + qemuDomainPrepareStorageSourceBlockdev(disk, src, priv, cfg) < 0) + return -1; + + return 0; +} + + /** * qemuDomainDetermineDiskChain: * @driver: qemu driver object @@ -6134,7 +6160,6 @@ qemuDomainDetermineDiskChain(virQEMUDriver *driver, g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); virStorageSource *src; /* iterator for the backing chain declared in XML */ virStorageSource *n; /* iterator for the backing chain detected from disk */ - qemuDomainObjPrivate *priv = vm->privateData; uid_t uid; gid_t gid; @@ -6218,18 +6243,7 @@ qemuDomainDetermineDiskChain(virQEMUDriver *driver, return -1; for (n = src->backingStore; virStorageSourceIsBacking(n); n = n->backingStore) { - /* convert detected ISO format to 'raw' as qemu would not understand it */ - if (n->format == VIR_STORAGE_FILE_ISO) - n->format = VIR_STORAGE_FILE_RAW; - - if (qemuDomainValidateStorageSource(n, priv->qemuCaps) < 0) - return -1; - - qemuDomainPrepareStorageSourceConfig(n, cfg); - qemuDomainPrepareDiskSourceData(disk, n); - - if (!qemuDiskBusIsSD(disk->bus) && - qemuDomainPrepareStorageSourceBlockdev(disk, n, priv, cfg) < 0) + if (qemuDomainPrepareStorageSource(n, vm, disk, cfg) < 0) return -1; } -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:47 +0300, Nikolai Barybin via Devel wrote:
This refactoring will simplify next changes.
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/qemu/qemu_domain.c | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

- propogate data-file to cmdline - determine data-file within disk chain - enable live disk insertion Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/qemu/qemu_block.c | 14 ++++++++++++++ src/qemu/qemu_command.c | 5 +++++ src/qemu/qemu_domain.c | 14 +++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c index 3c1305ec84..1915d5f5fd 100644 --- a/src/qemu/qemu_block.c +++ b/src/qemu/qemu_block.c @@ -1302,6 +1302,13 @@ qemuBlockStorageSourceGetFormatQcow2Props(virStorageSource *src, NULL) < 0) return -1; + if (src->dataFileStore) { + if (virJSONValueObjectAdd(&props, + "s:data-file", qemuBlockStorageSourceGetEffectiveNodename(src->dataFileStore), + NULL) < 0) + return -1; + } + return 0; } @@ -1859,6 +1866,13 @@ qemuBlockStorageSourceChainDetachPrepareBlockdev(virStorageSource *src) return NULL; VIR_APPEND_ELEMENT(data->srcdata, data->nsrcdata, backend); + + if (n->dataFileStore) { + if (!(backend = qemuBlockStorageSourceDetachPrepare(n->dataFileStore))) + return NULL; + + VIR_APPEND_ELEMENT(data->srcdata, data->nsrcdata, backend); + } } return g_steal_pointer(&data); diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index f4430275dc..cdc80cee73 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -11018,6 +11018,11 @@ qemuBuildStorageSourceChainAttachPrepareBlockdev(virStorageSource *top) if (qemuBuildStorageSourceChainAttachPrepareBlockdevOne(data, n, n->backingStore) < 0) return NULL; + + if (n->dataFileStore && + qemuBuildStorageSourceChainAttachPrepareBlockdevOne(data, n->dataFileStore, + n->dataFileStore->backingStore) < 0) + return NULL; } return g_steal_pointer(&data); diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 393c7dcac1..d0700db3be 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -6242,9 +6242,17 @@ qemuDomainDetermineDiskChain(virQEMUDriver *driver, true) < 0) return -1; + if (src->dataFileStore && + qemuDomainPrepareStorageSource(src->dataFileStore, vm, disk, cfg) < 0) + return -1; + for (n = src->backingStore; virStorageSourceIsBacking(n); n = n->backingStore) { if (qemuDomainPrepareStorageSource(n, vm, disk, cfg) < 0) return -1; + + if (n->dataFileStore && + qemuDomainPrepareStorageSource(n->dataFileStore, vm, disk, cfg) < 0) + return -1; } if (qemuDomainStorageSourceValidateDepth(disksrc, 0, disk->dst) < 0) @@ -9438,7 +9446,7 @@ qemuDomainPrepareDiskSourceData(virDomainDiskDef *disk, return; /* transfer properties valid only for the top level image */ - if (src == disk->src) + if (src == disk->src || src == disk->src->dataFileStore) src->detect_zeroes = disk->detect_zeroes; /* transfer properties valid for the full chain */ @@ -9667,6 +9675,10 @@ qemuDomainPrepareDiskSourceBlockdev(virDomainDiskDef *disk, for (n = disk->src; virStorageSourceIsBacking(n); n = n->backingStore) { if (qemuDomainPrepareStorageSourceBlockdev(disk, n, priv, cfg) < 0) return -1; + + if (n->dataFileStore && + qemuDomainPrepareStorageSourceBlockdev(disk, n->dataFileStore, priv, cfg) < 0) + return -1; } return 0; -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:48 +0300, Nikolai Barybin via Devel wrote:
- propogate data-file to cmdline - determine data-file within disk chain - enable live disk insertion
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- src/qemu/qemu_block.c | 14 ++++++++++++++ src/qemu/qemu_command.c | 5 +++++ src/qemu/qemu_domain.c | 14 +++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-)
We need to also forbid formatting of qcow2 images if 'data-file' woudl be requested by the user until it'll be implemented. Given that it's not really necessary I'll be adding @@ -2589,6 +2603,12 @@ qemuBlockStorageSourceCreateFormat(virDomainObj *vm, if (qemuBlockStorageSourceIsRaw(src)) return 0; + if (src->dataFileStore) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("creation of storage images with <dataStore> feature is not supported")); + return -1; + } +
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index f4430275dc..cdc80cee73 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -11018,6 +11018,11 @@ qemuBuildStorageSourceChainAttachPrepareBlockdev(virStorageSource *top) if (qemuBuildStorageSourceChainAttachPrepareBlockdevOne(data, n, n->backingStore) < 0) return NULL; + + if (n->dataFileStore && + qemuBuildStorageSourceChainAttachPrepareBlockdevOne(data, n->dataFileStore, + n->dataFileStore->backingStore) < 0)
It makes no sense to pass n->dataFileStore->backingStore. Instead I'll explain why we're not doing that in a comment.
+ return NULL; }
return g_steal_pointer(&data); diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 393c7dcac1..d0700db3be 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -6242,9 +6242,17 @@ qemuDomainDetermineDiskChain(virQEMUDriver *driver, true) < 0) return -1;
+ if (src->dataFileStore && + qemuDomainPrepareStorageSource(src->dataFileStore, vm, disk, cfg) < 0) + return -1;
This requires explanation otherwise it looks misplaced here.
+ for (n = src->backingStore; virStorageSourceIsBacking(n); n = n->backingStore) { if (qemuDomainPrepareStorageSource(n, vm, disk, cfg) < 0) return -1;
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

- qemuxmlconftest: check various xml definitions of dataFileStore: types file, block, network and the case when data-file belongs to qcow2 backing image - virstoragetest: create qcow2 image chains and check that data files are properly probed by metadata parser - qemuxmlactivetest: check that status files will contain data files' node names Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- .../qcow2-data-file-in.xml | 86 ++++++++++++++++ .../qcow2-data-file-out.xml | 86 ++++++++++++++++ tests/qemuxmlactivetest.c | 2 + ...sk-qcow2-datafile-store.x86_64-latest.args | 51 ++++++++++ ...isk-qcow2-datafile-store.x86_64-latest.xml | 81 +++++++++++++++ .../disk-qcow2-datafile-store.xml | 67 +++++++++++++ tests/qemuxmlconftest.c | 1 + tests/virstoragetest.c | 98 +++++++++++++++++++ tests/virstoragetestdata/out/qcow2-data_file | 20 ++++ .../out/qcow2-qcow2_qcow2-qcow2-data_file | 31 ++++++ 10 files changed, 523 insertions(+) create mode 100644 tests/qemustatusxml2xmldata/qcow2-data-file-in.xml create mode 100644 tests/qemustatusxml2xmldata/qcow2-data-file-out.xml create mode 100644 tests/qemuxmlconfdata/disk-qcow2-datafile-store.x86_64-latest.args create mode 100644 tests/qemuxmlconfdata/disk-qcow2-datafile-store.x86_64-latest.xml create mode 100644 tests/qemuxmlconfdata/disk-qcow2-datafile-store.xml create mode 100644 tests/virstoragetestdata/out/qcow2-data_file create mode 100644 tests/virstoragetestdata/out/qcow2-qcow2_qcow2-qcow2-data_file diff --git a/tests/qemustatusxml2xmldata/qcow2-data-file-in.xml b/tests/qemustatusxml2xmldata/qcow2-data-file-in.xml new file mode 100644 index 0000000000..40479dd3d6 --- /dev/null +++ b/tests/qemustatusxml2xmldata/qcow2-data-file-in.xml @@ -0,0 +1,86 @@ +<domstatus state='running' reason='booted' pid='3803518'> + <taint flag='high-privileges'/> + <monitor path='/var/lib/libvirt/qemu/test.monitor' type='unix'/> + <vcpus> + <vcpu id='0' pid='3803519'/> + </vcpus> + <qemuCaps> + <flag name='vnet-hdr'/> + <flag name='qxl.vgamem_mb'/> + <flag name='qxl-vga.vgamem_mb'/> + <flag name='pc-dimm'/> + </qemuCaps> + <lockstate>testtest</lockstate> + <devices> + <device alias='balloon0'/> + <device alias='video0'/> + <device alias='serial0'/> + <device alias='net0'/> + <device alias='usb'/> + </devices> + <numad nodeset='0-2' cpuset='1,3'/> + <libDir path='/tmp'/> + <channelTargetDir path='/var/lib/libvirt/qemu/channel/target'/> + <memoryBackingDir path='/var/lib/libvirt/qemu/ram/1-QEMUGuest1'/> + <allowReboot value='yes'/> + <nodename index='0'/> + <fdset index='0'/> + <blockjobs active='no'/> + <agentTimeout>-2</agentTimeout> + <domain type='qemu' id='1'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-i386</emulator> + <disk type='block' device='cdrom'> + <driver name='qemu' type='raw'/> + <source dev='/dev/HostVG/QEMUGuest2'/> + <backingStore/> + <target dev='hdc' bus='ide'/> + <readonly/> + <address type='drive' controller='0' bus='1' target='0' unit='0'/> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/image.qcow2'/> + <backingStore/> + <dataFileStore type='file'> + <format type='raw'/> + <source file='/tmp/data-file'> + <privateData> + <nodenames> + <nodename type='storage' name='test-storage-data-file'/> + </nodenames> + </privateData> + </source> + </dataFileStore> + <target dev='vda' bus='virtio'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> + </disk> + <controller type='usb' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='none'/> + <memballoon model='virtio'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </memballoon> + </devices> + </domain> +</domstatus> diff --git a/tests/qemustatusxml2xmldata/qcow2-data-file-out.xml b/tests/qemustatusxml2xmldata/qcow2-data-file-out.xml new file mode 100644 index 0000000000..40479dd3d6 --- /dev/null +++ b/tests/qemustatusxml2xmldata/qcow2-data-file-out.xml @@ -0,0 +1,86 @@ +<domstatus state='running' reason='booted' pid='3803518'> + <taint flag='high-privileges'/> + <monitor path='/var/lib/libvirt/qemu/test.monitor' type='unix'/> + <vcpus> + <vcpu id='0' pid='3803519'/> + </vcpus> + <qemuCaps> + <flag name='vnet-hdr'/> + <flag name='qxl.vgamem_mb'/> + <flag name='qxl-vga.vgamem_mb'/> + <flag name='pc-dimm'/> + </qemuCaps> + <lockstate>testtest</lockstate> + <devices> + <device alias='balloon0'/> + <device alias='video0'/> + <device alias='serial0'/> + <device alias='net0'/> + <device alias='usb'/> + </devices> + <numad nodeset='0-2' cpuset='1,3'/> + <libDir path='/tmp'/> + <channelTargetDir path='/var/lib/libvirt/qemu/channel/target'/> + <memoryBackingDir path='/var/lib/libvirt/qemu/ram/1-QEMUGuest1'/> + <allowReboot value='yes'/> + <nodename index='0'/> + <fdset index='0'/> + <blockjobs active='no'/> + <agentTimeout>-2</agentTimeout> + <domain type='qemu' id='1'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-i386</emulator> + <disk type='block' device='cdrom'> + <driver name='qemu' type='raw'/> + <source dev='/dev/HostVG/QEMUGuest2'/> + <backingStore/> + <target dev='hdc' bus='ide'/> + <readonly/> + <address type='drive' controller='0' bus='1' target='0' unit='0'/> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/image.qcow2'/> + <backingStore/> + <dataFileStore type='file'> + <format type='raw'/> + <source file='/tmp/data-file'> + <privateData> + <nodenames> + <nodename type='storage' name='test-storage-data-file'/> + </nodenames> + </privateData> + </source> + </dataFileStore> + <target dev='vda' bus='virtio'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> + </disk> + <controller type='usb' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='none'/> + <memballoon model='virtio'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </memballoon> + </devices> + </domain> +</domstatus> diff --git a/tests/qemuxmlactivetest.c b/tests/qemuxmlactivetest.c index 23ac807c76..8cf44090ce 100644 --- a/tests/qemuxmlactivetest.c +++ b/tests/qemuxmlactivetest.c @@ -250,6 +250,8 @@ mymain(void) DO_TEST_STATUS("memory-backing-dir"); + DO_TEST_STATUS("qcow2-data-file"); + cleanup: qemuTestDriverFree(&driver); diff --git a/tests/qemuxmlconfdata/disk-qcow2-datafile-store.x86_64-latest.args b/tests/qemuxmlconfdata/disk-qcow2-datafile-store.x86_64-latest.args new file mode 100644 index 0000000000..5a64246af6 --- /dev/null +++ b/tests/qemuxmlconfdata/disk-qcow2-datafile-store.x86_64-latest.args @@ -0,0 +1,51 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1 \ +USER=test \ +LOGNAME=test \ +XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.local/share \ +XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.cache \ +XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.config \ +/usr/bin/qemu-system-x86_64 \ +-name guest=QEMUGuest1,debug-threads=on \ +-S \ +-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain--1-QEMUGuest1/master-key.aes"}' \ +-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram,acpi=off \ +-accel tcg \ +-cpu qemu64 \ +-m size=219136k \ +-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \ +-overcommit mem-lock=off \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-display none \ +-no-user-config \ +-nodefaults \ +-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \ +-mon chardev=charmonitor,id=monitor,mode=control \ +-rtc base=utc \ +-no-shutdown \ +-boot strict=on \ +-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \ +-blockdev '{"driver":"file","filename":"/tmp/data-file-store","node-name":"libvirt-9-storage","read-only":false}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/path/to/sock"},"export":"Volume2/Image","node-name":"libvirt-8-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-8-format","read-only":false,"driver":"qcow2","data-file":"libvirt-9-storage","file":"libvirt-8-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x2","drive":"libvirt-8-format","id":"virtio-disk0","bootindex":1}' \ +-blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest1","node-name":"libvirt-7-storage","read-only":false}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/var/run/nbdsock"},"export":"bar","node-name":"libvirt-6-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-6-format","read-only":false,"driver":"qcow2","data-file":"libvirt-7-storage","file":"libvirt-6-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x3","drive":"libvirt-6-format","id":"virtio-disk1"}' \ +-blockdev '{"driver":"nbd","server":{"type":"unix","path":"/path/to/sock/datafile"},"export":"Volume2/ImageDataFile","node-name":"libvirt-5-storage","read-only":false}' \ +-blockdev '{"driver":"file","filename":"/var/lib/libvirt/images/rhel7.1484071879","node-name":"libvirt-4-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-4-format","read-only":false,"driver":"qcow2","data-file":"libvirt-5-storage","file":"libvirt-4-storage"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x4","drive":"libvirt-4-format","id":"virtio-disk2"}' \ +-blockdev '{"driver":"file","filename":"/tmp/data-file-store-2","node-name":"libvirt-3-storage","read-only":true}' \ +-blockdev '{"driver":"file","filename":"/var/lib/libvirt/images/rhel7.1484071877","node-name":"libvirt-2-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-2-format","read-only":true,"driver":"qcow2","data-file":"libvirt-3-storage","file":"libvirt-2-storage"}' \ +-blockdev '{"driver":"file","filename":"/var/lib/libvirt/images/rhel7.1484071880","node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \ +-blockdev '{"node-name":"libvirt-1-format","read-only":false,"driver":"qcow2","file":"libvirt-1-storage","backing":"libvirt-2-format"}' \ +-device '{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x5","drive":"libvirt-1-format","id":"virtio-disk3"}' \ +-audiodev '{"id":"audio1","driver":"none"}' \ +-device '{"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.0","addr":"0x6"}' \ +-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \ +-msg timestamp=on diff --git a/tests/qemuxmlconfdata/disk-qcow2-datafile-store.x86_64-latest.xml b/tests/qemuxmlconfdata/disk-qcow2-datafile-store.x86_64-latest.xml new file mode 100644 index 0000000000..d0a6cb3238 --- /dev/null +++ b/tests/qemuxmlconfdata/disk-qcow2-datafile-store.x86_64-latest.xml @@ -0,0 +1,81 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <cpu mode='custom' match='exact' check='none'> + <model fallback='forbid'>qemu64</model> + </cpu> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='network' device='disk'> + <driver name='qemu' type='qcow2'/> + <source protocol='nbd' name='Volume2/Image'> + <host transport='unix' socket='/path/to/sock'/> + </source> + <dataFileStore type='file'> + <format type='raw'/> + <source file='/tmp/data-file-store'/> + </dataFileStore> + <target dev='vda' bus='virtio'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </disk> + <disk type='network' device='disk'> + <driver name='qemu' type='qcow2'/> + <source protocol='nbd' name='bar'> + <host transport='unix' socket='/var/run/nbdsock'/> + </source> + <dataFileStore type='block'> + <format type='raw'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + </dataFileStore> + <target dev='vdb' bus='virtio'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/var/lib/libvirt/images/rhel7.1484071879'/> + <dataFileStore type='network'> + <format type='raw'/> + <source protocol='nbd' name='Volume2/ImageDataFile'> + <host transport='unix' socket='/path/to/sock/datafile'/> + </source> + </dataFileStore> + <target dev='vdc' bus='virtio'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/var/lib/libvirt/images/rhel7.1484071880'/> + <backingStore type='file'> + <format type='qcow2'/> + <source file='/var/lib/libvirt/images/rhel7.1484071877'/> + <dataFileStore type='file'> + <format type='raw'/> + <source file='/tmp/data-file-store-2'/> + </dataFileStore> + </backingStore> + <target dev='vdd' bus='virtio'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='none'/> + <memballoon model='virtio'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/> + </memballoon> + </devices> +</domain> diff --git a/tests/qemuxmlconfdata/disk-qcow2-datafile-store.xml b/tests/qemuxmlconfdata/disk-qcow2-datafile-store.xml new file mode 100644 index 0000000000..797f8d6b34 --- /dev/null +++ b/tests/qemuxmlconfdata/disk-qcow2-datafile-store.xml @@ -0,0 +1,67 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='network' device='disk'> + <driver name='qemu' type='qcow2'/> + <source protocol='nbd' name='Volume2/Image'> + <host transport='unix' socket='/path/to/sock'/> + </source> + <dataFileStore type='file'> + <format type='raw'/> + <source file='/tmp/data-file-store'/> + </dataFileStore> + <target dev='vda' bus='virtio'/> + </disk> + <disk type='network' device='disk'> + <driver name='qemu' type='qcow2'/> + <source protocol='nbd' name='bar'> + <host transport='unix' socket='/var/run/nbdsock'/> + </source> + <dataFileStore type='block'> + <format type='raw'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + </dataFileStore> + <target dev='vdb' bus='virtio'/> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/var/lib/libvirt/images/rhel7.1484071879'/> + <dataFileStore type='network'> + <format type='raw'/> + <source protocol='nbd' name='Volume2/ImageDataFile'> + <host transport='unix' socket='/path/to/sock/datafile'/> + </source> + </dataFileStore> + <target dev='vdc' bus='virtio'/> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/var/lib/libvirt/images/rhel7.1484071880'/> + <backingStore type='file'> + <format type='qcow2'/> + <source file='/var/lib/libvirt/images/rhel7.1484071877'/> + <dataFileStore type='file'> + <format type='raw'/> + <source file='/tmp/data-file-store-2'/> + </dataFileStore> + </backingStore> + <target dev='vdd' bus='virtio'/> + </disk> + <controller type='usb' index='0'/> + <controller type='pci' index='0' model='pci-root'/> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/qemuxmlconftest.c b/tests/qemuxmlconftest.c index 194a4b8553..f3c8d0ae34 100644 --- a/tests/qemuxmlconftest.c +++ b/tests/qemuxmlconftest.c @@ -1651,6 +1651,7 @@ mymain(void) DO_TEST_CAPS_LATEST_PARSE_ERROR("disk-scsi-incompatible-address"); DO_TEST_CAPS_LATEST("disk-backing-chains-index"); DO_TEST_CAPS_LATEST("disk-backing-chains-noindex"); + DO_TEST_CAPS_LATEST("disk-qcow2-datafile-store"); DO_TEST_CAPS_ARCH_LATEST_FULL("disk-source-fd", "x86_64", ARG_FD_GROUP, "testgroup2", 2, 200, 205, ARG_FD_GROUP, "testgroup5", 1, 204, diff --git a/tests/virstoragetest.c b/tests/virstoragetest.c index 75d04e8030..24893064ac 100644 --- a/tests/virstoragetest.c +++ b/tests/virstoragetest.c @@ -129,6 +129,76 @@ testPrepImages(void) } +static void +testPrepImagesWithQcow2DataFiles(char **chains) +{ + g_autoptr(virCommand) cmdData1 = NULL; + g_autoptr(virCommand) cmdData2 = NULL; + g_autoptr(virCommand) cmdqcow2_1 = NULL; + g_autoptr(virCommand) cmdqcow2_2_backing = NULL; + g_autoptr(virCommand) cmdqcow2_2 = NULL; + g_autofree char *data1 = g_strdup_printf("%s/data-1", datadir); + g_autofree char *data2 = g_strdup_printf("%s/data-2", datadir); + g_autofree char *qcow2_1 = g_strdup_printf("%s/qcow2-1", datadir); + g_autofree char *qcow2_1_opt_create = g_strdup_printf("data_file=%s,data_file_raw=true", data1); + g_autofree char *qcow2_2_backing = g_strdup_printf("%s/qcow2-2-backing", datadir); + g_autofree char *qcow2_2_backing_opt_create = g_strdup_printf("data_file=%s,data_file_raw=true", data2); + g_autofree char *qcow2_2 = g_strdup_printf("%s/qcow2-2", datadir); + g_autofree char *qemuimg = virFindFileInPath("qemu-img"); + + if (!qemuimg) + return; + + /* Clean up from any earlier failed tests */ + virFileDeleteTree(datadir); + + if (g_mkdir_with_parents(datadir, 0777) < 0) { + VIR_TEST_VERBOSE("unable to create directory '%s'\n", datadir); + return; + } + + /* create 2 chains containing data-file + * 1. qcow2 image with data-file + * 2. qcow2 image with backing qcow2 image containing data-file */ + cmdData1 = virCommandNewArgList(qemuimg, "create", + "-f", "raw", + data1, "1k", NULL); + cmdData2 = virCommandNewArgList(qemuimg, "create", + "-f", "raw", + data2, "1k", NULL); + + cmdqcow2_1 = virCommandNewArgList(qemuimg, "create", + "-f", "qcow2", + "-o", qcow2_1_opt_create, + qcow2_1, "1k", NULL); + + cmdqcow2_2_backing = virCommandNewArgList(qemuimg, "create", + "-f", "qcow2", + "-o", qcow2_2_backing_opt_create, + qcow2_2_backing, "1k", NULL); + + cmdqcow2_2 = virCommandNewArgList(qemuimg, "create", + "-f", "qcow2", + "-F", "qcow2", + "-b", qcow2_2_backing, + qcow2_2, NULL); + + if (virCommandRun(cmdData1, NULL) < 0 || + virCommandRun(cmdData2, NULL) < 0 || + virCommandRun(cmdqcow2_1, NULL) < 0 || + virCommandRun(cmdqcow2_2_backing, NULL) < 0 || + virCommandRun(cmdqcow2_2, NULL) < 0) { + VIR_TEST_VERBOSE("failed to create backing chain with data file in '%s'\n", datadir); + return; + } + + chains[0] = g_steal_pointer(&qcow2_1); + chains[1] = g_steal_pointer(&qcow2_2); + + return; +} + + enum { EXP_PASS = 0, EXP_FAIL = 1, @@ -203,6 +273,26 @@ testStorageChain(const void *args) NULLSTR(virStorageFileFormatTypeToString(elt->format)), virStorageNetProtocolTypeToString(elt->protocol), NULLSTR(elt->nhosts ? elt->hosts[0].name : NULL)); + + if (elt->dataFileStore) { + g_autofree char *strippedPathDataFileRaw = virTestStablePath(elt->dataFileRaw); + g_autofree char *strippedPathDataFile = virTestStablePath(elt->dataFileStore->path); + + virBufferAsprintf(&buf, + "dataFileRaw: %s\n\n\n" + "dataFileStoreSource:\n" + "path: %s\n" + "capacity: %lld\n" + "encryption: %d\n" + "type:%s\n" + "format:%s\n", + strippedPathDataFileRaw, + strippedPathDataFile, + elt->dataFileStore->capacity, + !!elt->dataFileStore->encryption, + NULLSTR(virStorageTypeToString(elt->dataFileStore->type)), + NULLSTR(virStorageFileFormatTypeToString(elt->dataFileStore->format))); + } } virBufferTrim(&buf, "\n"); @@ -419,6 +509,7 @@ mymain(void) struct testPathRelativeBacking data4; struct testBackingParseData data5; g_autofree char *realchain = NULL; + g_auto(GStrv) qcow2chainDataFile = g_new0(char *, 2); virStorageSource fakeChain[4]; virStorageSource *chain = &fakeChain[0]; virStorageSource *chain2 = &fakeChain[1]; @@ -462,6 +553,13 @@ mymain(void) TEST_CHAIN("qcow2-qcow2_qcow2-qcow2_raw-raw", realchain, VIR_STORAGE_FILE_QCOW2, EXP_PASS); TEST_CHAIN("qcow2-auto_qcow2-qcow2_raw-raw", realchain, VIR_STORAGE_FILE_AUTO, EXP_PASS); + /* Prep 2 chains: + * 1) qcow2 image with data file + * 2) qcow2 -> qcow2 backing image with data file */ + testPrepImagesWithQcow2DataFiles(qcow2chainDataFile); + TEST_CHAIN("qcow2-data_file", qcow2chainDataFile[0], VIR_STORAGE_FILE_QCOW2, EXP_PASS); + TEST_CHAIN("qcow2-qcow2_qcow2-qcow2-data_file", qcow2chainDataFile[1], VIR_STORAGE_FILE_QCOW2, EXP_PASS); + testCleanupImages(); /* Test various combinations of qcow2 images with missing 'backing_format' */ diff --git a/tests/virstoragetestdata/out/qcow2-data_file b/tests/virstoragetestdata/out/qcow2-data_file new file mode 100644 index 0000000000..f132c9580a --- /dev/null +++ b/tests/virstoragetestdata/out/qcow2-data_file @@ -0,0 +1,20 @@ +path:ABS_BUILDDIR/virstoragedata/qcow2-1 +backingStoreRaw: <null> +backingStoreRawFormat: none(0) +capacity: 1024 +encryption: 0 +relPath:<null> +type:file +format:qcow2 +protocol:none +hostname:<null> + +dataFileRaw: ABS_BUILDDIR/virstoragedata/data-1 + + +dataFileStoreSource: +path: ABS_BUILDDIR/virstoragedata/data-1 +capacity: 0 +encryption: 0 +type:file +format:raw diff --git a/tests/virstoragetestdata/out/qcow2-qcow2_qcow2-qcow2-data_file b/tests/virstoragetestdata/out/qcow2-qcow2_qcow2-qcow2-data_file new file mode 100644 index 0000000000..3cfb1833cc --- /dev/null +++ b/tests/virstoragetestdata/out/qcow2-qcow2_qcow2-qcow2-data_file @@ -0,0 +1,31 @@ +path:ABS_BUILDDIR/virstoragedata/qcow2-2 +backingStoreRaw: ABS_BUILDDIR/virstoragedata/qcow2-2-backing +backingStoreRawFormat: qcow2(14) +capacity: 1024 +encryption: 0 +relPath:<null> +type:file +format:qcow2 +protocol:none +hostname:<null> + +path:ABS_BUILDDIR/virstoragedata/qcow2-2-backing +backingStoreRaw: <null> +backingStoreRawFormat: none(0) +capacity: 1024 +encryption: 0 +relPath:<null> +type:file +format:qcow2 +protocol:none +hostname:<null> + +dataFileRaw: ABS_BUILDDIR/virstoragedata/data-2 + + +dataFileStoreSource: +path: ABS_BUILDDIR/virstoragedata/data-2 +capacity: 0 +encryption: 0 +type:file +format:raw -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:49 +0300, Nikolai Barybin via Devel wrote:
- qemuxmlconftest: check various xml definitions of dataFileStore: types file, block, network and the case when data-file belongs to qcow2 backing image
- virstoragetest: create qcow2 image chains and check that data files are properly probed by metadata parser
- qemuxmlactivetest: check that status files will contain data files' node names
Each of the above should have been a separate patch. I'll thus split up this into 3 patches, each for it's own test program.
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> ---
[...]
+static void +testPrepImagesWithQcow2DataFiles(char **chains) +{ + g_autoptr(virCommand) cmdData1 = NULL; + g_autoptr(virCommand) cmdData2 = NULL; + g_autoptr(virCommand) cmdqcow2_1 = NULL; + g_autoptr(virCommand) cmdqcow2_2_backing = NULL; + g_autoptr(virCommand) cmdqcow2_2 = NULL; + g_autofree char *data1 = g_strdup_printf("%s/data-1", datadir); + g_autofree char *data2 = g_strdup_printf("%s/data-2", datadir); + g_autofree char *qcow2_1 = g_strdup_printf("%s/qcow2-1", datadir); + g_autofree char *qcow2_1_opt_create = g_strdup_printf("data_file=%s,data_file_raw=true", data1); + g_autofree char *qcow2_2_backing = g_strdup_printf("%s/qcow2-2-backing", datadir); + g_autofree char *qcow2_2_backing_opt_create = g_strdup_printf("data_file=%s,data_file_raw=true", data2); + g_autofree char *qcow2_2 = g_strdup_printf("%s/qcow2-2", datadir); + g_autofree char *qemuimg = virFindFileInPath("qemu-img"); + + if (!qemuimg) + return; + + /* Clean up from any earlier failed tests */ + virFileDeleteTree(datadir); + + if (g_mkdir_with_parents(datadir, 0777) < 0) { + VIR_TEST_VERBOSE("unable to create directory '%s'\n", datadir); + return; + } + + /* create 2 chains containing data-file + * 1. qcow2 image with data-file + * 2. qcow2 image with backing qcow2 image containing data-file */ + cmdData1 = virCommandNewArgList(qemuimg, "create", + "-f", "raw", + data1, "1k", NULL); + cmdData2 = virCommandNewArgList(qemuimg, "create", + "-f", "raw", + data2, "1k", NULL); + + cmdqcow2_1 = virCommandNewArgList(qemuimg, "create", + "-f", "qcow2", + "-o", qcow2_1_opt_create, + qcow2_1, "1k", NULL); + + cmdqcow2_2_backing = virCommandNewArgList(qemuimg, "create", + "-f", "qcow2", + "-o", qcow2_2_backing_opt_create, + qcow2_2_backing, "1k", NULL); + + cmdqcow2_2 = virCommandNewArgList(qemuimg, "create", + "-f", "qcow2", + "-F", "qcow2", + "-b", qcow2_2_backing, + qcow2_2, NULL); + + if (virCommandRun(cmdData1, NULL) < 0 || + virCommandRun(cmdData2, NULL) < 0 || + virCommandRun(cmdqcow2_1, NULL) < 0 || + virCommandRun(cmdqcow2_2_backing, NULL) < 0 || + virCommandRun(cmdqcow2_2, NULL) < 0) { + VIR_TEST_VERBOSE("failed to create backing chain with data file in '%s'\n", datadir); + return;
While all of the above will be deleted ... this error handling doesn't make sense, as you don't actually signal a failure to the caller, letting it fail later.
+ } + + chains[0] = g_steal_pointer(&qcow2_1); + chains[1] = g_steal_pointer(&qcow2_2); + + return; +}
Historically I've refactored virstoragetest to avoid use of 'qemu-img' so I'll delete all of the above and replace it with example images directly commited into the repository as we do with most of the other cases.

Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- docs/formatdomain.rst | 45 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index 18b60fe260..54a9e01034 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -2708,24 +2708,38 @@ paravirtualized driver is specified via the ``disk`` element. </backingStore> <target dev='vdd' bus='virtio'/> </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2' queues='4' queue_size='256' /> + <source file='/var/lib/libvirt/images/domain2.qcow'/> + <backingStore type='file'> + <format type='qcow2'/> + <source file='/var/lib/libvirt/images/base-with-data-file.qcow'/> + </backingStore> + <dataFileStore type='block'> + <format type='raw'/> + <source dev='/dev/mapper/base2'/> + <dataFileStore/> + </backingStore> + <target dev='vde' bus='virtio'/> + </disk> <disk type='nvme' device='disk'> <driver name='qemu' type='raw'/> <source type='pci' managed='yes' namespace='1'> <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/> </source> - <target dev='vde' bus='virtio'/> + <target dev='vdf' bus='virtio'/> </disk> <disk type='vhostuser' device='disk'> <driver name='qemu' type='raw'/> <source type='unix' path='/tmp/vhost-blk.sock'> <reconnect enabled='yes' timeout='10'/> </source> - <target dev='vdf' bus='virtio'/> + <target dev='vdg' bus='virtio'/> </disk> <disk type='vhostvdpa' device='disk'> <driver name='qemu' type='raw'/> <source dev='/dev/vhost-vdpa-0' /> - <target dev='vdg' bus='virtio'/> + <target dev='vdh' bus='virtio'/> </disk> </devices> ... @@ -3116,6 +3130,27 @@ paravirtualized driver is specified via the ``disk`` element. accessible or its disk chain is broken, with startupPolicy 'optional' the guest will drop this disk. This feature doesn't support migration currently. +``dataFileStore`` + This element describes external data file store, which is represented by ``qcow2`` + incompatible features bit and allows to store guest clusters are the external + data file. For such images, clusters in the external data file are not refcounted. + The following attribute is supported in ``dataFileStore``: + + ``type`` + The ``type`` attribute represents the type of disk used by the data file store, + see disk type attribute above for more details and possible values. + + Moreover, ``dataFileStore`` supports the following sub-elements: + + ``format`` + The ``format`` element contains ``type`` attribute which specifies the + internal format of the data file store. Only ``raw`` value is supported. + + ``source`` + This element has the same structure as the ``source`` element in ``disk``. + It specifies which file, device, or network location contains the data of + the described data file store. + ``backingStore`` This element describes the backing store used by the disk specified by sibling ``source`` element. :since:`Since 1.2.4`. If the hypervisor driver @@ -3161,6 +3196,10 @@ paravirtualized driver is specified via the ``disk`` element. ``backingStore`` If the backing store is not self-contained, the next element in the chain is described by nested ``backingStore`` element. + ``dataFileStore`` + If backing store is in ``qcow2`` format it is allowed to have data file store. + But it should me mentioned that an image can have either backing store or data + file store, not both. ``mirror`` This element is present if the hypervisor has started a long-running block -- 2.43.5

On Wed, Nov 20, 2024 at 18:48:50 +0300, Nikolai Barybin via Devel wrote:
Signed-off-by: Nikolai Barybin <nikolai.barybin@virtuozzo.com> --- docs/formatdomain.rst | 45 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-)
I'll adapt the docs to conform with the changes in naming and placement of the element as well as squash it into the commit which adds the schema.
diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index 18b60fe260..54a9e01034 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -2708,24 +2708,38 @@ paravirtualized driver is specified via the ``disk`` element. </backingStore> <target dev='vdd' bus='virtio'/> </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2' queues='4' queue_size='256' /> + <source file='/var/lib/libvirt/images/domain2.qcow'/> + <backingStore type='file'> + <format type='qcow2'/> + <source file='/var/lib/libvirt/images/base-with-data-file.qcow'/> + </backingStore> + <dataFileStore type='block'> + <format type='raw'/> + <source dev='/dev/mapper/base2'/> + <dataFileStore/> + </backingStore> + <target dev='vde' bus='virtio'/> + </disk> <disk type='nvme' device='disk'> <driver name='qemu' type='raw'/> <source type='pci' managed='yes' namespace='1'> <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/> </source> - <target dev='vde' bus='virtio'/> + <target dev='vdf' bus='virtio'/> </disk> <disk type='vhostuser' device='disk'> <driver name='qemu' type='raw'/> <source type='unix' path='/tmp/vhost-blk.sock'> <reconnect enabled='yes' timeout='10'/> </source> - <target dev='vdf' bus='virtio'/> + <target dev='vdg' bus='virtio'/> </disk> <disk type='vhostvdpa' device='disk'> <driver name='qemu' type='raw'/> <source dev='/dev/vhost-vdpa-0' /> - <target dev='vdg' bus='virtio'/> + <target dev='vdh' bus='virtio'/> </disk> </devices> ... @@ -3116,6 +3130,27 @@ paravirtualized driver is specified via the ``disk`` element. accessible or its disk chain is broken, with startupPolicy 'opt> guest will drop this disk. This feature doesn't support migration currently.
+``dataFileStore`` + This element describes external data file store, which is represented by ``qcow2`` + incompatible features bit and allows to store guest clusters are the external + data file. For such images, clusters in the external data file are not refcounted. + The following attribute is supported in ``dataFileStore``: + + ``type`` + The ``type`` attribute represents the type of disk used by the data file store, + see disk type attribute above for more details and possible values. + + Moreover, ``dataFileStore`` supports the following sub-elements: + + ``format`` + The ``format`` element contains ``type`` attribute which specifies the + internal format of the data file store. Only ``raw`` value is supported. + + ``source`` + This element has the same structure as the ``source`` element in ``disk``. + It specifies which file, device, or network location contains the data of + the described data file store. +

On Wed, Nov 20, 2024 at 18:48:35 +0300, Nikolai Barybin via Devel wrote:
Hello everyone!
With help of Peter's comprehensive review I finally present 3rd version of this series.
Changes since last revision:
- minor code improvements including memory leaks fixes
- splitted and regrouped some patches for more logical structure
- fixed issue with erroneous disk with data file detatch
- completely rewritten tests
- added some piece of documentation
Thanks for the new version. I was recently informed about another potential use of the 'data-file' feature so any comments I reply with I'll be directly fixing in the patches themselves. No need to post another version. Please remember for future postings to write commit messages. I asked for them the last time too.

Hello everyone!
With help of Peter's comprehensive review I finally present 3rd version of this series.
Changes since last revision:
- minor code improvements including memory leaks fixes
- splitted and regrouped some patches for more logical structure
- fixed issue with erroneous disk with data file detatch
- completely rewritten tests
- added some piece of documentation Thanks for the new version. I was recently informed about another
On Wed, Nov 20, 2024 at 18:48:35 +0300, Nikolai Barybin via Devel wrote: potential use of the 'data-file' feature so any comments I reply with I'll be directly fixing in the patches themselves. No need to post another version.
Please remember for future postings to write commit messages. I asked for them the last time too.
Thank you, Peter! Sure, I read your previous notes regarding commit messages and even added some, but should've added more. I'll keep this in mind next time.

On Wed, Nov 20, 2024 at 18:48:35 +0300, Nikolai Barybin via Devel wrote:
Hello everyone!
Nikolai Barybin (15): conf: add data-file feature and related fields to virStorageSource Add VIR_STORAGE_FILE_FEATURE_DATA_FILE to virStorageFileFeature enum conf: schemas: add data-file store to domain rng schema conf: implement XML parsing/formating for dataFileStore storage file: add getDataFile function to FileTypeInfo storage file: add qcow2 data-file path parsing from header storage file: fill in src->dataFileStore during file probe security: DAC: handle qcow2 data-file on image label set/restore security: selinux: handle qcow2 data-file on image label set/restore security: apparmor: handle qcow2 data-file qemu: put data-file path to VM's cgroup and namespace qemu: factor out qemuDomainPrepareStorageSource() qemu: enable basic qcow2 data-file feature support tests: add qcow2 data-file tests docs: formatdomain: describe dataFileStore element of disk
I've finished my reviews and tests. As noted during the review I've renamed and moved the position of the XML element so that it's stored under the <source element: <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/tmp/datafile.qcow2'> <dataStore type='block'> <format type='raw'/> <source dev='/dev/lvm/base1'/> </dataStore> </source> <target dev='vda' bus='virtio'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> </disk> I've fixed few bugs identified while testing the code and I'm currently running upstream CI. Once that finishes I'll push these patches with the modification I've suggested so that we avoid another round trip. Thanks! Peter
participants (2)
-
Nikolai Barybin
-
Peter Krempa