[libvirt] [PATCH v2 0/6] Add support for qcow3 images

Add support for creating qcow3 images with or without lazy_refcounts, using them with qemu and creating snapshots (both internal and external). Diff to V1: https://www.redhat.com/archives/libvir-list/2013-January/msg00610.html * only use one bitmap for all features (and one <features> element) * support snapshots * detect which features are supported by qemu-img Ján Tomko (6): storage: refactor qemu-img command line generation util: use helper functions for extracting image headers util: add support for probing qcow3 images qemu: add support for running domains with qcow3 images storage: add support for creating qcow3 images qemu: add qcow3 support to external snapshots docs/formatsnapshot.html.in | 7 + docs/formatstorage.html.in | 14 +- docs/schemas/Makefile.am | 1 + docs/schemas/domaincommon.rng | 1 + docs/schemas/domainsnapshot.rng | 4 + docs/schemas/storagefeatures.rng | 17 +++ docs/schemas/storagevol.rng | 6 +- libvirt.spec.in | 1 + mingw-libvirt.spec.in | 2 + src/conf/snapshot_conf.c | 63 +++++++++ src/conf/snapshot_conf.h | 2 + src/conf/storage_conf.c | 69 ++++++++++ src/conf/storage_conf.h | 4 + src/libvirt_private.syms | 4 + src/qemu/qemu_command.c | 2 +- src/qemu/qemu_driver.c | 24 +++- src/qemu/qemu_hotplug.c | 4 +- src/storage/storage_backend.c | 189 ++++++++++++++++++------- src/storage/storage_backend_fs.c | 6 + src/util/virstoragefile.c | 288 ++++++++++++++++++++++++++------------- src/util/virstoragefile.h | 13 ++ 21 files changed, 565 insertions(+), 156 deletions(-) create mode 100644 docs/schemas/storagefeatures.rng -- 1.7.12.4

