All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/14] LUKS: encryption slot management using amend interface
@ 2020-03-08 15:18 Maxim Levitsky
  2020-03-08 15:18 ` [PATCH v2 01/14] qcrypto/core: add generic infrastructure for crypto options amendment Maxim Levitsky
                   ` (14 more replies)
  0 siblings, 15 replies; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

Hi!
Here is the updated series of my patches, incorporating all the feedback I received.

This implements the API interface that we agreed upon except that I merged the
LUKSKeyslotActive/LUKSKeyslotInactive union into a struct because otherwise
I need nested unions which are not supported currently by QAPI parser.
This didn't change the API and thus once support for nested unions is there,
it can always be implemented in backward compatible way.

I hope that this series will finally be considered for merging, since I am somewhat running
out of time to finish this task.

Patches are strictly divided by topic to 3 groups, and each group depends on former groups.

* Patches 1,2 implement qcrypto generic amend interface, including definition
  of structs used in crypto.json and implement this in luks crypto driver
  Nothing is exposed to the user at this stage

* Patches 3-9 use the code from patches 1,2 to implement qemu-img amend based encryption slot management
  for luks and for qcow2, and add a bunch of iotests to cover that.

* Patches 10-13 add x-blockdev-amend (I'll drop the -x prefix if you like), and wire it
  to luks and qcow2 driver to implement qmp based encryption slot management also using
  the code from patches 1,2, and also add a bunch of iotests to cover this.

Tested with -raw,-qcow2 and -luks iotests and 'make check'

Best regards,
        Maxim Levitsky

clone of "luks-keymgmnt-v2"

Maxim Levitsky (14):
  qcrypto/core: add generic infrastructure for crypto options amendment
  qcrypto/luks: implement encryption key management
  block/amend: add 'force' option
  block/amend: separate amend and create options for qemu-img
  block/amend: refactor qcow2 amend options
  block/crypto: rename two functions
  block/crypto: implement the encryption key management
  block/qcow2: extend qemu-img amend interface with crypto options
  iotests: filter few more luks specific create options
  iotests: qemu-img tests for luks key management
  block/core: add generic infrastructure for x-blockdev-amend qmp
    command
  block/crypto: implement blockdev-amend
  block/qcow2: implement blockdev-amend
  iotests: add tests for blockdev-amend

 block.c                          |   4 +-
 block/Makefile.objs              |   2 +-
 block/amend.c                    | 108 +++++++++
 block/crypto.c                   | 203 ++++++++++++++--
 block/crypto.h                   |  47 +++-
 block/qcow2.c                    | 314 ++++++++++++++----------
 crypto/block-luks.c              | 398 ++++++++++++++++++++++++++++++-
 crypto/block.c                   |  31 +++
 crypto/blockpriv.h               |   8 +
 docs/tools/qemu-img.rst          |   5 +-
 include/block/block.h            |   1 +
 include/block/block_int.h        |  24 +-
 include/crypto/block.h           |  22 ++
 qapi/block-core.json             |  68 ++++++
 qapi/crypto.json                 |  75 +++++-
 qapi/job.json                    |   4 +-
 qemu-img-cmds.hx                 |   4 +-
 qemu-img.c                       |  44 +++-
 tests/qemu-iotests/049.out       | 102 ++++----
 tests/qemu-iotests/061.out       |  12 +-
 tests/qemu-iotests/079.out       |  18 +-
 tests/qemu-iotests/082.out       | 176 ++++----------
 tests/qemu-iotests/085.out       |  38 +--
 tests/qemu-iotests/087.out       |   6 +-
 tests/qemu-iotests/115.out       |   2 +-
 tests/qemu-iotests/121.out       |   4 +-
 tests/qemu-iotests/125.out       | 192 +++++++--------
 tests/qemu-iotests/134.out       |   2 +-
 tests/qemu-iotests/144.out       |   4 +-
 tests/qemu-iotests/158.out       |   4 +-
 tests/qemu-iotests/182.out       |   2 +-
 tests/qemu-iotests/185.out       |   8 +-
 tests/qemu-iotests/188.out       |   2 +-
 tests/qemu-iotests/189.out       |   4 +-
 tests/qemu-iotests/198.out       |   4 +-
 tests/qemu-iotests/243.out       |  16 +-
 tests/qemu-iotests/250.out       |   2 +-
 tests/qemu-iotests/255.out       |   8 +-
 tests/qemu-iotests/263.out       |   4 +-
 tests/qemu-iotests/280.out       |   2 +-
 tests/qemu-iotests/284.out       |   6 +-
 tests/qemu-iotests/300           | 207 ++++++++++++++++
 tests/qemu-iotests/300.out       |  99 ++++++++
 tests/qemu-iotests/301           |  90 +++++++
 tests/qemu-iotests/301.out       |  30 +++
 tests/qemu-iotests/302           | 278 +++++++++++++++++++++
 tests/qemu-iotests/302.out       |  40 ++++
 tests/qemu-iotests/303           | 233 ++++++++++++++++++
 tests/qemu-iotests/303.out       |  33 +++
 tests/qemu-iotests/common.filter |   6 +-
 tests/qemu-iotests/group         |   6 +
 51 files changed, 2486 insertions(+), 516 deletions(-)
 create mode 100644 block/amend.c
 create mode 100755 tests/qemu-iotests/300
 create mode 100644 tests/qemu-iotests/300.out
 create mode 100755 tests/qemu-iotests/301
 create mode 100644 tests/qemu-iotests/301.out
 create mode 100755 tests/qemu-iotests/302
 create mode 100644 tests/qemu-iotests/302.out
 create mode 100755 tests/qemu-iotests/303
 create mode 100644 tests/qemu-iotests/303.out

-- 
2.17.2



^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH v2 01/14] qcrypto/core: add generic infrastructure for crypto options amendment
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
@ 2020-03-08 15:18 ` Maxim Levitsky
  2020-03-08 15:18 ` [PATCH v2 02/14] qcrypto/luks: implement encryption key management Maxim Levitsky
                   ` (13 subsequent siblings)
  14 siblings, 0 replies; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

This will be used first to implement luks keyslot management.

block_crypto_amend_opts_init will be used to convert
qemu-img cmdline to QCryptoBlockAmendOptions

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
---
 block/crypto.c         | 17 +++++++++++++++++
 block/crypto.h         |  3 +++
 crypto/block.c         | 31 +++++++++++++++++++++++++++++++
 crypto/blockpriv.h     |  8 ++++++++
 include/crypto/block.h | 22 ++++++++++++++++++++++
 qapi/crypto.json       | 16 ++++++++++++++++
 6 files changed, 97 insertions(+)

diff --git a/block/crypto.c b/block/crypto.c
index 24823835c1..ecf96a7a9b 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -184,6 +184,23 @@ block_crypto_create_opts_init(QDict *opts, Error **errp)
     return ret;
 }
 
+QCryptoBlockAmendOptions *
+block_crypto_amend_opts_init(QDict *opts, Error **errp)
+{
+    Visitor *v;
+    QCryptoBlockAmendOptions *ret;
+
+    v = qobject_input_visitor_new_flat_confused(opts, errp);
+    if (!v) {
+        return NULL;
+    }
+
+    visit_type_QCryptoBlockAmendOptions(v, NULL, &ret, errp);
+
+    visit_free(v);
+    return ret;
+}
+
 
 static int block_crypto_open_generic(QCryptoBlockFormat format,
                                      QemuOptsList *opts_spec,
diff --git a/block/crypto.h b/block/crypto.h
index b935695e79..06e044c9be 100644
--- a/block/crypto.h
+++ b/block/crypto.h
@@ -91,6 +91,9 @@
 QCryptoBlockCreateOptions *
 block_crypto_create_opts_init(QDict *opts, Error **errp);
 
+QCryptoBlockAmendOptions *
+block_crypto_amend_opts_init(QDict *opts, Error **errp);
+
 QCryptoBlockOpenOptions *
 block_crypto_open_opts_init(QDict *opts, Error **errp);
 
diff --git a/crypto/block.c b/crypto/block.c
index 325752871c..0ce67db641 100644
--- a/crypto/block.c
+++ b/crypto/block.c
@@ -115,6 +115,37 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
 }
 
 
+int qcrypto_block_amend_options(QCryptoBlock *block,
+                                QCryptoBlockReadFunc readfunc,
+                                QCryptoBlockWriteFunc writefunc,
+                                void *opaque,
+                                QCryptoBlockAmendOptions *options,
+                                bool force,
+                                Error **errp)
+{
+    if (options->format != block->format) {
+        error_setg(errp,
+                   "Cannot amend encryption format");
+        return -1;
+    }
+
+    if (!block->driver->amend) {
+        error_setg(errp,
+                   "Crypto format %s doesn't support format options amendment",
+                   QCryptoBlockFormat_str(block->format));
+        return -1;
+    }
+
+    return block->driver->amend(block,
+                                readfunc,
+                                writefunc,
+                                opaque,
+                                options,
+                                force,
+                                errp);
+}
+
+
 QCryptoBlockInfo *qcrypto_block_get_info(QCryptoBlock *block,
                                          Error **errp)
 {
diff --git a/crypto/blockpriv.h b/crypto/blockpriv.h
index 71c59cb542..3c7ccea504 100644
--- a/crypto/blockpriv.h
+++ b/crypto/blockpriv.h
@@ -62,6 +62,14 @@ struct QCryptoBlockDriver {
                   void *opaque,
                   Error **errp);
 
+    int (*amend)(QCryptoBlock *block,
+                 QCryptoBlockReadFunc readfunc,
+                 QCryptoBlockWriteFunc writefunc,
+                 void *opaque,
+                 QCryptoBlockAmendOptions *options,
+                 bool force,
+                 Error **errp);
+
     int (*get_info)(QCryptoBlock *block,
                     QCryptoBlockInfo *info,
                     Error **errp);
diff --git a/include/crypto/block.h b/include/crypto/block.h
index d49d2c2da9..e4553cf33d 100644
--- a/include/crypto/block.h
+++ b/include/crypto/block.h
@@ -144,6 +144,28 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
                                    void *opaque,
                                    Error **errp);
 
+/**
+ * qcrypto_block_amend_options:
+ * @block: the block encryption object
+ *
+ * @readfunc: callback for reading data from the volume header
+ * @writefunc: callback for writing data to the volume header
+ * @opaque: data to pass to @readfunc and @writefunc
+ * @options: the new/amended encryption options
+ * @force: hint for the driver to allow unsafe operation
+ * @errp: error pointer
+ *
+ * Changes the crypto options of the encryption format
+ *
+ */
+int qcrypto_block_amend_options(QCryptoBlock *block,
+                                QCryptoBlockReadFunc readfunc,
+                                QCryptoBlockWriteFunc writefunc,
+                                void *opaque,
+                                QCryptoBlockAmendOptions *options,
+                                bool force,
+                                Error **errp);
+
 
 /**
  * qcrypto_block_get_info:
diff --git a/qapi/crypto.json b/qapi/crypto.json
index b2a4cff683..3fd0ce177e 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -309,3 +309,19 @@
   'base': 'QCryptoBlockInfoBase',
   'discriminator': 'format',
   'data': { 'luks': 'QCryptoBlockInfoLUKS' } }
+
+
+
+##
+# @QCryptoBlockAmendOptions:
+#
+# The options that are available for all encryption formats
+# when amending encryption settings
+#
+# Since: 5.0
+##
+{ 'union': 'QCryptoBlockAmendOptions',
+  'base': 'QCryptoBlockOptionsBase',
+  'discriminator': 'format',
+  'data': {
+            } }
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 02/14] qcrypto/luks: implement encryption key management
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
  2020-03-08 15:18 ` [PATCH v2 01/14] qcrypto/core: add generic infrastructure for crypto options amendment Maxim Levitsky
@ 2020-03-08 15:18 ` Maxim Levitsky
  2020-03-10 10:58   ` Max Reitz
  2020-04-28 13:16   ` Daniel P. Berrangé
  2020-03-08 15:18 ` [PATCH v2 03/14] block/amend: add 'force' option Maxim Levitsky
                   ` (12 subsequent siblings)
  14 siblings, 2 replies; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

Next few patches will expose that functionality
to the user.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 crypto/block-luks.c | 398 +++++++++++++++++++++++++++++++++++++++++++-
 qapi/crypto.json    |  61 ++++++-
 2 files changed, 455 insertions(+), 4 deletions(-)

diff --git a/crypto/block-luks.c b/crypto/block-luks.c
index 4861db810c..b11ee08c6d 100644
--- a/crypto/block-luks.c
+++ b/crypto/block-luks.c
@@ -32,6 +32,7 @@
 #include "qemu/uuid.h"
 
 #include "qemu/coroutine.h"
+#include "qemu/bitmap.h"
 
 /*
  * Reference for the LUKS format implemented here is
@@ -70,6 +71,9 @@ typedef struct QCryptoBlockLUKSKeySlot QCryptoBlockLUKSKeySlot;
 
 #define QCRYPTO_BLOCK_LUKS_SECTOR_SIZE 512LL
 
+#define QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS 2000
+#define QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS 40
+
 static const char qcrypto_block_luks_magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN] = {
     'L', 'U', 'K', 'S', 0xBA, 0xBE
 };
@@ -219,6 +223,9 @@ struct QCryptoBlockLUKS {
 
     /* Hash algorithm used in pbkdf2 function */
     QCryptoHashAlgorithm hash_alg;
+
+    /* Name of the secret that was used to open the image */
+    char *secret;
 };
 
 
@@ -1069,6 +1076,108 @@ qcrypto_block_luks_find_key(QCryptoBlock *block,
     return -1;
 }
 
+/*
+ * Returns true if a slot i is marked as active
+ * (contains encrypted copy of the master key)
+ */
+static bool
+qcrypto_block_luks_slot_active(const QCryptoBlockLUKS *luks,
+                               unsigned int slot_idx)
+{
+    uint32_t val = luks->header.key_slots[slot_idx].active;
+    return val ==  QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED;
+}
+
+/*
+ * Returns the number of slots that are marked as active
+ * (slots that contain encrypted copy of the master key)
+ */
+static unsigned int
+qcrypto_block_luks_count_active_slots(const QCryptoBlockLUKS *luks)
+{
+    size_t i = 0;
+    unsigned int ret = 0;
+
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+        if (qcrypto_block_luks_slot_active(luks, i)) {
+            ret++;
+        }
+    }
+    return ret;
+}
+
+/*
+ * Finds first key slot which is not active
+ * Returns the key slot index, or -1 if it doesn't exist
+ */
+static int
+qcrypto_block_luks_find_free_keyslot(const QCryptoBlockLUKS *luks)
+{
+    size_t i;
+
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+        if (!qcrypto_block_luks_slot_active(luks, i)) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+/*
+ * Erases an keyslot given its index
+ * Returns:
+ *    0 if the keyslot was erased successfully
+ *   -1 if a error occurred while erasing the keyslot
+ *
+ */
+static int
+qcrypto_block_luks_erase_key(QCryptoBlock *block,
+                             unsigned int slot_idx,
+                             QCryptoBlockWriteFunc writefunc,
+                             void *opaque,
+                             Error **errp)
+{
+    QCryptoBlockLUKS *luks = block->opaque;
+    QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx];
+    g_autofree uint8_t *garbagesplitkey = NULL;
+    size_t splitkeylen = luks->header.master_key_len * slot->stripes;
+    size_t i;
+
+    assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
+    assert(splitkeylen > 0);
+    garbagesplitkey = g_new0(uint8_t, splitkeylen);
+
+    /* Reset the key slot header */
+    memset(slot->salt, 0, QCRYPTO_BLOCK_LUKS_SALT_LEN);
+    slot->iterations = 0;
+    slot->active = QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED;
+
+    qcrypto_block_luks_store_header(block,  writefunc, opaque, errp);
+    /*
+     * Now try to erase the key material, even if the header
+     * update failed
+     */
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS; i++) {
+        if (qcrypto_random_bytes(garbagesplitkey, splitkeylen, errp) < 0) {
+            /*
+             * If we failed to get the random data, still write
+             * at least zeros to the key slot at least once
+             */
+            if (i > 0) {
+                return -1;
+            }
+        }
+        if (writefunc(block,
+                      slot->key_offset_sector * QCRYPTO_BLOCK_LUKS_SECTOR_SIZE,
+                      garbagesplitkey,
+                      splitkeylen,
+                      opaque,
+                      errp) != splitkeylen) {
+            return -1;
+        }
+    }
+    return 0;
+}
 
 static int
 qcrypto_block_luks_open(QCryptoBlock *block,
@@ -1099,6 +1208,7 @@ qcrypto_block_luks_open(QCryptoBlock *block,
 
     luks = g_new0(QCryptoBlockLUKS, 1);
     block->opaque = luks;
+    luks->secret = g_strdup(options->u.luks.key_secret);
 
     if (qcrypto_block_luks_load_header(block, readfunc, opaque, errp) < 0) {
         goto fail;
@@ -1164,6 +1274,7 @@ qcrypto_block_luks_open(QCryptoBlock *block,
  fail:
     qcrypto_block_free_cipher(block);
     qcrypto_ivgen_free(block->ivgen);
+    g_free(luks->secret);
     g_free(luks);
     return -1;
 }
@@ -1204,7 +1315,7 @@ qcrypto_block_luks_create(QCryptoBlock *block,
 
     memcpy(&luks_opts, &options->u.luks, sizeof(luks_opts));
     if (!luks_opts.has_iter_time) {
-        luks_opts.iter_time = 2000;
+        luks_opts.iter_time = QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS;
     }
     if (!luks_opts.has_cipher_alg) {
         luks_opts.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256;
@@ -1244,6 +1355,8 @@ qcrypto_block_luks_create(QCryptoBlock *block,
                    optprefix ? optprefix : "");
         goto error;
     }
+    luks->secret = g_strdup(options->u.luks.key_secret);
+
     password = qcrypto_secret_lookup_as_utf8(luks_opts.key_secret, errp);
     if (!password) {
         goto error;
@@ -1471,10 +1584,286 @@ qcrypto_block_luks_create(QCryptoBlock *block,
     qcrypto_block_free_cipher(block);
     qcrypto_ivgen_free(block->ivgen);
 
+    g_free(luks->secret);
     g_free(luks);
     return -1;
 }
 
+/*
+ * Given LUKSKeyslotUpdate command, set @slots_bitmap with all slots
+ * that will be updated with new password (or erased)
+ * returns 0 on success, and -1 on failure
+ */
+static int
+qcrypto_block_luks_get_update_bitmap(QCryptoBlock *block,
+                                     QCryptoBlockReadFunc readfunc,
+                                     void *opaque,
+                                     const QCryptoBlockAmendOptionsLUKS *opts,
+                                     unsigned long *slots_bitmap,
+                                     Error **errp)
+{
+    const QCryptoBlockLUKS *luks = block->opaque;
+    size_t i;
+
+    bitmap_zero(slots_bitmap, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
+
+    if (opts->has_keyslot) {
+        /* keyslot set, select only this keyslot */
+        int keyslot = opts->keyslot;
+
+        if (keyslot < 0 || keyslot >= QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS) {
+            error_setg(errp,
+                       "Invalid slot %u specified, must be between 0 and %u",
+                       keyslot, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS - 1);
+            return -1;
+        }
+        bitmap_set(slots_bitmap, keyslot, 1);
+
+    } else if (opts->has_old_secret) {
+        /* initially select all active keyslots */
+        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+            if (qcrypto_block_luks_slot_active(luks, i)) {
+                bitmap_set(slots_bitmap, i, 1);
+            }
+        }
+    } else {
+        /* find a free keyslot */
+        int slot = qcrypto_block_luks_find_free_keyslot(luks);
+
+        if (slot == -1) {
+            error_setg(errp,
+                       "Can't add a keyslot - all key slots are in use");
+            return -1;
+        }
+        bitmap_set(slots_bitmap, slot, 1);
+    }
+
+    if (opts->has_old_secret) {
+        /* now deselect all keyslots that don't contain the password */
+        g_autofree uint8_t *tmpkey = g_new0(uint8_t,
+                                            luks->header.master_key_len);
+
+        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+            g_autofree char *old_password = NULL;
+            int rv;
+
+            if (!test_bit(i, slots_bitmap)) {
+                continue;
+            }
+
+            old_password = qcrypto_secret_lookup_as_utf8(opts->old_secret,
+                                                         errp);
+            if (!old_password) {
+                return -1;
+            }
+
+            rv = qcrypto_block_luks_load_key(block,
+                                             i,
+                                             old_password,
+                                             tmpkey,
+                                             readfunc,
+                                             opaque,
+                                             errp);
+            if (rv == -1) {
+                return -1;
+            } else if (rv == 0) {
+                bitmap_clear(slots_bitmap, i, 1);
+            }
+        }
+    }
+    return 0;
+}
+
+/*
+ * Erase a set of keyslots given in @slots_bitmap
+ */
+static int qcrypto_block_luks_erase_keys(QCryptoBlock *block,
+                                         QCryptoBlockReadFunc readfunc,
+                                         QCryptoBlockWriteFunc writefunc,
+                                         void *opaque,
+                                         unsigned long *slots_bitmap,
+                                         bool force,
+                                         Error **errp)
+{
+    QCryptoBlockLUKS *luks = block->opaque;
+    long slot_count = bitmap_count_one(slots_bitmap,
+                                       QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
+    size_t i;
+
+    /* safety checks */
+    if (!force && slot_count == qcrypto_block_luks_count_active_slots(luks)) {
+        error_setg(errp,
+                   "Requested operation will erase all active keyslots"
+                   " which will erase all the data in the image"
+                   " irreversibly - refusing operation");
+        return -EINVAL;
+    }
+
+    /* new apply the update */
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+        if (!test_bit(i, slots_bitmap)) {
+            continue;
+        }
+        if (qcrypto_block_luks_erase_key(block, i, writefunc, opaque, errp)) {
+            error_append_hint(errp, "Failed to erase keyslot %zu", i);
+            return -EINVAL;
+        }
+    }
+    return 0;
+}
+
+/*
+ * Set a set of keyslots to @master_key encrypted by @new_secret
+ */
+static int qcrypto_block_luks_set_keys(QCryptoBlock *block,
+                                       QCryptoBlockReadFunc readfunc,
+                                       QCryptoBlockWriteFunc writefunc,
+                                       void *opaque,
+                                       unsigned long *slots_bitmap,
+                                       uint8_t *master_key,
+                                       uint64_t iter_time,
+                                       char *new_secret,
+                                       bool force,
+                                       Error **errp)
+{
+    QCryptoBlockLUKS *luks = block->opaque;
+    g_autofree char *new_password = NULL;
+    size_t i;
+
+    /* safety checks */
+    if (!force) {
+        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+            if (!test_bit(i, slots_bitmap)) {
+                continue;
+            }
+            if (qcrypto_block_luks_slot_active(luks, i)) {
+                error_setg(errp,
+                           "Refusing to overwrite active slot %zu - "
+                           "please erase it first", i);
+                return -EINVAL;
+            }
+        }
+    }
+
+    /* Load the new password */
+    new_password = qcrypto_secret_lookup_as_utf8(new_secret, errp);
+    if (!new_password) {
+        return -EINVAL;
+    }
+
+    /* Apply the update */
+    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
+        if (!test_bit(i, slots_bitmap)) {
+            continue;
+        }
+        if (qcrypto_block_luks_store_key(block, i, new_password, master_key,
+                                         iter_time, writefunc, opaque, errp)) {
+            error_append_hint(errp, "Failed to write to keyslot %zu", i);
+            return -EINVAL;
+        }
+    }
+    return 0;
+}
+
+static int
+qcrypto_block_luks_amend_options(QCryptoBlock *block,
+                                 QCryptoBlockReadFunc readfunc,
+                                 QCryptoBlockWriteFunc writefunc,
+                                 void *opaque,
+                                 QCryptoBlockAmendOptions *options,
+                                 bool force,
+                                 Error **errp)
+{
+    QCryptoBlockLUKS *luks = block->opaque;
+    QCryptoBlockAmendOptionsLUKS *opts_luks = &options->u.luks;
+    g_autofree uint8_t *master_key = NULL;
+    g_autofree unsigned long *update_bitmap = NULL;
+    char *unlock_secret = NULL;
+    long slot_count;
+
+    unlock_secret = opts_luks->has_unlock_secret ? opts_luks->unlock_secret :
+                                                   luks->secret;
+
+    /* Retrieve set of slots that we need to update */
+    update_bitmap = bitmap_new(QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
+    if (qcrypto_block_luks_get_update_bitmap(block, readfunc, opaque, opts_luks,
+                                             update_bitmap, errp) != 0) {
+        return -1;
+    }
+
+    slot_count = bitmap_count_one(update_bitmap,
+                                  QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
+
+    /* no matching slots, so nothing to do */
+    if (slot_count == 0) {
+        error_setg(errp, "Requested operation didn't match any slots");
+        return -1;
+    }
+
+    if (opts_luks->state == LUKS_KEYSLOT_STATE_ACTIVE) {
+
+        uint64_t iter_time = opts_luks->has_iter_time ?
+                             opts_luks->iter_time :
+                             QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS;
+
+        if (!opts_luks->has_new_secret) {
+            error_setg(errp, "'new-secret' is required to activate a keyslot");
+            return -EINVAL;
+        }
+        if (opts_luks->has_old_secret) {
+            error_setg(errp,
+                       "'old-secret' must not be given when activating keyslots");
+            return -EINVAL;
+        }
+
+        /* Locate the password that will be used to retrieve the master key */
+        g_autofree char *old_password;
+        old_password = qcrypto_secret_lookup_as_utf8(unlock_secret,  errp);
+        if (!old_password) {
+            return -EINVAL;
+        }
+
+        /* Try to retrieve the master key */
+        master_key = g_new0(uint8_t, luks->header.master_key_len);
+        if (qcrypto_block_luks_find_key(block, old_password, master_key,
+                                        readfunc, opaque, errp) < 0) {
+            error_append_hint(errp, "Failed to retrieve the master key");
+            return -EINVAL;
+        }
+
+        /* Now set the new keyslots */
+        if (qcrypto_block_luks_set_keys(block, readfunc, writefunc,
+                                        opaque, update_bitmap, master_key,
+                                        iter_time,
+                                        opts_luks->new_secret,
+                                        force, errp) != 0) {
+            return -1;
+        }
+    } else {
+        if (opts_luks->has_new_secret) {
+            error_setg(errp,
+                       "'new-secret' must not be given when erasing keyslots");
+            return -EINVAL;
+        }
+        if (opts_luks->has_iter_time) {
+            error_setg(errp,
+                       "'iter-time' must not be given when erasing keyslots");
+            return -EINVAL;
+        }
+        if (opts_luks->has_unlock_secret) {
+            error_setg(errp,
+                       "'unlock_secret' must not be given when erasing keyslots");
+            return -EINVAL;
+        }
+
+        if (qcrypto_block_luks_erase_keys(block, readfunc, writefunc,
+                                          opaque, update_bitmap, force,
+                                          errp) != 0) {
+            return -1;
+        }
+    }
+    return 0;
+}
 
 static int qcrypto_block_luks_get_info(QCryptoBlock *block,
                                        QCryptoBlockInfo *info,
@@ -1523,7 +1912,11 @@ static int qcrypto_block_luks_get_info(QCryptoBlock *block,
 
 static void qcrypto_block_luks_cleanup(QCryptoBlock *block)
 {
-    g_free(block->opaque);
+    QCryptoBlockLUKS *luks = block->opaque;
+    if (luks) {
+        g_free(luks->secret);
+        g_free(luks);
+    }
 }
 
 
@@ -1560,6 +1953,7 @@ qcrypto_block_luks_encrypt(QCryptoBlock *block,
 const QCryptoBlockDriver qcrypto_block_driver_luks = {
     .open = qcrypto_block_luks_open,
     .create = qcrypto_block_luks_create,
+    .amend = qcrypto_block_luks_amend_options,
     .get_info = qcrypto_block_luks_get_info,
     .cleanup = qcrypto_block_luks_cleanup,
     .decrypt = qcrypto_block_luks_decrypt,
diff --git a/qapi/crypto.json b/qapi/crypto.json
index 3fd0ce177e..fe600fc608 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -1,6 +1,8 @@
 # -*- Mode: Python -*-
 #
 
+{ 'include': 'common.json' }
+
 ##
 # = Cryptography
 ##
@@ -297,7 +299,6 @@
            'uuid': 'str',
            'slots': [ 'QCryptoBlockInfoLUKSSlot' ] }}
 
-
 ##
 # @QCryptoBlockInfo:
 #
@@ -310,7 +311,63 @@
   'discriminator': 'format',
   'data': { 'luks': 'QCryptoBlockInfoLUKS' } }
 
+##
+# @LUKSKeyslotState:
+#
+# Defines state of keyslots that are affected by the update
+#
+# @active:    The slots contain the given password and marked as active
+# @inactive:  The slots are erased (contain garbage) and marked as inactive
+#
+# Since: 5.0
+##
+{ 'enum': 'LUKSKeyslotState',
+  'data': [ 'active', 'inactive' ] }
+
 
+##
+# @QCryptoBlockAmendOptionsLUKS:
+#
+# This struct defines the update parameters that activate/de-activate set
+# of keyslots
+#
+# @state: the desired state of the keyslots
+#
+# @new-secret:    The ID of a QCryptoSecret object providing the password to be
+#                 written into added active keyslots
+#
+# @old-secret:    Optional (for deactivation only)
+#                 If given will deactive all keyslots that
+#                 match password located in QCryptoSecret with this ID
+#
+# @iter-time:     Optional (for activation only)
+#                 Number of milliseconds to spend in
+#                 PBKDF passphrase processing for the newly activated keyslot.
+#                 Currently defaults to 2000.
+#
+# @keyslot:       Optional. ID of the keyslot to activate/deactivate.
+#                 For keyslot activation, keyslot should not be active already
+#                 (this is unsafe to update an active keyslot),
+#                 but possible if 'force' parameter is given.
+#                 If keyslot is not given, first free keyslot will be written.
+#
+#                 For keyslot deactivation, this parameter specifies the exact
+#                 keyslot to deactivate
+#
+# @unlock-secret: Optional. The ID of a QCryptoSecret object providing the
+#                 password to use to retrive current master key.
+#                 Defaults to the same secret that was used to open the image
+#
+#
+# Since 5.0
+##
+{ 'struct': 'QCryptoBlockAmendOptionsLUKS',
+  'data': { 'state': 'LUKSKeyslotState',
+            '*new-secret': 'str',
+            '*old-secret': 'str',
+            '*keyslot': 'int',
+            '*iter-time': 'int',
+            '*unlock-secret': 'str' } }
 
 ##
 # @QCryptoBlockAmendOptions:
@@ -324,4 +381,4 @@
   'base': 'QCryptoBlockOptionsBase',
   'discriminator': 'format',
   'data': {
-            } }
+          'luks': 'QCryptoBlockAmendOptionsLUKS' } }
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 03/14] block/amend: add 'force' option
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
  2020-03-08 15:18 ` [PATCH v2 01/14] qcrypto/core: add generic infrastructure for crypto options amendment Maxim Levitsky
  2020-03-08 15:18 ` [PATCH v2 02/14] qcrypto/luks: implement encryption key management Maxim Levitsky
@ 2020-03-08 15:18 ` Maxim Levitsky
  2020-03-08 15:18 ` [PATCH v2 04/14] block/amend: separate amend and create options for qemu-img Maxim Levitsky
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

'force' option will be used for some unsafe amend operations.

This includes things like erasing last keyslot in luks based formats
(which destroys the data, unless the master key is backed up
by external means), but that _might_ be desired result.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
---
 block.c                   | 4 +++-
 block/qcow2.c             | 1 +
 docs/tools/qemu-img.rst   | 5 ++++-
 include/block/block.h     | 1 +
 include/block/block_int.h | 1 +
 qemu-img-cmds.hx          | 4 ++--
 qemu-img.c                | 8 +++++++-
 7 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/block.c b/block.c
index 1bdb9c679d..3b6f51aa3e 100644
--- a/block.c
+++ b/block.c
@@ -6320,6 +6320,7 @@ void bdrv_remove_aio_context_notifier(BlockDriverState *bs,
 
 int bdrv_amend_options(BlockDriverState *bs, QemuOpts *opts,
                        BlockDriverAmendStatusCB *status_cb, void *cb_opaque,
+                       bool force,
                        Error **errp)
 {
     if (!bs->drv) {
@@ -6331,7 +6332,8 @@ int bdrv_amend_options(BlockDriverState *bs, QemuOpts *opts,
                    bs->drv->format_name);
         return -ENOTSUP;
     }
-    return bs->drv->bdrv_amend_options(bs, opts, status_cb, cb_opaque, errp);
+    return bs->drv->bdrv_amend_options(bs, opts, status_cb,
+                                       cb_opaque, force, errp);
 }
 
 /*
diff --git a/block/qcow2.c b/block/qcow2.c
index 3c754f616b..b55e5b7c1f 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -5145,6 +5145,7 @@ static void qcow2_amend_helper_cb(BlockDriverState *bs,
 static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
                                BlockDriverAmendStatusCB *status_cb,
                                void *cb_opaque,
+                               bool force,
                                Error **errp)
 {
     BDRVQcow2State *s = bs->opaque;
diff --git a/docs/tools/qemu-img.rst b/docs/tools/qemu-img.rst
index 0080f83a76..fc2dca6649 100644
--- a/docs/tools/qemu-img.rst
+++ b/docs/tools/qemu-img.rst
@@ -249,11 +249,14 @@ Command description:
 
 .. program:: qemu-img-commands
 
-.. option:: amend [--object OBJECTDEF] [--image-opts] [-p] [-q] [-f FMT] [-t CACHE] -o OPTIONS FILENAME
+.. option:: amend [--object OBJECTDEF] [--image-opts] [-p] [-q] [-f FMT] [-t CACHE] [--force] -o OPTIONS FILENAME
 
   Amends the image format specific *OPTIONS* for the image file
   *FILENAME*. Not all file formats support this operation.
 
+  --force allows some unsafe operations. Currently for -f luks, it allows to
+  erase last encryption key, and to overwrite an active encryption key.
+
 .. option:: bench [-c COUNT] [-d DEPTH] [-f FMT] [--flush-interval=FLUSH_INTERVAL] [-i AIO] [-n] [--no-drain] [-o OFFSET] [--pattern=PATTERN] [-q] [-s BUFFER_SIZE] [-S STEP_SIZE] [-t CACHE] [-w] [-U] FILENAME
 
   Run a simple sequential I/O benchmark on the specified image. If ``-w`` is
diff --git a/include/block/block.h b/include/block/block.h
index cd6b5b95aa..dda18a3fa3 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -389,6 +389,7 @@ typedef void BlockDriverAmendStatusCB(BlockDriverState *bs, int64_t offset,
                                       int64_t total_work_size, void *opaque);
 int bdrv_amend_options(BlockDriverState *bs_new, QemuOpts *opts,
                        BlockDriverAmendStatusCB *status_cb, void *cb_opaque,
+                       bool force,
                        Error **errp);
 
 /* check if a named node can be replaced when doing drive-mirror */
diff --git a/include/block/block_int.h b/include/block/block_int.h
index 6f9fd5e20e..24d00fbf48 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -426,6 +426,7 @@ struct BlockDriver {
     int (*bdrv_amend_options)(BlockDriverState *bs, QemuOpts *opts,
                               BlockDriverAmendStatusCB *status_cb,
                               void *cb_opaque,
+                              bool force,
                               Error **errp);
 
     void (*bdrv_debug_event)(BlockDriverState *bs, BlkdebugEvent event);
diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx
index c9c54de1df..9920f1f9d4 100644
--- a/qemu-img-cmds.hx
+++ b/qemu-img-cmds.hx
@@ -10,9 +10,9 @@ HXCOMM When amending the rST sections, please remember to copy the usage
 HXCOMM over to the per-command sections in qemu-img.texi.
 
 DEF("amend", img_amend,
-    "amend [--object objectdef] [--image-opts] [-p] [-q] [-f fmt] [-t cache] -o options filename")
+    "amend [--object objectdef] [--image-opts] [-p] [-q] [-f fmt] [-t cache] [--force] -o options filename")
 SRST
-.. option:: amend [--object OBJECTDEF] [--image-opts] [-p] [-q] [-f FMT] [-t CACHE] -o OPTIONS FILENAME
+.. option:: amend [--object OBJECTDEF] [--image-opts] [-p] [-q] [-f FMT] [-t CACHE] [--force] -o OPTIONS FILENAME
 ERST
 
 DEF("bench", img_bench,
diff --git a/qemu-img.c b/qemu-img.c
index 804630a368..551388676f 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -71,6 +71,7 @@ enum {
     OPTION_SHRINK = 266,
     OPTION_SALVAGE = 267,
     OPTION_TARGET_IS_ZERO = 268,
+    OPTION_FORCE = 269,
 };
 
 typedef enum OutputFormat {
@@ -3920,6 +3921,7 @@ static int img_amend(int argc, char **argv)
     BlockBackend *blk = NULL;
     BlockDriverState *bs = NULL;
     bool image_opts = false;
+    bool force = false;
 
     cache = BDRV_DEFAULT_CACHE;
     for (;;) {
@@ -3927,6 +3929,7 @@ static int img_amend(int argc, char **argv)
             {"help", no_argument, 0, 'h'},
             {"object", required_argument, 0, OPTION_OBJECT},
             {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
+            {"force", no_argument, 0, OPTION_FORCE},
             {0, 0, 0, 0}
         };
         c = getopt_long(argc, argv, ":ho:f:t:pq",
@@ -3982,6 +3985,9 @@ static int img_amend(int argc, char **argv)
         case OPTION_IMAGE_OPTS:
             image_opts = true;
             break;
+        case OPTION_FORCE:
+            force = true;
+            break;
         }
     }
 
@@ -4059,7 +4065,7 @@ static int img_amend(int argc, char **argv)
 
     /* In case the driver does not call amend_status_cb() */
     qemu_progress_print(0.f, 0);
-    ret = bdrv_amend_options(bs, opts, &amend_status_cb, NULL, &err);
+    ret = bdrv_amend_options(bs, opts, &amend_status_cb, NULL, force, &err);
     qemu_progress_print(100.f, 0);
     if (ret < 0) {
         error_report_err(err);
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 04/14] block/amend: separate amend and create options for qemu-img
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (2 preceding siblings ...)
  2020-03-08 15:18 ` [PATCH v2 03/14] block/amend: add 'force' option Maxim Levitsky
