Skip to contentSkip to conversation

vmdk: Implement blockdev-create

PATCH In review Kevin Wolf 4 participants 19 messages Dec 7, 2018 Block layer

lore
Share & tools
lore

Kevin Wolf

author
#0 lore raw

v4 cover vmdk: Implement blockdev-create

I picked up the patch series from Fam and rebased it to current master (which involved a major rework on the test case) and tried to address Markus' review comments for v2. I did not do any further review of the actual code, but it passes the tests, so I guess having it in tree is better than continuing to let it bitrot.

v4:
- Use "legacyESX" in QAPI instead of "legacyesx" (requires an additional exception from the QAPI rules, but the string is directly written into the VMDK file, and "legacyESX" is the spelling in the spec). The QemuOpts based parser stays case sensitive in v4. [Markus]
- Documentation fixes [Markus]
- Added tests for adapter types
- Added patch 5 to forbid excess extents in blockdev-create [Markus]

Fam Zheng (3):
vmdk: Refactor vmdk_create_extent
vmdk: Implement .bdrv_co_create callback
iotests: Filter cid numbers in VMDK extent info

Kevin Wolf (2):
iotests: Add VMDK tests for blockdev-create
vmdk: Reject excess extents in blockdev-create

 qapi/block-core.json             |  71 +++++
 qapi/qapi-schema.json            |  16 +-
 block/vmdk.c                     | 532 ++++++++++++++++++++++---------
 tests/qemu-iotests/237           | 237 ++++++++++++++
 tests/qemu-iotests/237.out       | 348 ++++++++++++++++++++
 tests/qemu-iotests/common.filter |   1 +
 tests/qemu-iotests/group         |   1 +
 tests/qemu-iotests/iotests.py    |   1 +
 8 files changed, 1049 insertions(+), 158 deletions(-)
 create mode 100755 tests/qemu-iotests/237
 create mode 100644 tests/qemu-iotests/237.out
Signature · 1 line
2.19.2
author
#1 lore raw

PATCH v4 1/5 vmdk: Refactor vmdk_create_extent

From: Fam Zheng <[email protected]>

The extracted vmdk_init_extent takes a BlockBackend object and initializes the format metadata. It is the common part between "qemu-img create" and "blockdev-create".

Add a "BlockBackend *pbb" parameter to vmdk_create_extent, to return the opened BB to the caller in the next patch.

---
 block/vmdk.c | 69 ++++++++++++++++++++++++++++++++--------------------
 1 file changed, 43 insertions(+), 26 deletions(-)
block/vmdk.c +43 -26
index 2c9e86d98f..32fc2c84b3 100644
@@ -1741,35 +1741,17 @@ static int coroutine_fn vmdk_co_pwrite_zeroes(BlockDriverState *bs,
     return ret;
 }
 
-static int vmdk_create_extent(const char *filename, int64_t filesize,
-                              bool flat, bool compress, bool zeroed_grain,
-                              QemuOpts *opts, Error **errp)
+static int vmdk_init_extent(BlockBackend *blk,
+                            int64_t filesize, bool flat,
+                            bool compress, bool zeroed_grain,
+                            Error **errp)
 {
     int ret, i;
-    BlockBackend *blk = NULL;
     VMDK4Header header;
-    Error *local_err = NULL;
     uint32_t tmp, magic, grains, gd_sectors, gt_size, gt_count;
     uint32_t *gd_buf = NULL;
     int gd_buf_size;
 
-    ret = bdrv_create_file(filename, opts, &local_err);
-    if (ret < 0) {
-        error_propagate(errp, local_err);
-        goto exit;
-    }
-
-    blk = blk_new_open(filename, NULL, NULL,
-                       BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL,
-                       &local_err);
-    if (blk == NULL) {
-        error_propagate(errp, local_err);
-        ret = -EIO;
-        goto exit;
-    }
-
-    blk_set_allow_write_beyond_eof(blk, true);
-
     if (flat) {
         ret = blk_truncate(blk, filesize, PREALLOC_MODE_OFF, errp);
         goto exit;
@@ -1863,15 +1845,50 @@ static int vmdk_create_extent(const char *filename, int64_t filesize,
                      gd_buf, gd_buf_size, 0);
     if (ret < 0) {
         error_setg(errp, QERR_IO_ERROR);
-        goto exit;
     }
 
     ret = 0;
+exit:
+    g_free(gd_buf);
+    return ret;
+}
+
+static int vmdk_create_extent(const char *filename, int64_t filesize,
+                              bool flat, bool compress, bool zeroed_grain,
+                              BlockBackend **pbb,
+                              QemuOpts *opts, Error **errp)
+{
+    int ret;
+    BlockBackend *blk = NULL;
+    Error *local_err = NULL;
+
+    ret = bdrv_create_file(filename, opts, &local_err);
+    if (ret < 0) {
+        error_propagate(errp, local_err);
+        goto exit;
+    }
+
+    blk = blk_new_open(filename, NULL, NULL,
+                       BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL,
+                       &local_err);
+    if (blk == NULL) {
+        error_propagate(errp, local_err);
+        ret = -EIO;
+        goto exit;
+    }
+
+    blk_set_allow_write_beyond_eof(blk, true);
+
+    ret = vmdk_init_extent(blk, filesize, flat, compress, zeroed_grain, errp);
 exit:
     if (blk) {
-        blk_unref(blk);
+        if (pbb) {
+            *pbb = blk;
+        } else {
+            blk_unref(blk);
+            blk = NULL;
+        }
     }
-    g_free(gd_buf);
     return ret;
 }
 
@@ -2094,7 +2111,7 @@ static int coroutine_fn vmdk_co_create_opts(const char *filename, QemuOpts *opts
         snprintf(ext_filename, PATH_MAX, "%s%s", path, desc_filename);
 
         if (vmdk_create_extent(ext_filename, size,
-                               flat, compress, zeroed_grain, opts, errp)) {
+                               flat, compress, zeroed_grain, NULL, opts, errp)) {
             ret = -EINVAL;
             goto exit;
         }
Signature · 1 line
2.19.2
author
#2 lore raw

PATCH v4 2/5 vmdk: Implement .bdrv_co_create callback

In reply to Kevin Wolf

From: Fam Zheng <[email protected]>

This makes VMDK support blockdev-create. The implementation reuses the image creation code in vmdk_co_create_opts which now acceptes a callback pointer to "retrieve" BlockBackend pointers from the caller. This way we separate the logic between file/extent acquisition and initialization.

The QAPI command parameters are mostly the same as the old create_opts except the dropped legacy @compat6 switch, which is redundant with @hwversion.

---
 qapi/block-core.json  |  70 +++++++
 qapi/qapi-schema.json |  16 +-
 block/vmdk.c          | 448 ++++++++++++++++++++++++++++++------------
 3 files changed, 400 insertions(+), 134 deletions(-)
qapi/block-core.json +70 -0
index d4fe710836..0793550cf2 100644
@@ -4021,6 +4021,75 @@
             'size':             'size',
             '*cluster-size' :   'size' } }
 