Branch by imgformat first, since we only add new features if -o backing_fmt is supported. Switch to virBuffer for generating -o options to make adding new options easier. The only change in the generated command line is movement of the -o/-F options to the end when creating images with backing stores. --- src/storage/storage_backend.c | 77 +++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c index cab72c6..f9e604a 100644 --- a/src/storage/storage_backend.c +++ b/src/storage/storage_backend.c @@ -669,6 +669,8 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, bool do_encryption = (vol->target.encryption != NULL); unsigned long long int size_arg; bool preallocate = false; + char *options = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1); @@ -810,61 +812,44 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, if (inputvol) { virCommandAddArgList(cmd, "convert", "-f", inputType, "-O", type, inputPath, vol->target.path, NULL); - - if (imgformat == QEMU_IMG_BACKING_FORMAT_OPTIONS && - (do_encryption || preallocate)) { - virCommandAddArg(cmd, "-o"); - virCommandAddArgFormat(cmd, "%s%s%s", do_encryption ? "encryption=on" : "", - (do_encryption && preallocate) ? "," : "", - preallocate ? "preallocation=metadata" : ""); - } else if (do_encryption) { - virCommandAddArg(cmd, "-e"); - } } else if (vol->backingStore.path) { + virCommandAddArgList(cmd, "create", "-f", type, "-b", + vol->backingStore.path, vol->target.path, NULL); + virCommandAddArgFormat(cmd, "%lluK", size_arg); + } else { virCommandAddArgList(cmd, "create", "-f", type, - "-b", vol->backingStore.path, NULL); + vol->target.path, NULL); + virCommandAddArgFormat(cmd, "%lluK", size_arg); + } - switch (imgformat) { - case QEMU_IMG_BACKING_FORMAT_FLAG: - virCommandAddArgList(cmd, "-F", backingType, vol->target.path, - NULL); - virCommandAddArgFormat(cmd, "%lluK", size_arg); + if (imgformat == QEMU_IMG_BACKING_FORMAT_OPTIONS) { + if (do_encryption) + virBufferAddLit(&buf, ",encryption=on"); - if (do_encryption) - virCommandAddArg(cmd, "-e"); - break; + if (!inputvol && vol->backingStore.path) + virBufferAsprintf(&buf, ",backing_fmt=%s", backingType); + else if (preallocate) + virBufferAddLit(&buf, ",preallocation=metadata"); - case QEMU_IMG_BACKING_FORMAT_OPTIONS: - virCommandAddArg(cmd, "-o"); - virCommandAddArgFormat(cmd, "backing_fmt=%s%s", backingType, - do_encryption ? ",encryption=on" : ""); - virCommandAddArg(cmd, vol->target.path); - virCommandAddArgFormat(cmd, "%lluK", size_arg); - break; + if (virBufferError(&buf) > 0) { + virReportOOMError(); + goto cleanup; + } - default: - VIR_INFO("Unable to set backing store format for %s with %s", - vol->target.path, create_tool); + if ((options = virBufferContentAndReset(&buf))) + virCommandAddArgList(cmd, "-o", &(options[1]), NULL); - virCommandAddArg(cmd, vol->target.path); - virCommandAddArgFormat(cmd, "%lluK", size_arg); - if (do_encryption) - virCommandAddArg(cmd, "-e"); - } + VIR_FREE(options); } else { - virCommandAddArgList(cmd, "create", "-f", type, - vol->target.path, NULL); - virCommandAddArgFormat(cmd, "%lluK", size_arg); - - if (imgformat == QEMU_IMG_BACKING_FORMAT_OPTIONS && - (do_encryption || preallocate)) { - virCommandAddArg(cmd, "-o"); - virCommandAddArgFormat(cmd, "%s%s%s", do_encryption ? "encryption=on" : "", - (do_encryption && preallocate) ? "," : "", - preallocate ? "preallocation=metadata" : ""); - } else if (do_encryption) { - virCommandAddArg(cmd, "-e"); + if (!inputvol && vol->backingStore.path) { + if (imgformat == QEMU_IMG_BACKING_FORMAT_FLAG) + virCommandAddArgList(cmd, "-F", backingType, NULL); + else + VIR_INFO("Unable to set backing store format for %s with %s", + vol->target.path, create_tool); } + if (do_encryption) + virCommandAddArg(cmd, "-e"); } ret = virStorageBackendCreateExecCommand(pool, vol, cmd); -- 1.7.12.4

On Tue, Feb 05, 2013 at 12:56:12PM +0100, Ján Tomko wrote:
Branch by imgformat first, since we only add new features if -o backing_fmt is supported.
Switch to virBuffer for generating -o options to make adding new options easier.
The only change in the generated command line is movement of the -o/-F options to the end when creating images with backing stores. --- src/storage/storage_backend.c | 77 +++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 46 deletions(-)
diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c index cab72c6..f9e604a 100644 --- a/src/storage/storage_backend.c +++ b/src/storage/storage_backend.c @@ -669,6 +669,8 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, bool do_encryption = (vol->target.encryption != NULL); unsigned long long int size_arg; bool preallocate = false; + char *options = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER;
virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1);
@@ -810,61 +812,44 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, if (inputvol) { virCommandAddArgList(cmd, "convert", "-f", inputType, "-O", type, inputPath, vol->target.path, NULL); - - if (imgformat == QEMU_IMG_BACKING_FORMAT_OPTIONS && - (do_encryption || preallocate)) { - virCommandAddArg(cmd, "-o"); - virCommandAddArgFormat(cmd, "%s%s%s", do_encryption ? "encryption=on" : "", - (do_encryption && preallocate) ? "," : "", - preallocate ? "preallocation=metadata" : ""); - } else if (do_encryption) { - virCommandAddArg(cmd, "-e"); - } } else if (vol->backingStore.path) { + virCommandAddArgList(cmd, "create", "-f", type, "-b", + vol->backingStore.path, vol->target.path, NULL); + virCommandAddArgFormat(cmd, "%lluK", size_arg); + } else { virCommandAddArgList(cmd, "create", "-f", type, - "-b", vol->backingStore.path, NULL); + vol->target.path, NULL); + virCommandAddArgFormat(cmd, "%lluK", size_arg); + }
- switch (imgformat) { - case QEMU_IMG_BACKING_FORMAT_FLAG: - virCommandAddArgList(cmd, "-F", backingType, vol->target.path, - NULL); - virCommandAddArgFormat(cmd, "%lluK", size_arg); + if (imgformat == QEMU_IMG_BACKING_FORMAT_OPTIONS) { + if (do_encryption) + virBufferAddLit(&buf, ",encryption=on");
- if (do_encryption) - virCommandAddArg(cmd, "-e"); - break; + if (!inputvol && vol->backingStore.path) + virBufferAsprintf(&buf, ",backing_fmt=%s", backingType); + else if (preallocate) + virBufferAddLit(&buf, ",preallocation=metadata");
This looks like it would result in "-o ,preallocation=metadata" if the do_encryption arg is false. I don't believe that will work.
- case QEMU_IMG_BACKING_FORMAT_OPTIONS: - virCommandAddArg(cmd, "-o"); - virCommandAddArgFormat(cmd, "backing_fmt=%s%s", backingType, - do_encryption ? ",encryption=on" : ""); - virCommandAddArg(cmd, vol->target.path); - virCommandAddArgFormat(cmd, "%lluK", size_arg); - break; + if (virBufferError(&buf) > 0) { + virReportOOMError(); + goto cleanup; + }
- default: - VIR_INFO("Unable to set backing store format for %s with %s", - vol->target.path, create_tool); + if ((options = virBufferContentAndReset(&buf))) + virCommandAddArgList(cmd, "-o", &(options[1]), NULL);
IIUC the flow correctly this also means that instead of # qemu-img create -f qcow2 foo 10G -o preallocation=metadata the code now does # qemu-img create -f qcow2 -o preallocation=metadata foo 10G while it appears qemu-img currently allows for option args to come after positional args, this isn't a nice thing to rely on in general. It is the kind of silent behaviour that someting is likely to break.
- virCommandAddArg(cmd, vol->target.path); - virCommandAddArgFormat(cmd, "%lluK", size_arg); - if (do_encryption) - virCommandAddArg(cmd, "-e"); - } + VIR_FREE(options); } else { - virCommandAddArgList(cmd, "create", "-f", type, - vol->target.path, NULL); - virCommandAddArgFormat(cmd, "%lluK", size_arg); - - if (imgformat == QEMU_IMG_BACKING_FORMAT_OPTIONS && - (do_encryption || preallocate)) { - virCommandAddArg(cmd, "-o"); - virCommandAddArgFormat(cmd, "%s%s%s", do_encryption ? "encryption=on" : "", - (do_encryption && preallocate) ? "," : "", - preallocate ? "preallocation=metadata" : ""); - } else if (do_encryption) { - virCommandAddArg(cmd, "-e"); + if (!inputvol && vol->backingStore.path) { + if (imgformat == QEMU_IMG_BACKING_FORMAT_FLAG) + virCommandAddArgList(cmd, "-F", backingType, NULL); + else + VIR_INFO("Unable to set backing store format for %s with %s", + vol->target.path, create_tool);
s/INFO/DEBUG/
} + if (do_encryption) + virCommandAddArg(cmd, "-e"); }
ret = virStorageBackendCreateExecCommand(pool, vol, cmd);
In general, I'd be alot happier if we had a test case to cover the generation of the qemu-img command line args. ie separate out the creation of the 'virCommandPtr' instance, from the execution of it, and then have a test case which validates the args we have constructed. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 02/05/13 18:10, Daniel P. Berrange wrote:
On Tue, Feb 05, 2013 at 12:56:12PM +0100, Ján Tomko wrote:
- switch (imgformat) { - case QEMU_IMG_BACKING_FORMAT_FLAG: - virCommandAddArgList(cmd, "-F", backingType, vol->target.path, - NULL); - virCommandAddArgFormat(cmd, "%lluK", size_arg); + if (imgformat == QEMU_IMG_BACKING_FORMAT_OPTIONS) { + if (do_encryption) + virBufferAddLit(&buf, ",encryption=on");
- if (do_encryption) - virCommandAddArg(cmd, "-e"); - break; + if (!inputvol && vol->backingStore.path) + virBufferAsprintf(&buf, ",backing_fmt=%s", backingType); + else if (preallocate) + virBufferAddLit(&buf, ",preallocation=metadata");
This looks like it would result in "-o ,preallocation=metadata" if the do_encryption arg is false. I don't believe that will work.
Yes, all the options are added with a leading comma...
- case QEMU_IMG_BACKING_FORMAT_OPTIONS: - virCommandAddArg(cmd, "-o"); - virCommandAddArgFormat(cmd, "backing_fmt=%s%s", backingType, - do_encryption ? ",encryption=on" : ""); - virCommandAddArg(cmd, vol->target.path); - virCommandAddArgFormat(cmd, "%lluK", size_arg); - break; + if (virBufferError(&buf) > 0) { + virReportOOMError(); + goto cleanup; + }
- default: - VIR_INFO("Unable to set backing store format for %s with %s", - vol->target.path, create_tool); + if ((options = virBufferContentAndReset(&buf))) + virCommandAddArgList(cmd, "-o", &(options[1]), NULL);
...which is ignored when the options are added to the argument list.
IIUC the flow correctly this also means that instead of
# qemu-img create -f qcow2 foo 10G -o preallocation=metadata
the code now does
# qemu-img create -f qcow2 -o preallocation=metadata foo 10G
while it appears qemu-img currently allows for option args to come after positional args, this isn't a nice thing to rely on in general. It is the kind of silent behaviour that someting is likely to break.
Right now we add the -o encryption|preallocation or -e option args after the positional arguments in every case except for "create" with "-o backing_fmt", so it would be the first command line both with and without my patch. But yes, it would be nicer with options in the front and a test for it. Jan

On Wed, Feb 06, 2013 at 12:24:50PM +0100, Ján Tomko wrote:
On 02/05/13 18:10, Daniel P. Berrange wrote:
On Tue, Feb 05, 2013 at 12:56:12PM +0100, Ján Tomko wrote:
- switch (imgformat) { - case QEMU_IMG_BACKING_FORMAT_FLAG: - virCommandAddArgList(cmd, "-F", backingType, vol->target.path, - NULL); - virCommandAddArgFormat(cmd, "%lluK", size_arg); + if (imgformat == QEMU_IMG_BACKING_FORMAT_OPTIONS) { + if (do_encryption) + virBufferAddLit(&buf, ",encryption=on");
- if (do_encryption) - virCommandAddArg(cmd, "-e"); - break; + if (!inputvol && vol->backingStore.path) + virBufferAsprintf(&buf, ",backing_fmt=%s", backingType); + else if (preallocate) + virBufferAddLit(&buf, ",preallocation=metadata");
This looks like it would result in "-o ,preallocation=metadata" if the do_encryption arg is false. I don't believe that will work.
Yes, all the options are added with a leading comma...
- case QEMU_IMG_BACKING_FORMAT_OPTIONS: - virCommandAddArg(cmd, "-o"); - virCommandAddArgFormat(cmd, "backing_fmt=%s%s", backingType, - do_encryption ? ",encryption=on" : ""); - virCommandAddArg(cmd, vol->target.path); - virCommandAddArgFormat(cmd, "%lluK", size_arg); - break; + if (virBufferError(&buf) > 0) { + virReportOOMError(); + goto cleanup; + }
- default: - VIR_INFO("Unable to set backing store format for %s with %s", - vol->target.path, create_tool); + if ((options = virBufferContentAndReset(&buf))) + virCommandAddArgList(cmd, "-o", &(options[1]), NULL);
...which is ignored when the options are added to the argument list.
Ewww, this is gross & really obscure. Add the comma at the end, and then use 'virBufferTrim' after the last one, along with a comment indicating why you are doing this. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Rename/retype the little endian functions used for qed images and add corresponding big endian ones. --- src/util/virstoragefile.c | 152 ++++++++++++++++++++-------------------------- 1 file changed, 66 insertions(+), 86 deletions(-) diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index 6e2d61e..9e50ccb 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -208,6 +208,51 @@ static struct FileTypeInfo const fileTypeInfo[] = { }; verify(ARRAY_CARDINALITY(fileTypeInfo) == VIR_STORAGE_FILE_LAST); + +static uint64_t +getBEHeader64(const unsigned char *buf) +{ + return (((uint64_t)buf[0] << 56) + | ((uint64_t)buf[1] << 48) + | ((uint64_t)buf[2] << 40) + | ((uint64_t)buf[3] << 32) + | ((uint64_t)buf[4] << 24) + | ((uint64_t)buf[5] << 16) + | ((uint64_t)buf[6] << 8) + | ((uint64_t)buf[7])); +} + +static uint32_t +getBEHeader32(const unsigned char *buf) +{ + return (((uint32_t)buf[0] << 24) + | ((uint32_t)buf[1] << 16) + | ((uint32_t)buf[2] << 8) + | ((uint32_t)buf[3])); +} + +static uint32_t +getLEHeader32(const unsigned char *loc) +{ + return (((uint32_t)loc[3] << 24) | + ((uint32_t)loc[2] << 16) | + ((uint32_t)loc[1] << 8) | + ((uint32_t)loc[0] << 0)); +} + +static uint64_t +getLEHeader64(const unsigned char *loc) +{ + return (((uint64_t)loc[7] << 56) | + ((uint64_t)loc[6] << 48) | + ((uint64_t)loc[5] << 40) | + ((uint64_t)loc[4] << 32) | + ((uint64_t)loc[3] << 24) | + ((uint64_t)loc[2] << 16) | + ((uint64_t)loc[1] << 8) | + ((uint64_t)loc[0] << 0)); +} + static int cowGetBackingStore(char **res, int *format, @@ -255,16 +300,8 @@ qcow2GetBackingStoreFormat(int *format, */ while (offset < (buf_size-8) && offset < (extension_end-8)) { - unsigned int magic = - (buf[offset] << 24) + - (buf[offset+1] << 16) + - (buf[offset+2] << 8) + - (buf[offset+3]); - unsigned int len = - (buf[offset+4] << 24) + - (buf[offset+5] << 16) + - (buf[offset+6] << 8) + - (buf[offset+7]); + unsigned int magic = getBEHeader32(buf + offset); + unsigned int len = getBEHeader32(buf + offset + 4); offset += 8; @@ -312,20 +349,12 @@ qcowXGetBackingStore(char **res, if (buf_size < QCOWX_HDR_BACKING_FILE_OFFSET+8+4) return BACKING_STORE_INVALID; - offset = (((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET] << 56) - | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+1] << 48) - | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+2] << 40) - | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+3] << 32) - | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+4] << 24) - | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+5] << 16) - | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+6] << 8) - | buf[QCOWX_HDR_BACKING_FILE_OFFSET+7]); /* QCowHeader.backing_file_offset */ + /* QCowHeader.backing_file_offset */ + offset = getBEHeader64(buf + QCOWX_HDR_BACKING_FILE_OFFSET); if (offset > buf_size) return BACKING_STORE_INVALID; - size = ((buf[QCOWX_HDR_BACKING_FILE_SIZE] << 24) - | (buf[QCOWX_HDR_BACKING_FILE_SIZE+1] << 16) - | (buf[QCOWX_HDR_BACKING_FILE_SIZE+2] << 8) - | buf[QCOWX_HDR_BACKING_FILE_SIZE+3]); /* QCowHeader.backing_file_size */ + /* QCowHeader.backing_file_size */ + size = getBEHeader32(buf + QCOWX_HDR_BACKING_FILE_SIZE); if (size == 0) { if (format) *format = VIR_STORAGE_FILE_NONE; @@ -468,28 +497,6 @@ cleanup: return ret; } -static unsigned long -qedGetHeaderUL(const unsigned char *loc) -{ - return (((unsigned long)loc[3] << 24) | - ((unsigned long)loc[2] << 16) | - ((unsigned long)loc[1] << 8) | - ((unsigned long)loc[0] << 0)); -} - -static unsigned long long -qedGetHeaderULL(const unsigned char *loc) -{ - return (((unsigned long long)loc[7] << 56) | - ((unsigned long long)loc[6] << 48) | - ((unsigned long long)loc[5] << 40) | - ((unsigned long long)loc[4] << 32) | - ((unsigned long long)loc[3] << 24) | - ((unsigned long long)loc[2] << 16) | - ((unsigned long long)loc[1] << 8) | - ((unsigned long long)loc[0] << 0)); -} - static int qedGetBackingStore(char **res, int *format, @@ -503,7 +510,7 @@ qedGetBackingStore(char **res, /* Check if this image has a backing file */ if (buf_size < QED_HDR_FEATURES_OFFSET+8) return BACKING_STORE_INVALID; - flags = qedGetHeaderULL(buf + QED_HDR_FEATURES_OFFSET); + flags = getLEHeader64(buf + QED_HDR_FEATURES_OFFSET); if (!(flags & QED_F_BACKING_FILE)) { *format = VIR_STORAGE_FILE_NONE; return BACKING_STORE_OK; @@ -512,10 +519,10 @@ qedGetBackingStore(char **res, /* Parse the backing file */ if (buf_size < QED_HDR_BACKING_FILE_OFFSET+8) return BACKING_STORE_INVALID; - offset = qedGetHeaderUL(buf + QED_HDR_BACKING_FILE_OFFSET); + offset = getLEHeader32(buf + QED_HDR_BACKING_FILE_OFFSET); if (offset > buf_size) return BACKING_STORE_INVALID; - size = qedGetHeaderUL(buf + QED_HDR_BACKING_FILE_SIZE); + size = getLEHeader32(buf + QED_HDR_BACKING_FILE_SIZE); if (size == 0) return BACKING_STORE_OK; if (offset + size > buf_size || offset + size < offset) @@ -633,19 +640,10 @@ virStorageFileMatchesVersion(int format, if ((fileTypeInfo[format].versionOffset + 4) > buflen) return false; - if (fileTypeInfo[format].endian == LV_LITTLE_ENDIAN) { - version = - (buf[fileTypeInfo[format].versionOffset+3] << 24) | - (buf[fileTypeInfo[format].versionOffset+2] << 16) | - (buf[fileTypeInfo[format].versionOffset+1] << 8) | - (buf[fileTypeInfo[format].versionOffset]); - } else { - version = - (buf[fileTypeInfo[format].versionOffset] << 24) | - (buf[fileTypeInfo[format].versionOffset+1] << 16) | - (buf[fileTypeInfo[format].versionOffset+2] << 8) | - (buf[fileTypeInfo[format].versionOffset+3]); - } + if (fileTypeInfo[format].endian == LV_LITTLE_ENDIAN) + version = getLEHeader32(buf + fileTypeInfo[format].versionOffset); + else + version = getBEHeader32(buf + fileTypeInfo[format].versionOffset); VIR_DEBUG("Compare detected version %d vs expected version %d", version, fileTypeInfo[format].versionNumber); @@ -688,25 +686,11 @@ virStorageFileGetMetadataFromBuf(int format, return 1; if (fileTypeInfo[format].endian == LV_LITTLE_ENDIAN) { - meta->capacity = - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+7] << 56) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+6] << 48) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+5] << 40) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+4] << 32) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+3] << 24) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+2] << 16) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+1] << 8) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset]); + meta->capacity = getLEHeader64(buf + + fileTypeInfo[format].sizeOffset); } else { - meta->capacity = - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset] << 56) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+1] << 48) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+2] << 40) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+3] << 32) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+4] << 24) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+5] << 16) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+6] << 8) | - ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+7]); + meta->capacity = getBEHeader64(buf + + fileTypeInfo[format].sizeOffset); } /* Avoid unlikely, but theoretically possible overflow */ if (meta->capacity > (ULLONG_MAX / fileTypeInfo[format].sizeMultiplier)) @@ -715,14 +699,10 @@ virStorageFileGetMetadataFromBuf(int format, } if (fileTypeInfo[format].qcowCryptOffset != -1) { - int crypt_format; - - crypt_format = - (buf[fileTypeInfo[format].qcowCryptOffset] << 24) | - (buf[fileTypeInfo[format].qcowCryptOffset+1] << 16) | - (buf[fileTypeInfo[format].qcowCryptOffset+2] << 8) | - (buf[fileTypeInfo[format].qcowCryptOffset+3]); - meta->encrypted = crypt_format != 0; + int crypt_fmt; + + crypt_fmt = getBEHeader32(buf + fileTypeInfo[format].qcowCryptOffset); + meta->encrypted = crypt_fmt != 0; } if (fileTypeInfo[format].getBackingStore != NULL) { -- 1.7.12.4

Add new 'qcow3' format, and support for detecting the backing file and qcow3 features in the header (just one so far). --- docs/schemas/storagevol.rng | 1 + src/storage/storage_backend_fs.c | 1 + src/util/virstoragefile.c | 101 +++++++++++++++++++++++++++++++++++---- src/util/virstoragefile.h | 11 +++++ 4 files changed, 106 insertions(+), 8 deletions(-) diff --git a/docs/schemas/storagevol.rng b/docs/schemas/storagevol.rng index 10b7847..653491d 100644 --- a/docs/schemas/storagevol.rng +++ b/docs/schemas/storagevol.rng @@ -190,6 +190,7 @@ <value>iso</value> <value>qcow</value> <value>qcow2</value> + <value>qcow3</value> <value>qed</value> <value>vmdk</value> <value>vpc</value> diff --git a/src/storage/storage_backend_fs.c b/src/storage/storage_backend_fs.c index a582804..f356173 100644 --- a/src/storage/storage_backend_fs.c +++ b/src/storage/storage_backend_fs.c @@ -136,6 +136,7 @@ virStorageBackendProbeTarget(virStorageVolTargetPtr target, switch (target->format) { case VIR_STORAGE_FILE_QCOW: case VIR_STORAGE_FILE_QCOW2: + case VIR_STORAGE_FILE_QCOW3: (*encryption)->format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW; break; default: diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index 9e50ccb..c74a8fd 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -36,6 +36,7 @@ #endif #include "dirname.h" #include "viralloc.h" +#include "virbitmap.h" #include "virerror.h" #include "virlog.h" #include "virfile.h" @@ -50,9 +51,13 @@ VIR_ENUM_IMPL(virStorageFileFormat, "none", "raw", "dir", "bochs", "cloop", "cow", "dmg", "iso", - "qcow", "qcow2", "qed", "vmdk", "vpc", + "qcow", "qcow2", "qcow3", "qed", "vmdk", "vpc", "fat", "vhd", "vdi") +VIR_ENUM_IMPL(virStorageFileFeaturesQcow3, + VIR_STORAGE_FILE_FEAT_QCOW3_LAST, + "lazy_refcounts") + enum lv_endian { LV_LITTLE_ENDIAN = 1, /* 1234 */ LV_BIG_ENDIAN /* 4321 */ @@ -96,6 +101,8 @@ static int qcow1GetBackingStore(char **, int *, const unsigned char *, size_t); static int qcow2GetBackingStore(char **, int *, const unsigned char *, size_t); +static int qcow3GetBackingStore(char **, int *, + const unsigned char *, size_t); static int vmdk4GetBackingStore(char **, int *, const unsigned char *, size_t); static int @@ -111,6 +118,12 @@ qedGetBackingStore(char **, int *, const unsigned char *, size_t); #define QCOW1_HDR_TOTAL_SIZE (QCOW1_HDR_CRYPT+4+8) #define QCOW2_HDR_TOTAL_SIZE (QCOW2_HDR_CRYPT+4+4+8+8+4+4+8) +#define QCOW3_HDR_FEATURES_INCOMPATIBLE (QCOW2_HDR_TOTAL_SIZE) +#define QCOW3_HDR_FEATURES_COMPATIBLE (QCOW3_HDR_FEATURES_INCOMPATIBLE+8) +#define QCOW3_HDR_FEATURES_AUTOCLEAR (QCOW3_HDR_FEATURES_COMPATIBLE+8) + +/* The location of the header size [4 bytes] */ +#define QCOW3_HDR_SIZE (QCOW2_HDR_CRYPT+4+4+8+8+4+4+8+8+8+8+4) #define QCOW2_HDR_EXTENSION_END 0 #define QCOW2_HDR_EXTENSION_BACKING_FORMAT 0xE2792ACA @@ -178,6 +191,11 @@ static struct FileTypeInfo const fileTypeInfo[] = { LV_BIG_ENDIAN, 4, 2, QCOWX_HDR_IMAGE_SIZE, 8, 1, QCOW2_HDR_CRYPT, qcow2GetBackingStore, }, + [VIR_STORAGE_FILE_QCOW3] = { + 0, "QFI", NULL, + LV_BIG_ENDIAN, 4, 3, + QCOWX_HDR_IMAGE_SIZE, 8, 1, QCOW2_HDR_CRYPT, qcow3GetBackingStore, + }, [VIR_STORAGE_FILE_QED] = { /* http://wiki.qemu.org/Features/QED */ 0, "QED", NULL, @@ -209,6 +227,19 @@ static struct FileTypeInfo const fileTypeInfo[] = { verify(ARRAY_CARDINALITY(fileTypeInfo) == VIR_STORAGE_FILE_LAST); +enum Qcow3CompatibleFeatures { + QCOW3_COMPATIBLE_LAZY_REFCOUNTS, + + QCOW3_COMPATIBLE_LAST, +}; + +/* Translation from qcow3 feature bits to enum VirStorageFileFeaturesQcow3 */ +int Qcow3CompatibleFeaturesArray [] = { + VIR_STORAGE_FILE_FEAT_QCOW3_LAZY_REFCOUNTS, /* QCOW3_COMPATIBLE_LAZY_REFCOUNTS */ +}; +verify(ARRAY_CARDINALITY(Qcow3CompatibleFeaturesArray) == QCOW3_COMPATIBLE_LAST); + + static uint64_t getBEHeader64(const unsigned char *buf) { @@ -334,14 +365,37 @@ done: static int +qcow3GetFeatures(virBitmapPtr features, + const unsigned char *buf, + size_t buf_size) +{ + uint64_t bits; + int i; + + if (buf_size < QCOW3_HDR_SIZE) + return -1; + + bits = getBEHeader64(buf + QCOW3_HDR_FEATURES_COMPATIBLE); + for (i = 0; i < QCOW3_COMPATIBLE_LAST; i++) { + if (Qcow3CompatibleFeaturesArray[i] >= 0) + if (bits & ((uint64_t) 1 << i)) + if (virBitmapSetBit(features, + Qcow3CompatibleFeaturesArray[i]) < 0) + return -1; + } + return 0; +} + +static int qcowXGetBackingStore(char **res, int *format, const unsigned char *buf, size_t buf_size, - bool isQCow2) + unsigned int version) { unsigned long long offset; unsigned int size; + unsigned long hdr_size; *res = NULL; if (format) @@ -394,11 +448,17 @@ qcowXGetBackingStore(char **res, * Thus the file region to search for extensions is * between the end of the header (QCOW2_HDR_TOTAL_SIZE) * and the start of the backingStoreName (offset) + * + * QCow3 files can have a variable header length, it's stored at + * QCOW3_HDR_SIZE. */ - if (isQCow2 && format && - qcow2GetBackingStoreFormat(format, buf, buf_size, QCOW2_HDR_TOTAL_SIZE, - offset) < 0) - return BACKING_STORE_INVALID; + if (format && (version == 2 || version == 3)) { + hdr_size = (version == 2) ? QCOW2_HDR_TOTAL_SIZE + : getBEHeader32(buf + QCOW3_HDR_SIZE); + if (qcow2GetBackingStoreFormat(format, buf, buf_size, hdr_size, + offset) < 0) + return BACKING_STORE_INVALID; + } return BACKING_STORE_OK; } @@ -415,7 +475,7 @@ qcow1GetBackingStore(char **res, /* QCow1 doesn't have the extensions capability * used to store backing format */ *format = VIR_STORAGE_FILE_AUTO; - ret = qcowXGetBackingStore(res, NULL, buf, buf_size, false); + ret = qcowXGetBackingStore(res, NULL, buf, buf_size, 1); if (ret == 0 && *buf == '\0') *format = VIR_STORAGE_FILE_NONE; return ret; @@ -427,7 +487,16 @@ qcow2GetBackingStore(char **res, const unsigned char *buf, size_t buf_size) { - return qcowXGetBackingStore(res, format, buf, buf_size, true); + return qcowXGetBackingStore(res, format, buf, buf_size, 2); +} + +static int +qcow3GetBackingStore(char **res, + int *format, + const unsigned char *buf, + size_t buf_size) +{ + return qcowXGetBackingStore(res, format, buf, buf_size, 3); } @@ -748,7 +817,22 @@ virStorageFileGetMetadataFromBuf(int format, } } + if (format == VIR_STORAGE_FILE_QCOW3) { + if (!(meta->features = virBitmapNew(VIR_STORAGE_FILE_FEAT_QCOW3_LAST))) + goto no_memory; + + if (qcow3GetFeatures(meta->features, buf, buflen) < 0) + goto error; + } + return 0; + +no_memory: + virReportOOMError(); +error: + VIR_FREE(meta->backingStore); + virBitmapFree(meta->features); + return -1; } @@ -1063,6 +1147,7 @@ virStorageFileFreeMetadata(virStorageFileMetadata *meta) return; virStorageFileFreeMetadata(meta->backingMeta); + virBitmapFree(meta->features); VIR_FREE(meta->backingStore); VIR_FREE(meta->backingStoreRaw); VIR_FREE(meta); diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h index 821d07e..88b3b6f 100644 --- a/src/util/virstoragefile.h +++ b/src/util/virstoragefile.h @@ -24,6 +24,7 @@ #ifndef __VIR_STORAGE_FILE_H__ # define __VIR_STORAGE_FILE_H__ +# include "virbitmap.h" # include "virutil.h" enum virStorageFileFormat { @@ -39,6 +40,7 @@ enum virStorageFileFormat { VIR_STORAGE_FILE_ISO, VIR_STORAGE_FILE_QCOW, VIR_STORAGE_FILE_QCOW2, + VIR_STORAGE_FILE_QCOW3, VIR_STORAGE_FILE_QED, VIR_STORAGE_FILE_VMDK, VIR_STORAGE_FILE_VPC, @@ -51,6 +53,14 @@ enum virStorageFileFormat { VIR_ENUM_DECL(virStorageFileFormat); +enum virStorageFileFeaturesQcow3 { + VIR_STORAGE_FILE_FEAT_QCOW3_NONE = -1, + VIR_STORAGE_FILE_FEAT_QCOW3_LAZY_REFCOUNTS = 0, + + VIR_STORAGE_FILE_FEAT_QCOW3_LAST, +}; +VIR_ENUM_DECL(virStorageFileFeaturesQcow3); + typedef struct _virStorageFileMetadata virStorageFileMetadata; typedef virStorageFileMetadata *virStorageFileMetadataPtr; struct _virStorageFileMetadata { @@ -61,6 +71,7 @@ struct _virStorageFileMetadata { virStorageFileMetadataPtr backingMeta; unsigned long long capacity; bool encrypted; + virBitmapPtr features; }; # ifndef DEV_BSIZE -- 1.7.12.4

Translate the 'qcow3' format to 'qcow2' driver when dealing with qemu. This allows running domains with qcow3 images and creating internal snapshots. --- docs/schemas/domaincommon.rng | 1 + src/libvirt_private.syms | 1 + src/qemu/qemu_command.c | 2 +- src/qemu/qemu_driver.c | 3 ++- src/qemu/qemu_hotplug.c | 4 ++-- src/util/virstoragefile.c | 8 ++++++++ src/util/virstoragefile.h | 1 + 7 files changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng index 049f232..39c4cac 100644 --- a/docs/schemas/domaincommon.rng +++ b/docs/schemas/domaincommon.rng @@ -1263,6 +1263,7 @@ <value>iso</value> <value>qcow</value> <value>qcow2</value> + <value>qcow3</value> <value>qed</value> <value>vmdk</value> <value>vpc</value> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index c589236..4504ccd 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1168,6 +1168,7 @@ virStorageGenerateQcowPassphrase; # storage_file.h virStorageFileChainLookup; +virStorageFileFormatToStringQemu; virStorageFileFormatTypeFromString; virStorageFileFormatTypeToString; virStorageFileFreeMetadata; diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index f6273c1..fc76a35 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -2440,7 +2440,7 @@ qemuBuildDriveStr(virConnectPtr conn ATTRIBUTE_UNUSED, disk->type != VIR_DOMAIN_DISK_TYPE_DIR && qemuCapsGet(caps, QEMU_CAPS_DRIVE_FORMAT)) virBufferAsprintf(&opt, ",format=%s", - virStorageFileFormatTypeToString(disk->format)); + virStorageFileFormatToStringQemu(disk->format)); /* generate geometry command string */ if (disk->geometry.cylinders > 0 && diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 812bf95..79f78fa 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -10735,7 +10735,8 @@ qemuDomainSnapshotPrepare(virDomainObjPtr vm, virDomainSnapshotDefPtr def, break; } if (vm->def->disks[i]->format > 0 && - vm->def->disks[i]->format != VIR_STORAGE_FILE_QCOW2) { + vm->def->disks[i]->format != VIR_STORAGE_FILE_QCOW2 && + vm->def->disks[i]->format != VIR_STORAGE_FILE_QCOW3) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("internal snapshot for disk %s unsupported " "for storage type %s"), diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 18c4109..35fad91 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -134,9 +134,9 @@ int qemuDomainChangeEjectableMedia(virQEMUDriverPtr driver, if (disk->type != VIR_DOMAIN_DISK_TYPE_DIR) { if (disk->format > 0) - format = virStorageFileFormatTypeToString(disk->format); + format = virStorageFileFormatToStringQemu(disk->format); else if (origdisk->format > 0) - format = virStorageFileFormatTypeToString(origdisk->format); + format = virStorageFileFormatToStringQemu(origdisk->format); } ret = qemuMonitorChangeMedia(priv->mon, driveAlias, diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index c74a8fd..1d6a13e 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -1474,3 +1474,11 @@ error: *meta = NULL; return NULL; } + +const char *virStorageFileFormatToStringQemu(enum virStorageFileFormat format) +{ + if (format == VIR_STORAGE_FILE_QCOW3) + return virStorageFileFormatTypeToString(VIR_STORAGE_FILE_QCOW2); + else + return virStorageFileFormatTypeToString(format); +} diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h index 88b3b6f..f765e71 100644 --- a/src/util/virstoragefile.h +++ b/src/util/virstoragefile.h @@ -117,5 +117,6 @@ int virStorageFileGetLVMKey(const char *path, char **key); int virStorageFileGetSCSIKey(const char *path, char **key); +const char *virStorageFileFormatToStringQemu(enum virStorageFileFormat format); #endif /* __VIR_STORAGE_FILE_H__ */ -- 1.7.12.4

This adds the <features> element to volume XML and allows virStorageBackendCreateQemuImg to create qcow3 volumes. For qcow3 images, it runs qemu-img with -f qcow2 -o compat=1.1,... For qcow2 images, it specifies -o compat=0.10 if it's supported, just in case the default of qemu-img changed to qcow3 in the future. --- docs/formatstorage.html.in | 14 ++++- docs/schemas/Makefile.am | 1 + docs/schemas/storagefeatures.rng | 17 ++++++ docs/schemas/storagevol.rng | 5 +- libvirt.spec.in | 1 + mingw-libvirt.spec.in | 2 + src/conf/storage_conf.c | 69 ++++++++++++++++++++++++ src/conf/storage_conf.h | 4 ++ src/libvirt_private.syms | 3 ++ src/storage/storage_backend.c | 114 ++++++++++++++++++++++++++++++++++++--- src/storage/storage_backend_fs.c | 5 ++ src/util/virstoragefile.c | 27 ++++++++++ src/util/virstoragefile.h | 1 + 13 files changed, 254 insertions(+), 9 deletions(-) create mode 100644 docs/schemas/storagefeatures.rng diff --git a/docs/formatstorage.html.in b/docs/formatstorage.html.in index 9f93db8..37bf8ed 100644 --- a/docs/formatstorage.html.in +++ b/docs/formatstorage.html.in @@ -298,13 +298,16 @@ ... <target> <path>/var/lib/virt/images/sparse.img</path> - <format type='qcow2'/> + <format type='qcow3'/> <permissions> <owner>107</owner> <group>107</group> <mode>0744</mode> <label>virt_image_t</label> </permissions> + <features> + <lazy_refcounts/> + </features> </target></pre> <dl> @@ -333,6 +336,15 @@ contains the MAC (eg SELinux) label string. <span class="since">Since 0.4.1</span> </dd> + <dt><code>features</code></dt> + <dd>Provides a format-specific list of features enabled in the volume. + So far, this is only supported for <code>qcow3</code>. Valid values + are: + <ul> + <li><code><lazy_refcounts></code> for delayed refcount updates + (This makes snapshot creation faster).<span class="since">Since + 1.0.3</span></li> + </dd> </dl> <h3><a name="StorageVolBacking">Backing store elements</a></h3> diff --git a/docs/schemas/Makefile.am b/docs/schemas/Makefile.am index 4413d9e..6e54048 100644 --- a/docs/schemas/Makefile.am +++ b/docs/schemas/Makefile.am @@ -15,6 +15,7 @@ schema_DATA = \ nwfilter.rng \ secret.rng \ storageencryption.rng \ + storagefeatures.rng \ storagepool.rng \ storagevol.rng diff --git a/docs/schemas/storagefeatures.rng b/docs/schemas/storagefeatures.rng new file mode 100644 index 0000000..d8ee857 --- /dev/null +++ b/docs/schemas/storagefeatures.rng @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- A Relax NG schema for the libvirt volume features XML format --> +<grammar xmlns="http://relaxng.org/ns/structure/1.0" + datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> + + <define name='diskFormatFeatures'> + <element name='features'> + <interleave> + <optional> + <element name="lazy_refcounts"> + <empty/> + </element> + </optional> + </interleave> + </element> + </define> +</grammar> diff --git a/docs/schemas/storagevol.rng b/docs/schemas/storagevol.rng index 653491d..b426eca 100644 --- a/docs/schemas/storagevol.rng +++ b/docs/schemas/storagevol.rng @@ -8,7 +8,7 @@ </start> <include href='storageencryption.rng'/> - + <include href='storagefeatures.rng'/> <define name='vol'> <element name='volume'> @@ -111,6 +111,9 @@ <optional> <ref name='encryption'/> </optional> + <optional> + <ref name='diskFormatFeatures'/> + </optional> </element> </define> diff --git a/libvirt.spec.in b/libvirt.spec.in index c2da6ec..93591d1 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -1937,6 +1937,7 @@ fi %{_datadir}/libvirt/schemas/nwfilter.rng %{_datadir}/libvirt/schemas/secret.rng %{_datadir}/libvirt/schemas/storageencryption.rng +%{_datadir}/libvirt/schemas/storagefeatures.rng %{_datadir}/libvirt/schemas/storagepool.rng %{_datadir}/libvirt/schemas/storagevol.rng diff --git a/mingw-libvirt.spec.in b/mingw-libvirt.spec.in index b27b0ee..9c7ba1e 100644 --- a/mingw-libvirt.spec.in +++ b/mingw-libvirt.spec.in @@ -213,6 +213,7 @@ rm -rf $RPM_BUILD_ROOT%{mingw64_libexecdir}/libvirt-guests.sh %{mingw32_datadir}/libvirt/schemas/nwfilter.rng %{mingw32_datadir}/libvirt/schemas/secret.rng %{mingw32_datadir}/libvirt/schemas/storageencryption.rng +%{mingw32_datadir}/libvirt/schemas/storagefeatures.rng %{mingw32_datadir}/libvirt/schemas/storagepool.rng %{mingw32_datadir}/libvirt/schemas/storagevol.rng %dir %{mingw32_datadir}/libvirt/api/ @@ -272,6 +273,7 @@ rm -rf $RPM_BUILD_ROOT%{mingw64_libexecdir}/libvirt-guests.sh %{mingw64_datadir}/libvirt/schemas/nwfilter.rng %{mingw64_datadir}/libvirt/schemas/secret.rng %{mingw64_datadir}/libvirt/schemas/storageencryption.rng +%{mingw64_datadir}/libvirt/schemas/storagefeatures.rng %{mingw64_datadir}/libvirt/schemas/storagepool.rng %{mingw64_datadir}/libvirt/schemas/storagevol.rng %dir %{mingw64_datadir}/libvirt/api/ diff --git a/src/conf/storage_conf.c b/src/conf/storage_conf.c index 7a39998..6fa536a 100644 --- a/src/conf/storage_conf.c +++ b/src/conf/storage_conf.c @@ -293,6 +293,7 @@ virStorageVolDefFree(virStorageVolDefPtr def) { } VIR_FREE(def->source.extents); + virBitmapFree(def->target.features); VIR_FREE(def->target.path); VIR_FREE(def->target.perms.label); VIR_FREE(def->target.timestamps); @@ -1105,6 +1106,36 @@ virStorageSize(const char *unit, return 0; } +static int +virStorageVolDefParseTargetFeatures(virStorageVolTargetPtr target, + xmlNodePtr start_node) +{ + int value; + xmlNodePtr cur; + + target->features = virBitmapNew(VIR_STORAGE_FILE_FEAT_QCOW3_LAST); + + if (!target->features) + goto no_memory; + + for (cur = start_node->children; cur; cur = cur->next) { + if (cur->type != XML_ELEMENT_NODE) + continue; + + value = virStorageFileFeaturesQcow3TypeFromString((const char*) + cur->name); + if (value >= 0 && virBitmapSetBit(target->features, value) < 0) + goto error; + } + + return 0; +no_memory: + virReportOOMError(); +error: + virBitmapFree(target->features); + return -1; +} + static virStorageVolDefPtr virStorageVolDefParseXML(virStoragePoolDefPtr pool, xmlXPathContextPtr ctxt) { @@ -1211,6 +1242,13 @@ virStorageVolDefParseXML(virStoragePoolDefPtr pool, DEFAULT_VOL_PERM_MODE) < 0) goto cleanup; + if (pool->type == VIR_STORAGE_POOL_DIR && + ret->target.format == VIR_STORAGE_FILE_QCOW3 && + (node = virXPathNode("./target/features", ctxt))) { + if (virStorageVolDefParseTargetFeatures(&(ret->target), node) < 0) + goto cleanup; + } + return ret; cleanup: @@ -1290,10 +1328,35 @@ virStorageVolTimestampFormat(virBufferPtr buf, const char *name, } static int +virStorageVolDefFeaturesFormat(virBufferPtr buf, + int format, + virBitmapPtr features) +{ + int i; + bool tmp; + if (format != VIR_STORAGE_FILE_QCOW3) + return 0; + + virBufferAddLit(buf, "<features>\n"); + if (features) { + for (i = 0; i < VIR_STORAGE_FILE_FEAT_QCOW3_LAST; i++) { + if (virBitmapGetBit(features, i, &tmp) == 0 && tmp) { + virBufferEscapeString(buf, " <%s/>\n", + virStorageFileFeaturesQcow3TypeToString(i)); + } + } + } + virBufferAddLit(buf, "</features>\n"); + + return 0; +} + +static int virStorageVolTargetDefFormat(virStorageVolOptionsPtr options, virBufferPtr buf, virStorageVolTargetPtr def, const char *type) { + virBufferAsprintf(buf, " <%s>\n", type); if (def->path) @@ -1341,6 +1404,12 @@ virStorageVolTargetDefFormat(virStorageVolOptionsPtr options, virBufferAdjustIndent(buf, -4); } + if (def->format == VIR_STORAGE_FILE_QCOW3) { + virBufferAdjustIndent(buf, 4); + virStorageVolDefFeaturesFormat(buf, def->format, def->features); + virBufferAdjustIndent(buf, -4); + } + virBufferAsprintf(buf, " </%s>\n", type); return 0; diff --git a/src/conf/storage_conf.h b/src/conf/storage_conf.h index ad16eca..e7f0744 100644 --- a/src/conf/storage_conf.h +++ b/src/conf/storage_conf.h @@ -28,6 +28,7 @@ # include "virutil.h" # include "storage_encryption_conf.h" # include "virthread.h" +# include "virstoragefile.h" # include <libxml/tree.h> @@ -93,6 +94,9 @@ struct _virStorageVolTarget { int type; /* only used by disk backend for partition type */ /* Currently used only in virStorageVolDef.target, not in .backingstore. */ virStorageEncryptionPtr encryption; + /* Format-specific features. Currently only used for qcow3. + * Only in virStorageVolDef.target, not in .backingstore. */ + virBitmapPtr features; }; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 4504ccd..60d224c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1168,6 +1168,8 @@ virStorageGenerateQcowPassphrase; # storage_file.h virStorageFileChainLookup; +virStorageFileFeaturesQcow3TypeFromString; +virStorageFileFeaturesQcow3TypeToString; virStorageFileFormatToStringQemu; virStorageFileFormatTypeFromString; virStorageFileFormatTypeToString; @@ -1181,6 +1183,7 @@ virStorageFileIsSharedFS; virStorageFileIsSharedFSType; virStorageFileProbeFormat; virStorageFileProbeFormatFromFD; +virStorageFileQemuImgQcow3Options; virStorageFileResize; # sysinfo.h diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c index f9e604a..e4c586d 100644 --- a/src/storage/storage_backend.c +++ b/src/storage/storage_backend.c @@ -654,6 +654,75 @@ cleanup: return ret; } +static int virStorageBackendQEMUImgOpts(const char *qemuimg, virBitmapPtr opts) +{ + char *buf = NULL; + char *p, *q = NULL, *r; + int ret = -1; + int val; + virCommandPtr cmd = virCommandNewArgList(qemuimg, "create", "-o", "?", + "-f", "qcow2", "/dev/null", NULL); + + virCommandAddEnvString(cmd, "LC_ALL=C"); + virCommandSetOutputBuffer(cmd, &buf); + + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + + q = strchr(buf, '\n'); + while (q) { + p = q + 1; + q = strchr(p, '\n'); + if (STRPREFIX(p, "compat ")) { + ret = 1; + if (opts) + continue; + else + goto cleanup; + } + r = strchr(p, ' '); + if (!r) + goto cleanup; + *r = '\0'; + val = virStorageFileFeaturesQcow3TypeFromString(p); + if (val >= 0) + ignore_value(virBitmapSetBit(opts, val)); + } + + if (ret < 0) + ret = 0; + +cleanup: + virCommandFree(cmd); + VIR_FREE(buf); + return ret; +} + +static int virStorageBackendQemuCheckFeatures(virBitmapPtr opts, virBitmapPtr feats) +{ + int i; + bool val; + int ret = -1; + + for (i = 0; i < VIR_STORAGE_FILE_FEAT_QCOW3_LAST; i++) { + if (virBitmapGetBit(feats, i, &val) < 0) + goto cleanup; + if (val) { + if (virBitmapGetBit(opts, i, &val) < 0) + goto cleanup; + if (!val) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("feature %s not supported by qemu-img"), + virStorageFileFeaturesQcow3TypeToString(i)); + goto cleanup; + } + } + } + ret = 0; +cleanup: + return ret; +} static int virStorageBackendCreateQemuImg(virConnectPtr conn, @@ -665,27 +734,29 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, int ret = -1; char *create_tool; int imgformat = -1; + int compat = 0; virCommandPtr cmd = NULL; bool do_encryption = (vol->target.encryption != NULL); unsigned long long int size_arg; bool preallocate = false; - char *options = NULL; + const char *options = NULL; virBuffer buf = VIR_BUFFER_INITIALIZER; + virBitmapPtr opts = NULL; virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1); preallocate = !!(flags & VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA); - const char *type = virStorageFileFormatTypeToString(vol->target.format); + const char *type = virStorageFileFormatToStringQemu(vol->target.format); const char *backingType = vol->backingStore.path ? - virStorageFileFormatTypeToString(vol->backingStore.format) : NULL; + virStorageFileFormatToStringQemu(vol->backingStore.format) : NULL; const char *inputBackingPath = (inputvol ? inputvol->backingStore.path : NULL); const char *inputPath = inputvol ? inputvol->target.path : NULL; /* Treat input block devices as 'raw' format */ const char *inputType = inputPath ? - virStorageFileFormatTypeToString(inputvol->type == VIR_STORAGE_VOL_BLOCK ? + virStorageFileFormatToStringQemu(inputvol->type == VIR_STORAGE_VOL_BLOCK ? VIR_STORAGE_FILE_RAW : inputvol->target.format) : NULL; @@ -702,9 +773,11 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, inputvol->target.format); return -1; } - if (preallocate && vol->target.format != VIR_STORAGE_FILE_QCOW2) { + if (preallocate && vol->target.format != VIR_STORAGE_FILE_QCOW2 + && vol->target.format != VIR_STORAGE_FILE_QCOW3) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("metadata preallocation only available with qcow2")); + _("metadata preallocation only available with qcow2" + " or qcow3")); return -1; } @@ -763,7 +836,8 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, virStorageEncryptionPtr enc; if (vol->target.format != VIR_STORAGE_FILE_QCOW && - vol->target.format != VIR_STORAGE_FILE_QCOW2) { + vol->target.format != VIR_STORAGE_FILE_QCOW2 && + vol->target.format != VIR_STORAGE_FILE_QCOW3) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("qcow volume encryption unsupported with " "volume format %s"), type); @@ -807,6 +881,18 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, if (imgformat < 0) goto cleanup; + if (vol->target.format == VIR_STORAGE_FILE_QCOW3 && + (opts = virBitmapNew(VIR_STORAGE_FILE_FEAT_QCOW3_LAST)) == NULL) { + virReportOOMError(); + goto cleanup; + } + + if (imgformat == QEMU_IMG_BACKING_FORMAT_OPTIONS) { + compat = virStorageBackendQEMUImgOpts(create_tool, opts); + if (compat < 0) + goto cleanup; + } + cmd = virCommandNew(create_tool); if (inputvol) { @@ -831,6 +917,18 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, else if (preallocate) virBufferAddLit(&buf, ",preallocation=metadata"); + if (vol->target.format == VIR_STORAGE_FILE_QCOW3) { + if (virStorageBackendQemuCheckFeatures(opts, vol->target.features) < 0) + goto cleanup; + options = virStorageFileQemuImgQcow3Options(vol->target.features); + if (!options) + goto cleanup; + virBufferAdd(&buf, options, strlen(options)); + VIR_FREE(options); + } else if (compat && vol->target.format == VIR_STORAGE_FILE_QCOW2) { + virBufferAddLit(&buf, ",compat=0.10"); + } + if (virBufferError(&buf) > 0) { virReportOOMError(); goto cleanup; @@ -854,6 +952,8 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, ret = virStorageBackendCreateExecCommand(pool, vol, cmd); cleanup: + virBitmapFree(opts); + virBufferFreeAndReset(&buf); VIR_FREE(create_tool); virCommandFree(cmd); diff --git a/src/storage/storage_backend_fs.c b/src/storage/storage_backend_fs.c index f356173..538f540 100644 --- a/src/storage/storage_backend_fs.c +++ b/src/storage/storage_backend_fs.c @@ -150,6 +150,11 @@ virStorageBackendProbeTarget(virStorageVolTargetPtr target, */ } + if (target->format == VIR_STORAGE_FILE_QCOW3) { + target->features = meta->features; + meta->features = NULL; + } + virStorageFileFreeMetadata(meta); return ret; diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index 1d6a13e..7e49ba3 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -1482,3 +1482,30 @@ const char *virStorageFileFormatToStringQemu(enum virStorageFileFormat format) else return virStorageFileFormatTypeToString(format); } + +const char *virStorageFileQemuImgQcow3Options(virBitmapPtr features) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + bool value; + + virBufferAddLit(&buf, ",compat=1.1"); + + if (features) { + if (virBitmapGetBit(features, + VIR_STORAGE_FILE_FEAT_QCOW3_LAZY_REFCOUNTS, + &value) < 0) + goto no_memory; + if (value) + virBufferAddLit(&buf, ",lazy_refcounts=on"); + } + + if (virBufferError(&buf) > 0) + goto no_memory; + + return virBufferContentAndReset(&buf); + +no_memory: + virBufferFreeAndReset(&buf); + virReportOOMError(); + return NULL; +} diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h index f765e71..1a8cb53 100644 --- a/src/util/virstoragefile.h +++ b/src/util/virstoragefile.h @@ -118,5 +118,6 @@ int virStorageFileGetLVMKey(const char *path, int virStorageFileGetSCSIKey(const char *path, char **key); const char *virStorageFileFormatToStringQemu(enum virStorageFileFormat format); +const char *virStorageFileQemuImgQcow3Options(virBitmapPtr features); #endif /* __VIR_STORAGE_FILE_H__ */ -- 1.7.12.4

On Tue, Feb 05, 2013 at 12:56:16PM +0100, Ján Tomko wrote:
diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c index f9e604a..e4c586d 100644 --- a/src/storage/storage_backend.c +++ b/src/storage/storage_backend.c @@ -654,6 +654,75 @@ cleanup: return ret; }
+static int virStorageBackendQEMUImgOpts(const char *qemuimg, virBitmapPtr opts) +{ + char *buf = NULL; + char *p, *q = NULL, *r; + int ret = -1; + int val; + virCommandPtr cmd = virCommandNewArgList(qemuimg, "create", "-o", "?", + "-f", "qcow2", "/dev/null", NULL); +
I can understand why you're doing this, but gives me a really bad feeling. After 5 years we've finally stopped parsing -help from the QEMU binary...only to start doing the same for qemu-img.
+ virCommandAddEnvString(cmd, "LC_ALL=C"); + virCommandSetOutputBuffer(cmd, &buf); + + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + + q = strchr(buf, '\n'); + while (q) { + p = q + 1; + q = strchr(p, '\n'); + if (STRPREFIX(p, "compat ")) { + ret = 1; + if (opts) + continue; + else + goto cleanup; + } + r = strchr(p, ' '); + if (!r) + goto cleanup; + *r = '\0'; + val = virStorageFileFeaturesQcow3TypeFromString(p); + if (val >= 0) + ignore_value(virBitmapSetBit(opts, val)); + } + + if (ret < 0) + ret = 0; + +cleanup: + virCommandFree(cmd); + VIR_FREE(buf); + return ret; +}
IMHO we should just invoke qemu-img with the feature list we have, and rely on it to report any unsupported features itself, rather than trying to pre-detect them ourselves. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 02/05/13 18:14, Daniel P. Berrange wrote:
I can understand why you're doing this, but gives me a really bad feeling. After 5 years we've finally stopped parsing -help from the QEMU binary...only to start doing the same for qemu-img.
...
IMHO we should just invoke qemu-img with the feature list we have, and rely on it to report any unsupported features itself, rather than trying to pre-detect them ourselves.
Daniel
I only did it because of the 'compat' feature, which might be needed to create qcow2 images in the future, if the default of qemu-img gets changed to qcow3. Jan

On Wed, Feb 06, 2013 at 12:36:37PM +0100, Ján Tomko wrote:
On 02/05/13 18:14, Daniel P. Berrange wrote:
I can understand why you're doing this, but gives me a really bad feeling. After 5 years we've finally stopped parsing -help from the QEMU binary...only to start doing the same for qemu-img.
...
IMHO we should just invoke qemu-img with the feature list we have, and rely on it to report any unsupported features itself, rather than trying to pre-detect them ourselves.
Daniel
I only did it because of the 'compat' feature, which might be needed to create qcow2 images in the future, if the default of qemu-img gets changed to qcow3.
Well IMHO that would be a mistake on QMEU's part. IMHO they ought to just allow the user to specify an explicit '-f qcow3' to get all the latests qcow3 format features enabled and leave '-f qcow2' on its current behaviour forever. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

--- docs/formatsnapshot.html.in | 7 +++++ docs/schemas/domainsnapshot.rng | 4 +++ src/conf/snapshot_conf.c | 63 +++++++++++++++++++++++++++++++++++++++++ src/conf/snapshot_conf.h | 2 ++ src/qemu/qemu_driver.c | 21 +++++++++++--- 5 files changed, 93 insertions(+), 4 deletions(-) diff --git a/docs/formatsnapshot.html.in b/docs/formatsnapshot.html.in index 8fcc04c..842ebab 100644 --- a/docs/formatsnapshot.html.in +++ b/docs/formatsnapshot.html.in @@ -167,6 +167,13 @@ snapshots, the original file name becomes the read-only snapshot, and the new file name contains the read-write delta of all disk changes since the snapshot. + + An optional <code>features</code> element may be specified, + containing format-specific features. So far only for qcow3 + driver type. See + <a href="formatstorage.html#StorageVolTarget">storage volume + target</a> for valid values. <span class="since">Since 1.0.3 + </span> </dd> </dl> </dd> diff --git a/docs/schemas/domainsnapshot.rng b/docs/schemas/domainsnapshot.rng index 45d55b5..7ceb582 100644 --- a/docs/schemas/domainsnapshot.rng +++ b/docs/schemas/domainsnapshot.rng @@ -6,6 +6,7 @@ </start> <include href='domaincommon.rng'/> + <include href='storagefeatures.rng'/> <define name='domainsnapshot'> <element name='domainsnapshot'> @@ -144,6 +145,9 @@ <empty/> </element> </optional> + <optional> + <ref name='diskFormatFeatures'/> + </optional> </interleave> </group> </choice> diff --git a/src/conf/snapshot_conf.c b/src/conf/snapshot_conf.c index fe77bd0..7be04f4 100644 --- a/src/conf/snapshot_conf.c +++ b/src/conf/snapshot_conf.c @@ -103,6 +103,35 @@ void virDomainSnapshotDefFree(virDomainSnapshotDefPtr def) } static int +virDomainSnapshotDiskDefParseFeatures(xmlNodePtr node, + virDomainSnapshotDiskDefPtr def) +{ + int value; + xmlNodePtr cur; + + def->features = virBitmapNew(VIR_STORAGE_FILE_FEAT_QCOW3_LAST); + if (!def->features) + goto no_memory; + + for (cur = node->children; cur; cur = cur->next) { + if (cur->type != XML_ELEMENT_NODE) + continue; + value = virStorageFileFeaturesQcow3TypeFromString((const char*) + cur->name); + if (value >= 0 && virBitmapSetBit(def->features, value) < 0) + goto error; + } + + return 0; + +no_memory: + virReportOOMError(); +error: + virBitmapFree(def->features); + return -1; +} + +static int virDomainSnapshotDiskDefParseXML(xmlNodePtr node, virDomainSnapshotDiskDefPtr def) { @@ -146,6 +175,10 @@ virDomainSnapshotDiskDefParseXML(xmlNodePtr node, goto cleanup; } VIR_FREE(driver); + } else if (!def->features && + xmlStrEqual(cur->name, BAD_CAST "features")) { + if (virDomainSnapshotDiskDefParseFeatures(cur, def) < 0) + goto cleanup; } } cur = cur->next; @@ -551,6 +584,30 @@ cleanup: return ret; } +static int +virDomainSnapshotDefFeaturesFormat(virBufferPtr buf, + int format, + virBitmapPtr features) +{ + int i; + bool tmp; + if (format != VIR_STORAGE_FILE_QCOW3) + return 0; + + virBufferAddLit(buf, "<features>\n"); + if (features) { + for (i = 0; i < VIR_STORAGE_FILE_FEAT_QCOW3_LAST; i++) { + if (virBitmapGetBit(features, i, &tmp) == 0 && tmp) { + virBufferEscapeString(buf, " <%s/>\n", + virStorageFileFeaturesQcow3TypeToString(i)); + } + } + } + virBufferAddLit(buf, "</features>\n"); + + return 0; +} + char *virDomainSnapshotDefFormat(const char *domain_uuid, virDomainSnapshotDefPtr def, unsigned int flags, @@ -602,6 +659,12 @@ char *virDomainSnapshotDefFormat(const char *domain_uuid, virBufferEscapeString(&buf, " <driver type='%s'/>\n", virStorageFileFormatTypeToString( disk->format)); + if (disk->format == VIR_STORAGE_FILE_QCOW3) { + virBufferAdjustIndent(&buf, 6); + virDomainSnapshotDefFeaturesFormat(&buf, disk->format, + disk->features); + virBufferAdjustIndent(&buf, -6); + } if (disk->file) virBufferEscapeString(&buf, " <source file='%s'/>\n", disk->file); diff --git a/src/conf/snapshot_conf.h b/src/conf/snapshot_conf.h index f1d5995..7a8b08a 100644 --- a/src/conf/snapshot_conf.h +++ b/src/conf/snapshot_conf.h @@ -53,6 +53,8 @@ struct _virDomainSnapshotDiskDef { int snapshot; /* enum virDomainSnapshotLocation */ char *file; /* new source file when snapshot is external */ int format; /* enum virStorageFileFormat */ + virBitmapPtr features; /* format-specific features. + currently only used for qcow3 */ }; /* Stores the complete snapshot metadata */ diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 79f78fa..078d876 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -10513,6 +10513,7 @@ qemuDomainSnapshotCreateInactiveExternal(virQEMUDriverPtr driver, virDomainDiskDefPtr defdisk; virCommandPtr cmd = NULL; const char *qemuImgPath; + const char *opts = NULL; virBitmapPtr created; int ret = -1; @@ -10541,16 +10542,25 @@ qemuDomainSnapshotCreateInactiveExternal(virQEMUDriverPtr driver, if (!(cmd = virCommandNewArgList(qemuImgPath, "create", "-f", - virStorageFileFormatTypeToString(snapdisk->format), + virStorageFileFormatToStringQemu(snapdisk->format), "-o", NULL))) goto cleanup; + if (snapdisk->format == VIR_STORAGE_FILE_QCOW3) { + opts = virStorageFileQemuImgQcow3Options(snapdisk->features); + if (!opts) + goto cleanup; + } else if (snapdisk->format == VIR_STORAGE_FILE_QCOW2) { + opts = strdup(",compat=0.10"); + } + if (defdisk->format > 0) { /* adds cmd line arg: backing_file=/path/to/backing/file,backing_fmd=format */ - virCommandAddArgFormat(cmd, "backing_file=%s,backing_fmt=%s", + virCommandAddArgFormat(cmd, "backing_file=%s,backing_fmt=%s%s", defdisk->src, - virStorageFileFormatTypeToString(defdisk->format)); + virStorageFileFormatToStringQemu(defdisk->format), + opts ? opts : ""); } else { if (!driver->allowDiskFormatProbing) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, @@ -10561,7 +10571,8 @@ qemuDomainSnapshotCreateInactiveExternal(virQEMUDriverPtr driver, } /* adds cmd line arg: backing_file=/path/to/backing/file */ - virCommandAddArgFormat(cmd, "backing_file=%s", defdisk->src); + virCommandAddArgFormat(cmd, "backing_file=%s%s", defdisk->src, + opts ? opts : ""); } /* adds cmd line args: /path/to/target/file */ @@ -10597,6 +10608,7 @@ qemuDomainSnapshotCreateInactiveExternal(virQEMUDriverPtr driver, ret = 0; cleanup: + VIR_FREE(opts); virCommandFree(cmd); /* unlink images if creation has failed */ @@ -10759,6 +10771,7 @@ qemuDomainSnapshotPrepare(virDomainObjPtr vm, virDomainSnapshotDefPtr def, if (!disk->format) { disk->format = VIR_STORAGE_FILE_QCOW2; } else if (disk->format != VIR_STORAGE_FILE_QCOW2 && + disk->format != VIR_STORAGE_FILE_QCOW3 && disk->format != VIR_STORAGE_FILE_QED) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("external snapshot format for disk %s " -- 1.7.12.4
participants (2)
-
Daniel P. Berrange
-
Ján Tomko