@ 2020-03-08 15:18 ` Maxim Levitsky
  2020-04-28 15:03   ` Daniel P. Berrangé
  2020-03-08 15:18 ` [PATCH v2 05/14] block/amend: refactor qcow2 amend options Maxim Levitsky
                   ` (10 subsequent siblings)
  14 siblings, 1 reply; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

Some options are only useful for creation
(or hard to be amended, like cluster size for qcow2), while some other
options are only useful for amend, like upcoming keyslot management
options for luks

Since currently only qcow2 supports amend, move all its options
to a common macro and then include it in each action option list.

In future it might be useful to remove some options which are
not supported anyway from amend list, which currently
cause an error message if amended.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/qcow2.c             | 160 +++++++++++++++++++++-----------------
 include/block/block_int.h |   4 +
 qemu-img.c                |  18 ++---
 3 files changed, 100 insertions(+), 82 deletions(-)

diff --git a/block/qcow2.c b/block/qcow2.c
index b55e5b7c1f..9574085772 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -5440,83 +5440,96 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
     s->signaled_corruption = true;
 }
 
+#define QCOW_COMMON_OPTIONS                                         \
+    {                                                               \
+        .name = BLOCK_OPT_SIZE,                                     \
+        .type = QEMU_OPT_SIZE,                                      \
+        .help = "Virtual disk size"                                 \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_COMPAT_LEVEL,                             \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Compatibility level (v2 [0.10] or v3 [1.1])"       \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_BACKING_FILE,                             \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "File name of a base image"                         \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_BACKING_FMT,                              \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Image format of the base image"                    \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_DATA_FILE,                                \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "File name of an external data file"                \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_DATA_FILE_RAW,                            \
+        .type = QEMU_OPT_BOOL,                                      \
+        .help = "The external data file must stay valid "           \
+                "as a raw image"                                    \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_ENCRYPT,                                  \
+        .type = QEMU_OPT_BOOL,                                      \
+        .help = "Encrypt the image with format 'aes'. (Deprecated " \
+                "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",    \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_ENCRYPT_FORMAT,                           \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Encrypt the image, format choices: 'aes', 'luks'", \
+    },                                                              \
+    BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",                     \
+        "ID of secret providing qcow AES key or LUKS passphrase"),  \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),               \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),              \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),                \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),           \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),                 \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),                \
+    {                                                               \
+        .name = BLOCK_OPT_CLUSTER_SIZE,                             \
+        .type = QEMU_OPT_SIZE,                                      \
+        .help = "qcow2 cluster size",                               \
+        .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)            \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_PREALLOC,                                 \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Preallocation mode (allowed values: off, "         \
+                "metadata, falloc, full)"                           \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_LAZY_REFCOUNTS,                           \
+        .type = QEMU_OPT_BOOL,                                      \
+        .help = "Postpone refcount updates",                        \
+        .def_value_str = "off"                                      \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_REFCOUNT_BITS,                            \
+        .type = QEMU_OPT_NUMBER,                                    \
+        .help = "Width of a reference count entry in bits",         \
+        .def_value_str = "16"                                       \
+    }                                                               \
+
 static QemuOptsList qcow2_create_opts = {
     .name = "qcow2-create-opts",
     .head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head),
     .desc = {
-        {
-            .name = BLOCK_OPT_SIZE,
-            .type = QEMU_OPT_SIZE,
-            .help = "Virtual disk size"
-        },
-        {
-            .name = BLOCK_OPT_COMPAT_LEVEL,
-            .type = QEMU_OPT_STRING,
-            .help = "Compatibility level (v2 [0.10] or v3 [1.1])"
-        },
-        {
-            .name = BLOCK_OPT_BACKING_FILE,
-            .type = QEMU_OPT_STRING,
-            .help = "File name of a base image"
-        },
-        {
-            .name = BLOCK_OPT_BACKING_FMT,
-            .type = QEMU_OPT_STRING,
-            .help = "Image format of the base image"
-        },
-        {
-            .name = BLOCK_OPT_DATA_FILE,
-            .type = QEMU_OPT_STRING,
-            .help = "File name of an external data file"
-        },
-        {
-            .name = BLOCK_OPT_DATA_FILE_RAW,
-            .type = QEMU_OPT_BOOL,
-            .help = "The external data file must stay valid as a raw image"
-        },
-        {
-            .name = BLOCK_OPT_ENCRYPT,
-            .type = QEMU_OPT_BOOL,
-            .help = "Encrypt the image with format 'aes'. (Deprecated "
-                    "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",
-        },
-        {
-            .name = BLOCK_OPT_ENCRYPT_FORMAT,
-            .type = QEMU_OPT_STRING,
-            .help = "Encrypt the image, format choices: 'aes', 'luks'",
-        },
-        BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",
-            "ID of secret providing qcow AES key or LUKS passphrase"),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),
-        {
-            .name = BLOCK_OPT_CLUSTER_SIZE,
-            .type = QEMU_OPT_SIZE,
-            .help = "qcow2 cluster size",
-            .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)
-        },
-        {
-            .name = BLOCK_OPT_PREALLOC,
-            .type = QEMU_OPT_STRING,
-            .help = "Preallocation mode (allowed values: off, metadata, "
-                    "falloc, full)"
-        },
-        {
-            .name = BLOCK_OPT_LAZY_REFCOUNTS,
-            .type = QEMU_OPT_BOOL,
-            .help = "Postpone refcount updates",
-            .def_value_str = "off"
-        },
-        {
-            .name = BLOCK_OPT_REFCOUNT_BITS,
-            .type = QEMU_OPT_NUMBER,
-            .help = "Width of a reference count entry in bits",
-            .def_value_str = "16"
-        },
+        QCOW_COMMON_OPTIONS,
+        { /* end of list */ }
+    }
+};
+
+static QemuOptsList qcow2_amend_opts = {
+    .name = "qcow2-amend-opts",
+    .head = QTAILQ_HEAD_INITIALIZER(qcow2_amend_opts.head),
+    .desc = {
+        QCOW_COMMON_OPTIONS,
         { /* end of list */ }
     }
 };
@@ -5576,6 +5589,7 @@ BlockDriver bdrv_qcow2 = {
     .bdrv_inactivate            = qcow2_inactivate,
 
     .create_opts         = &qcow2_create_opts,
+    .amend_opts          = &qcow2_amend_opts,
     .strong_runtime_opts = qcow2_strong_runtime_opts,
     .mutable_opts        = mutable_opts,
     .bdrv_co_check       = qcow2_co_check,
diff --git a/include/block/block_int.h b/include/block/block_int.h
index 24d00fbf48..48a4c2fa1c 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -406,6 +406,10 @@ struct BlockDriver {
 
     /* List of options for creating images, terminated by name == NULL */
     QemuOptsList *create_opts;
+
+    /* List of options for image amend*/
+    QemuOptsList *amend_opts;
+
     /*
      * If this driver supports reopening images this contains a
      * NULL-terminated list of the runtime options that can be
diff --git a/qemu-img.c b/qemu-img.c
index 551388676f..2555aedee9 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -3898,11 +3898,11 @@ static int print_amend_option_help(const char *format)
         return 1;
     }
 
-    /* Every driver supporting amendment must have create_opts */
-    assert(drv->create_opts);
+    /* Every driver supporting amendment must have amend_opts */
+    assert(drv->amend_opts);
 
     printf("Creation options for '%s':\n", format);
-    qemu_opts_print_help(drv->create_opts, false);
+    qemu_opts_print_help(drv->amend_opts, false);
     printf("\nNote that not all of these options may be amendable.\n");
     return 0;
 }
@@ -3912,7 +3912,7 @@ static int img_amend(int argc, char **argv)
     Error *err = NULL;
     int c, ret = 0;
     char *options = NULL;
-    QemuOptsList *create_opts = NULL;
+    QemuOptsList *amend_opts = NULL;
     QemuOpts *opts = NULL;
     const char *fmt = NULL, *filename, *cache;
     int flags;
@@ -4051,11 +4051,11 @@ static int img_amend(int argc, char **argv)
         goto out;
     }
 
-    /* Every driver supporting amendment must have create_opts */
-    assert(bs->drv->create_opts);
+    /* Every driver supporting amendment must have amend_opts */
+    assert(bs->drv->amend_opts);
 