+##
+# @BlockdevVmdkSubformat:
+#
+# Subformat options for VMDK images
+#
+# @monolithicSparse:     Single file image with sparse cluster allocation
+#
+# @monolithicFlat:       Single flat data image and a descriptor file
+#
+# @twoGbMaxExtentSparse: Data is split into 2GB (per virtual LBA) sparse extent
+#                        files, in addition to a descriptor file
+#
+# @twoGbMaxExtentFlat:   Data is split into 2GB (per virtual LBA) flat extent
+#                        files, in addition to a descriptor file
+#
+# @streamOptimized:      Single file image sparse cluster allocation, optimized
+#                        for streaming over network.
+#
+# Since: 4.0
+##
+{ 'enum': 'BlockdevVmdkSubformat',
+  'data': [ 'monolithicSparse', 'monolithicFlat', 'twoGbMaxExtentSparse',
+            'twoGbMaxExtentFlat', 'streamOptimized'] }
+
+##
+# @BlockdevVmdkAdapterType:
+#
+# Adapter type info for VMDK images
+#
+# Since: 4.0
+##
+{ 'enum': 'BlockdevVmdkAdapterType',
+  'data': [ 'ide', 'buslogic', 'lsilogic', 'legacyESX'] }
+
+##
+# @BlockdevCreateOptionsVmdk:
+#
+# Driver specific image creation options for VMDK.
+#
+# @file         Where to store the new image file. This refers to the image
+#               file for monolithcSparse and streamOptimized format, or the
+#               descriptor file for other formats.
+# @size         Size of the virtual disk in bytes
+# @extents      Where to store the data extents. Required for monolithcFlat,
+#               twoGbMaxExtentSparse and twoGbMaxExtentFlat formats. For
+#               monolithicFlat, only one entry is required; for
+#               twoGbMaxExtent* formats, the number of entries required is
+#               calculated as extent_number = virtual_size / 2GB.
+# @subformat    The subformat of the VMDK image. Default: "monolithicSparse".
+# @backing-file The path of backing file. Default: no backing file is used.
+# @adapter-type The adapter type used to fill in the descriptor. Default: ide.
+# @hwversion    Hardware version. The meaningful options are "4" or "6".
+#               Default: "4".
+# @zeroed-grain Whether to enable zeroed-grain feature for sparse subformats.
+#               Default: false.
+#
+# Since: 4.0
+##
+{ 'struct': 'BlockdevCreateOptionsVmdk',
+  'data': { 'file':             'BlockdevRef',
+            'size':             'size',
+            '*extents':          ['BlockdevRef'],
+            '*subformat':       'BlockdevVmdkSubformat',
+            '*backing-file':    'str',
+            '*adapter-type':    'BlockdevVmdkAdapterType',
+            '*hwversion':       'str',
+            '*zeroed-grain':    'bool' } }
+
+
 ##
 # @SheepdogRedundancyType:
 #
@@ -4215,6 +4284,7 @@
       'ssh':            'BlockdevCreateOptionsSsh',
       'vdi':            'BlockdevCreateOptionsVdi',
       'vhdx':           'BlockdevCreateOptionsVhdx',
+      'vmdk':           'BlockdevCreateOptionsVmdk',
       'vpc':            'BlockdevCreateOptionsVpc'
   } }
 
qapi/qapi-schema.json +9 -7
index 65b6dc2f6f..339f9b315b 100644
@@ -63,13 +63,15 @@
         'query-tpm-types',
         'ringbuf-read' ],
     'name-case-whitelist': [
-        'ACPISlotType',         # DIMM, visible through query-acpi-ospm-status
-        'CpuInfoMIPS',          # PC, visible through query-cpu
-        'CpuInfoTricore',       # PC, visible through query-cpu
-        'QapiErrorClass',       # all members, visible through errors
-        'UuidInfo',             # UUID, visible through query-uuid
-        'X86CPURegister32',     # all members, visible indirectly through qom-get
-        'q_obj_CpuInfo-base'    # CPU, visible through query-cpu
+        'ACPISlotType',             # DIMM, visible through query-acpi-ospm-status
+        'CpuInfoMIPS',              # PC, visible through query-cpu
+        'CpuInfoTricore',           # PC, visible through query-cpu
+        'BlockdevVmdkSubformat',    # all members, to match VMDK spec spellings
+        'BlockdevVmdkAdapterType',  # legacyESX, to match VMDK spec spellings
+        'QapiErrorClass',           # all members, visible through errors
+        'UuidInfo',                 # UUID, visible through query-uuid
+        'X86CPURegister32',         # all members, visible indirectly through qom-get
+        'q_obj_CpuInfo-base'        # CPU, visible through query-cpu
     ] } }
 
 # Documentation generated with qapi-gen.py is in source order, with
block/vmdk.c +321 -127

554 lines not on this page · raw message

Signature · 1 line
2.19.2
Reviewed-by on v4 2/5
#3 lore raw

Re: [Qemu-devel] [PATCH v4 2/5] vmdk: Implement .bdrv_co_create callback

Quoted 65 lines · Kevin Wolf

From: Fam Zheng <[email protected]>

This makes VMDK support blockdev-create. The implementation reuses the image creation code in vmdk_co_create_opts which now acceptes a callback pointer to "retrieve" BlockBackend pointers from the caller. This way we separate the logic between file/extent acquisition and initialization.

The QAPI command parameters are mostly the same as the old create_opts except the dropped legacy @compat6 switch, which is redundant with @hwversion.

---
 qapi/block-core.json  |  70 +++++++
 qapi/qapi-schema.json |  16 +-
 block/vmdk.c          | 448 ++++++++++++++++++++++++++++++------------
 3 files changed, 400 insertions(+), 134 deletions(-)
qapi/block-core.json +38 -0
index d4fe710836..0793550cf2 100644
@@ -4021,6 +4021,75 @@
             'size':             'size',
             '*cluster-size' :   'size' } }
 
+##
+# @BlockdevVmdkSubformat:
+#
+# Subformat options for VMDK images
+#
+# @monolithicSparse:     Single file image with sparse cluster allocation
+#
+# @monolithicFlat:       Single flat data image and a descriptor file
+#
+# @twoGbMaxExtentSparse: Data is split into 2GB (per virtual LBA) sparse extent
+#                        files, in addition to a descriptor file
+#
+# @twoGbMaxExtentFlat:   Data is split into 2GB (per virtual LBA) flat extent
+#                        files, in addition to a descriptor file
+#
+# @streamOptimized:      Single file image sparse cluster allocation, optimized
+#                        for streaming over network.
+#
+# Since: 4.0
+##
+{ 'enum': 'BlockdevVmdkSubformat',
+  'data': [ 'monolithicSparse', 'monolithicFlat', 'twoGbMaxExtentSparse',
+            'twoGbMaxExtentFlat', 'streamOptimized'] }
+
+##
+# @BlockdevVmdkAdapterType:
+#
+# Adapter type info for VMDK images
+#
+# Since: 4.0
+##
+{ 'enum': 'BlockdevVmdkAdapterType',
+  'data': [ 'ide', 'buslogic', 'lsilogic', 'legacyESX'] }
+
+##
+# @BlockdevCreateOptionsVmdk:
+#
+# Driver specific image creation options for VMDK.

qapi/block-core.json line 4071

Quoted 10 lines
qapi/block-core.json +10 -0
+#
+# @file         Where to store the new image file. This refers to the image
+#               file for monolithcSparse and streamOptimized format, or the
+#               descriptor file for other formats.
+# @size         Size of the virtual disk in bytes
+# @extents      Where to store the data extents. Required for monolithcFlat,
+#               twoGbMaxExtentSparse and twoGbMaxExtentFlat formats. For
+#               monolithicFlat, only one entry is required; for
+#               twoGbMaxExtent* formats, the number of entries required is
+#               calculated as extent_number = virtual_size / 2GB.

Doesn't quite spell out that the number of extents has to match exactly. I'm not sure I care.

Quoted 32 lines
qapi/block-core.json +22 -0
+# @subformat    The subformat of the VMDK image. Default: "monolithicSparse".
+# @backing-file The path of backing file. Default: no backing file is used.
+# @adapter-type The adapter type used to fill in the descriptor. Default: ide.
+# @hwversion    Hardware version. The meaningful options are "4" or "6".
+#               Default: "4".
+# @zeroed-grain Whether to enable zeroed-grain feature for sparse subformats.
+#               Default: false.
+#
+# Since: 4.0
+##
+{ 'struct': 'BlockdevCreateOptionsVmdk',
+  'data': { 'file':             'BlockdevRef',
+            'size':             'size',
+            '*extents':          ['BlockdevRef'],
+            '*subformat':       'BlockdevVmdkSubformat',
+            '*backing-file':    'str',
+            '*adapter-type':    'BlockdevVmdkAdapterType',
+            '*hwversion':       'str',
+            '*zeroed-grain':    'bool' } }
+
+
 ##
 # @SheepdogRedundancyType:
 #
@@ -4215,6 +4284,7 @@
       'ssh':            'BlockdevCreateOptionsSsh',
       'vdi':            'BlockdevCreateOptionsVdi',
       'vhdx':           'BlockdevCreateOptionsVhdx',
+      'vmdk':           'BlockdevCreateOptionsVmdk',
       'vpc':            'BlockdevCreateOptionsVpc'
   } }
 

[...]

#4 lore raw

Re: [Qemu-devel] [PATCH v4 2/5] vmdk: Implement .bdrv_co_create callback

Quoted 25 lines · Markus Armbruster
Quoted 23 lines · Kevin Wolf