-    create_opts = qemu_opts_append(create_opts, bs->drv->create_opts);
-    opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
+    amend_opts = qemu_opts_append(amend_opts, bs->drv->amend_opts);
+    opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
     qemu_opts_do_parse(opts, options, NULL, &err);
     if (err) {
         error_report_err(err);
@@ -4078,7 +4078,7 @@ out:
 out_no_progress:
     blk_unref(blk);
     qemu_opts_del(opts);
-    qemu_opts_free(create_opts);
+    qemu_opts_free(amend_opts);
     g_free(options);
 
     if (ret) {
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 05/14] block/amend: refactor qcow2 amend options
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (3 preceding siblings ...)
  2020-03-08 15:18 ` [PATCH v2 04/14] block/amend: separate amend and create options for qemu-img Maxim Levitsky
@ 2020-03-08 15:18 ` Maxim Levitsky
  2020-04-28 15:51   ` Daniel P. Berrangé
  2020-03-08 15:18 ` [PATCH v2 06/14] block/crypto: rename two functions Maxim Levitsky
                   ` (9 subsequent siblings)
  14 siblings, 1 reply; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

Some qcow2 create options can't be used for amend.
Remove them from the qcow2 create options and add generic logic to detect
such options in qemu-img

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/qcow2.c              | 108 ++++++---------------
 qemu-img.c                 |  18 +++-
 tests/qemu-iotests/049.out | 102 ++++++++++----------
 tests/qemu-iotests/061.out |  12 ++-
 tests/qemu-iotests/079.out |  18 ++--
 tests/qemu-iotests/082.out | 149 ++++------------------------
 tests/qemu-iotests/085.out |  38 ++++----
 tests/qemu-iotests/087.out |   6 +-
 tests/qemu-iotests/115.out |   2 +-
 tests/qemu-iotests/121.out |   4 +-
 tests/qemu-iotests/125.out | 192 ++++++++++++++++++-------------------
 tests/qemu-iotests/134.out |   2 +-
 tests/qemu-iotests/144.out |   4 +-
 tests/qemu-iotests/158.out |   4 +-
 tests/qemu-iotests/182.out |   2 +-
 tests/qemu-iotests/185.out |   8 +-
 tests/qemu-iotests/188.out |   2 +-
 tests/qemu-iotests/189.out |   4 +-
 tests/qemu-iotests/198.out |   4 +-
 tests/qemu-iotests/243.out |  16 ++--
 tests/qemu-iotests/250.out |   2 +-
 tests/qemu-iotests/255.out |   8 +-
 tests/qemu-iotests/263.out |   4 +-
 tests/qemu-iotests/280.out |   2 +-
 24 files changed, 283 insertions(+), 428 deletions(-)

diff --git a/block/qcow2.c b/block/qcow2.c
index 9574085772..81e7895e7c 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -2946,17 +2946,6 @@ static int qcow2_change_backing_file(BlockDriverState *bs,
     return qcow2_update_header(bs);
 }
 
-static int qcow2_crypt_method_from_format(const char *encryptfmt)
-{
-    if (g_str_equal(encryptfmt, "luks")) {
-        return QCOW_CRYPT_LUKS;
-    } else if (g_str_equal(encryptfmt, "aes")) {
-        return QCOW_CRYPT_AES;
-    } else {
-        return -EINVAL;
-    }
-}
-
 static int qcow2_set_up_encryption(BlockDriverState *bs,
                                    QCryptoBlockCreateOptions *cryptoopts,
                                    Error **errp)
@@ -5155,9 +5144,6 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
     bool lazy_refcounts = s->use_lazy_refcounts;
     bool data_file_raw = data_file_is_raw(bs);
     const char *compat = NULL;
-    uint64_t cluster_size = s->cluster_size;
-    bool encrypt;
-    int encformat;
     int refcount_bits = s->refcount_bits;
     int ret;
     QemuOptDesc *desc = opts->list->desc;
@@ -5182,44 +5168,12 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
                 error_setg(errp, "Unknown compatibility level %s", compat);
                 return -EINVAL;
             }
-        } else if (!strcmp(desc->name, BLOCK_OPT_PREALLOC)) {
-            error_setg(errp, "Cannot change preallocation mode");
-            return -ENOTSUP;
         } else if (!strcmp(desc->name, BLOCK_OPT_SIZE)) {
             new_size = qemu_opt_get_size(opts, BLOCK_OPT_SIZE, 0);
         } else if (!strcmp(desc->name, BLOCK_OPT_BACKING_FILE)) {
             backing_file = qemu_opt_get(opts, BLOCK_OPT_BACKING_FILE);
         } else if (!strcmp(desc->name, BLOCK_OPT_BACKING_FMT)) {
             backing_format = qemu_opt_get(opts, BLOCK_OPT_BACKING_FMT);
-        } else if (!strcmp(desc->name, BLOCK_OPT_ENCRYPT)) {
-            encrypt = qemu_opt_get_bool(opts, BLOCK_OPT_ENCRYPT,
-                                        !!s->crypto);
-
-            if (encrypt != !!s->crypto) {
-                error_setg(errp,
-                           "Changing the encryption flag is not supported");
-                return -ENOTSUP;
-            }
-        } else if (!strcmp(desc->name, BLOCK_OPT_ENCRYPT_FORMAT)) {
-            encformat = qcow2_crypt_method_from_format(
-                qemu_opt_get(opts, BLOCK_OPT_ENCRYPT_FORMAT));
-
-            if (encformat != s->crypt_method_header) {
-                error_setg(errp,
-                           "Changing the encryption format is not supported");
-                return -ENOTSUP;
-            }
-        } else if (g_str_has_prefix(desc->name, "encrypt.")) {
-            error_setg(errp,
-                       "Changing the encryption parameters is not supported");
-            return -ENOTSUP;
-        } else if (!strcmp(desc->name, BLOCK_OPT_CLUSTER_SIZE)) {
-            cluster_size = qemu_opt_get_size(opts, BLOCK_OPT_CLUSTER_SIZE,
-                                             cluster_size);
-            if (cluster_size != s->cluster_size) {
-                error_setg(errp, "Changing the cluster size is not supported");
-                return -ENOTSUP;
-            }
         } else if (!strcmp(desc->name, BLOCK_OPT_LAZY_REFCOUNTS)) {
             lazy_refcounts = qemu_opt_get_bool(opts, BLOCK_OPT_LAZY_REFCOUNTS,
                                                lazy_refcounts);
@@ -5472,37 +5426,6 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
         .help = "The external data file must stay valid "           \
                 "as a raw image"                                    \
     },                                                              \
-    {                                                               \
-        .name = BLOCK_OPT_ENCRYPT,                                  \
-        .type = QEMU_OPT_BOOL,                                      \
-        .help = "Encrypt the image with format 'aes'. (Deprecated " \
-                "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",    \
-    },                                                              \
-    {                                                               \
-        .name = BLOCK_OPT_ENCRYPT_FORMAT,                           \
-        .type = QEMU_OPT_STRING,                                    \
-        .help = "Encrypt the image, format choices: 'aes', 'luks'", \
-    },                                                              \
-    BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",                     \
-        "ID of secret providing qcow AES key or LUKS passphrase"),  \
-    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),               \
-    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),              \
-    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),                \
-    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),           \
-    BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),                 \
-    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),                \
-    {                                                               \
-        .name = BLOCK_OPT_CLUSTER_SIZE,                             \
-        .type = QEMU_OPT_SIZE,                                      \
-        .help = "qcow2 cluster size",                               \
-        .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)            \
-    },                                                              \
-    {                                                               \
-        .name = BLOCK_OPT_PREALLOC,                                 \
-        .type = QEMU_OPT_STRING,                                    \
-        .help = "Preallocation mode (allowed values: off, "         \
-                "metadata, falloc, full)"                           \
-    },                                                              \
     {                                                               \
         .name = BLOCK_OPT_LAZY_REFCOUNTS,                           \
         .type = QEMU_OPT_BOOL,                                      \
@@ -5520,6 +5443,37 @@ static QemuOptsList qcow2_create_opts = {
     .name = "qcow2-create-opts",
     .head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head),
     .desc = {
+        {                                                               \
+            .name = BLOCK_OPT_ENCRYPT,                                  \
+            .type = QEMU_OPT_BOOL,                                      \
+            .help = "Encrypt the image with format 'aes'. (Deprecated " \
+                    "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",    \
+        },                                                              \
+        {                                                               \
+            .name = BLOCK_OPT_ENCRYPT_FORMAT,                           \
+            .type = QEMU_OPT_STRING,                                    \
+            .help = "Encrypt the image, format choices: 'aes', 'luks'", \
+        },                                                              \
+        BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",                     \
+            "ID of secret providing qcow AES key or LUKS passphrase"),  \
+        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),               \
+        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),              \
+        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),                \
+        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),           \
+        BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),                 \
+        BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),                \
+        {                                                               \
+            .name = BLOCK_OPT_CLUSTER_SIZE,                             \
+            .type = QEMU_OPT_SIZE,                                      \
+            .help = "qcow2 cluster size",                               \
+            .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)            \
+        },                                                              \
+        {                                                               \
+            .name = BLOCK_OPT_PREALLOC,                                 \
+            .type = QEMU_OPT_STRING,                                    \
+            .help = "Preallocation mode (allowed values: off, "         \
+                    "metadata, falloc, full)"                           \
+        },                                                              \
         QCOW_COMMON_OPTIONS,
         { /* end of list */ }
     }
diff --git a/qemu-img.c b/qemu-img.c
index 2555aedee9..ddb25fad8f 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -3901,9 +3901,8 @@ static int print_amend_option_help(const char *format)
     /* Every driver supporting amendment must have amend_opts */
     assert(drv->amend_opts);
 
-    printf("Creation options for '%s':\n", format);
+    printf("Amend options for '%s':\n", format);
     qemu_opts_print_help(drv->amend_opts, false);
-    printf("\nNote that not all of these options may be amendable.\n");
     return 0;
 }
 
@@ -4057,7 +4056,22 @@ static int img_amend(int argc, char **argv)
     amend_opts = qemu_opts_append(amend_opts, bs->drv->amend_opts);
     opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
     qemu_opts_do_parse(opts, options, NULL, &err);
+
     if (err) {
+        /* Try to parse options using the create options*/
+        Error *err1 = NULL;
+        amend_opts = qemu_opts_append(amend_opts, bs->drv->create_opts);
+        qemu_opts_del(opts);
+        opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
+        qemu_opts_do_parse(opts, options, NULL, &err1);
+
+        if (!err1) {
+            error_append_hint(&err,
+                              "This option is only supported for image creation\n");
+        } else {
+            error_free(err1);
+        }
+
         error_report_err(err);
         ret = -1;
         goto out;
diff --git a/tests/qemu-iotests/049.out b/tests/qemu-iotests/049.out
index affa55b341..dad3be70d3 100644
--- a/tests/qemu-iotests/049.out
+++ b/tests/qemu-iotests/049.out
@@ -4,90 +4,90 @@ QA output created by 049
 == 1. Traditional size parameter ==
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1024
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1024 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1024b
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1024 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1k
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1024 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1K
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1024 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1048576 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1048576 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1G
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1073741824 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1073741824 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1T
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1099511627776 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1099511627776 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1024.0
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1024 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1024.0b
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1024 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1.5k
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1536 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1536 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1.5K
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1536 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1536 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1.5M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1572864 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1572864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1.5G
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1610612736 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1610612736 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 TEST_DIR/t.qcow2 1.5T
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1649267441664 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1649267441664 lazy_refcounts=off refcount_bits=16
 
 == 2. Specifying size via -o ==
 
 qemu-img create -f qcow2 -o size=1024 TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1024 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o size=1024b TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1024 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o size=1k TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1024 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o size=1K TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1024 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o size=1M TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1048576 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1048576 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o size=1G TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1073741824 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1073741824 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o size=1T TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1099511627776 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1099511627776 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o size=1024.0 TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1024 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o size=1024.0b TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1024 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1024 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o size=1.5k TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1536 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1536 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o size=1.5K TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1536 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1536 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o size=1.5M TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1572864 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1572864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o size=1.5G TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1610612736 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1610612736 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o size=1.5T TEST_DIR/t.qcow2
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=1649267441664 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=1649267441664 lazy_refcounts=off refcount_bits=16
 
 == 3. Invalid sizes ==
 
@@ -129,84 +129,84 @@ qemu-img: TEST_DIR/t.qcow2: The image size must be specified only once
 == Check correct interpretation of suffixes for cluster size ==
 
 qemu-img create -f qcow2 -o cluster_size=1024 TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=1024 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=1024 size=67108864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o cluster_size=1024b TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=1024 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=1024 size=67108864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o cluster_size=1k TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=1024 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=1024 size=67108864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o cluster_size=1K TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=1024 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=1024 size=67108864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o cluster_size=1M TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=1048576 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=1048576 size=67108864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o cluster_size=1024.0 TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=1024 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=1024 size=67108864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o cluster_size=1024.0b TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=1024 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=1024 size=67108864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o cluster_size=0.5k TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=512 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=512 size=67108864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o cluster_size=0.5K TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=512 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=512 size=67108864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o cluster_size=0.5M TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=524288 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=524288 size=67108864 lazy_refcounts=off refcount_bits=16
 
 == Check compat level option ==
 
 qemu-img create -f qcow2 -o compat=0.10 TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat=0.10 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=67108864 compat=0.10 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o compat=1.1 TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat=1.1 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=67108864 compat=1.1 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o compat=0.42 TEST_DIR/t.qcow2 64M
 qemu-img: TEST_DIR/t.qcow2: Invalid parameter '0.42'
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat=0.42 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=67108864 compat=0.42 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o compat=foobar TEST_DIR/t.qcow2 64M
 qemu-img: TEST_DIR/t.qcow2: Invalid parameter 'foobar'
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat=foobar cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=67108864 compat=foobar lazy_refcounts=off refcount_bits=16
 
 == Check preallocation option ==
 
 qemu-img create -f qcow2 -o preallocation=off TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=65536 preallocation=off lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 preallocation=off size=67108864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o preallocation=metadata TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=65536 preallocation=metadata lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 preallocation=metadata size=67108864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o preallocation=1234 TEST_DIR/t.qcow2 64M
 qemu-img: TEST_DIR/t.qcow2: Invalid parameter '1234'
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 cluster_size=65536 preallocation=1234 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 preallocation=1234 size=67108864 lazy_refcounts=off refcount_bits=16
 
 == Check encryption option ==
 
 qemu-img create -f qcow2 -o encryption=off TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 encryption=off cluster_size=65536 size=67108864 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 --object secret,id=sec0,data=123456 -o encryption=on,encrypt.key-secret=sec0 TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=on encrypt.key-secret=sec0 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 encryption=on encrypt.key-secret=sec0 cluster_size=65536 size=67108864 lazy_refcounts=off refcount_bits=16
 
 == Check lazy_refcounts option (only with v3) ==
 
 qemu-img create -f qcow2 -o compat=1.1,lazy_refcounts=off TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat=1.1 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=67108864 compat=1.1 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o compat=1.1,lazy_refcounts=on TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat=1.1 cluster_size=65536 lazy_refcounts=on refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=67108864 compat=1.1 lazy_refcounts=on refcount_bits=16
 
 qemu-img create -f qcow2 -o compat=0.10,lazy_refcounts=off TEST_DIR/t.qcow2 64M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat=0.10 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=67108864 compat=0.10 lazy_refcounts=off refcount_bits=16
 
 qemu-img create -f qcow2 -o compat=0.10,lazy_refcounts=on TEST_DIR/t.qcow2 64M
 qemu-img: TEST_DIR/t.qcow2: Lazy refcounts only supported with compatibility level 1.1 and above (use version=v3 or greater)
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 compat=0.10 cluster_size=65536 lazy_refcounts=on refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=67108864 compat=0.10 lazy_refcounts=on refcount_bits=16
 
 *** done
diff --git a/tests/qemu-iotests/061.out b/tests/qemu-iotests/061.out
index 8b3091a412..2ce6bcff06 100644
--- a/tests/qemu-iotests/061.out
+++ b/tests/qemu-iotests/061.out
@@ -353,16 +353,20 @@ qemu-img: Lazy refcounts only supported with compatibility level 1.1 and above (
 qemu-img: Lazy refcounts only supported with compatibility level 1.1 and above (use compat=1.1 or greater)
 qemu-img: Unknown compatibility level 0.42
 qemu-img: Invalid parameter 'foo'
-qemu-img: Changing the cluster size is not supported
-qemu-img: Changing the encryption flag is not supported
-qemu-img: Cannot change preallocation mode
+qemu-img: Invalid parameter 'cluster_size'
+This option is only supported for image creation
+qemu-img: Invalid parameter 'encryption'
+This option is only supported for image creation
+qemu-img: Invalid parameter 'preallocation'
+This option is only supported for image creation
 
 === Testing correct handling of unset value ===
 
 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
 Should work:
 Should not work:
-qemu-img: Changing the cluster size is not supported
+qemu-img: Invalid parameter 'cluster_size'
+This option is only supported for image creation
 
 === Testing zero expansion on inactive clusters ===
 
diff --git a/tests/qemu-iotests/079.out b/tests/qemu-iotests/079.out
index aab922fb36..c7e37152f4 100644
--- a/tests/qemu-iotests/079.out
+++ b/tests/qemu-iotests/079.out
@@ -1,14 +1,14 @@
 QA output created by 079
 === Check option preallocation and cluster_size ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4294967296 preallocation=metadata
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4294967296 preallocation=metadata
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4294967296 preallocation=metadata
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4294967296 preallocation=metadata
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4294967296 preallocation=metadata
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4294967296 preallocation=metadata
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4294967296 preallocation=metadata
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4294967296 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=4294967296
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=4294967296
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=4294967296
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=4294967296
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=4294967296
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=4294967296
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=4294967296
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=4294967296
 qemu-img: TEST_DIR/t.IMGFMT: Cluster size must be a power of two between 512 and 2048k
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4294967296 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=4294967296
 *** done
diff --git a/tests/qemu-iotests/082.out b/tests/qemu-iotests/082.out
index 9d4ed4dc9d..c68458da8c 100644
--- a/tests/qemu-iotests/082.out
+++ b/tests/qemu-iotests/082.out
@@ -3,14 +3,14 @@ QA output created by 082
 === create: Options specified more than once ===
 
 Testing: create -f foo -f qcow2 TEST_DIR/t.qcow2 128M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=134217728 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 lazy_refcounts=off refcount_bits=16
 image: TEST_DIR/t.IMGFMT
 file format: IMGFMT
 virtual size: 128 MiB (134217728 bytes)
 cluster_size: 65536
 
 Testing: create -f qcow2 -o cluster_size=4k -o lazy_refcounts=on TEST_DIR/t.qcow2 128M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=134217728 cluster_size=4096 lazy_refcounts=on refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=4096 size=134217728 lazy_refcounts=on refcount_bits=16
 image: TEST_DIR/t.IMGFMT
 file format: IMGFMT
 virtual size: 128 MiB (134217728 bytes)
@@ -22,7 +22,7 @@ Format specific information:
     corrupt: false
 
 Testing: create -f qcow2 -o cluster_size=4k -o lazy_refcounts=on -o cluster_size=8k TEST_DIR/t.qcow2 128M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=134217728 cluster_size=8192 lazy_refcounts=on refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=8192 size=134217728 lazy_refcounts=on refcount_bits=16
 image: TEST_DIR/t.IMGFMT
 file format: IMGFMT
 virtual size: 128 MiB (134217728 bytes)
@@ -34,7 +34,7 @@ Format specific information:
     corrupt: false
 
 Testing: create -f qcow2 -o cluster_size=4k,cluster_size=8k TEST_DIR/t.qcow2 128M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=134217728 cluster_size=8192 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=8192 size=134217728 lazy_refcounts=off refcount_bits=16
 image: TEST_DIR/t.IMGFMT
 file format: IMGFMT
 virtual size: 128 MiB (134217728 bytes)
@@ -227,10 +227,10 @@ Supported options:
   size=<size>            - Virtual disk size
 
 Testing: create -f qcow2 -u -o backing_file=TEST_DIR/t.qcow2,,help TEST_DIR/t.qcow2 128M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/t.qcow2,,help cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/t.qcow2,,help lazy_refcounts=off refcount_bits=16
 
 Testing: create -f qcow2 -u -o backing_file=TEST_DIR/t.qcow2,,? TEST_DIR/t.qcow2 128M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/t.qcow2,,? cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/t.qcow2,,? lazy_refcounts=off refcount_bits=16
 
 Testing: create -f qcow2 -o backing_file=TEST_DIR/t.qcow2, -o help TEST_DIR/t.qcow2 128M
 qemu-img: Invalid option list: backing_file=TEST_DIR/t.qcow2,
@@ -279,7 +279,7 @@ qemu-img: Format driver 'bochs' does not support image creation
 === convert: Options specified more than once ===
 
 Testing: create -f qcow2 TEST_DIR/t.qcow2 128M
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=134217728 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 lazy_refcounts=off refcount_bits=16
 
 Testing: convert -f foo -f qcow2 TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base
 image: TEST_DIR/t.IMGFMT.base
@@ -614,197 +614,93 @@ cluster_size: 65536
 === amend: help for -o ===
 
 Testing: amend -f qcow2 -o help TEST_DIR/t.qcow2
-Creation options for 'qcow2':
+Amend options for 'qcow2':
   backing_file=<str>     - File name of a base image
   backing_fmt=<str>      - Image format of the base image
-  cluster_size=<size>    - qcow2 cluster size
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
-  encrypt.cipher-alg=<str> - Name of encryption cipher algorithm
-  encrypt.cipher-mode=<str> - Name of encryption cipher mode
-  encrypt.format=<str>   - Encrypt the image, format choices: 'aes', 'luks'
-  encrypt.hash-alg=<str> - Name of encryption hash algorithm
-  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
-  encrypt.ivgen-alg=<str> - Name of IV generator algorithm
-  encrypt.ivgen-hash-alg=<str> - Name of IV generator hash algorithm
-  encrypt.key-secret=<str> - ID of secret providing qcow AES key or LUKS passphrase
-  encryption=<bool (on/off)> - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
-  preallocation=<str>    - Preallocation mode (allowed values: off, metadata, falloc, full)
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
 
-Note that not all of these options may be amendable.
-
 Testing: amend -f qcow2 -o ? TEST_DIR/t.qcow2
-Creation options for 'qcow2':
+Amend options for 'qcow2':
   backing_file=<str>     - File name of a base image
   backing_fmt=<str>      - Image format of the base image
-  cluster_size=<size>    - qcow2 cluster size
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
-  encrypt.cipher-alg=<str> - Name of encryption cipher algorithm
-  encrypt.cipher-mode=<str> - Name of encryption cipher mode
-  encrypt.format=<str>   - Encrypt the image, format choices: 'aes', 'luks'
-  encrypt.hash-alg=<str> - Name of encryption hash algorithm
-  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
-  encrypt.ivgen-alg=<str> - Name of IV generator algorithm
-  encrypt.ivgen-hash-alg=<str> - Name of IV generator hash algorithm
-  encrypt.key-secret=<str> - ID of secret providing qcow AES key or LUKS passphrase
-  encryption=<bool (on/off)> - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
-  preallocation=<str>    - Preallocation mode (allowed values: off, metadata, falloc, full)
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
 
-Note that not all of these options may be amendable.
-
 Testing: amend -f qcow2 -o cluster_size=4k,help TEST_DIR/t.qcow2
-Creation options for 'qcow2':
+Amend options for 'qcow2':
   backing_file=<str>     - File name of a base image
   backing_fmt=<str>      - Image format of the base image
-  cluster_size=<size>    - qcow2 cluster size
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
-  encrypt.cipher-alg=<str> - Name of encryption cipher algorithm
-  encrypt.cipher-mode=<str> - Name of encryption cipher mode
-  encrypt.format=<str>   - Encrypt the image, format choices: 'aes', 'luks'
-  encrypt.hash-alg=<str> - Name of encryption hash algorithm
-  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
-  encrypt.ivgen-alg=<str> - Name of IV generator algorithm
-  encrypt.ivgen-hash-alg=<str> - Name of IV generator hash algorithm
-  encrypt.key-secret=<str> - ID of secret providing qcow AES key or LUKS passphrase
-  encryption=<bool (on/off)> - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
-  preallocation=<str>    - Preallocation mode (allowed values: off, metadata, falloc, full)
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
 
-Note that not all of these options may be amendable.
-
 Testing: amend -f qcow2 -o cluster_size=4k,? TEST_DIR/t.qcow2
-Creation options for 'qcow2':
+Amend options for 'qcow2':
   backing_file=<str>     - File name of a base image
   backing_fmt=<str>      - Image format of the base image
-  cluster_size=<size>    - qcow2 cluster size
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
-  encrypt.cipher-alg=<str> - Name of encryption cipher algorithm
-  encrypt.cipher-mode=<str> - Name of encryption cipher mode
-  encrypt.format=<str>   - Encrypt the image, format choices: 'aes', 'luks'
-  encrypt.hash-alg=<str> - Name of encryption hash algorithm
-  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
-  encrypt.ivgen-alg=<str> - Name of IV generator algorithm
-  encrypt.ivgen-hash-alg=<str> - Name of IV generator hash algorithm
-  encrypt.key-secret=<str> - ID of secret providing qcow AES key or LUKS passphrase
-  encryption=<bool (on/off)> - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
-  preallocation=<str>    - Preallocation mode (allowed values: off, metadata, falloc, full)
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
 
-Note that not all of these options may be amendable.
-
 Testing: amend -f qcow2 -o help,cluster_size=4k TEST_DIR/t.qcow2
-Creation options for 'qcow2':
+Amend options for 'qcow2':
   backing_file=<str>     - File name of a base image
   backing_fmt=<str>      - Image format of the base image
-  cluster_size=<size>    - qcow2 cluster size
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
-  encrypt.cipher-alg=<str> - Name of encryption cipher algorithm
-  encrypt.cipher-mode=<str> - Name of encryption cipher mode
-  encrypt.format=<str>   - Encrypt the image, format choices: 'aes', 'luks'
-  encrypt.hash-alg=<str> - Name of encryption hash algorithm
-  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
-  encrypt.ivgen-alg=<str> - Name of IV generator algorithm
-  encrypt.ivgen-hash-alg=<str> - Name of IV generator hash algorithm
-  encrypt.key-secret=<str> - ID of secret providing qcow AES key or LUKS passphrase
-  encryption=<bool (on/off)> - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
-  preallocation=<str>    - Preallocation mode (allowed values: off, metadata, falloc, full)
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
 
-Note that not all of these options may be amendable.
-
 Testing: amend -f qcow2 -o ?,cluster_size=4k TEST_DIR/t.qcow2
-Creation options for 'qcow2':
+Amend options for 'qcow2':
   backing_file=<str>     - File name of a base image
   backing_fmt=<str>      - Image format of the base image
-  cluster_size=<size>    - qcow2 cluster size
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
-  encrypt.cipher-alg=<str> - Name of encryption cipher algorithm
-  encrypt.cipher-mode=<str> - Name of encryption cipher mode
-  encrypt.format=<str>   - Encrypt the image, format choices: 'aes', 'luks'
-  encrypt.hash-alg=<str> - Name of encryption hash algorithm
-  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
-  encrypt.ivgen-alg=<str> - Name of IV generator algorithm
-  encrypt.ivgen-hash-alg=<str> - Name of IV generator hash algorithm
-  encrypt.key-secret=<str> - ID of secret providing qcow AES key or LUKS passphrase
-  encryption=<bool (on/off)> - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
-  preallocation=<str>    - Preallocation mode (allowed values: off, metadata, falloc, full)
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
 
-Note that not all of these options may be amendable.
-
 Testing: amend -f qcow2 -o cluster_size=4k -o help TEST_DIR/t.qcow2
-Creation options for 'qcow2':
+Amend options for 'qcow2':
   backing_file=<str>     - File name of a base image
   backing_fmt=<str>      - Image format of the base image
-  cluster_size=<size>    - qcow2 cluster size
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
-  encrypt.cipher-alg=<str> - Name of encryption cipher algorithm
-  encrypt.cipher-mode=<str> - Name of encryption cipher mode
-  encrypt.format=<str>   - Encrypt the image, format choices: 'aes', 'luks'
-  encrypt.hash-alg=<str> - Name of encryption hash algorithm
-  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
-  encrypt.ivgen-alg=<str> - Name of IV generator algorithm
-  encrypt.ivgen-hash-alg=<str> - Name of IV generator hash algorithm
-  encrypt.key-secret=<str> - ID of secret providing qcow AES key or LUKS passphrase
-  encryption=<bool (on/off)> - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
-  preallocation=<str>    - Preallocation mode (allowed values: off, metadata, falloc, full)
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
 
-Note that not all of these options may be amendable.
-
 Testing: amend -f qcow2 -o cluster_size=4k -o ? TEST_DIR/t.qcow2
-Creation options for 'qcow2':
+Amend options for 'qcow2':
   backing_file=<str>     - File name of a base image
   backing_fmt=<str>      - Image format of the base image
-  cluster_size=<size>    - qcow2 cluster size
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
-  encrypt.cipher-alg=<str> - Name of encryption cipher algorithm
-  encrypt.cipher-mode=<str> - Name of encryption cipher mode
-  encrypt.format=<str>   - Encrypt the image, format choices: 'aes', 'luks'
-  encrypt.hash-alg=<str> - Name of encryption hash algorithm
-  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
-  encrypt.ivgen-alg=<str> - Name of IV generator algorithm
-  encrypt.ivgen-hash-alg=<str> - Name of IV generator hash algorithm
-  encrypt.key-secret=<str> - ID of secret providing qcow AES key or LUKS passphrase
-  encryption=<bool (on/off)> - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
-  preallocation=<str>    - Preallocation mode (allowed values: off, metadata, falloc, full)
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
 
-Note that not all of these options may be amendable.
-
 Testing: amend -f qcow2 -o backing_file=TEST_DIR/t.qcow2,,help TEST_DIR/t.qcow2
 
 Testing: rebase -u -b  -f qcow2 TEST_DIR/t.qcow2
@@ -823,29 +719,16 @@ Testing: amend -f qcow2 -o backing_file=TEST_DIR/t.qcow2 -o ,, -o help TEST_DIR/
 qemu-img: Invalid option list: ,,
 
 Testing: amend -f qcow2 -o help
-Creation options for 'qcow2':
+Amend options for 'qcow2':
   backing_file=<str>     - File name of a base image
   backing_fmt=<str>      - Image format of the base image
-  cluster_size=<size>    - qcow2 cluster size
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
-  encrypt.cipher-alg=<str> - Name of encryption cipher algorithm
-  encrypt.cipher-mode=<str> - Name of encryption cipher mode
-  encrypt.format=<str>   - Encrypt the image, format choices: 'aes', 'luks'
-  encrypt.hash-alg=<str> - Name of encryption hash algorithm
-  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
-  encrypt.ivgen-alg=<str> - Name of IV generator algorithm
-  encrypt.ivgen-hash-alg=<str> - Name of IV generator hash algorithm
-  encrypt.key-secret=<str> - ID of secret providing qcow AES key or LUKS passphrase
-  encryption=<bool (on/off)> - Encrypt the image with format 'aes'. (Deprecated in favor of encrypt.format=aes)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
-  preallocation=<str>    - Preallocation mode (allowed values: off, metadata, falloc, full)
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
 
-Note that not all of these options may be amendable.
-
 Testing: amend -o help
 qemu-img: Expecting one image file name
 
diff --git a/tests/qemu-iotests/085.out b/tests/qemu-iotests/085.out
index d94ad22f70..eade125a33 100644
--- a/tests/qemu-iotests/085.out
+++ b/tests/qemu-iotests/085.out
@@ -13,7 +13,7 @@ Formatting 'TEST_DIR/t.IMGFMT.2', fmt=IMGFMT size=134217728
 === Create a single snapshot on virtio0 ===
 
 { 'execute': 'blockdev-snapshot-sync', 'arguments': { 'device': 'virtio0', 'snapshot-file':'TEST_DIR/1-snapshot-v0.IMGFMT', 'format': 'IMGFMT' } }
-Formatting 'TEST_DIR/1-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/t.qcow2.1 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/1-snapshot-v0.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/t.qcow2.1 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 
 === Invalid command - missing device and nodename ===
@@ -30,40 +30,40 @@ Formatting 'TEST_DIR/1-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file
 === Create several transactional group snapshots ===
 
 { 'execute': 'transaction', 'arguments': {'actions': [ { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio0', 'snapshot-file': 'TEST_DIR/2-snapshot-v0.IMGFMT' } }, { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio1', 'snapshot-file': 'TEST_DIR/2-snapshot-v1.IMGFMT' } } ] } }
-Formatting 'TEST_DIR/2-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/1-snapshot-v0.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
-Formatting 'TEST_DIR/2-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/t.qcow2.2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/2-snapshot-v0.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/1-snapshot-v0.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/2-snapshot-v1.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/t.qcow2.2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 { 'execute': 'transaction', 'arguments': {'actions': [ { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio0', 'snapshot-file': 'TEST_DIR/3-snapshot-v0.IMGFMT' } }, { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio1', 'snapshot-file': 'TEST_DIR/3-snapshot-v1.IMGFMT' } } ] } }
-Formatting 'TEST_DIR/3-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/2-snapshot-v0.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
-Formatting 'TEST_DIR/3-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/2-snapshot-v1.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/3-snapshot-v0.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/2-snapshot-v0.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/3-snapshot-v1.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/2-snapshot-v1.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 { 'execute': 'transaction', 'arguments': {'actions': [ { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio0', 'snapshot-file': 'TEST_DIR/4-snapshot-v0.IMGFMT' } }, { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio1', 'snapshot-file': 'TEST_DIR/4-snapshot-v1.IMGFMT' } } ] } }
-Formatting 'TEST_DIR/4-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/3-snapshot-v0.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
-Formatting 'TEST_DIR/4-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/3-snapshot-v1.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/4-snapshot-v0.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/3-snapshot-v0.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/4-snapshot-v1.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/3-snapshot-v1.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 { 'execute': 'transaction', 'arguments': {'actions': [ { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio0', 'snapshot-file': 'TEST_DIR/5-snapshot-v0.IMGFMT' } }, { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio1', 'snapshot-file': 'TEST_DIR/5-snapshot-v1.IMGFMT' } } ] } }
-Formatting 'TEST_DIR/5-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/4-snapshot-v0.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
-Formatting 'TEST_DIR/5-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/4-snapshot-v1.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/5-snapshot-v0.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/4-snapshot-v0.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/5-snapshot-v1.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/4-snapshot-v1.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 { 'execute': 'transaction', 'arguments': {'actions': [ { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio0', 'snapshot-file': 'TEST_DIR/6-snapshot-v0.IMGFMT' } }, { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio1', 'snapshot-file': 'TEST_DIR/6-snapshot-v1.IMGFMT' } } ] } }
-Formatting 'TEST_DIR/6-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/5-snapshot-v0.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
-Formatting 'TEST_DIR/6-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/5-snapshot-v1.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/6-snapshot-v0.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/5-snapshot-v0.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/6-snapshot-v1.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/5-snapshot-v1.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 { 'execute': 'transaction', 'arguments': {'actions': [ { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio0', 'snapshot-file': 'TEST_DIR/7-snapshot-v0.IMGFMT' } }, { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio1', 'snapshot-file': 'TEST_DIR/7-snapshot-v1.IMGFMT' } } ] } }
-Formatting 'TEST_DIR/7-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/6-snapshot-v0.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
-Formatting 'TEST_DIR/7-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/6-snapshot-v1.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/7-snapshot-v0.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/6-snapshot-v0.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/7-snapshot-v1.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/6-snapshot-v1.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 { 'execute': 'transaction', 'arguments': {'actions': [ { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio0', 'snapshot-file': 'TEST_DIR/8-snapshot-v0.IMGFMT' } }, { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio1', 'snapshot-file': 'TEST_DIR/8-snapshot-v1.IMGFMT' } } ] } }
-Formatting 'TEST_DIR/8-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/7-snapshot-v0.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
-Formatting 'TEST_DIR/8-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/7-snapshot-v1.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/8-snapshot-v0.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/7-snapshot-v0.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/8-snapshot-v1.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/7-snapshot-v1.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 { 'execute': 'transaction', 'arguments': {'actions': [ { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio0', 'snapshot-file': 'TEST_DIR/9-snapshot-v0.IMGFMT' } }, { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio1', 'snapshot-file': 'TEST_DIR/9-snapshot-v1.IMGFMT' } } ] } }
-Formatting 'TEST_DIR/9-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/8-snapshot-v0.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
-Formatting 'TEST_DIR/9-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/8-snapshot-v1.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/9-snapshot-v0.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/8-snapshot-v0.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/9-snapshot-v1.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/8-snapshot-v1.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 { 'execute': 'transaction', 'arguments': {'actions': [ { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio0', 'snapshot-file': 'TEST_DIR/10-snapshot-v0.IMGFMT' } }, { 'type': 'blockdev-snapshot-sync', 'data' : { 'device': 'virtio1', 'snapshot-file': 'TEST_DIR/10-snapshot-v1.IMGFMT' } } ] } }
-Formatting 'TEST_DIR/10-snapshot-v0.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/9-snapshot-v0.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
-Formatting 'TEST_DIR/10-snapshot-v1.qcow2', fmt=qcow2 size=134217728 backing_file=TEST_DIR/9-snapshot-v1.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/10-snapshot-v0.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/9-snapshot-v0.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/10-snapshot-v1.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 backing_file=TEST_DIR/9-snapshot-v1.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 
 === Create a couple of snapshots using blockdev-snapshot ===
diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out
index 2d92ea847b..f23bffbbf1 100644
--- a/tests/qemu-iotests/087.out
+++ b/tests/qemu-iotests/087.out
@@ -34,7 +34,7 @@ QMP_VERSION
 
 === Encrypted image QCow ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encryption=on encrypt.key-secret=sec0 size=134217728
 Testing:
 QMP_VERSION
 {"return": {}}
@@ -46,7 +46,7 @@ QMP_VERSION
 
 === Encrypted image LUKS ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encrypt.format=luks encrypt.key-secret=sec0
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec0 size=134217728
 Testing:
 QMP_VERSION
 {"return": {}}
@@ -58,7 +58,7 @@ QMP_VERSION
 
 === Missing driver ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encryption=on encrypt.key-secret=sec0 size=134217728
 Testing: -S
 QMP_VERSION
 {"return": {}}
diff --git a/tests/qemu-iotests/115.out b/tests/qemu-iotests/115.out
index dbdad17b27..074b5cc84d 100644
--- a/tests/qemu-iotests/115.out
+++ b/tests/qemu-iotests/115.out
@@ -2,7 +2,7 @@ QA output created by 115
 
 === Testing large refcount and L1 table ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=268435456 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=268435456
 No errors were found on the image.
 100.00% allocated clusters
 *** done
diff --git a/tests/qemu-iotests/121.out b/tests/qemu-iotests/121.out
index 613d56185e..fbe4b575f7 100644
--- a/tests/qemu-iotests/121.out
+++ b/tests/qemu-iotests/121.out
@@ -4,7 +4,7 @@ QA output created by 121
 
 --- Test 1 ---
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=66060288 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=66060288
 Image resized.
 wrote 1049600/1049600 bytes at offset 65011712
 1.001 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -14,7 +14,7 @@ No errors were found on the image.
 
 --- Test 2 ---
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=66061312 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=66061312
 Image resized.
 wrote 133120/133120 bytes at offset 66060288
 130 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
diff --git a/tests/qemu-iotests/125.out b/tests/qemu-iotests/125.out
index 596905f533..767c52d622 100644
--- a/tests/qemu-iotests/125.out
+++ b/tests/qemu-iotests/125.out
@@ -1,6 +1,6 @@
 QA output created by 125
 --- cluster_size=512 growth_size=16 create_mode=off growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -8,7 +8,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=off growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -16,7 +16,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=off growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -24,7 +24,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=off growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -32,7 +32,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=metadata growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -40,7 +40,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=metadata growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -48,7 +48,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=metadata growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -56,7 +56,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=metadata growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -64,7 +64,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=falloc growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -72,7 +72,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=falloc growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -80,7 +80,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=falloc growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -88,7 +88,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=falloc growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -96,7 +96,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=full growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -104,7 +104,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=full growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -112,7 +112,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=full growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -120,7 +120,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=16 create_mode=full growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -128,7 +128,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=off growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -136,7 +136,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=off growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -144,7 +144,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=off growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -152,7 +152,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=off growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -160,7 +160,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=metadata growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -168,7 +168,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=metadata growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -176,7 +176,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=metadata growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -184,7 +184,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=metadata growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -192,7 +192,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=falloc growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -200,7 +200,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=falloc growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -208,7 +208,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=falloc growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -216,7 +216,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=falloc growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -224,7 +224,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=full growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -232,7 +232,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=full growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -240,7 +240,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=full growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -248,7 +248,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=48 create_mode=full growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -256,7 +256,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=off growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -264,7 +264,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=off growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -272,7 +272,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=off growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -280,7 +280,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=off growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -288,7 +288,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=metadata growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -296,7 +296,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=metadata growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -304,7 +304,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=metadata growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -312,7 +312,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=metadata growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -320,7 +320,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=falloc growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -328,7 +328,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=falloc growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -336,7 +336,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=falloc growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -344,7 +344,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=falloc growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -352,7 +352,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=full growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -360,7 +360,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=full growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -368,7 +368,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=full growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -376,7 +376,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=512 growth_size=80 create_mode=full growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -384,7 +384,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=off growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -392,7 +392,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=off growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -400,7 +400,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=off growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -408,7 +408,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=off growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -416,7 +416,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=metadata growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -424,7 +424,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=metadata growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -432,7 +432,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=metadata growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -440,7 +440,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=metadata growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -448,7 +448,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=falloc growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -456,7 +456,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=falloc growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -464,7 +464,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=falloc growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -472,7 +472,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=falloc growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -480,7 +480,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=full growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -488,7 +488,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=full growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -496,7 +496,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=full growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -504,7 +504,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=16 create_mode=full growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -512,7 +512,7 @@ wrote 16384/16384 bytes at offset 2048000
 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=off growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -520,7 +520,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=off growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -528,7 +528,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=off growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -536,7 +536,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=off growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -544,7 +544,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=metadata growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -552,7 +552,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=metadata growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -560,7 +560,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=metadata growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -568,7 +568,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=metadata growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -576,7 +576,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=falloc growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -584,7 +584,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=falloc growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -592,7 +592,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=falloc growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -600,7 +600,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=falloc growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -608,7 +608,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=full growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -616,7 +616,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=full growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -624,7 +624,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=full growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -632,7 +632,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=48 create_mode=full growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -640,7 +640,7 @@ wrote 49152/49152 bytes at offset 2048000
 48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=off growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -648,7 +648,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=off growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -656,7 +656,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=off growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -664,7 +664,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=off growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -672,7 +672,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=metadata growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -680,7 +680,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=metadata growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -688,7 +688,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=metadata growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -696,7 +696,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=metadata growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -704,7 +704,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=falloc growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -712,7 +712,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=falloc growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -720,7 +720,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=falloc growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -728,7 +728,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=falloc growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -736,7 +736,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=full growth_mode=off ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -744,7 +744,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=full growth_mode=metadata ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -752,7 +752,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=full growth_mode=falloc ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -760,7 +760,7 @@ wrote 81920/81920 bytes at offset 2048000
 80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 
 --- cluster_size=64k growth_size=80 create_mode=full growth_mode=full ---
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2048000 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=2048000
 Image resized.
 wrote 2048000/2048000 bytes at offset 0
 1.953 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
diff --git a/tests/qemu-iotests/134.out b/tests/qemu-iotests/134.out
index 09d46f6b17..f2878f5f3a 100644
--- a/tests/qemu-iotests/134.out
+++ b/tests/qemu-iotests/134.out
@@ -1,5 +1,5 @@
 QA output created by 134
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encryption=on encrypt.key-secret=sec0 size=134217728
 
 == reading whole image ==
 read 134217728/134217728 bytes at offset 0
diff --git a/tests/qemu-iotests/144.out b/tests/qemu-iotests/144.out
index c7aa2e4820..3900a46ed0 100644
--- a/tests/qemu-iotests/144.out
+++ b/tests/qemu-iotests/144.out
@@ -9,7 +9,7 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=536870912
 { 'execute': 'qmp_capabilities' }
 {"return": {}}
 { 'execute': 'blockdev-snapshot-sync', 'arguments': { 'device': 'virtio0', 'snapshot-file':'TEST_DIR/tmp.IMGFMT', 'format': 'IMGFMT' } }
-Formatting 'TEST_DIR/tmp.qcow2', fmt=qcow2 size=536870912 backing_file=TEST_DIR/t.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/tmp.qcow2', fmt=qcow2 cluster_size=65536 size=536870912 backing_file=TEST_DIR/t.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 
 === Performing block-commit on active layer ===
@@ -31,6 +31,6 @@ Formatting 'TEST_DIR/tmp.qcow2', fmt=qcow2 size=536870912 backing_file=TEST_DIR/
 === Performing Live Snapshot 2 ===
 
 { 'execute': 'blockdev-snapshot-sync', 'arguments': { 'device': 'virtio0', 'snapshot-file':'TEST_DIR/tmp2.IMGFMT', 'format': 'IMGFMT' } }
-Formatting 'TEST_DIR/tmp2.qcow2', fmt=qcow2 size=536870912 backing_file=TEST_DIR/t.qcow2 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/tmp2.qcow2', fmt=qcow2 cluster_size=65536 size=536870912 backing_file=TEST_DIR/t.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 *** done
diff --git a/tests/qemu-iotests/158.out b/tests/qemu-iotests/158.out
index 6def216e55..fa2294bb85 100644
--- a/tests/qemu-iotests/158.out
+++ b/tests/qemu-iotests/158.out
@@ -1,6 +1,6 @@
 QA output created by 158
 == create base ==
-Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=134217728 encryption=on encrypt.key-secret=sec0
+Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT encryption=on encrypt.key-secret=sec0 size=134217728
 
 == writing whole image ==
 wrote 134217728/134217728 bytes at offset 0
@@ -10,7 +10,7 @@ wrote 134217728/134217728 bytes at offset 0
 read 134217728/134217728 bytes at offset 0
 128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 == create overlay ==
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 backing_file=TEST_DIR/t.IMGFMT.base encryption=on encrypt.key-secret=sec0
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encryption=on encrypt.key-secret=sec0 size=134217728 backing_file=TEST_DIR/t.IMGFMT.base
 
 == writing part of a cluster ==
 wrote 1024/1024 bytes at offset 0
diff --git a/tests/qemu-iotests/182.out b/tests/qemu-iotests/182.out
index a8eea166c3..a31bfb57b3 100644
--- a/tests/qemu-iotests/182.out
+++ b/tests/qemu-iotests/182.out
@@ -13,7 +13,7 @@ Is another process using the image [TEST_DIR/t.qcow2]?
 {'execute': 'blockdev-add', 'arguments': { 'node-name': 'node0', 'driver': 'file', 'filename': 'TEST_DIR/t.IMGFMT', 'locking': 'on' } }
 {"return": {}}
 {'execute': 'blockdev-snapshot-sync', 'arguments': { 'node-name': 'node0', 'snapshot-file': 'TEST_DIR/t.IMGFMT.overlay', 'snapshot-node-name': 'node1' } }
-Formatting 'TEST_DIR/t.qcow2.overlay', fmt=qcow2 size=197120 backing_file=TEST_DIR/t.qcow2 backing_fmt=file cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2.overlay', fmt=qcow2 cluster_size=65536 size=197120 backing_file=TEST_DIR/t.qcow2 backing_fmt=file lazy_refcounts=off refcount_bits=16
 {"return": {}}
 {'execute': 'blockdev-add', 'arguments': { 'node-name': 'node1', 'driver': 'file', 'filename': 'TEST_DIR/t.IMGFMT', 'locking': 'on' } }
 {"return": {}}
diff --git a/tests/qemu-iotests/185.out b/tests/qemu-iotests/185.out
index 9a3b65782b..41c4773edc 100644
--- a/tests/qemu-iotests/185.out
+++ b/tests/qemu-iotests/185.out
@@ -9,14 +9,14 @@ Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864
 === Creating backing chain ===
 
 { 'execute': 'blockdev-snapshot-sync', 'arguments': { 'device': 'disk', 'snapshot-file': 'TEST_DIR/t.IMGFMT.mid', 'format': 'IMGFMT', 'mode': 'absolute-paths' } }
-Formatting 'TEST_DIR/t.qcow2.mid', fmt=qcow2 size=67108864 backing_file=TEST_DIR/t.qcow2.base backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2.mid', fmt=qcow2 cluster_size=65536 size=67108864 backing_file=TEST_DIR/t.qcow2.base backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 { 'execute': 'human-monitor-command', 'arguments': { 'command-line': 'qemu-io disk "write 0 4M"' } }
 wrote 4194304/4194304 bytes at offset 0
 4 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 {"return": ""}
 { 'execute': 'blockdev-snapshot-sync', 'arguments': { 'device': 'disk', 'snapshot-file': 'TEST_DIR/t.IMGFMT', 'format': 'IMGFMT', 'mode': 'absolute-paths' } }
-Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 backing_file=TEST_DIR/t.qcow2.mid backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 size=67108864 backing_file=TEST_DIR/t.qcow2.mid backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
 {"return": {}}
 
 === Start commit job and exit qemu ===
@@ -48,7 +48,7 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 backing_file=TEST_DIR/t.q
 { 'execute': 'qmp_capabilities' }
 {"return": {}}
 { 'execute': 'drive-mirror', 'arguments': { 'device': 'disk', 'target': 'TEST_DIR/t.IMGFMT.copy', 'format': 'IMGFMT', 'sync': 'full', 'speed': 65536 } }
-Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 cluster_size=65536 size=67108864 lazy_refcounts=off refcount_bits=16
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "disk"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}}
 {"return": {}}
@@ -62,7 +62,7 @@ Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 cluster_size=65536 l
 { 'execute': 'qmp_capabilities' }
 {"return": {}}
 { 'execute': 'drive-backup', 'arguments': { 'device': 'disk', 'target': 'TEST_DIR/t.IMGFMT.copy', 'format': 'IMGFMT', 'sync': 'full', 'speed': 65536 } }
-Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 cluster_size=65536 size=67108864 lazy_refcounts=off refcount_bits=16
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "disk"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}}
 {"timestamp": {"seconds":  TIMESTAMP, "microseconds":  TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "paused", "id": "disk"}}
diff --git a/tests/qemu-iotests/188.out b/tests/qemu-iotests/188.out
index c568ef3701..4b9aadd51c 100644
--- a/tests/qemu-iotests/188.out
+++ b/tests/qemu-iotests/188.out
@@ -1,5 +1,5 @@
 QA output created by 188
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 size=16777216
 
 == reading whole image ==
 read 16777216/16777216 bytes at offset 0
diff --git a/tests/qemu-iotests/189.out b/tests/qemu-iotests/189.out
index a0b7c9c24c..e536d95d53 100644
--- a/tests/qemu-iotests/189.out
+++ b/tests/qemu-iotests/189.out
@@ -1,6 +1,6 @@
 QA output created by 189
 == create base ==
-Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=16777216 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 size=16777216
 
 == writing whole image ==
 wrote 16777216/16777216 bytes at offset 0
@@ -10,7 +10,7 @@ wrote 16777216/16777216 bytes at offset 0
 read 16777216/16777216 bytes at offset 0
 16 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 == create overlay ==
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 backing_file=TEST_DIR/t.IMGFMT.base encrypt.format=luks encrypt.key-secret=sec1 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec1 encrypt.iter-time=10 size=16777216 backing_file=TEST_DIR/t.IMGFMT.base
 
 == writing part of a cluster ==
 wrote 1024/1024 bytes at offset 0
diff --git a/tests/qemu-iotests/198.out b/tests/qemu-iotests/198.out
index 831ce3a289..b0f2d417af 100644
--- a/tests/qemu-iotests/198.out
+++ b/tests/qemu-iotests/198.out
@@ -1,12 +1,12 @@
 QA output created by 198
 == create base ==
-Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=16777216 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 size=16777216
 
 == writing whole image base ==
 wrote 16777216/16777216 bytes at offset 0
 16 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 == create overlay ==
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 backing_file=TEST_DIR/t.IMGFMT.base encrypt.format=luks encrypt.key-secret=sec1 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec1 encrypt.iter-time=10 size=16777216 backing_file=TEST_DIR/t.IMGFMT.base
 
 == writing whole image layer ==
 wrote 16777216/16777216 bytes at offset 0
diff --git a/tests/qemu-iotests/243.out b/tests/qemu-iotests/243.out
index dcb33fac32..341ef3b7d0 100644
--- a/tests/qemu-iotests/243.out
+++ b/tests/qemu-iotests/243.out
@@ -2,31 +2,31 @@ QA output created by 243
 
 === preallocation=off ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=67108864
 File size: 196616
 Disk usage: low
 
 === preallocation=metadata ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=67108864
 File size: 67436544
 Disk usage: low
 
 === preallocation=falloc ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=67108864
 File size: 67436544
 Disk usage: high
 
 === preallocation=full ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=67108864
 File size: 67436544
 Disk usage: high
 
 === External data file: preallocation=off ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 data_file=TEST_DIR/t.IMGFMT.data preallocation=off
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=off size=67108864 data_file=TEST_DIR/t.IMGFMT.data
 qcow2 file size: 196616
 data file size:  67108864
 qcow2 disk usage: low
@@ -34,7 +34,7 @@ data disk usage:  low
 
 === External data file: preallocation=metadata ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 data_file=TEST_DIR/t.IMGFMT.data preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=67108864 data_file=TEST_DIR/t.IMGFMT.data
 qcow2 file size: 327680
 data file size:  67108864
 qcow2 disk usage: low
@@ -42,7 +42,7 @@ data disk usage:  low
 
 === External data file: preallocation=falloc ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 data_file=TEST_DIR/t.IMGFMT.data preallocation=falloc
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=falloc size=67108864 data_file=TEST_DIR/t.IMGFMT.data
 qcow2 file size: 327680
 data file size:  67108864
 qcow2 disk usage: low
@@ -50,7 +50,7 @@ data disk usage:  high
 
 === External data file: preallocation=full ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 data_file=TEST_DIR/t.IMGFMT.data preallocation=full
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=full size=67108864 data_file=TEST_DIR/t.IMGFMT.data
 qcow2 file size: 327680
 data file size:  67108864
 qcow2 disk usage: low
diff --git a/tests/qemu-iotests/250.out b/tests/qemu-iotests/250.out
index f480fd273b..0b737097f3 100644
--- a/tests/qemu-iotests/250.out
+++ b/tests/qemu-iotests/250.out
@@ -1,5 +1,5 @@
 QA output created by 250
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=2202009600 preallocation=metadata
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT preallocation=metadata size=2202009600
 discard 10485760/10485760 bytes at offset 0
 10 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 discard 10485760/10485760 bytes at offset 2191523840
diff --git a/tests/qemu-iotests/255.out b/tests/qemu-iotests/255.out
index 348909fdef..96502e4336 100644
--- a/tests/qemu-iotests/255.out
+++ b/tests/qemu-iotests/255.out
@@ -3,9 +3,9 @@ Finishing a commit job with background reads
 
 === Create backing chain and start VM ===
 
-Formatting 'TEST_DIR/PID-t.qcow2.mid', fmt=qcow2 size=134217728 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/PID-t.qcow2.mid', fmt=qcow2 cluster_size=65536 size=134217728 lazy_refcounts=off refcount_bits=16
 
-Formatting 'TEST_DIR/PID-t.qcow2', fmt=qcow2 size=134217728 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/PID-t.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 lazy_refcounts=off refcount_bits=16
 
 === Start background read requests ===
 
@@ -23,9 +23,9 @@ Closing the VM while a job is being cancelled
 
 === Create images and start VM ===
 
-Formatting 'TEST_DIR/PID-src.qcow2', fmt=qcow2 size=134217728 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/PID-src.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 lazy_refcounts=off refcount_bits=16
 
-Formatting 'TEST_DIR/PID-dst.qcow2', fmt=qcow2 size=134217728 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/PID-dst.qcow2', fmt=qcow2 cluster_size=65536 size=134217728 lazy_refcounts=off refcount_bits=16
 
 wrote 1048576/1048576 bytes at offset 0
 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
diff --git a/tests/qemu-iotests/263.out b/tests/qemu-iotests/263.out
index 0c982c55cb..4cf5c79ed8 100644
--- a/tests/qemu-iotests/263.out
+++ b/tests/qemu-iotests/263.out
@@ -2,7 +2,7 @@ QA output created by 263
 
 testing LUKS qcow2 encryption
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 size=1048576
 == reading the whole image ==
 read 1048576/1048576 bytes at offset 0
 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -21,7 +21,7 @@ read 982528/982528 bytes at offset 66048
 
 testing legacy AES qcow2 encryption
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 encrypt.format=aes encrypt.key-secret=sec0
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encrypt.format=aes encrypt.key-secret=sec0 size=1048576
 == reading the whole image ==
 read 1048576/1048576 bytes at offset 0
 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
diff --git a/tests/qemu-iotests/280.out b/tests/qemu-iotests/280.out
index 5d382faaa8..0c0a30456b 100644
--- a/tests/qemu-iotests/280.out
+++ b/tests/qemu-iotests/280.out
@@ -1,4 +1,4 @@
-Formatting 'TEST_DIR/PID-base', fmt=qcow2 size=67108864 cluster_size=65536 lazy_refcounts=off refcount_bits=16
+Formatting 'TEST_DIR/PID-base', fmt=qcow2 cluster_size=65536 size=67108864 lazy_refcounts=off refcount_bits=16
 
 === Launch VM ===
 Enabling migration QMP events on VM...
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 06/14] block/crypto: rename two functions
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (4 preceding siblings ...)
  2020-03-08 15:18 ` [PATCH v2 05/14] block/amend: refactor qcow2 amend options Maxim Levitsky
@ 2020-03-08 15:18 ` Maxim Levitsky
  2020-03-08 15:18 ` [PATCH v2 07/14] block/crypto: implement the encryption key management Maxim Levitsky
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

rename the write_func to create_write_func, and init_func to create_init_func.
This is preparation for other write_func that will be used to update the encryption keys.

No functional changes

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
---
 block/crypto.c | 25 ++++++++++++-------------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/block/crypto.c b/block/crypto.c
index ecf96a7a9b..0b37dae564 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -78,12 +78,12 @@ struct BlockCryptoCreateData {
 };
 
 
-static ssize_t block_crypto_write_func(QCryptoBlock *block,
-                                       size_t offset,
-                                       const uint8_t *buf,
-                                       size_t buflen,
-                                       void *opaque,
-                                       Error **errp)
+static ssize_t block_crypto_create_write_func(QCryptoBlock *block,
+                                              size_t offset,
+                                              const uint8_t *buf,
+                                              size_t buflen,
+                                              void *opaque,
+                                              Error **errp)
 {
     struct BlockCryptoCreateData *data = opaque;
     ssize_t ret;
@@ -96,11 +96,10 @@ static ssize_t block_crypto_write_func(QCryptoBlock *block,
     return ret;
 }
 
-
-static ssize_t block_crypto_init_func(QCryptoBlock *block,
-                                      size_t headerlen,
-                                      void *opaque,
-                                      Error **errp)
+static ssize_t block_crypto_create_init_func(QCryptoBlock *block,
+                                             size_t headerlen,
+                                             void *opaque,
+                                             Error **errp)
 {
     struct BlockCryptoCreateData *data = opaque;
 
@@ -296,8 +295,8 @@ static int block_crypto_co_create_generic(BlockDriverState *bs,
     };
 
     crypto = qcrypto_block_create(opts, NULL,
-                                  block_crypto_init_func,
-                                  block_crypto_write_func,
+                                  block_crypto_create_init_func,
+                                  block_crypto_create_write_func,
                                   &data,
                                   errp);
 
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 07/14] block/crypto: implement the encryption key management
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (5 preceding siblings ...)
  2020-03-08 15:18 ` [PATCH v2 06/14] block/crypto: rename two functions Maxim Levitsky
@ 2020-03-08 15:18 ` Maxim Levitsky
  2020-04-28 16:15   ` Daniel P. Berrangé
  2020-03-08 15:18 ` [PATCH v2 08/14] block/qcow2: extend qemu-img amend interface with crypto options Maxim Levitsky
                   ` (7 subsequent siblings)
  14 siblings, 1 reply; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

This implements the encryption key management using the generic code in
qcrypto layer and exposes it to the user via qemu-img

This code adds another 'write_func' because the initialization
write_func works directly on the underlying file, and amend
works on instance of luks device.

This commit also adds a 'hack/workaround' I and Kevin Wolf (thanks)
made to make the driver both support write sharing (to avoid breaking the users),
and be safe against concurrent  metadata update (the keyslots)

Eventually the write sharing for luks driver will be deprecated
and removed together with this hack.

The hack is that we ask (as a format driver) for BLK_PERM_CONSISTENT_READ
and then when we want to update the keys, we unshare that permission.
So if someone else has the image open, even readonly, encryption
key update will fail gracefully.

Also thanks to Daniel Berrange for the idea of
unsharing read, rather that write permission which allows
to avoid cases when the other user had opened the image read-only.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/crypto.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++--
 block/crypto.h |  44 +++++++++++++++--
 2 files changed, 163 insertions(+), 8 deletions(-)

diff --git a/block/crypto.c b/block/crypto.c
index 0b37dae564..727a3fde58 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -36,6 +36,7 @@ typedef struct BlockCrypto BlockCrypto;
 
 struct BlockCrypto {
     QCryptoBlock *block;
+    bool updating_keys;
 };
 
 
@@ -70,6 +71,24 @@ static ssize_t block_crypto_read_func(QCryptoBlock *block,
     return ret;
 }
 
+static ssize_t block_crypto_write_func(QCryptoBlock *block,
+                                       size_t offset,
+                                       const uint8_t *buf,
+                                       size_t buflen,
+                                       void *opaque,
+                                       Error **errp)
+{
+    BlockDriverState *bs = opaque;
+    ssize_t ret;
+
+    ret = bdrv_pwrite(bs->file, offset, buf, buflen);
+    if (ret < 0) {
+        error_setg_errno(errp, -ret, "Could not write encryption header");
+        return ret;
+    }
+    return ret;
+}
+
 
 struct BlockCryptoCreateData {
     BlockBackend *blk;
@@ -148,6 +167,19 @@ static QemuOptsList block_crypto_create_opts_luks = {
 };
 
 
+static QemuOptsList block_crypto_amend_opts_luks = {
+    .name = "crypto",
+    .head = QTAILQ_HEAD_INITIALIZER(block_crypto_create_opts_luks.head),
+    .desc = {
+        BLOCK_CRYPTO_OPT_DEF_LUKS_STATE(""),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT(""),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_OLD_SECRET(""),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_NEW_SECRET(""),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(""),
+        { /* end of list */ }
+    },
+};
+
 QCryptoBlockOpenOptions *
 block_crypto_open_opts_init(QDict *opts, Error **errp)
 {
@@ -661,6 +693,95 @@ block_crypto_get_specific_info_luks(BlockDriverState *bs, Error **errp)
     return spec_info;
 }
 
+static int
+block_crypto_amend_options_luks(BlockDriverState *bs,
+                           QemuOpts *opts,
+                           BlockDriverAmendStatusCB *status_cb,
+                           void *cb_opaque,
+                           bool force,
+                           Error **errp)
+{
+    BlockCrypto *crypto = bs->opaque;
+    QDict *cryptoopts = NULL;
+    QCryptoBlockAmendOptions *amend_options = NULL;
+    int ret;
+
+    assert(crypto);
+    assert(crypto->block);
+    crypto->updating_keys = true;
+
+    ret = bdrv_child_refresh_perms(bs, bs->file, errp);
+    if (ret < 0) {
+        goto cleanup;
+    }
+
+    cryptoopts = qemu_opts_to_qdict(opts, NULL);
+    qdict_put_str(cryptoopts, "format", "luks");
+    amend_options = block_crypto_amend_opts_init(cryptoopts, errp);
+    if (!amend_options) {
+        ret = -EINVAL;
+        goto cleanup;
+    }
+
+    ret = qcrypto_block_amend_options(crypto->block,
+                                      block_crypto_read_func,
+                                      block_crypto_write_func,
+                                      bs,
+                                      amend_options,
+                                      force,
+                                      errp);
+cleanup:
+    crypto->updating_keys = false;
+    bdrv_child_refresh_perms(bs, bs->file, errp);
+    qapi_free_QCryptoBlockAmendOptions(amend_options);
+    qobject_unref(cryptoopts);
+    return ret;
+}
+
+
+static void
+block_crypto_child_perms(BlockDriverState *bs, BdrvChild *c,
+                         const BdrvChildRole *role,
+                         BlockReopenQueue *reopen_queue,
+                         uint64_t perm, uint64_t shared,
+                         uint64_t *nperm, uint64_t *nshared)
+{
+
+    BlockCrypto *crypto = bs->opaque;
+
+    bdrv_filter_default_perms(bs, c, role, reopen_queue,
+            perm, shared, nperm, nshared);
+    /*
+     * Ask for consistent read permission so that if
+     * someone else tries to open this image with this permission
+     * neither will be able to edit encryption keys, since
+     * we will unshare that permission while trying to
+     * update the encryption keys
+     */
+    if (!(bs->open_flags & BDRV_O_NO_IO)) {
+        *nperm |= BLK_PERM_CONSISTENT_READ;
+    }
+    /*
+     * This driver doesn't modify LUKS metadata except
+     * when updating the encryption slots.
+     * Thus unlike a proper format driver we don't ask for
+     * shared write/read permission. However we need it
+     * when we are updating the keys, to ensure that only we
+     * have access to the device.
+     *
+     * Encryption update will set the crypto->updating_keys
+     * during that period and refresh permissions
+     *
+     */
+    if (crypto->updating_keys) {
+        /* need exclusive write access for header update */
+        *nperm |= BLK_PERM_WRITE;
+        /* unshare read and write permission */
+        *nshared &= ~(BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE);
+    }
+}
+
+
 static const char *const block_crypto_strong_runtime_opts[] = {
     BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET,
 
@@ -673,13 +794,12 @@ static BlockDriver bdrv_crypto_luks = {
     .bdrv_probe         = block_crypto_probe_luks,
     .bdrv_open          = block_crypto_open_luks,
     .bdrv_close         = block_crypto_close,
-    /* This driver doesn't modify LUKS metadata except when creating image.
-     * Allow share-rw=on as a special case. */
-    .bdrv_child_perm    = bdrv_filter_default_perms,
+    .bdrv_child_perm    = block_crypto_child_perms,
     .bdrv_co_create     = block_crypto_co_create_luks,
     .bdrv_co_create_opts = block_crypto_co_create_opts_luks,
     .bdrv_co_truncate   = block_crypto_co_truncate,
     .create_opts        = &block_crypto_create_opts_luks,
+    .amend_opts         = &block_crypto_amend_opts_luks,
 
     .bdrv_reopen_prepare = block_crypto_reopen_prepare,
     .bdrv_refresh_limits = block_crypto_refresh_limits,
@@ -688,6 +808,7 @@ static BlockDriver bdrv_crypto_luks = {
     .bdrv_getlength     = block_crypto_getlength,
     .bdrv_get_info      = block_crypto_get_info_luks,
     .bdrv_get_specific_info = block_crypto_get_specific_info_luks,
+    .bdrv_amend_options = block_crypto_amend_options_luks,
 
     .strong_runtime_opts = block_crypto_strong_runtime_opts,
 };
diff --git a/block/crypto.h b/block/crypto.h
index 06e044c9be..64276b1a15 100644
--- a/block/crypto.h
+++ b/block/crypto.h
@@ -41,6 +41,11 @@
 #define BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG "ivgen-hash-alg"
 #define BLOCK_CRYPTO_OPT_LUKS_HASH_ALG "hash-alg"
 #define BLOCK_CRYPTO_OPT_LUKS_ITER_TIME "iter-time"
+#define BLOCK_CRYPTO_OPT_LUKS_KEYSLOT "keyslot"
+#define BLOCK_CRYPTO_OPT_LUKS_STATE "state"
+#define BLOCK_CRYPTO_OPT_LUKS_OLD_SECRET "old-secret"
+#define BLOCK_CRYPTO_OPT_LUKS_NEW_SECRET "new-secret"
+
 
 #define BLOCK_CRYPTO_OPT_DEF_LUKS_KEY_SECRET(prefix)                    \
     BLOCK_CRYPTO_OPT_DEF_KEY_SECRET(prefix,                             \
@@ -81,11 +86,40 @@
         .help = "Name of encryption hash algorithm",     \
     }
 
-#define BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(prefix)           \
-    {                                                         \
-        .name = prefix BLOCK_CRYPTO_OPT_LUKS_ITER_TIME,       \
-        .type = QEMU_OPT_NUMBER,                              \
-        .help = "Time to spend in PBKDF in milliseconds",     \
+#define BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(prefix)        \
+    {                                                      \
+        .name = prefix BLOCK_CRYPTO_OPT_LUKS_ITER_TIME,    \
+        .type = QEMU_OPT_NUMBER,                           \
+        .help = "Time to spend in PBKDF in milliseconds",  \
+    }
+
+#define BLOCK_CRYPTO_OPT_DEF_LUKS_STATE(prefix)                           \
+    {                                                                     \
+        .name = prefix BLOCK_CRYPTO_OPT_LUKS_STATE,                       \
+        .type = QEMU_OPT_STRING,                                          \
+        .help = "Select new state of affected keyslots (active/inactive)",\
+    }
+
+#define BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT(prefix)              \
+    {                                                          \
+        .name = prefix BLOCK_CRYPTO_OPT_LUKS_KEYSLOT,          \
+        .type = QEMU_OPT_NUMBER,                               \
+        .help = "Select a single keyslot to modify explicitly",\
+    }
+
+#define BLOCK_CRYPTO_OPT_DEF_LUKS_OLD_SECRET(prefix)            \
+    {                                                           \
+        .name = prefix BLOCK_CRYPTO_OPT_LUKS_OLD_SECRET,        \
+        .type = QEMU_OPT_STRING,                                \
+        .help = "Select all keyslots that match this password", \
+    }
+
+#define BLOCK_CRYPTO_OPT_DEF_LUKS_NEW_SECRET(prefix)            \
+    {                                                           \
+        .name = prefix BLOCK_CRYPTO_OPT_LUKS_NEW_SECRET,        \
+        .type = QEMU_OPT_STRING,                                \
+        .help = "New secret to set in the matching keyslots. "  \
+                "Empty string to erase",                        \
     }
 
 QCryptoBlockCreateOptions *
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 08/14] block/qcow2: extend qemu-img amend interface with crypto options
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (6 preceding siblings ...)
  2020-03-08 15:18 ` [PATCH v2 07/14] block/crypto: implement the encryption key management Maxim Levitsky
@ 2020-03-08 15:18 ` Maxim Levitsky
  2020-04-28 16:17   ` Daniel P. Berrangé
  2020-03-08 15:18 ` [PATCH v2 09/14] iotests: filter few more luks specific create options Maxim Levitsky
                   ` (6 subsequent siblings)
  14 siblings, 1 reply; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

Now that we have all the infrastructure in place,
wire it in the qcow2 driver and expose this to the user.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/qcow2.c              | 80 ++++++++++++++++++++++++++++++++------
 tests/qemu-iotests/082.out | 45 +++++++++++++++++++++
 2 files changed, 114 insertions(+), 11 deletions(-)

diff --git a/block/qcow2.c b/block/qcow2.c
index 81e7895e7c..10b22544f2 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -176,6 +176,19 @@ static ssize_t qcow2_crypto_hdr_write_func(QCryptoBlock *block, size_t offset,
     return ret;
 }
 
+static QDict*
+qcow2_extract_crypto_opts(QemuOpts *opts, const char *fmt, Error **errp)
+{
+    QDict *cryptoopts_qdict;
+    QDict *opts_qdict;
+
+    /* Extract "encrypt." options into a qdict */
+    opts_qdict = qemu_opts_to_qdict(opts, NULL);
+    qdict_extract_subqdict(opts_qdict, &cryptoopts_qdict, "encrypt.");
+    qobject_unref(opts_qdict);
+    qdict_put_str(cryptoopts_qdict, "format", "luks");
+    return cryptoopts_qdict;
+}
 
 /* 
  * read qcow2 extension and fill bs
@@ -4615,20 +4628,18 @@ static ssize_t qcow2_measure_crypto_hdr_write_func(QCryptoBlock *block,
 static bool qcow2_measure_luks_headerlen(QemuOpts *opts, size_t *len,
                                          Error **errp)
 {
-    QDict *opts_qdict;
-    QDict *cryptoopts_qdict;
     QCryptoBlockCreateOptions *cryptoopts;
+    QDict *crypto_opts_dict;
     QCryptoBlock *crypto;
 
-    /* Extract "encrypt." options into a qdict */
-    opts_qdict = qemu_opts_to_qdict(opts, NULL);
-    qdict_extract_subqdict(opts_qdict, &cryptoopts_qdict, "encrypt.");
-    qobject_unref(opts_qdict);
+    crypto_opts_dict = qcow2_extract_crypto_opts(opts, "luks", errp);
+    if (!crypto_opts_dict) {
+        return false;
+    }
+
+    cryptoopts = block_crypto_create_opts_init(crypto_opts_dict, errp);
+    qobject_unref(crypto_opts_dict);
 
-    /* Build QCryptoBlockCreateOptions object from qdict */
-    qdict_put_str(cryptoopts_qdict, "format", "luks");
-    cryptoopts = block_crypto_create_opts_init(cryptoopts_qdict, errp);
-    qobject_unref(cryptoopts_qdict);
     if (!cryptoopts) {
         return false;
     }
@@ -5067,6 +5078,7 @@ typedef enum Qcow2AmendOperation {
     QCOW2_NO_OPERATION = 0,
 
     QCOW2_UPGRADING,
+    QCOW2_UPDATING_ENCRYPTION,
     QCOW2_CHANGING_REFCOUNT_ORDER,
     QCOW2_DOWNGRADING,
 } Qcow2AmendOperation;
@@ -5148,6 +5160,7 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
     int ret;
     QemuOptDesc *desc = opts->list->desc;
     Qcow2AmendHelperCBInfo helper_cb_info;
+    bool encryption_update = false;
 
     while (desc && desc->name) {
         if (!qemu_opt_find(opts, desc->name)) {
@@ -5174,6 +5187,18 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
             backing_file = qemu_opt_get(opts, BLOCK_OPT_BACKING_FILE);
         } else if (!strcmp(desc->name, BLOCK_OPT_BACKING_FMT)) {
             backing_format = qemu_opt_get(opts, BLOCK_OPT_BACKING_FMT);
+        } else if (g_str_has_prefix(desc->name, "encrypt.")) {
+            if (!s->crypto) {
+                error_setg(errp,
+                           "Can't amend encryption options - encryption not present");
+                return -EINVAL;
+            }
+            if (s->crypt_method_header != QCOW_CRYPT_LUKS) {
+                error_setg(errp,
+                           "Only LUKS encryption options can be amended");
+                return -ENOTSUP;
+            }
+            encryption_update = true;
         } else if (!strcmp(desc->name, BLOCK_OPT_LAZY_REFCOUNTS)) {
             lazy_refcounts = qemu_opt_get_bool(opts, BLOCK_OPT_LAZY_REFCOUNTS,
                                                lazy_refcounts);
@@ -5216,7 +5241,8 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
         .original_status_cb = status_cb,
         .original_cb_opaque = cb_opaque,
         .total_operations = (new_version != old_version)
-                          + (s->refcount_bits != refcount_bits)
+                          + (s->refcount_bits != refcount_bits) +
+                            (encryption_update == true)
     };
 
     /* Upgrade first (some features may require compat=1.1) */
@@ -5229,6 +5255,33 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
         }
     }
 
+    if (encryption_update) {
+        QDict *amend_opts_dict;
+        QCryptoBlockAmendOptions *amend_opts;
+
+        helper_cb_info.current_operation = QCOW2_UPDATING_ENCRYPTION;
+        amend_opts_dict = qcow2_extract_crypto_opts(opts, "luks", errp);
+        if (!amend_opts_dict) {
+            return -EINVAL;
+        }
+        amend_opts = block_crypto_amend_opts_init(amend_opts_dict, errp);
+        qobject_unref(amend_opts_dict);
+        if (!amend_opts) {
+            return -EINVAL;
+        }
+        ret = qcrypto_block_amend_options(s->crypto,
+                                          qcow2_crypto_hdr_read_func,
+                                          qcow2_crypto_hdr_write_func,
+                                          bs,
+                                          amend_opts,
+                                          force,
+                                          errp);
+        qapi_free_QCryptoBlockAmendOptions(amend_opts);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+
     if (s->refcount_bits != refcount_bits) {
         int refcount_order = ctz32(refcount_bits);
 
@@ -5483,6 +5536,11 @@ static QemuOptsList qcow2_amend_opts = {
     .name = "qcow2-amend-opts",
     .head = QTAILQ_HEAD_INITIALIZER(qcow2_amend_opts.head),
     .desc = {
+        BLOCK_CRYPTO_OPT_DEF_LUKS_STATE("encrypt."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT("encrypt."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_OLD_SECRET("encrypt."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_NEW_SECRET("encrypt."),
+        BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),
         QCOW_COMMON_OPTIONS,
         { /* end of list */ }
     }
diff --git a/tests/qemu-iotests/082.out b/tests/qemu-iotests/082.out
index c68458da8c..6558f38ba8 100644
--- a/tests/qemu-iotests/082.out
+++ b/tests/qemu-iotests/082.out
@@ -620,6 +620,11 @@ Amend options for 'qcow2':
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
+  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
+  encrypt.keyslot=<num>  - Select a single keyslot to modify explicitly
+  encrypt.new-secret=<str> - New secret to set in the matching keyslots. Empty string to erase
+  encrypt.old-secret=<str> - Select all keyslots that match this password
+  encrypt.state=<str>    - Select new state of affected keyslots (active/inactive)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
@@ -631,6 +636,11 @@ Amend options for 'qcow2':
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
+  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
+  encrypt.keyslot=<num>  - Select a single keyslot to modify explicitly
+  encrypt.new-secret=<str> - New secret to set in the matching keyslots. Empty string to erase
+  encrypt.old-secret=<str> - Select all keyslots that match this password
+  encrypt.state=<str>    - Select new state of affected keyslots (active/inactive)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
@@ -642,6 +652,11 @@ Amend options for 'qcow2':
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
+  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
+  encrypt.keyslot=<num>  - Select a single keyslot to modify explicitly
+  encrypt.new-secret=<str> - New secret to set in the matching keyslots. Empty string to erase
+  encrypt.old-secret=<str> - Select all keyslots that match this password
+  encrypt.state=<str>    - Select new state of affected keyslots (active/inactive)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
@@ -653,6 +668,11 @@ Amend options for 'qcow2':
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
+  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
+  encrypt.keyslot=<num>  - Select a single keyslot to modify explicitly
+  encrypt.new-secret=<str> - New secret to set in the matching keyslots. Empty string to erase
+  encrypt.old-secret=<str> - Select all keyslots that match this password
+  encrypt.state=<str>    - Select new state of affected keyslots (active/inactive)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
@@ -664,6 +684,11 @@ Amend options for 'qcow2':
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
+  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
+  encrypt.keyslot=<num>  - Select a single keyslot to modify explicitly
+  encrypt.new-secret=<str> - New secret to set in the matching keyslots. Empty string to erase
+  encrypt.old-secret=<str> - Select all keyslots that match this password
+  encrypt.state=<str>    - Select new state of affected keyslots (active/inactive)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
@@ -675,6 +700,11 @@ Amend options for 'qcow2':
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
+  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
+  encrypt.keyslot=<num>  - Select a single keyslot to modify explicitly
+  encrypt.new-secret=<str> - New secret to set in the matching keyslots. Empty string to erase
+  encrypt.old-secret=<str> - Select all keyslots that match this password
+  encrypt.state=<str>    - Select new state of affected keyslots (active/inactive)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
@@ -686,6 +716,11 @@ Amend options for 'qcow2':
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
+  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
+  encrypt.keyslot=<num>  - Select a single keyslot to modify explicitly
+  encrypt.new-secret=<str> - New secret to set in the matching keyslots. Empty string to erase
+  encrypt.old-secret=<str> - Select all keyslots that match this password
+  encrypt.state=<str>    - Select new state of affected keyslots (active/inactive)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
@@ -697,6 +732,11 @@ Amend options for 'qcow2':
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
+  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
+  encrypt.keyslot=<num>  - Select a single keyslot to modify explicitly
+  encrypt.new-secret=<str> - New secret to set in the matching keyslots. Empty string to erase
+  encrypt.old-secret=<str> - Select all keyslots that match this password
+  encrypt.state=<str>    - Select new state of affected keyslots (active/inactive)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
@@ -725,6 +765,11 @@ Amend options for 'qcow2':
   compat=<str>           - Compatibility level (v2 [0.10] or v3 [1.1])
   data_file=<str>        - File name of an external data file
   data_file_raw=<bool (on/off)> - The external data file must stay valid as a raw image
+  encrypt.iter-time=<num> - Time to spend in PBKDF in milliseconds
+  encrypt.keyslot=<num>  - Select a single keyslot to modify explicitly
+  encrypt.new-secret=<str> - New secret to set in the matching keyslots. Empty string to erase
+  encrypt.old-secret=<str> - Select all keyslots that match this password
+  encrypt.state=<str>    - Select new state of affected keyslots (active/inactive)
   lazy_refcounts=<bool (on/off)> - Postpone refcount updates
   refcount_bits=<num>    - Width of a reference count entry in bits
   size=<size>            - Virtual disk size
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 09/14] iotests: filter few more luks specific create options
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (7 preceding siblings ...)
  2020-03-08 15:18 ` [PATCH v2 08/14] block/qcow2: extend qemu-img amend interface with crypto options Maxim Levitsky
@ 2020-03-08 15:18 ` Maxim Levitsky
  2020-04-28 16:19   ` Daniel P. Berrangé
  2020-03-08 15:18 ` [PATCH v2 10/14] iotests: qemu-img tests for luks key management Maxim Levitsky
                   ` (5 subsequent siblings)
  14 siblings, 1 reply; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

This allows more tests to be able to have same output on both qcow2 luks encrypted images
and raw luks images

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 tests/qemu-iotests/087.out       | 6 +++---
 tests/qemu-iotests/134.out       | 2 +-
 tests/qemu-iotests/158.out       | 4 ++--
 tests/qemu-iotests/188.out       | 2 +-
 tests/qemu-iotests/189.out       | 4 ++--
 tests/qemu-iotests/198.out       | 4 ++--
 tests/qemu-iotests/263.out       | 4 ++--
 tests/qemu-iotests/284.out       | 6 +++---
 tests/qemu-iotests/common.filter | 6 ++++--
 9 files changed, 20 insertions(+), 18 deletions(-)

diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out
index f23bffbbf1..d5ff53302e 100644
--- a/tests/qemu-iotests/087.out
+++ b/tests/qemu-iotests/087.out
@@ -34,7 +34,7 @@ QMP_VERSION
 
 === Encrypted image QCow ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encryption=on encrypt.key-secret=sec0 size=134217728
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encryption=on size=134217728
 Testing:
 QMP_VERSION
 {"return": {}}
@@ -46,7 +46,7 @@ QMP_VERSION
 
 === Encrypted image LUKS ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec0 size=134217728
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
 Testing:
 QMP_VERSION
 {"return": {}}
@@ -58,7 +58,7 @@ QMP_VERSION
 
 === Missing driver ===
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encryption=on encrypt.key-secret=sec0 size=134217728
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encryption=on size=134217728
 Testing: -S
 QMP_VERSION
 {"return": {}}
diff --git a/tests/qemu-iotests/134.out b/tests/qemu-iotests/134.out
index f2878f5f3a..e4733c0b81 100644
--- a/tests/qemu-iotests/134.out
+++ b/tests/qemu-iotests/134.out
@@ -1,5 +1,5 @@
 QA output created by 134
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encryption=on encrypt.key-secret=sec0 size=134217728
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encryption=on size=134217728
 
 == reading whole image ==
 read 134217728/134217728 bytes at offset 0
diff --git a/tests/qemu-iotests/158.out b/tests/qemu-iotests/158.out
index fa2294bb85..52ea9a488f 100644
--- a/tests/qemu-iotests/158.out
+++ b/tests/qemu-iotests/158.out
@@ -1,6 +1,6 @@
 QA output created by 158
 == create base ==
-Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT encryption=on encrypt.key-secret=sec0 size=134217728
+Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT encryption=on size=134217728
 
 == writing whole image ==
 wrote 134217728/134217728 bytes at offset 0
@@ -10,7 +10,7 @@ wrote 134217728/134217728 bytes at offset 0
 read 134217728/134217728 bytes at offset 0
 128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 == create overlay ==
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encryption=on encrypt.key-secret=sec0 size=134217728 backing_file=TEST_DIR/t.IMGFMT.base
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encryption=on size=134217728 backing_file=TEST_DIR/t.IMGFMT.base
 
 == writing part of a cluster ==
 wrote 1024/1024 bytes at offset 0
diff --git a/tests/qemu-iotests/188.out b/tests/qemu-iotests/188.out
index 4b9aadd51c..5426861b18 100644
--- a/tests/qemu-iotests/188.out
+++ b/tests/qemu-iotests/188.out
@@ -1,5 +1,5 @@
 QA output created by 188
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 size=16777216
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216
 
 == reading whole image ==
 read 16777216/16777216 bytes at offset 0
diff --git a/tests/qemu-iotests/189.out b/tests/qemu-iotests/189.out
index e536d95d53..bc213cbe14 100644
--- a/tests/qemu-iotests/189.out
+++ b/tests/qemu-iotests/189.out
@@ -1,6 +1,6 @@
 QA output created by 189
 == create base ==
-Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 size=16777216
+Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=16777216
 
 == writing whole image ==
 wrote 16777216/16777216 bytes at offset 0
@@ -10,7 +10,7 @@ wrote 16777216/16777216 bytes at offset 0
 read 16777216/16777216 bytes at offset 0
 16 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 == create overlay ==
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec1 encrypt.iter-time=10 size=16777216 backing_file=TEST_DIR/t.IMGFMT.base
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 backing_file=TEST_DIR/t.IMGFMT.base
 
 == writing part of a cluster ==
 wrote 1024/1024 bytes at offset 0
diff --git a/tests/qemu-iotests/198.out b/tests/qemu-iotests/198.out
index b0f2d417af..acfdf96b0c 100644
--- a/tests/qemu-iotests/198.out
+++ b/tests/qemu-iotests/198.out
@@ -1,12 +1,12 @@
 QA output created by 198
 == create base ==
-Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 size=16777216
+Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=16777216
 
 == writing whole image base ==
 wrote 16777216/16777216 bytes at offset 0
 16 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 == create overlay ==
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec1 encrypt.iter-time=10 size=16777216 backing_file=TEST_DIR/t.IMGFMT.base
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=16777216 backing_file=TEST_DIR/t.IMGFMT.base
 
 == writing whole image layer ==
 wrote 16777216/16777216 bytes at offset 0
diff --git a/tests/qemu-iotests/263.out b/tests/qemu-iotests/263.out
index 4cf5c79ed8..54bfbeeff8 100644
--- a/tests/qemu-iotests/263.out
+++ b/tests/qemu-iotests/263.out
@@ -2,7 +2,7 @@ QA output created by 263
 
 testing LUKS qcow2 encryption
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10 size=1048576
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
 == reading the whole image ==
 read 1048576/1048576 bytes at offset 0
 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
@@ -21,7 +21,7 @@ read 982528/982528 bytes at offset 66048
 
 testing legacy AES qcow2 encryption
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT encrypt.format=aes encrypt.key-secret=sec0 size=1048576
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
 == reading the whole image ==
 read 1048576/1048576 bytes at offset 0
 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
diff --git a/tests/qemu-iotests/284.out b/tests/qemu-iotests/284.out
index 48216f5742..a929239302 100644
--- a/tests/qemu-iotests/284.out
+++ b/tests/qemu-iotests/284.out
@@ -2,7 +2,7 @@ QA output created by 284
 
 testing LUKS qcow2 encryption
 
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
 
 == cluster size 512
 == checking image refcounts ==
@@ -21,7 +21,7 @@ wrote 1/1 bytes at offset 512
 
 == rechecking image refcounts ==
 No errors were found on the image.
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
 
 == cluster size 2048
 == checking image refcounts ==
@@ -40,7 +40,7 @@ wrote 1/1 bytes at offset 2048
 
 == rechecking image refcounts ==
 No errors were found on the image.
-Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 encrypt.format=luks encrypt.key-secret=sec0 encrypt.iter-time=10
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576
 
 == cluster size 32768
 == checking image refcounts ==
diff --git a/tests/qemu-iotests/common.filter b/tests/qemu-iotests/common.filter
index 3f8ee3e5f7..bcc4495d52 100644
--- a/tests/qemu-iotests/common.filter
+++ b/tests/qemu-iotests/common.filter
@@ -150,8 +150,10 @@ _filter_img_create()
         -e "s# block_state_zero=\\(on\\|off\\)##g" \
         -e "s# log_size=[0-9]\\+##g" \
         -e "s# refcount_bits=[0-9]\\+##g" \
-        -e "s# key-secret=[a-zA-Z0-9]\\+##g" \
-        -e "s# iter-time=[0-9]\\+##g" \
+        -e "s# \\(encrypt\\.\\)\\?key-secret=[a-zA-Z0-9]\\+##g" \
+        -e "s# \\(encrypt\\.\\)\\?slot=[0-9]\\+##g" \
+        -e "s# \\(encrypt\\.\\)\\?iter-time=[0-9]\\+##g" \
+        -e "s# encrypt\\.format=[a-zA-Z0-9]\\+##g" \
         -e "s# force_size=\\(on\\|off\\)##g"
 }
 
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 10/14] iotests: qemu-img tests for luks key management
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (8 preceding siblings ...)
  2020-03-08 15:18 ` [PATCH v2 09/14] iotests: filter few more luks specific create options Maxim Levitsky
@ 2020-03-08 15:18 ` Maxim Levitsky
  2020-04-28 16:21   ` Daniel P. Berrangé
  2020-03-08 15:19 ` [PATCH v2 11/14] block/core: add generic infrastructure for x-blockdev-amend qmp command Maxim Levitsky
                   ` (4 subsequent siblings)
  14 siblings, 1 reply; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

This commit adds two tests, which test the new amend interface
of both luks raw images and qcow2 luks encrypted images.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 tests/qemu-iotests/300     | 207 +++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/300.out |  99 ++++++++++++++++++
 tests/qemu-iotests/301     |  90 ++++++++++++++++
 tests/qemu-iotests/301.out |  30 ++++++
 tests/qemu-iotests/group   |   3 +
 5 files changed, 429 insertions(+)
 create mode 100755 tests/qemu-iotests/300
 create mode 100644 tests/qemu-iotests/300.out
 create mode 100755 tests/qemu-iotests/301
 create mode 100644 tests/qemu-iotests/301.out

diff --git a/tests/qemu-iotests/300 b/tests/qemu-iotests/300
new file mode 100755
index 0000000000..aa1a77690f
--- /dev/null
+++ b/tests/qemu-iotests/300
@@ -0,0 +1,207 @@
+#!/usr/bin/env bash
+#
+# Test encryption key management with luks
+# Based on 134
+#
+# Copyright (C) 2019 Red Hat, Inc.
+#
+# 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/>.
+#
+
+# creator
+owner=mlevitsk@redhat.com
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+status=1	# failure is the default!
+
+_cleanup()
+{
+	_cleanup_test_img
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+_supported_fmt qcow2 luks
+_supported_proto file #TODO
+
+QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT
+
+if [ "$IMGFMT" = "qcow2" ] ; then
+	PR="encrypt."
+	EXTRA_IMG_ARGS="-o encrypt.format=luks"
+fi
+
+
+# secrets: you are supposed to see the password as *******, see :-)
+S0="--object secret,id=sec0,data=hunter0"
+S1="--object secret,id=sec1,data=hunter1"
+S2="--object secret,id=sec2,data=hunter2"
+S3="--object secret,id=sec3,data=hunter3"
+S4="--object secret,id=sec4,data=hunter4"
+SECRETS="$S0 $S1 $S2 $S3 $S4"
+
+# image with given secret
+IMGS0="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec0"
+IMGS1="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec1"
+IMGS2="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec2"
+IMGS3="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec3"
+IMGS4="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,${PR}key-secret=sec4"
+
+
+echo "== creating a test image =="
+_make_test_img $S0 $EXTRA_IMG_ARGS -o ${PR}key-secret=sec0,${PR}iter-time=10 32M
+
+echo
+echo "== test that key 0 opens the image =="
+$QEMU_IO $S0 -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir
+
+echo
+echo "== adding a password to slot 4 =="
+$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}state=active,${PR}new-secret=sec4,${PR}iter-time=10,${PR}keyslot=4
+echo "== adding a password to slot 1 =="
+$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}state=active,${PR}new-secret=sec1,${PR}iter-time=10
+echo "== adding a password to slot 3 =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}state=active,${PR}new-secret=sec3,${PR}iter-time=10,${PR}keyslot=3
+
+echo "== adding a password to slot 2 =="
+$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}state=active,${PR}new-secret=sec2,${PR}iter-time=10
+
+
+echo "== erase slot 4 =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}state=inactive,${PR}keyslot=4 | _filter_img_create
+
+
+echo
+echo "== all secrets should work =="
+for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do
+	$QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir
+done
+
+echo
+echo "== erase slot 0 and try it =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}state=inactive,${PR}old-secret=sec0 | _filter_img_create
+$QEMU_IO $SECRETS -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir
+
+echo
+echo "== erase slot 2 and try it =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}state=inactive,${PR}keyslot=2 | _filter_img_create
+$QEMU_IO $SECRETS -c "read 0 4096" $IMGS2 | _filter_qemu_io | _filter_testdir
+
+
+# at this point slots 1 and 3 should be active
+
+echo
+echo "== filling  4 slots with secret 2 =="
+for i in $(seq 0 3) ; do
+	$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}state=active,${PR}new-secret=sec2,${PR}iter-time=10
+done
+
+echo
+echo "== adding secret 0 =="
+	$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}state=active,${PR}new-secret=sec0,${PR}iter-time=10
+
+echo
+echo "== adding secret 3 (last slot) =="
+	$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}state=active,${PR}new-secret=sec3,${PR}iter-time=10
+
+echo
+echo "== trying to add another slot (should fail) =="
+$QEMU_IMG amend $SECRETS $IMGS2 -o ${PR}state=active,${PR}new-secret=sec3,${PR}iter-time=10
+
+echo
+echo "== all secrets should work again =="
+for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do
+	$QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir
+done
+
+
+echo
+
+echo "== erase all keys of secret 2=="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}state=inactive,${PR}old-secret=sec2
+
+echo "== erase all keys of secret 1=="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}state=inactive,${PR}old-secret=sec1
+
+echo "== erase all keys of secret 0=="
+$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}state=inactive,${PR}old-secret=sec0
+
+echo "== erasing secret3 will fail now since it is the only secret (in 3 slots) =="
+$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}state=inactive,${PR}old-secret=sec3
+
+echo
+echo "== only secret3 should work now  =="
+for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do
+	$QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir
+done
+
+echo
+echo "== add secret0  =="
+$QEMU_IMG amend $SECRETS $IMGS3 -o ${PR}state=active,${PR}new-secret=sec0,${PR}iter-time=10
+
+echo "== erase secret3 =="
+$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}state=inactive,${PR}old-secret=sec3
+
+echo
+echo "== only secret0 should work now  =="
+for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do
+	$QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir
+done
+
+echo
+echo "== replace secret0 with secret1 (should fail)  =="
+$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}state=active,${PR}new-secret=sec1,${PR}keyslot=0
+
+echo
+echo "== replace secret0 with secret1 with force (should work)  =="
+$QEMU_IMG amend $SECRETS $IMGS0 -o ${PR}state=active,${PR}new-secret=sec1,${PR}iter-time=10,${PR}keyslot=0 --force
+
+echo
+echo "== only secret1 should work now  =="
+for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do
+	$QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir
+done
+
+
+echo
+echo "== erase last secret (should fail)  =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}state=inactive,${PR}keyslot=0
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}state=inactive,${PR}old-secret=sec1
+
+
+echo "== erase non existing secrets (should fail)  =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}state=inactive,${PR}old-secret=sec5 --force
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}state=inactive,${PR}old-secret=sec0 --force
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}state=inactive,${PR}keyslot=1 --force
+
+echo
+echo "== erase last secret with force by slot (should work)  =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o ${PR}state=inactive,${PR}keyslot=0 --force
+
+echo
+echo "== we have no secrets now, data is lost forever =="
+for IMG in "$IMGS0" "$IMGS1" "$IMGS2" "$IMGS3"; do
+	$QEMU_IO $SECRETS -c "read 0 4096" $IMG | _filter_qemu_io | _filter_testdir
+done
+
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
+
diff --git a/tests/qemu-iotests/300.out b/tests/qemu-iotests/300.out
new file mode 100644
index 0000000000..09ca556387
--- /dev/null
+++ b/tests/qemu-iotests/300.out
@@ -0,0 +1,99 @@
+QA output created by 300
+== creating a test image ==
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432
+
+== test that key 0 opens the image ==
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== adding a password to slot 4 ==
+== adding a password to slot 1 ==
+== adding a password to slot 3 ==
+== adding a password to slot 2 ==
+== erase slot 4 ==
+
+== all secrets should work ==
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== erase slot 0 and try it ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+
+== erase slot 2 and try it ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+
+== filling  4 slots with secret 2 ==
+
+== adding secret 0 ==
+
+== adding secret 3 (last slot) ==
+
+== trying to add another slot (should fail) ==
+qemu-img: Can't add a keyslot - all key slots are in use
+
+== all secrets should work again ==
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== erase all keys of secret 2==
+== erase all keys of secret 1==
+== erase all keys of secret 0==
+== erasing secret3 will fail now since it is the only secret (in 3 slots) ==
+qemu-img: Requested operation will erase all active keyslots which will erase all the data in the image irreversibly - refusing operation
+
+== only secret3 should work now  ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== add secret0  ==
+== erase secret3 ==
+
+== only secret0 should work now  ==
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+
+== replace secret0 with secret1 (should fail)  ==
+qemu-img: Refusing to overwrite active slot 0 - please erase it first
+
+== replace secret0 with secret1 with force (should work)  ==
+
+== only secret1 should work now  ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+
+== erase last secret (should fail)  ==
+qemu-img: Requested operation will erase all active keyslots which will erase all the data in the image irreversibly - refusing operation
+qemu-img: Requested operation will erase all active keyslots which will erase all the data in the image irreversibly - refusing operation
+== erase non existing secrets (should fail)  ==
+qemu-img: No secret with id 'sec5'
+qemu-img: Requested operation didn't match any slots
+
+== erase last secret with force by slot (should work)  ==
+
+== we have no secrets now, data is lost forever ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+*** done
diff --git a/tests/qemu-iotests/301 b/tests/qemu-iotests/301
new file mode 100755
index 0000000000..9c95ed8c9a
--- /dev/null
+++ b/tests/qemu-iotests/301
@@ -0,0 +1,90 @@
+#
+# Copyright (C) 2019 Red Hat, Inc.
+#
+# 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/>.
+#
+
+# creator
+owner=mlevitsk@redhat.com
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+status=1	# failure is the default!
+
+_cleanup()
+{
+	_cleanup_test_img
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+_supported_fmt luks
+_supported_proto file #TODO
+
+QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT
+
+# you are supposed to see the password as *******, see :-)
+S0="--object secret,id=sec0,data=hunter0"
+S1="--object secret,id=sec1,data=hunter1"
+SECRETS="$S0 $S1"
+
+
+IMGS0="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,key-secret=sec0"
+IMGS1="--image-opts driver=$IMGFMT,file.filename=$TEST_IMG,key-secret=sec1"
+
+echo "== creating a test image =="
+_make_test_img $S0 -o "key-secret=sec0,iter-time=10" 32M
+
+echo
+echo "== test that key 0 opens the image =="
+$QEMU_IO $S0 -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir
+
+echo
+echo "== adding a password to slot 1 =="
+$QEMU_IMG amend $SECRETS $IMGS0 -o state=active,new-secret=sec1,keyslot=1,iter-time=10
+
+echo
+echo "== 'backup' the image header =="
+dd if=$TEST_IMG_FILE of=${TEST_IMG_FILE}.bk bs=4K skip=0 count=1
+
+echo
+echo "== erase slot 0 =="
+$QEMU_IMG amend $SECRETS $IMGS1 -o state=inactive,keyslot=0 | _filter_img_create
+
+echo
+echo "== test that key 0 doesn't open the image =="
+$QEMU_IO $S0 -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir
+
+echo
+echo "== 'restore' the image header =="
+dd if=${TEST_IMG_FILE}.bk of=${TEST_IMG_FILE} bs=4K skip=0 count=1 conv=notrunc
+
+echo
+echo "== test that key 0 still doesn't open the image (key material is erased) =="
+$QEMU_IO $SECRETS -c "read 0 4096" $IMGS0 | _filter_qemu_io | _filter_testdir
+
+echo
+echo "== test that key 1 still works =="
+$QEMU_IO $SECRETS -c "read 0 4096" $IMGS1 | _filter_qemu_io | _filter_testdir
+
+echo "*** done"
+rm -f $seq.full
+status=0
+
+
+exit 0
diff --git a/tests/qemu-iotests/301.out b/tests/qemu-iotests/301.out
new file mode 100644
index 0000000000..e653c30330
--- /dev/null
+++ b/tests/qemu-iotests/301.out
@@ -0,0 +1,30 @@
+QA output created by 301
+== creating a test image ==
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432
+
+== test that key 0 opens the image ==
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+== adding a password to slot 1 ==
+
+== 'backup' the image header ==
+1+0 records in
+1+0 records out
+
+== erase slot 0 ==
+
+== test that key 0 doesn't open the image ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+
+== 'restore' the image header ==
+1+0 records in
+1+0 records out
+
+== test that key 0 still doesn't open the image (key material is erased) ==
+qemu-io: can't open: Invalid password, cannot unlock any keyslot
+
+== test that key 1 still works ==
+read 4096/4096 bytes at offset 0
+4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+*** done
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 0317667695..0941a4f64e 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -293,3 +293,6 @@
 283 auto quick
 284 rw
 286 rw quick
+
+300 rw auto
+301 rw auto quick
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 11/14] block/core: add generic infrastructure for x-blockdev-amend qmp command
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (9 preceding siblings ...)
  2020-03-08 15:18 ` [PATCH v2 10/14] iotests: qemu-img tests for luks key management Maxim Levitsky
@ 2020-03-08 15:19 ` Maxim Levitsky
  2020-04-28 16:25   ` Daniel P. Berrangé
  2020-03-08 15:19 ` [PATCH v2 12/14] block/crypto: implement blockdev-amend Maxim Levitsky
                   ` (3 subsequent siblings)
  14 siblings, 1 reply; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

blockdev-amend will be used similiar to blockdev-create
to allow on the fly changes of the structure of the format based block devices.

Current plan is to first support encryption keyslot management for luks
based formats (raw and embedded in qcow2)

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/Makefile.objs       |   2 +-
 block/amend.c             | 108 ++++++++++++++++++++++++++++++++++++++
 include/block/block_int.h |  21 +++++---
 qapi/block-core.json      |  42 +++++++++++++++
 qapi/job.json             |   4 +-
 5 files changed, 169 insertions(+), 8 deletions(-)
 create mode 100644 block/amend.c

diff --git a/block/Makefile.objs b/block/Makefile.objs
index 3bcb35c81d..5f0e60e7b4 100644
--- a/block/Makefile.objs
+++ b/block/Makefile.objs
@@ -19,7 +19,7 @@ block-obj-$(CONFIG_WIN32) += file-win32.o win32-aio.o
 block-obj-$(CONFIG_POSIX) += file-posix.o
 block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
 block-obj-$(CONFIG_LINUX_IO_URING) += io_uring.o
-block-obj-y += null.o mirror.o commit.o io.o create.o
+block-obj-y += null.o mirror.o commit.o io.o create.o amend.o
 block-obj-y += throttle-groups.o
 block-obj-$(CONFIG_LINUX) += nvme.o
 
diff --git a/block/amend.c b/block/amend.c
new file mode 100644
index 0000000000..2db7b1eafc
--- /dev/null
+++ b/block/amend.c
@@ -0,0 +1,108 @@
+/*
+ * Block layer code related to image options amend
+ *
+ * Copyright (c) 2018 Kevin Wolf <kwolf@redhat.com>
+ * Copyright (c) 2019 Maxim Levitsky <mlevitsk@redhat.com>
+ *
+ * Heavily based on create.c
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "block/block_int.h"
+#include "qemu/job.h"
+#include "qemu/main-loop.h"
+#include "qapi/qapi-commands-block-core.h"
+#include "qapi/qapi-visit-block-core.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/error.h"
+
+typedef struct BlockdevAmendJob {
+    Job common;
+    BlockdevAmendOptions *opts;
+    BlockDriverState *bs;
+    bool force;
+} BlockdevAmendJob;
+
+static int coroutine_fn blockdev_amend_run(Job *job, Error **errp)
+{
+    BlockdevAmendJob *s = container_of(job, BlockdevAmendJob, common);
+    int ret;
+
+    job_progress_set_remaining(&s->common, 1);
+    ret = s->bs->drv->bdrv_co_amend(s->bs, s->opts, s->force, errp);
+    job_progress_update(&s->common, 1);
+    qapi_free_BlockdevAmendOptions(s->opts);
+    return ret;
+}
+
+static const JobDriver blockdev_amend_job_driver = {
+    .instance_size = sizeof(BlockdevAmendJob),
+    .job_type      = JOB_TYPE_AMEND,
+    .run           = blockdev_amend_run,
+};
+
+void qmp_x_blockdev_amend(const char *job_id,
+                          const char *node_name,
+                          BlockdevAmendOptions *options,
+                          bool has_force,
+                          bool force,
+                          Error **errp)
+{
+    BlockdevAmendJob *s;
+    const char *fmt = BlockdevDriver_str(options->driver);
+    BlockDriver *drv = bdrv_find_format(fmt);
+    BlockDriverState *bs = bdrv_find_node(node_name);
+
+    /*
+     * If the driver is in the schema, we know that it exists. But it may not
+     * be whitelisted.
+     */
+    assert(drv);
+    if (bdrv_uses_whitelist() && !bdrv_is_whitelisted(drv, false)) {
+        error_setg(errp, "Driver is not whitelisted");
+        return;
+    }
+
+    if (bs->drv != drv) {
+        error_setg(errp,
+                   "x-blockdev-amend doesn't support changing the block driver");
+        return;
+    }
+
+    /* Error out if the driver doesn't support .bdrv_co_amend */
+    if (!drv->bdrv_co_amend) {
+        error_setg(errp, "Driver does not support x-blockdev-amend");
+        return;
+    }
+
+    /* Create the block job */
+    s = job_create(job_id, &blockdev_amend_job_driver, NULL,
+                   bdrv_get_aio_context(bs), JOB_DEFAULT | JOB_MANUAL_DISMISS,
+                   NULL, NULL, errp);
+    if (!s) {
+        return;
+    }
+
+    s->bs = bs,
+    s->opts = QAPI_CLONE(BlockdevAmendOptions, options),
+    s->force = has_force ? force : false;
+    job_start(&s->common);
+}
diff --git a/include/block/block_int.h b/include/block/block_int.h
index 48a4c2fa1c..c993cf9540 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -132,11 +132,26 @@ struct BlockDriver {
     int (*bdrv_file_open)(BlockDriverState *bs, QDict *options, int flags,
                           Error **errp);
     void (*bdrv_close)(BlockDriverState *bs);
+
+
     int coroutine_fn (*bdrv_co_create)(BlockdevCreateOptions *opts,
                                        Error **errp);
     int coroutine_fn (*bdrv_co_create_opts)(const char *filename,
                                             QemuOpts *opts,
                                             Error **errp);
+
+    int coroutine_fn (*bdrv_co_amend)(BlockDriverState *bs,
+                                      BlockdevAmendOptions *opts,
+                                      bool force,
+                                      Error **errp);
+
+    int (*bdrv_amend_options)(BlockDriverState *bs,
+                              QemuOpts *opts,
+                              BlockDriverAmendStatusCB *status_cb,
+                              void *cb_opaque,
+                              bool force,
+                              Error **errp);
+
     int (*bdrv_make_empty)(BlockDriverState *bs);
 
     /*
@@ -427,12 +442,6 @@ struct BlockDriver {
                                       BdrvCheckResult *result,
                                       BdrvCheckMode fix);
 
-    int (*bdrv_amend_options)(BlockDriverState *bs, QemuOpts *opts,
-                              BlockDriverAmendStatusCB *status_cb,
-                              void *cb_opaque,
-                              bool force,
-                              Error **errp);
-
     void (*bdrv_debug_event)(BlockDriverState *bs, BlkdebugEvent event);
 
     /* TODO Better pass a option string/QDict/QemuOpts to add any rule? */
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 85e27bb61f..192da75a10 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -4757,6 +4757,48 @@
   'data': { 'job-id': 'str',
             'options': 'BlockdevCreateOptions' } }
 
+##
+# @BlockdevAmendOptions:
+#
+# Options for amending an image format
+#
+# @driver           block driver that is suitable for the image
+#
+# Since: 5.0
+##
+{ 'union': 'BlockdevAmendOptions',
+  'base': {
+      'driver':         'BlockdevDriver' },
+  'discriminator': 'driver',
+  'data': {
+  } }
+
+##
+# @x-blockdev-amend:
+#
+# Starts a job to amend format specific options of an existing open block device
+# The job is automatically finalized, but a manual job-dismiss is required.
+#
+# @job-id:          Identifier for the newly created job.
+#
+# @node-name:       Name of the block node to work on
+#
+# @options:         Options (driver specific)
+#
+# @force:           Allow unsafe operations, format specific
+#                   For luks that allows erase of the last active keyslot
+#                   (permanent loss of data),
+#                   and replacement of an active keyslot
+#                   (possible loss of data if IO error happens)
+#
+# Since: 5.0
+##
+{ 'command': 'x-blockdev-amend',
+  'data': { 'job-id': 'str',
+            'node-name': 'str',
+            'options': 'BlockdevAmendOptions',
+            '*force': 'bool' } }
+
 ##
 # @blockdev-open-tray:
 #
diff --git a/qapi/job.json b/qapi/job.json
index 5e658281f5..7e4f7fcaa4 100644
--- a/qapi/job.json
+++ b/qapi/job.json
@@ -19,10 +19,12 @@
 #
 # @create: image creation job type, see "blockdev-create" (since 3.0)
 #
+# @amend: image options amend job type, see "x-blockdev-amend" (since 5.0)
+#
 # Since: 1.7
 ##
 { 'enum': 'JobType',
-  'data': ['commit', 'stream', 'mirror', 'backup', 'create'] }
+  'data': ['commit', 'stream', 'mirror', 'backup', 'create', 'amend'] }
 
 ##
 # @JobStatus:
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 12/14] block/crypto: implement blockdev-amend
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (10 preceding siblings ...)
  2020-03-08 15:19 ` [PATCH v2 11/14] block/core: add generic infrastructure for x-blockdev-amend qmp command Maxim Levitsky