From: Fam Zheng <[email protected]>

This makes VMDK support blockdev-create. The implementation reuses the image creation code in vmdk_co_create_opts which now acceptes a callback pointer to "retrieve" BlockBackend pointers from the caller. This way we separate the logic between file/extent acquisition and initialization.

The QAPI command parameters are mostly the same as the old create_opts except the dropped legacy @compat6 switch, which is redundant with @hwversion.

---
 qapi/block-core.json  |  70 +++++++
 qapi/qapi-schema.json |  16 +-
 block/vmdk.c          | 448 ++++++++++++++++++++++++++++++------------
 3 files changed, 400 insertions(+), 134 deletions(-)
qapi/block-core.json +0 -0
index d4fe710836..0793550cf2 100644

[...]

Quoted 9 lines
Quoted 9 lines
qapi/block-core.json +9 -0
+##
+# @BlockdevCreateOptionsVmdk:
+#
+# Driver specific image creation options for VMDK.
+#
+# @file         Where to store the new image file. This refers to the image
+#               file for monolithcSparse and streamOptimized format, or the
+#               descriptor file for other formats.
+# @size         Size of the virtual disk in bytes
Quoted 8 lines

qapi/block-core.json line 4071

Quoted 5 lines
qapi/block-core.json +5 -0
+# @extents      Where to store the data extents. Required for monolithcFlat,
+#               twoGbMaxExtentSparse and twoGbMaxExtentFlat formats. For
+#               monolithicFlat, only one entry is required; for
+#               twoGbMaxExtent* formats, the number of entries required is
+#               calculated as extent_number = virtual_size / 2GB.

Doesn't quite spell out that the number of extents has to match exactly. I'm not sure I care.

Nevermind, you address that in PATCH 5.

[...]

author
#5 lore raw

PATCH v4 3/5 iotests: Filter cid numbers in VMDK extent info

In reply to Kevin Wolf

From: Fam Zheng <[email protected]>

---
 tests/qemu-iotests/common.filter | 1 +
 tests/qemu-iotests/iotests.py    | 1 +
 2 files changed, 2 insertions(+)
tests/qemu-iotests/common.filter +1 -0
index 2031e353a5..1aa7d57140 100644
@@ -165,6 +165,7 @@ _filter_img_info()
         -e "/table_size: [0-9]\\+/d" \
         -e "/compat: '[^']*'/d" \
         -e "/compat6: \\(on\\|off\\)/d" \
+        -e "s/cid: [0-9]\+/cid: XXXXXXXXXX/" \
         -e "/static: \\(on\\|off\\)/d" \
         -e "/zeroed_grain: \\(on\\|off\\)/d" \
         -e "/subformat: '[^']*'/d" \
tests/qemu-iotests/iotests.py +1 -0
index d537538ba0..4142937239 100644
@@ -248,6 +248,7 @@ def filter_img_info(output, filename):
                    .replace(imgfmt, 'IMGFMT')
         line = re.sub('iters: [0-9]+', 'iters: XXX', line)
         line = re.sub('uuid: [-a-f0-9]+', 'uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', line)
+        line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line)
         lines.append(line)
     return '\n'.join(lines)
 
Signature · 1 line
2.19.2
author
#6 lore raw

PATCH v4 4/5 iotests: Add VMDK tests for blockdev-create

In reply to Kevin Wolf
---
 tests/qemu-iotests/237     | 233 +++++++++++++++++++++++++
 tests/qemu-iotests/237.out | 347 +++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/group   |   1 +
 3 files changed, 581 insertions(+)
 create mode 100755 tests/qemu-iotests/237
 create mode 100644 tests/qemu-iotests/237.out
tests/qemu-iotests/237 new file +233 -0
new file mode 100755
index 0000000000..e04a1ac6be
@@ -0,0 +1,233 @@
+#!/usr/bin/env python
+#
+# Test vmdk and file image creation
+#
+# Copyright (C) 2018 Red Hat, Inc.
+#
+# Creator/Owner: Kevin Wolf <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import iotests
+from iotests import imgfmt
+
+iotests.verify_image_format(supported_fmts=['vmdk'])
+
+def blockdev_create(vm, options):
+    result = vm.qmp_log('blockdev-create', job_id='job0', options=options)
+
+    if 'return' in result:
+        assert result['return'] == {}
+        vm.run_job('job0')
+    iotests.log("")
+
+with iotests.FilePath('t.vmdk') as disk_path, \
+     iotests.FilePath('t.vmdk.1') as extent1_path, \
+     iotests.FilePath('t.vmdk.2') as extent2_path, \
+     iotests.FilePath('t.vmdk.3') as extent3_path, \
+     iotests.VM() as vm:
+
+    #
+    # Successful image creation (defaults)
+    #
+    iotests.log("=== Successful image creation (defaults) ===")
+    iotests.log("")
+
+    size = 5 * 1024 * 1024 * 1024
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': 'file',
+                          'filename': disk_path,
+                          'size': 0 })
+
+    vm.qmp_log('blockdev-add', driver='file', filename=disk_path,
+               node_name='imgfile')
+
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': 'imgfile',
+                          'size': size })
+    vm.shutdown()
+
+    iotests.img_info_log(disk_path)
+
+    #
+    # Successful image creation (inline blockdev-add, explicit defaults)
+    #
+    iotests.log("=== Successful image creation (inline blockdev-add, explicit defaults) ===")
+    iotests.log("")
+
+    # Choose a different size to show that we got a new image
+    size = 64 * 1024 * 1024
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': 'file',
+                          'filename': disk_path,
+                          'size': 0 })
+
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': {
+                              'driver': 'file',
+                              'filename': disk_path,
+                          },
+                          'size': size,
+                          'extents': [],
+                          'subformat': 'monolithicSparse',
+                          'adapter-type': 'ide',
+                          'hwversion': '4',
+                          'zeroed-grain': False })
+    vm.shutdown()
+
+    iotests.img_info_log(disk_path)
+
+    #
+    # Successful image creation (non-default options)
+    #
+    iotests.log("=== Successful image creation (with non-default options) ===")
+    iotests.log("")
+
+    # Choose a different size to show that we got a new image
+    size = 32 * 1024 * 1024
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': 'file',
+                          'filename': disk_path,
+                          'size': 0 })
+
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': {
+                              'driver': 'file',
+                              'filename': disk_path,
+                          },
+                          'size': size,
+                          'extents': [],
+                          'subformat': 'monolithicSparse',
+                          'adapter-type': 'buslogic',
+                          'zeroed-grain': True })
+    vm.shutdown()
+
+    iotests.img_info_log(disk_path)
+
+    #
+    # Invalid BlockdevRef
+    #
+    iotests.log("=== Invalid BlockdevRef ===")
+    iotests.log("")
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': "this doesn't exist",
+                          'size': size })
+    vm.shutdown()
+
+    #
+    # Adapter types
+    #
+
+    iotests.log("=== Adapter types ===")
+    iotests.log("")
+
+    vm.add_blockdev('driver=file,filename=%s,node-name=node0' % (disk_path))
+
+    # Valid
+    iotests.log("== Valid adapter types ==")
+    iotests.log("")
+
+    vm.launch()
+    for adapter_type in [ 'ide', 'buslogic', 'lsilogic', 'legacyESX' ]:
+        blockdev_create(vm, { 'driver': imgfmt,
+                              'file': 'node0',
+                              'size': size,
+                              'adapter-type': adapter_type })
+    vm.shutdown()
+
+    # Invalid
+    iotests.log("== Invalid adapter types ==")
+    iotests.log("")
+
+    vm.launch()
+    for adapter_type in [ 'foo', 'IDE', 'legacyesx', 1 ]:
+        blockdev_create(vm, { 'driver': imgfmt,
+                              'file': 'node0',
+                              'size': size,
+                              'adapter-type': adapter_type })
+    vm.shutdown()
+
+    #
+    # Other subformats
+    #
+    iotests.log("=== Other subformats ===")
+    iotests.log("")
+
+    for path in [ extent1_path, extent2_path, extent3_path ]:
+        msg = iotests.qemu_img_pipe('create', '-f', imgfmt, path, '0')
+        iotests.log(msg, [iotests.filter_testfiles])
+
+    vm.add_blockdev('driver=file,filename=%s,node-name=ext1' % (extent1_path))
+    vm.add_blockdev('driver=file,filename=%s,node-name=ext2' % (extent2_path))
+    vm.add_blockdev('driver=file,filename=%s,node-name=ext3' % (extent3_path))
+
+    # Missing extent
+    iotests.log("== Missing extent ==")
+    iotests.log("")
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': 'node0',
+                          'size': size,
+                          'subformat': 'monolithicFlat' })
+    vm.shutdown()
+
+    # Correct extent
+    iotests.log("== Correct extent ==")
+    iotests.log("")
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': 'node0',
+                          'size': size,
+                          'subformat': 'monolithicFlat',
+                          'extents': ['ext1'] })
+    vm.shutdown()
+
+    # Extra extent
+    iotests.log("== Extra extent ==")
+    iotests.log("")
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': 'node0',
+                          'size': 512,
+                          'subformat': 'monolithicFlat',
+                          'extents': ['ext1', 'ext2', 'ext3'] })
+    vm.shutdown()
+
+    # Split formats
+    iotests.log("== Split formats ==")
+    iotests.log("")
+
+    for size in [ 512, 1073741824, 2147483648, 5368709120 ]:
+        for subfmt in [ 'twoGbMaxExtentFlat', 'twoGbMaxExtentSparse' ]:
+            iotests.log("= %s %d =" % (subfmt, size))
+            iotests.log("")
+
+            vm.launch()
+            blockdev_create(vm, { 'driver': imgfmt,
+                                  'file': 'node0',
+                                  'size': size,
+                                  'subformat': subfmt,
+                                  'extents': ['ext1', 'ext2', 'ext3'] })
+            vm.shutdown()
+
+            iotests.img_info_log(disk_path)
tests/qemu-iotests/237.out new file +347 -0

347 lines not on this page · raw message

tests/qemu-iotests/group +1 -0
index 61a6d98ebd..c4de595f29 100644
@@ -233,3 +233,4 @@
 233 auto quick
 234 auto quick migration
 235 auto quick
+237 rw auto quick
Signature · 1 line
2.19.2
#7 lore raw

Re: [Qemu-devel] [PATCH v4 4/5] iotests: Add VMDK tests for blockdev-create

Quoted 182 lines · Kevin Wolf
---
 tests/qemu-iotests/237     | 233 +++++++++++++++++++++++++
 tests/qemu-iotests/237.out | 347 +++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/group   |   1 +
 3 files changed, 581 insertions(+)
 create mode 100755 tests/qemu-iotests/237
 create mode 100644 tests/qemu-iotests/237.out
tests/qemu-iotests/237 new file +166 -0
new file mode 100755
index 0000000000..e04a1ac6be
@@ -0,0 +1,233 @@
+#!/usr/bin/env python
+#
+# Test vmdk and file image creation
+#
+# Copyright (C) 2018 Red Hat, Inc.
+#
+# Creator/Owner: Kevin Wolf <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import iotests
+from iotests import imgfmt
+
+iotests.verify_image_format(supported_fmts=['vmdk'])
+
+def blockdev_create(vm, options):
+    result = vm.qmp_log('blockdev-create', job_id='job0', options=options)
+
+    if 'return' in result:
+        assert result['return'] == {}
+        vm.run_job('job0')
+    iotests.log("")
+
+with iotests.FilePath('t.vmdk') as disk_path, \
+     iotests.FilePath('t.vmdk.1') as extent1_path, \
+     iotests.FilePath('t.vmdk.2') as extent2_path, \
+     iotests.FilePath('t.vmdk.3') as extent3_path, \
+     iotests.VM() as vm:
+
+    #
+    # Successful image creation (defaults)
+    #
+    iotests.log("=== Successful image creation (defaults) ===")
+    iotests.log("")
+
+    size = 5 * 1024 * 1024 * 1024
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': 'file',
+                          'filename': disk_path,
+                          'size': 0 })
+
+    vm.qmp_log('blockdev-add', driver='file', filename=disk_path,
+               node_name='imgfile')
+
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': 'imgfile',
+                          'size': size })
+    vm.shutdown()
+
+    iotests.img_info_log(disk_path)
+
+    #
+    # Successful image creation (inline blockdev-add, explicit defaults)
+    #
+    iotests.log("=== Successful image creation (inline blockdev-add, explicit defaults) ===")
+    iotests.log("")
+
+    # Choose a different size to show that we got a new image
+    size = 64 * 1024 * 1024
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': 'file',
+                          'filename': disk_path,
+                          'size': 0 })
+
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': {
+                              'driver': 'file',
+                              'filename': disk_path,
+                          },
+                          'size': size,
+                          'extents': [],
+                          'subformat': 'monolithicSparse',
+                          'adapter-type': 'ide',
+                          'hwversion': '4',
+                          'zeroed-grain': False })
+    vm.shutdown()
+
+    iotests.img_info_log(disk_path)
+
+    #
+    # Successful image creation (non-default options)
+    #
+    iotests.log("=== Successful image creation (with non-default options) ===")
+    iotests.log("")
+
+    # Choose a different size to show that we got a new image
+    size = 32 * 1024 * 1024
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': 'file',
+                          'filename': disk_path,
+                          'size': 0 })
+
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': {
+                              'driver': 'file',
+                              'filename': disk_path,
+                          },
+                          'size': size,
+                          'extents': [],
+                          'subformat': 'monolithicSparse',
+                          'adapter-type': 'buslogic',
+                          'zeroed-grain': True })
+    vm.shutdown()
+
+    iotests.img_info_log(disk_path)
+
+    #
+    # Invalid BlockdevRef
+    #
+    iotests.log("=== Invalid BlockdevRef ===")
+    iotests.log("")
+
+    vm.launch()
+    blockdev_create(vm, { 'driver': imgfmt,
+                          'file': "this doesn't exist",
+                          'size': size })
+    vm.shutdown()
+
+    #
+    # Adapter types
+    #
+
+    iotests.log("=== Adapter types ===")
+    iotests.log("")
+
+    vm.add_blockdev('driver=file,filename=%s,node-name=node0' % (disk_path))
+
+    # Valid
+    iotests.log("== Valid adapter types ==")
+    iotests.log("")
+
+    vm.launch()
+    for adapter_type in [ 'ide', 'buslogic', 'lsilogic', 'legacyESX' ]:
+        blockdev_create(vm, { 'driver': imgfmt,
+                              'file': 'node0',
+                              'size': size,
+                              'adapter-type': adapter_type })
+    vm.shutdown()
+
+    # Invalid
+    iotests.log("== Invalid adapter types ==")
+    iotests.log("")
+
+    vm.launch()
+    for adapter_type in [ 'foo', 'IDE', 'legacyesx', 1 ]:
+        blockdev_create(vm, { 'driver': imgfmt,
+                              'file': 'node0',
+                              'size': size,
+                              'adapter-type': adapter_type })
+    vm.shutdown()
+

tests/qemu-iotests/237 line 176

Quoted 10 lines
tests/qemu-iotests/237 new file +10 -0
+    #
+    # Other subformats
+    #
+    iotests.log("=== Other subformats ===")
+    iotests.log("")
+
+    for path in [ extent1_path, extent2_path, extent3_path ]:
+        msg = iotests.qemu_img_pipe('create', '-f', imgfmt, path, '0')
+        iotests.log(msg, [iotests.filter_testfiles])
+

You lost

+ vm.add_blockdev('driver=file,filename=%s,node-name=node0' % (disk_path))

here since v3. Intentional?

Quoted 3 lines
tests/qemu-iotests/237 new file +3 -0
+    vm.add_blockdev('driver=file,filename=%s,node-name=ext1' % (extent1_path))
+    vm.add_blockdev('driver=file,filename=%s,node-name=ext2' % (extent2_path))
+    vm.add_blockdev('driver=file,filename=%s,node-name=ext3' % (extent3_path))

[...]

#8 lore raw

Re: [Qemu-devel] [PATCH v4 4/5] iotests: Add VMDK tests for blockdev-create

In reply to Kevin Wolf

git-am complains

Applying: iotests: Add VMDK tests for blockdev-create .git/rebase-apply/patch:281: trailing whitespace.
format:
.git/rebase-apply/patch:308: trailing whitespace.
format:
.git/rebase-apply/patch:335: trailing whitespace.
format:
.git/rebase-apply/patch:600: new blank line at EOF. +
warning: 4 lines add whitespace errors.