@ 2020-03-08 15:19 ` Maxim Levitsky
  2020-03-08 15:19 ` [PATCH v2 13/14] block/qcow2: " Maxim Levitsky
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
---
 block/crypto.c       | 72 ++++++++++++++++++++++++++++++++------------
 qapi/block-core.json | 14 ++++++++-
 2 files changed, 66 insertions(+), 20 deletions(-)

diff --git a/block/crypto.c b/block/crypto.c
index 727a3fde58..389586200f 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -694,32 +694,21 @@ block_crypto_get_specific_info_luks(BlockDriverState *bs, Error **errp)
 }
 
 static int
-block_crypto_amend_options_luks(BlockDriverState *bs,
-                           QemuOpts *opts,
-                           BlockDriverAmendStatusCB *status_cb,
-                           void *cb_opaque,
-                           bool force,
-                           Error **errp)
+block_crypto_amend_options_generic_luks(BlockDriverState *bs,
+                                        QCryptoBlockAmendOptions *amend_options,
+                                        bool force,
+                                        Error **errp)
 {
     BlockCrypto *crypto = bs->opaque;
-    QDict *cryptoopts = NULL;
-    QCryptoBlockAmendOptions *amend_options = NULL;
     int ret;
 
     assert(crypto);
     assert(crypto->block);
-    crypto->updating_keys = true;
 
+    /* apply for exclusive read/write permissions to the underlying file*/
+    crypto->updating_keys = true;
     ret = bdrv_child_refresh_perms(bs, bs->file, errp);
-    if (ret < 0) {
-        goto cleanup;
-    }
-
-    cryptoopts = qemu_opts_to_qdict(opts, NULL);
-    qdict_put_str(cryptoopts, "format", "luks");
-    amend_options = block_crypto_amend_opts_init(cryptoopts, errp);
-    if (!amend_options) {
-        ret = -EINVAL;
+    if (ret) {
         goto cleanup;
     }
 
@@ -731,13 +720,57 @@ block_crypto_amend_options_luks(BlockDriverState *bs,
                                       force,
                                       errp);
 cleanup:
+    /* release exclusive read/write permissions to the underlying file*/
     crypto->updating_keys = false;
     bdrv_child_refresh_perms(bs, bs->file, errp);
-    qapi_free_QCryptoBlockAmendOptions(amend_options);
+    return ret;
+}
+
+static int
+block_crypto_amend_options_luks(BlockDriverState *bs,
+                                QemuOpts *opts,
+                                BlockDriverAmendStatusCB *status_cb,
+                                void *cb_opaque,
+                                bool force,
+                                Error **errp)
+{
+    BlockCrypto *crypto = bs->opaque;
+    QDict *cryptoopts = NULL;
+    QCryptoBlockAmendOptions *amend_options = NULL;
+    int ret = -EINVAL;
+
+    assert(crypto);
+    assert(crypto->block);
+
+    cryptoopts = qemu_opts_to_qdict(opts, NULL);
+    qdict_put_str(cryptoopts, "format", "luks");
+    amend_options = block_crypto_amend_opts_init(cryptoopts, errp);
     qobject_unref(cryptoopts);
+    if (!amend_options) {
+        goto cleanup;
+    }
+    ret = block_crypto_amend_options_generic_luks(bs, amend_options,
+                                                  force, errp);
+cleanup:
+    qapi_free_QCryptoBlockAmendOptions(amend_options);
     return ret;
 }
 
+static int
+coroutine_fn block_crypto_co_amend_luks(BlockDriverState *bs,
+                                        BlockdevAmendOptions *opts,
+                                        bool force,
+                                        Error **errp)
+{
+    QCryptoBlockAmendOptions amend_opts;
+
+    amend_opts = (QCryptoBlockAmendOptions) {
+        .format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
+        .u.luks = *qapi_BlockdevAmendOptionsLUKS_base(&opts->u.luks),
+    };
+    return block_crypto_amend_options_generic_luks(bs, &amend_opts,
+                                                   force, errp);
+}
 
 static void
 block_crypto_child_perms(BlockDriverState *bs, BdrvChild *c,
@@ -809,6 +842,7 @@ static BlockDriver bdrv_crypto_luks = {
     .bdrv_get_info      = block_crypto_get_info_luks,
     .bdrv_get_specific_info = block_crypto_get_specific_info_luks,
     .bdrv_amend_options = block_crypto_amend_options_luks,
+    .bdrv_co_amend      = block_crypto_co_amend_luks,
 
     .strong_runtime_opts = block_crypto_strong_runtime_opts,
 };
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 192da75a10..967b5738c9 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -4757,6 +4757,18 @@
   'data': { 'job-id': 'str',
             'options': 'BlockdevCreateOptions' } }
 
+##
+# @BlockdevAmendOptionsLUKS:
+#
+# Driver specific image amend options for LUKS.
+#
+# Since: 5.0
+##
+{ 'struct': 'BlockdevAmendOptionsLUKS',
+  'base': 'QCryptoBlockAmendOptionsLUKS',
+  'data': { }
+}
+
 ##
 # @BlockdevAmendOptions:
 #
@@ -4771,7 +4783,7 @@
       'driver':         'BlockdevDriver' },
   'discriminator': 'driver',
   'data': {
-  } }
+      'luks':           'BlockdevAmendOptionsLUKS' } }
 
 ##
 # @x-blockdev-amend:
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 13/14] block/qcow2: implement blockdev-amend
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (11 preceding siblings ...)
  2020-03-08 15:19 ` [PATCH v2 12/14] block/crypto: implement blockdev-amend Maxim Levitsky
@ 2020-03-08 15:19 ` Maxim Levitsky
  2020-03-08 15:19 ` [PATCH v2 14/14] iotests: add tests for blockdev-amend Maxim Levitsky
  2020-03-12 11:56 ` [PATCH v2 00/14] LUKS: encryption slot management using amend interface Eric Blake
  14 siblings, 0 replies; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