author
#9 lore raw

Re: [Qemu-devel] [PATCH v4 4/5] iotests: Add VMDK tests for blockdev-create

Am 07.12.2018 um 14:12 hat Markus Armbruster geschrieben:

Quoted 12 lines

git-am complains

Applying: iotests: Add VMDK tests for blockdev-create .git/rebase-apply/patch:281: trailing whitespace.
format:
.git/rebase-apply/patch:308: trailing whitespace.
format:
.git/rebase-apply/patch:335: trailing whitespace.
format:
.git/rebase-apply/patch:600: new blank line at EOF. +
warning: 4 lines add whitespace errors.

This is in the reference output, so trailing whitespace/blank lines are actually correct.

Kevin

#10 lore raw

Re: [Qemu-devel] [PATCH v4 4/5] iotests: Add VMDK tests for blockdev-create

Quoted 14 lines · Kevin Wolf

Am 07.12.2018 um 14:12 hat Markus Armbruster geschrieben:

Quoted 12 lines

git-am complains

Applying: iotests: Add VMDK tests for blockdev-create .git/rebase-apply/patch:281: trailing whitespace.
format:
.git/rebase-apply/patch:308: trailing whitespace.
format:
.git/rebase-apply/patch:335: trailing whitespace.
format:
.git/rebase-apply/patch:600: new blank line at EOF. +
warning: 4 lines add whitespace errors.

Quoted 2 lines

This is in the reference output, so trailing whitespace/blank lines are actually correct.

Ah, but doesn't ./check already ignore differences in trailing whitespace present in the actual running that is not present in the *.out files, precisely so we don't have to check in trailing whitespace reference outputs?

Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
author
#11 lore raw

Re: [Qemu-devel] [PATCH v4 4/5] iotests: Add VMDK tests for blockdev-create

Am 07.12.2018 um 16:40 hat Eric Blake geschrieben:

Quoted 15 lines · Kevin Wolf
Quoted 14 lines

Am 07.12.2018 um 14:12 hat Markus Armbruster geschrieben:

Quoted 12 lines

git-am complains

Applying: iotests: Add VMDK tests for blockdev-create .git/rebase-apply/patch:281: trailing whitespace.
format:
.git/rebase-apply/patch:308: trailing whitespace.
format:
.git/rebase-apply/patch:335: trailing whitespace.
format:
.git/rebase-apply/patch:600: new blank line at EOF. +
warning: 4 lines add whitespace errors.

Quoted 7 lines
Quoted 2 lines

This is in the reference output, so trailing whitespace/blank lines are actually correct.

Ah, but doesn't ./check already ignore differences in trailing whitespace present in the actual running that is not present in the *.out files, precisely so we don't have to check in trailing whitespace reference outputs?

It does ignore whitespace changes, so even if we remove that whitespace, the test won't fail. But I don't think that's a good reason to check in inaccurate reference output.

There are a few test cases that have a reference output like this and it's always annoying: When I later add a new subtest, I add the new test code, review the ./check output and if it looks good, I do something like 'cp 237.out.bad 237.out'. At that point, I'll have to manually revert completely unrelated whitespace changes again.

Some 'git am' warnings feel like the lesser evil to me.

Kevin

#12 lore raw

Re: [Qemu-devel] [PATCH v4 4/5] iotests: Add VMDK tests for blockdev-create

Quoted 28 lines · Kevin Wolf

Am 07.12.2018 um 16:40 hat Eric Blake geschrieben:

Quoted 15 lines · Kevin Wolf
Quoted 14 lines

Am 07.12.2018 um 14:12 hat Markus Armbruster geschrieben:

Quoted 12 lines

git-am complains

Applying: iotests: Add VMDK tests for blockdev-create .git/rebase-apply/patch:281: trailing whitespace.
format:
.git/rebase-apply/patch:308: trailing whitespace.
format:
.git/rebase-apply/patch:335: trailing whitespace.
format:
.git/rebase-apply/patch:600: new blank line at EOF. +
warning: 4 lines add whitespace errors.

Quoted 7 lines
Quoted 2 lines

This is in the reference output, so trailing whitespace/blank lines are actually correct.

Ah, but doesn't ./check already ignore differences in trailing whitespace present in the actual running that is not present in the *.out files, precisely so we don't have to check in trailing whitespace reference outputs?

It does ignore whitespace changes, so even if we remove that whitespace, the test won't fail. But I don't think that's a good reason to check in inaccurate reference output.

Quoted 5 lines

There are a few test cases that have a reference output like this and it's always annoying: When I later add a new subtest, I add the new test code, review the ./check output and if it looks good, I do something like 'cp 237.out.bad 237.out'. At that point, I'll have to manually revert completely unrelated whitespace changes again.

Is it worth teaching iotests.img_info_log() to strip trailing whitespace? Or even to teach qemu-img itself to quit generating trailing whitespace?

Quoted 2 lines

Some 'git am' warnings feel like the lesser evil to me.

True, and a comment in the commit message about intentionally triggering a known checkpatch flag goes a long ways to document why you want the trailing whitespace (assuming we don't instead decide to tackle a root cause of having the whitespace in the first place).

Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
author
#13 lore raw

Re: [Qemu-devel] [PATCH v4 4/5] iotests: Add VMDK tests for blockdev-create

Am 07.12.2018 um 17:52 hat Eric Blake geschrieben:

Quoted 29 lines · Kevin Wolf
Quoted 28 lines

Am 07.12.2018 um 16:40 hat Eric Blake geschrieben:

Quoted 15 lines · Kevin Wolf
Quoted 14 lines

Am 07.12.2018 um 14:12 hat Markus Armbruster geschrieben:

Quoted 12 lines

git-am complains

Applying: iotests: Add VMDK tests for blockdev-create .git/rebase-apply/patch:281: trailing whitespace.
format:
.git/rebase-apply/patch:308: trailing whitespace.
format:
.git/rebase-apply/patch:335: trailing whitespace.
format:
.git/rebase-apply/patch:600: new blank line at EOF. +
warning: 4 lines add whitespace errors.

Quoted 7 lines
Quoted 2 lines

This is in the reference output, so trailing whitespace/blank lines are actually correct.

Ah, but doesn't ./check already ignore differences in trailing whitespace present in the actual running that is not present in the *.out files, precisely so we don't have to check in trailing whitespace reference outputs?

It does ignore whitespace changes, so even if we remove that whitespace, the test won't fail. But I don't think that's a good reason to check in inaccurate reference output.

Quoted 8 lines
Quoted 5 lines

There are a few test cases that have a reference output like this and it's always annoying: When I later add a new subtest, I add the new test code, review the ./check output and if it looks good, I do something like 'cp 237.out.bad 237.out'. At that point, I'll have to manually revert completely unrelated whitespace changes again.

Is it worth teaching iotests.img_info_log() to strip trailing whitespace? Or even to teach qemu-img itself to quit generating trailing whitespace?

Not sure if it's worth it, but if someone proposed such a change, I wouldn't object anyway.

Quoted 6 lines
Quoted 1 line

Some 'git am' warnings feel like the lesser evil to me.

True, and a comment in the commit message about intentionally triggering a known checkpatch flag goes a long ways to document why you want the trailing whitespace (assuming we don't instead decide to tackle a root cause of having the whitespace in the first place).

Fair enough. Once it's merged, it doesn't make a difference any more, but it could have made review a bit easier.

Kevin

author
#14 lore raw

PATCH v4 5/5 vmdk: Reject excess extents in blockdev-create

In reply to Kevin Wolf

Clarify that the number of extents provided in BlockdevCreateOptionsVmdk must match the number of extents that will actually be used. Providing more extents will result in an error now.

This requires adapting the test case to provide the right number of extents.

---
 qapi/block-core.json       |  3 ++-
 block/vmdk.c               | 29 ++++++++++++++++++++++++-----
 tests/qemu-iotests/237     |  6 +++++-
 tests/qemu-iotests/237.out | 13 +++++++------
 4 files changed, 38 insertions(+), 13 deletions(-)
qapi/block-core.json +2 -1
index 0793550cf2..afdd2f89a2 100644
@@ -4068,7 +4068,8 @@
 #               twoGbMaxExtentSparse and twoGbMaxExtentFlat formats. For
 #               monolithicFlat, only one entry is required; for
 #               twoGbMaxExtent* formats, the number of entries required is
-#               calculated as extent_number = virtual_size / 2GB.
+#               calculated as extent_number = virtual_size / 2GB. Providing
+#               more extents than will be used is an error.
 # @subformat    The subformat of the VMDK image. Default: "monolithicSparse".
 # @backing-file The path of backing file. Default: no backing file is used.
 # @adapter-type The adapter type used to fill in the descriptor. Default: ide.
block/vmdk.c +24 -5
index 5a162ee85c..682ad93aa1 100644
@@ -1970,6 +1970,7 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
 {
     int extent_idx;
     BlockBackend *blk = NULL;
+    BlockBackend *extent_blk;
     Error *local_err = NULL;
     char *desc = NULL;
     int ret = 0;
@@ -2107,7 +2108,6 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
     }
     extent_idx = 1;
     while (created_size < size) {
-        BlockBackend *extent_blk;
         int64_t cur_size = MIN(size - created_size, extent_size);
         extent_blk = extent_fn(cur_size, extent_idx, flat, split, compress,
                                zeroed_grain, opaque, errp);
@@ -2121,6 +2121,17 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
         extent_idx++;
         blk_unref(extent_blk);
     }
+
+    /* Check whether we got excess extents */
+    extent_blk = extent_fn(-1, extent_idx, flat, split, compress, zeroed_grain,
+                           opaque, NULL);
+    if (extent_blk) {
+        blk_unref(extent_blk);
+        error_setg(errp, "List of extents contains unused extents");
+        ret = -EINVAL;
+        goto exit;
+    }
+
     /* generate descriptor file */
     desc = g_strdup_printf(desc_template,
                            g_random_int(),
@@ -2181,6 +2192,12 @@ static BlockBackend *vmdk_co_create_opts_cb(int64_t size, int idx,
     char *ext_filename = NULL;
     char *rel_filename = NULL;
 
+    /* We're done, don't create excess extents. */
+    if (size == -1) {
+        assert(errp == NULL);
+        return NULL;
+    }
+
     if (idx == 0) {
         rel_filename = g_strdup_printf("%s%s", data->prefix, data->postfix);
     } else if (split) {
@@ -2342,10 +2359,12 @@ static BlockBackend *vmdk_co_create_cb(int64_t size, int idx,
     blk_set_allow_write_beyond_eof(blk, true);
     bdrv_unref(bs);
 
-    ret = vmdk_init_extent(blk, size, flat, compress, zeroed_grain, errp);
-    if (ret) {
-        blk_unref(blk);
-        blk = NULL;
+    if (size != -1) {
+        ret = vmdk_init_extent(blk, size, flat, compress, zeroed_grain, errp);
+        if (ret) {
+            blk_unref(blk);
+            blk = NULL;
+        }
     }
     return blk;
 }
tests/qemu-iotests/237 +5 -1
index e04a1ac6be..251771d7fb 100755
@@ -20,6 +20,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
+import math
 import iotests
 from iotests import imgfmt
 
@@ -222,12 +223,15 @@ with iotests.FilePath('t.vmdk') as disk_path, \
             iotests.log("= %s %d =" % (subfmt, size))
             iotests.log("")
 
+            num_extents = math.ceil(size / 2.0**31)
+            extents = [ "ext%d" % (i) for i in range(1, num_extents + 1) ]
+
             vm.launch()
             blockdev_create(vm, { 'driver': imgfmt,
                                   'file': 'node0',
                                   'size': size,
                                   'subformat': subfmt,
-                                  'extents': ['ext1', 'ext2', 'ext3'] })
+                                  'extents': extents })
             vm.shutdown()
 
             iotests.img_info_log(disk_path)
tests/qemu-iotests/237.out +7 -6
index 1aa9aad349..241c864369 100644
@@ -154,6 +154,7 @@ Job failed: Extent [0] not specified
 
 {"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 512, "subformat": "monolithicFlat"}}}
 {"return": {}}
+Job failed: List of extents contains unused extents
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}
 
@@ -161,7 +162,7 @@ Job failed: Extent [0] not specified
 
 = twoGbMaxExtentFlat 512 =
 
-{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 512, "subformat": "twoGbMaxExtentFlat"}}}
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 512, "subformat": "twoGbMaxExtentFlat"}}}
 {"return": {}}
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}
@@ -181,7 +182,7 @@ Format specific information:
 
 = twoGbMaxExtentSparse 512 =
 
-{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 512, "subformat": "twoGbMaxExtentSparse"}}}
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 512, "subformat": "twoGbMaxExtentSparse"}}}
 {"return": {}}
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}
@@ -203,7 +204,7 @@ Format specific information:
 
 = twoGbMaxExtentFlat 1073741824 =
 
-{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 1073741824, "subformat": "twoGbMaxExtentFlat"}}}
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 1073741824, "subformat": "twoGbMaxExtentFlat"}}}
 {"return": {}}
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}
@@ -223,7 +224,7 @@ Format specific information:
 
 = twoGbMaxExtentSparse 1073741824 =
 
-{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 1073741824, "subformat": "twoGbMaxExtentSparse"}}}
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 1073741824, "subformat": "twoGbMaxExtentSparse"}}}
 {"return": {}}
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}
@@ -245,7 +246,7 @@ Format specific information:
 
 = twoGbMaxExtentFlat 2147483648 =
 
-{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 2147483648, "subformat": "twoGbMaxExtentFlat"}}}
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 2147483648, "subformat": "twoGbMaxExtentFlat"}}}
 {"return": {}}
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}
@@ -265,7 +266,7 @@ Format specific information:
 
 = twoGbMaxExtentSparse 2147483648 =
 
-{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 2147483648, "subformat": "twoGbMaxExtentSparse"}}}
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 2147483648, "subformat": "twoGbMaxExtentSparse"}}}
 {"return": {}}
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}
Signature · 1 line
2.19.2
#15 lore raw

Re: [Qemu-devel] [PATCH v4 5/5] vmdk: Reject excess extents in blockdev-create

Quoted 68 lines · Kevin Wolf

Clarify that the number of extents provided in BlockdevCreateOptionsVmdk must match the number of extents that will actually be used. Providing more extents will result in an error now.

This requires adapting the test case to provide the right number of extents.

---
 qapi/block-core.json       |  3 ++-
 block/vmdk.c               | 29 ++++++++++++++++++++++++-----
 tests/qemu-iotests/237     |  6 +++++-
 tests/qemu-iotests/237.out | 13 +++++++------
 4 files changed, 38 insertions(+), 13 deletions(-)
qapi/block-core.json +2 -1
index 0793550cf2..afdd2f89a2 100644
@@ -4068,7 +4068,8 @@
 #               twoGbMaxExtentSparse and twoGbMaxExtentFlat formats. For
 #               monolithicFlat, only one entry is required; for
 #               twoGbMaxExtent* formats, the number of entries required is
-#               calculated as extent_number = virtual_size / 2GB.
+#               calculated as extent_number = virtual_size / 2GB. Providing
+#               more extents than will be used is an error.
 # @subformat    The subformat of the VMDK image. Default: "monolithicSparse".
 # @backing-file The path of backing file. Default: no backing file is used.
 # @adapter-type The adapter type used to fill in the descriptor. Default: ide.
block/vmdk.c +12 -1
index 5a162ee85c..682ad93aa1 100644
@@ -1970,6 +1970,7 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
 {
     int extent_idx;
     BlockBackend *blk = NULL;
+    BlockBackend *extent_blk;
     Error *local_err = NULL;
     char *desc = NULL;
     int ret = 0;
@@ -2107,7 +2108,6 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
     }
     extent_idx = 1;
     while (created_size < size) {
-        BlockBackend *extent_blk;
         int64_t cur_size = MIN(size - created_size, extent_size);
         extent_blk = extent_fn(cur_size, extent_idx, flat, split, compress,
                                zeroed_grain, opaque, errp);
@@ -2121,6 +2121,17 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
         extent_idx++;
         blk_unref(extent_blk);
     }