Currently the implementation only supports amending the encryption
options, unlike the qemu-img version

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
---
 block/qcow2.c        | 39 +++++++++++++++++++++++++++++++++++++++
 qapi/block-core.json | 16 +++++++++++++++-
 2 files changed, 54 insertions(+), 1 deletion(-)

diff --git a/block/qcow2.c b/block/qcow2.c
index 10b22544f2..8fde20344d 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -5397,6 +5397,44 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
     return 0;
 }
 
+static int coroutine_fn qcow2_co_amend(BlockDriverState *bs,
+                                       BlockdevAmendOptions *opts,
+                                       bool force,
+                                       Error **errp)
+{
+    BlockdevAmendOptionsQcow2 *qopts = &opts->u.qcow2;
+    BDRVQcow2State *s = bs->opaque;
+    int ret = 0;
+
+    if (qopts->has_encrypt) {
+        if (!s->crypto) {
+            error_setg(errp, "image is not encrypted, can't amend");
+            return -EOPNOTSUPP;
+        }
+
+        if (qopts->encrypt->format != Q_CRYPTO_BLOCK_FORMAT_LUKS) {
+            error_setg(errp,
+                       "Amend can't be used to change the qcow2 encryption format");
+            return -EOPNOTSUPP;
+        }
+
+        if (s->crypt_method_header != QCOW_CRYPT_LUKS) {
+            error_setg(errp,
+                       "Only LUKS encryption options can be amended for qcow2 with blockdev-amend");
+            return -EOPNOTSUPP;
+        }
+
+        ret = qcrypto_block_amend_options(s->crypto,
+                                          qcow2_crypto_hdr_read_func,
+                                          qcow2_crypto_hdr_write_func,
+                                          bs,
+                                          qopts->encrypt,
+                                          force,
+                                          errp);
+    }
+    return ret;
+}
+
 /*
  * If offset or size are negative, respectively, they will not be included in
  * the BLOCK_IMAGE_CORRUPTED event emitted.
@@ -5606,6 +5644,7 @@ BlockDriver bdrv_qcow2 = {
     .mutable_opts        = mutable_opts,
     .bdrv_co_check       = qcow2_co_check,
     .bdrv_amend_options  = qcow2_amend_options,
+    .bdrv_co_amend       = qcow2_co_amend,
 
     .bdrv_detach_aio_context  = qcow2_detach_aio_context,
     .bdrv_attach_aio_context  = qcow2_attach_aio_context,
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 967b5738c9..4b69b0e195 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -4769,6 +4769,19 @@
   'data': { }
 }
 
+##
+# @BlockdevAmendOptionsQcow2:
+#
+# Driver specific image amend options for qcow2.
+# For now, only encryption options can be amended
+#
+# @encrypt          Encryption options to be amended
+#
+# Since: 5.0
+##
+{ 'struct': 'BlockdevAmendOptionsQcow2',
+  'data': { '*encrypt':         'QCryptoBlockAmendOptions' } }
+
 ##
 # @BlockdevAmendOptions:
 #
@@ -4783,7 +4796,8 @@
       'driver':         'BlockdevDriver' },
   'discriminator': 'driver',
   'data': {
-      'luks':           'BlockdevAmendOptionsLUKS' } }
+      'luks':           'BlockdevAmendOptionsLUKS',
+      'qcow2':          'BlockdevAmendOptionsQcow2' } }
 
 ##
 # @x-blockdev-amend:
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 14/14] iotests: add tests for blockdev-amend
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (12 preceding siblings ...)
  2020-03-08 15:19 ` [PATCH v2 13/14] block/qcow2: " Maxim Levitsky
@ 2020-03-08 15:19 ` Maxim Levitsky
  2020-04-28 16:23   ` Daniel P. Berrangé
  2020-03-12 11:56 ` [PATCH v2 00/14] LUKS: encryption slot management using amend interface Eric Blake
  14 siblings, 1 reply; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-08 15:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, Maxim Levitsky,
	John Snow

This commit adds two tests that cover the
new blockdev-amend functionality of luks and qcow2 driver

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 tests/qemu-iotests/302     | 278 +++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/302.out |  40 ++++++
 tests/qemu-iotests/303     | 233 +++++++++++++++++++++++++++++++
 tests/qemu-iotests/303.out |  33 +++++
 tests/qemu-iotests/group   |   3 +
 5 files changed, 587 insertions(+)
 create mode 100755 tests/qemu-iotests/302
 create mode 100644 tests/qemu-iotests/302.out
 create mode 100755 tests/qemu-iotests/303
 create mode 100644 tests/qemu-iotests/303.out

diff --git a/tests/qemu-iotests/302 b/tests/qemu-iotests/302
new file mode 100755
index 0000000000..a6b1155c33
--- /dev/null
+++ b/tests/qemu-iotests/302
@@ -0,0 +1,278 @@
+#!/usr/bin/env python3
+#
+# Test case QMP's encrypted key management
+#
+# Copyright (C) 2019 Red Hat, Inc.
+#
+# 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
+import os
+import time
+import json
+
+test_img = os.path.join(iotests.test_dir, 'test.img')
+
+class Secret:
+    def __init__(self, index):
+        self._id = "keysec" + str(index)
+        # you are not supposed to see the password...
+        self._secret = "hunter" + str(index)
+
+    def id(self):
+        return self._id
+
+    def secret(self):
+        return self._secret
+
+    def to_cmdline_object(self):
+        return  [ "secret,id=" + self._id + ",data=" + self._secret]
+
+    def to_qmp_object(self):
+        return { "qom_type" : "secret", "id": self.id(),
+                 "props": { "data": self.secret() } }
+
+################################################################################
+class EncryptionSetupTestCase(iotests.QMPTestCase):
+
+    # test case startup
+    def setUp(self):
+        # start the VM
+        self.vm = iotests.VM()
+        self.vm.launch()
+
+        # create the secrets and load 'em into the VM
+        self.secrets = [ Secret(i) for i in range(0, 6) ]
+        for secret in self.secrets:
+            result = self.vm.qmp("object-add", **secret.to_qmp_object())
+            self.assert_qmp(result, 'return', {})
+
+        if iotests.imgfmt == "qcow2":
+            self.pfx = "encrypt."
+            self.img_opts = [ '-o', "encrypt.format=luks" ]
+        else:
+            self.pfx = ""
+            self.img_opts = []
+
+    # test case shutdown
+    def tearDown(self):
+        # stop the VM
+        self.vm.shutdown()
+
+    ###########################################################################
+    # create the encrypted block device
+    def createImg(self, file, secret):
+
+        iotests.qemu_img(
+            'create',
+            '--object', *secret.to_cmdline_object(),
+            '-f', iotests.imgfmt,
+            '-o', self.pfx + 'key-secret=' + secret.id(),
+            '-o', self.pfx + 'iter-time=10',
+            *self.img_opts,
+            file,
+            '1M')
+
+    ###########################################################################
+    # open an encrypted block device
+    def openImageQmp(self, id, file, secret, read_only = False):
+
+        encrypt_options = {
+            'key-secret' : secret.id()
+        }
+
+        if iotests.imgfmt == "qcow2":
+            encrypt_options = {
+                'encrypt': {
+                    'format':'luks',
+                    **encrypt_options
+                }
+            }
+
+        result = self.vm.qmp('blockdev-add', **
+            {
+                'driver': iotests.imgfmt,
+                'node-name': id,
+                'read-only': read_only,
+
+                **encrypt_options,
+
+                'file': {
+                    'driver': 'file',
+                    'filename': test_img,
+                }
+            }
+        )
+        self.assert_qmp(result, 'return', {})
+
+    # close the encrypted block device
+    def closeImageQmp(self, id):
+        result = self.vm.qmp('blockdev-del', **{ 'node-name': id })
+        self.assert_qmp(result, 'return', {})
+
+    ###########################################################################
+    # add a key to an encrypted block device
+    def addKeyQmp(self, id, secret, unlock_secret = None,
+                  slot = None, force = False):
+
+        crypt_options = {
+            'state'      : 'active',
+            'new-secret' : secret.id(),
+            'iter-time' : 10
+        }
+
+        if slot != None:
+            crypt_options['keyslot'] = slot
+
+
+        if unlock_secret != None:
+            crypt_options['unlock-secret'] = unlock_secret.id()
+
+        if iotests.imgfmt == "qcow2":
+            crypt_options['format'] = 'luks'
+            crypt_options = {
+                'encrypt': crypt_options
+            }
+
+        args = {
+            'node-name': id,
+            'job-id' : 'job_add_key',
+            'options' : {
+                    'driver' : iotests.imgfmt,
+                    **crypt_options
+                },
+        }
+
+        if force == True:
+            args['force'] = True
+
+        #TODO: check what jobs return
+        result = self.vm.qmp('x-blockdev-amend', **args)
+        assert result['return'] == {}
+        self.vm.run_job('job_add_key')
+
+    # erase a key from an encrypted block device
+    def eraseKeyQmp(self, id, secret = None, slot = None, force = False):
+
+        crypt_options = {
+            'state'      : 'inactive',
+        }
+
+        if slot != None:
+            crypt_options['keyslot'] = slot
+        if secret != None:
+            crypt_options['old-secret'] = secret.id()
+
+        if iotests.imgfmt == "qcow2":
+            crypt_options['format'] = 'luks'
+            crypt_options = {
+                'encrypt': crypt_options
+            }
+
+        args = {
+            'node-name': id,
+            'job-id' : 'job_erase_key',
+            'options' : {
+                    'driver' : iotests.imgfmt,
+                    **crypt_options
+                },
+        }
+
+        if force == True:
+            args['force'] = True
+
+        result = self.vm.qmp('x-blockdev-amend', **args)
+        assert result['return'] == {}
+        self.vm.run_job('job_erase_key')
+
+    ###########################################################################
+    # create image, and change its key
+    def testChangeKey(self):
+
+        # create the image with secret0 and open it
+        self.createImg(test_img, self.secrets[0]);
+        self.openImageQmp("testdev", test_img, self.secrets[0])
+
+        # add key to slot 1
+        self.addKeyQmp("testdev", secret = self.secrets[1])
+
+        # add key to slot 5
+        self.addKeyQmp("testdev", secret = self.secrets[2], slot=5)
+
+        # erase key from slot 0
+        self.eraseKeyQmp("testdev", secret = self.secrets[0])
+
+        #reopen the image with secret1
+        self.closeImageQmp("testdev")
+        self.openImageQmp("testdev", test_img, self.secrets[1])
+
+        # close and erase the image for good
+        self.closeImageQmp("testdev")
+        os.remove(test_img)
+
+    # test that if we erase the old password,
+    # we can still change the encryption keys using 'old-secret'
+    def testOldPassword(self):
+
+        # create the image with secret0 and open it
+        self.createImg(test_img, self.secrets[0]);
+        self.openImageQmp("testdev", test_img, self.secrets[0])
+
+        # add key to slot 1
+        self.addKeyQmp("testdev", secret = self.secrets[1])
+
+        # erase key from slot 0
+        self.eraseKeyQmp("testdev", secret = self.secrets[0])
+
+        # this will fail as the old password is no longer valid
+        self.addKeyQmp("testdev", secret = self.secrets[2])
+
+        # this will work
+        self.addKeyQmp("testdev", secret = self.secrets[2], unlock_secret = self.secrets[1])
+
+        # close and erase the image for good
+        self.closeImageQmp("testdev")
+        os.remove(test_img)
+
+    def testUseForceLuke(self):
+
+        self.createImg(test_img, self.secrets[0]);
+        self.openImageQmp("testdev", test_img, self.secrets[0])
+
+        # Add bunch of secrets
+        self.addKeyQmp("testdev", secret = self.secrets[1], slot=4)
+        self.addKeyQmp("testdev", secret = self.secrets[4], slot=2)
+
+        # overwrite an active secret
+        self.addKeyQmp("testdev", secret = self.secrets[5], slot=2)
+        self.addKeyQmp("testdev", secret = self.secrets[5], slot=2, force=True)
+
+        self.addKeyQmp("testdev", secret = self.secrets[0])
+
+        # Now erase all the secrets
+        self.eraseKeyQmp("testdev", secret = self.secrets[5])
+        self.eraseKeyQmp("testdev", slot=4)
+
+        # erase last keyslot
+        self.eraseKeyQmp("testdev", secret = self.secrets[0])
+        self.eraseKeyQmp("testdev", secret = self.secrets[0], force=True)
+
+        self.closeImageQmp("testdev")
+        os.remove(test_img)
+
+
+if __name__ == '__main__':
+    # Encrypted formats support
+    iotests.main(supported_fmts = ['qcow2', 'luks'])
diff --git a/tests/qemu-iotests/302.out b/tests/qemu-iotests/302.out
new file mode 100644
index 0000000000..da4d43d31e
--- /dev/null
+++ b/tests/qemu-iotests/302.out
@@ -0,0 +1,40 @@
+...
+----------------------------------------------------------------------
+Ran 3 tests
+
+OK
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
+{"return": {}}
+Job failed: Invalid password, cannot unlock any keyslot
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+Job failed: Refusing to overwrite active slot 2 - please erase it first
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_add_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
+{"return": {}}
+Job failed: Requested operation will erase all active keyslots which will erase all the data in the image irreversibly - refusing operation
+{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job_erase_key"}}
+{"return": {}}
diff --git a/tests/qemu-iotests/303 b/tests/qemu-iotests/303
new file mode 100755
index 0000000000..58f6ba3ef7
--- /dev/null
+++ b/tests/qemu-iotests/303
@@ -0,0 +1,233 @@
+#!/usr/bin/env python3
+#
+# Test case for encryption key management versus image sharing
+#
+# Copyright (C) 2019 Red Hat, Inc.
+#
+# 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
+import os
+import time
+import json
+
+test_img = os.path.join(iotests.test_dir, 'test.img')
+
+class Secret:
+    def __init__(self, index):
+        self._id = "keysec" + str(index)
+        # you are not supposed to see the password...
+        self._secret = "hunter" + str(index)
+
+    def id(self):
+        return self._id
+
+    def secret(self):
+        return self._secret
+
+    def to_cmdline_object(self):
+        return  [ "secret,id=" + self._id + ",data=" + self._secret]
+
+    def to_qmp_object(self):
+        return { "qom_type" : "secret", "id": self.id(),
+                 "props": { "data": self.secret() } }
+
+################################################################################
+
+class EncryptionSetupTestCase(iotests.QMPTestCase):
+
+    # test case startup
+    def setUp(self):
+
+        # start the VMs
+        self.vm1 = iotests.VM(path_suffix = 'VM1')
+        self.vm2 = iotests.VM(path_suffix = 'VM2')
+        self.vm1.launch()
+        self.vm2.launch()
+
+        # create the secrets and load 'em into the VMs
+        self.secrets = [ Secret(i) for i in range(0, 4) ]
+        for secret in self.secrets:
+            result = self.vm1.qmp("object-add", **secret.to_qmp_object())
+            self.assert_qmp(result, 'return', {})
+            result = self.vm2.qmp("object-add", **secret.to_qmp_object())
+            self.assert_qmp(result, 'return', {})
+
+    # test case shutdown
+    def tearDown(self):
+        # stop the VM
+        self.vm1.shutdown()
+        self.vm2.shutdown()
+
+    ###########################################################################
+    # create the encrypted block device using qemu-img
+    def createImg(self, file, secret):
+
+        output = iotests.qemu_img_pipe(
+            'create',
+            '--object', *secret.to_cmdline_object(),
+            '-f', iotests.imgfmt,
+            '-o', 'key-secret=' + secret.id(),
+            '-o', 'iter-time=10',
+            file,
+            '1M')
+
+        iotests.log(output, filters=[iotests.filter_test_dir])
+
+    # attempts to add a key using qemu-img
+    def addKey(self, file, secret_open, secret_add):
+
+        image_options = {
+            'key-secret' : secret_open.id(),
+            'driver' : iotests.imgfmt,
+            'file' : {
+                'driver':'file',
+                'filename': file,
+                }
+            }
+
+        output = iotests.qemu_img_pipe(
+            'amend',
+            '--object', *secret_open.to_cmdline_object(),
+            '--object', *secret_add.to_cmdline_object(),
+
+            '-o', 'state=active',
+            '-o', 'new-secret=' + secret_add.id(),
+            '-o', 'iter-time=10',
+
+            "json:" + json.dumps(image_options)
+            )
+
+        iotests.log(output, filters=[iotests.filter_test_dir])
+
+    ###########################################################################
+    # open an encrypted block device
+    def openImageQmp(self, vm, id, file, secret,
+                     readOnly = False, reOpen = False):
+
+        command = 'x-blockdev-reopen' if reOpen else 'blockdev-add'
+
+        result = vm.qmp(command, **
+            {
+                'driver': iotests.imgfmt,
+                'node-name': id,
+                'read-only': readOnly,
+                'key-secret' : secret.id(),
+                'file': {
+                    'driver': 'file',
+                    'filename': test_img,
+                }
+            }
+        )
+        self.assert_qmp(result, 'return', {})
+
+    # close the encrypted block device
+    def closeImageQmp(self, vm, id):
+        result = vm.qmp('blockdev-del', **{ 'node-name': id })
+        self.assert_qmp(result, 'return', {})
+
+    ###########################################################################
+
+    # add a key to an encrypted block device
+    def addKeyQmp(self, vm, id, secret):
+
+        args = {
+            'node-name': id,
+            'job-id' : 'job0',
+            'options' : {
+                'state'     : 'active',
+                'driver'    : iotests.imgfmt,
+                'new-secret': secret.id(),
+                'iter-time' : 10
+            },
+        }
+
+        result = vm.qmp('x-blockdev-amend', **args)
+        assert result['return'] == {}
+        vm.run_job('job0')
+
+    # test that when the image opened by two qemu processes,
+    # neither of them can update the image
+    def test1(self):
+        self.createImg(test_img, self.secrets[0]);
+
+        # VM1 opens the image and adds a key
+        self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0])
+        self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[1])
+
+
+        # VM2 opens the image
+        self.openImageQmp(self.vm2, "testdev", test_img, self.secrets[0])
+
+
+        # neither VMs now should be able to add a key
+        self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[2])
+        self.addKeyQmp(self.vm2, "testdev", secret = self.secrets[2])
+
+
+        # VM 1 closes the image
+        self.closeImageQmp(self.vm1, "testdev")
+
+
+        # now VM2 can add the key
+        self.addKeyQmp(self.vm2, "testdev", secret = self.secrets[2])
+
+
+        # qemu-img should also not be able to add a key
+        self.addKey(test_img, self.secrets[0], self.secrets[2])
+
+        # cleanup
+        self.closeImageQmp(self.vm2, "testdev")
+        os.remove(test_img)
+
+
+    def test2(self):
+        self.createImg(test_img, self.secrets[0]);
+
+        # VM1 opens the image readonly
+        self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0],
+                          readOnly = True)
+
+        # VM2 opens the image
+        self.openImageQmp(self.vm2, "testdev", test_img, self.secrets[0])
+
+        # VM1 can't add a key since image is readonly
+        self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[2])
+
+        # VM2 can't add a key since VM is has the image opened
+        self.addKeyQmp(self.vm2, "testdev", secret = self.secrets[2])
+
+
+        #VM1 reopens the image read-write
+        self.openImageQmp(self.vm1, "testdev", test_img, self.secrets[0],
+                          reOpen = True, readOnly = False)
+
+        # VM1 still can't add the key
+        self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[2])
+
+        # VM2 gets away
+        self.closeImageQmp(self.vm2, "testdev")
+
+        # VM1 now can add the key
+        self.addKeyQmp(self.vm1, "testdev", secret = self.secrets[2])
+
+        self.closeImageQmp(self.vm1, "testdev")
+        os.remove(test_img)
+
+
+if __name__ == '__main__':
+    # support only raw luks since luks encrypted qcow2 is a proper
+    # format driver which doesn't allow any sharing
+    iotests.main(supported_fmts = ['luks'])
diff --git a/tests/qemu-iotests/303.out b/tests/qemu-iotests/303.out
new file mode 100644
index 0000000000..a62bb20b67
--- /dev/null
+++ b/tests/qemu-iotests/303.out
@@ -0,0 +1,33 @@
+..
+----------------------------------------------------------------------
+Ran 2 tests
+
+OK
+Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10
+
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+Job failed: Failed to get shared "consistent read" lock
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+Job failed: Failed to get shared "consistent read" lock
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+qemu-img: Failed to get shared "consistent read" lock
+Is another process using the image [TEST_DIR/test.img]?
+
+Formatting 'TEST_DIR/test.img', fmt=luks size=1048576 key-secret=keysec0 iter-time=10
+
+Job failed: Block node is read-only
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+Job failed: Failed to get shared "consistent read" lock
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+Job failed: Failed to get shared "consistent read" lock
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 0941a4f64e..a44f5aff0d 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -296,3 +296,6 @@
 
 300 rw auto
 301 rw auto quick
+302 rw auto
+303 rw auto
+
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 02/14] qcrypto/luks: implement encryption key management
  2020-03-08 15:18 ` [PATCH v2 02/14] qcrypto/luks: implement encryption key management Maxim Levitsky
@ 2020-03-10 10:58   ` Max Reitz
  2020-03-10 11:05     ` Maxim Levitsky
  2020-04-28 13:16   ` Daniel P. Berrangé
  1 sibling, 1 reply; 35+ messages in thread
From: Max Reitz @ 2020-03-10 10:58 UTC (permalink / raw)
  To: Maxim Levitsky, qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, John Snow


[-- Attachment #1.1: Type: text/plain, Size: 2152 bytes --]

On 08.03.20 16:18, Maxim Levitsky wrote:
> Next few patches will expose that functionality
> to the user.
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  crypto/block-luks.c | 398 +++++++++++++++++++++++++++++++++++++++++++-
>  qapi/crypto.json    |  61 ++++++-
>  2 files changed, 455 insertions(+), 4 deletions(-)

[...]

> +##
> +# @QCryptoBlockAmendOptionsLUKS:
> +#
> +# This struct defines the update parameters that activate/de-activate set
> +# of keyslots
> +#
> +# @state: the desired state of the keyslots
> +#
> +# @new-secret:    The ID of a QCryptoSecret object providing the password to be
> +#                 written into added active keyslots
> +#
> +# @old-secret:    Optional (for deactivation only)
> +#                 If given will deactive all keyslots that
> +#                 match password located in QCryptoSecret with this ID
> +#
> +# @iter-time:     Optional (for activation only)
> +#                 Number of milliseconds to spend in
> +#                 PBKDF passphrase processing for the newly activated keyslot.
> +#                 Currently defaults to 2000.
> +#
> +# @keyslot:       Optional. ID of the keyslot to activate/deactivate.
> +#                 For keyslot activation, keyslot should not be active already
> +#                 (this is unsafe to update an active keyslot),
> +#                 but possible if 'force' parameter is given.
> +#                 If keyslot is not given, first free keyslot will be written.
> +#
> +#                 For keyslot deactivation, this parameter specifies the exact
> +#                 keyslot to deactivate
> +#
> +# @unlock-secret: Optional. The ID of a QCryptoSecret object providing the
> +#                 password to use to retrive current master key.
> +#                 Defaults to the same secret that was used to open the image

So this matches Markus’ proposal except everything is flattened (because
we don’t support nested unions, AFAIU).  Sounds OK to me.  The only
difference is @unlock-secret, which did not appear in his proposal.  Why
do we need it again?

Max


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 02/14] qcrypto/luks: implement encryption key management
  2020-03-10 10:58   ` Max Reitz
@ 2020-03-10 11:05     ` Maxim Levitsky
  2020-03-10 11:59       ` Kevin Wolf
  0 siblings, 1 reply; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-10 11:05 UTC (permalink / raw)
  To: Max Reitz, qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, John Snow

On Tue, 2020-03-10 at 11:58 +0100, Max Reitz wrote:
> On 08.03.20 16:18, Maxim Levitsky wrote:
> > Next few patches will expose that functionality
> > to the user.
> > 
> > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > ---
> >  crypto/block-luks.c | 398 +++++++++++++++++++++++++++++++++++++++++++-
> >  qapi/crypto.json    |  61 ++++++-
> >  2 files changed, 455 insertions(+), 4 deletions(-)
> 
> [...]
> 
> > +##
> > +# @QCryptoBlockAmendOptionsLUKS:
> > +#
> > +# This struct defines the update parameters that activate/de-activate set
> > +# of keyslots
> > +#
> > +# @state: the desired state of the keyslots
> > +#
> > +# @new-secret:    The ID of a QCryptoSecret object providing the password to be
> > +#                 written into added active keyslots
> > +#
> > +# @old-secret:    Optional (for deactivation only)
> > +#                 If given will deactive all keyslots that
> > +#                 match password located in QCryptoSecret with this ID
> > +#
> > +# @iter-time:     Optional (for activation only)
> > +#                 Number of milliseconds to spend in
> > +#                 PBKDF passphrase processing for the newly activated keyslot.
> > +#                 Currently defaults to 2000.
> > +#
> > +# @keyslot:       Optional. ID of the keyslot to activate/deactivate.
> > +#                 For keyslot activation, keyslot should not be active already
> > +#                 (this is unsafe to update an active keyslot),
> > +#                 but possible if 'force' parameter is given.
> > +#                 If keyslot is not given, first free keyslot will be written.
> > +#
> > +#                 For keyslot deactivation, this parameter specifies the exact
> > +#                 keyslot to deactivate
> > +#
> > +# @unlock-secret: Optional. The ID of a QCryptoSecret object providing the
> > +#                 password to use to retrive current master key.
> > +#                 Defaults to the same secret that was used to open the image
> 
> So this matches Markus’ proposal except everything is flattened (because
> we don’t support nested unions, AFAIU).  Sounds OK to me.  The only
> difference is @unlock-secret, which did not appear in his proposal.  Why
> do we need it again?

That a little undocumented hack that will disappear one day.
Its because the driver currently doesn't keep a copy of the master key,
and instead only keeps ciper objects, often from outside libraries,
and in theory these objects might even be implemented in hardware so that
master key might be not in memory at all, so I kind of don't want yet
to keep it in memory.
Thus when doing the key management, I need to retrieve the master key again,
similar to how it is done on image opening. I use the same secret as was used for opening,
but in case the keys were changed already, that secret might not work anymore.
Thus I added this parameter to specify basically the old password, which is reasonable
when updating passwords.
I usually omit this hack in the discussions as it is orthogonal to the rest of the API.

Best regards,
	Maxim Levitsky


> 
> Max
> 




^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 02/14] qcrypto/luks: implement encryption key management
  2020-03-10 11:05     ` Maxim Levitsky
@ 2020-03-10 11:59       ` Kevin Wolf
  2020-03-10 12:02         ` Maxim Levitsky
  0 siblings, 1 reply; 35+ messages in thread
From: Kevin Wolf @ 2020-03-10 11:59 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Daniel P. Berrangé,
	qemu-block, Markus Armbruster, qemu-devel, Max Reitz, John Snow

Am 10.03.2020 um 12:05 hat Maxim Levitsky geschrieben:
> On Tue, 2020-03-10 at 11:58 +0100, Max Reitz wrote:
> > On 08.03.20 16:18, Maxim Levitsky wrote:
> > > Next few patches will expose that functionality
> > > to the user.
> > > 
> > > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > > ---
> > >  crypto/block-luks.c | 398 +++++++++++++++++++++++++++++++++++++++++++-
> > >  qapi/crypto.json    |  61 ++++++-
> > >  2 files changed, 455 insertions(+), 4 deletions(-)
> > 
> > [...]
> > 
> > > +##
> > > +# @QCryptoBlockAmendOptionsLUKS:
> > > +#
> > > +# This struct defines the update parameters that activate/de-activate set
> > > +# of keyslots
> > > +#
> > > +# @state: the desired state of the keyslots
> > > +#
> > > +# @new-secret:    The ID of a QCryptoSecret object providing the password to be
> > > +#                 written into added active keyslots
> > > +#
> > > +# @old-secret:    Optional (for deactivation only)
> > > +#                 If given will deactive all keyslots that
> > > +#                 match password located in QCryptoSecret with this ID
> > > +#
> > > +# @iter-time:     Optional (for activation only)
> > > +#                 Number of milliseconds to spend in
> > > +#                 PBKDF passphrase processing for the newly activated keyslot.
> > > +#                 Currently defaults to 2000.
> > > +#
> > > +# @keyslot:       Optional. ID of the keyslot to activate/deactivate.
> > > +#                 For keyslot activation, keyslot should not be active already
> > > +#                 (this is unsafe to update an active keyslot),
> > > +#                 but possible if 'force' parameter is given.
> > > +#                 If keyslot is not given, first free keyslot will be written.
> > > +#
> > > +#                 For keyslot deactivation, this parameter specifies the exact
> > > +#                 keyslot to deactivate
> > > +#
> > > +# @unlock-secret: Optional. The ID of a QCryptoSecret object providing the
> > > +#                 password to use to retrive current master key.
> > > +#                 Defaults to the same secret that was used to open the image
> > 
> > So this matches Markus’ proposal except everything is flattened (because
> > we don’t support nested unions, AFAIU).  Sounds OK to me.  The only
> > difference is @unlock-secret, which did not appear in his proposal.  Why
> > do we need it again?
> 
> That a little undocumented hack that will disappear one day.

It is very much documented (just a few lines above this one), and even
if it weren't documented, that wouldn't make it an unstable ABI.

If you don't want to make it to become stable ABI, you either need to
drop it or it needs an x- prefix, and its documentation should specify
what prevents it from being a stable ABI.

> Its because the driver currently doesn't keep a copy of the master key,
> and instead only keeps ciper objects, often from outside libraries,
> and in theory these objects might even be implemented in hardware so that
> master key might be not in memory at all, so I kind of don't want yet
> to keep it in memory.
> Thus when doing the key management, I need to retrieve the master key again,
> similar to how it is done on image opening. I use the same secret as was used for opening,
> but in case the keys were changed already, that secret might not work anymore.
> Thus I added this parameter to specify basically the old password, which is reasonable
> when updating passwords.
> I usually omit this hack in the discussions as it is orthogonal to the rest of the API.

How will this requirement disappear one day?

Kevin



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 02/14] qcrypto/luks: implement encryption key management
  2020-03-10 11:59       ` Kevin Wolf
@ 2020-03-10 12:02         ` Maxim Levitsky
  2020-03-11 12:55           ` Maxim Levitsky
  0 siblings, 1 reply; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-10 12:02 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: Daniel P. Berrangé,
	qemu-block, Markus Armbruster, qemu-devel, Max Reitz, John Snow

On Tue, 2020-03-10 at 12:59 +0100, Kevin Wolf wrote:
> Am 10.03.2020 um 12:05 hat Maxim Levitsky geschrieben:
> > On Tue, 2020-03-10 at 11:58 +0100, Max Reitz wrote:
> > > On 08.03.20 16:18, Maxim Levitsky wrote:
> > > > Next few patches will expose that functionality
> > > > to the user.
> > > > 
> > > > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > > > ---
> > > >  crypto/block-luks.c | 398 +++++++++++++++++++++++++++++++++++++++++++-
> > > >  qapi/crypto.json    |  61 ++++++-
> > > >  2 files changed, 455 insertions(+), 4 deletions(-)
> > > 
> > > [...]
> > > 
> > > > +##
> > > > +# @QCryptoBlockAmendOptionsLUKS:
> > > > +#
> > > > +# This struct defines the update parameters that activate/de-activate set
> > > > +# of keyslots
> > > > +#
> > > > +# @state: the desired state of the keyslots
> > > > +#
> > > > +# @new-secret:    The ID of a QCryptoSecret object providing the password to be
> > > > +#                 written into added active keyslots
> > > > +#
> > > > +# @old-secret:    Optional (for deactivation only)
> > > > +#                 If given will deactive all keyslots that
> > > > +#                 match password located in QCryptoSecret with this ID
> > > > +#
> > > > +# @iter-time:     Optional (for activation only)
> > > > +#                 Number of milliseconds to spend in
> > > > +#                 PBKDF passphrase processing for the newly activated keyslot.
> > > > +#                 Currently defaults to 2000.
> > > > +#
> > > > +# @keyslot:       Optional. ID of the keyslot to activate/deactivate.
> > > > +#                 For keyslot activation, keyslot should not be active already
> > > > +#                 (this is unsafe to update an active keyslot),
> > > > +#                 but possible if 'force' parameter is given.
> > > > +#                 If keyslot is not given, first free keyslot will be written.
> > > > +#
> > > > +#                 For keyslot deactivation, this parameter specifies the exact
> > > > +#                 keyslot to deactivate
> > > > +#
> > > > +# @unlock-secret: Optional. The ID of a QCryptoSecret object providing the
> > > > +#                 password to use to retrive current master key.
> > > > +#                 Defaults to the same secret that was used to open the image
> > > 
> > > So this matches Markus’ proposal except everything is flattened (because
> > > we don’t support nested unions, AFAIU).  Sounds OK to me.  The only
> > > difference is @unlock-secret, which did not appear in his proposal.  Why
> > > do we need it again?
> > 
> > That a little undocumented hack that will disappear one day.
> 
> It is very much documented (just a few lines above this one), and even
> if it weren't documented, that wouldn't make it an unstable ABI.
> 
> If you don't want to make it to become stable ABI, you either need to
> drop it or it needs an x- prefix, and its documentation should specify
> what prevents it from being a stable ABI.
> 
> > Its because the driver currently doesn't keep a copy of the master key,
> > and instead only keeps ciper objects, often from outside libraries,
> > and in theory these objects might even be implemented in hardware so that
> > master key might be not in memory at all, so I kind of don't want yet
> > to keep it in memory.
> > Thus when doing the key management, I need to retrieve the master key again,
> > similar to how it is done on image opening. I use the same secret as was used for opening,
> > but in case the keys were changed already, that secret might not work anymore.
> > Thus I added this parameter to specify basically the old password, which is reasonable
> > when updating passwords.
> > I usually omit this hack in the discussions as it is orthogonal to the rest of the API.
> 
> How will this requirement disappear one day?
If I cave in and keep a copy of the master key in the memory :-)

Best regards,
	Maxim Levitsky

> 
> Kevin




^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 02/14] qcrypto/luks: implement encryption key management
  2020-03-10 12:02         ` Maxim Levitsky
@ 2020-03-11 12:55           ` Maxim Levitsky
  0 siblings, 0 replies; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-11 12:55 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: Daniel P. Berrangé,
	qemu-block, Markus Armbruster, qemu-devel, Max Reitz, John Snow

On Tue, 2020-03-10 at 14:02 +0200, Maxim Levitsky wrote:
> On Tue, 2020-03-10 at 12:59 +0100, Kevin Wolf wrote:
> > Am 10.03.2020 um 12:05 hat Maxim Levitsky geschrieben:
> > > On Tue, 2020-03-10 at 11:58 +0100, Max Reitz wrote:
> > > > On 08.03.20 16:18, Maxim Levitsky wrote:
> > > > > Next few patches will expose that functionality
> > > > > to the user.
> > > > > 
> > > > > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > > > > ---
> > > > >  crypto/block-luks.c | 398 +++++++++++++++++++++++++++++++++++++++++++-
> > > > >  qapi/crypto.json    |  61 ++++++-
> > > > >  2 files changed, 455 insertions(+), 4 deletions(-)
> > > > 
> > > > [...]
> > > > 
> > > > > +##
> > > > > +# @QCryptoBlockAmendOptionsLUKS:
> > > > > +#
> > > > > +# This struct defines the update parameters that activate/de-activate set
> > > > > +# of keyslots
> > > > > +#
> > > > > +# @state: the desired state of the keyslots
> > > > > +#
> > > > > +# @new-secret:    The ID of a QCryptoSecret object providing the password to be
> > > > > +#                 written into added active keyslots
> > > > > +#
> > > > > +# @old-secret:    Optional (for deactivation only)
> > > > > +#                 If given will deactive all keyslots that
> > > > > +#                 match password located in QCryptoSecret with this ID
> > > > > +#
> > > > > +# @iter-time:     Optional (for activation only)
> > > > > +#                 Number of milliseconds to spend in
> > > > > +#                 PBKDF passphrase processing for the newly activated keyslot.
> > > > > +#                 Currently defaults to 2000.
> > > > > +#
> > > > > +# @keyslot:       Optional. ID of the keyslot to activate/deactivate.
> > > > > +#                 For keyslot activation, keyslot should not be active already
> > > > > +#                 (this is unsafe to update an active keyslot),
> > > > > +#                 but possible if 'force' parameter is given.
> > > > > +#                 If keyslot is not given, first free keyslot will be written.
> > > > > +#
> > > > > +#                 For keyslot deactivation, this parameter specifies the exact
> > > > > +#                 keyslot to deactivate
> > > > > +#
> > > > > +# @unlock-secret: Optional. The ID of a QCryptoSecret object providing the
> > > > > +#                 password to use to retrive current master key.
> > > > > +#                 Defaults to the same secret that was used to open the image
> > > > 
> > > > So this matches Markus’ proposal except everything is flattened (because
> > > > we don’t support nested unions, AFAIU).  Sounds OK to me.  The only
> > > > difference is @unlock-secret, which did not appear in his proposal.  Why
> > > > do we need it again?
> > > 
> > > That a little undocumented hack that will disappear one day.
> > 
> > It is very much documented (just a few lines above this one), and even
> > if it weren't documented, that wouldn't make it an unstable ABI.
> > 
> > If you don't want to make it to become stable ABI, you either need to
> > drop it or it needs an x- prefix, and its documentation should specify
> > what prevents it from being a stable ABI.
> > 
> > > Its because the driver currently doesn't keep a copy of the master key,
> > > and instead only keeps ciper objects, often from outside libraries,
> > > and in theory these objects might even be implemented in hardware so that
> > > master key might be not in memory at all, so I kind of don't want yet
> > > to keep it in memory.
> > > Thus when doing the key management, I need to retrieve the master key again,
> > > similar to how it is done on image opening. I use the same secret as was used for opening,
> > > but in case the keys were changed already, that secret might not work anymore.
> > > Thus I added this parameter to specify basically the old password, which is reasonable
> > > when updating passwords.
> > > I usually omit this hack in the discussions as it is orthogonal to the rest of the API.
> > 
> > How will this requirement disappear one day?
> 
> If I cave in and keep a copy of the master key in the memory :-)
> 
> Best regards,
> 	Maxim Levitsky
> 
> > 
> > Kevin
> 
> 
OK folks, besides this hack (which I can remove if you insist, although I don't
think it matters), what else should I do to move forward to get this accepted?

Best regards,
	Maxim Levitsky



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 00/14] LUKS: encryption slot management using amend interface
  2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
                   ` (13 preceding siblings ...)
  2020-03-08 15:19 ` [PATCH v2 14/14] iotests: add tests for blockdev-amend Maxim Levitsky
@ 2020-03-12 11:56 ` Eric Blake
  2020-03-12 14:33   ` Maxim Levitsky
  14 siblings, 1 reply; 35+ messages in thread
From: Eric Blake @ 2020-03-12 11:56 UTC (permalink / raw)
  To: Maxim Levitsky, qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, John Snow

On 3/8/20 10:18 AM, Maxim Levitsky wrote:
> Hi!
> Here is the updated series of my patches, incorporating all the feedback I received.
> 

> Patches are strictly divided by topic to 3 groups, and each group depends on former groups.
> 
> * Patches 1,2 implement qcrypto generic amend interface, including definition
>    of structs used in crypto.json and implement this in luks crypto driver
>    Nothing is exposed to the user at this stage
> 
> * Patches 3-9 use the code from patches 1,2 to implement qemu-img amend based encryption slot management
>    for luks and for qcow2, and add a bunch of iotests to cover that.
> 
> * Patches 10-13 add x-blockdev-amend (I'll drop the -x prefix if you like), and wire it
>    to luks and qcow2 driver to implement qmp based encryption slot management also using
>    the code from patches 1,2, and also add a bunch of iotests to cover this.

>   tests/qemu-iotests/284.out       |   6 +-
>   tests/qemu-iotests/300           | 207 ++++++++++++++++

Any reason why you skipped straight to test 300, rather than using an 
available slot like 290?  (Admittedly, our process for reserving slots 
is not very high-tech: manually scan the list for what other patches out 
there have claimed a slot, and be prepared to renumber when rebasing)

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3226
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 00/14] LUKS: encryption slot management using amend interface
  2020-03-12 11:56 ` [PATCH v2 00/14] LUKS: encryption slot management using amend interface Eric Blake
@ 2020-03-12 14:33   ` Maxim Levitsky
  0 siblings, 0 replies; 35+ messages in thread
From: Maxim Levitsky @ 2020-03-12 14:33 UTC (permalink / raw)
  To: Eric Blake, qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, Markus Armbruster, Max Reitz, John Snow

On Thu, 2020-03-12 at 06:56 -0500, Eric Blake wrote:
> On 3/8/20 10:18 AM, Maxim Levitsky wrote:
> > Hi!
> > Here is the updated series of my patches, incorporating all the feedback I received.
> > 
> > Patches are strictly divided by topic to 3 groups, and each group depends on former groups.
> > 
> > * Patches 1,2 implement qcrypto generic amend interface, including definition
> >    of structs used in crypto.json and implement this in luks crypto driver
> >    Nothing is exposed to the user at this stage
> > 
> > * Patches 3-9 use the code from patches 1,2 to implement qemu-img amend based encryption slot management
> >    for luks and for qcow2, and add a bunch of iotests to cover that.
> > 
> > * Patches 10-13 add x-blockdev-amend (I'll drop the -x prefix if you like), and wire it
> >    to luks and qcow2 driver to implement qmp based encryption slot management also using
> >    the code from patches 1,2, and also add a bunch of iotests to cover this.
> >   tests/qemu-iotests/284.out       |   6 +-
> >   tests/qemu-iotests/300           | 207 ++++++++++++++++
> 
> Any reason why you skipped straight to test 300, rather than using an 
> available slot like 290?  (Admittedly, our process for reserving slots 
> is not very high-tech: manually scan the list for what other patches out 
> there have claimed a slot, and be prepared to renumber when rebasing)
The only reason I used these slots is that I know sadly that I'll have to resend and
rebase this patchset for a while, and every time a test with the number I use is added,
this causes relatively hard to fix conflict (or at least I don't know how to fix these conflicts effectively)

Thus I used safe numbers, but at the rate this task progresses I won't be surprised that when this is merged,
these will be test numbers to use...

TL;DR - these are placeholders, and once the patch set is blesssed for merging upstream I'll update this next
available numbers.

Best regards,
	Maxim Levitsky



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 02/14] qcrypto/luks: implement encryption key management
  2020-03-08 15:18 ` [PATCH v2 02/14] qcrypto/luks: implement encryption key management Maxim Levitsky
  2020-03-10 10:58   ` Max Reitz
@ 2020-04-28 13:16   ` Daniel P. Berrangé
  2020-05-03  8:55     ` Maxim Levitsky
  1 sibling, 1 reply; 35+ messages in thread
From: Daniel P. Berrangé @ 2020-04-28 13:16 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	John Snow

On Sun, Mar 08, 2020 at 05:18:51PM +0200, Maxim Levitsky wrote:
> Next few patches will expose that functionality
> to the user.
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  crypto/block-luks.c | 398 +++++++++++++++++++++++++++++++++++++++++++-
>  qapi/crypto.json    |  61 ++++++-
>  2 files changed, 455 insertions(+), 4 deletions(-)
> 
> diff --git a/crypto/block-luks.c b/crypto/block-luks.c
> index 4861db810c..b11ee08c6d 100644
> --- a/crypto/block-luks.c
> +++ b/crypto/block-luks.c

> +/*
> + * Erases an keyslot given its index
> + * Returns:
> + *    0 if the keyslot was erased successfully
> + *   -1 if a error occurred while erasing the keyslot
> + *
> + */
> +static int
> +qcrypto_block_luks_erase_key(QCryptoBlock *block,
> +                             unsigned int slot_idx,
> +                             QCryptoBlockWriteFunc writefunc,
> +                             void *opaque,
> +                             Error **errp)
> +{
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx];
> +    g_autofree uint8_t *garbagesplitkey = NULL;
> +    size_t splitkeylen = luks->header.master_key_len * slot->stripes;
> +    size_t i;
> +
> +    assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> +    assert(splitkeylen > 0);
> +    garbagesplitkey = g_new0(uint8_t, splitkeylen);
> +
> +    /* Reset the key slot header */
> +    memset(slot->salt, 0, QCRYPTO_BLOCK_LUKS_SALT_LEN);
> +    slot->iterations = 0;
> +    slot->active = QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED;
> +
> +    qcrypto_block_luks_store_header(block,  writefunc, opaque, errp);