+
+    /* Check whether we got excess extents */
+    extent_blk = extent_fn(-1, extent_idx, flat, split, compress, zeroed_grain,
+                           opaque, NULL);
+    if (extent_blk) {
+        blk_unref(extent_blk);
+        error_setg(errp, "List of extents contains unused extents");
+        ret = -EINVAL;
+        goto exit;
+    }
+
     /* generate descriptor file */
     desc = g_strdup_printf(desc_template,
                            g_random_int(),

block/vmdk.c static BlockBackend *vmdk_co_create_opts_cb(int64_t size, int idx, · line 2200

Quoted 9 lines
block/vmdk.c +6 -0
@@ -2181,6 +2192,12 @@ static BlockBackend *vmdk_co_create_opts_cb(int64_t size, int idx,
     char *ext_filename = NULL;
     char *rel_filename = NULL;
 
+    /* We're done, don't create excess extents. */
+    if (size == -1) {
+        assert(errp == NULL);
+        return NULL;
+    }
+

I'm afraid I don't get this hunk.

Quoted 107 lines
block/vmdk.c +6 -4
     if (idx == 0) {
         rel_filename = g_strdup_printf("%s%s", data->prefix, data->postfix);
     } else if (split) {
@@ -2342,10 +2359,12 @@ static BlockBackend *vmdk_co_create_cb(int64_t size, int idx,
     blk_set_allow_write_beyond_eof(blk, true);
     bdrv_unref(bs);
 
-    ret = vmdk_init_extent(blk, size, flat, compress, zeroed_grain, errp);
-    if (ret) {
-        blk_unref(blk);
-        blk = NULL;
+    if (size != -1) {
+        ret = vmdk_init_extent(blk, size, flat, compress, zeroed_grain, errp);
+        if (ret) {
+            blk_unref(blk);
+            blk = NULL;
+        }
     }
     return blk;
 }
tests/qemu-iotests/237 +5 -1
index e04a1ac6be..251771d7fb 100755
@@ -20,6 +20,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
+import math
 import iotests
 from iotests import imgfmt
 
@@ -222,12 +223,15 @@ with iotests.FilePath('t.vmdk') as disk_path, \
             iotests.log("= %s %d =" % (subfmt, size))
             iotests.log("")
 
+            num_extents = math.ceil(size / 2.0**31)
+            extents = [ "ext%d" % (i) for i in range(1, num_extents + 1) ]
+
             vm.launch()
             blockdev_create(vm, { 'driver': imgfmt,
                                   'file': 'node0',
                                   'size': size,
                                   'subformat': subfmt,
-                                  'extents': ['ext1', 'ext2', 'ext3'] })
+                                  'extents': extents })
             vm.shutdown()
 
             iotests.img_info_log(disk_path)
tests/qemu-iotests/237.out +6 -5
index 1aa9aad349..241c864369 100644
@@ -154,6 +154,7 @@ Job failed: Extent [0] not specified
 
 {"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 512, "subformat": "monolithicFlat"}}}
 {"return": {}}
+Job failed: List of extents contains unused extents
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}
 
@@ -161,7 +162,7 @@ Job failed: Extent [0] not specified
 
 = twoGbMaxExtentFlat 512 =
 
-{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 512, "subformat": "twoGbMaxExtentFlat"}}}
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 512, "subformat": "twoGbMaxExtentFlat"}}}
 {"return": {}}
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}
@@ -181,7 +182,7 @@ Format specific information:
 
 = twoGbMaxExtentSparse 512 =
 
-{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 512, "subformat": "twoGbMaxExtentSparse"}}}
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 512, "subformat": "twoGbMaxExtentSparse"}}}
 {"return": {}}
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}
@@ -203,7 +204,7 @@ Format specific information:
 
 = twoGbMaxExtentFlat 1073741824 =
 
-{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 1073741824, "subformat": "twoGbMaxExtentFlat"}}}
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 1073741824, "subformat": "twoGbMaxExtentFlat"}}}
 {"return": {}}
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}
@@ -223,7 +224,7 @@ Format specific information:
 
 = twoGbMaxExtentSparse 1073741824 =
 
-{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 1073741824, "subformat": "twoGbMaxExtentSparse"}}}
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 1073741824, "subformat": "twoGbMaxExtentSparse"}}}
 {"return": {}}
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}
@@ -245,7 +246,7 @@ Format specific information:
 
 = twoGbMaxExtentFlat 2147483648 =
 
-{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 2147483648, "subformat": "twoGbMaxExtentFlat"}}}
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 2147483648, "subformat": "twoGbMaxExtentFlat"}}}
 {"return": {}}
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}

tests/qemu-iotests/237.out Format specific information: · line 272

Quoted 8 lines
tests/qemu-iotests/237.out +1 -1
@@ -265,7 +266,7 @@ Format specific information:
 
 = twoGbMaxExtentSparse 2147483648 =
 
-{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1", "ext2", "ext3"], "file": "node0", "size": 2147483648, "subformat": "twoGbMaxExtentSparse"}}}
+{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "vmdk", "extents": ["ext1"], "file": "node0", "size": 2147483648, "subformat": "twoGbMaxExtentSparse"}}}
 {"return": {}}
 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
 {"return": {}}

Apart from the hunk that stumps me, the patch looks good to me.

author
#16 lore raw

Re: [Qemu-devel] [PATCH v4 5/5] vmdk: Reject excess extents in blockdev-create

Am 07.12.2018 um 14:11 hat Markus Armbruster geschrieben:

Quoted 73 lines
Quoted 71 lines · Kevin Wolf

Clarify that the number of extents provided in BlockdevCreateOptionsVmdk must match the number of extents that will actually be used. Providing more extents will result in an error now.

This requires adapting the test case to provide the right number of extents.

---
 qapi/block-core.json       |  3 ++-
 block/vmdk.c               | 29 ++++++++++++++++++++++++-----
 tests/qemu-iotests/237     |  6 +++++-
 tests/qemu-iotests/237.out | 13 +++++++------
 4 files changed, 38 insertions(+), 13 deletions(-)
qapi/block-core.json +2 -1
index 0793550cf2..afdd2f89a2 100644
@@ -4068,7 +4068,8 @@
 #               twoGbMaxExtentSparse and twoGbMaxExtentFlat formats. For
 #               monolithicFlat, only one entry is required; for
 #               twoGbMaxExtent* formats, the number of entries required is
-#               calculated as extent_number = virtual_size / 2GB.
+#               calculated as extent_number = virtual_size / 2GB. Providing
+#               more extents than will be used is an error.
 # @subformat    The subformat of the VMDK image. Default: "monolithicSparse".
 # @backing-file The path of backing file. Default: no backing file is used.
 # @adapter-type The adapter type used to fill in the descriptor. Default: ide.
block/vmdk.c +12 -1
index 5a162ee85c..682ad93aa1 100644
@@ -1970,6 +1970,7 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
 {
     int extent_idx;
     BlockBackend *blk = NULL;
+    BlockBackend *extent_blk;
     Error *local_err = NULL;
     char *desc = NULL;
     int ret = 0;
@@ -2107,7 +2108,6 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
     }
     extent_idx = 1;
     while (created_size < size) {
-        BlockBackend *extent_blk;
         int64_t cur_size = MIN(size - created_size, extent_size);
         extent_blk = extent_fn(cur_size, extent_idx, flat, split, compress,
                                zeroed_grain, opaque, errp);
@@ -2121,6 +2121,17 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
         extent_idx++;
         blk_unref(extent_blk);
     }