This may set  errp and we don't return immediately, so....

> +    /*
> +     * Now try to erase the key material, even if the header
> +     * update failed
> +     */
> +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS; i++) {
> +        if (qcrypto_random_bytes(garbagesplitkey, splitkeylen, errp) < 0) {

...this may then set errp a second time, which is not permitted.

This call needs to use a "local_err", and error_propagate(errp, local_err).
The latter is a no-op if errp is already set.

> +            /*
> +             * If we failed to get the random data, still write
> +             * at least zeros to the key slot at least once
> +             */
> +            if (i > 0) {
> +                return -1;
> +            }
> +        }
> +        if (writefunc(block,
> +                      slot->key_offset_sector * QCRYPTO_BLOCK_LUKS_SECTOR_SIZE,
> +                      garbagesplitkey,
> +                      splitkeylen,
> +                      opaque,
> +                      errp) != splitkeylen) {

same issue with errp here too.

> +            return -1;
> +        }
> +    }
> +    return 0;
> +}


> +/*
> + * Given LUKSKeyslotUpdate command, set @slots_bitmap with all slots
> + * that will be updated with new password (or erased)
> + * returns 0 on success, and -1 on failure
> + */
> +static int
> +qcrypto_block_luks_get_update_bitmap(QCryptoBlock *block,
> +                                     QCryptoBlockReadFunc readfunc,
> +                                     void *opaque,
> +                                     const QCryptoBlockAmendOptionsLUKS *opts,
> +                                     unsigned long *slots_bitmap,
> +                                     Error **errp)
> +{
> +    const QCryptoBlockLUKS *luks = block->opaque;
> +    size_t i;
> +
> +    bitmap_zero(slots_bitmap, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> +
> +    if (opts->has_keyslot) {
> +        /* keyslot set, select only this keyslot */
> +        int keyslot = opts->keyslot;
> +
> +        if (keyslot < 0 || keyslot >= QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS) {
> +            error_setg(errp,
> +                       "Invalid slot %u specified, must be between 0 and %u",
> +                       keyslot, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS - 1);
> +            return -1;
> +        }
> +        bitmap_set(slots_bitmap, keyslot, 1);
> +
> +    } else if (opts->has_old_secret) {
> +        /* initially select all active keyslots */
> +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +            if (qcrypto_block_luks_slot_active(luks, i)) {
> +                bitmap_set(slots_bitmap, i, 1);
> +            }
> +        }
> +    } else {
> +        /* find a free keyslot */
> +        int slot = qcrypto_block_luks_find_free_keyslot(luks);
> +
> +        if (slot == -1) {
> +            error_setg(errp,
> +                       "Can't add a keyslot - all key slots are in use");
> +            return -1;
> +        }
> +        bitmap_set(slots_bitmap, slot, 1);
> +    }
> +
> +    if (opts->has_old_secret) {
> +        /* now deselect all keyslots that don't contain the password */
> +        g_autofree uint8_t *tmpkey = g_new0(uint8_t,
> +                                            luks->header.master_key_len);
> +
> +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +            g_autofree char *old_password = NULL;
> +            int rv;
> +
> +            if (!test_bit(i, slots_bitmap)) {
> +                continue;
> +            }
> +
> +            old_password = qcrypto_secret_lookup_as_utf8(opts->old_secret,
> +                                                         errp);
> +            if (!old_password) {
> +                return -1;
> +            }
> +
> +            rv = qcrypto_block_luks_load_key(block,
> +                                             i,
> +                                             old_password,
> +                                             tmpkey,
> +                                             readfunc,
> +                                             opaque,
> +                                             errp);
> +            if (rv == -1) {
> +                return -1;
> +            } else if (rv == 0) {
> +                bitmap_clear(slots_bitmap, i, 1);
> +            }
> +        }
> +    }
> +    return 0;
> +}

I'm not really liking this function as a concept. Some of the code
only applies to the "add key" code path, while some of it only
applies to the "erase key" code path.

I'd prefer it if qcrypto_block_luks_erase_keys directly had the
required logic, likewise qcrypto_block_luks_set_keys, and thus
get rid of the bitmap concept entirely. I thin kit'd make the
logic easier to understand.

> +
> +/*
> + * Erase a set of keyslots given in @slots_bitmap
> + */
> +static int qcrypto_block_luks_erase_keys(QCryptoBlock *block,
> +                                         QCryptoBlockReadFunc readfunc,
> +                                         QCryptoBlockWriteFunc writefunc,
> +                                         void *opaque,
> +                                         unsigned long *slots_bitmap,
> +                                         bool force,
> +                                         Error **errp)
> +{
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    long slot_count = bitmap_count_one(slots_bitmap,
> +                                       QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> +    size_t i;
> +
> +    /* safety checks */
> +    if (!force && slot_count == qcrypto_block_luks_count_active_slots(luks)) {
> +        error_setg(errp,
> +                   "Requested operation will erase all active keyslots"
> +                   " which will erase all the data in the image"
> +                   " irreversibly - refusing operation");
> +        return -EINVAL;
> +    }
> +
> +    /* new apply the update */
> +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +        if (!test_bit(i, slots_bitmap)) {
> +            continue;
> +        }
> +        if (qcrypto_block_luks_erase_key(block, i, writefunc, opaque, errp)) {
> +            error_append_hint(errp, "Failed to erase keyslot %zu", i);
> +            return -EINVAL;
> +        }
> +    }
> +    return 0;
> +}
> +
> +/*
> + * Set a set of keyslots to @master_key encrypted by @new_secret
> + */
> +static int qcrypto_block_luks_set_keys(QCryptoBlock *block,
> +                                       QCryptoBlockReadFunc readfunc,
> +                                       QCryptoBlockWriteFunc writefunc,
> +                                       void *opaque,
> +                                       unsigned long *slots_bitmap,
> +                                       uint8_t *master_key,
> +                                       uint64_t iter_time,
> +                                       char *new_secret,
> +                                       bool force,
> +                                       Error **errp)

I'd call this  "add_key" instead of "set_keys".  I'm also unclear why
we need to support setting a range of keyslots. AFAIK, adding a key
should only ever affect a single keyslot.

> +{
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    g_autofree char *new_password = NULL;
> +    size_t i;
> +
> +    /* safety checks */
> +    if (!force) {
> +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +            if (!test_bit(i, slots_bitmap)) {
> +                continue;
> +            }
> +            if (qcrypto_block_luks_slot_active(luks, i)) {
> +                error_setg(errp,
> +                           "Refusing to overwrite active slot %zu - "
> +                           "please erase it first", i);
> +                return -EINVAL;
> +            }
> +        }
> +    }
> +
> +    /* Load the new password */
> +    new_password = qcrypto_secret_lookup_as_utf8(new_secret, errp);
> +    if (!new_password) {
> +        return -EINVAL;
> +    }
> +
> +    /* Apply the update */
> +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> +        if (!test_bit(i, slots_bitmap)) {
> +            continue;
> +        }
> +        if (qcrypto_block_luks_store_key(block, i, new_password, master_key,
> +                                         iter_time, writefunc, opaque, errp)) {
> +            error_append_hint(errp, "Failed to write to keyslot %zu", i);
> +            return -EINVAL;
> +        }
> +    }
> +    return 0;
> +}
> +
> +static int
> +qcrypto_block_luks_amend_options(QCryptoBlock *block,
> +                                 QCryptoBlockReadFunc readfunc,
> +                                 QCryptoBlockWriteFunc writefunc,
> +                                 void *opaque,
> +                                 QCryptoBlockAmendOptions *options,
> +                                 bool force,
> +                                 Error **errp)
> +{
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    QCryptoBlockAmendOptionsLUKS *opts_luks = &options->u.luks;
> +    g_autofree uint8_t *master_key = NULL;
> +    g_autofree unsigned long *update_bitmap = NULL;
> +    char *unlock_secret = NULL;
> +    long slot_count;
> +
> +    unlock_secret = opts_luks->has_unlock_secret ? opts_luks->unlock_secret :
> +                                                   luks->secret;
> +
> +    /* Retrieve set of slots that we need to update */
> +    update_bitmap = bitmap_new(QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> +    if (qcrypto_block_luks_get_update_bitmap(block, readfunc, opaque, opts_luks,
> +                                             update_bitmap, errp) != 0) {
> +        return -1;
> +    }
> +
> +    slot_count = bitmap_count_one(update_bitmap,
> +                                  QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> +
> +    /* no matching slots, so nothing to do */
> +    if (slot_count == 0) {
> +        error_setg(errp, "Requested operation didn't match any slots");
> +        return -1;
> +    }
> +
> +    if (opts_luks->state == LUKS_KEYSLOT_STATE_ACTIVE) {
> +
> +        uint64_t iter_time = opts_luks->has_iter_time ?
> +                             opts_luks->iter_time :
> +                             QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS;
> +
> +        if (!opts_luks->has_new_secret) {
> +            error_setg(errp, "'new-secret' is required to activate a keyslot");
> +            return -EINVAL;

return -1,   we shouldn't return errno values in luks code in general
as we use  Error **errp.

> +        }
> +        if (opts_luks->has_old_secret) {
> +            error_setg(errp,
> +                       "'old-secret' must not be given when activating keyslots");
> +            return -EINVAL;
> +        }
> +
> +        /* Locate the password that will be used to retrieve the master key */
> +        g_autofree char *old_password;
> +        old_password = qcrypto_secret_lookup_as_utf8(unlock_secret,  errp);
> +        if (!old_password) {
> +            return -EINVAL;
> +        }
> +
> +        /* Try to retrieve the master key */
> +        master_key = g_new0(uint8_t, luks->header.master_key_len);
> +        if (qcrypto_block_luks_find_key(block, old_password, master_key,
> +                                        readfunc, opaque, errp) < 0) {
> +            error_append_hint(errp, "Failed to retrieve the master key");
> +            return -EINVAL;
> +        }
> +
> +        /* Now set the new keyslots */
> +        if (qcrypto_block_luks_set_keys(block, readfunc, writefunc,
> +                                        opaque, update_bitmap, master_key,
> +                                        iter_time,
> +                                        opts_luks->new_secret,
> +                                        force, errp) != 0) {
> +            return -1;
> +        }
> +    } else {
> +        if (opts_luks->has_new_secret) {
> +            error_setg(errp,
> +                       "'new-secret' must not be given when erasing keyslots");
> +            return -EINVAL;
> +        }
> +        if (opts_luks->has_iter_time) {
> +            error_setg(errp,
> +                       "'iter-time' must not be given when erasing keyslots");
> +            return -EINVAL;
> +        }
> +        if (opts_luks->has_unlock_secret) {
> +            error_setg(errp,
> +                       "'unlock_secret' must not be given when erasing keyslots");
> +            return -EINVAL;
> +        }
> +
> +        if (qcrypto_block_luks_erase_keys(block, readfunc, writefunc,
> +                                          opaque, update_bitmap, force,
> +                                          errp) != 0) {
> +            return -1;
> +        }
> +    }
> +    return 0;
> +}
>  
>  static int qcrypto_block_luks_get_info(QCryptoBlock *block,
>                                         QCryptoBlockInfo *info,
> @@ -1523,7 +1912,11 @@ static int qcrypto_block_luks_get_info(QCryptoBlock *block,
>  
>  static void qcrypto_block_luks_cleanup(QCryptoBlock *block)
>  {
> -    g_free(block->opaque);
> +    QCryptoBlockLUKS *luks = block->opaque;
> +    if (luks) {
> +        g_free(luks->secret);
> +        g_free(luks);
> +    }
>  }
>  
>  
> @@ -1560,6 +1953,7 @@ qcrypto_block_luks_encrypt(QCryptoBlock *block,
>  const QCryptoBlockDriver qcrypto_block_driver_luks = {
>      .open = qcrypto_block_luks_open,
>      .create = qcrypto_block_luks_create,
> +    .amend = qcrypto_block_luks_amend_options,
>      .get_info = qcrypto_block_luks_get_info,
>      .cleanup = qcrypto_block_luks_cleanup,
>      .decrypt = qcrypto_block_luks_decrypt,
> diff --git a/qapi/crypto.json b/qapi/crypto.json
> index 3fd0ce177e..fe600fc608 100644
> --- a/qapi/crypto.json
> +++ b/qapi/crypto.json
> @@ -1,6 +1,8 @@
>  # -*- Mode: Python -*-
>  #
>  
> +{ 'include': 'common.json' }
> +
>  ##
>  # = Cryptography
>  ##
> @@ -297,7 +299,6 @@
>             'uuid': 'str',
>             'slots': [ 'QCryptoBlockInfoLUKSSlot' ] }}
>  
> -
>  ##
>  # @QCryptoBlockInfo:
>  #
> @@ -310,7 +311,63 @@
>    'discriminator': 'format',
>    'data': { 'luks': 'QCryptoBlockInfoLUKS' } }
>  
> +##
> +# @LUKSKeyslotState:
> +#
> +# Defines state of keyslots that are affected by the update
> +#
> +# @active:    The slots contain the given password and marked as active
> +# @inactive:  The slots are erased (contain garbage) and marked as inactive
> +#
> +# Since: 5.0
> +##
> +{ 'enum': 'LUKSKeyslotState',
> +  'data': [ 'active', 'inactive' ] }

This should be called  QCryptoBLockLUKSKeyslotState

> +##
> +# @QCryptoBlockAmendOptionsLUKS:
> +#
> +# This struct defines the update parameters that activate/de-activate set
> +# of keyslots
> +#
> +# @state: the desired state of the keyslots
> +#
> +# @new-secret:    The ID of a QCryptoSecret object providing the password to be
> +#                 written into added active keyslots
> +#
> +# @old-secret:    Optional (for deactivation only)
> +#                 If given will deactive all keyslots that
> +#                 match password located in QCryptoSecret with this ID
> +#
> +# @iter-time:     Optional (for activation only)
> +#                 Number of milliseconds to spend in
> +#                 PBKDF passphrase processing for the newly activated keyslot.
> +#                 Currently defaults to 2000.
> +#
> +# @keyslot:       Optional. ID of the keyslot to activate/deactivate.
> +#                 For keyslot activation, keyslot should not be active already
> +#                 (this is unsafe to update an active keyslot),
> +#                 but possible if 'force' parameter is given.
> +#                 If keyslot is not given, first free keyslot will be written.
> +#
> +#                 For keyslot deactivation, this parameter specifies the exact
> +#                 keyslot to deactivate
> +#
> +# @unlock-secret: Optional. The ID of a QCryptoSecret object providing the
> +#                 password to use to retrive current master key.
> +#                 Defaults to the same secret that was used to open the image

My inclination would be to just call this  "@secret", as it serves the
same purpose as the "@secret" parameter used when opening the image.

> +{ 'struct': 'QCryptoBlockAmendOptionsLUKS',
> +  'data': { 'state': 'LUKSKeyslotState',
> +            '*new-secret': 'str',
> +            '*old-secret': 'str',
> +            '*keyslot': 'int',
> +            '*iter-time': 'int',
> +            '*unlock-secret': 'str' } }
>  
>  ##
>  # @QCryptoBlockAmendOptions:
> @@ -324,4 +381,4 @@
>    'base': 'QCryptoBlockOptionsBase',
>    'discriminator': 'format',
>    'data': {
> -            } }
> +          'luks': 'QCryptoBlockAmendOptionsLUKS' } }

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 04/14] block/amend: separate amend and create options for qemu-img
  2020-03-08 15:18 ` [PATCH v2 04/14] block/amend: separate amend and create options for qemu-img Maxim Levitsky
@ 2020-04-28 15:03   ` Daniel P. Berrangé
  2020-04-28 15:49     ` Daniel P. Berrangé
  0 siblings, 1 reply; 35+ messages in thread
From: Daniel P. Berrangé @ 2020-04-28 15:03 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	John Snow

On Sun, Mar 08, 2020 at 05:18:53PM +0200, Maxim Levitsky wrote:
> Some options are only useful for creation
> (or hard to be amended, like cluster size for qcow2), while some other
> options are only useful for amend, like upcoming keyslot management
> options for luks
> 
> Since currently only qcow2 supports amend, move all its options
> to a common macro and then include it in each action option list.
> 
> In future it might be useful to remove some options which are
> not supported anyway from amend list, which currently
> cause an error message if amended.

In the v1 posting I had suggested changing this patch, so that it
only adds things to the amend list that actually can be amended. 

https://lists.gnu.org/archive/html/qemu-devel/2020-01/msg07570.html

> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  block/qcow2.c             | 160 +++++++++++++++++++++-----------------
>  include/block/block_int.h |   4 +
>  qemu-img.c                |  18 ++---
>  3 files changed, 100 insertions(+), 82 deletions(-)
> 
> diff --git a/block/qcow2.c b/block/qcow2.c
> index b55e5b7c1f..9574085772 100644
> --- a/block/qcow2.c
> +++ b/block/qcow2.c
> @@ -5440,83 +5440,96 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
>      s->signaled_corruption = true;
>  }
>  
> +#define QCOW_COMMON_OPTIONS                                         \
> +    {                                                               \
> +        .name = BLOCK_OPT_SIZE,                                     \
> +        .type = QEMU_OPT_SIZE,                                      \
> +        .help = "Virtual disk size"                                 \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_COMPAT_LEVEL,                             \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "Compatibility level (v2 [0.10] or v3 [1.1])"       \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_BACKING_FILE,                             \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "File name of a base image"                         \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_BACKING_FMT,                              \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "Image format of the base image"                    \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_DATA_FILE,                                \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "File name of an external data file"                \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_DATA_FILE_RAW,                            \
> +        .type = QEMU_OPT_BOOL,                                      \
> +        .help = "The external data file must stay valid "           \
> +                "as a raw image"                                    \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_ENCRYPT,                                  \
> +        .type = QEMU_OPT_BOOL,                                      \
> +        .help = "Encrypt the image with format 'aes'. (Deprecated " \
> +                "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",    \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_ENCRYPT_FORMAT,                           \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "Encrypt the image, format choices: 'aes', 'luks'", \
> +    },                                                              \
> +    BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",                     \
> +        "ID of secret providing qcow AES key or LUKS passphrase"),  \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),               \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),              \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),                \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),           \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),                 \
> +    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),                \
> +    {                                                               \
> +        .name = BLOCK_OPT_CLUSTER_SIZE,                             \
> +        .type = QEMU_OPT_SIZE,                                      \
> +        .help = "qcow2 cluster size",                               \
> +        .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)            \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_PREALLOC,                                 \
> +        .type = QEMU_OPT_STRING,                                    \
> +        .help = "Preallocation mode (allowed values: off, "         \
> +                "metadata, falloc, full)"                           \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_LAZY_REFCOUNTS,                           \
> +        .type = QEMU_OPT_BOOL,                                      \
> +        .help = "Postpone refcount updates",                        \
> +        .def_value_str = "off"                                      \
> +    },                                                              \
> +    {                                                               \
> +        .name = BLOCK_OPT_REFCOUNT_BITS,                            \
> +        .type = QEMU_OPT_NUMBER,                                    \
> +        .help = "Width of a reference count entry in bits",         \
> +        .def_value_str = "16"                                       \
> +    }                                                               \
> +
>  static QemuOptsList qcow2_create_opts = {
>      .name = "qcow2-create-opts",
>      .head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head),
>      .desc = {
> -        {
> -            .name = BLOCK_OPT_SIZE,
> -            .type = QEMU_OPT_SIZE,
> -            .help = "Virtual disk size"
> -        },
> -        {
> -            .name = BLOCK_OPT_COMPAT_LEVEL,
> -            .type = QEMU_OPT_STRING,
> -            .help = "Compatibility level (v2 [0.10] or v3 [1.1])"
> -        },
> -        {
> -            .name = BLOCK_OPT_BACKING_FILE,
> -            .type = QEMU_OPT_STRING,
> -            .help = "File name of a base image"
> -        },
> -        {
> -            .name = BLOCK_OPT_BACKING_FMT,
> -            .type = QEMU_OPT_STRING,
> -            .help = "Image format of the base image"
> -        },
> -        {
> -            .name = BLOCK_OPT_DATA_FILE,
> -            .type = QEMU_OPT_STRING,
> -            .help = "File name of an external data file"
> -        },
> -        {
> -            .name = BLOCK_OPT_DATA_FILE_RAW,
> -            .type = QEMU_OPT_BOOL,
> -            .help = "The external data file must stay valid as a raw image"
> -        },
> -        {
> -            .name = BLOCK_OPT_ENCRYPT,
> -            .type = QEMU_OPT_BOOL,
> -            .help = "Encrypt the image with format 'aes'. (Deprecated "
> -                    "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",
> -        },
> -        {
> -            .name = BLOCK_OPT_ENCRYPT_FORMAT,
> -            .type = QEMU_OPT_STRING,
> -            .help = "Encrypt the image, format choices: 'aes', 'luks'",
> -        },
> -        BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",
> -            "ID of secret providing qcow AES key or LUKS passphrase"),
> -        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),
> -        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),
> -        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),
> -        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),
> -        BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),
> -        BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),
> -        {
> -            .name = BLOCK_OPT_CLUSTER_SIZE,
> -            .type = QEMU_OPT_SIZE,
> -            .help = "qcow2 cluster size",
> -            .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)
> -        },
> -        {
> -            .name = BLOCK_OPT_PREALLOC,
> -            .type = QEMU_OPT_STRING,
> -            .help = "Preallocation mode (allowed values: off, metadata, "
> -                    "falloc, full)"
> -        },
> -        {
> -            .name = BLOCK_OPT_LAZY_REFCOUNTS,
> -            .type = QEMU_OPT_BOOL,
> -            .help = "Postpone refcount updates",
> -            .def_value_str = "off"
> -        },
> -        {
> -            .name = BLOCK_OPT_REFCOUNT_BITS,
> -            .type = QEMU_OPT_NUMBER,
> -            .help = "Width of a reference count entry in bits",
> -            .def_value_str = "16"
> -        },
> +        QCOW_COMMON_OPTIONS,
> +        { /* end of list */ }
> +    }
> +};
> +
> +static QemuOptsList qcow2_amend_opts = {
> +    .name = "qcow2-amend-opts",
> +    .head = QTAILQ_HEAD_INITIALIZER(qcow2_amend_opts.head),
> +    .desc = {
> +        QCOW_COMMON_OPTIONS,
>          { /* end of list */ }
>      }
>  };
> @@ -5576,6 +5589,7 @@ BlockDriver bdrv_qcow2 = {
>      .bdrv_inactivate            = qcow2_inactivate,
>  
>      .create_opts         = &qcow2_create_opts,
> +    .amend_opts          = &qcow2_amend_opts,
>      .strong_runtime_opts = qcow2_strong_runtime_opts,
>      .mutable_opts        = mutable_opts,
>      .bdrv_co_check       = qcow2_co_check,
> diff --git a/include/block/block_int.h b/include/block/block_int.h
> index 24d00fbf48..48a4c2fa1c 100644
> --- a/include/block/block_int.h
> +++ b/include/block/block_int.h
> @@ -406,6 +406,10 @@ struct BlockDriver {
>  
>      /* List of options for creating images, terminated by name == NULL */
>      QemuOptsList *create_opts;
> +
> +    /* List of options for image amend*/
> +    QemuOptsList *amend_opts;
> +
>      /*
>       * If this driver supports reopening images this contains a
>       * NULL-terminated list of the runtime options that can be
> diff --git a/qemu-img.c b/qemu-img.c
> index 551388676f..2555aedee9 100644
> --- a/qemu-img.c
> +++ b/qemu-img.c
> @@ -3898,11 +3898,11 @@ static int print_amend_option_help(const char *format)
>          return 1;
>      }
>  
> -    /* Every driver supporting amendment must have create_opts */
> -    assert(drv->create_opts);
> +    /* Every driver supporting amendment must have amend_opts */
> +    assert(drv->amend_opts);
>  
>      printf("Creation options for '%s':\n", format);
> -    qemu_opts_print_help(drv->create_opts, false);
> +    qemu_opts_print_help(drv->amend_opts, false);
>      printf("\nNote that not all of these options may be amendable.\n");
>      return 0;
>  }
> @@ -3912,7 +3912,7 @@ static int img_amend(int argc, char **argv)
>      Error *err = NULL;
>      int c, ret = 0;
>      char *options = NULL;
> -    QemuOptsList *create_opts = NULL;
> +    QemuOptsList *amend_opts = NULL;
>      QemuOpts *opts = NULL;
>      const char *fmt = NULL, *filename, *cache;
>      int flags;
> @@ -4051,11 +4051,11 @@ static int img_amend(int argc, char **argv)
>          goto out;
>      }
>  
> -    /* Every driver supporting amendment must have create_opts */
> -    assert(bs->drv->create_opts);
> +    /* Every driver supporting amendment must have amend_opts */
> +    assert(bs->drv->amend_opts);
>  
> -    create_opts = qemu_opts_append(create_opts, bs->drv->create_opts);
> -    opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
> +    amend_opts = qemu_opts_append(amend_opts, bs->drv->amend_opts);
> +    opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
>      qemu_opts_do_parse(opts, options, NULL, &err);
>      if (err) {
>          error_report_err(err);
> @@ -4078,7 +4078,7 @@ out:
>  out_no_progress:
>      blk_unref(blk);
>      qemu_opts_del(opts);
> -    qemu_opts_free(create_opts);
> +    qemu_opts_free(amend_opts);
>      g_free(options);
>  
>      if (ret) {
> -- 
> 2.17.2
> 

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 04/14] block/amend: separate amend and create options for qemu-img
  2020-04-28 15:03   ` Daniel P. Berrangé
@ 2020-04-28 15:49     ` Daniel P. Berrangé
  0 siblings, 0 replies; 35+ messages in thread
From: Daniel P. Berrangé @ 2020-04-28 15:49 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, Markus Armbruster, qemu-devel, Max Reitz,
	John Snow

On Tue, Apr 28, 2020 at 04:03:33PM +0100, Daniel P. Berrangé wrote:
> On Sun, Mar 08, 2020 at 05:18:53PM +0200, Maxim Levitsky wrote:
> > Some options are only useful for creation
> > (or hard to be amended, like cluster size for qcow2), while some other
> > options are only useful for amend, like upcoming keyslot management
> > options for luks
> > 
> > Since currently only qcow2 supports amend, move all its options
> > to a common macro and then include it in each action option list.
> > 
> > In future it might be useful to remove some options which are
> > not supported anyway from amend list, which currently
> > cause an error message if amended.
> 
> In the v1 posting I had suggested changing this patch, so that it
> only adds things to the amend list that actually can be amended. 
> 
> https://lists.gnu.org/archive/html/qemu-devel/2020-01/msg07570.html

Never mind, I should have read ahead in the series to see the next
patch

So for this patch

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>


> 
> > 
> > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > ---
> >  block/qcow2.c             | 160 +++++++++++++++++++++-----------------
> >  include/block/block_int.h |   4 +
> >  qemu-img.c                |  18 ++---
> >  3 files changed, 100 insertions(+), 82 deletions(-)
> > 
> > diff --git a/block/qcow2.c b/block/qcow2.c
> > index b55e5b7c1f..9574085772 100644
> > --- a/block/qcow2.c
> > +++ b/block/qcow2.c
> > @@ -5440,83 +5440,96 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
> >      s->signaled_corruption = true;
> >  }
> >  
> > +#define QCOW_COMMON_OPTIONS                                         \
> > +    {                                                               \
> > +        .name = BLOCK_OPT_SIZE,                                     \
> > +        .type = QEMU_OPT_SIZE,                                      \
> > +        .help = "Virtual disk size"                                 \
> > +    },                                                              \
> > +    {                                                               \
> > +        .name = BLOCK_OPT_COMPAT_LEVEL,                             \
> > +        .type = QEMU_OPT_STRING,                                    \
> > +        .help = "Compatibility level (v2 [0.10] or v3 [1.1])"       \
> > +    },                                                              \
> > +    {                                                               \
> > +        .name = BLOCK_OPT_BACKING_FILE,                             \
> > +        .type = QEMU_OPT_STRING,                                    \
> > +        .help = "File name of a base image"                         \
> > +    },                                                              \
> > +    {                                                               \
> > +        .name = BLOCK_OPT_BACKING_FMT,                              \
> > +        .type = QEMU_OPT_STRING,                                    \
> > +        .help = "Image format of the base image"                    \
> > +    },                                                              \
> > +    {                                                               \
> > +        .name = BLOCK_OPT_DATA_FILE,                                \
> > +        .type = QEMU_OPT_STRING,                                    \
> > +        .help = "File name of an external data file"                \
> > +    },                                                              \
> > +    {                                                               \
> > +        .name = BLOCK_OPT_DATA_FILE_RAW,                            \
> > +        .type = QEMU_OPT_BOOL,                                      \
> > +        .help = "The external data file must stay valid "           \
> > +                "as a raw image"                                    \
> > +    },                                                              \
> > +    {                                                               \
> > +        .name = BLOCK_OPT_ENCRYPT,                                  \
> > +        .type = QEMU_OPT_BOOL,                                      \
> > +        .help = "Encrypt the image with format 'aes'. (Deprecated " \
> > +                "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",    \
> > +    },                                                              \
> > +    {                                                               \
> > +        .name = BLOCK_OPT_ENCRYPT_FORMAT,                           \
> > +        .type = QEMU_OPT_STRING,                                    \
> > +        .help = "Encrypt the image, format choices: 'aes', 'luks'", \
> > +    },                                                              \
> > +    BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",                     \
> > +        "ID of secret providing qcow AES key or LUKS passphrase"),  \
> > +    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),               \
> > +    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),              \
> > +    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),                \
> > +    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),           \
> > +    BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),                 \
> > +    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),                \
> > +    {                                                               \
> > +        .name = BLOCK_OPT_CLUSTER_SIZE,                             \
> > +        .type = QEMU_OPT_SIZE,                                      \
> > +        .help = "qcow2 cluster size",                               \
> > +        .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)            \
> > +    },                                                              \
> > +    {                                                               \
> > +        .name = BLOCK_OPT_PREALLOC,                                 \
> > +        .type = QEMU_OPT_STRING,                                    \
> > +        .help = "Preallocation mode (allowed values: off, "         \
> > +                "metadata, falloc, full)"                           \
> > +    },                                                              \
> > +    {                                                               \
> > +        .name = BLOCK_OPT_LAZY_REFCOUNTS,                           \
> > +        .type = QEMU_OPT_BOOL,                                      \
> > +        .help = "Postpone refcount updates",                        \
> > +        .def_value_str = "off"                                      \
> > +    },                                                              \
> > +    {                                                               \
> > +        .name = BLOCK_OPT_REFCOUNT_BITS,                            \
> > +        .type = QEMU_OPT_NUMBER,                                    \
> > +        .help = "Width of a reference count entry in bits",         \
> > +        .def_value_str = "16"                                       \
> > +    }                                                               \
> > +
> >  static QemuOptsList qcow2_create_opts = {
> >      .name = "qcow2-create-opts",
> >      .head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head),
> >      .desc = {
> > -        {
> > -            .name = BLOCK_OPT_SIZE,
> > -            .type = QEMU_OPT_SIZE,
> > -            .help = "Virtual disk size"
> > -        },
> > -        {
> > -            .name = BLOCK_OPT_COMPAT_LEVEL,
> > -            .type = QEMU_OPT_STRING,
> > -            .help = "Compatibility level (v2 [0.10] or v3 [1.1])"
> > -        },
> > -        {
> > -            .name = BLOCK_OPT_BACKING_FILE,
> > -            .type = QEMU_OPT_STRING,
> > -            .help = "File name of a base image"
> > -        },
> > -        {
> > -            .name = BLOCK_OPT_BACKING_FMT,
> > -            .type = QEMU_OPT_STRING,
> > -            .help = "Image format of the base image"
> > -        },
> > -        {
> > -            .name = BLOCK_OPT_DATA_FILE,
> > -            .type = QEMU_OPT_STRING,
> > -            .help = "File name of an external data file"
> > -        },
> > -        {
> > -            .name = BLOCK_OPT_DATA_FILE_RAW,
> > -            .type = QEMU_OPT_BOOL,
> > -            .help = "The external data file must stay valid as a raw image"
> > -        },
> > -        {
> > -            .name = BLOCK_OPT_ENCRYPT,
> > -            .type = QEMU_OPT_BOOL,
> > -            .help = "Encrypt the image with format 'aes'. (Deprecated "
> > -                    "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",
> > -        },
> > -        {
> > -            .name = BLOCK_OPT_ENCRYPT_FORMAT,
> > -            .type = QEMU_OPT_STRING,
> > -            .help = "Encrypt the image, format choices: 'aes', 'luks'",
> > -        },
> > -        BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",
> > -            "ID of secret providing qcow AES key or LUKS passphrase"),
> > -        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),
> > -        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),
> > -        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),
> > -        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),
> > -        BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),
> > -        BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),
> > -        {
> > -            .name = BLOCK_OPT_CLUSTER_SIZE,
> > -            .type = QEMU_OPT_SIZE,
> > -            .help = "qcow2 cluster size",
> > -            .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)
> > -        },
> > -        {
> > -            .name = BLOCK_OPT_PREALLOC,
> > -            .type = QEMU_OPT_STRING,
> > -            .help = "Preallocation mode (allowed values: off, metadata, "
> > -                    "falloc, full)"
> > -        },
> > -        {
> > -            .name = BLOCK_OPT_LAZY_REFCOUNTS,
> > -            .type = QEMU_OPT_BOOL,
> > -            .help = "Postpone refcount updates",
> > -            .def_value_str = "off"
> > -        },
> > -        {
> > -            .name = BLOCK_OPT_REFCOUNT_BITS,
> > -            .type = QEMU_OPT_NUMBER,
> > -            .help = "Width of a reference count entry in bits",
> > -            .def_value_str = "16"
> > -        },
> > +        QCOW_COMMON_OPTIONS,
> > +        { /* end of list */ }
> > +    }
> > +};
> > +
> > +static QemuOptsList qcow2_amend_opts = {
> > +    .name = "qcow2-amend-opts",
> > +    .head = QTAILQ_HEAD_INITIALIZER(qcow2_amend_opts.head),
> > +    .desc = {
> > +        QCOW_COMMON_OPTIONS,
> >          { /* end of list */ }
> >      }
> >  };
> > @@ -5576,6 +5589,7 @@ BlockDriver bdrv_qcow2 = {
> >      .bdrv_inactivate            = qcow2_inactivate,
> >  
> >      .create_opts         = &qcow2_create_opts,
> > +    .amend_opts          = &qcow2_amend_opts,
> >      .strong_runtime_opts = qcow2_strong_runtime_opts,
> >      .mutable_opts        = mutable_opts,
> >      .bdrv_co_check       = qcow2_co_check,
> > diff --git a/include/block/block_int.h b/include/block/block_int.h
> > index 24d00fbf48..48a4c2fa1c 100644
> > --- a/include/block/block_int.h
> > +++ b/include/block/block_int.h
> > @@ -406,6 +406,10 @@ struct BlockDriver {
> >  
> >      /* List of options for creating images, terminated by name == NULL */
> >      QemuOptsList *create_opts;
> > +
> > +    /* List of options for image amend*/
> > +    QemuOptsList *amend_opts;
> > +
> >      /*
> >       * If this driver supports reopening images this contains a
> >       * NULL-terminated list of the runtime options that can be
> > diff --git a/qemu-img.c b/qemu-img.c
> > index 551388676f..2555aedee9 100644
> > --- a/qemu-img.c
> > +++ b/qemu-img.c
> > @@ -3898,11 +3898,11 @@ static int print_amend_option_help(const char *format)
> >          return 1;
> >      }
> >  
> > -    /* Every driver supporting amendment must have create_opts */
> > -    assert(drv->create_opts);
> > +    /* Every driver supporting amendment must have amend_opts */
> > +    assert(drv->amend_opts);
> >  
> >      printf("Creation options for '%s':\n", format);
> > -    qemu_opts_print_help(drv->create_opts, false);
> > +    qemu_opts_print_help(drv->amend_opts, false);
> >      printf("\nNote that not all of these options may be amendable.\n");
> >      return 0;
> >  }
> > @@ -3912,7 +3912,7 @@ static int img_amend(int argc, char **argv)
> >      Error *err = NULL;
> >      int c, ret = 0;
> >      char *options = NULL;
> > -    QemuOptsList *create_opts = NULL;
> > +    QemuOptsList *amend_opts = NULL;
> >      QemuOpts *opts = NULL;
> >      const char *fmt = NULL, *filename, *cache;
> >      int flags;
> > @@ -4051,11 +4051,11 @@ static int img_amend(int argc, char **argv)
> >          goto out;
> >      }
> >  
> > -    /* Every driver supporting amendment must have create_opts */
> > -    assert(bs->drv->create_opts);
> > +    /* Every driver supporting amendment must have amend_opts */
> > +    assert(bs->drv->amend_opts);
> >  
> > -    create_opts = qemu_opts_append(create_opts, bs->drv->create_opts);
> > -    opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
> > +    amend_opts = qemu_opts_append(amend_opts, bs->drv->amend_opts);
> > +    opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
> >      qemu_opts_do_parse(opts, options, NULL, &err);
> >      if (err) {
> >          error_report_err(err);
> > @@ -4078,7 +4078,7 @@ out:
> >  out_no_progress:
> >      blk_unref(blk);
> >      qemu_opts_del(opts);
> > -    qemu_opts_free(create_opts);
> > +    qemu_opts_free(amend_opts);
> >      g_free(options);
> >  
> >      if (ret) {
> > -- 
> > 2.17.2
> > 
> 
> Regards,
> Daniel
> -- 
> |: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
> |: https://libvirt.org         -o-            https://fstop138.berrange.com :|
> |: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|
> 
> 

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 05/14] block/amend: refactor qcow2 amend options
  2020-03-08 15:18 ` [PATCH v2 05/14] block/amend: refactor qcow2 amend options Maxim Levitsky
@ 2020-04-28 15:51   ` Daniel P. Berrangé
  0 siblings, 0 replies; 35+ messages in thread
From: Daniel P. Berrangé @ 2020-04-28 15:51 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	John Snow

On Sun, Mar 08, 2020 at 05:18:54PM +0200, Maxim Levitsky wrote:
> Some qcow2 create options can't be used for amend.
> Remove them from the qcow2 create options and add generic logic to detect
> such options in qemu-img
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  block/qcow2.c              | 108 ++++++---------------
>  qemu-img.c                 |  18 +++-
>  tests/qemu-iotests/049.out | 102 ++++++++++----------
>  tests/qemu-iotests/061.out |  12 ++-
>  tests/qemu-iotests/079.out |  18 ++--
>  tests/qemu-iotests/082.out | 149 ++++------------------------
>  tests/qemu-iotests/085.out |  38 ++++----
>  tests/qemu-iotests/087.out |   6 +-
>  tests/qemu-iotests/115.out |   2 +-
>  tests/qemu-iotests/121.out |   4 +-
>  tests/qemu-iotests/125.out | 192 ++++++++++++++++++-------------------
>  tests/qemu-iotests/134.out |   2 +-
>  tests/qemu-iotests/144.out |   4 +-
>  tests/qemu-iotests/158.out |   4 +-
>  tests/qemu-iotests/182.out |   2 +-
>  tests/qemu-iotests/185.out |   8 +-
>  tests/qemu-iotests/188.out |   2 +-
>  tests/qemu-iotests/189.out |   4 +-
>  tests/qemu-iotests/198.out |   4 +-
>  tests/qemu-iotests/243.out |  16 ++--
>  tests/qemu-iotests/250.out |   2 +-
>  tests/qemu-iotests/255.out |   8 +-
>  tests/qemu-iotests/263.out |   4 +-
>  tests/qemu-iotests/280.out |   2 +-
>  24 files changed, 283 insertions(+), 428 deletions(-)

Kind of annoying how the option order changes, but that's not
a functional problem and there's no easy way to avoid it.
Perhaps the original code should have alphabetically ordered
them but that's not your problem to solve really.

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 07/14] block/crypto: implement the encryption key management
  2020-03-08 15:18 ` [PATCH v2 07/14] block/crypto: implement the encryption key management Maxim Levitsky
@ 2020-04-28 16:15   ` Daniel P. Berrangé
  0 siblings, 0 replies; 35+ messages in thread
From: Daniel P. Berrangé @ 2020-04-28 16:15 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	John Snow

On Sun, Mar 08, 2020 at 05:18:56PM +0200, Maxim Levitsky wrote:
> This implements the encryption key management using the generic code in
> qcrypto layer and exposes it to the user via qemu-img
> 
> This code adds another 'write_func' because the initialization
> write_func works directly on the underlying file, and amend
> works on instance of luks device.
> 
> This commit also adds a 'hack/workaround' I and Kevin Wolf (thanks)
> made to make the driver both support write sharing (to avoid breaking the users),
> and be safe against concurrent  metadata update (the keyslots)
> 
> Eventually the write sharing for luks driver will be deprecated
> and removed together with this hack.
> 
> The hack is that we ask (as a format driver) for BLK_PERM_CONSISTENT_READ
> and then when we want to update the keys, we unshare that permission.
> So if someone else has the image open, even readonly, encryption
> key update will fail gracefully.
> 
> Also thanks to Daniel Berrange for the idea of
> unsharing read, rather that write permission which allows
> to avoid cases when the other user had opened the image read-only.
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  block/crypto.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++--
>  block/crypto.h |  44 +++++++++++++++--
>  2 files changed, 163 insertions(+), 8 deletions(-)

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>


> @@ -661,6 +693,95 @@ block_crypto_get_specific_info_luks(BlockDriverState *bs, Error **errp)
>      return spec_info;
>  }
>  
> +static int
> +block_crypto_amend_options_luks(BlockDriverState *bs,
> +                           QemuOpts *opts,
> +                           BlockDriverAmendStatusCB *status_cb,
> +                           void *cb_opaque,
> +                           bool force,
> +                           Error **errp)

Nitpick - align options after the "("


> @@ -81,11 +86,40 @@
>          .help = "Name of encryption hash algorithm",     \
>      }
>  
> -#define BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(prefix)           \
> -    {                                                         \
> -        .name = prefix BLOCK_CRYPTO_OPT_LUKS_ITER_TIME,       \
> -        .type = QEMU_OPT_NUMBER,                              \
> -        .help = "Time to spend in PBKDF in milliseconds",     \
> +#define BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(prefix)        \
> +    {                                                      \
> +        .name = prefix BLOCK_CRYPTO_OPT_LUKS_ITER_TIME,    \
> +        .type = QEMU_OPT_NUMBER,                           \
> +        .help = "Time to spend in PBKDF in milliseconds",  \
> +    }

Nitpick - no need to change whitespace here

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 08/14] block/qcow2: extend qemu-img amend interface with crypto options
  2020-03-08 15:18 ` [PATCH v2 08/14] block/qcow2: extend qemu-img amend interface with crypto options Maxim Levitsky
@ 2020-04-28 16:17   ` Daniel P. Berrangé
  0 siblings, 0 replies; 35+ messages in thread
From: Daniel P. Berrangé @ 2020-04-28 16:17 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	John Snow

On Sun, Mar 08, 2020 at 05:18:57PM +0200, Maxim Levitsky wrote:
> Now that we have all the infrastructure in place,
> wire it in the qcow2 driver and expose this to the user.
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  block/qcow2.c              | 80 ++++++++++++++++++++++++++++++++------
>  tests/qemu-iotests/082.out | 45 +++++++++++++++++++++
>  2 files changed, 114 insertions(+), 11 deletions(-)

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 09/14] iotests: filter few more luks specific create options
  2020-03-08 15:18 ` [PATCH v2 09/14] iotests: filter few more luks specific create options Maxim Levitsky
@ 2020-04-28 16:19   ` Daniel P. Berrangé
  0 siblings, 0 replies; 35+ messages in thread
From: Daniel P. Berrangé @ 2020-04-28 16:19 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, Markus Armbruster, qemu-devel, Max Reitz,
	John Snow

On Sun, Mar 08, 2020 at 05:18:58PM +0200, Maxim Levitsky wrote:
> This allows more tests to be able to have same output on both qcow2 luks encrypted images
> and raw luks images
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  tests/qemu-iotests/087.out       | 6 +++---
>  tests/qemu-iotests/134.out       | 2 +-
>  tests/qemu-iotests/158.out       | 4 ++--
>  tests/qemu-iotests/188.out       | 2 +-
>  tests/qemu-iotests/189.out       | 4 ++--
>  tests/qemu-iotests/198.out       | 4 ++--
>  tests/qemu-iotests/263.out       | 4 ++--
>  tests/qemu-iotests/284.out       | 6 +++---
>  tests/qemu-iotests/common.filter | 6 ++++--
>  9 files changed, 20 insertions(+), 18 deletions(-)

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 10/14] iotests: qemu-img tests for luks key management
  2020-03-08 15:18 ` [PATCH v2 10/14] iotests: qemu-img tests for luks key management Maxim Levitsky
@ 2020-04-28 16:21   ` Daniel P. Berrangé
  0 siblings, 0 replies; 35+ messages in thread
From: Daniel P. Berrangé @ 2020-04-28 16:21 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	John Snow

On Sun, Mar 08, 2020 at 05:18:59PM +0200, Maxim Levitsky wrote:
> This commit adds two tests, which test the new amend interface
> of both luks raw images and qcow2 luks encrypted images.
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  tests/qemu-iotests/300     | 207 +++++++++++++++++++++++++++++++++++++
>  tests/qemu-iotests/300.out |  99 ++++++++++++++++++
>  tests/qemu-iotests/301     |  90 ++++++++++++++++
>  tests/qemu-iotests/301.out |  30 ++++++
>  tests/qemu-iotests/group   |   3 +
>  5 files changed, 429 insertions(+)
>  create mode 100755 tests/qemu-iotests/300
>  create mode 100644 tests/qemu-iotests/300.out
>  create mode 100755 tests/qemu-iotests/301
>  create mode 100644 tests/qemu-iotests/301.out

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 14/14] iotests: add tests for blockdev-amend
  2020-03-08 15:19 ` [PATCH v2 14/14] iotests: add tests for blockdev-amend Maxim Levitsky
@ 2020-04-28 16:23   ` Daniel P. Berrangé
  0 siblings, 0 replies; 35+ messages in thread
From: Daniel P. Berrangé @ 2020-04-28 16:23 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	John Snow

On Sun, Mar 08, 2020 at 05:19:03PM +0200, Maxim Levitsky wrote:
> This commit adds two tests that cover the
> new blockdev-amend functionality of luks and qcow2 driver
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  tests/qemu-iotests/302     | 278 +++++++++++++++++++++++++++++++++++++
>  tests/qemu-iotests/302.out |  40 ++++++
>  tests/qemu-iotests/303     | 233 +++++++++++++++++++++++++++++++
>  tests/qemu-iotests/303.out |  33 +++++
>  tests/qemu-iotests/group   |   3 +
>  5 files changed, 587 insertions(+)
>  create mode 100755 tests/qemu-iotests/302
>  create mode 100644 tests/qemu-iotests/302.out
>  create mode 100755 tests/qemu-iotests/303
>  create mode 100644 tests/qemu-iotests/303.out

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 11/14] block/core: add generic infrastructure for x-blockdev-amend qmp command
  2020-03-08 15:19 ` [PATCH v2 11/14] block/core: add generic infrastructure for x-blockdev-amend qmp command Maxim Levitsky
@ 2020-04-28 16:25   ` Daniel P. Berrangé
  0 siblings, 0 replies; 35+ messages in thread
From: Daniel P. Berrangé @ 2020-04-28 16:25 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	John Snow

On Sun, Mar 08, 2020 at 05:19:00PM +0200, Maxim Levitsky wrote:
> blockdev-amend will be used similiar to blockdev-create
> to allow on the fly changes of the structure of the format based block devices.
> 
> Current plan is to first support encryption keyslot management for luks
> based formats (raw and embedded in qcow2)
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  block/Makefile.objs       |   2 +-
>  block/amend.c             | 108 ++++++++++++++++++++++++++++++++++++++
>  include/block/block_int.h |  21 +++++---
>  qapi/block-core.json      |  42 +++++++++++++++
>  qapi/job.json             |   4 +-
>  5 files changed, 169 insertions(+), 8 deletions(-)
>  create mode 100644 block/amend.c

> diff --git a/block/amend.c b/block/amend.c
> new file mode 100644
> index 0000000000..2db7b1eafc
> --- /dev/null
> +++ b/block/amend.c
> @@ -0,0 +1,108 @@
> +/*
> + * Block layer code related to image options amend
> + *
> + * Copyright (c) 2018 Kevin Wolf <kwolf@redhat.com>
> + * Copyright (c) 2019 Maxim Levitsky <mlevitsk@redhat.com>

I would have expected these to be  "Copyright (C) 2019 Red Hat, Inc"
since employees usually have to assign copyright, unless they were
done outside of work context.

Aside from that 

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 02/14] qcrypto/luks: implement encryption key management
  2020-04-28 13:16   ` Daniel P. Berrangé
@ 2020-05-03  8:55     ` Maxim Levitsky
  2020-05-04  9:18       ` Daniel P. Berrangé
  0 siblings, 1 reply; 35+ messages in thread
From: Maxim Levitsky @ 2020-05-03  8:55 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Kevin Wolf, qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	John Snow