+
+    /* Check whether we got excess extents */
+    extent_blk = extent_fn(-1, extent_idx, flat, split, compress, zeroed_grain,
+                           opaque, NULL);
+    if (extent_blk) {
+        blk_unref(extent_blk);
+        error_setg(errp, "List of extents contains unused extents");
+        ret = -EINVAL;
+        goto exit;
+    }
+
     /* generate descriptor file */
     desc = g_strdup_printf(desc_template,
                            g_random_int(),
@@ -2181,6 +2192,12 @@ static BlockBackend *vmdk_co_create_opts_cb(int64_t size, int idx,
     char *ext_filename = NULL;
     char *rel_filename = NULL;
 
Quoted 8 lines

block/vmdk.c static BlockBackend *vmdk_co_create_opts_cb(int64_t size, int idx, · line 2200

Quoted 6 lines
block/vmdk.c +6 -0
+    /* We're done, don't create excess extents. */
+    if (size == -1) {
+        assert(errp == NULL);
+        return NULL;
+    }
+

I'm afraid I don't get this hunk.

vmdk_co_create_opts_cb() is for the legacy code path, where 'qemu-img create' passes us a QemuOpts. This doesn't contain extents, so rather than returning ther next extent from the input, this function simply creates a new extent file each time it is called.

When checking whether we have too many extents, we don't know from which code path we came, so we have to call the callback uncondtionally and see whether it still returns an extra extent. In this case, creating a new one would obviously be counterproductive and trigger an error, so I just return NULL immediately.

If you have a suggestion for a comment to put somewhere, I'll gladly add it.

Kevin

Reviewed-by
#17 lore raw

Re: [Qemu-devel] [PATCH v4 5/5] vmdk: Reject excess extents in blockdev-create

Quoted 88 lines · Kevin Wolf

Am 07.12.2018 um 14:11 hat Markus Armbruster geschrieben:

Quoted 73 lines
Quoted 71 lines · Kevin Wolf

Clarify that the number of extents provided in BlockdevCreateOptionsVmdk must match the number of extents that will actually be used. Providing more extents will result in an error now.

This requires adapting the test case to provide the right number of extents.

---
 qapi/block-core.json       |  3 ++-
 block/vmdk.c               | 29 ++++++++++++++++++++++++-----
 tests/qemu-iotests/237     |  6 +++++-
 tests/qemu-iotests/237.out | 13 +++++++------
 4 files changed, 38 insertions(+), 13 deletions(-)
qapi/block-core.json +2 -1
index 0793550cf2..afdd2f89a2 100644
@@ -4068,7 +4068,8 @@
 #               twoGbMaxExtentSparse and twoGbMaxExtentFlat formats. For
 #               monolithicFlat, only one entry is required; for
 #               twoGbMaxExtent* formats, the number of entries required is
-#               calculated as extent_number = virtual_size / 2GB.
+#               calculated as extent_number = virtual_size / 2GB. Providing
+#               more extents than will be used is an error.
 # @subformat    The subformat of the VMDK image. Default: "monolithicSparse".
 # @backing-file The path of backing file. Default: no backing file is used.
 # @adapter-type The adapter type used to fill in the descriptor. Default: ide.
block/vmdk.c +12 -1
index 5a162ee85c..682ad93aa1 100644
@@ -1970,6 +1970,7 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
 {
     int extent_idx;
     BlockBackend *blk = NULL;
+    BlockBackend *extent_blk;
     Error *local_err = NULL;
     char *desc = NULL;
     int ret = 0;
@@ -2107,7 +2108,6 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
     }
     extent_idx = 1;
     while (created_size < size) {
-        BlockBackend *extent_blk;
         int64_t cur_size = MIN(size - created_size, extent_size);
         extent_blk = extent_fn(cur_size, extent_idx, flat, split, compress,
                                zeroed_grain, opaque, errp);
@@ -2121,6 +2121,17 @@ static int coroutine_fn vmdk_co_do_create(int64_t size,
         extent_idx++;
         blk_unref(extent_blk);
     }
+
+    /* Check whether we got excess extents */
+    extent_blk = extent_fn(-1, extent_idx, flat, split, compress, zeroed_grain,
+                           opaque, NULL);
+    if (extent_blk) {
+        blk_unref(extent_blk);
+        error_setg(errp, "List of extents contains unused extents");
+        ret = -EINVAL;
+        goto exit;
+    }
+
     /* generate descriptor file */
     desc = g_strdup_printf(desc_template,
                            g_random_int(),
@@ -2181,6 +2192,12 @@ static BlockBackend *vmdk_co_create_opts_cb(int64_t size, int idx,
     char *ext_filename = NULL;
     char *rel_filename = NULL;
 
Quoted 8 lines

block/vmdk.c static BlockBackend *vmdk_co_create_opts_cb(int64_t size, int idx, · line 2200

Quoted 6 lines
block/vmdk.c +6 -0
+    /* We're done, don't create excess extents. */
+    if (size == -1) {
+        assert(errp == NULL);
+        return NULL;
+    }
+

I'm afraid I don't get this hunk.

vmdk_co_create_opts_cb() is for the legacy code path, where 'qemu-img create' passes us a QemuOpts. This doesn't contain extents, so rather than returning ther next extent from the input, this function simply creates a new extent file each time it is called.

Quoted 8 lines

When checking whether we have too many extents, we don't know from which code path we came, so we have to call the callback uncondtionally and see whether it still returns an extra extent. In this case, creating a new one would obviously be counterproductive and trigger an error, so I just return NULL immediately.

If you have a suggestion for a comment to put somewhere, I'll gladly add it.

Coming up with intelligble comments exceed my mental capabilities right now, I'm afraid. But I can still give my

#18 lore raw
In reply to Kevin Wolf
Patchew URL: https://patchew.org/QEMU/[email protected]/

Patchew URL: https://patchew.org/QEMU/[email protected]/

Hi,

This series seems to have some coding style problems. See output below for more information:

Message-id: [email protected]
Subject: [Qemu-devel] [PATCH v4 0/5] vmdk: Implement blockdev-create
Type: series

=== TEST SCRIPT BEGIN ===
#!/bin/bash

BASE=base
n=1
total=$(git log --oneline $BASE.. | wc -l)
failed=0

git config --local diff.renamelimit 0
git config --local diff.renames True
git config --local diff.algorithm histogram

commits="$(git log --format=%H --reverse $BASE..)" for c in $commits; do

    echo "Checking PATCH $n/$total: $(git log -n 1 --format=%s $c)..."
    if ! git show $c --format=email | ./scripts/checkpatch.pl --mailback -; then
        failed=1
        echo
    fi
    n=$((n+1))

done

exit $failed
=== TEST SCRIPT END ===

Updating 3c8cf5a9c21ff8782164d1def7f44bd888713384 Switched to a new branch 'test'
914b679 vmdk: Reject excess extents in blockdev-create 2721cff iotests: Add VMDK tests for blockdev-create c098e37 iotests: Filter cid numbers in VMDK extent info 3e8f068 vmdk: Implement .bdrv_co_create callback 54b29ff vmdk: Refactor vmdk_create_extent

=== OUTPUT BEGIN ===
Checking PATCH 1/5: vmdk: Refactor vmdk_create_extent...
WARNING: line over 80 characters
#125: FILE: block/vmdk.c:2114:
+ flat, compress, zeroed_grain, NULL, opts, errp)) {

total: 0 errors, 1 warnings, 104 lines checked

Your patch has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS.
Checking PATCH 2/5: vmdk: Implement .bdrv_co_create callback...
WARNING: line over 80 characters
#216: FILE: block/vmdk.c:2075:
+ bdrv_get_full_backing_filename_from_filename(blk_bs(blk)->filename, backing_file,

WARNING: line over 80 characters
#371: FILE: block/vmdk.c:2174:
+ bool flat, bool split, bool compress,

WARNING: line over 80 characters
#389: FILE: block/vmdk.c:2192:
+ rel_filename = g_strdup_printf("%s-flat%s", data->prefix, data->postfix);

WARNING: line over 80 characters
#406: FILE: block/vmdk.c:2209:
+static int coroutine_fn vmdk_co_create_opts(const char *filename, QemuOpts *opts,

total: 0 errors, 4 warnings, 658 lines checked

Your patch has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS.
Checking PATCH 3/5: iotests: Filter cid numbers in VMDK extent info... Checking PATCH 4/5: iotests: Add VMDK tests for blockdev-create...
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? #11:
new file mode 100755

ERROR: trailing whitespace
#282: FILE: tests/qemu-iotests/237.out:28:
+ format: $

ERROR: trailing whitespace
#309: FILE: tests/qemu-iotests/237.out:55:
+ format: $

ERROR: trailing whitespace
#336: FILE: tests/qemu-iotests/237.out:82:
+ format: $

total: 3 errors, 1 warnings, 584 lines checked

Your patch has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS.

Checking PATCH 5/5: vmdk: Reject excess extents in blockdev-create... === OUTPUT END ===

Test command exited with code: 1

The full log is available at
http://patchew.org/logs/[email protected]/testing.checkpatch/?type=message.
---
Email generated automatically by Patchew [http://patchew.org/].
Please send your feedback to [email protected]