On Tue, 2020-04-28 at 14:16 +0100, Daniel P. Berrangé wrote:
> On Sun, Mar 08, 2020 at 05:18:51PM +0200, Maxim Levitsky wrote:
> > Next few patches will expose that functionality
> > to the user.
> > 
> > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > ---
> >  crypto/block-luks.c | 398 +++++++++++++++++++++++++++++++++++++++++++-
> >  qapi/crypto.json    |  61 ++++++-
> >  2 files changed, 455 insertions(+), 4 deletions(-)
> > 
> > diff --git a/crypto/block-luks.c b/crypto/block-luks.c
> > index 4861db810c..b11ee08c6d 100644
> > --- a/crypto/block-luks.c
> > +++ b/crypto/block-luks.c
> > +/*
> > + * Erases an keyslot given its index
> > + * Returns:
> > + *    0 if the keyslot was erased successfully
> > + *   -1 if a error occurred while erasing the keyslot
> > + *
> > + */
> > +static int
> > +qcrypto_block_luks_erase_key(QCryptoBlock *block,
> > +                             unsigned int slot_idx,
> > +                             QCryptoBlockWriteFunc writefunc,
> > +                             void *opaque,
> > +                             Error **errp)
> > +{
> > +    QCryptoBlockLUKS *luks = block->opaque;
> > +    QCryptoBlockLUKSKeySlot *slot = &luks->header.key_slots[slot_idx];
> > +    g_autofree uint8_t *garbagesplitkey = NULL;
> > +    size_t splitkeylen = luks->header.master_key_len * slot->stripes;
> > +    size_t i;
> > +
> > +    assert(slot_idx < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> > +    assert(splitkeylen > 0);
> > +    garbagesplitkey = g_new0(uint8_t, splitkeylen);
> > +
> > +    /* Reset the key slot header */
> > +    memset(slot->salt, 0, QCRYPTO_BLOCK_LUKS_SALT_LEN);
> > +    slot->iterations = 0;
> > +    slot->active = QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED;
> > +
> > +    qcrypto_block_luks_store_header(block,  writefunc, opaque, errp);
> 
> This may set  errp and we don't return immediately, so....
> 
> > +    /*
> > +     * Now try to erase the key material, even if the header
> > +     * update failed
> > +     */
> > +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_ERASE_ITERATIONS; i++) {
> > +        if (qcrypto_random_bytes(garbagesplitkey, splitkeylen, errp) < 0) {
> 
> ...this may then set errp a second time, which is not permitted.
> 
> This call needs to use a "local_err", and error_propagate(errp, local_err).
> The latter is a no-op if errp is already set.

Fixed! Thanks for pointing this out!

> 
> > +            /*
> > +             * If we failed to get the random data, still write
> > +             * at least zeros to the key slot at least once
> > +             */
> > +            if (i > 0) {
> > +                return -1;
> > +            }
> > +        }
> > +        if (writefunc(block,
> > +                      slot->key_offset_sector * QCRYPTO_BLOCK_LUKS_SECTOR_SIZE,
> > +                      garbagesplitkey,
> > +                      splitkeylen,
> > +                      opaque,
> > +                      errp) != splitkeylen) {
> 
> same issue with errp here too.

Fixed too of course
> 
> > +            return -1;
> > +        }
> > +    }
> > +    return 0;
> > +}
> 
> 
> > +/*
> > + * Given LUKSKeyslotUpdate command, set @slots_bitmap with all slots
> > + * that will be updated with new password (or erased)
> > + * returns 0 on success, and -1 on failure
> > + */
> > +static int
> > +qcrypto_block_luks_get_update_bitmap(QCryptoBlock *block,
> > +                                     QCryptoBlockReadFunc readfunc,
> > +                                     void *opaque,
> > +                                     const QCryptoBlockAmendOptionsLUKS *opts,
> > +                                     unsigned long *slots_bitmap,
> > +                                     Error **errp)
> > +{
> > +    const QCryptoBlockLUKS *luks = block->opaque;
> > +    size_t i;
> > +
> > +    bitmap_zero(slots_bitmap, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> > +
> > +    if (opts->has_keyslot) {
> > +        /* keyslot set, select only this keyslot */
> > +        int keyslot = opts->keyslot;
> > +
> > +        if (keyslot < 0 || keyslot >= QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS) {
> > +            error_setg(errp,
> > +                       "Invalid slot %u specified, must be between 0 and %u",
> > +                       keyslot, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS - 1);
> > +            return -1;
> > +        }
> > +        bitmap_set(slots_bitmap, keyslot, 1);
> > +
> > +    } else if (opts->has_old_secret) {
> > +        /* initially select all active keyslots */
> > +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > +            if (qcrypto_block_luks_slot_active(luks, i)) {
> > +                bitmap_set(slots_bitmap, i, 1);
> > +            }
> > +        }
> > +    } else {
> > +        /* find a free keyslot */
> > +        int slot = qcrypto_block_luks_find_free_keyslot(luks);
> > +
> > +        if (slot == -1) {
> > +            error_setg(errp,
> > +                       "Can't add a keyslot - all key slots are in use");
> > +            return -1;
> > +        }
> > +        bitmap_set(slots_bitmap, slot, 1);
> > +    }
> > +
> > +    if (opts->has_old_secret) {
> > +        /* now deselect all keyslots that don't contain the password */
> > +        g_autofree uint8_t *tmpkey = g_new0(uint8_t,
> > +                                            luks->header.master_key_len);
> > +
> > +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > +            g_autofree char *old_password = NULL;
> > +            int rv;
> > +
> > +            if (!test_bit(i, slots_bitmap)) {
> > +                continue;
> > +            }
> > +
> > +            old_password = qcrypto_secret_lookup_as_utf8(opts->old_secret,
> > +                                                         errp);
> > +            if (!old_password) {
> > +                return -1;
> > +            }
> > +
> > +            rv = qcrypto_block_luks_load_key(block,
> > +                                             i,
> > +                                             old_password,
> > +                                             tmpkey,
> > +                                             readfunc,
> > +                                             opaque,
> > +                                             errp);
> > +            if (rv == -1) {
> > +                return -1;
> > +            } else if (rv == 0) {
> > +                bitmap_clear(slots_bitmap, i, 1);
> > +            }
> > +        }
> > +    }
> > +    return 0;
> > +}
> 
> I'm not really liking this function as a concept. Some of the code
> only applies to the "add key" code path, while some of it only
> applies to the "erase key" code path.
> 
> I'd prefer it if qcrypto_block_luks_erase_keys directly had the
> required logic, likewise qcrypto_block_luks_set_keys, and thus
> get rid of the bitmap concept entirely. I thin kit'd make the
> logic easier to understand.

It used to be like that in former versions that I did send, I added the concept
of the bitmap very recently to reflect the way we defined this in the spec.
I don't mind that much coming back to older version of doing this,
but beware that it won't be that clear either.


> 
> > +
> > +/*
> > + * Erase a set of keyslots given in @slots_bitmap
> > + */
> > +static int qcrypto_block_luks_erase_keys(QCryptoBlock *block,
> > +                                         QCryptoBlockReadFunc readfunc,
> > +                                         QCryptoBlockWriteFunc writefunc,
> > +                                         void *opaque,
> > +                                         unsigned long *slots_bitmap,
> > +                                         bool force,
> > +                                         Error **errp)
> > +{
> > +    QCryptoBlockLUKS *luks = block->opaque;
> > +    long slot_count = bitmap_count_one(slots_bitmap,
> > +                                       QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> > +    size_t i;
> > +
> > +    /* safety checks */
> > +    if (!force && slot_count == qcrypto_block_luks_count_active_slots(luks)) {
> > +        error_setg(errp,
> > +                   "Requested operation will erase all active keyslots"
> > +                   " which will erase all the data in the image"
> > +                   " irreversibly - refusing operation");
> > +        return -EINVAL;
> > +    }
> > +
> > +    /* new apply the update */
> > +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > +        if (!test_bit(i, slots_bitmap)) {
> > +            continue;
> > +        }
> > +        if (qcrypto_block_luks_erase_key(block, i, writefunc, opaque, errp)) {
> > +            error_append_hint(errp, "Failed to erase keyslot %zu", i);
> > +            return -EINVAL;
> > +        }
> > +    }
> > +    return 0;
> > +}
> > +
> > +/*
> > + * Set a set of keyslots to @master_key encrypted by @new_secret
> > + */
> > +static int qcrypto_block_luks_set_keys(QCryptoBlock *block,
> > +                                       QCryptoBlockReadFunc readfunc,
> > +                                       QCryptoBlockWriteFunc writefunc,
> > +                                       void *opaque,
> > +                                       unsigned long *slots_bitmap,
> > +                                       uint8_t *master_key,
> > +                                       uint64_t iter_time,
> > +                                       char *new_secret,
> > +                                       bool force,
> > +                                       Error **errp)
> 
> I'd call this  "add_key" instead of "set_keys".  I'm also unclear why
> we need to support setting a range of keyslots. AFAIK, adding a key
> should only ever affect a single keyslot.
Mostly for consistency. There is a very corner case of inline replacing
all keys that match one password with another.

If possible I would like to keep it this way though.


> 
> > +{
> > +    QCryptoBlockLUKS *luks = block->opaque;
> > +    g_autofree char *new_password = NULL;
> > +    size_t i;
> > +
> > +    /* safety checks */
> > +    if (!force) {
> > +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > +            if (!test_bit(i, slots_bitmap)) {
> > +                continue;
> > +            }
> > +            if (qcrypto_block_luks_slot_active(luks, i)) {
> > +                error_setg(errp,
> > +                           "Refusing to overwrite active slot %zu - "
> > +                           "please erase it first", i);
> > +                return -EINVAL;
> > +            }
> > +        }
> > +    }
> > +
> > +    /* Load the new password */
> > +    new_password = qcrypto_secret_lookup_as_utf8(new_secret, errp);
> > +    if (!new_password) {
> > +        return -EINVAL;
> > +    }
> > +
> > +    /* Apply the update */
> > +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > +        if (!test_bit(i, slots_bitmap)) {
> > +            continue;
> > +        }
> > +        if (qcrypto_block_luks_store_key(block, i, new_password, master_key,
> > +                                         iter_time, writefunc, opaque, errp)) {
> > +            error_append_hint(errp, "Failed to write to keyslot %zu", i);
> > +            return -EINVAL;
> > +        }
> > +    }
> > +    return 0;
> > +}
> > +
> > +static int
> > +qcrypto_block_luks_amend_options(QCryptoBlock *block,
> > +                                 QCryptoBlockReadFunc readfunc,
> > +                                 QCryptoBlockWriteFunc writefunc,
> > +                                 void *opaque,
> > +                                 QCryptoBlockAmendOptions *options,
> > +                                 bool force,
> > +                                 Error **errp)
> > +{
> > +    QCryptoBlockLUKS *luks = block->opaque;
> > +    QCryptoBlockAmendOptionsLUKS *opts_luks = &options->u.luks;
> > +    g_autofree uint8_t *master_key = NULL;
> > +    g_autofree unsigned long *update_bitmap = NULL;
> > +    char *unlock_secret = NULL;
> > +    long slot_count;
> > +
> > +    unlock_secret = opts_luks->has_unlock_secret ? opts_luks->unlock_secret :
> > +                                                   luks->secret;
> > +
> > +    /* Retrieve set of slots that we need to update */
> > +    update_bitmap = bitmap_new(QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> > +    if (qcrypto_block_luks_get_update_bitmap(block, readfunc, opaque, opts_luks,
> > +                                             update_bitmap, errp) != 0) {
> > +        return -1;
> > +    }
> > +
> > +    slot_count = bitmap_count_one(update_bitmap,
> > +                                  QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> > +
> > +    /* no matching slots, so nothing to do */
> > +    if (slot_count == 0) {
> > +        error_setg(errp, "Requested operation didn't match any slots");
> > +        return -1;
> > +    }
> > +
> > +    if (opts_luks->state == LUKS_KEYSLOT_STATE_ACTIVE) {
> > +
> > +        uint64_t iter_time = opts_luks->has_iter_time ?
> > +                             opts_luks->iter_time :
> > +                             QCRYPTO_BLOCK_LUKS_DEFAULT_ITER_TIME_MS;
> > +
> > +        if (!opts_luks->has_new_secret) {
> > +            error_setg(errp, "'new-secret' is required to activate a keyslot");
> > +            return -EINVAL;
> 
> return -1,   we shouldn't return errno values in luks code in general
> as we use  Error **errp.
Yep, fixed.

> 
> > +        }
> > +        if (opts_luks->has_old_secret) {
> > +            error_setg(errp,
> > +                       "'old-secret' must not be given when activating keyslots");
> > +            return -EINVAL;
> > +        }
> > +
> > +        /* Locate the password that will be used to retrieve the master key */
> > +        g_autofree char *old_password;
> > +        old_password = qcrypto_secret_lookup_as_utf8(unlock_secret,  errp);
> > +        if (!old_password) {
> > +            return -EINVAL;
> > +        }
> > +
> > +        /* Try to retrieve the master key */
> > +        master_key = g_new0(uint8_t, luks->header.master_key_len);
> > +        if (qcrypto_block_luks_find_key(block, old_password, master_key,
> > +                                        readfunc, opaque, errp) < 0) {
> > +            error_append_hint(errp, "Failed to retrieve the master key");
> > +            return -EINVAL;
> > +        }
> > +
> > +        /* Now set the new keyslots */
> > +        if (qcrypto_block_luks_set_keys(block, readfunc, writefunc,
> > +                                        opaque, update_bitmap, master_key,
> > +                                        iter_time,
> > +                                        opts_luks->new_secret,
> > +                                        force, errp) != 0) {
> > +            return -1;
> > +        }
> > +    } else {
> > +        if (opts_luks->has_new_secret) {
> > +            error_setg(errp,
> > +                       "'new-secret' must not be given when erasing keyslots");
> > +            return -EINVAL;
> > +        }
> > +        if (opts_luks->has_iter_time) {
> > +            error_setg(errp,
> > +                       "'iter-time' must not be given when erasing keyslots");
> > +            return -EINVAL;
> > +        }
> > +        if (opts_luks->has_unlock_secret) {
> > +            error_setg(errp,
> > +                       "'unlock_secret' must not be given when erasing keyslots");
> > +            return -EINVAL;
> > +        }
> > +
> > +        if (qcrypto_block_luks_erase_keys(block, readfunc, writefunc,
> > +                                          opaque, update_bitmap, force,
> > +                                          errp) != 0) {
> > +            return -1;
> > +        }
> > +    }
> > +    return 0;
> > +}
> >  
> >  static int qcrypto_block_luks_get_info(QCryptoBlock *block,
> >                                         QCryptoBlockInfo *info,
> > @@ -1523,7 +1912,11 @@ static int qcrypto_block_luks_get_info(QCryptoBlock *block,
> >  
> >  static void qcrypto_block_luks_cleanup(QCryptoBlock *block)
> >  {
> > -    g_free(block->opaque);
> > +    QCryptoBlockLUKS *luks = block->opaque;
> > +    if (luks) {
> > +        g_free(luks->secret);
> > +        g_free(luks);
> > +    }
> >  }
> >  
> >  
> > @@ -1560,6 +1953,7 @@ qcrypto_block_luks_encrypt(QCryptoBlock *block,
> >  const QCryptoBlockDriver qcrypto_block_driver_luks = {
> >      .open = qcrypto_block_luks_open,
> >      .create = qcrypto_block_luks_create,
> > +    .amend = qcrypto_block_luks_amend_options,
> >      .get_info = qcrypto_block_luks_get_info,
> >      .cleanup = qcrypto_block_luks_cleanup,
> >      .decrypt = qcrypto_block_luks_decrypt,
> > diff --git a/qapi/crypto.json b/qapi/crypto.json
> > index 3fd0ce177e..fe600fc608 100644
> > --- a/qapi/crypto.json
> > +++ b/qapi/crypto.json
> > @@ -1,6 +1,8 @@
> >  # -*- Mode: Python -*-
> >  #
> >  
> > +{ 'include': 'common.json' }
> > +
> >  ##
> >  # = Cryptography
> >  ##
> > @@ -297,7 +299,6 @@
> >             'uuid': 'str',
> >             'slots': [ 'QCryptoBlockInfoLUKSSlot' ] }}
> >  
> > -
> >  ##
> >  # @QCryptoBlockInfo:
> >  #
> > @@ -310,7 +311,63 @@
> >    'discriminator': 'format',
> >    'data': { 'luks': 'QCryptoBlockInfoLUKS' } }
> >  
> > +##
> > +# @LUKSKeyslotState:
> > +#
> > +# Defines state of keyslots that are affected by the update
> > +#
> > +# @active:    The slots contain the given password and marked as active
> > +# @inactive:  The slots are erased (contain garbage) and marked as inactive
> > +#
> > +# Since: 5.0
> > +##
> > +{ 'enum': 'LUKSKeyslotState',
> > +  'data': [ 'active', 'inactive' ] }
> 
> This should be called  QCryptoBLockLUKSKeyslotState
Roger that!

> 
> > +##
> > +# @QCryptoBlockAmendOptionsLUKS:
> > +#
> > +# This struct defines the update parameters that activate/de-activate set
> > +# of keyslots
> > +#
> > +# @state: the desired state of the keyslots
> > +#
> > +# @new-secret:    The ID of a QCryptoSecret object providing the password to be
> > +#                 written into added active keyslots
> > +#
> > +# @old-secret:    Optional (for deactivation only)
> > +#                 If given will deactive all keyslots that
> > +#                 match password located in QCryptoSecret with this ID
> > +#
> > +# @iter-time:     Optional (for activation only)
> > +#                 Number of milliseconds to spend in
> > +#                 PBKDF passphrase processing for the newly activated keyslot.
> > +#                 Currently defaults to 2000.
> > +#
> > +# @keyslot:       Optional. ID of the keyslot to activate/deactivate.
> > +#                 For keyslot activation, keyslot should not be active already
> > +#                 (this is unsafe to update an active keyslot),
> > +#                 but possible if 'force' parameter is given.
> > +#                 If keyslot is not given, first free keyslot will be written.
> > +#
> > +#                 For keyslot deactivation, this parameter specifies the exact
> > +#                 keyslot to deactivate
> > +#
> > +# @unlock-secret: Optional. The ID of a QCryptoSecret object providing the
> > +#                 password to use to retrive current master key.
> > +#                 Defaults to the same secret that was used to open the image
> 
> My inclination would be to just call this  "@secret", as it serves the
> same purpose as the "@secret" parameter used when opening the image.

Let it be 'secret' I don't mind at all.

> 
> > +{ 'struct': 'QCryptoBlockAmendOptionsLUKS',
> > +  'data': { 'state': 'LUKSKeyslotState',
> > +            '*new-secret': 'str',
> > +            '*old-secret': 'str',
> > +            '*keyslot': 'int',
> > +            '*iter-time': 'int',
> > +            '*unlock-secret': 'str' } }
> >  
> >  ##
> >  # @QCryptoBlockAmendOptions:
> > @@ -324,4 +381,4 @@
> >    'base': 'QCryptoBlockOptionsBase',
> >    'discriminator': 'format',
> >    'data': {
> > -            } }
> > +          'luks': 'QCryptoBlockAmendOptionsLUKS' } }
> 
> Regards,
> Daniel


Best regards and thanks for the review,
	Maxim Levitsky



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v2 02/14] qcrypto/luks: implement encryption key management
  2020-05-03  8:55     ` Maxim Levitsky
@ 2020-05-04  9:18       ` Daniel P. Berrangé
  0 siblings, 0 replies; 35+ messages in thread
From: Daniel P. Berrangé @ 2020-05-04  9:18 UTC (permalink / raw)
  To: Maxim Levitsky
  Cc: Kevin Wolf, qemu-block, qemu-devel, Markus Armbruster, Max Reitz,
	John Snow

On Sun, May 03, 2020 at 11:55:35AM +0300, Maxim Levitsky wrote:
> On Tue, 2020-04-28 at 14:16 +0100, Daniel P. Berrangé wrote:
> > On Sun, Mar 08, 2020 at 05:18:51PM +0200, Maxim Levitsky wrote:
> > > Next few patches will expose that functionality
> > > to the user.
> > > 
> > > Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> > > ---
> > >  crypto/block-luks.c | 398 +++++++++++++++++++++++++++++++++++++++++++-
> > >  qapi/crypto.json    |  61 ++++++-
> > >  2 files changed, 455 insertions(+), 4 deletions(-)


> > > +/*
> > > + * Given LUKSKeyslotUpdate command, set @slots_bitmap with all slots
> > > + * that will be updated with new password (or erased)
> > > + * returns 0 on success, and -1 on failure
> > > + */
> > > +static int
> > > +qcrypto_block_luks_get_update_bitmap(QCryptoBlock *block,
> > > +                                     QCryptoBlockReadFunc readfunc,
> > > +                                     void *opaque,
> > > +                                     const QCryptoBlockAmendOptionsLUKS *opts,
> > > +                                     unsigned long *slots_bitmap,
> > > +                                     Error **errp)
> > > +{
> > > +    const QCryptoBlockLUKS *luks = block->opaque;
> > > +    size_t i;
> > > +
> > > +    bitmap_zero(slots_bitmap, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> > > +
> > > +    if (opts->has_keyslot) {
> > > +        /* keyslot set, select only this keyslot */
> > > +        int keyslot = opts->keyslot;
> > > +
> > > +        if (keyslot < 0 || keyslot >= QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS) {
> > > +            error_setg(errp,
> > > +                       "Invalid slot %u specified, must be between 0 and %u",
> > > +                       keyslot, QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS - 1);
> > > +            return -1;
> > > +        }
> > > +        bitmap_set(slots_bitmap, keyslot, 1);
> > > +
> > > +    } else if (opts->has_old_secret) {
> > > +        /* initially select all active keyslots */
> > > +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > > +            if (qcrypto_block_luks_slot_active(luks, i)) {
> > > +                bitmap_set(slots_bitmap, i, 1);
> > > +            }
> > > +        }
> > > +    } else {
> > > +        /* find a free keyslot */
> > > +        int slot = qcrypto_block_luks_find_free_keyslot(luks);
> > > +
> > > +        if (slot == -1) {
> > > +            error_setg(errp,
> > > +                       "Can't add a keyslot - all key slots are in use");
> > > +            return -1;
> > > +        }
> > > +        bitmap_set(slots_bitmap, slot, 1);
> > > +    }
> > > +
> > > +    if (opts->has_old_secret) {
> > > +        /* now deselect all keyslots that don't contain the password */
> > > +        g_autofree uint8_t *tmpkey = g_new0(uint8_t,
> > > +                                            luks->header.master_key_len);
> > > +
> > > +        for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > > +            g_autofree char *old_password = NULL;
> > > +            int rv;
> > > +
> > > +            if (!test_bit(i, slots_bitmap)) {
> > > +                continue;
> > > +            }
> > > +
> > > +            old_password = qcrypto_secret_lookup_as_utf8(opts->old_secret,
> > > +                                                         errp);
> > > +            if (!old_password) {
> > > +                return -1;
> > > +            }
> > > +
> > > +            rv = qcrypto_block_luks_load_key(block,
> > > +                                             i,
> > > +                                             old_password,
> > > +                                             tmpkey,
> > > +                                             readfunc,
> > > +                                             opaque,
> > > +                                             errp);
> > > +            if (rv == -1) {
> > > +                return -1;
> > > +            } else if (rv == 0) {
> > > +                bitmap_clear(slots_bitmap, i, 1);
> > > +            }
> > > +        }
> > > +    }
> > > +    return 0;
> > > +}
> > 
> > I'm not really liking this function as a concept. Some of the code
> > only applies to the "add key" code path, while some of it only
> > applies to the "erase key" code path.
> > 
> > I'd prefer it if qcrypto_block_luks_erase_keys directly had the
> > required logic, likewise qcrypto_block_luks_set_keys, and thus
> > get rid of the bitmap concept entirely. I thin kit'd make the
> > logic easier to understand.
> 
> It used to be like that in former versions that I did send, I added the concept
> of the bitmap very recently to reflect the way we defined this in the spec.
> I don't mind that much coming back to older version of doing this,
> but beware that it won't be that clear either.

My view is that removing and adding keys are fundamentally different
operations, so although there's some parts that are in common, overall
it is better to keep them clearly separate.

> > > +/*
> > > + * Erase a set of keyslots given in @slots_bitmap
> > > + */
> > > +static int qcrypto_block_luks_erase_keys(QCryptoBlock *block,
> > > +                                         QCryptoBlockReadFunc readfunc,
> > > +                                         QCryptoBlockWriteFunc writefunc,
> > > +                                         void *opaque,
> > > +                                         unsigned long *slots_bitmap,
> > > +                                         bool force,
> > > +                                         Error **errp)
> > > +{
> > > +    QCryptoBlockLUKS *luks = block->opaque;
> > > +    long slot_count = bitmap_count_one(slots_bitmap,
> > > +                                       QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> > > +    size_t i;
> > > +
> > > +    /* safety checks */
> > > +    if (!force && slot_count == qcrypto_block_luks_count_active_slots(luks)) {
> > > +        error_setg(errp,
> > > +                   "Requested operation will erase all active keyslots"
> > > +                   " which will erase all the data in the image"
> > > +                   " irreversibly - refusing operation");
> > > +        return -EINVAL;
> > > +    }
> > > +
> > > +    /* new apply the update */
> > > +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > > +        if (!test_bit(i, slots_bitmap)) {
> > > +            continue;
> > > +        }
> > > +        if (qcrypto_block_luks_erase_key(block, i, writefunc, opaque, errp)) {
> > > +            error_append_hint(errp, "Failed to erase keyslot %zu", i);
> > > +            return -EINVAL;
> > > +        }
> > > +    }
> > > +    return 0;
> > > +}
> > > +
> > > +/*
> > > + * Set a set of keyslots to @master_key encrypted by @new_secret
> > > + */
> > > +static int qcrypto_block_luks_set_keys(QCryptoBlock *block,
> > > +                                       QCryptoBlockReadFunc readfunc,
> > > +                                       QCryptoBlockWriteFunc writefunc,
> > > +                                       void *opaque,
> > > +                                       unsigned long *slots_bitmap,
> > > +                                       uint8_t *master_key,
> > > +                                       uint64_t iter_time,
> > > +                                       char *new_secret,
> > > +                                       bool force,
> > > +                                       Error **errp)
> > 
> > I'd call this  "add_key" instead of "set_keys".  I'm also unclear why
> > we need to support setting a range of keyslots. AFAIK, adding a key
> > should only ever affect a single keyslot.
> Mostly for consistency. There is a very corner case of inline replacing
> all keys that match one password with another.

I don't see that as a use case we care about. There's no benefit to having
the same password repeated in multiple slots.

> If possible I would like to keep it this way though.

IMHO the the bitmap just needlessly complicates the code for a feature
that is irrelevant to us.


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH v2 04/14] block/amend: separate amend and create options for qemu-img
  2020-01-30 17:29 Maxim Levitsky
@ 2020-01-30 17:29 ` Maxim Levitsky
  0 siblings, 0 replies; 35+ messages in thread
From: Maxim Levitsky @ 2020-01-30 17:29 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, Daniel P. Berrangé,
	qemu-block, John Snow, Markus Armbruster, Max Reitz,
	Maxim Levitsky

Some options are only useful for creation
(or hard to be amended, like cluster size for qcow2), while some other
options are only useful for amend, like upcoming keyslot management
options for luks

Since currently only qcow2 supports amend, move all its options
to a common macro and then include it in each action option list.

In future it might be useful to remove some options which are
not supported anyway from amend list, which currently
cause an error message if amended.

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 block/qcow2.c             | 160 +++++++++++++++++++++-----------------
 include/block/block_int.h |   4 +
 qemu-img.c                |  18 ++---
 3 files changed, 100 insertions(+), 82 deletions(-)

diff --git a/block/qcow2.c b/block/qcow2.c
index 6bcf4a5fc4..c6c2deee75 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -5445,83 +5445,96 @@ void qcow2_signal_corruption(BlockDriverState *bs, bool fatal, int64_t offset,
     s->signaled_corruption = true;
 }
 
+#define QCOW_COMMON_OPTIONS                                         \
+    {                                                               \
+        .name = BLOCK_OPT_SIZE,                                     \
+        .type = QEMU_OPT_SIZE,                                      \
+        .help = "Virtual disk size"                                 \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_COMPAT_LEVEL,                             \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Compatibility level (v2 [0.10] or v3 [1.1])"       \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_BACKING_FILE,                             \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "File name of a base image"                         \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_BACKING_FMT,                              \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Image format of the base image"                    \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_DATA_FILE,                                \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "File name of an external data file"                \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_DATA_FILE_RAW,                            \
+        .type = QEMU_OPT_BOOL,                                      \
+        .help = "The external data file must stay valid "           \
+                "as a raw image"                                    \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_ENCRYPT,                                  \
+        .type = QEMU_OPT_BOOL,                                      \
+        .help = "Encrypt the image with format 'aes'. (Deprecated " \
+                "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",    \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_ENCRYPT_FORMAT,                           \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Encrypt the image, format choices: 'aes', 'luks'", \
+    },                                                              \
+    BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",                     \
+        "ID of secret providing qcow AES key or LUKS passphrase"),  \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),               \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),              \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),                \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),           \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),                 \
+    BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),                \
+    {                                                               \
+        .name = BLOCK_OPT_CLUSTER_SIZE,                             \
+        .type = QEMU_OPT_SIZE,                                      \
+        .help = "qcow2 cluster size",                               \
+        .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)            \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_PREALLOC,                                 \
+        .type = QEMU_OPT_STRING,                                    \
+        .help = "Preallocation mode (allowed values: off, "         \
+                "metadata, falloc, full)"                           \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_LAZY_REFCOUNTS,                           \
+        .type = QEMU_OPT_BOOL,                                      \
+        .help = "Postpone refcount updates",                        \
+        .def_value_str = "off"                                      \
+    },                                                              \
+    {                                                               \
+        .name = BLOCK_OPT_REFCOUNT_BITS,                            \
+        .type = QEMU_OPT_NUMBER,                                    \
+        .help = "Width of a reference count entry in bits",         \
+        .def_value_str = "16"                                       \
+    }                                                               \
+
 static QemuOptsList qcow2_create_opts = {
     .name = "qcow2-create-opts",
     .head = QTAILQ_HEAD_INITIALIZER(qcow2_create_opts.head),
     .desc = {
-        {
-            .name = BLOCK_OPT_SIZE,
-            .type = QEMU_OPT_SIZE,
-            .help = "Virtual disk size"
-        },
-        {
-            .name = BLOCK_OPT_COMPAT_LEVEL,
-            .type = QEMU_OPT_STRING,
-            .help = "Compatibility level (v2 [0.10] or v3 [1.1])"
-        },
-        {
-            .name = BLOCK_OPT_BACKING_FILE,
-            .type = QEMU_OPT_STRING,
-            .help = "File name of a base image"
-        },
-        {
-            .name = BLOCK_OPT_BACKING_FMT,
-            .type = QEMU_OPT_STRING,
-            .help = "Image format of the base image"
-        },
-        {
-            .name = BLOCK_OPT_DATA_FILE,
-            .type = QEMU_OPT_STRING,
-            .help = "File name of an external data file"
-        },
-        {
-            .name = BLOCK_OPT_DATA_FILE_RAW,
-            .type = QEMU_OPT_BOOL,
-            .help = "The external data file must stay valid as a raw image"
-        },
-        {
-            .name = BLOCK_OPT_ENCRYPT,
-            .type = QEMU_OPT_BOOL,
-            .help = "Encrypt the image with format 'aes'. (Deprecated "
-                    "in favor of " BLOCK_OPT_ENCRYPT_FORMAT "=aes)",
-        },
-        {
-            .name = BLOCK_OPT_ENCRYPT_FORMAT,
-            .type = QEMU_OPT_STRING,
-            .help = "Encrypt the image, format choices: 'aes', 'luks'",
-        },
-        BLOCK_CRYPTO_OPT_DEF_KEY_SECRET("encrypt.",
-            "ID of secret providing qcow AES key or LUKS passphrase"),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_ALG("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_CIPHER_MODE("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_ALG("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG("encrypt."),
-        BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME("encrypt."),
-        {
-            .name = BLOCK_OPT_CLUSTER_SIZE,
-            .type = QEMU_OPT_SIZE,
-            .help = "qcow2 cluster size",
-            .def_value_str = stringify(DEFAULT_CLUSTER_SIZE)
-        },
-        {
-            .name = BLOCK_OPT_PREALLOC,
-            .type = QEMU_OPT_STRING,
-            .help = "Preallocation mode (allowed values: off, metadata, "
-                    "falloc, full)"
-        },
-        {
-            .name = BLOCK_OPT_LAZY_REFCOUNTS,
-            .type = QEMU_OPT_BOOL,
-            .help = "Postpone refcount updates",
-            .def_value_str = "off"
-        },
-        {
-            .name = BLOCK_OPT_REFCOUNT_BITS,
-            .type = QEMU_OPT_NUMBER,
-            .help = "Width of a reference count entry in bits",
-            .def_value_str = "16"
-        },
+        QCOW_COMMON_OPTIONS,
+        { /* end of list */ }
+    }
+};
+
+static QemuOptsList qcow2_amend_opts = {
+    .name = "qcow2-amend-opts",
+    .head = QTAILQ_HEAD_INITIALIZER(qcow2_amend_opts.head),
+    .desc = {
+        QCOW_COMMON_OPTIONS,
         { /* end of list */ }
     }
 };
@@ -5581,6 +5594,7 @@ BlockDriver bdrv_qcow2 = {
     .bdrv_inactivate            = qcow2_inactivate,
 
     .create_opts         = &qcow2_create_opts,
+    .amend_opts          = &qcow2_amend_opts,
     .strong_runtime_opts = qcow2_strong_runtime_opts,
     .mutable_opts        = mutable_opts,
     .bdrv_co_check       = qcow2_co_check,
diff --git a/include/block/block_int.h b/include/block/block_int.h
index 810a9ecb86..6f0abf8544 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -407,6 +407,10 @@ struct BlockDriver {
 
     /* List of options for creating images, terminated by name == NULL */
     QemuOptsList *create_opts;
+
+    /* List of options for image amend*/
+    QemuOptsList *amend_opts;
+
     /*
      * If this driver supports reopening images this contains a
      * NULL-terminated list of the runtime options that can be
diff --git a/qemu-img.c b/qemu-img.c
index a79f3904db..befd53943b 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -3878,11 +3878,11 @@ static int print_amend_option_help(const char *format)
         return 1;
     }
 
-    /* Every driver supporting amendment must have create_opts */
-    assert(drv->create_opts);
+    /* Every driver supporting amendment must have amend_opts */
+    assert(drv->amend_opts);
 
     printf("Creation options for '%s':\n", format);
-    qemu_opts_print_help(drv->create_opts, false);
+    qemu_opts_print_help(drv->amend_opts, false);
     printf("\nNote that not all of these options may be amendable.\n");
     return 0;
 }
@@ -3892,7 +3892,7 @@ static int img_amend(int argc, char **argv)
     Error *err = NULL;
     int c, ret = 0;
     char *options = NULL;
-    QemuOptsList *create_opts = NULL;
+    QemuOptsList *amend_opts = NULL;
     QemuOpts *opts = NULL;
     const char *fmt = NULL, *filename, *cache;
     int flags;
@@ -4031,11 +4031,11 @@ static int img_amend(int argc, char **argv)
         goto out;
     }
 
-    /* Every driver supporting amendment must have create_opts */
-    assert(bs->drv->create_opts);
+    /* Every driver supporting amendment must have amend_opts */
+    assert(bs->drv->amend_opts);
 
-    create_opts = qemu_opts_append(create_opts, bs->drv->create_opts);
-    opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
+    amend_opts = qemu_opts_append(amend_opts, bs->drv->amend_opts);
+    opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
     qemu_opts_do_parse(opts, options, NULL, &err);
     if (err) {
         error_report_err(err);
@@ -4058,7 +4058,7 @@ out:
 out_no_progress:
     blk_unref(blk);
     qemu_opts_del(opts);
-    qemu_opts_free(create_opts);
+    qemu_opts_free(amend_opts);
     g_free(options);
 
     if (ret) {
-- 
2.17.2



^ permalink raw reply related	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2020-05-04  9:19 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-03-08 15:18 [PATCH v2 00/14] LUKS: encryption slot management using amend interface Maxim Levitsky
2020-03-08 15:18 ` [PATCH v2 01/14] qcrypto/core: add generic infrastructure for crypto options amendment Maxim Levitsky
2020-03-08 15:18 ` [PATCH v2 02/14] qcrypto/luks: implement encryption key management Maxim Levitsky
2020-03-10 10:58   ` Max Reitz
2020-03-10 11:05     ` Maxim Levitsky
2020-03-10 11:59       ` Kevin Wolf
2020-03-10 12:02         ` Maxim Levitsky
2020-03-11 12:55           ` Maxim Levitsky
2020-04-28 13:16   ` Daniel P. Berrangé
2020-05-03  8:55     ` Maxim Levitsky
2020-05-04  9:18       ` Daniel P. Berrangé
2020-03-08 15:18 ` [PATCH v2 03/14] block/amend: add 'force' option Maxim Levitsky
2020-03-08 15:18 ` [PATCH v2 04/14] block/amend: separate amend and create options for qemu-img Maxim Levitsky
2020-04-28 15:03   ` Daniel P. Berrangé
2020-04-28 15:49     ` Daniel P. Berrangé
2020-03-08 15:18 ` [PATCH v2 05/14] block/amend: refactor qcow2 amend options Maxim Levitsky
2020-04-28 15:51   ` Daniel P. Berrangé
2020-03-08 15:18 ` [PATCH v2 06/14] block/crypto: rename two functions Maxim Levitsky
2020-03-08 15:18 ` [PATCH v2 07/14] block/crypto: implement the encryption key management Maxim Levitsky
2020-04-28 16:15   ` Daniel P. Berrangé
2020-03-08 15:18 ` [PATCH v2 08/14] block/qcow2: extend qemu-img amend interface with crypto options Maxim Levitsky
2020-04-28 16:17   ` Daniel P. Berrangé
2020-03-08 15:18 ` [PATCH v2 09/14] iotests: filter few more luks specific create options Maxim Levitsky
2020-04-28 16:19   ` Daniel P. Berrangé
2020-03-08 15:18 ` [PATCH v2 10/14] iotests: qemu-img tests for luks key management Maxim Levitsky
2020-04-28 16:21   ` Daniel P. Berrangé
2020-03-08 15:19 ` [PATCH v2 11/14] block/core: add generic infrastructure for x-blockdev-amend qmp command Maxim Levitsky
2020-04-28 16:25   ` Daniel P. Berrangé
2020-03-08 15:19 ` [PATCH v2 12/14] block/crypto: implement blockdev-amend Maxim Levitsky
2020-03-08 15:19 ` [PATCH v2 13/14] block/qcow2: " Maxim Levitsky
2020-03-08 15:19 ` [PATCH v2 14/14] iotests: add tests for blockdev-amend Maxim Levitsky
2020-04-28 16:23   ` Daniel P. Berrangé
2020-03-12 11:56 ` [PATCH v2 00/14] LUKS: encryption slot management using amend interface Eric Blake
2020-03-12 14:33   ` Maxim Levitsky
  -- strict thread matches above, loose matches on Subject: below --
2020-01-30 17:29 Maxim Levitsky
2020-01-30 17:29 ` [PATCH v2 04/14] block/amend: separate amend and create options for qemu-img Maxim Levitsky

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.