]> git.proxmox.com Git - pve-qemu.git/commitdiff
backup: add patch to initialize bcs bitmap early enough for PBS
authorFabian Ebner <f.ebner@proxmox.com>
Wed, 2 Mar 2022 09:05:16 +0000 (10:05 +0100)
committerWolfgang Bumiller <w.bumiller@proxmox.com>
Thu, 3 Mar 2022 10:37:17 +0000 (11:37 +0100)
This is necessary for multi-disk backups where not all jobs are
immediately started after they are created. QEMU commit
06e0a9c16405c0a4c1eca33cf286cc04c42066a2 did already part of the work,
ensuring that new writes after job creation don't pass through to the
backup, but not yet for the MIRROR_SYNC_MODE_BITMAP case which is used
for PBS.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
52 files changed:
debian/patches/pve/0025-PVE-Backup-add-vma-backup-format-code.patch [deleted file]
debian/patches/pve/0025-block-backup-move-bcs-bitmap-initialization-to-job-c.patch [new file with mode: 0644]
debian/patches/pve/0026-PVE-Backup-add-backup-dump-block-driver.patch [deleted file]
debian/patches/pve/0026-PVE-Backup-add-vma-backup-format-code.patch [new file with mode: 0644]
debian/patches/pve/0027-PVE-Backup-add-backup-dump-block-driver.patch [new file with mode: 0644]
debian/patches/pve/0027-PVE-Backup-proxmox-backup-patches-for-qemu.patch [deleted file]
debian/patches/pve/0028-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch [deleted file]
debian/patches/pve/0028-PVE-Backup-proxmox-backup-patches-for-qemu.patch [new file with mode: 0644]
debian/patches/pve/0029-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch [deleted file]
debian/patches/pve/0029-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch [new file with mode: 0644]
debian/patches/pve/0030-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch [new file with mode: 0644]
debian/patches/pve/0030-PVE-various-PBS-fixes.patch [deleted file]
debian/patches/pve/0031-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch [deleted file]
debian/patches/pve/0031-PVE-various-PBS-fixes.patch [new file with mode: 0644]
debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch [new file with mode: 0644]
debian/patches/pve/0032-PVE-add-query_proxmox_support-QMP-command.patch [deleted file]
debian/patches/pve/0033-PVE-add-query-pbs-bitmap-info-QMP-call.patch [deleted file]
debian/patches/pve/0033-PVE-add-query_proxmox_support-QMP-command.patch [new file with mode: 0644]
debian/patches/pve/0034-PVE-add-query-pbs-bitmap-info-QMP-call.patch [new file with mode: 0644]
debian/patches/pve/0034-PVE-redirect-stderr-to-journal-when-daemonized.patch [deleted file]
debian/patches/pve/0035-PVE-Add-sequential-job-transaction-support.patch [deleted file]
debian/patches/pve/0035-PVE-redirect-stderr-to-journal-when-daemonized.patch [new file with mode: 0644]
debian/patches/pve/0036-PVE-Add-sequential-job-transaction-support.patch [new file with mode: 0644]
debian/patches/pve/0036-PVE-Backup-Use-a-transaction-to-synchronize-job-stat.patch [deleted file]
debian/patches/pve/0037-PVE-Backup-Don-t-block-on-finishing-and-cleanup-crea.patch [deleted file]
debian/patches/pve/0037-PVE-Backup-Use-a-transaction-to-synchronize-job-stat.patch [new file with mode: 0644]
debian/patches/pve/0038-PVE-Backup-Don-t-block-on-finishing-and-cleanup-crea.patch [new file with mode: 0644]
debian/patches/pve/0038-PVE-Migrate-dirty-bitmap-state-via-savevm.patch [deleted file]
debian/patches/pve/0039-PVE-Migrate-dirty-bitmap-state-via-savevm.patch [new file with mode: 0644]
debian/patches/pve/0039-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch [deleted file]
debian/patches/pve/0040-PVE-fall-back-to-open-iscsi-initiatorname.patch [deleted file]
debian/patches/pve/0040-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch [new file with mode: 0644]
debian/patches/pve/0041-PVE-Use-coroutine-QMP-for-backup-cancel_backup.patch [deleted file]
debian/patches/pve/0041-PVE-fall-back-to-open-iscsi-initiatorname.patch [new file with mode: 0644]
debian/patches/pve/0042-PBS-add-master-key-support.patch [deleted file]
debian/patches/pve/0042-PVE-Use-coroutine-QMP-for-backup-cancel_backup.patch [new file with mode: 0644]
debian/patches/pve/0043-PBS-add-master-key-support.patch [new file with mode: 0644]
debian/patches/pve/0043-PVE-block-pbs-fast-path-reads-without-allocation-if-.patch [deleted file]
debian/patches/pve/0044-PVE-block-pbs-fast-path-reads-without-allocation-if-.patch [new file with mode: 0644]
debian/patches/pve/0044-PVE-block-stream-increase-chunk-size.patch [deleted file]
debian/patches/pve/0045-PVE-block-stream-increase-chunk-size.patch [new file with mode: 0644]
debian/patches/pve/0045-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch [deleted file]
debian/patches/pve/0046-block-add-alloc-track-driver.patch [deleted file]
debian/patches/pve/0046-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch [new file with mode: 0644]
debian/patches/pve/0047-PVE-whitelist-invalid-QAPI-names-for-backwards-compa.patch [deleted file]
debian/patches/pve/0047-block-add-alloc-track-driver.patch [new file with mode: 0644]
debian/patches/pve/0048-PVE-savevm-async-register-yank-before-migration_inco.patch [deleted file]
debian/patches/pve/0048-PVE-whitelist-invalid-QAPI-names-for-backwards-compa.patch [new file with mode: 0644]
debian/patches/pve/0049-PVE-savevm-async-register-yank-before-migration_inco.patch [new file with mode: 0644]
debian/patches/pve/0049-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch [deleted file]
debian/patches/pve/0050-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch [new file with mode: 0644]
debian/patches/series

diff --git a/debian/patches/pve/0025-PVE-Backup-add-vma-backup-format-code.patch b/debian/patches/pve/0025-PVE-Backup-add-vma-backup-format-code.patch
deleted file mode 100644 (file)
index c4ed5bb..0000000
+++ /dev/null
@@ -1,2729 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Dietmar Maurer <dietmar@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:57 +0200
-Subject: [PATCH] PVE-Backup: add vma backup format code
-
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-[FE: create: register all streams before entering coroutines]
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
----
- block/meson.build |   2 +
- meson.build       |   5 +
- vma-reader.c      | 857 ++++++++++++++++++++++++++++++++++++++++++++++
- vma-writer.c      | 790 ++++++++++++++++++++++++++++++++++++++++++
- vma.c             | 851 +++++++++++++++++++++++++++++++++++++++++++++
- vma.h             | 150 ++++++++
- 6 files changed, 2655 insertions(+)
- create mode 100644 vma-reader.c
- create mode 100644 vma-writer.c
- create mode 100644 vma.c
- create mode 100644 vma.h
-
-diff --git a/block/meson.build b/block/meson.build
-index c9d1fdca7d..72081a9974 100644
---- a/block/meson.build
-+++ b/block/meson.build
-@@ -44,6 +44,8 @@ block_ss.add(files(
-   'zeroinit.c',
- ), zstd, zlib, gnutls)
-+block_ss.add(files('../vma-writer.c'), libuuid)
-+
- softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c'))
- block_ss.add(when: 'CONFIG_QCOW1', if_true: files('qcow.c'))
-diff --git a/meson.build b/meson.build
-index 96de1a6ef9..54c23b9567 100644
---- a/meson.build
-+++ b/meson.build
-@@ -1202,6 +1202,8 @@ keyutils = dependency('libkeyutils', required: false,
- has_gettid = cc.has_function('gettid')
-+libuuid = cc.find_library('uuid', required: true)
-+
- # libselinux
- selinux = dependency('libselinux',
-                      required: get_option('selinux'),
-@@ -3070,6 +3072,9 @@ if have_tools
-                dependencies: [blockdev, qemuutil, gnutls, selinux],
-                install: true)
-+  vma = executable('vma', files('vma.c', 'vma-reader.c') + genh,
-+                   dependencies: [authz, block, crypto, io, qom], install: true)
-+
-   subdir('storage-daemon')
-   subdir('contrib/rdmacm-mux')
-   subdir('contrib/elf2dmp')
-diff --git a/vma-reader.c b/vma-reader.c
-new file mode 100644
-index 0000000000..2b1d1cdab3
---- /dev/null
-+++ b/vma-reader.c
-@@ -0,0 +1,857 @@
-+/*
-+ * VMA: Virtual Machine Archive
-+ *
-+ * Copyright (C) 2012 Proxmox Server Solutions
-+ *
-+ * Authors:
-+ *  Dietmar Maurer (dietmar@proxmox.com)
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ *
-+ */
-+
-+#include "qemu/osdep.h"
-+#include <glib.h>
-+#include <uuid/uuid.h>
-+
-+#include "qemu-common.h"
-+#include "qemu/timer.h"
-+#include "qemu/ratelimit.h"
-+#include "vma.h"
-+#include "block/block.h"
-+#include "sysemu/block-backend.h"
-+
-+static unsigned char zero_vma_block[VMA_BLOCK_SIZE];
-+
-+typedef struct VmaRestoreState {
-+    BlockBackend *target;
-+    bool write_zeroes;
-+    unsigned long *bitmap;
-+    int bitmap_size;
-+}  VmaRestoreState;
-+
-+struct VmaReader {
-+    int fd;
-+    GChecksum *md5csum;
-+    GHashTable *blob_hash;
-+    unsigned char *head_data;
-+    VmaDeviceInfo devinfo[256];
-+    VmaRestoreState rstate[256];
-+    GList *cdata_list;
-+    guint8 vmstate_stream;
-+    uint32_t vmstate_clusters;
-+    /* to show restore percentage if run with -v */
-+    time_t start_time;
-+    int64_t cluster_count;
-+    int64_t clusters_read;
-+    int64_t zero_cluster_data;
-+    int64_t partial_zero_cluster_data;
-+    int clusters_read_per;
-+};
-+
-+static guint
-+g_int32_hash(gconstpointer v)
-+{
-+    return *(const uint32_t *)v;
-+}
-+
-+static gboolean
-+g_int32_equal(gconstpointer v1, gconstpointer v2)
-+{
-+    return *((const uint32_t *)v1) == *((const uint32_t *)v2);
-+}
-+
-+static int vma_reader_get_bitmap(VmaRestoreState *rstate, int64_t cluster_num)
-+{
-+    assert(rstate);
-+    assert(rstate->bitmap);
-+
-+    unsigned long val, idx, bit;
-+
-+    idx = cluster_num / BITS_PER_LONG;
-+
-+    assert(rstate->bitmap_size > idx);
-+
-+    bit = cluster_num % BITS_PER_LONG;
-+    val = rstate->bitmap[idx];
-+
-+    return !!(val & (1UL << bit));
-+}
-+
-+static void vma_reader_set_bitmap(VmaRestoreState *rstate, int64_t cluster_num,
-+                                  int dirty)
-+{
-+    assert(rstate);
-+    assert(rstate->bitmap);
-+
-+    unsigned long val, idx, bit;
-+
-+    idx = cluster_num / BITS_PER_LONG;
-+
-+    assert(rstate->bitmap_size > idx);
-+
-+    bit = cluster_num % BITS_PER_LONG;
-+    val = rstate->bitmap[idx];
-+    if (dirty) {
-+        if (!(val & (1UL << bit))) {
-+            val |= 1UL << bit;
-+        }
-+    } else {
-+        if (val & (1UL << bit)) {
-+            val &= ~(1UL << bit);
-+        }
-+    }
-+    rstate->bitmap[idx] = val;
-+}
-+
-+typedef struct VmaBlob {
-+    uint32_t start;
-+    uint32_t len;
-+    void *data;
-+} VmaBlob;
-+
-+static const VmaBlob *get_header_blob(VmaReader *vmar, uint32_t pos)
-+{
-+    assert(vmar);
-+    assert(vmar->blob_hash);
-+
-+    return g_hash_table_lookup(vmar->blob_hash, &pos);
-+}
-+
-+static const char *get_header_str(VmaReader *vmar, uint32_t pos)
-+{
-+    const VmaBlob *blob = get_header_blob(vmar, pos);
-+    if (!blob) {
-+        return NULL;
-+    }
-+    const char *res = (char *)blob->data;
-+    if (res[blob->len-1] != '\0') {
-+        return NULL;
-+    }
-+    return res;
-+}
-+
-+static ssize_t
-+safe_read(int fd, unsigned char *buf, size_t count)
-+{
-+    ssize_t n;
-+
-+    do {
-+        n = read(fd, buf, count);
-+    } while (n < 0 && errno == EINTR);
-+
-+    return n;
-+}
-+
-+static ssize_t
-+full_read(int fd, unsigned char *buf, size_t len)
-+{
-+    ssize_t n;
-+    size_t total;
-+
-+    total = 0;
-+
-+    while (len > 0) {
-+        n = safe_read(fd, buf, len);
-+
-+        if (n == 0) {
-+            return total;
-+        }
-+
-+        if (n <= 0) {
-+            break;
-+        }
-+
-+        buf += n;
-+        total += n;
-+        len -= n;
-+    }
-+
-+    if (len) {
-+        return -1;
-+    }
-+
-+    return total;
-+}
-+
-+void vma_reader_destroy(VmaReader *vmar)
-+{
-+    assert(vmar);
-+
-+    if (vmar->fd >= 0) {
-+        close(vmar->fd);
-+    }
-+
-+    if (vmar->cdata_list) {
-+        g_list_free(vmar->cdata_list);
-+    }
-+
-+    int i;
-+    for (i = 1; i < 256; i++) {
-+        if (vmar->rstate[i].bitmap) {
-+            g_free(vmar->rstate[i].bitmap);
-+        }
-+    }
-+
-+    if (vmar->md5csum) {
-+        g_checksum_free(vmar->md5csum);
-+    }
-+
-+    if (vmar->blob_hash) {
-+        g_hash_table_destroy(vmar->blob_hash);
-+    }
-+
-+    if (vmar->head_data) {
-+        g_free(vmar->head_data);
-+    }
-+
-+    g_free(vmar);
-+
-+};
-+
-+static int vma_reader_read_head(VmaReader *vmar, Error **errp)
-+{
-+    assert(vmar);
-+    assert(errp);
-+    assert(*errp == NULL);
-+
-+    unsigned char md5sum[16];
-+    int i;
-+    int ret = 0;
-+
-+    vmar->head_data = g_malloc(sizeof(VmaHeader));
-+
-+    if (full_read(vmar->fd, vmar->head_data, sizeof(VmaHeader)) !=
-+        sizeof(VmaHeader)) {
-+        error_setg(errp, "can't read vma header - %s",
-+                   errno ? g_strerror(errno) : "got EOF");
-+        return -1;
-+    }
-+
-+    VmaHeader *h = (VmaHeader *)vmar->head_data;
-+
-+    if (h->magic != VMA_MAGIC) {
-+        error_setg(errp, "not a vma file - wrong magic number");
-+        return -1;
-+    }
-+
-+    uint32_t header_size = GUINT32_FROM_BE(h->header_size);
-+    int need = header_size - sizeof(VmaHeader);
-+    if (need <= 0) {
-+        error_setg(errp, "wrong vma header size %d", header_size);
-+        return -1;
-+    }
-+
-+    vmar->head_data = g_realloc(vmar->head_data, header_size);
-+    h = (VmaHeader *)vmar->head_data;
-+
-+    if (full_read(vmar->fd, vmar->head_data + sizeof(VmaHeader), need) !=
-+        need) {
-+        error_setg(errp, "can't read vma header data - %s",
-+                   errno ? g_strerror(errno) : "got EOF");
-+        return -1;
-+    }
-+
-+    memcpy(md5sum, h->md5sum, 16);
-+    memset(h->md5sum, 0, 16);
-+
-+    g_checksum_reset(vmar->md5csum);
-+    g_checksum_update(vmar->md5csum, vmar->head_data, header_size);
-+    gsize csize = 16;
-+    g_checksum_get_digest(vmar->md5csum, (guint8 *)(h->md5sum), &csize);
-+
-+    if (memcmp(md5sum, h->md5sum, 16) != 0) {
-+        error_setg(errp, "wrong vma header chechsum");
-+        return -1;
-+    }
-+
-+    /* we can modify header data after checksum verify */
-+    h->header_size = header_size;
-+
-+    h->version = GUINT32_FROM_BE(h->version);
-+    if (h->version != 1) {
-+        error_setg(errp, "wrong vma version %d", h->version);
-+        return -1;
-+    }
-+
-+    h->ctime = GUINT64_FROM_BE(h->ctime);
-+    h->blob_buffer_offset = GUINT32_FROM_BE(h->blob_buffer_offset);
-+    h->blob_buffer_size = GUINT32_FROM_BE(h->blob_buffer_size);
-+
-+    uint32_t bstart = h->blob_buffer_offset + 1;
-+    uint32_t bend = h->blob_buffer_offset + h->blob_buffer_size;
-+
-+    if (bstart <= sizeof(VmaHeader)) {
-+        error_setg(errp, "wrong vma blob buffer offset %d",
-+                   h->blob_buffer_offset);
-+        return -1;
-+    }
-+
-+    if (bend > header_size) {
-+        error_setg(errp, "wrong vma blob buffer size %d/%d",
-+                   h->blob_buffer_offset, h->blob_buffer_size);
-+        return -1;
-+    }
-+
-+    while ((bstart + 2) <= bend) {
-+        uint32_t size = vmar->head_data[bstart] +
-+            (vmar->head_data[bstart+1] << 8);
-+        if ((bstart + size + 2) <= bend) {
-+            VmaBlob *blob = g_new0(VmaBlob, 1);
-+            blob->start = bstart - h->blob_buffer_offset;
-+            blob->len = size;
-+            blob->data = vmar->head_data + bstart + 2;
-+            g_hash_table_insert(vmar->blob_hash, &blob->start, blob);
-+        }
-+        bstart += size + 2;
-+    }
-+
-+
-+    int count = 0;
-+    for (i = 1; i < 256; i++) {
-+        VmaDeviceInfoHeader *dih = &h->dev_info[i];
-+        uint32_t devname_ptr = GUINT32_FROM_BE(dih->devname_ptr);
-+        uint64_t size = GUINT64_FROM_BE(dih->size);
-+        const char *devname =  get_header_str(vmar, devname_ptr);
-+
-+        if (size && devname) {
-+            count++;
-+            vmar->devinfo[i].size = size;
-+            vmar->devinfo[i].devname = devname;
-+
-+            if (strcmp(devname, "vmstate") == 0) {
-+                vmar->vmstate_stream = i;
-+            }
-+        }
-+    }
-+
-+    for (i = 0; i < VMA_MAX_CONFIGS; i++) {
-+        uint32_t name_ptr = GUINT32_FROM_BE(h->config_names[i]);
-+        uint32_t data_ptr = GUINT32_FROM_BE(h->config_data[i]);
-+
-+        if (!(name_ptr && data_ptr)) {
-+            continue;
-+        }
-+        const char *name =  get_header_str(vmar, name_ptr);
-+        const VmaBlob *blob = get_header_blob(vmar, data_ptr);
-+
-+        if (!(name && blob)) {
-+            error_setg(errp, "vma contains invalid data pointers");
-+            return -1;
-+        }
-+
-+        VmaConfigData *cdata = g_new0(VmaConfigData, 1);
-+        cdata->name = name;
-+        cdata->data = blob->data;
-+        cdata->len = blob->len;
-+
-+        vmar->cdata_list = g_list_append(vmar->cdata_list, cdata);
-+    }
-+
-+    return ret;
-+};
-+
-+VmaReader *vma_reader_create(const char *filename, Error **errp)
-+{
-+    assert(filename);
-+    assert(errp);
-+
-+    VmaReader *vmar = g_new0(VmaReader, 1);
-+
-+    if (strcmp(filename, "-") == 0) {
-+        vmar->fd = dup(0);
-+    } else {
-+        vmar->fd = open(filename, O_RDONLY);
-+    }
-+
-+    if (vmar->fd < 0) {
-+        error_setg(errp, "can't open file %s - %s\n", filename,
-+                   g_strerror(errno));
-+        goto err;
-+    }
-+
-+    vmar->md5csum = g_checksum_new(G_CHECKSUM_MD5);
-+    if (!vmar->md5csum) {
-+        error_setg(errp, "can't allocate cmsum\n");
-+        goto err;
-+    }
-+
-+    vmar->blob_hash = g_hash_table_new_full(g_int32_hash, g_int32_equal,
-+                                            NULL, g_free);
-+
-+    if (vma_reader_read_head(vmar, errp) < 0) {
-+        goto err;
-+    }
-+
-+    return vmar;
-+
-+err:
-+    if (vmar) {
-+        vma_reader_destroy(vmar);
-+    }
-+
-+    return NULL;
-+}
-+
-+VmaHeader *vma_reader_get_header(VmaReader *vmar)
-+{
-+    assert(vmar);
-+    assert(vmar->head_data);
-+
-+    return (VmaHeader *)(vmar->head_data);
-+}
-+
-+GList *vma_reader_get_config_data(VmaReader *vmar)
-+{
-+    assert(vmar);
-+    assert(vmar->head_data);
-+
-+    return vmar->cdata_list;
-+}
-+
-+VmaDeviceInfo *vma_reader_get_device_info(VmaReader *vmar, guint8 dev_id)
-+{
-+    assert(vmar);
-+    assert(dev_id);
-+
-+    if (vmar->devinfo[dev_id].size && vmar->devinfo[dev_id].devname) {
-+        return &vmar->devinfo[dev_id];
-+    }
-+
-+    return NULL;
-+}
-+
-+static void allocate_rstate(VmaReader *vmar,  guint8 dev_id,
-+                            BlockBackend *target, bool write_zeroes)
-+{
-+    assert(vmar);
-+    assert(dev_id);
-+
-+    vmar->rstate[dev_id].target = target;
-+    vmar->rstate[dev_id].write_zeroes = write_zeroes;
-+
-+    int64_t size = vmar->devinfo[dev_id].size;
-+
-+    int64_t bitmap_size = (size/BDRV_SECTOR_SIZE) +
-+        (VMA_CLUSTER_SIZE/BDRV_SECTOR_SIZE) * BITS_PER_LONG - 1;
-+    bitmap_size /= (VMA_CLUSTER_SIZE/BDRV_SECTOR_SIZE) * BITS_PER_LONG;
-+
-+    vmar->rstate[dev_id].bitmap_size = bitmap_size;
-+    vmar->rstate[dev_id].bitmap = g_new0(unsigned long, bitmap_size);
-+
-+    vmar->cluster_count += size/VMA_CLUSTER_SIZE;
-+}
-+
-+int vma_reader_register_bs(VmaReader *vmar, guint8 dev_id, BlockBackend *target,
-+                           bool write_zeroes, Error **errp)
-+{
-+    assert(vmar);
-+    assert(target != NULL);
-+    assert(dev_id);
-+    assert(vmar->rstate[dev_id].target == NULL);
-+
-+    int64_t size = blk_getlength(target);
-+    int64_t size_diff = size - vmar->devinfo[dev_id].size;
-+
-+    /* storage types can have different size restrictions, so it
-+     * is not always possible to create an image with exact size.
-+     * So we tolerate a size difference up to 4MB.
-+     */
-+    if ((size_diff < 0) || (size_diff > 4*1024*1024)) {
-+        error_setg(errp, "vma_reader_register_bs for stream %s failed - "
-+                   "unexpected size %zd != %zd", vmar->devinfo[dev_id].devname,
-+                   size, vmar->devinfo[dev_id].size);
-+        return -1;
-+    }
-+
-+    allocate_rstate(vmar, dev_id, target, write_zeroes);
-+
-+    return 0;
-+}
-+
-+static ssize_t safe_write(int fd, void *buf, size_t count)
-+{
-+    ssize_t n;
-+
-+    do {
-+        n = write(fd, buf, count);
-+    } while (n < 0 && errno == EINTR);
-+
-+    return n;
-+}
-+
-+static size_t full_write(int fd, void *buf, size_t len)
-+{
-+    ssize_t n;
-+    size_t total;
-+
-+    total = 0;
-+
-+    while (len > 0) {
-+        n = safe_write(fd, buf, len);
-+        if (n < 0) {
-+            return n;
-+        }
-+        buf += n;
-+        total += n;
-+        len -= n;
-+    }
-+
-+    if (len) {
-+        /* incomplete write ? */
-+        return -1;
-+    }
-+
-+    return total;
-+}
-+
-+static int restore_write_data(VmaReader *vmar, guint8 dev_id,
-+                              BlockBackend *target, int vmstate_fd,
-+                              unsigned char *buf, int64_t sector_num,
-+                              int nb_sectors, Error **errp)
-+{
-+    assert(vmar);
-+
-+    if (dev_id == vmar->vmstate_stream) {
-+        if (vmstate_fd >= 0) {
-+            int len = nb_sectors * BDRV_SECTOR_SIZE;
-+            int res = full_write(vmstate_fd, buf, len);
-+            if (res < 0) {
-+                error_setg(errp, "write vmstate failed %d", res);
-+                return -1;
-+            }
-+        }
-+    } else {
-+        int res = blk_pwrite(target, sector_num * BDRV_SECTOR_SIZE, buf, nb_sectors * BDRV_SECTOR_SIZE, 0);
-+        if (res < 0) {
-+            error_setg(errp, "blk_pwrite to %s failed (%d)",
-+                       bdrv_get_device_name(blk_bs(target)), res);
-+            return -1;
-+        }
-+    }
-+    return 0;
-+}
-+
-+static int restore_extent(VmaReader *vmar, unsigned char *buf,
-+                          int extent_size, int vmstate_fd,
-+                          bool verbose, bool verify, Error **errp)
-+{
-+    assert(vmar);
-+    assert(buf);
-+
-+    VmaExtentHeader *ehead = (VmaExtentHeader *)buf;
-+    int start = VMA_EXTENT_HEADER_SIZE;
-+    int i;
-+
-+    for (i = 0; i < VMA_BLOCKS_PER_EXTENT; i++) {
-+        uint64_t block_info = GUINT64_FROM_BE(ehead->blockinfo[i]);
-+        uint64_t cluster_num = block_info & 0xffffffff;
-+        uint8_t dev_id = (block_info >> 32) & 0xff;
-+        uint16_t mask = block_info >> (32+16);
-+        int64_t max_sector;
-+
-+        if (!dev_id) {
-+            continue;
-+        }
-+
-+        VmaRestoreState *rstate = &vmar->rstate[dev_id];
-+        BlockBackend *target = NULL;
-+
-+        if (dev_id != vmar->vmstate_stream) {
-+            target = rstate->target;
-+            if (!verify && !target) {
-+                error_setg(errp, "got wrong dev id %d", dev_id);
-+                return -1;
-+            }
-+
-+            if (vma_reader_get_bitmap(rstate, cluster_num)) {
-+                error_setg(errp, "found duplicated cluster %zd for stream %s",
-+                          cluster_num, vmar->devinfo[dev_id].devname);
-+                return -1;
-+            }
-+            vma_reader_set_bitmap(rstate, cluster_num, 1);
-+
-+            max_sector = vmar->devinfo[dev_id].size/BDRV_SECTOR_SIZE;
-+        } else {
-+            max_sector = G_MAXINT64;
-+            if (cluster_num != vmar->vmstate_clusters) {
-+                error_setg(errp, "found out of order vmstate data");
-+                return -1;
-+            }
-+            vmar->vmstate_clusters++;
-+        }
-+
-+        vmar->clusters_read++;
-+
-+        if (verbose) {
-+            time_t duration = time(NULL) - vmar->start_time;
-+            int percent = (vmar->clusters_read*100)/vmar->cluster_count;
-+            if (percent != vmar->clusters_read_per) {
-+                printf("progress %d%% (read %zd bytes, duration %zd sec)\n",
-+                       percent, vmar->clusters_read*VMA_CLUSTER_SIZE,
-+                       duration);
-+                fflush(stdout);
-+                vmar->clusters_read_per = percent;
-+            }
-+        }
-+
-+        /* try to write whole clusters to speedup restore */
-+        if (mask == 0xffff) {
-+            if ((start + VMA_CLUSTER_SIZE) > extent_size) {
-+                error_setg(errp, "short vma extent - too many blocks");
-+                return -1;
-+            }
-+            int64_t sector_num = (cluster_num * VMA_CLUSTER_SIZE) /
-+                BDRV_SECTOR_SIZE;
-+            int64_t end_sector = sector_num +
-+                VMA_CLUSTER_SIZE/BDRV_SECTOR_SIZE;
-+
-+            if (end_sector > max_sector) {
-+                end_sector = max_sector;
-+            }
-+
-+            if (end_sector <= sector_num) {
-+                error_setg(errp, "got wrong block address - write beyond end");
-+                return -1;
-+            }
-+
-+            if (!verify) {
-+                int nb_sectors = end_sector - sector_num;
-+                if (restore_write_data(vmar, dev_id, target, vmstate_fd,
-+                                       buf + start, sector_num, nb_sectors,
-+                                       errp) < 0) {
-+                    return -1;
-+                }
-+            }
-+
-+            start += VMA_CLUSTER_SIZE;
-+        } else {
-+            int j;
-+            int bit = 1;
-+
-+            for (j = 0; j < 16; j++) {
-+                int64_t sector_num = (cluster_num*VMA_CLUSTER_SIZE +
-+                                      j*VMA_BLOCK_SIZE)/BDRV_SECTOR_SIZE;
-+
-+                int64_t end_sector = sector_num +
-+                    VMA_BLOCK_SIZE/BDRV_SECTOR_SIZE;
-+                if (end_sector > max_sector) {
-+                    end_sector = max_sector;
-+                }
-+
-+                if (mask & bit) {
-+                    if ((start + VMA_BLOCK_SIZE) > extent_size) {
-+                        error_setg(errp, "short vma extent - too many blocks");
-+                        return -1;
-+                    }
-+
-+                    if (end_sector <= sector_num) {
-+                        error_setg(errp, "got wrong block address - "
-+                                   "write beyond end");
-+                        return -1;
-+                    }
-+
-+                    if (!verify) {
-+                        int nb_sectors = end_sector - sector_num;
-+                        if (restore_write_data(vmar, dev_id, target, vmstate_fd,
-+                                               buf + start, sector_num,
-+                                               nb_sectors, errp) < 0) {
-+                            return -1;
-+                        }
-+                    }
-+
-+                    start += VMA_BLOCK_SIZE;
-+
-+                } else {
-+
-+
-+                    if (end_sector > sector_num) {
-+                        /* Todo: use bdrv_co_write_zeroes (but that need to
-+                         * be run inside coroutine?)
-+                         */
-+                        int nb_sectors = end_sector - sector_num;
-+                        int zero_size = BDRV_SECTOR_SIZE*nb_sectors;
-+                        vmar->zero_cluster_data += zero_size;
-+                        if (mask != 0) {
-+                            vmar->partial_zero_cluster_data += zero_size;
-+                        }
-+
-+                        if (rstate->write_zeroes && !verify) {
-+                            if (restore_write_data(vmar, dev_id, target, vmstate_fd,
-+                                                   zero_vma_block, sector_num,
-+                                                   nb_sectors, errp) < 0) {
-+                                return -1;
-+                            }
-+                        }
-+                    }
-+                }
-+
-+                bit = bit << 1;
-+            }
-+        }
-+    }
-+
-+    if (start != extent_size) {
-+        error_setg(errp, "vma extent error - missing blocks");
-+        return -1;
-+    }
-+
-+    return 0;
-+}
-+
-+static int vma_reader_restore_full(VmaReader *vmar, int vmstate_fd,
-+                                   bool verbose, bool verify,
-+                                   Error **errp)
-+{
-+    assert(vmar);
-+    assert(vmar->head_data);
-+
-+    int ret = 0;
-+    unsigned char buf[VMA_MAX_EXTENT_SIZE];
-+    int buf_pos = 0;
-+    unsigned char md5sum[16];
-+    VmaHeader *h = (VmaHeader *)vmar->head_data;
-+
-+    vmar->start_time = time(NULL);
-+
-+    while (1) {
-+        int bytes = full_read(vmar->fd, buf + buf_pos, sizeof(buf) - buf_pos);
-+        if (bytes < 0) {
-+            error_setg(errp, "read failed - %s", g_strerror(errno));
-+            return -1;
-+        }
-+
-+        buf_pos += bytes;
-+
-+        if (!buf_pos) {
-+            break; /* EOF */
-+        }
-+
-+        if (buf_pos < VMA_EXTENT_HEADER_SIZE) {
-+            error_setg(errp, "read short extent (%d bytes)", buf_pos);
-+            return -1;
-+        }
-+
-+        VmaExtentHeader *ehead = (VmaExtentHeader *)buf;
-+
-+        /* extract md5sum */
-+        memcpy(md5sum, ehead->md5sum, sizeof(ehead->md5sum));
-+        memset(ehead->md5sum, 0, sizeof(ehead->md5sum));
-+
-+        g_checksum_reset(vmar->md5csum);
-+        g_checksum_update(vmar->md5csum, buf, VMA_EXTENT_HEADER_SIZE);
-+        gsize csize = 16;
-+        g_checksum_get_digest(vmar->md5csum, ehead->md5sum, &csize);
-+
-+        if (memcmp(md5sum, ehead->md5sum, 16) != 0) {
-+            error_setg(errp, "wrong vma extent header chechsum");
-+            return -1;
-+        }
-+
-+        if (memcmp(h->uuid, ehead->uuid, sizeof(ehead->uuid)) != 0) {
-+            error_setg(errp, "wrong vma extent uuid");
-+            return -1;
-+        }
-+
-+        if (ehead->magic != VMA_EXTENT_MAGIC || ehead->reserved1 != 0) {
-+            error_setg(errp, "wrong vma extent header magic");
-+            return -1;
-+        }
-+
-+        int block_count = GUINT16_FROM_BE(ehead->block_count);
-+        int extent_size = VMA_EXTENT_HEADER_SIZE + block_count*VMA_BLOCK_SIZE;
-+
-+        if (buf_pos < extent_size) {
-+            error_setg(errp, "short vma extent (%d < %d)", buf_pos,
-+                       extent_size);
-+            return -1;
-+        }
-+
-+        if (restore_extent(vmar, buf, extent_size, vmstate_fd, verbose,
-+                           verify, errp) < 0) {
-+            return -1;
-+        }
-+
-+        if (buf_pos > extent_size) {
-+            memmove(buf, buf + extent_size, buf_pos - extent_size);
-+            buf_pos = buf_pos - extent_size;
-+        } else {
-+            buf_pos = 0;
-+        }
-+    }
-+
-+    bdrv_drain_all();
-+
-+    int i;
-+    for (i = 1; i < 256; i++) {
-+        VmaRestoreState *rstate = &vmar->rstate[i];
-+        if (!rstate->target) {
-+            continue;
-+        }
-+
-+        if (blk_flush(rstate->target) < 0) {
-+            error_setg(errp, "vma blk_flush %s failed",
-+                       vmar->devinfo[i].devname);
-+            return -1;
-+        }
-+
-+        if (vmar->devinfo[i].size &&
-+            (strcmp(vmar->devinfo[i].devname, "vmstate") != 0)) {
-+            assert(rstate->bitmap);
-+
-+            int64_t cluster_num, end;
-+
-+            end = (vmar->devinfo[i].size + VMA_CLUSTER_SIZE - 1) /
-+                VMA_CLUSTER_SIZE;
-+
-+            for (cluster_num = 0; cluster_num < end; cluster_num++) {
-+                if (!vma_reader_get_bitmap(rstate, cluster_num)) {
-+                    error_setg(errp, "detected missing cluster %zd "
-+                               "for stream %s", cluster_num,
-+                               vmar->devinfo[i].devname);
-+                    return -1;
-+                }
-+            }
-+        }
-+    }
-+
-+    if (verbose) {
-+        if (vmar->clusters_read) {
-+            printf("total bytes read %zd, sparse bytes %zd (%.3g%%)\n",
-+                   vmar->clusters_read*VMA_CLUSTER_SIZE,
-+                   vmar->zero_cluster_data,
-+                   (double)(100.0*vmar->zero_cluster_data)/
-+                   (vmar->clusters_read*VMA_CLUSTER_SIZE));
-+
-+            int64_t datasize = vmar->clusters_read*VMA_CLUSTER_SIZE-vmar->zero_cluster_data;
-+            if (datasize) { // this does not make sense for empty files
-+                printf("space reduction due to 4K zero blocks %.3g%%\n",
-+                       (double)(100.0*vmar->partial_zero_cluster_data) / datasize);
-+            }
-+        } else {
-+            printf("vma archive contains no image data\n");
-+        }
-+    }
-+    return ret;
-+}
-+
-+int vma_reader_restore(VmaReader *vmar, int vmstate_fd, bool verbose,
-+                       Error **errp)
-+{
-+    return vma_reader_restore_full(vmar, vmstate_fd, verbose, false, errp);
-+}
-+
-+int vma_reader_verify(VmaReader *vmar, bool verbose, Error **errp)
-+{
-+    guint8 dev_id;
-+
-+    for (dev_id = 1; dev_id < 255; dev_id++) {
-+        if (vma_reader_get_device_info(vmar, dev_id)) {
-+            allocate_rstate(vmar, dev_id, NULL, false);
-+        }
-+    }
-+
-+    return vma_reader_restore_full(vmar, -1, verbose, true, errp);
-+}
-+
-diff --git a/vma-writer.c b/vma-writer.c
-new file mode 100644
-index 0000000000..11d8321ffd
---- /dev/null
-+++ b/vma-writer.c
-@@ -0,0 +1,790 @@
-+/*
-+ * VMA: Virtual Machine Archive
-+ *
-+ * Copyright (C) 2012 Proxmox Server Solutions
-+ *
-+ * Authors:
-+ *  Dietmar Maurer (dietmar@proxmox.com)
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ *
-+ */
-+
-+#include "qemu/osdep.h"
-+#include <glib.h>
-+#include <uuid/uuid.h>
-+
-+#include "vma.h"
-+#include "block/block.h"
-+#include "monitor/monitor.h"
-+#include "qemu/main-loop.h"
-+#include "qemu/coroutine.h"
-+#include "qemu/cutils.h"
-+
-+#define DEBUG_VMA 0
-+
-+#define DPRINTF(fmt, ...)\
-+    do { if (DEBUG_VMA) { printf("vma: " fmt, ## __VA_ARGS__); } } while (0)
-+
-+#define WRITE_BUFFERS 5
-+#define HEADER_CLUSTERS 8
-+#define HEADERBUF_SIZE (VMA_CLUSTER_SIZE*HEADER_CLUSTERS)
-+
-+struct VmaWriter {
-+    int fd;
-+    FILE *cmd;
-+    int status;
-+    char errmsg[8192];
-+    uuid_t uuid;
-+    bool header_written;
-+    bool closed;
-+
-+    /* we always write extents */
-+    unsigned char *outbuf;
-+    int outbuf_pos; /* in bytes */
-+    int outbuf_count; /* in VMA_BLOCKS */
-+    uint64_t outbuf_block_info[VMA_BLOCKS_PER_EXTENT];
-+
-+    unsigned char *headerbuf;
-+
-+    GChecksum *md5csum;
-+    CoMutex flush_lock;
-+    Coroutine *co_writer;
-+
-+    /* drive informations */
-+    VmaStreamInfo stream_info[256];
-+    guint stream_count;
-+
-+    guint8 vmstate_stream;
-+    uint32_t vmstate_clusters;
-+
-+    /* header blob table */
-+    char *header_blob_table;
-+    uint32_t header_blob_table_size;
-+    uint32_t header_blob_table_pos;
-+
-+    /* store for config blobs */
-+    uint32_t config_names[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
-+    uint32_t config_data[VMA_MAX_CONFIGS];  /* offset into blob_buffer table */
-+    uint32_t config_count;
-+};
-+
-+void vma_writer_set_error(VmaWriter *vmaw, const char *fmt, ...)
-+{
-+    va_list ap;
-+
-+    if (vmaw->status < 0) {
-+        return;
-+    }
-+
-+    vmaw->status = -1;
-+
-+    va_start(ap, fmt);
-+    g_vsnprintf(vmaw->errmsg, sizeof(vmaw->errmsg), fmt, ap);
-+    va_end(ap);
-+
-+    DPRINTF("vma_writer_set_error: %s\n", vmaw->errmsg);
-+}
-+
-+static uint32_t allocate_header_blob(VmaWriter *vmaw, const char *data,
-+                                     size_t len)
-+{
-+    if (len > 65535) {
-+        return 0;
-+    }
-+
-+    if (!vmaw->header_blob_table ||
-+        (vmaw->header_blob_table_size <
-+         (vmaw->header_blob_table_pos + len + 2))) {
-+        int newsize = vmaw->header_blob_table_size + ((len + 2 + 511)/512)*512;
-+
-+        vmaw->header_blob_table = g_realloc(vmaw->header_blob_table, newsize);
-+        memset(vmaw->header_blob_table + vmaw->header_blob_table_size,
-+               0, newsize - vmaw->header_blob_table_size);
-+        vmaw->header_blob_table_size = newsize;
-+    }
-+
-+    uint32_t cpos = vmaw->header_blob_table_pos;
-+    vmaw->header_blob_table[cpos] = len & 255;
-+    vmaw->header_blob_table[cpos+1] = (len >> 8) & 255;
-+    memcpy(vmaw->header_blob_table + cpos + 2, data, len);
-+    vmaw->header_blob_table_pos += len + 2;
-+    return cpos;
-+}
-+
-+static uint32_t allocate_header_string(VmaWriter *vmaw, const char *str)
-+{
-+    assert(vmaw);
-+
-+    size_t len = strlen(str) + 1;
-+
-+    return allocate_header_blob(vmaw, str, len);
-+}
-+
-+int vma_writer_add_config(VmaWriter *vmaw, const char *name, gpointer data,
-+                          gsize len)
-+{
-+    assert(vmaw);
-+    assert(!vmaw->header_written);
-+    assert(vmaw->config_count < VMA_MAX_CONFIGS);
-+    assert(name);
-+    assert(data);
-+
-+    gchar *basename = g_path_get_basename(name);
-+    uint32_t name_ptr = allocate_header_string(vmaw, basename);
-+    g_free(basename);
-+
-+    if (!name_ptr) {
-+        return -1;
-+    }
-+
-+    uint32_t data_ptr = allocate_header_blob(vmaw, data, len);
-+    if (!data_ptr) {
-+        return -1;
-+    }
-+
-+    vmaw->config_names[vmaw->config_count] = name_ptr;
-+    vmaw->config_data[vmaw->config_count] = data_ptr;
-+
-+    vmaw->config_count++;
-+
-+    return 0;
-+}
-+
-+int vma_writer_register_stream(VmaWriter *vmaw, const char *devname,
-+                               size_t size)
-+{
-+    assert(vmaw);
-+    assert(devname);
-+    assert(!vmaw->status);
-+
-+    if (vmaw->header_written) {
-+        vma_writer_set_error(vmaw, "vma_writer_register_stream: header "
-+                             "already written");
-+        return -1;
-+    }
-+
-+    guint n = vmaw->stream_count + 1;
-+
-+    /* we can have dev_ids form 1 to 255 (0 reserved)
-+     * 255(-1) reseverd for safety
-+     */
-+    if (n > 254) {
-+        vma_writer_set_error(vmaw, "vma_writer_register_stream: "
-+                             "too many drives");
-+        return -1;
-+    }
-+
-+    if (size <= 0) {
-+        vma_writer_set_error(vmaw, "vma_writer_register_stream: "
-+                             "got strange size %zd", size);
-+        return -1;
-+    }
-+
-+    DPRINTF("vma_writer_register_stream %s %zu %d\n", devname, size, n);
-+
-+    vmaw->stream_info[n].devname = g_strdup(devname);
-+    vmaw->stream_info[n].size = size;
-+
-+    vmaw->stream_info[n].cluster_count = (size + VMA_CLUSTER_SIZE - 1) /
-+        VMA_CLUSTER_SIZE;
-+
-+    vmaw->stream_count = n;
-+
-+    if (strcmp(devname, "vmstate") == 0) {
-+        vmaw->vmstate_stream = n;
-+    }
-+
-+    return n;
-+}
-+
-+static void coroutine_fn yield_until_fd_writable(int fd)
-+{
-+    assert(qemu_in_coroutine());
-+    AioContext *ctx = qemu_get_current_aio_context();
-+    aio_set_fd_handler(ctx, fd, false, NULL, (IOHandler *)qemu_coroutine_enter,
-+                       NULL, qemu_coroutine_self());
-+    qemu_coroutine_yield();
-+    aio_set_fd_handler(ctx, fd, false, NULL, NULL, NULL, NULL);
-+}
-+
-+static ssize_t coroutine_fn
-+vma_queue_write(VmaWriter *vmaw, const void *buf, size_t bytes)
-+{
-+    DPRINTF("vma_queue_write enter %zd\n", bytes);
-+
-+    assert(vmaw);
-+    assert(buf);
-+    assert(bytes <= VMA_MAX_EXTENT_SIZE);
-+
-+    size_t done = 0;
-+    ssize_t ret;
-+
-+    assert(vmaw->co_writer == NULL);
-+
-+    vmaw->co_writer = qemu_coroutine_self();
-+
-+    while (done < bytes) {
-+        if (vmaw->status < 0) {
-+            DPRINTF("vma_queue_write detected canceled backup\n");
-+            done = -1;
-+            break;
-+        }
-+        yield_until_fd_writable(vmaw->fd);
-+        ret = write(vmaw->fd, buf + done, bytes - done);
-+        if (ret > 0) {
-+            done += ret;
-+            DPRINTF("vma_queue_write written %zd %zd\n", done, ret);
-+        } else if (ret < 0) {
-+            if (errno == EAGAIN || errno == EWOULDBLOCK) {
-+                /* try again */
-+           } else {
-+                vma_writer_set_error(vmaw, "vma_queue_write: write error - %s",
-+                                     g_strerror(errno));
-+                done = -1; /* always return failure for partial writes */
-+                break;
-+            }
-+        } else if (ret == 0) {
-+            /* should not happen - simply try again */
-+        }
-+    }
-+
-+    vmaw->co_writer = NULL;
-+
-+    return (done == bytes) ? bytes : -1;
-+}
-+
-+VmaWriter *vma_writer_create(const char *filename, uuid_t uuid, Error **errp)
-+{
-+    const char *p;
-+
-+    assert(sizeof(VmaHeader) == (4096 + 8192));
-+    assert(G_STRUCT_OFFSET(VmaHeader, config_names) == 2044);
-+    assert(G_STRUCT_OFFSET(VmaHeader, config_data) == 3068);
-+    assert(G_STRUCT_OFFSET(VmaHeader, dev_info) == 4096);
-+    assert(sizeof(VmaExtentHeader) == 512);
-+
-+    VmaWriter *vmaw = g_new0(VmaWriter, 1);
-+    vmaw->fd = -1;
-+
-+    vmaw->md5csum = g_checksum_new(G_CHECKSUM_MD5);
-+    if (!vmaw->md5csum) {
-+        error_setg(errp, "can't allocate cmsum\n");
-+        goto err;
-+    }
-+
-+    if (strstart(filename, "exec:", &p)) {
-+        vmaw->cmd = popen(p, "w");
-+        if (vmaw->cmd == NULL) {
-+            error_setg(errp, "can't popen command '%s' - %s\n", p,
-+                       g_strerror(errno));
-+            goto err;
-+        }
-+        vmaw->fd = fileno(vmaw->cmd);
-+
-+        /* try to use O_NONBLOCK */
-+        fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_NONBLOCK);
-+
-+    } else {
-+        struct stat st;
-+        int oflags;
-+        const char *tmp_id_str;
-+
-+        if ((stat(filename, &st) == 0) && S_ISFIFO(st.st_mode)) {
-+            oflags = O_NONBLOCK|O_WRONLY;
-+            vmaw->fd = qemu_open(filename, oflags, errp);
-+        } else if (strstart(filename, "/dev/fdset/", &tmp_id_str)) {
-+            oflags = O_NONBLOCK|O_WRONLY;
-+            vmaw->fd = qemu_open(filename, oflags, errp);
-+        } else if (strstart(filename, "/dev/fdname/", &tmp_id_str)) {
-+            vmaw->fd = monitor_get_fd(monitor_cur(), tmp_id_str, errp);
-+            if (vmaw->fd < 0) {
-+                goto err;
-+            }
-+            /* try to use O_NONBLOCK */
-+            fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_NONBLOCK);
-+        } else  {
-+            oflags = O_NONBLOCK|O_DIRECT|O_WRONLY|O_EXCL;
-+            vmaw->fd = qemu_create(filename, oflags, 0644, errp);
-+        }
-+
-+        if (vmaw->fd < 0) {
-+            error_setg(errp, "can't open file %s - %s\n", filename,
-+                       g_strerror(errno));
-+            goto err;
-+        }
-+    }
-+
-+    /* we use O_DIRECT, so we need to align IO buffers */
-+
-+    vmaw->outbuf = qemu_memalign(512, VMA_MAX_EXTENT_SIZE);
-+    vmaw->headerbuf = qemu_memalign(512, HEADERBUF_SIZE);
-+
-+    vmaw->outbuf_count = 0;
-+    vmaw->outbuf_pos = VMA_EXTENT_HEADER_SIZE;
-+
-+    vmaw->header_blob_table_pos = 1; /* start at pos 1 */
-+
-+    qemu_co_mutex_init(&vmaw->flush_lock);
-+
-+    uuid_copy(vmaw->uuid, uuid);
-+
-+    return vmaw;
-+
-+err:
-+    if (vmaw) {
-+        if (vmaw->cmd) {
-+            pclose(vmaw->cmd);
-+        } else if (vmaw->fd >= 0) {
-+            close(vmaw->fd);
-+        }
-+
-+        if (vmaw->md5csum) {
-+            g_checksum_free(vmaw->md5csum);
-+        }
-+
-+        g_free(vmaw);
-+    }
-+
-+    return NULL;
-+}
-+
-+static int coroutine_fn vma_write_header(VmaWriter *vmaw)
-+{
-+    assert(vmaw);
-+    unsigned char *buf = vmaw->headerbuf;
-+    VmaHeader *head = (VmaHeader *)buf;
-+
-+    int i;
-+
-+    DPRINTF("VMA WRITE HEADER\n");
-+
-+    if (vmaw->status < 0) {
-+        return vmaw->status;
-+    }
-+
-+    memset(buf, 0, HEADERBUF_SIZE);
-+
-+    head->magic = VMA_MAGIC;
-+    head->version = GUINT32_TO_BE(1); /* v1 */
-+    memcpy(head->uuid, vmaw->uuid, 16);
-+
-+    time_t ctime = time(NULL);
-+    head->ctime = GUINT64_TO_BE(ctime);
-+
-+    for (i = 0; i < VMA_MAX_CONFIGS; i++) {
-+        head->config_names[i] = GUINT32_TO_BE(vmaw->config_names[i]);
-+        head->config_data[i] = GUINT32_TO_BE(vmaw->config_data[i]);
-+    }
-+
-+    /* 32 bytes per device (12 used currently) = 8192 bytes max */
-+    for (i = 1; i <= 254; i++) {
-+        VmaStreamInfo *si = &vmaw->stream_info[i];
-+        if (si->size) {
-+            assert(si->devname);
-+            uint32_t devname_ptr = allocate_header_string(vmaw, si->devname);
-+            if (!devname_ptr) {
-+                return -1;
-+            }
-+            head->dev_info[i].devname_ptr = GUINT32_TO_BE(devname_ptr);
-+            head->dev_info[i].size = GUINT64_TO_BE(si->size);
-+        }
-+    }
-+
-+    uint32_t header_size = sizeof(VmaHeader) + vmaw->header_blob_table_size;
-+    head->header_size = GUINT32_TO_BE(header_size);
-+
-+    if (header_size > HEADERBUF_SIZE) {
-+        return -1; /* just to be sure */
-+    }
-+
-+    uint32_t blob_buffer_offset = sizeof(VmaHeader);
-+    memcpy(buf + blob_buffer_offset, vmaw->header_blob_table,
-+           vmaw->header_blob_table_size);
-+    head->blob_buffer_offset = GUINT32_TO_BE(blob_buffer_offset);
-+    head->blob_buffer_size = GUINT32_TO_BE(vmaw->header_blob_table_pos);
-+
-+    g_checksum_reset(vmaw->md5csum);
-+    g_checksum_update(vmaw->md5csum, (const guchar *)buf, header_size);
-+    gsize csize = 16;
-+    g_checksum_get_digest(vmaw->md5csum, (guint8 *)(head->md5sum), &csize);
-+
-+    return vma_queue_write(vmaw, buf, header_size);
-+}
-+
-+static int coroutine_fn vma_writer_flush(VmaWriter *vmaw)
-+{
-+    assert(vmaw);
-+
-+    int ret;
-+    int i;
-+
-+    if (vmaw->status < 0) {
-+        return vmaw->status;
-+    }
-+
-+    if (!vmaw->header_written) {
-+        vmaw->header_written = true;
-+        ret = vma_write_header(vmaw);
-+        if (ret < 0) {
-+            vma_writer_set_error(vmaw, "vma_writer_flush: write header failed");
-+            return ret;
-+        }
-+    }
-+
-+    DPRINTF("VMA WRITE FLUSH %d %d\n", vmaw->outbuf_count, vmaw->outbuf_pos);
-+
-+
-+    VmaExtentHeader *ehead = (VmaExtentHeader *)vmaw->outbuf;
-+
-+    ehead->magic = VMA_EXTENT_MAGIC;
-+    ehead->reserved1 = 0;
-+
-+    for (i = 0; i < VMA_BLOCKS_PER_EXTENT; i++) {
-+        ehead->blockinfo[i] = GUINT64_TO_BE(vmaw->outbuf_block_info[i]);
-+    }
-+
-+    guint16 block_count = (vmaw->outbuf_pos - VMA_EXTENT_HEADER_SIZE) /
-+        VMA_BLOCK_SIZE;
-+
-+    ehead->block_count = GUINT16_TO_BE(block_count);
-+
-+    memcpy(ehead->uuid, vmaw->uuid, sizeof(ehead->uuid));
-+    memset(ehead->md5sum, 0, sizeof(ehead->md5sum));
-+
-+    g_checksum_reset(vmaw->md5csum);
-+    g_checksum_update(vmaw->md5csum, vmaw->outbuf, VMA_EXTENT_HEADER_SIZE);
-+    gsize csize = 16;
-+    g_checksum_get_digest(vmaw->md5csum, ehead->md5sum, &csize);
-+
-+    int bytes = vmaw->outbuf_pos;
-+    ret = vma_queue_write(vmaw, vmaw->outbuf, bytes);
-+    if (ret != bytes) {
-+        vma_writer_set_error(vmaw, "vma_writer_flush: failed write");
-+    }
-+
-+    vmaw->outbuf_count = 0;
-+    vmaw->outbuf_pos = VMA_EXTENT_HEADER_SIZE;
-+
-+    for (i = 0; i < VMA_BLOCKS_PER_EXTENT; i++) {
-+        vmaw->outbuf_block_info[i] = 0;
-+    }
-+
-+    return vmaw->status;
-+}
-+
-+static int vma_count_open_streams(VmaWriter *vmaw)
-+{
-+    g_assert(vmaw != NULL);
-+
-+    int i;
-+    int open_drives = 0;
-+    for (i = 0; i <= 255; i++) {
-+        if (vmaw->stream_info[i].size && !vmaw->stream_info[i].finished) {
-+            open_drives++;
-+        }
-+    }
-+
-+    return open_drives;
-+}
-+
-+
-+/**
-+ * You need to call this if the vma archive does not contain
-+ * any data stream.
-+ */
-+int coroutine_fn
-+vma_writer_flush_output(VmaWriter *vmaw)
-+{
-+    qemu_co_mutex_lock(&vmaw->flush_lock);
-+    int ret = vma_writer_flush(vmaw);
-+    qemu_co_mutex_unlock(&vmaw->flush_lock);
-+    if (ret < 0) {
-+        vma_writer_set_error(vmaw, "vma_writer_flush_header failed");
-+    }
-+    return ret;
-+}
-+
-+/**
-+ * all jobs should call this when there is no more data
-+ * Returns: number of remaining stream (0 ==> finished)
-+ */
-+int coroutine_fn
-+vma_writer_close_stream(VmaWriter *vmaw, uint8_t dev_id)
-+{
-+    g_assert(vmaw != NULL);
-+
-+    DPRINTF("vma_writer_set_status %d\n", dev_id);
-+    if (!vmaw->stream_info[dev_id].size) {
-+        vma_writer_set_error(vmaw, "vma_writer_close_stream: "
-+                             "no such stream %d", dev_id);
-+        return -1;
-+    }
-+    if (vmaw->stream_info[dev_id].finished) {
-+        vma_writer_set_error(vmaw, "vma_writer_close_stream: "
-+                             "stream already closed %d", dev_id);
-+        return -1;
-+    }
-+
-+    vmaw->stream_info[dev_id].finished = true;
-+
-+    int open_drives = vma_count_open_streams(vmaw);
-+
-+    if (open_drives <= 0) {
-+        DPRINTF("vma_writer_set_status all drives completed\n");
-+        vma_writer_flush_output(vmaw);
-+    }
-+
-+    return open_drives;
-+}
-+
-+int vma_writer_get_status(VmaWriter *vmaw, VmaStatus *status)
-+{
-+    int i;
-+
-+    g_assert(vmaw != NULL);
-+
-+    if (status) {
-+        status->status = vmaw->status;
-+        g_strlcpy(status->errmsg, vmaw->errmsg, sizeof(status->errmsg));
-+        for (i = 0; i <= 255; i++) {
-+            status->stream_info[i] = vmaw->stream_info[i];
-+        }
-+
-+        uuid_unparse_lower(vmaw->uuid, status->uuid_str);
-+    }
-+
-+    status->closed = vmaw->closed;
-+
-+    return vmaw->status;
-+}
-+
-+static int vma_writer_get_buffer(VmaWriter *vmaw)
-+{
-+    int ret = 0;
-+
-+    qemu_co_mutex_lock(&vmaw->flush_lock);
-+
-+    /* wait until buffer is available */
-+    while (vmaw->outbuf_count >= (VMA_BLOCKS_PER_EXTENT - 1)) {
-+        ret = vma_writer_flush(vmaw);
-+        if (ret < 0) {
-+            vma_writer_set_error(vmaw, "vma_writer_get_buffer: flush failed");
-+            break;
-+        }
-+    }
-+
-+    qemu_co_mutex_unlock(&vmaw->flush_lock);
-+
-+    return ret;
-+}
-+
-+
-+int64_t coroutine_fn
-+vma_writer_write(VmaWriter *vmaw, uint8_t dev_id, int64_t cluster_num,
-+                 const unsigned char *buf, size_t *zero_bytes)
-+{
-+    g_assert(vmaw != NULL);
-+    g_assert(zero_bytes != NULL);
-+
-+    *zero_bytes = 0;
-+
-+    if (vmaw->status < 0) {
-+        return vmaw->status;
-+    }
-+
-+    if (!dev_id || !vmaw->stream_info[dev_id].size) {
-+        vma_writer_set_error(vmaw, "vma_writer_write: "
-+                             "no such stream %d", dev_id);
-+        return -1;
-+    }
-+
-+    if (vmaw->stream_info[dev_id].finished) {
-+        vma_writer_set_error(vmaw, "vma_writer_write: "
-+                             "stream already closed %d", dev_id);
-+        return -1;
-+    }
-+
-+
-+    if (cluster_num >= (((uint64_t)1)<<32)) {
-+        vma_writer_set_error(vmaw, "vma_writer_write: "
-+                             "cluster number out of range");
-+        return -1;
-+    }
-+
-+    if (dev_id == vmaw->vmstate_stream) {
-+        if (cluster_num != vmaw->vmstate_clusters) {
-+            vma_writer_set_error(vmaw, "vma_writer_write: "
-+                                 "non sequential vmstate write");
-+        }
-+        vmaw->vmstate_clusters++;
-+    } else if (cluster_num >= vmaw->stream_info[dev_id].cluster_count) {
-+        vma_writer_set_error(vmaw, "vma_writer_write: cluster number too big");
-+        return -1;
-+    }
-+
-+    /* wait until buffer is available */
-+    if (vma_writer_get_buffer(vmaw) < 0) {
-+        vma_writer_set_error(vmaw, "vma_writer_write: "
-+                             "vma_writer_get_buffer failed");
-+        return -1;
-+    }
-+
-+    DPRINTF("VMA WRITE %d %zd\n", dev_id, cluster_num);
-+
-+    uint64_t dev_size = vmaw->stream_info[dev_id].size;
-+    uint16_t mask = 0;
-+
-+    if (buf) {
-+        int i;
-+        int bit = 1;
-+        uint64_t byte_offset = cluster_num * VMA_CLUSTER_SIZE;
-+        for (i = 0; i < 16; i++) {
-+            const unsigned char *vmablock = buf + (i*VMA_BLOCK_SIZE);
-+
-+            // Note: If the source is not 64k-aligned, we might reach 4k blocks
-+            // after the end of the device. Always mark these as zero in the
-+            // mask, so the restore handles them correctly.
-+            if (byte_offset < dev_size &&
-+                !buffer_is_zero(vmablock, VMA_BLOCK_SIZE))
-+            {
-+                mask |= bit;
-+                memcpy(vmaw->outbuf + vmaw->outbuf_pos, vmablock,
-+                       VMA_BLOCK_SIZE);
-+
-+                // prevent memory leakage on unaligned last block
-+                if (byte_offset + VMA_BLOCK_SIZE > dev_size) {
-+                    uint64_t real_data_in_block = dev_size - byte_offset;
-+                    memset(vmaw->outbuf + vmaw->outbuf_pos + real_data_in_block,
-+                           0, VMA_BLOCK_SIZE - real_data_in_block);
-+                }
-+
-+                vmaw->outbuf_pos += VMA_BLOCK_SIZE;
-+            } else {
-+                DPRINTF("VMA WRITE %zd ZERO BLOCK %d\n", cluster_num, i);
-+                vmaw->stream_info[dev_id].zero_bytes += VMA_BLOCK_SIZE;
-+                *zero_bytes += VMA_BLOCK_SIZE;
-+            }
-+
-+            byte_offset += VMA_BLOCK_SIZE;
-+            bit = bit << 1;
-+        }
-+    } else {
-+        DPRINTF("VMA WRITE %zd ZERO CLUSTER\n", cluster_num);
-+        vmaw->stream_info[dev_id].zero_bytes += VMA_CLUSTER_SIZE;
-+        *zero_bytes += VMA_CLUSTER_SIZE;
-+    }
-+
-+    uint64_t block_info = ((uint64_t)mask) << (32+16);
-+    block_info |= ((uint64_t)dev_id) << 32;
-+    block_info |= (cluster_num & 0xffffffff);
-+    vmaw->outbuf_block_info[vmaw->outbuf_count] = block_info;
-+
-+    DPRINTF("VMA WRITE MASK %zd %zx\n", cluster_num, block_info);
-+
-+    vmaw->outbuf_count++;
-+
-+    /** NOTE: We allways write whole clusters, but we correctly set
-+     * transferred bytes. So transferred == size when when everything
-+     * went OK.
-+     */
-+    size_t transferred = VMA_CLUSTER_SIZE;
-+
-+    if (dev_id != vmaw->vmstate_stream) {
-+        uint64_t last = (cluster_num + 1) * VMA_CLUSTER_SIZE;
-+        if (last > dev_size) {
-+            uint64_t diff = last - dev_size;
-+            if (diff >= VMA_CLUSTER_SIZE) {
-+                vma_writer_set_error(vmaw, "vma_writer_write: "
-+                                     "read after last cluster");
-+                return -1;
-+            }
-+            transferred -= diff;
-+        }
-+    }
-+
-+    vmaw->stream_info[dev_id].transferred += transferred;
-+
-+    return transferred;
-+}
-+
-+void vma_writer_error_propagate(VmaWriter *vmaw, Error **errp)
-+{
-+    if (vmaw->status < 0 && *errp == NULL) {
-+        error_setg(errp, "%s", vmaw->errmsg);
-+    }
-+}
-+
-+int vma_writer_close(VmaWriter *vmaw, Error **errp)
-+{
-+    g_assert(vmaw != NULL);
-+
-+    int i;
-+
-+    qemu_co_mutex_lock(&vmaw->flush_lock); // wait for pending writes
-+
-+    assert(vmaw->co_writer == NULL);
-+
-+    if (vmaw->cmd) {
-+        if (pclose(vmaw->cmd) < 0) {
-+            vma_writer_set_error(vmaw, "vma_writer_close: "
-+                                 "pclose failed - %s", g_strerror(errno));
-+        }
-+    } else {
-+        if (close(vmaw->fd) < 0) {
-+            vma_writer_set_error(vmaw, "vma_writer_close: "
-+                                 "close failed - %s", g_strerror(errno));
-+        }
-+    }
-+
-+    for (i = 0; i <= 255; i++) {
-+        VmaStreamInfo *si = &vmaw->stream_info[i];
-+        if (si->size) {
-+            if (!si->finished) {
-+                vma_writer_set_error(vmaw, "vma_writer_close: "
-+                                     "detected open stream '%s'", si->devname);
-+            } else if ((si->transferred != si->size) &&
-+                       (i != vmaw->vmstate_stream)) {
-+                vma_writer_set_error(vmaw, "vma_writer_close: "
-+                                     "incomplete stream '%s' (%zd != %zd)",
-+                                     si->devname, si->transferred, si->size);
-+            }
-+        }
-+    }
-+
-+    for (i = 0; i <= 255; i++) {
-+        vmaw->stream_info[i].finished = 1; /* mark as closed */
-+    }
-+
-+    vmaw->closed = 1;
-+
-+    if (vmaw->status < 0 && *errp == NULL) {
-+        error_setg(errp, "%s", vmaw->errmsg);
-+    }
-+
-+    qemu_co_mutex_unlock(&vmaw->flush_lock);
-+
-+    return vmaw->status;
-+}
-+
-+void vma_writer_destroy(VmaWriter *vmaw)
-+{
-+    assert(vmaw);
-+
-+    int i;
-+
-+    for (i = 0; i <= 255; i++) {
-+        if (vmaw->stream_info[i].devname) {
-+            g_free(vmaw->stream_info[i].devname);
-+        }
-+    }
-+
-+    if (vmaw->md5csum) {
-+        g_checksum_free(vmaw->md5csum);
-+    }
-+
-+    qemu_vfree(vmaw->headerbuf);
-+    qemu_vfree(vmaw->outbuf);
-+    g_free(vmaw);
-+}
-diff --git a/vma.c b/vma.c
-new file mode 100644
-index 0000000000..df542b7732
---- /dev/null
-+++ b/vma.c
-@@ -0,0 +1,851 @@
-+/*
-+ * VMA: Virtual Machine Archive
-+ *
-+ * Copyright (C) 2012-2013 Proxmox Server Solutions
-+ *
-+ * Authors:
-+ *  Dietmar Maurer (dietmar@proxmox.com)
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ *
-+ */
-+
-+#include "qemu/osdep.h"
-+#include <glib.h>
-+
-+#include "vma.h"
-+#include "qemu-common.h"
-+#include "qemu/module.h"
-+#include "qemu/error-report.h"
-+#include "qemu/main-loop.h"
-+#include "qemu/cutils.h"
-+#include "qapi/qmp/qdict.h"
-+#include "sysemu/block-backend.h"
-+
-+static void help(void)
-+{
-+    const char *help_msg =
-+        "usage: vma command [command options]\n"
-+        "\n"
-+        "vma list <filename>\n"
-+        "vma config <filename> [-c config]\n"
-+        "vma create <filename> [-c config] pathname ...\n"
-+        "vma extract <filename> [-r <fifo>] <targetdir>\n"
-+        "vma verify <filename> [-v]\n"
-+        ;
-+
-+    printf("%s", help_msg);
-+    exit(1);
-+}
-+
-+static const char *extract_devname(const char *path, char **devname, int index)
-+{
-+    assert(path);
-+
-+    const char *sep = strchr(path, '=');
-+
-+    if (sep) {
-+        *devname = g_strndup(path, sep - path);
-+        path = sep + 1;
-+    } else {
-+        if (index >= 0) {
-+            *devname = g_strdup_printf("disk%d", index);
-+        } else {
-+            *devname = NULL;
-+        }
-+    }
-+
-+    return path;
-+}
-+
-+static void print_content(VmaReader *vmar)
-+{
-+    assert(vmar);
-+
-+    VmaHeader *head = vma_reader_get_header(vmar);
-+
-+    GList *l = vma_reader_get_config_data(vmar);
-+    while (l && l->data) {
-+        VmaConfigData *cdata = (VmaConfigData *)l->data;
-+        l = g_list_next(l);
-+        printf("CFG: size: %d name: %s\n", cdata->len, cdata->name);
-+    }
-+
-+    int i;
-+    VmaDeviceInfo *di;
-+    for (i = 1; i < 255; i++) {
-+        di = vma_reader_get_device_info(vmar, i);
-+        if (di) {
-+            if (strcmp(di->devname, "vmstate") == 0) {
-+                printf("VMSTATE: dev_id=%d memory: %zd\n", i, di->size);
-+            } else {
-+                printf("DEV: dev_id=%d size: %zd devname: %s\n",
-+                       i, di->size, di->devname);
-+            }
-+        }
-+    }
-+    /* ctime is the last entry we print */
-+    printf("CTIME: %s", ctime(&head->ctime));
-+    fflush(stdout);
-+}
-+
-+static int list_content(int argc, char **argv)
-+{
-+    int c, ret = 0;
-+    const char *filename;
-+
-+    for (;;) {
-+        c = getopt(argc, argv, "h");
-+        if (c == -1) {
-+            break;
-+        }
-+        switch (c) {
-+        case '?':
-+        case 'h':
-+            help();
-+            break;
-+        default:
-+            g_assert_not_reached();
-+        }
-+    }
-+
-+    /* Get the filename */
-+    if ((optind + 1) != argc) {
-+        help();
-+    }
-+    filename = argv[optind++];
-+
-+    Error *errp = NULL;
-+    VmaReader *vmar = vma_reader_create(filename, &errp);
-+
-+    if (!vmar) {
-+        g_error("%s", error_get_pretty(errp));
-+    }
-+
-+    print_content(vmar);
-+
-+    vma_reader_destroy(vmar);
-+
-+    return ret;
-+}
-+
-+typedef struct RestoreMap {
-+    char *devname;
-+    char *path;
-+    char *format;
-+    uint64_t throttling_bps;
-+    char *throttling_group;
-+    char *cache;
-+    bool write_zero;
-+} RestoreMap;
-+
-+static bool try_parse_option(char **line, const char *optname, char **out, const char *inbuf) {
-+    size_t optlen = strlen(optname);
-+    if (strncmp(*line, optname, optlen) != 0 || (*line)[optlen] != '=') {
-+        return false;
-+    }
-+    if (*out) {
-+        g_error("read map failed - duplicate value for option '%s'", optname);
-+    }
-+    char *value = (*line) + optlen + 1; /* including a '=' */
-+    char *colon = strchr(value, ':');
-+    if (!colon) {
-+        g_error("read map failed - option '%s' not terminated ('%s')",
-+                optname, inbuf);
-+    }
-+    *line = colon+1;
-+    *out = g_strndup(value, colon - value);
-+    return true;
-+}
-+
-+static uint64_t verify_u64(const char *text) {
-+    uint64_t value;
-+    const char *endptr = NULL;
-+    if (qemu_strtou64(text, &endptr, 0, &value) != 0 || !endptr || *endptr) {
-+        g_error("read map failed - not a number: %s", text);
-+    }
-+    return value;
-+}
-+
-+static int extract_content(int argc, char **argv)
-+{
-+    int c, ret = 0;
-+    int verbose = 0;
-+    const char *filename;
-+    const char *dirname;
-+    const char *readmap = NULL;
-+
-+    for (;;) {
-+        c = getopt(argc, argv, "hvr:");
-+        if (c == -1) {
-+            break;
-+        }
-+        switch (c) {
-+        case '?':
-+        case 'h':
-+            help();
-+            break;
-+        case 'r':
-+            readmap = optarg;
-+            break;
-+        case 'v':
-+            verbose = 1;
-+            break;
-+        default:
-+            help();
-+        }
-+    }
-+
-+    /* Get the filename */
-+    if ((optind + 2) != argc) {
-+        help();
-+    }
-+    filename = argv[optind++];
-+    dirname = argv[optind++];
-+
-+    Error *errp = NULL;
-+    VmaReader *vmar = vma_reader_create(filename, &errp);
-+
-+    if (!vmar) {
-+        g_error("%s", error_get_pretty(errp));
-+    }
-+
-+    if (mkdir(dirname, 0777) < 0) {
-+        g_error("unable to create target directory %s - %s",
-+                dirname, g_strerror(errno));
-+    }
-+
-+    GList *l = vma_reader_get_config_data(vmar);
-+    while (l && l->data) {
-+        VmaConfigData *cdata = (VmaConfigData *)l->data;
-+        l = g_list_next(l);
-+        char *cfgfn = g_strdup_printf("%s/%s", dirname, cdata->name);
-+        GError *err = NULL;
-+        if (!g_file_set_contents(cfgfn, (gchar *)cdata->data, cdata->len,
-+                                 &err)) {
-+            g_error("unable to write file: %s", err->message);
-+        }
-+    }
-+
-+    GHashTable *devmap = g_hash_table_new(g_str_hash, g_str_equal);
-+
-+    if (readmap) {
-+        print_content(vmar);
-+
-+        FILE *map = fopen(readmap, "r");
-+        if (!map) {
-+            g_error("unable to open fifo %s - %s", readmap, g_strerror(errno));
-+        }
-+
-+        while (1) {
-+            char inbuf[8192];
-+            char *line = fgets(inbuf, sizeof(inbuf), map);
-+            char *format = NULL;
-+            char *bps = NULL;
-+            char *group = NULL;
-+            char *cache = NULL;
-+            if (!line || line[0] == '\0' || !strcmp(line, "done\n")) {
-+                break;
-+            }
-+            int len = strlen(line);
-+            if (line[len - 1] == '\n') {
-+                line[len - 1] = '\0';
-+                if (len == 1) {
-+                    break;
-+                }
-+            }
-+
-+            while (1) {
-+                if (!try_parse_option(&line, "format", &format, inbuf) &&
-+                    !try_parse_option(&line, "throttling.bps", &bps, inbuf) &&
-+                    !try_parse_option(&line, "throttling.group", &group, inbuf) &&
-+                    !try_parse_option(&line, "cache", &cache, inbuf))
-+                {
-+                    break;
-+                }
-+            }
-+
-+            uint64_t bps_value = 0;
-+            if (bps) {
-+                bps_value = verify_u64(bps);
-+                g_free(bps);
-+            }
-+
-+            const char *path;
-+            bool write_zero;
-+            if (line[0] == '0' && line[1] == ':') {
-+                path = line + 2;
-+                write_zero = false;
-+            } else if (line[0] == '1' && line[1] == ':') {
-+                path = line + 2;
-+                write_zero = true;
-+            } else {
-+                g_error("read map failed - parse error ('%s')", inbuf);
-+            }
-+
-+            char *devname = NULL;
-+            path = extract_devname(path, &devname, -1);
-+            if (!devname) {
-+                g_error("read map failed - no dev name specified ('%s')",
-+                        inbuf);
-+            }
-+
-+            RestoreMap *map = g_new0(RestoreMap, 1);
-+            map->devname = g_strdup(devname);
-+            map->path = g_strdup(path);
-+            map->format = format;
-+            map->throttling_bps = bps_value;
-+            map->throttling_group = group;
-+            map->cache = cache;
-+            map->write_zero = write_zero;
-+
-+            g_hash_table_insert(devmap, map->devname, map);
-+
-+        };
-+    }
-+
-+    int i;
-+    int vmstate_fd = -1;
-+    guint8 vmstate_stream = 0;
-+
-+    BlockBackend *blk = NULL;
-+
-+    for (i = 1; i < 255; i++) {
-+        VmaDeviceInfo *di = vma_reader_get_device_info(vmar, i);
-+        if (di && (strcmp(di->devname, "vmstate") == 0)) {
-+            vmstate_stream = i;
-+            char *statefn = g_strdup_printf("%s/vmstate.bin", dirname);
-+            vmstate_fd = open(statefn, O_WRONLY|O_CREAT|O_EXCL, 0644);
-+            if (vmstate_fd < 0) {
-+                g_error("create vmstate file '%s' failed - %s", statefn,
-+                        g_strerror(errno));
-+            }
-+            g_free(statefn);
-+        } else if (di) {
-+            char *devfn = NULL;
-+            const char *format = NULL;
-+            uint64_t throttling_bps = 0;
-+            const char *throttling_group = NULL;
-+            const char *cache = NULL;
-+            int flags = BDRV_O_RDWR;
-+            bool write_zero = true;
-+
-+            if (readmap) {
-+                RestoreMap *map;
-+                map = (RestoreMap *)g_hash_table_lookup(devmap, di->devname);
-+                if (map == NULL) {
-+                    g_error("no device name mapping for %s", di->devname);
-+                }
-+                devfn = map->path;
-+                format = map->format;
-+                throttling_bps = map->throttling_bps;
-+                throttling_group = map->throttling_group;
-+                cache = map->cache;
-+                write_zero = map->write_zero;
-+            } else {
-+                devfn = g_strdup_printf("%s/tmp-disk-%s.raw",
-+                                        dirname, di->devname);
-+                printf("DEVINFO %s %zd\n", devfn, di->size);
-+
-+                bdrv_img_create(devfn, "raw", NULL, NULL, NULL, di->size,
-+                                flags, true, &errp);
-+                if (errp) {
-+                    g_error("can't create file %s: %s", devfn,
-+                            error_get_pretty(errp));
-+                }
-+
-+                /* Note: we created an empty file above, so there is no
-+                 * need to write zeroes (so we generate a sparse file)
-+                 */
-+                write_zero = false;
-+            }
-+
-+          size_t devlen = strlen(devfn);
-+          QDict *options = NULL;
-+            bool writethrough;
-+            if (format) {
-+                /* explicit format from commandline */
-+                options = qdict_new();
-+                qdict_put_str(options, "driver", format);
-+            } else if ((devlen > 4 && strcmp(devfn+devlen-4, ".raw") == 0) ||
-+                     strncmp(devfn, "/dev/", 5) == 0)
-+          {
-+                /* This part is now deprecated for PVE as well (just as qemu
-+                 * deprecated not specifying an explicit raw format, too.
-+                 */
-+              /* explicit raw format */
-+              options = qdict_new();
-+              qdict_put_str(options, "driver", "raw");
-+          }
-+            if (cache && bdrv_parse_cache_mode(cache, &flags, &writethrough)) {
-+                g_error("invalid cache option: %s\n", cache);
-+            }
-+
-+          if (errp || !(blk = blk_new_open(devfn, NULL, options, flags, &errp))) {
-+                g_error("can't open file %s - %s", devfn,
-+                        error_get_pretty(errp));
-+            }
-+
-+            if (cache) {
-+                blk_set_enable_write_cache(blk, !writethrough);
-+            }
-+
-+            if (throttling_group) {
-+                blk_io_limits_enable(blk, throttling_group);
-+            }
-+
-+            if (throttling_bps) {
-+                if (!throttling_group) {
-+                    blk_io_limits_enable(blk, devfn);
-+                }
-+
-+                ThrottleConfig cfg;
-+                throttle_config_init(&cfg);
-+                cfg.buckets[THROTTLE_BPS_WRITE].avg = throttling_bps;
-+                Error *err = NULL;
-+                if (!throttle_is_valid(&cfg, &err)) {
-+                    error_report_err(err);
-+                    g_error("failed to apply throttling");
-+                }
-+                blk_set_io_limits(blk, &cfg);
-+            }
-+
-+            if (vma_reader_register_bs(vmar, i, blk, write_zero, &errp) < 0) {
-+                g_error("%s", error_get_pretty(errp));
-+            }
-+
-+            if (!readmap) {
-+                g_free(devfn);
-+            }
-+        }
-+    }
-+
-+    if (vma_reader_restore(vmar, vmstate_fd, verbose, &errp) < 0) {
-+        g_error("restore failed - %s", error_get_pretty(errp));
-+    }
-+
-+    if (!readmap) {
-+        for (i = 1; i < 255; i++) {
-+            VmaDeviceInfo *di = vma_reader_get_device_info(vmar, i);
-+            if (di && (i != vmstate_stream)) {
-+                char *tmpfn = g_strdup_printf("%s/tmp-disk-%s.raw",
-+                                              dirname, di->devname);
-+                char *fn = g_strdup_printf("%s/disk-%s.raw",
-+                                           dirname, di->devname);
-+                if (rename(tmpfn, fn) != 0) {
-+                    g_error("rename %s to %s failed - %s",
-+                            tmpfn, fn, g_strerror(errno));
-+                }
-+            }
-+        }
-+    }
-+
-+    vma_reader_destroy(vmar);
-+
-+    blk_unref(blk);
-+
-+    bdrv_close_all();
-+
-+    return ret;
-+}
-+
-+static int verify_content(int argc, char **argv)
-+{
-+    int c, ret = 0;
-+    int verbose = 0;
-+    const char *filename;
-+
-+    for (;;) {
-+        c = getopt(argc, argv, "hv");
-+        if (c == -1) {
-+            break;
-+        }
-+        switch (c) {
-+        case '?':
-+        case 'h':
-+            help();
-+            break;
-+        case 'v':
-+            verbose = 1;
-+            break;
-+        default:
-+            help();
-+        }
-+    }
-+
-+    /* Get the filename */
-+    if ((optind + 1) != argc) {
-+        help();
-+    }
-+    filename = argv[optind++];
-+
-+    Error *errp = NULL;
-+    VmaReader *vmar = vma_reader_create(filename, &errp);
-+
-+    if (!vmar) {
-+        g_error("%s", error_get_pretty(errp));
-+    }
-+
-+    if (verbose) {
-+        print_content(vmar);
-+    }
-+
-+    if (vma_reader_verify(vmar, verbose, &errp) < 0) {
-+        g_error("verify failed - %s", error_get_pretty(errp));
-+    }
-+
-+    vma_reader_destroy(vmar);
-+
-+    bdrv_close_all();
-+
-+    return ret;
-+}
-+
-+typedef struct BackupJob {
-+    BlockBackend *target;
-+    int64_t len;
-+    VmaWriter *vmaw;
-+    uint8_t dev_id;
-+} BackupJob;
-+
-+#define BACKUP_SECTORS_PER_CLUSTER (VMA_CLUSTER_SIZE / BDRV_SECTOR_SIZE)
-+
-+static void coroutine_fn backup_run_empty(void *opaque)
-+{
-+    VmaWriter *vmaw = (VmaWriter *)opaque;
-+
-+    vma_writer_flush_output(vmaw);
-+
-+    Error *err = NULL;
-+    if (vma_writer_close(vmaw, &err) != 0) {
-+        g_warning("vma_writer_close failed %s", error_get_pretty(err));
-+    }
-+}
-+
-+static void coroutine_fn backup_run(void *opaque)
-+{
-+    BackupJob *job = (BackupJob *)opaque;
-+    struct iovec iov;
-+    QEMUIOVector qiov;
-+
-+    int64_t start, end;
-+    int ret = 0;
-+
-+    unsigned char *buf = blk_blockalign(job->target, VMA_CLUSTER_SIZE);
-+
-+    start = 0;
-+    end = DIV_ROUND_UP(job->len / BDRV_SECTOR_SIZE,
-+                       BACKUP_SECTORS_PER_CLUSTER);
-+
-+    for (; start < end; start++) {
-+        iov.iov_base = buf;
-+        iov.iov_len = VMA_CLUSTER_SIZE;
-+        qemu_iovec_init_external(&qiov, &iov, 1);
-+
-+        ret = blk_co_preadv(job->target, start * VMA_CLUSTER_SIZE,
-+                            VMA_CLUSTER_SIZE, &qiov, 0);
-+        if (ret < 0) {
-+            vma_writer_set_error(job->vmaw, "read error", -1);
-+            goto out;
-+        }
-+
-+        size_t zb = 0;
-+        if (vma_writer_write(job->vmaw, job->dev_id, start, buf, &zb) < 0) {
-+            vma_writer_set_error(job->vmaw, "backup_dump_cb vma_writer_write failed", -1);
-+            goto out;
-+        }
-+    }
-+
-+
-+out:
-+    if (vma_writer_close_stream(job->vmaw, job->dev_id) <= 0) {
-+        Error *err = NULL;
-+        if (vma_writer_close(job->vmaw, &err) != 0) {
-+            g_warning("vma_writer_close failed %s", error_get_pretty(err));
-+        }
-+    }
-+    qemu_vfree(buf);
-+}
-+
-+static int create_archive(int argc, char **argv)
-+{
-+    int i, c;
-+    int verbose = 0;
-+    const char *archivename;
-+    GList *backup_coroutines = NULL;
-+    GList *config_files = NULL;
-+
-+    for (;;) {
-+        c = getopt(argc, argv, "hvc:");
-+        if (c == -1) {
-+            break;
-+        }
-+        switch (c) {
-+        case '?':
-+        case 'h':
-+            help();
-+            break;
-+        case 'c':
-+            config_files = g_list_append(config_files, optarg);
-+            break;
-+        case 'v':
-+            verbose = 1;
-+            break;
-+        default:
-+            g_assert_not_reached();
-+        }
-+    }
-+
-+
-+    /* make sure we an archive name */
-+    if ((optind + 1) > argc) {
-+        help();
-+    }
-+
-+    archivename = argv[optind++];
-+
-+    uuid_t uuid;
-+    uuid_generate(uuid);
-+
-+    Error *local_err = NULL;
-+    VmaWriter *vmaw = vma_writer_create(archivename, uuid, &local_err);
-+
-+    if (vmaw == NULL) {
-+        g_error("%s", error_get_pretty(local_err));
-+    }
-+
-+    GList *l = config_files;
-+    while (l && l->data) {
-+        char *name = l->data;
-+        char *cdata = NULL;
-+        gsize clen = 0;
-+        GError *err = NULL;
-+        if (!g_file_get_contents(name, &cdata, &clen, &err)) {
-+            unlink(archivename);
-+            g_error("Unable to read file: %s", err->message);
-+        }
-+
-+        if (vma_writer_add_config(vmaw, name, cdata, clen) != 0) {
-+            unlink(archivename);
-+            g_error("Unable to append config data %s (len = %zd)",
-+                    name, clen);
-+        }
-+        l = g_list_next(l);
-+    }
-+
-+    int devcount = 0;
-+    while (optind < argc) {
-+        const char *path = argv[optind++];
-+        char *devname = NULL;
-+        path = extract_devname(path, &devname, devcount++);
-+
-+        Error *errp = NULL;
-+        BlockBackend *target;
-+
-+        target = blk_new_open(path, NULL, NULL, 0, &errp);
-+        if (!target) {
-+            unlink(archivename);
-+            g_error("bdrv_open '%s' failed - %s", path, error_get_pretty(errp));
-+        }
-+        int64_t size = blk_getlength(target);
-+        int dev_id = vma_writer_register_stream(vmaw, devname, size);
-+        if (dev_id <= 0) {
-+            unlink(archivename);
-+            g_error("vma_writer_register_stream '%s' failed", devname);
-+        }
-+
-+        BackupJob *job = g_new0(BackupJob, 1);
-+        job->len = size;
-+        job->target = target;
-+        job->vmaw = vmaw;
-+        job->dev_id = dev_id;
-+
-+        Coroutine *co = qemu_coroutine_create(backup_run, job);
-+        // Don't enter coroutine yet, because it might write the header before
-+        // all streams can be registered.
-+        backup_coroutines = g_list_append(backup_coroutines, co);
-+    }
-+
-+    VmaStatus vmastat;
-+    int percent = 0;
-+    int last_percent = -1;
-+
-+    if (devcount) {
-+        GList *entry = backup_coroutines;
-+        while (entry && entry->data) {
-+            Coroutine *co = entry->data;
-+            qemu_coroutine_enter(co);
-+            entry = g_list_next(entry);
-+        }
-+
-+        while (1) {
-+            main_loop_wait(false);
-+            vma_writer_get_status(vmaw, &vmastat);
-+
-+            if (verbose) {
-+
-+                uint64_t total = 0;
-+                uint64_t transferred = 0;
-+                uint64_t zero_bytes = 0;
-+
-+                int i;
-+                for (i = 0; i < 256; i++) {
-+                    if (vmastat.stream_info[i].size) {
-+                        total += vmastat.stream_info[i].size;
-+                        transferred += vmastat.stream_info[i].transferred;
-+                        zero_bytes += vmastat.stream_info[i].zero_bytes;
-+                    }
-+                }
-+                percent = (transferred*100)/total;
-+                if (percent != last_percent) {
-+                    fprintf(stderr, "progress %d%% %zd/%zd %zd\n", percent,
-+                            transferred, total, zero_bytes);
-+                    fflush(stderr);
-+
-+                    last_percent = percent;
-+                }
-+            }
-+
-+            if (vmastat.closed) {
-+                break;
-+            }
-+        }
-+    } else {
-+        Coroutine *co = qemu_coroutine_create(backup_run_empty, vmaw);
-+        qemu_coroutine_enter(co);
-+        while (1) {
-+            main_loop_wait(false);
-+            vma_writer_get_status(vmaw, &vmastat);
-+            if (vmastat.closed) {
-+                    break;
-+            }
-+        }
-+    }
-+
-+    bdrv_drain_all();
-+
-+    vma_writer_get_status(vmaw, &vmastat);
-+
-+    if (verbose) {
-+        for (i = 0; i < 256; i++) {
-+            VmaStreamInfo *si = &vmastat.stream_info[i];
-+            if (si->size) {
-+                fprintf(stderr, "image %s: size=%zd zeros=%zd saved=%zd\n",
-+                        si->devname, si->size, si->zero_bytes,
-+                        si->size - si->zero_bytes);
-+            }
-+        }
-+    }
-+
-+    if (vmastat.status < 0) {
-+        unlink(archivename);
-+        g_error("creating vma archive failed");
-+    }
-+
-+    g_list_free(backup_coroutines);
-+    g_list_free(config_files);
-+    vma_writer_destroy(vmaw);
-+    return 0;
-+}
-+
-+static int dump_config(int argc, char **argv)
-+{
-+    int c, ret = 0;
-+    const char *filename;
-+    const char *config_name = "qemu-server.conf";
-+
-+    for (;;) {
-+        c = getopt(argc, argv, "hc:");
-+        if (c == -1) {
-+            break;
-+        }
-+        switch (c) {
-+        case '?':
-+        case 'h':
-+            help();
-+            break;
-+        case 'c':
-+            config_name = optarg;
-+            break;
-+        default:
-+            help();
-+        }
-+    }
-+
-+    /* Get the filename */
-+    if ((optind + 1) != argc) {
-+        help();
-+    }
-+    filename = argv[optind++];
-+
-+    Error *errp = NULL;
-+    VmaReader *vmar = vma_reader_create(filename, &errp);
-+
-+    if (!vmar) {
-+        g_error("%s", error_get_pretty(errp));
-+    }
-+
-+    int found = 0;
-+    GList *l = vma_reader_get_config_data(vmar);
-+    while (l && l->data) {
-+        VmaConfigData *cdata = (VmaConfigData *)l->data;
-+        l = g_list_next(l);
-+        if (strcmp(cdata->name, config_name) == 0) {
-+            found = 1;
-+            fwrite(cdata->data,  cdata->len, 1, stdout);
-+            break;
-+        }
-+    }
-+
-+    vma_reader_destroy(vmar);
-+
-+    bdrv_close_all();
-+
-+    if (!found) {
-+        fprintf(stderr, "unable to find configuration data '%s'\n", config_name);
-+        return -1;
-+    }
-+
-+    return ret;
-+}
-+
-+int main(int argc, char **argv)
-+{
-+    const char *cmdname;
-+    Error *main_loop_err = NULL;
-+
-+    error_init(argv[0]);
-+    module_call_init(MODULE_INIT_TRACE);
-+    qemu_init_exec_dir(argv[0]);
-+
-+    if (qemu_init_main_loop(&main_loop_err)) {
-+        g_error("%s", error_get_pretty(main_loop_err));
-+    }
-+
-+    bdrv_init();
-+    module_call_init(MODULE_INIT_QOM);
-+
-+    if (argc < 2) {
-+        help();
-+    }
-+
-+    cmdname = argv[1];
-+    argc--; argv++;
-+
-+
-+    if (!strcmp(cmdname, "list")) {
-+        return list_content(argc, argv);
-+    } else if (!strcmp(cmdname, "create")) {
-+        return create_archive(argc, argv);
-+    } else if (!strcmp(cmdname, "extract")) {
-+        return extract_content(argc, argv);
-+    } else if (!strcmp(cmdname, "verify")) {
-+        return verify_content(argc, argv);
-+    } else if (!strcmp(cmdname, "config")) {
-+        return dump_config(argc, argv);
-+    }
-+
-+    help();
-+    return 0;
-+}
-diff --git a/vma.h b/vma.h
-new file mode 100644
-index 0000000000..c895c97f6d
---- /dev/null
-+++ b/vma.h
-@@ -0,0 +1,150 @@
-+/*
-+ * VMA: Virtual Machine Archive
-+ *
-+ * Copyright (C) Proxmox Server Solutions
-+ *
-+ * Authors:
-+ *  Dietmar Maurer (dietmar@proxmox.com)
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ *
-+ */
-+
-+#ifndef BACKUP_VMA_H
-+#define BACKUP_VMA_H
-+
-+#include <uuid/uuid.h>
-+#include "qapi/error.h"
-+#include "block/block.h"
-+
-+#define VMA_BLOCK_BITS 12
-+#define VMA_BLOCK_SIZE (1<<VMA_BLOCK_BITS)
-+#define VMA_CLUSTER_BITS (VMA_BLOCK_BITS+4)
-+#define VMA_CLUSTER_SIZE (1<<VMA_CLUSTER_BITS)
-+
-+#if VMA_CLUSTER_SIZE != 65536
-+#error unexpected cluster size
-+#endif
-+
-+#define VMA_EXTENT_HEADER_SIZE 512
-+#define VMA_BLOCKS_PER_EXTENT 59
-+#define VMA_MAX_CONFIGS 256
-+
-+#define VMA_MAX_EXTENT_SIZE \
-+    (VMA_EXTENT_HEADER_SIZE+VMA_CLUSTER_SIZE*VMA_BLOCKS_PER_EXTENT)
-+#if VMA_MAX_EXTENT_SIZE != 3867136
-+#error unexpected VMA_EXTENT_SIZE
-+#endif
-+
-+/* File Format Definitions */
-+
-+#define VMA_MAGIC (GUINT32_TO_BE(('V'<<24)|('M'<<16)|('A'<<8)|0x00))
-+#define VMA_EXTENT_MAGIC (GUINT32_TO_BE(('V'<<24)|('M'<<16)|('A'<<8)|'E'))
-+
-+typedef struct VmaDeviceInfoHeader {
-+    uint32_t devname_ptr; /* offset into blob_buffer table */
-+    uint32_t reserved0;
-+    uint64_t size; /* device size in bytes */
-+    uint64_t reserved1;
-+    uint64_t reserved2;
-+} VmaDeviceInfoHeader;
-+
-+typedef struct VmaHeader {
-+    uint32_t magic;
-+    uint32_t version;
-+    unsigned char uuid[16];
-+    int64_t ctime;
-+    unsigned char md5sum[16];
-+
-+    uint32_t blob_buffer_offset;
-+    uint32_t blob_buffer_size;
-+    uint32_t header_size;
-+
-+    unsigned char reserved[1984];
-+
-+    uint32_t config_names[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
-+    uint32_t config_data[VMA_MAX_CONFIGS];  /* offset into blob_buffer table */
-+
-+    uint32_t reserved1;
-+
-+    VmaDeviceInfoHeader dev_info[256];
-+} VmaHeader;
-+
-+typedef struct VmaExtentHeader {
-+    uint32_t magic;
-+    uint16_t reserved1;
-+    uint16_t block_count;
-+    unsigned char uuid[16];
-+    unsigned char md5sum[16];
-+    uint64_t blockinfo[VMA_BLOCKS_PER_EXTENT];
-+} VmaExtentHeader;
-+
-+/* functions/definitions to read/write vma files */
-+
-+typedef struct VmaReader VmaReader;
-+
-+typedef struct VmaWriter VmaWriter;
-+
-+typedef struct VmaConfigData {
-+    const char *name;
-+    const void *data;
-+    uint32_t len;
-+} VmaConfigData;
-+
-+typedef struct VmaStreamInfo {
-+    uint64_t size;
-+    uint64_t cluster_count;
-+    uint64_t transferred;
-+    uint64_t zero_bytes;
-+    int finished;
-+    char *devname;
-+} VmaStreamInfo;
-+
-+typedef struct VmaStatus {
-+    int status;
-+    bool closed;
-+    char errmsg[8192];
-+    char uuid_str[37];
-+    VmaStreamInfo stream_info[256];
-+} VmaStatus;
-+
-+typedef struct VmaDeviceInfo {
-+    uint64_t size; /* device size in bytes */
-+    const char *devname;
-+} VmaDeviceInfo;
-+
-+VmaWriter *vma_writer_create(const char *filename, uuid_t uuid, Error **errp);
-+int vma_writer_close(VmaWriter *vmaw, Error **errp);
-+void vma_writer_error_propagate(VmaWriter *vmaw, Error **errp);
-+void vma_writer_destroy(VmaWriter *vmaw);
-+int vma_writer_add_config(VmaWriter *vmaw, const char *name, gpointer data,
-+                          size_t len);
-+int vma_writer_register_stream(VmaWriter *vmaw, const char *devname,
-+                               size_t size);
-+
-+int64_t coroutine_fn vma_writer_write(VmaWriter *vmaw, uint8_t dev_id,
-+                                      int64_t cluster_num,
-+                                      const unsigned char *buf,
-+                                      size_t *zero_bytes);
-+
-+int coroutine_fn vma_writer_close_stream(VmaWriter *vmaw, uint8_t dev_id);
-+int coroutine_fn vma_writer_flush_output(VmaWriter *vmaw);
-+
-+int vma_writer_get_status(VmaWriter *vmaw, VmaStatus *status);
-+void vma_writer_set_error(VmaWriter *vmaw, const char *fmt, ...);
-+
-+
-+VmaReader *vma_reader_create(const char *filename, Error **errp);
-+void vma_reader_destroy(VmaReader *vmar);
-+VmaHeader *vma_reader_get_header(VmaReader *vmar);
-+GList *vma_reader_get_config_data(VmaReader *vmar);
-+VmaDeviceInfo *vma_reader_get_device_info(VmaReader *vmar, guint8 dev_id);
-+int vma_reader_register_bs(VmaReader *vmar, guint8 dev_id,
-+                           BlockBackend *target, bool write_zeroes,
-+                           Error **errp);
-+int vma_reader_restore(VmaReader *vmar, int vmstate_fd, bool verbose,
-+                       Error **errp);
-+int vma_reader_verify(VmaReader *vmar, bool verbose, Error **errp);
-+
-+#endif /* BACKUP_VMA_H */
diff --git a/debian/patches/pve/0025-block-backup-move-bcs-bitmap-initialization-to-job-c.patch b/debian/patches/pve/0025-block-backup-move-bcs-bitmap-initialization-to-job-c.patch
new file mode 100644 (file)
index 0000000..6a540f8
--- /dev/null
@@ -0,0 +1,58 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Fabian Ebner <f.ebner@proxmox.com>
+Date: Wed, 2 Mar 2022 08:35:05 +0100
+Subject: [PATCH] block/backup: move bcs bitmap initialization to job creation
+
+For backing up the state of multiple disks from the same time, a job
+for each disk has to be created. It's convenient if the jobs don't
+have to be started at the same time and if operation of the VM can be
+resumed after job creation. This would lead to a window between job
+creation and running the job, where writes can happen. But no writes
+should happen between setting up the copy-before-write filter and
+setting up the block copy state bitmap, because then new writes would
+just pass through.
+
+Commit 06e0a9c16405c0a4c1eca33cf286cc04c42066a2 moved initalization of
+the bitmap to setting up the copy-before-write filter when sync_mode
+is not MIRROR_SYNC_MODE_BITMAP. Ensure that the bitmap is initialized
+upon job creation for the remaining case too, by moving the
+backup_init_bcs_bitmap call to backup_job_create.
+
+Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
+---
+ block/backup.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/block/backup.c b/block/backup.c
+index 21d5983779..47e218857d 100644
+--- a/block/backup.c
++++ b/block/backup.c
+@@ -239,8 +239,8 @@ static void backup_init_bcs_bitmap(BackupBlockJob *job)
+         assert(ret);
+     } else if (job->sync_mode == MIRROR_SYNC_MODE_TOP) {
+         /*
+-         * We can't hog the coroutine to initialize this thoroughly.
+-         * Set a flag and resume work when we are able to yield safely.
++         * Initialization is costly here. Simply set a flag and let the
++         * backup_run coroutine resume work once it can yield safely.
+          */
+         block_copy_set_skip_unallocated(job->bcs, true);
+     }
+@@ -254,8 +254,6 @@ static int coroutine_fn backup_run(Job *job, Error **errp)
+     BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
+     int ret;
+-    backup_init_bcs_bitmap(s);
+-
+     if (s->sync_mode == MIRROR_SYNC_MODE_TOP) {
+         int64_t offset = 0;
+         int64_t count;
+@@ -493,6 +491,8 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
+     block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
+                        &error_abort);
++    backup_init_bcs_bitmap(job);
++
+     return &job->common;
+  error:
diff --git a/debian/patches/pve/0026-PVE-Backup-add-backup-dump-block-driver.patch b/debian/patches/pve/0026-PVE-Backup-add-backup-dump-block-driver.patch
deleted file mode 100644 (file)
index aa6e992..0000000
+++ /dev/null
@@ -1,321 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Dietmar Maurer <dietmar@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:58 +0200
-Subject: [PATCH] PVE-Backup: add backup-dump block driver
-
-- add backup-dump block driver block/backup-dump.c
-- move BackupBlockJob declaration from block/backup.c to include/block/block_int.h
-- block/backup.c - backup-job-create: also consider source cluster size
-- job.c: make job_should_pause non-static
-
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- block/backup-dump.c       | 168 ++++++++++++++++++++++++++++++++++++++
- block/backup.c            |  30 ++-----
- block/meson.build         |   1 +
- include/block/block_int.h |  35 ++++++++
- job.c                     |   3 +-
- 5 files changed, 214 insertions(+), 23 deletions(-)
- create mode 100644 block/backup-dump.c
-
-diff --git a/block/backup-dump.c b/block/backup-dump.c
-new file mode 100644
-index 0000000000..93d7f46950
---- /dev/null
-+++ b/block/backup-dump.c
-@@ -0,0 +1,168 @@
-+/*
-+ * BlockDriver to send backup data stream to a callback function
-+ *
-+ * Copyright (C) 2020 Proxmox Server Solutions GmbH
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ *
-+ */
-+
-+#include "qemu/osdep.h"
-+#include "qemu-common.h"
-+#include "qom/object_interfaces.h"
-+#include "block/block_int.h"
-+
-+typedef struct {
-+    int             dump_cb_block_size;
-+    uint64_t        byte_size;
-+    BackupDumpFunc *dump_cb;
-+    void           *dump_cb_data;
-+} BDRVBackupDumpState;
-+
-+static int qemu_backup_dump_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
-+{
-+    BDRVBackupDumpState *s = bs->opaque;
-+
-+    bdi->cluster_size = s->dump_cb_block_size;
-+    return 0;
-+}
-+
-+static int qemu_backup_dump_check_perm(
-+    BlockDriverState *bs,
-+    uint64_t perm,
-+    uint64_t shared,
-+    Error **errp)
-+{
-+    /* Nothing to do. */
-+    return 0;
-+}
-+
-+static void qemu_backup_dump_set_perm(
-+    BlockDriverState *bs,
-+    uint64_t perm,
-+    uint64_t shared)
-+{
-+    /* Nothing to do. */
-+}
-+
-+static void qemu_backup_dump_abort_perm_update(BlockDriverState *bs)
-+{
-+    /* Nothing to do. */
-+}
-+
-+static void qemu_backup_dump_refresh_limits(BlockDriverState *bs, Error **errp)
-+{
-+    bs->bl.request_alignment = BDRV_SECTOR_SIZE; /* No sub-sector I/O */
-+}
-+
-+static void qemu_backup_dump_close(BlockDriverState *bs)
-+{
-+    /* Nothing to do. */
-+}
-+
-+static int64_t qemu_backup_dump_getlength(BlockDriverState *bs)
-+{
-+    BDRVBackupDumpState *s = bs->opaque;
-+
-+    return s->byte_size;
-+}
-+
-+static coroutine_fn int qemu_backup_dump_co_writev(
-+    BlockDriverState *bs,
-+    int64_t sector_num,
-+    int nb_sectors,
-+    QEMUIOVector *qiov,
-+    int flags)
-+{
-+    /* flags can be only values we set in supported_write_flags */
-+    assert(flags == 0);
-+
-+    BDRVBackupDumpState *s = bs->opaque;
-+    off_t offset = sector_num * BDRV_SECTOR_SIZE;
-+
-+    uint64_t written = 0;
-+
-+    for (int i = 0; i < qiov->niov; ++i) {
-+        const struct iovec *v = &qiov->iov[i];
-+
-+        int rc = s->dump_cb(s->dump_cb_data, offset, v->iov_len, v->iov_base);
-+        if (rc < 0) {
-+            return rc;
-+        }
-+
-+        if (rc != v->iov_len) {
-+            return -EIO;
-+        }
-+
-+        written += v->iov_len;
-+        offset += v->iov_len;
-+    }
-+
-+    return written;
-+}
-+
-+static void qemu_backup_dump_child_perm(
-+    BlockDriverState *bs,
-+    BdrvChild *c,
-+    BdrvChildRole role,
-+    BlockReopenQueue *reopen_queue,
-+    uint64_t perm, uint64_t shared,
-+    uint64_t *nperm, uint64_t *nshared)
-+{
-+    *nperm = BLK_PERM_ALL;
-+    *nshared = BLK_PERM_ALL;
-+}
-+
-+static BlockDriver bdrv_backup_dump_drive = {
-+    .format_name                  = "backup-dump-drive",
-+    .protocol_name                = "backup-dump",
-+    .instance_size                = sizeof(BDRVBackupDumpState),
-+
-+    .bdrv_close                   = qemu_backup_dump_close,
-+    .bdrv_has_zero_init           = bdrv_has_zero_init_1,
-+    .bdrv_getlength               = qemu_backup_dump_getlength,
-+    .bdrv_get_info                = qemu_backup_dump_get_info,
-+
-+    .bdrv_co_writev               = qemu_backup_dump_co_writev,
-+
-+    .bdrv_refresh_limits          = qemu_backup_dump_refresh_limits,
-+    .bdrv_check_perm              = qemu_backup_dump_check_perm,
-+    .bdrv_set_perm                = qemu_backup_dump_set_perm,
-+    .bdrv_abort_perm_update       = qemu_backup_dump_abort_perm_update,
-+    .bdrv_child_perm              = qemu_backup_dump_child_perm,
-+};
-+
-+static void bdrv_backup_dump_init(void)
-+{
-+    bdrv_register(&bdrv_backup_dump_drive);
-+}
-+
-+block_init(bdrv_backup_dump_init);
-+
-+
-+BlockDriverState *bdrv_backup_dump_create(
-+    int dump_cb_block_size,
-+    uint64_t byte_size,
-+    BackupDumpFunc *dump_cb,
-+    void *dump_cb_data,
-+    Error **errp)
-+{
-+    BDRVBackupDumpState *state;
-+    BlockDriverState *bs = bdrv_new_open_driver(
-+        &bdrv_backup_dump_drive, NULL, BDRV_O_RDWR, errp);
-+
-+    if (!bs) {
-+        return NULL;
-+    }
-+
-+    bs->total_sectors = byte_size / BDRV_SECTOR_SIZE;
-+    bs->opaque = state = g_new0(BDRVBackupDumpState, 1);
-+
-+    state->dump_cb_block_size = dump_cb_block_size;
-+    state->byte_size = byte_size;
-+    state->dump_cb = dump_cb;
-+    state->dump_cb_data = dump_cb_data;
-+
-+    return bs;
-+}
-diff --git a/block/backup.c b/block/backup.c
-index 21d5983779..7d9aed1a60 100644
---- a/block/backup.c
-+++ b/block/backup.c
-@@ -29,28 +29,6 @@
- #include "block/copy-before-write.h"
--typedef struct BackupBlockJob {
--    BlockJob common;
--    BlockDriverState *cbw;
--    BlockDriverState *source_bs;
--    BlockDriverState *target_bs;
--
--    BdrvDirtyBitmap *sync_bitmap;
--
--    MirrorSyncMode sync_mode;
--    BitmapSyncMode bitmap_mode;
--    BlockdevOnError on_source_error;
--    BlockdevOnError on_target_error;
--    uint64_t len;
--    int64_t cluster_size;
--    BackupPerf perf;
--
--    BlockCopyState *bcs;
--
--    bool wait;
--    BlockCopyCallState *bg_bcs_call;
--} BackupBlockJob;
--
- static const BlockJobDriver backup_job_driver;
- static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret)
-@@ -457,6 +435,14 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
-     }
-     cluster_size = block_copy_cluster_size(bcs);
-+    if (cluster_size < 0) {
-+        goto error;
-+    }
-+
-+    BlockDriverInfo bdi;
-+    if (bdrv_get_info(bs, &bdi) == 0) {
-+        cluster_size = MAX(cluster_size, bdi.cluster_size);
-+    }
-     if (perf->max_chunk && perf->max_chunk < cluster_size) {
-         error_setg(errp, "Required max-chunk (%" PRIi64 ") is less than backup "
-diff --git a/block/meson.build b/block/meson.build
-index 72081a9974..7883df047c 100644
---- a/block/meson.build
-+++ b/block/meson.build
-@@ -4,6 +4,7 @@ block_ss.add(files(
-   'aio_task.c',
-   'amend.c',
-   'backup.c',
-+  'backup-dump.c',
-   'copy-before-write.c',
-   'blkdebug.c',
-   'blklogwrites.c',
-diff --git a/include/block/block_int.h b/include/block/block_int.h
-index ee0aeb1414..1574b5564b 100644
---- a/include/block/block_int.h
-+++ b/include/block/block_int.h
-@@ -26,6 +26,7 @@
- #include "block/accounting.h"
- #include "block/block.h"
-+#include "block/block-copy.h"
- #include "block/aio-wait.h"
- #include "qemu/queue.h"
- #include "qemu/coroutine.h"
-@@ -64,6 +65,40 @@
- #define BLOCK_PROBE_BUF_SIZE        512
-+typedef int BackupDumpFunc(void *opaque, uint64_t offset, uint64_t bytes, const void *buf);
-+
-+BlockDriverState *bdrv_backuo_dump_create(
-+    int dump_cb_block_size,
-+    uint64_t byte_size,
-+    BackupDumpFunc *dump_cb,
-+    void *dump_cb_data,
-+    Error **errp);
-+
-+// Needs to be defined here, since it's used in blockdev.c to detect PVE backup
-+// jobs with source_bs
-+typedef struct BlockCopyState BlockCopyState;
-+typedef struct BackupBlockJob {
-+    BlockJob common;
-+    BlockDriverState *cbw;
-+    BlockDriverState *source_bs;
-+    BlockDriverState *target_bs;
-+
-+    BdrvDirtyBitmap *sync_bitmap;
-+
-+    MirrorSyncMode sync_mode;
-+    BitmapSyncMode bitmap_mode;
-+    BlockdevOnError on_source_error;
-+    BlockdevOnError on_target_error;
-+    uint64_t len;
-+    int64_t cluster_size;
-+    BackupPerf perf;
-+
-+    BlockCopyState *bcs;
-+
-+    bool wait;
-+    BlockCopyCallState *bg_bcs_call;
-+} BackupBlockJob;
-+
- enum BdrvTrackedRequestType {
-     BDRV_TRACKED_READ,
-     BDRV_TRACKED_WRITE,
-diff --git a/job.c b/job.c
-index dbfa67bb0a..af25dd5b98 100644
---- a/job.c
-+++ b/job.c
-@@ -276,7 +276,8 @@ static bool job_started(Job *job)
-     return job->co;
- }
--static bool job_should_pause(Job *job)
-+bool job_should_pause(Job *job);
-+bool job_should_pause(Job *job)
- {
-     return job->pause_count > 0;
- }
diff --git a/debian/patches/pve/0026-PVE-Backup-add-vma-backup-format-code.patch b/debian/patches/pve/0026-PVE-Backup-add-vma-backup-format-code.patch
new file mode 100644 (file)
index 0000000..c4ed5bb
--- /dev/null
@@ -0,0 +1,2729 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Dietmar Maurer <dietmar@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:57 +0200
+Subject: [PATCH] PVE-Backup: add vma backup format code
+
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[FE: create: register all streams before entering coroutines]
+Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
+---
+ block/meson.build |   2 +
+ meson.build       |   5 +
+ vma-reader.c      | 857 ++++++++++++++++++++++++++++++++++++++++++++++
+ vma-writer.c      | 790 ++++++++++++++++++++++++++++++++++++++++++
+ vma.c             | 851 +++++++++++++++++++++++++++++++++++++++++++++
+ vma.h             | 150 ++++++++
+ 6 files changed, 2655 insertions(+)
+ create mode 100644 vma-reader.c
+ create mode 100644 vma-writer.c
+ create mode 100644 vma.c
+ create mode 100644 vma.h
+
+diff --git a/block/meson.build b/block/meson.build
+index c9d1fdca7d..72081a9974 100644
+--- a/block/meson.build
++++ b/block/meson.build
+@@ -44,6 +44,8 @@ block_ss.add(files(
+   'zeroinit.c',
+ ), zstd, zlib, gnutls)
++block_ss.add(files('../vma-writer.c'), libuuid)
++
+ softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c'))
+ block_ss.add(when: 'CONFIG_QCOW1', if_true: files('qcow.c'))
+diff --git a/meson.build b/meson.build
+index 96de1a6ef9..54c23b9567 100644
+--- a/meson.build
++++ b/meson.build
+@@ -1202,6 +1202,8 @@ keyutils = dependency('libkeyutils', required: false,
+ has_gettid = cc.has_function('gettid')
++libuuid = cc.find_library('uuid', required: true)
++
+ # libselinux
+ selinux = dependency('libselinux',
+                      required: get_option('selinux'),
+@@ -3070,6 +3072,9 @@ if have_tools
+                dependencies: [blockdev, qemuutil, gnutls, selinux],
+                install: true)
++  vma = executable('vma', files('vma.c', 'vma-reader.c') + genh,
++                   dependencies: [authz, block, crypto, io, qom], install: true)
++
+   subdir('storage-daemon')
+   subdir('contrib/rdmacm-mux')
+   subdir('contrib/elf2dmp')
+diff --git a/vma-reader.c b/vma-reader.c
+new file mode 100644
+index 0000000000..2b1d1cdab3
+--- /dev/null
++++ b/vma-reader.c
+@@ -0,0 +1,857 @@
++/*
++ * VMA: Virtual Machine Archive
++ *
++ * Copyright (C) 2012 Proxmox Server Solutions
++ *
++ * Authors:
++ *  Dietmar Maurer (dietmar@proxmox.com)
++ *
++ * This work is licensed under the terms of the GNU GPL, version 2 or later.
++ * See the COPYING file in the top-level directory.
++ *
++ */
++
++#include "qemu/osdep.h"
++#include <glib.h>
++#include <uuid/uuid.h>
++
++#include "qemu-common.h"
++#include "qemu/timer.h"
++#include "qemu/ratelimit.h"
++#include "vma.h"
++#include "block/block.h"
++#include "sysemu/block-backend.h"
++
++static unsigned char zero_vma_block[VMA_BLOCK_SIZE];
++
++typedef struct VmaRestoreState {
++    BlockBackend *target;
++    bool write_zeroes;
++    unsigned long *bitmap;
++    int bitmap_size;
++}  VmaRestoreState;
++
++struct VmaReader {
++    int fd;
++    GChecksum *md5csum;
++    GHashTable *blob_hash;
++    unsigned char *head_data;
++    VmaDeviceInfo devinfo[256];
++    VmaRestoreState rstate[256];
++    GList *cdata_list;
++    guint8 vmstate_stream;
++    uint32_t vmstate_clusters;
++    /* to show restore percentage if run with -v */
++    time_t start_time;
++    int64_t cluster_count;
++    int64_t clusters_read;
++    int64_t zero_cluster_data;
++    int64_t partial_zero_cluster_data;
++    int clusters_read_per;
++};
++
++static guint
++g_int32_hash(gconstpointer v)
++{
++    return *(const uint32_t *)v;
++}
++
++static gboolean
++g_int32_equal(gconstpointer v1, gconstpointer v2)
++{
++    return *((const uint32_t *)v1) == *((const uint32_t *)v2);
++}
++
++static int vma_reader_get_bitmap(VmaRestoreState *rstate, int64_t cluster_num)
++{
++    assert(rstate);
++    assert(rstate->bitmap);
++
++    unsigned long val, idx, bit;
++
++    idx = cluster_num / BITS_PER_LONG;
++
++    assert(rstate->bitmap_size > idx);
++
++    bit = cluster_num % BITS_PER_LONG;
++    val = rstate->bitmap[idx];
++
++    return !!(val & (1UL << bit));
++}
++
++static void vma_reader_set_bitmap(VmaRestoreState *rstate, int64_t cluster_num,
++                                  int dirty)
++{
++    assert(rstate);
++    assert(rstate->bitmap);
++
++    unsigned long val, idx, bit;
++
++    idx = cluster_num / BITS_PER_LONG;
++
++    assert(rstate->bitmap_size > idx);
++
++    bit = cluster_num % BITS_PER_LONG;
++    val = rstate->bitmap[idx];
++    if (dirty) {
++        if (!(val & (1UL << bit))) {
++            val |= 1UL << bit;
++        }
++    } else {
++        if (val & (1UL << bit)) {
++            val &= ~(1UL << bit);
++        }
++    }
++    rstate->bitmap[idx] = val;
++}
++
++typedef struct VmaBlob {
++    uint32_t start;
++    uint32_t len;
++    void *data;
++} VmaBlob;
++
++static const VmaBlob *get_header_blob(VmaReader *vmar, uint32_t pos)
++{
++    assert(vmar);
++    assert(vmar->blob_hash);
++
++    return g_hash_table_lookup(vmar->blob_hash, &pos);
++}
++
++static const char *get_header_str(VmaReader *vmar, uint32_t pos)
++{
++    const VmaBlob *blob = get_header_blob(vmar, pos);
++    if (!blob) {
++        return NULL;
++    }
++    const char *res = (char *)blob->data;
++    if (res[blob->len-1] != '\0') {
++        return NULL;
++    }
++    return res;
++}
++
++static ssize_t
++safe_read(int fd, unsigned char *buf, size_t count)
++{
++    ssize_t n;
++
++    do {
++        n = read(fd, buf, count);
++    } while (n < 0 && errno == EINTR);
++
++    return n;
++}
++
++static ssize_t
++full_read(int fd, unsigned char *buf, size_t len)
++{
++    ssize_t n;
++    size_t total;
++
++    total = 0;
++
++    while (len > 0) {
++        n = safe_read(fd, buf, len);
++
++        if (n == 0) {
++            return total;
++        }
++
++        if (n <= 0) {
++            break;
++        }
++
++        buf += n;
++        total += n;
++        len -= n;
++    }
++
++    if (len) {
++        return -1;
++    }
++
++    return total;
++}
++
++void vma_reader_destroy(VmaReader *vmar)
++{
++    assert(vmar);
++
++    if (vmar->fd >= 0) {
++        close(vmar->fd);
++    }
++
++    if (vmar->cdata_list) {
++        g_list_free(vmar->cdata_list);
++    }
++
++    int i;
++    for (i = 1; i < 256; i++) {
++        if (vmar->rstate[i].bitmap) {
++            g_free(vmar->rstate[i].bitmap);
++        }
++    }
++
++    if (vmar->md5csum) {
++        g_checksum_free(vmar->md5csum);
++    }
++
++    if (vmar->blob_hash) {
++        g_hash_table_destroy(vmar->blob_hash);
++    }
++
++    if (vmar->head_data) {
++        g_free(vmar->head_data);
++    }
++
++    g_free(vmar);
++
++};
++
++static int vma_reader_read_head(VmaReader *vmar, Error **errp)
++{
++    assert(vmar);
++    assert(errp);
++    assert(*errp == NULL);
++
++    unsigned char md5sum[16];
++    int i;
++    int ret = 0;
++
++    vmar->head_data = g_malloc(sizeof(VmaHeader));
++
++    if (full_read(vmar->fd, vmar->head_data, sizeof(VmaHeader)) !=
++        sizeof(VmaHeader)) {
++        error_setg(errp, "can't read vma header - %s",
++                   errno ? g_strerror(errno) : "got EOF");
++        return -1;
++    }
++
++    VmaHeader *h = (VmaHeader *)vmar->head_data;
++
++    if (h->magic != VMA_MAGIC) {
++        error_setg(errp, "not a vma file - wrong magic number");
++        return -1;
++    }
++
++    uint32_t header_size = GUINT32_FROM_BE(h->header_size);
++    int need = header_size - sizeof(VmaHeader);
++    if (need <= 0) {
++        error_setg(errp, "wrong vma header size %d", header_size);
++        return -1;
++    }
++
++    vmar->head_data = g_realloc(vmar->head_data, header_size);
++    h = (VmaHeader *)vmar->head_data;
++
++    if (full_read(vmar->fd, vmar->head_data + sizeof(VmaHeader), need) !=
++        need) {
++        error_setg(errp, "can't read vma header data - %s",
++                   errno ? g_strerror(errno) : "got EOF");
++        return -1;
++    }
++
++    memcpy(md5sum, h->md5sum, 16);
++    memset(h->md5sum, 0, 16);
++
++    g_checksum_reset(vmar->md5csum);
++    g_checksum_update(vmar->md5csum, vmar->head_data, header_size);
++    gsize csize = 16;
++    g_checksum_get_digest(vmar->md5csum, (guint8 *)(h->md5sum), &csize);
++
++    if (memcmp(md5sum, h->md5sum, 16) != 0) {
++        error_setg(errp, "wrong vma header chechsum");
++        return -1;
++    }
++
++    /* we can modify header data after checksum verify */
++    h->header_size = header_size;
++
++    h->version = GUINT32_FROM_BE(h->version);
++    if (h->version != 1) {
++        error_setg(errp, "wrong vma version %d", h->version);
++        return -1;
++    }
++
++    h->ctime = GUINT64_FROM_BE(h->ctime);
++    h->blob_buffer_offset = GUINT32_FROM_BE(h->blob_buffer_offset);
++    h->blob_buffer_size = GUINT32_FROM_BE(h->blob_buffer_size);
++
++    uint32_t bstart = h->blob_buffer_offset + 1;
++    uint32_t bend = h->blob_buffer_offset + h->blob_buffer_size;
++
++    if (bstart <= sizeof(VmaHeader)) {
++        error_setg(errp, "wrong vma blob buffer offset %d",
++                   h->blob_buffer_offset);
++        return -1;
++    }
++
++    if (bend > header_size) {
++        error_setg(errp, "wrong vma blob buffer size %d/%d",
++                   h->blob_buffer_offset, h->blob_buffer_size);
++        return -1;
++    }
++
++    while ((bstart + 2) <= bend) {
++        uint32_t size = vmar->head_data[bstart] +
++            (vmar->head_data[bstart+1] << 8);
++        if ((bstart + size + 2) <= bend) {
++            VmaBlob *blob = g_new0(VmaBlob, 1);
++            blob->start = bstart - h->blob_buffer_offset;
++            blob->len = size;
++            blob->data = vmar->head_data + bstart + 2;
++            g_hash_table_insert(vmar->blob_hash, &blob->start, blob);
++        }
++        bstart += size + 2;
++    }
++
++
++    int count = 0;
++    for (i = 1; i < 256; i++) {
++        VmaDeviceInfoHeader *dih = &h->dev_info[i];
++        uint32_t devname_ptr = GUINT32_FROM_BE(dih->devname_ptr);
++        uint64_t size = GUINT64_FROM_BE(dih->size);
++        const char *devname =  get_header_str(vmar, devname_ptr);
++
++        if (size && devname) {
++            count++;
++            vmar->devinfo[i].size = size;
++            vmar->devinfo[i].devname = devname;
++
++            if (strcmp(devname, "vmstate") == 0) {
++                vmar->vmstate_stream = i;
++            }
++        }
++    }
++
++    for (i = 0; i < VMA_MAX_CONFIGS; i++) {
++        uint32_t name_ptr = GUINT32_FROM_BE(h->config_names[i]);
++        uint32_t data_ptr = GUINT32_FROM_BE(h->config_data[i]);
++
++        if (!(name_ptr && data_ptr)) {
++            continue;
++        }
++        const char *name =  get_header_str(vmar, name_ptr);
++        const VmaBlob *blob = get_header_blob(vmar, data_ptr);
++
++        if (!(name && blob)) {
++            error_setg(errp, "vma contains invalid data pointers");
++            return -1;
++        }
++
++        VmaConfigData *cdata = g_new0(VmaConfigData, 1);
++        cdata->name = name;
++        cdata->data = blob->data;
++        cdata->len = blob->len;
++
++        vmar->cdata_list = g_list_append(vmar->cdata_list, cdata);
++    }
++
++    return ret;
++};
++
++VmaReader *vma_reader_create(const char *filename, Error **errp)
++{
++    assert(filename);
++    assert(errp);
++
++    VmaReader *vmar = g_new0(VmaReader, 1);
++
++    if (strcmp(filename, "-") == 0) {
++        vmar->fd = dup(0);
++    } else {
++        vmar->fd = open(filename, O_RDONLY);
++    }
++
++    if (vmar->fd < 0) {
++        error_setg(errp, "can't open file %s - %s\n", filename,
++                   g_strerror(errno));
++        goto err;
++    }
++
++    vmar->md5csum = g_checksum_new(G_CHECKSUM_MD5);
++    if (!vmar->md5csum) {
++        error_setg(errp, "can't allocate cmsum\n");
++        goto err;
++    }
++
++    vmar->blob_hash = g_hash_table_new_full(g_int32_hash, g_int32_equal,
++                                            NULL, g_free);
++
++    if (vma_reader_read_head(vmar, errp) < 0) {
++        goto err;
++    }
++
++    return vmar;
++
++err:
++    if (vmar) {
++        vma_reader_destroy(vmar);
++    }
++
++    return NULL;
++}
++
++VmaHeader *vma_reader_get_header(VmaReader *vmar)
++{
++    assert(vmar);
++    assert(vmar->head_data);
++
++    return (VmaHeader *)(vmar->head_data);
++}
++
++GList *vma_reader_get_config_data(VmaReader *vmar)
++{
++    assert(vmar);
++    assert(vmar->head_data);
++
++    return vmar->cdata_list;
++}
++
++VmaDeviceInfo *vma_reader_get_device_info(VmaReader *vmar, guint8 dev_id)
++{
++    assert(vmar);
++    assert(dev_id);
++
++    if (vmar->devinfo[dev_id].size && vmar->devinfo[dev_id].devname) {
++        return &vmar->devinfo[dev_id];
++    }
++
++    return NULL;
++}
++
++static void allocate_rstate(VmaReader *vmar,  guint8 dev_id,
++                            BlockBackend *target, bool write_zeroes)
++{
++    assert(vmar);
++    assert(dev_id);
++
++    vmar->rstate[dev_id].target = target;
++    vmar->rstate[dev_id].write_zeroes = write_zeroes;
++
++    int64_t size = vmar->devinfo[dev_id].size;
++
++    int64_t bitmap_size = (size/BDRV_SECTOR_SIZE) +
++        (VMA_CLUSTER_SIZE/BDRV_SECTOR_SIZE) * BITS_PER_LONG - 1;
++    bitmap_size /= (VMA_CLUSTER_SIZE/BDRV_SECTOR_SIZE) * BITS_PER_LONG;
++
++    vmar->rstate[dev_id].bitmap_size = bitmap_size;
++    vmar->rstate[dev_id].bitmap = g_new0(unsigned long, bitmap_size);
++
++    vmar->cluster_count += size/VMA_CLUSTER_SIZE;
++}
++
++int vma_reader_register_bs(VmaReader *vmar, guint8 dev_id, BlockBackend *target,
++                           bool write_zeroes, Error **errp)
++{
++    assert(vmar);
++    assert(target != NULL);
++    assert(dev_id);
++    assert(vmar->rstate[dev_id].target == NULL);
++
++    int64_t size = blk_getlength(target);
++    int64_t size_diff = size - vmar->devinfo[dev_id].size;
++
++    /* storage types can have different size restrictions, so it
++     * is not always possible to create an image with exact size.
++     * So we tolerate a size difference up to 4MB.
++     */
++    if ((size_diff < 0) || (size_diff > 4*1024*1024)) {
++        error_setg(errp, "vma_reader_register_bs for stream %s failed - "
++                   "unexpected size %zd != %zd", vmar->devinfo[dev_id].devname,
++                   size, vmar->devinfo[dev_id].size);
++        return -1;
++    }
++
++    allocate_rstate(vmar, dev_id, target, write_zeroes);
++
++    return 0;
++}
++
++static ssize_t safe_write(int fd, void *buf, size_t count)
++{
++    ssize_t n;
++
++    do {
++        n = write(fd, buf, count);
++    } while (n < 0 && errno == EINTR);
++
++    return n;
++}
++
++static size_t full_write(int fd, void *buf, size_t len)
++{
++    ssize_t n;
++    size_t total;
++
++    total = 0;
++
++    while (len > 0) {
++        n = safe_write(fd, buf, len);
++        if (n < 0) {
++            return n;
++        }
++        buf += n;
++        total += n;
++        len -= n;
++    }
++
++    if (len) {
++        /* incomplete write ? */
++        return -1;
++    }
++
++    return total;
++}
++
++static int restore_write_data(VmaReader *vmar, guint8 dev_id,
++                              BlockBackend *target, int vmstate_fd,
++                              unsigned char *buf, int64_t sector_num,
++                              int nb_sectors, Error **errp)
++{
++    assert(vmar);
++
++    if (dev_id == vmar->vmstate_stream) {
++        if (vmstate_fd >= 0) {
++            int len = nb_sectors * BDRV_SECTOR_SIZE;
++            int res = full_write(vmstate_fd, buf, len);
++            if (res < 0) {
++                error_setg(errp, "write vmstate failed %d", res);
++                return -1;
++            }
++        }
++    } else {
++        int res = blk_pwrite(target, sector_num * BDRV_SECTOR_SIZE, buf, nb_sectors * BDRV_SECTOR_SIZE, 0);
++        if (res < 0) {
++            error_setg(errp, "blk_pwrite to %s failed (%d)",
++                       bdrv_get_device_name(blk_bs(target)), res);
++            return -1;
++        }
++    }
++    return 0;
++}
++
++static int restore_extent(VmaReader *vmar, unsigned char *buf,
++                          int extent_size, int vmstate_fd,
++                          bool verbose, bool verify, Error **errp)
++{
++    assert(vmar);
++    assert(buf);
++
++    VmaExtentHeader *ehead = (VmaExtentHeader *)buf;
++    int start = VMA_EXTENT_HEADER_SIZE;
++    int i;
++
++    for (i = 0; i < VMA_BLOCKS_PER_EXTENT; i++) {
++        uint64_t block_info = GUINT64_FROM_BE(ehead->blockinfo[i]);
++        uint64_t cluster_num = block_info & 0xffffffff;
++        uint8_t dev_id = (block_info >> 32) & 0xff;
++        uint16_t mask = block_info >> (32+16);
++        int64_t max_sector;
++
++        if (!dev_id) {
++            continue;
++        }
++
++        VmaRestoreState *rstate = &vmar->rstate[dev_id];
++        BlockBackend *target = NULL;
++
++        if (dev_id != vmar->vmstate_stream) {
++            target = rstate->target;
++            if (!verify && !target) {
++                error_setg(errp, "got wrong dev id %d", dev_id);
++                return -1;
++            }
++
++            if (vma_reader_get_bitmap(rstate, cluster_num)) {
++                error_setg(errp, "found duplicated cluster %zd for stream %s",
++                          cluster_num, vmar->devinfo[dev_id].devname);
++                return -1;
++            }
++            vma_reader_set_bitmap(rstate, cluster_num, 1);
++
++            max_sector = vmar->devinfo[dev_id].size/BDRV_SECTOR_SIZE;
++        } else {
++            max_sector = G_MAXINT64;
++            if (cluster_num != vmar->vmstate_clusters) {
++                error_setg(errp, "found out of order vmstate data");
++                return -1;
++            }
++            vmar->vmstate_clusters++;
++        }
++
++        vmar->clusters_read++;
++
++        if (verbose) {
++            time_t duration = time(NULL) - vmar->start_time;
++            int percent = (vmar->clusters_read*100)/vmar->cluster_count;
++            if (percent != vmar->clusters_read_per) {
++                printf("progress %d%% (read %zd bytes, duration %zd sec)\n",
++                       percent, vmar->clusters_read*VMA_CLUSTER_SIZE,
++                       duration);
++                fflush(stdout);
++                vmar->clusters_read_per = percent;
++            }
++        }
++
++        /* try to write whole clusters to speedup restore */
++        if (mask == 0xffff) {
++            if ((start + VMA_CLUSTER_SIZE) > extent_size) {
++                error_setg(errp, "short vma extent - too many blocks");
++                return -1;
++            }
++            int64_t sector_num = (cluster_num * VMA_CLUSTER_SIZE) /
++                BDRV_SECTOR_SIZE;
++            int64_t end_sector = sector_num +
++                VMA_CLUSTER_SIZE/BDRV_SECTOR_SIZE;
++
++            if (end_sector > max_sector) {
++                end_sector = max_sector;
++            }
++
++            if (end_sector <= sector_num) {
++                error_setg(errp, "got wrong block address - write beyond end");
++                return -1;
++            }
++
++            if (!verify) {
++                int nb_sectors = end_sector - sector_num;
++                if (restore_write_data(vmar, dev_id, target, vmstate_fd,
++                                       buf + start, sector_num, nb_sectors,
++                                       errp) < 0) {
++                    return -1;
++                }
++            }
++
++            start += VMA_CLUSTER_SIZE;
++        } else {
++            int j;
++            int bit = 1;
++
++            for (j = 0; j < 16; j++) {
++                int64_t sector_num = (cluster_num*VMA_CLUSTER_SIZE +
++                                      j*VMA_BLOCK_SIZE)/BDRV_SECTOR_SIZE;
++
++                int64_t end_sector = sector_num +
++                    VMA_BLOCK_SIZE/BDRV_SECTOR_SIZE;
++                if (end_sector > max_sector) {
++                    end_sector = max_sector;
++                }
++
++                if (mask & bit) {
++                    if ((start + VMA_BLOCK_SIZE) > extent_size) {
++                        error_setg(errp, "short vma extent - too many blocks");
++                        return -1;
++                    }
++
++                    if (end_sector <= sector_num) {
++                        error_setg(errp, "got wrong block address - "
++                                   "write beyond end");
++                        return -1;
++                    }
++
++                    if (!verify) {
++                        int nb_sectors = end_sector - sector_num;
++                        if (restore_write_data(vmar, dev_id, target, vmstate_fd,
++                                               buf + start, sector_num,
++                                               nb_sectors, errp) < 0) {
++                            return -1;
++                        }
++                    }
++
++                    start += VMA_BLOCK_SIZE;
++
++                } else {
++
++
++                    if (end_sector > sector_num) {
++                        /* Todo: use bdrv_co_write_zeroes (but that need to
++                         * be run inside coroutine?)
++                         */
++                        int nb_sectors = end_sector - sector_num;
++                        int zero_size = BDRV_SECTOR_SIZE*nb_sectors;
++                        vmar->zero_cluster_data += zero_size;
++                        if (mask != 0) {
++                            vmar->partial_zero_cluster_data += zero_size;
++                        }
++
++                        if (rstate->write_zeroes && !verify) {
++                            if (restore_write_data(vmar, dev_id, target, vmstate_fd,
++                                                   zero_vma_block, sector_num,
++                                                   nb_sectors, errp) < 0) {
++                                return -1;
++                            }
++                        }
++                    }
++                }
++
++                bit = bit << 1;
++            }
++        }
++    }
++
++    if (start != extent_size) {
++        error_setg(errp, "vma extent error - missing blocks");
++        return -1;
++    }
++
++    return 0;
++}
++
++static int vma_reader_restore_full(VmaReader *vmar, int vmstate_fd,
++                                   bool verbose, bool verify,
++                                   Error **errp)
++{
++    assert(vmar);
++    assert(vmar->head_data);
++
++    int ret = 0;
++    unsigned char buf[VMA_MAX_EXTENT_SIZE];
++    int buf_pos = 0;
++    unsigned char md5sum[16];
++    VmaHeader *h = (VmaHeader *)vmar->head_data;
++
++    vmar->start_time = time(NULL);
++
++    while (1) {
++        int bytes = full_read(vmar->fd, buf + buf_pos, sizeof(buf) - buf_pos);
++        if (bytes < 0) {
++            error_setg(errp, "read failed - %s", g_strerror(errno));
++            return -1;
++        }
++
++        buf_pos += bytes;
++
++        if (!buf_pos) {
++            break; /* EOF */
++        }
++
++        if (buf_pos < VMA_EXTENT_HEADER_SIZE) {
++            error_setg(errp, "read short extent (%d bytes)", buf_pos);
++            return -1;
++        }
++
++        VmaExtentHeader *ehead = (VmaExtentHeader *)buf;
++
++        /* extract md5sum */
++        memcpy(md5sum, ehead->md5sum, sizeof(ehead->md5sum));
++        memset(ehead->md5sum, 0, sizeof(ehead->md5sum));
++
++        g_checksum_reset(vmar->md5csum);
++        g_checksum_update(vmar->md5csum, buf, VMA_EXTENT_HEADER_SIZE);
++        gsize csize = 16;
++        g_checksum_get_digest(vmar->md5csum, ehead->md5sum, &csize);
++
++        if (memcmp(md5sum, ehead->md5sum, 16) != 0) {
++            error_setg(errp, "wrong vma extent header chechsum");
++            return -1;
++        }
++
++        if (memcmp(h->uuid, ehead->uuid, sizeof(ehead->uuid)) != 0) {
++            error_setg(errp, "wrong vma extent uuid");
++            return -1;
++        }
++
++        if (ehead->magic != VMA_EXTENT_MAGIC || ehead->reserved1 != 0) {
++            error_setg(errp, "wrong vma extent header magic");
++            return -1;
++        }
++
++        int block_count = GUINT16_FROM_BE(ehead->block_count);
++        int extent_size = VMA_EXTENT_HEADER_SIZE + block_count*VMA_BLOCK_SIZE;
++
++        if (buf_pos < extent_size) {
++            error_setg(errp, "short vma extent (%d < %d)", buf_pos,
++                       extent_size);
++            return -1;
++        }
++
++        if (restore_extent(vmar, buf, extent_size, vmstate_fd, verbose,
++                           verify, errp) < 0) {
++            return -1;
++        }
++
++        if (buf_pos > extent_size) {
++            memmove(buf, buf + extent_size, buf_pos - extent_size);
++            buf_pos = buf_pos - extent_size;
++        } else {
++            buf_pos = 0;
++        }
++    }
++
++    bdrv_drain_all();
++
++    int i;
++    for (i = 1; i < 256; i++) {
++        VmaRestoreState *rstate = &vmar->rstate[i];
++        if (!rstate->target) {
++            continue;
++        }
++
++        if (blk_flush(rstate->target) < 0) {
++            error_setg(errp, "vma blk_flush %s failed",
++                       vmar->devinfo[i].devname);
++            return -1;
++        }
++
++        if (vmar->devinfo[i].size &&
++            (strcmp(vmar->devinfo[i].devname, "vmstate") != 0)) {
++            assert(rstate->bitmap);
++
++            int64_t cluster_num, end;
++
++            end = (vmar->devinfo[i].size + VMA_CLUSTER_SIZE - 1) /
++                VMA_CLUSTER_SIZE;
++
++            for (cluster_num = 0; cluster_num < end; cluster_num++) {
++                if (!vma_reader_get_bitmap(rstate, cluster_num)) {
++                    error_setg(errp, "detected missing cluster %zd "
++                               "for stream %s", cluster_num,
++                               vmar->devinfo[i].devname);
++                    return -1;
++                }
++            }
++        }
++    }
++
++    if (verbose) {
++        if (vmar->clusters_read) {
++            printf("total bytes read %zd, sparse bytes %zd (%.3g%%)\n",
++                   vmar->clusters_read*VMA_CLUSTER_SIZE,
++                   vmar->zero_cluster_data,
++                   (double)(100.0*vmar->zero_cluster_data)/
++                   (vmar->clusters_read*VMA_CLUSTER_SIZE));
++
++            int64_t datasize = vmar->clusters_read*VMA_CLUSTER_SIZE-vmar->zero_cluster_data;
++            if (datasize) { // this does not make sense for empty files
++                printf("space reduction due to 4K zero blocks %.3g%%\n",
++                       (double)(100.0*vmar->partial_zero_cluster_data) / datasize);
++            }
++        } else {
++            printf("vma archive contains no image data\n");
++        }
++    }
++    return ret;
++}
++
++int vma_reader_restore(VmaReader *vmar, int vmstate_fd, bool verbose,
++                       Error **errp)
++{
++    return vma_reader_restore_full(vmar, vmstate_fd, verbose, false, errp);
++}
++
++int vma_reader_verify(VmaReader *vmar, bool verbose, Error **errp)
++{
++    guint8 dev_id;
++
++    for (dev_id = 1; dev_id < 255; dev_id++) {
++        if (vma_reader_get_device_info(vmar, dev_id)) {
++            allocate_rstate(vmar, dev_id, NULL, false);
++        }
++    }
++
++    return vma_reader_restore_full(vmar, -1, verbose, true, errp);
++}
++
+diff --git a/vma-writer.c b/vma-writer.c
+new file mode 100644
+index 0000000000..11d8321ffd
+--- /dev/null
++++ b/vma-writer.c
+@@ -0,0 +1,790 @@
++/*
++ * VMA: Virtual Machine Archive
++ *
++ * Copyright (C) 2012 Proxmox Server Solutions
++ *
++ * Authors:
++ *  Dietmar Maurer (dietmar@proxmox.com)
++ *
++ * This work is licensed under the terms of the GNU GPL, version 2 or later.
++ * See the COPYING file in the top-level directory.
++ *
++ */
++
++#include "qemu/osdep.h"
++#include <glib.h>
++#include <uuid/uuid.h>
++
++#include "vma.h"
++#include "block/block.h"
++#include "monitor/monitor.h"
++#include "qemu/main-loop.h"
++#include "qemu/coroutine.h"
++#include "qemu/cutils.h"
++
++#define DEBUG_VMA 0
++
++#define DPRINTF(fmt, ...)\
++    do { if (DEBUG_VMA) { printf("vma: " fmt, ## __VA_ARGS__); } } while (0)
++
++#define WRITE_BUFFERS 5
++#define HEADER_CLUSTERS 8
++#define HEADERBUF_SIZE (VMA_CLUSTER_SIZE*HEADER_CLUSTERS)
++
++struct VmaWriter {
++    int fd;
++    FILE *cmd;
++    int status;
++    char errmsg[8192];
++    uuid_t uuid;
++    bool header_written;
++    bool closed;
++
++    /* we always write extents */
++    unsigned char *outbuf;
++    int outbuf_pos; /* in bytes */
++    int outbuf_count; /* in VMA_BLOCKS */
++    uint64_t outbuf_block_info[VMA_BLOCKS_PER_EXTENT];
++
++    unsigned char *headerbuf;
++
++    GChecksum *md5csum;
++    CoMutex flush_lock;
++    Coroutine *co_writer;
++
++    /* drive informations */
++    VmaStreamInfo stream_info[256];
++    guint stream_count;
++
++    guint8 vmstate_stream;
++    uint32_t vmstate_clusters;
++
++    /* header blob table */
++    char *header_blob_table;
++    uint32_t header_blob_table_size;
++    uint32_t header_blob_table_pos;
++
++    /* store for config blobs */
++    uint32_t config_names[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
++    uint32_t config_data[VMA_MAX_CONFIGS];  /* offset into blob_buffer table */
++    uint32_t config_count;
++};
++
++void vma_writer_set_error(VmaWriter *vmaw, const char *fmt, ...)
++{
++    va_list ap;
++
++    if (vmaw->status < 0) {
++        return;
++    }
++
++    vmaw->status = -1;
++
++    va_start(ap, fmt);
++    g_vsnprintf(vmaw->errmsg, sizeof(vmaw->errmsg), fmt, ap);
++    va_end(ap);
++
++    DPRINTF("vma_writer_set_error: %s\n", vmaw->errmsg);
++}
++
++static uint32_t allocate_header_blob(VmaWriter *vmaw, const char *data,
++                                     size_t len)
++{
++    if (len > 65535) {
++        return 0;
++    }
++
++    if (!vmaw->header_blob_table ||
++        (vmaw->header_blob_table_size <
++         (vmaw->header_blob_table_pos + len + 2))) {
++        int newsize = vmaw->header_blob_table_size + ((len + 2 + 511)/512)*512;
++
++        vmaw->header_blob_table = g_realloc(vmaw->header_blob_table, newsize);
++        memset(vmaw->header_blob_table + vmaw->header_blob_table_size,
++               0, newsize - vmaw->header_blob_table_size);
++        vmaw->header_blob_table_size = newsize;
++    }
++
++    uint32_t cpos = vmaw->header_blob_table_pos;
++    vmaw->header_blob_table[cpos] = len & 255;
++    vmaw->header_blob_table[cpos+1] = (len >> 8) & 255;
++    memcpy(vmaw->header_blob_table + cpos + 2, data, len);
++    vmaw->header_blob_table_pos += len + 2;
++    return cpos;
++}
++
++static uint32_t allocate_header_string(VmaWriter *vmaw, const char *str)
++{
++    assert(vmaw);
++
++    size_t len = strlen(str) + 1;
++
++    return allocate_header_blob(vmaw, str, len);
++}
++
++int vma_writer_add_config(VmaWriter *vmaw, const char *name, gpointer data,
++                          gsize len)
++{
++    assert(vmaw);
++    assert(!vmaw->header_written);
++    assert(vmaw->config_count < VMA_MAX_CONFIGS);
++    assert(name);
++    assert(data);
++
++    gchar *basename = g_path_get_basename(name);
++    uint32_t name_ptr = allocate_header_string(vmaw, basename);
++    g_free(basename);
++
++    if (!name_ptr) {
++        return -1;
++    }
++
++    uint32_t data_ptr = allocate_header_blob(vmaw, data, len);
++    if (!data_ptr) {
++        return -1;
++    }
++
++    vmaw->config_names[vmaw->config_count] = name_ptr;
++    vmaw->config_data[vmaw->config_count] = data_ptr;
++
++    vmaw->config_count++;
++
++    return 0;
++}
++
++int vma_writer_register_stream(VmaWriter *vmaw, const char *devname,
++                               size_t size)
++{
++    assert(vmaw);
++    assert(devname);
++    assert(!vmaw->status);
++
++    if (vmaw->header_written) {
++        vma_writer_set_error(vmaw, "vma_writer_register_stream: header "
++                             "already written");
++        return -1;
++    }
++
++    guint n = vmaw->stream_count + 1;
++
++    /* we can have dev_ids form 1 to 255 (0 reserved)
++     * 255(-1) reseverd for safety
++     */
++    if (n > 254) {
++        vma_writer_set_error(vmaw, "vma_writer_register_stream: "
++                             "too many drives");
++        return -1;
++    }
++
++    if (size <= 0) {
++        vma_writer_set_error(vmaw, "vma_writer_register_stream: "
++                             "got strange size %zd", size);
++        return -1;
++    }
++
++    DPRINTF("vma_writer_register_stream %s %zu %d\n", devname, size, n);
++
++    vmaw->stream_info[n].devname = g_strdup(devname);
++    vmaw->stream_info[n].size = size;
++
++    vmaw->stream_info[n].cluster_count = (size + VMA_CLUSTER_SIZE - 1) /
++        VMA_CLUSTER_SIZE;
++
++    vmaw->stream_count = n;
++
++    if (strcmp(devname, "vmstate") == 0) {
++        vmaw->vmstate_stream = n;
++    }
++
++    return n;
++}
++
++static void coroutine_fn yield_until_fd_writable(int fd)
++{
++    assert(qemu_in_coroutine());
++    AioContext *ctx = qemu_get_current_aio_context();
++    aio_set_fd_handler(ctx, fd, false, NULL, (IOHandler *)qemu_coroutine_enter,
++                       NULL, qemu_coroutine_self());
++    qemu_coroutine_yield();
++    aio_set_fd_handler(ctx, fd, false, NULL, NULL, NULL, NULL);
++}
++
++static ssize_t coroutine_fn
++vma_queue_write(VmaWriter *vmaw, const void *buf, size_t bytes)
++{
++    DPRINTF("vma_queue_write enter %zd\n", bytes);
++
++    assert(vmaw);
++    assert(buf);
++    assert(bytes <= VMA_MAX_EXTENT_SIZE);
++
++    size_t done = 0;
++    ssize_t ret;
++
++    assert(vmaw->co_writer == NULL);
++
++    vmaw->co_writer = qemu_coroutine_self();
++
++    while (done < bytes) {
++        if (vmaw->status < 0) {
++            DPRINTF("vma_queue_write detected canceled backup\n");
++            done = -1;
++            break;
++        }
++        yield_until_fd_writable(vmaw->fd);
++        ret = write(vmaw->fd, buf + done, bytes - done);
++        if (ret > 0) {
++            done += ret;
++            DPRINTF("vma_queue_write written %zd %zd\n", done, ret);
++        } else if (ret < 0) {
++            if (errno == EAGAIN || errno == EWOULDBLOCK) {
++                /* try again */
++           } else {
++                vma_writer_set_error(vmaw, "vma_queue_write: write error - %s",
++                                     g_strerror(errno));
++                done = -1; /* always return failure for partial writes */
++                break;
++            }
++        } else if (ret == 0) {
++            /* should not happen - simply try again */
++        }
++    }
++
++    vmaw->co_writer = NULL;
++
++    return (done == bytes) ? bytes : -1;
++}
++
++VmaWriter *vma_writer_create(const char *filename, uuid_t uuid, Error **errp)
++{
++    const char *p;
++
++    assert(sizeof(VmaHeader) == (4096 + 8192));
++    assert(G_STRUCT_OFFSET(VmaHeader, config_names) == 2044);
++    assert(G_STRUCT_OFFSET(VmaHeader, config_data) == 3068);
++    assert(G_STRUCT_OFFSET(VmaHeader, dev_info) == 4096);
++    assert(sizeof(VmaExtentHeader) == 512);
++
++    VmaWriter *vmaw = g_new0(VmaWriter, 1);
++    vmaw->fd = -1;
++
++    vmaw->md5csum = g_checksum_new(G_CHECKSUM_MD5);
++    if (!vmaw->md5csum) {
++        error_setg(errp, "can't allocate cmsum\n");
++        goto err;
++    }
++
++    if (strstart(filename, "exec:", &p)) {
++        vmaw->cmd = popen(p, "w");
++        if (vmaw->cmd == NULL) {
++            error_setg(errp, "can't popen command '%s' - %s\n", p,
++                       g_strerror(errno));
++            goto err;
++        }
++        vmaw->fd = fileno(vmaw->cmd);
++
++        /* try to use O_NONBLOCK */
++        fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_NONBLOCK);
++
++    } else {
++        struct stat st;
++        int oflags;
++        const char *tmp_id_str;
++
++        if ((stat(filename, &st) == 0) && S_ISFIFO(st.st_mode)) {
++            oflags = O_NONBLOCK|O_WRONLY;
++            vmaw->fd = qemu_open(filename, oflags, errp);
++        } else if (strstart(filename, "/dev/fdset/", &tmp_id_str)) {
++            oflags = O_NONBLOCK|O_WRONLY;
++            vmaw->fd = qemu_open(filename, oflags, errp);
++        } else if (strstart(filename, "/dev/fdname/", &tmp_id_str)) {
++            vmaw->fd = monitor_get_fd(monitor_cur(), tmp_id_str, errp);
++            if (vmaw->fd < 0) {
++                goto err;
++            }
++            /* try to use O_NONBLOCK */
++            fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_NONBLOCK);
++        } else  {
++            oflags = O_NONBLOCK|O_DIRECT|O_WRONLY|O_EXCL;
++            vmaw->fd = qemu_create(filename, oflags, 0644, errp);
++        }
++
++        if (vmaw->fd < 0) {
++            error_setg(errp, "can't open file %s - %s\n", filename,
++                       g_strerror(errno));
++            goto err;
++        }
++    }
++
++    /* we use O_DIRECT, so we need to align IO buffers */
++
++    vmaw->outbuf = qemu_memalign(512, VMA_MAX_EXTENT_SIZE);
++    vmaw->headerbuf = qemu_memalign(512, HEADERBUF_SIZE);
++
++    vmaw->outbuf_count = 0;
++    vmaw->outbuf_pos = VMA_EXTENT_HEADER_SIZE;
++
++    vmaw->header_blob_table_pos = 1; /* start at pos 1 */
++
++    qemu_co_mutex_init(&vmaw->flush_lock);
++
++    uuid_copy(vmaw->uuid, uuid);
++
++    return vmaw;
++
++err:
++    if (vmaw) {
++        if (vmaw->cmd) {
++            pclose(vmaw->cmd);
++        } else if (vmaw->fd >= 0) {
++            close(vmaw->fd);
++        }
++
++        if (vmaw->md5csum) {
++            g_checksum_free(vmaw->md5csum);
++        }
++
++        g_free(vmaw);
++    }
++
++    return NULL;
++}
++
++static int coroutine_fn vma_write_header(VmaWriter *vmaw)
++{
++    assert(vmaw);
++    unsigned char *buf = vmaw->headerbuf;
++    VmaHeader *head = (VmaHeader *)buf;
++
++    int i;
++
++    DPRINTF("VMA WRITE HEADER\n");
++
++    if (vmaw->status < 0) {
++        return vmaw->status;
++    }
++
++    memset(buf, 0, HEADERBUF_SIZE);
++
++    head->magic = VMA_MAGIC;
++    head->version = GUINT32_TO_BE(1); /* v1 */
++    memcpy(head->uuid, vmaw->uuid, 16);
++
++    time_t ctime = time(NULL);
++    head->ctime = GUINT64_TO_BE(ctime);
++
++    for (i = 0; i < VMA_MAX_CONFIGS; i++) {
++        head->config_names[i] = GUINT32_TO_BE(vmaw->config_names[i]);
++        head->config_data[i] = GUINT32_TO_BE(vmaw->config_data[i]);
++    }
++
++    /* 32 bytes per device (12 used currently) = 8192 bytes max */
++    for (i = 1; i <= 254; i++) {
++        VmaStreamInfo *si = &vmaw->stream_info[i];
++        if (si->size) {
++            assert(si->devname);
++            uint32_t devname_ptr = allocate_header_string(vmaw, si->devname);
++            if (!devname_ptr) {
++                return -1;
++            }
++            head->dev_info[i].devname_ptr = GUINT32_TO_BE(devname_ptr);
++            head->dev_info[i].size = GUINT64_TO_BE(si->size);
++        }
++    }
++
++    uint32_t header_size = sizeof(VmaHeader) + vmaw->header_blob_table_size;
++    head->header_size = GUINT32_TO_BE(header_size);
++
++    if (header_size > HEADERBUF_SIZE) {
++        return -1; /* just to be sure */
++    }
++
++    uint32_t blob_buffer_offset = sizeof(VmaHeader);
++    memcpy(buf + blob_buffer_offset, vmaw->header_blob_table,
++           vmaw->header_blob_table_size);
++    head->blob_buffer_offset = GUINT32_TO_BE(blob_buffer_offset);
++    head->blob_buffer_size = GUINT32_TO_BE(vmaw->header_blob_table_pos);
++
++    g_checksum_reset(vmaw->md5csum);
++    g_checksum_update(vmaw->md5csum, (const guchar *)buf, header_size);
++    gsize csize = 16;
++    g_checksum_get_digest(vmaw->md5csum, (guint8 *)(head->md5sum), &csize);
++
++    return vma_queue_write(vmaw, buf, header_size);
++}
++
++static int coroutine_fn vma_writer_flush(VmaWriter *vmaw)
++{
++    assert(vmaw);
++
++    int ret;
++    int i;
++
++    if (vmaw->status < 0) {
++        return vmaw->status;
++    }
++
++    if (!vmaw->header_written) {
++        vmaw->header_written = true;
++        ret = vma_write_header(vmaw);
++        if (ret < 0) {
++            vma_writer_set_error(vmaw, "vma_writer_flush: write header failed");
++            return ret;
++        }
++    }
++
++    DPRINTF("VMA WRITE FLUSH %d %d\n", vmaw->outbuf_count, vmaw->outbuf_pos);
++
++
++    VmaExtentHeader *ehead = (VmaExtentHeader *)vmaw->outbuf;
++
++    ehead->magic = VMA_EXTENT_MAGIC;
++    ehead->reserved1 = 0;
++
++    for (i = 0; i < VMA_BLOCKS_PER_EXTENT; i++) {
++        ehead->blockinfo[i] = GUINT64_TO_BE(vmaw->outbuf_block_info[i]);
++    }
++
++    guint16 block_count = (vmaw->outbuf_pos - VMA_EXTENT_HEADER_SIZE) /
++        VMA_BLOCK_SIZE;
++
++    ehead->block_count = GUINT16_TO_BE(block_count);
++
++    memcpy(ehead->uuid, vmaw->uuid, sizeof(ehead->uuid));
++    memset(ehead->md5sum, 0, sizeof(ehead->md5sum));
++
++    g_checksum_reset(vmaw->md5csum);
++    g_checksum_update(vmaw->md5csum, vmaw->outbuf, VMA_EXTENT_HEADER_SIZE);
++    gsize csize = 16;
++    g_checksum_get_digest(vmaw->md5csum, ehead->md5sum, &csize);
++
++    int bytes = vmaw->outbuf_pos;
++    ret = vma_queue_write(vmaw, vmaw->outbuf, bytes);
++    if (ret != bytes) {
++        vma_writer_set_error(vmaw, "vma_writer_flush: failed write");
++    }
++
++    vmaw->outbuf_count = 0;
++    vmaw->outbuf_pos = VMA_EXTENT_HEADER_SIZE;
++
++    for (i = 0; i < VMA_BLOCKS_PER_EXTENT; i++) {
++        vmaw->outbuf_block_info[i] = 0;
++    }
++
++    return vmaw->status;
++}
++
++static int vma_count_open_streams(VmaWriter *vmaw)
++{
++    g_assert(vmaw != NULL);
++
++    int i;
++    int open_drives = 0;
++    for (i = 0; i <= 255; i++) {
++        if (vmaw->stream_info[i].size && !vmaw->stream_info[i].finished) {
++            open_drives++;
++        }
++    }
++
++    return open_drives;
++}
++
++
++/**
++ * You need to call this if the vma archive does not contain
++ * any data stream.
++ */
++int coroutine_fn
++vma_writer_flush_output(VmaWriter *vmaw)
++{
++    qemu_co_mutex_lock(&vmaw->flush_lock);
++    int ret = vma_writer_flush(vmaw);
++    qemu_co_mutex_unlock(&vmaw->flush_lock);
++    if (ret < 0) {
++        vma_writer_set_error(vmaw, "vma_writer_flush_header failed");
++    }
++    return ret;
++}
++
++/**
++ * all jobs should call this when there is no more data
++ * Returns: number of remaining stream (0 ==> finished)
++ */
++int coroutine_fn
++vma_writer_close_stream(VmaWriter *vmaw, uint8_t dev_id)
++{
++    g_assert(vmaw != NULL);
++
++    DPRINTF("vma_writer_set_status %d\n", dev_id);
++    if (!vmaw->stream_info[dev_id].size) {
++        vma_writer_set_error(vmaw, "vma_writer_close_stream: "
++                             "no such stream %d", dev_id);
++        return -1;
++    }
++    if (vmaw->stream_info[dev_id].finished) {
++        vma_writer_set_error(vmaw, "vma_writer_close_stream: "
++                             "stream already closed %d", dev_id);
++        return -1;
++    }
++
++    vmaw->stream_info[dev_id].finished = true;
++
++    int open_drives = vma_count_open_streams(vmaw);
++
++    if (open_drives <= 0) {
++        DPRINTF("vma_writer_set_status all drives completed\n");
++        vma_writer_flush_output(vmaw);
++    }
++
++    return open_drives;
++}
++
++int vma_writer_get_status(VmaWriter *vmaw, VmaStatus *status)
++{
++    int i;
++
++    g_assert(vmaw != NULL);
++
++    if (status) {
++        status->status = vmaw->status;
++        g_strlcpy(status->errmsg, vmaw->errmsg, sizeof(status->errmsg));
++        for (i = 0; i <= 255; i++) {
++            status->stream_info[i] = vmaw->stream_info[i];
++        }
++
++        uuid_unparse_lower(vmaw->uuid, status->uuid_str);
++    }
++
++    status->closed = vmaw->closed;
++
++    return vmaw->status;
++}
++
++static int vma_writer_get_buffer(VmaWriter *vmaw)
++{
++    int ret = 0;
++
++    qemu_co_mutex_lock(&vmaw->flush_lock);
++
++    /* wait until buffer is available */
++    while (vmaw->outbuf_count >= (VMA_BLOCKS_PER_EXTENT - 1)) {
++        ret = vma_writer_flush(vmaw);
++        if (ret < 0) {
++            vma_writer_set_error(vmaw, "vma_writer_get_buffer: flush failed");
++            break;
++        }
++    }
++
++    qemu_co_mutex_unlock(&vmaw->flush_lock);
++
++    return ret;
++}
++
++
++int64_t coroutine_fn
++vma_writer_write(VmaWriter *vmaw, uint8_t dev_id, int64_t cluster_num,
++                 const unsigned char *buf, size_t *zero_bytes)
++{
++    g_assert(vmaw != NULL);
++    g_assert(zero_bytes != NULL);
++
++    *zero_bytes = 0;
++
++    if (vmaw->status < 0) {
++        return vmaw->status;
++    }
++
++    if (!dev_id || !vmaw->stream_info[dev_id].size) {
++        vma_writer_set_error(vmaw, "vma_writer_write: "
++                             "no such stream %d", dev_id);
++        return -1;
++    }
++
++    if (vmaw->stream_info[dev_id].finished) {
++        vma_writer_set_error(vmaw, "vma_writer_write: "
++                             "stream already closed %d", dev_id);
++        return -1;
++    }
++
++
++    if (cluster_num >= (((uint64_t)1)<<32)) {
++        vma_writer_set_error(vmaw, "vma_writer_write: "
++                             "cluster number out of range");
++        return -1;
++    }
++
++    if (dev_id == vmaw->vmstate_stream) {
++        if (cluster_num != vmaw->vmstate_clusters) {
++            vma_writer_set_error(vmaw, "vma_writer_write: "
++                                 "non sequential vmstate write");
++        }
++        vmaw->vmstate_clusters++;
++    } else if (cluster_num >= vmaw->stream_info[dev_id].cluster_count) {
++        vma_writer_set_error(vmaw, "vma_writer_write: cluster number too big");
++        return -1;
++    }
++
++    /* wait until buffer is available */
++    if (vma_writer_get_buffer(vmaw) < 0) {
++        vma_writer_set_error(vmaw, "vma_writer_write: "
++                             "vma_writer_get_buffer failed");
++        return -1;
++    }
++
++    DPRINTF("VMA WRITE %d %zd\n", dev_id, cluster_num);
++
++    uint64_t dev_size = vmaw->stream_info[dev_id].size;
++    uint16_t mask = 0;
++
++    if (buf) {
++        int i;
++        int bit = 1;
++        uint64_t byte_offset = cluster_num * VMA_CLUSTER_SIZE;
++        for (i = 0; i < 16; i++) {
++            const unsigned char *vmablock = buf + (i*VMA_BLOCK_SIZE);
++
++            // Note: If the source is not 64k-aligned, we might reach 4k blocks
++            // after the end of the device. Always mark these as zero in the
++            // mask, so the restore handles them correctly.
++            if (byte_offset < dev_size &&
++                !buffer_is_zero(vmablock, VMA_BLOCK_SIZE))
++            {
++                mask |= bit;
++                memcpy(vmaw->outbuf + vmaw->outbuf_pos, vmablock,
++                       VMA_BLOCK_SIZE);
++
++                // prevent memory leakage on unaligned last block
++                if (byte_offset + VMA_BLOCK_SIZE > dev_size) {
++                    uint64_t real_data_in_block = dev_size - byte_offset;
++                    memset(vmaw->outbuf + vmaw->outbuf_pos + real_data_in_block,
++                           0, VMA_BLOCK_SIZE - real_data_in_block);
++                }
++
++                vmaw->outbuf_pos += VMA_BLOCK_SIZE;
++            } else {
++                DPRINTF("VMA WRITE %zd ZERO BLOCK %d\n", cluster_num, i);
++                vmaw->stream_info[dev_id].zero_bytes += VMA_BLOCK_SIZE;
++                *zero_bytes += VMA_BLOCK_SIZE;
++            }
++
++            byte_offset += VMA_BLOCK_SIZE;
++            bit = bit << 1;
++        }
++    } else {
++        DPRINTF("VMA WRITE %zd ZERO CLUSTER\n", cluster_num);
++        vmaw->stream_info[dev_id].zero_bytes += VMA_CLUSTER_SIZE;
++        *zero_bytes += VMA_CLUSTER_SIZE;
++    }
++
++    uint64_t block_info = ((uint64_t)mask) << (32+16);
++    block_info |= ((uint64_t)dev_id) << 32;
++    block_info |= (cluster_num & 0xffffffff);
++    vmaw->outbuf_block_info[vmaw->outbuf_count] = block_info;
++
++    DPRINTF("VMA WRITE MASK %zd %zx\n", cluster_num, block_info);
++
++    vmaw->outbuf_count++;
++
++    /** NOTE: We allways write whole clusters, but we correctly set
++     * transferred bytes. So transferred == size when when everything
++     * went OK.
++     */
++    size_t transferred = VMA_CLUSTER_SIZE;
++
++    if (dev_id != vmaw->vmstate_stream) {
++        uint64_t last = (cluster_num + 1) * VMA_CLUSTER_SIZE;
++        if (last > dev_size) {
++            uint64_t diff = last - dev_size;
++            if (diff >= VMA_CLUSTER_SIZE) {
++                vma_writer_set_error(vmaw, "vma_writer_write: "
++                                     "read after last cluster");
++                return -1;
++            }
++            transferred -= diff;
++        }
++    }
++
++    vmaw->stream_info[dev_id].transferred += transferred;
++
++    return transferred;
++}
++
++void vma_writer_error_propagate(VmaWriter *vmaw, Error **errp)
++{
++    if (vmaw->status < 0 && *errp == NULL) {
++        error_setg(errp, "%s", vmaw->errmsg);
++    }
++}
++
++int vma_writer_close(VmaWriter *vmaw, Error **errp)
++{
++    g_assert(vmaw != NULL);
++
++    int i;
++
++    qemu_co_mutex_lock(&vmaw->flush_lock); // wait for pending writes
++
++    assert(vmaw->co_writer == NULL);
++
++    if (vmaw->cmd) {
++        if (pclose(vmaw->cmd) < 0) {
++            vma_writer_set_error(vmaw, "vma_writer_close: "
++                                 "pclose failed - %s", g_strerror(errno));
++        }
++    } else {
++        if (close(vmaw->fd) < 0) {
++            vma_writer_set_error(vmaw, "vma_writer_close: "
++                                 "close failed - %s", g_strerror(errno));
++        }
++    }
++
++    for (i = 0; i <= 255; i++) {
++        VmaStreamInfo *si = &vmaw->stream_info[i];
++        if (si->size) {
++            if (!si->finished) {
++                vma_writer_set_error(vmaw, "vma_writer_close: "
++                                     "detected open stream '%s'", si->devname);
++            } else if ((si->transferred != si->size) &&
++                       (i != vmaw->vmstate_stream)) {
++                vma_writer_set_error(vmaw, "vma_writer_close: "
++                                     "incomplete stream '%s' (%zd != %zd)",
++                                     si->devname, si->transferred, si->size);
++            }
++        }
++    }
++
++    for (i = 0; i <= 255; i++) {
++        vmaw->stream_info[i].finished = 1; /* mark as closed */
++    }
++
++    vmaw->closed = 1;
++
++    if (vmaw->status < 0 && *errp == NULL) {
++        error_setg(errp, "%s", vmaw->errmsg);
++    }
++
++    qemu_co_mutex_unlock(&vmaw->flush_lock);
++
++    return vmaw->status;
++}
++
++void vma_writer_destroy(VmaWriter *vmaw)
++{
++    assert(vmaw);
++
++    int i;
++
++    for (i = 0; i <= 255; i++) {
++        if (vmaw->stream_info[i].devname) {
++            g_free(vmaw->stream_info[i].devname);
++        }
++    }
++
++    if (vmaw->md5csum) {
++        g_checksum_free(vmaw->md5csum);
++    }
++
++    qemu_vfree(vmaw->headerbuf);
++    qemu_vfree(vmaw->outbuf);
++    g_free(vmaw);
++}
+diff --git a/vma.c b/vma.c
+new file mode 100644
+index 0000000000..df542b7732
+--- /dev/null
++++ b/vma.c
+@@ -0,0 +1,851 @@
++/*
++ * VMA: Virtual Machine Archive
++ *
++ * Copyright (C) 2012-2013 Proxmox Server Solutions
++ *
++ * Authors:
++ *  Dietmar Maurer (dietmar@proxmox.com)
++ *
++ * This work is licensed under the terms of the GNU GPL, version 2 or later.
++ * See the COPYING file in the top-level directory.
++ *
++ */
++
++#include "qemu/osdep.h"
++#include <glib.h>
++
++#include "vma.h"
++#include "qemu-common.h"
++#include "qemu/module.h"
++#include "qemu/error-report.h"
++#include "qemu/main-loop.h"
++#include "qemu/cutils.h"
++#include "qapi/qmp/qdict.h"
++#include "sysemu/block-backend.h"
++
++static void help(void)
++{
++    const char *help_msg =
++        "usage: vma command [command options]\n"
++        "\n"
++        "vma list <filename>\n"
++        "vma config <filename> [-c config]\n"
++        "vma create <filename> [-c config] pathname ...\n"
++        "vma extract <filename> [-r <fifo>] <targetdir>\n"
++        "vma verify <filename> [-v]\n"
++        ;
++
++    printf("%s", help_msg);
++    exit(1);
++}
++
++static const char *extract_devname(const char *path, char **devname, int index)
++{
++    assert(path);
++
++    const char *sep = strchr(path, '=');
++
++    if (sep) {
++        *devname = g_strndup(path, sep - path);
++        path = sep + 1;
++    } else {
++        if (index >= 0) {
++            *devname = g_strdup_printf("disk%d", index);
++        } else {
++            *devname = NULL;
++        }
++    }
++
++    return path;
++}
++
++static void print_content(VmaReader *vmar)
++{
++    assert(vmar);
++
++    VmaHeader *head = vma_reader_get_header(vmar);
++
++    GList *l = vma_reader_get_config_data(vmar);
++    while (l && l->data) {
++        VmaConfigData *cdata = (VmaConfigData *)l->data;
++        l = g_list_next(l);
++        printf("CFG: size: %d name: %s\n", cdata->len, cdata->name);
++    }
++
++    int i;
++    VmaDeviceInfo *di;
++    for (i = 1; i < 255; i++) {
++        di = vma_reader_get_device_info(vmar, i);
++        if (di) {
++            if (strcmp(di->devname, "vmstate") == 0) {
++                printf("VMSTATE: dev_id=%d memory: %zd\n", i, di->size);
++            } else {
++                printf("DEV: dev_id=%d size: %zd devname: %s\n",
++                       i, di->size, di->devname);
++            }
++        }
++    }
++    /* ctime is the last entry we print */
++    printf("CTIME: %s", ctime(&head->ctime));
++    fflush(stdout);
++}
++
++static int list_content(int argc, char **argv)
++{
++    int c, ret = 0;
++    const char *filename;
++
++    for (;;) {
++        c = getopt(argc, argv, "h");
++        if (c == -1) {
++            break;
++        }
++        switch (c) {
++        case '?':
++        case 'h':
++            help();
++            break;
++        default:
++            g_assert_not_reached();
++        }
++    }
++
++    /* Get the filename */
++    if ((optind + 1) != argc) {
++        help();
++    }
++    filename = argv[optind++];
++
++    Error *errp = NULL;
++    VmaReader *vmar = vma_reader_create(filename, &errp);
++
++    if (!vmar) {
++        g_error("%s", error_get_pretty(errp));
++    }
++
++    print_content(vmar);
++
++    vma_reader_destroy(vmar);
++
++    return ret;
++}
++
++typedef struct RestoreMap {
++    char *devname;
++    char *path;
++    char *format;
++    uint64_t throttling_bps;
++    char *throttling_group;
++    char *cache;
++    bool write_zero;
++} RestoreMap;
++
++static bool try_parse_option(char **line, const char *optname, char **out, const char *inbuf) {
++    size_t optlen = strlen(optname);
++    if (strncmp(*line, optname, optlen) != 0 || (*line)[optlen] != '=') {
++        return false;
++    }
++    if (*out) {
++        g_error("read map failed - duplicate value for option '%s'", optname);
++    }
++    char *value = (*line) + optlen + 1; /* including a '=' */
++    char *colon = strchr(value, ':');
++    if (!colon) {
++        g_error("read map failed - option '%s' not terminated ('%s')",
++                optname, inbuf);
++    }
++    *line = colon+1;
++    *out = g_strndup(value, colon - value);
++    return true;
++}
++
++static uint64_t verify_u64(const char *text) {
++    uint64_t value;
++    const char *endptr = NULL;
++    if (qemu_strtou64(text, &endptr, 0, &value) != 0 || !endptr || *endptr) {
++        g_error("read map failed - not a number: %s", text);
++    }
++    return value;
++}
++
++static int extract_content(int argc, char **argv)
++{
++    int c, ret = 0;
++    int verbose = 0;
++    const char *filename;
++    const char *dirname;
++    const char *readmap = NULL;
++
++    for (;;) {
++        c = getopt(argc, argv, "hvr:");
++        if (c == -1) {
++            break;
++        }
++        switch (c) {
++        case '?':
++        case 'h':
++            help();
++            break;
++        case 'r':
++            readmap = optarg;
++            break;
++        case 'v':
++            verbose = 1;
++            break;
++        default:
++            help();
++        }
++    }
++
++    /* Get the filename */
++    if ((optind + 2) != argc) {
++        help();
++    }
++    filename = argv[optind++];
++    dirname = argv[optind++];
++
++    Error *errp = NULL;
++    VmaReader *vmar = vma_reader_create(filename, &errp);
++
++    if (!vmar) {
++        g_error("%s", error_get_pretty(errp));
++    }
++
++    if (mkdir(dirname, 0777) < 0) {
++        g_error("unable to create target directory %s - %s",
++                dirname, g_strerror(errno));
++    }
++
++    GList *l = vma_reader_get_config_data(vmar);
++    while (l && l->data) {
++        VmaConfigData *cdata = (VmaConfigData *)l->data;
++        l = g_list_next(l);
++        char *cfgfn = g_strdup_printf("%s/%s", dirname, cdata->name);
++        GError *err = NULL;
++        if (!g_file_set_contents(cfgfn, (gchar *)cdata->data, cdata->len,
++                                 &err)) {
++            g_error("unable to write file: %s", err->message);
++        }
++    }
++
++    GHashTable *devmap = g_hash_table_new(g_str_hash, g_str_equal);
++
++    if (readmap) {
++        print_content(vmar);
++
++        FILE *map = fopen(readmap, "r");
++        if (!map) {
++            g_error("unable to open fifo %s - %s", readmap, g_strerror(errno));
++        }
++
++        while (1) {
++            char inbuf[8192];
++            char *line = fgets(inbuf, sizeof(inbuf), map);
++            char *format = NULL;
++            char *bps = NULL;
++            char *group = NULL;
++            char *cache = NULL;
++            if (!line || line[0] == '\0' || !strcmp(line, "done\n")) {
++                break;
++            }
++            int len = strlen(line);
++            if (line[len - 1] == '\n') {
++                line[len - 1] = '\0';
++                if (len == 1) {
++                    break;
++                }
++            }
++
++            while (1) {
++                if (!try_parse_option(&line, "format", &format, inbuf) &&
++                    !try_parse_option(&line, "throttling.bps", &bps, inbuf) &&
++                    !try_parse_option(&line, "throttling.group", &group, inbuf) &&
++                    !try_parse_option(&line, "cache", &cache, inbuf))
++                {
++                    break;
++                }
++            }
++
++            uint64_t bps_value = 0;
++            if (bps) {
++                bps_value = verify_u64(bps);
++                g_free(bps);
++            }
++
++            const char *path;
++            bool write_zero;
++            if (line[0] == '0' && line[1] == ':') {
++                path = line + 2;
++                write_zero = false;
++            } else if (line[0] == '1' && line[1] == ':') {
++                path = line + 2;
++                write_zero = true;
++            } else {
++                g_error("read map failed - parse error ('%s')", inbuf);
++            }
++
++            char *devname = NULL;
++            path = extract_devname(path, &devname, -1);
++            if (!devname) {
++                g_error("read map failed - no dev name specified ('%s')",
++                        inbuf);
++            }
++
++            RestoreMap *map = g_new0(RestoreMap, 1);
++            map->devname = g_strdup(devname);
++            map->path = g_strdup(path);
++            map->format = format;
++            map->throttling_bps = bps_value;
++            map->throttling_group = group;
++            map->cache = cache;
++            map->write_zero = write_zero;
++
++            g_hash_table_insert(devmap, map->devname, map);
++
++        };
++    }
++
++    int i;
++    int vmstate_fd = -1;
++    guint8 vmstate_stream = 0;
++
++    BlockBackend *blk = NULL;
++
++    for (i = 1; i < 255; i++) {
++        VmaDeviceInfo *di = vma_reader_get_device_info(vmar, i);
++        if (di && (strcmp(di->devname, "vmstate") == 0)) {
++            vmstate_stream = i;
++            char *statefn = g_strdup_printf("%s/vmstate.bin", dirname);
++            vmstate_fd = open(statefn, O_WRONLY|O_CREAT|O_EXCL, 0644);
++            if (vmstate_fd < 0) {
++                g_error("create vmstate file '%s' failed - %s", statefn,
++                        g_strerror(errno));
++            }
++            g_free(statefn);
++        } else if (di) {
++            char *devfn = NULL;
++            const char *format = NULL;
++            uint64_t throttling_bps = 0;
++            const char *throttling_group = NULL;
++            const char *cache = NULL;
++            int flags = BDRV_O_RDWR;
++            bool write_zero = true;
++
++            if (readmap) {
++                RestoreMap *map;
++                map = (RestoreMap *)g_hash_table_lookup(devmap, di->devname);
++                if (map == NULL) {
++                    g_error("no device name mapping for %s", di->devname);
++                }
++                devfn = map->path;
++                format = map->format;
++                throttling_bps = map->throttling_bps;
++                throttling_group = map->throttling_group;
++                cache = map->cache;
++                write_zero = map->write_zero;
++            } else {
++                devfn = g_strdup_printf("%s/tmp-disk-%s.raw",
++                                        dirname, di->devname);
++                printf("DEVINFO %s %zd\n", devfn, di->size);
++
++                bdrv_img_create(devfn, "raw", NULL, NULL, NULL, di->size,
++                                flags, true, &errp);
++                if (errp) {
++                    g_error("can't create file %s: %s", devfn,
++                            error_get_pretty(errp));
++                }
++
++                /* Note: we created an empty file above, so there is no
++                 * need to write zeroes (so we generate a sparse file)
++                 */
++                write_zero = false;
++            }
++
++          size_t devlen = strlen(devfn);
++          QDict *options = NULL;
++            bool writethrough;
++            if (format) {
++                /* explicit format from commandline */
++                options = qdict_new();
++                qdict_put_str(options, "driver", format);
++            } else if ((devlen > 4 && strcmp(devfn+devlen-4, ".raw") == 0) ||
++                     strncmp(devfn, "/dev/", 5) == 0)
++          {
++                /* This part is now deprecated for PVE as well (just as qemu
++                 * deprecated not specifying an explicit raw format, too.
++                 */
++              /* explicit raw format */
++              options = qdict_new();
++              qdict_put_str(options, "driver", "raw");
++          }
++            if (cache && bdrv_parse_cache_mode(cache, &flags, &writethrough)) {
++                g_error("invalid cache option: %s\n", cache);
++            }
++
++          if (errp || !(blk = blk_new_open(devfn, NULL, options, flags, &errp))) {
++                g_error("can't open file %s - %s", devfn,
++                        error_get_pretty(errp));
++            }
++
++            if (cache) {
++                blk_set_enable_write_cache(blk, !writethrough);
++            }
++
++            if (throttling_group) {
++                blk_io_limits_enable(blk, throttling_group);
++            }
++
++            if (throttling_bps) {
++                if (!throttling_group) {
++                    blk_io_limits_enable(blk, devfn);
++                }
++
++                ThrottleConfig cfg;
++                throttle_config_init(&cfg);
++                cfg.buckets[THROTTLE_BPS_WRITE].avg = throttling_bps;
++                Error *err = NULL;
++                if (!throttle_is_valid(&cfg, &err)) {
++                    error_report_err(err);
++                    g_error("failed to apply throttling");
++                }
++                blk_set_io_limits(blk, &cfg);
++            }
++
++            if (vma_reader_register_bs(vmar, i, blk, write_zero, &errp) < 0) {
++                g_error("%s", error_get_pretty(errp));
++            }
++
++            if (!readmap) {
++                g_free(devfn);
++            }
++        }
++    }
++
++    if (vma_reader_restore(vmar, vmstate_fd, verbose, &errp) < 0) {
++        g_error("restore failed - %s", error_get_pretty(errp));
++    }
++
++    if (!readmap) {
++        for (i = 1; i < 255; i++) {
++            VmaDeviceInfo *di = vma_reader_get_device_info(vmar, i);
++            if (di && (i != vmstate_stream)) {
++                char *tmpfn = g_strdup_printf("%s/tmp-disk-%s.raw",
++                                              dirname, di->devname);
++                char *fn = g_strdup_printf("%s/disk-%s.raw",
++                                           dirname, di->devname);
++                if (rename(tmpfn, fn) != 0) {
++                    g_error("rename %s to %s failed - %s",
++                            tmpfn, fn, g_strerror(errno));
++                }
++            }
++        }
++    }
++
++    vma_reader_destroy(vmar);
++
++    blk_unref(blk);
++
++    bdrv_close_all();
++
++    return ret;
++}
++
++static int verify_content(int argc, char **argv)
++{
++    int c, ret = 0;
++    int verbose = 0;
++    const char *filename;
++
++    for (;;) {
++        c = getopt(argc, argv, "hv");
++        if (c == -1) {
++            break;
++        }
++        switch (c) {
++        case '?':
++        case 'h':
++            help();
++            break;
++        case 'v':
++            verbose = 1;
++            break;
++        default:
++            help();
++        }
++    }
++
++    /* Get the filename */
++    if ((optind + 1) != argc) {
++        help();
++    }
++    filename = argv[optind++];
++
++    Error *errp = NULL;
++    VmaReader *vmar = vma_reader_create(filename, &errp);
++
++    if (!vmar) {
++        g_error("%s", error_get_pretty(errp));
++    }
++
++    if (verbose) {
++        print_content(vmar);
++    }
++
++    if (vma_reader_verify(vmar, verbose, &errp) < 0) {
++        g_error("verify failed - %s", error_get_pretty(errp));
++    }
++
++    vma_reader_destroy(vmar);
++
++    bdrv_close_all();
++
++    return ret;
++}
++
++typedef struct BackupJob {
++    BlockBackend *target;
++    int64_t len;
++    VmaWriter *vmaw;
++    uint8_t dev_id;
++} BackupJob;
++
++#define BACKUP_SECTORS_PER_CLUSTER (VMA_CLUSTER_SIZE / BDRV_SECTOR_SIZE)
++
++static void coroutine_fn backup_run_empty(void *opaque)
++{
++    VmaWriter *vmaw = (VmaWriter *)opaque;
++
++    vma_writer_flush_output(vmaw);
++
++    Error *err = NULL;
++    if (vma_writer_close(vmaw, &err) != 0) {
++        g_warning("vma_writer_close failed %s", error_get_pretty(err));
++    }
++}
++
++static void coroutine_fn backup_run(void *opaque)
++{
++    BackupJob *job = (BackupJob *)opaque;
++    struct iovec iov;
++    QEMUIOVector qiov;
++
++    int64_t start, end;
++    int ret = 0;
++
++    unsigned char *buf = blk_blockalign(job->target, VMA_CLUSTER_SIZE);
++
++    start = 0;
++    end = DIV_ROUND_UP(job->len / BDRV_SECTOR_SIZE,
++                       BACKUP_SECTORS_PER_CLUSTER);
++
++    for (; start < end; start++) {
++        iov.iov_base = buf;
++        iov.iov_len = VMA_CLUSTER_SIZE;
++        qemu_iovec_init_external(&qiov, &iov, 1);
++
++        ret = blk_co_preadv(job->target, start * VMA_CLUSTER_SIZE,
++                            VMA_CLUSTER_SIZE, &qiov, 0);
++        if (ret < 0) {
++            vma_writer_set_error(job->vmaw, "read error", -1);
++            goto out;
++        }
++
++        size_t zb = 0;
++        if (vma_writer_write(job->vmaw, job->dev_id, start, buf, &zb) < 0) {
++            vma_writer_set_error(job->vmaw, "backup_dump_cb vma_writer_write failed", -1);
++            goto out;
++        }
++    }
++
++
++out:
++    if (vma_writer_close_stream(job->vmaw, job->dev_id) <= 0) {
++        Error *err = NULL;
++        if (vma_writer_close(job->vmaw, &err) != 0) {
++            g_warning("vma_writer_close failed %s", error_get_pretty(err));
++        }
++    }
++    qemu_vfree(buf);
++}
++
++static int create_archive(int argc, char **argv)
++{
++    int i, c;
++    int verbose = 0;
++    const char *archivename;
++    GList *backup_coroutines = NULL;
++    GList *config_files = NULL;
++
++    for (;;) {
++        c = getopt(argc, argv, "hvc:");
++        if (c == -1) {
++            break;
++        }
++        switch (c) {
++        case '?':
++        case 'h':
++            help();
++            break;
++        case 'c':
++            config_files = g_list_append(config_files, optarg);
++            break;
++        case 'v':
++            verbose = 1;
++            break;
++        default:
++            g_assert_not_reached();
++        }
++    }
++
++
++    /* make sure we an archive name */
++    if ((optind + 1) > argc) {
++        help();
++    }
++
++    archivename = argv[optind++];
++
++    uuid_t uuid;
++    uuid_generate(uuid);
++
++    Error *local_err = NULL;
++    VmaWriter *vmaw = vma_writer_create(archivename, uuid, &local_err);
++
++    if (vmaw == NULL) {
++        g_error("%s", error_get_pretty(local_err));
++    }
++
++    GList *l = config_files;
++    while (l && l->data) {
++        char *name = l->data;
++        char *cdata = NULL;
++        gsize clen = 0;
++        GError *err = NULL;
++        if (!g_file_get_contents(name, &cdata, &clen, &err)) {
++            unlink(archivename);
++            g_error("Unable to read file: %s", err->message);
++        }
++
++        if (vma_writer_add_config(vmaw, name, cdata, clen) != 0) {
++            unlink(archivename);
++            g_error("Unable to append config data %s (len = %zd)",
++                    name, clen);
++        }
++        l = g_list_next(l);
++    }
++
++    int devcount = 0;
++    while (optind < argc) {
++        const char *path = argv[optind++];
++        char *devname = NULL;
++        path = extract_devname(path, &devname, devcount++);
++
++        Error *errp = NULL;
++        BlockBackend *target;
++
++        target = blk_new_open(path, NULL, NULL, 0, &errp);
++        if (!target) {
++            unlink(archivename);
++            g_error("bdrv_open '%s' failed - %s", path, error_get_pretty(errp));
++        }
++        int64_t size = blk_getlength(target);
++        int dev_id = vma_writer_register_stream(vmaw, devname, size);
++        if (dev_id <= 0) {
++            unlink(archivename);
++            g_error("vma_writer_register_stream '%s' failed", devname);
++        }
++
++        BackupJob *job = g_new0(BackupJob, 1);
++        job->len = size;
++        job->target = target;
++        job->vmaw = vmaw;
++        job->dev_id = dev_id;
++
++        Coroutine *co = qemu_coroutine_create(backup_run, job);
++        // Don't enter coroutine yet, because it might write the header before
++        // all streams can be registered.
++        backup_coroutines = g_list_append(backup_coroutines, co);
++    }
++
++    VmaStatus vmastat;
++    int percent = 0;
++    int last_percent = -1;
++
++    if (devcount) {
++        GList *entry = backup_coroutines;
++        while (entry && entry->data) {
++            Coroutine *co = entry->data;
++            qemu_coroutine_enter(co);
++            entry = g_list_next(entry);
++        }
++
++        while (1) {
++            main_loop_wait(false);
++            vma_writer_get_status(vmaw, &vmastat);
++
++            if (verbose) {
++
++                uint64_t total = 0;
++                uint64_t transferred = 0;
++                uint64_t zero_bytes = 0;
++
++                int i;
++                for (i = 0; i < 256; i++) {
++                    if (vmastat.stream_info[i].size) {
++                        total += vmastat.stream_info[i].size;
++                        transferred += vmastat.stream_info[i].transferred;
++                        zero_bytes += vmastat.stream_info[i].zero_bytes;
++                    }
++                }
++                percent = (transferred*100)/total;
++                if (percent != last_percent) {
++                    fprintf(stderr, "progress %d%% %zd/%zd %zd\n", percent,
++                            transferred, total, zero_bytes);
++                    fflush(stderr);
++
++                    last_percent = percent;
++                }
++            }
++
++            if (vmastat.closed) {
++                break;
++            }
++        }
++    } else {
++        Coroutine *co = qemu_coroutine_create(backup_run_empty, vmaw);
++        qemu_coroutine_enter(co);
++        while (1) {
++            main_loop_wait(false);
++            vma_writer_get_status(vmaw, &vmastat);
++            if (vmastat.closed) {
++                    break;
++            }
++        }
++    }
++
++    bdrv_drain_all();
++
++    vma_writer_get_status(vmaw, &vmastat);
++
++    if (verbose) {
++        for (i = 0; i < 256; i++) {
++            VmaStreamInfo *si = &vmastat.stream_info[i];
++            if (si->size) {
++                fprintf(stderr, "image %s: size=%zd zeros=%zd saved=%zd\n",
++                        si->devname, si->size, si->zero_bytes,
++                        si->size - si->zero_bytes);
++            }
++        }
++    }
++
++    if (vmastat.status < 0) {
++        unlink(archivename);
++        g_error("creating vma archive failed");
++    }
++
++    g_list_free(backup_coroutines);
++    g_list_free(config_files);
++    vma_writer_destroy(vmaw);
++    return 0;
++}
++
++static int dump_config(int argc, char **argv)
++{
++    int c, ret = 0;
++    const char *filename;
++    const char *config_name = "qemu-server.conf";
++
++    for (;;) {
++        c = getopt(argc, argv, "hc:");
++        if (c == -1) {
++            break;
++        }
++        switch (c) {
++        case '?':
++        case 'h':
++            help();
++            break;
++        case 'c':
++            config_name = optarg;
++            break;
++        default:
++            help();
++        }
++    }
++
++    /* Get the filename */
++    if ((optind + 1) != argc) {
++        help();
++    }
++    filename = argv[optind++];
++
++    Error *errp = NULL;
++    VmaReader *vmar = vma_reader_create(filename, &errp);
++
++    if (!vmar) {
++        g_error("%s", error_get_pretty(errp));
++    }
++
++    int found = 0;
++    GList *l = vma_reader_get_config_data(vmar);
++    while (l && l->data) {
++        VmaConfigData *cdata = (VmaConfigData *)l->data;
++        l = g_list_next(l);
++        if (strcmp(cdata->name, config_name) == 0) {
++            found = 1;
++            fwrite(cdata->data,  cdata->len, 1, stdout);
++            break;
++        }
++    }
++
++    vma_reader_destroy(vmar);
++
++    bdrv_close_all();
++
++    if (!found) {
++        fprintf(stderr, "unable to find configuration data '%s'\n", config_name);
++        return -1;
++    }
++
++    return ret;
++}
++
++int main(int argc, char **argv)
++{
++    const char *cmdname;
++    Error *main_loop_err = NULL;
++
++    error_init(argv[0]);
++    module_call_init(MODULE_INIT_TRACE);
++    qemu_init_exec_dir(argv[0]);
++
++    if (qemu_init_main_loop(&main_loop_err)) {
++        g_error("%s", error_get_pretty(main_loop_err));
++    }
++
++    bdrv_init();
++    module_call_init(MODULE_INIT_QOM);
++
++    if (argc < 2) {
++        help();
++    }
++
++    cmdname = argv[1];
++    argc--; argv++;
++
++
++    if (!strcmp(cmdname, "list")) {
++        return list_content(argc, argv);
++    } else if (!strcmp(cmdname, "create")) {
++        return create_archive(argc, argv);
++    } else if (!strcmp(cmdname, "extract")) {
++        return extract_content(argc, argv);
++    } else if (!strcmp(cmdname, "verify")) {
++        return verify_content(argc, argv);
++    } else if (!strcmp(cmdname, "config")) {
++        return dump_config(argc, argv);
++    }
++
++    help();
++    return 0;
++}
+diff --git a/vma.h b/vma.h
+new file mode 100644
+index 0000000000..c895c97f6d
+--- /dev/null
++++ b/vma.h
+@@ -0,0 +1,150 @@
++/*
++ * VMA: Virtual Machine Archive
++ *
++ * Copyright (C) Proxmox Server Solutions
++ *
++ * Authors:
++ *  Dietmar Maurer (dietmar@proxmox.com)
++ *
++ * This work is licensed under the terms of the GNU GPL, version 2 or later.
++ * See the COPYING file in the top-level directory.
++ *
++ */
++
++#ifndef BACKUP_VMA_H
++#define BACKUP_VMA_H
++
++#include <uuid/uuid.h>
++#include "qapi/error.h"
++#include "block/block.h"
++
++#define VMA_BLOCK_BITS 12
++#define VMA_BLOCK_SIZE (1<<VMA_BLOCK_BITS)
++#define VMA_CLUSTER_BITS (VMA_BLOCK_BITS+4)
++#define VMA_CLUSTER_SIZE (1<<VMA_CLUSTER_BITS)
++
++#if VMA_CLUSTER_SIZE != 65536
++#error unexpected cluster size
++#endif
++
++#define VMA_EXTENT_HEADER_SIZE 512
++#define VMA_BLOCKS_PER_EXTENT 59
++#define VMA_MAX_CONFIGS 256
++
++#define VMA_MAX_EXTENT_SIZE \
++    (VMA_EXTENT_HEADER_SIZE+VMA_CLUSTER_SIZE*VMA_BLOCKS_PER_EXTENT)
++#if VMA_MAX_EXTENT_SIZE != 3867136
++#error unexpected VMA_EXTENT_SIZE
++#endif
++
++/* File Format Definitions */
++
++#define VMA_MAGIC (GUINT32_TO_BE(('V'<<24)|('M'<<16)|('A'<<8)|0x00))
++#define VMA_EXTENT_MAGIC (GUINT32_TO_BE(('V'<<24)|('M'<<16)|('A'<<8)|'E'))
++
++typedef struct VmaDeviceInfoHeader {
++    uint32_t devname_ptr; /* offset into blob_buffer table */
++    uint32_t reserved0;
++    uint64_t size; /* device size in bytes */
++    uint64_t reserved1;
++    uint64_t reserved2;
++} VmaDeviceInfoHeader;
++
++typedef struct VmaHeader {
++    uint32_t magic;
++    uint32_t version;
++    unsigned char uuid[16];
++    int64_t ctime;
++    unsigned char md5sum[16];
++
++    uint32_t blob_buffer_offset;
++    uint32_t blob_buffer_size;
++    uint32_t header_size;
++
++    unsigned char reserved[1984];
++
++    uint32_t config_names[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
++    uint32_t config_data[VMA_MAX_CONFIGS];  /* offset into blob_buffer table */
++
++    uint32_t reserved1;
++
++    VmaDeviceInfoHeader dev_info[256];
++} VmaHeader;
++
++typedef struct VmaExtentHeader {
++    uint32_t magic;
++    uint16_t reserved1;
++    uint16_t block_count;
++    unsigned char uuid[16];
++    unsigned char md5sum[16];
++    uint64_t blockinfo[VMA_BLOCKS_PER_EXTENT];
++} VmaExtentHeader;
++
++/* functions/definitions to read/write vma files */
++
++typedef struct VmaReader VmaReader;
++
++typedef struct VmaWriter VmaWriter;
++
++typedef struct VmaConfigData {
++    const char *name;
++    const void *data;
++    uint32_t len;
++} VmaConfigData;
++
++typedef struct VmaStreamInfo {
++    uint64_t size;
++    uint64_t cluster_count;
++    uint64_t transferred;
++    uint64_t zero_bytes;
++    int finished;
++    char *devname;
++} VmaStreamInfo;
++
++typedef struct VmaStatus {
++    int status;
++    bool closed;
++    char errmsg[8192];
++    char uuid_str[37];
++    VmaStreamInfo stream_info[256];
++} VmaStatus;
++
++typedef struct VmaDeviceInfo {
++    uint64_t size; /* device size in bytes */
++    const char *devname;
++} VmaDeviceInfo;
++
++VmaWriter *vma_writer_create(const char *filename, uuid_t uuid, Error **errp);
++int vma_writer_close(VmaWriter *vmaw, Error **errp);
++void vma_writer_error_propagate(VmaWriter *vmaw, Error **errp);
++void vma_writer_destroy(VmaWriter *vmaw);
++int vma_writer_add_config(VmaWriter *vmaw, const char *name, gpointer data,
++                          size_t len);
++int vma_writer_register_stream(VmaWriter *vmaw, const char *devname,
++                               size_t size);
++
++int64_t coroutine_fn vma_writer_write(VmaWriter *vmaw, uint8_t dev_id,
++                                      int64_t cluster_num,
++                                      const unsigned char *buf,
++                                      size_t *zero_bytes);
++
++int coroutine_fn vma_writer_close_stream(VmaWriter *vmaw, uint8_t dev_id);
++int coroutine_fn vma_writer_flush_output(VmaWriter *vmaw);
++
++int vma_writer_get_status(VmaWriter *vmaw, VmaStatus *status);
++void vma_writer_set_error(VmaWriter *vmaw, const char *fmt, ...);
++
++
++VmaReader *vma_reader_create(const char *filename, Error **errp);
++void vma_reader_destroy(VmaReader *vmar);
++VmaHeader *vma_reader_get_header(VmaReader *vmar);
++GList *vma_reader_get_config_data(VmaReader *vmar);
++VmaDeviceInfo *vma_reader_get_device_info(VmaReader *vmar, guint8 dev_id);
++int vma_reader_register_bs(VmaReader *vmar, guint8 dev_id,
++                           BlockBackend *target, bool write_zeroes,
++                           Error **errp);
++int vma_reader_restore(VmaReader *vmar, int vmstate_fd, bool verbose,
++                       Error **errp);
++int vma_reader_verify(VmaReader *vmar, bool verbose, Error **errp);
++
++#endif /* BACKUP_VMA_H */
diff --git a/debian/patches/pve/0027-PVE-Backup-add-backup-dump-block-driver.patch b/debian/patches/pve/0027-PVE-Backup-add-backup-dump-block-driver.patch
new file mode 100644 (file)
index 0000000..f2d6f87
--- /dev/null
@@ -0,0 +1,321 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Dietmar Maurer <dietmar@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:58 +0200
+Subject: [PATCH] PVE-Backup: add backup-dump block driver
+
+- add backup-dump block driver block/backup-dump.c
+- move BackupBlockJob declaration from block/backup.c to include/block/block_int.h
+- block/backup.c - backup-job-create: also consider source cluster size
+- job.c: make job_should_pause non-static
+
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ block/backup-dump.c       | 168 ++++++++++++++++++++++++++++++++++++++
+ block/backup.c            |  30 ++-----
+ block/meson.build         |   1 +
+ include/block/block_int.h |  35 ++++++++
+ job.c                     |   3 +-
+ 5 files changed, 214 insertions(+), 23 deletions(-)
+ create mode 100644 block/backup-dump.c
+
+diff --git a/block/backup-dump.c b/block/backup-dump.c
+new file mode 100644
+index 0000000000..93d7f46950
+--- /dev/null
++++ b/block/backup-dump.c
+@@ -0,0 +1,168 @@
++/*
++ * BlockDriver to send backup data stream to a callback function
++ *
++ * Copyright (C) 2020 Proxmox Server Solutions GmbH
++ *
++ * This work is licensed under the terms of the GNU GPL, version 2 or later.
++ * See the COPYING file in the top-level directory.
++ *
++ */
++
++#include "qemu/osdep.h"
++#include "qemu-common.h"
++#include "qom/object_interfaces.h"
++#include "block/block_int.h"
++
++typedef struct {
++    int             dump_cb_block_size;
++    uint64_t        byte_size;
++    BackupDumpFunc *dump_cb;
++    void           *dump_cb_data;
++} BDRVBackupDumpState;
++
++static int qemu_backup_dump_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
++{
++    BDRVBackupDumpState *s = bs->opaque;
++
++    bdi->cluster_size = s->dump_cb_block_size;
++    return 0;
++}
++
++static int qemu_backup_dump_check_perm(
++    BlockDriverState *bs,
++    uint64_t perm,
++    uint64_t shared,
++    Error **errp)
++{
++    /* Nothing to do. */
++    return 0;
++}
++
++static void qemu_backup_dump_set_perm(
++    BlockDriverState *bs,
++    uint64_t perm,
++    uint64_t shared)
++{
++    /* Nothing to do. */
++}
++
++static void qemu_backup_dump_abort_perm_update(BlockDriverState *bs)
++{
++    /* Nothing to do. */
++}
++
++static void qemu_backup_dump_refresh_limits(BlockDriverState *bs, Error **errp)
++{
++    bs->bl.request_alignment = BDRV_SECTOR_SIZE; /* No sub-sector I/O */
++}
++
++static void qemu_backup_dump_close(BlockDriverState *bs)
++{
++    /* Nothing to do. */
++}
++
++static int64_t qemu_backup_dump_getlength(BlockDriverState *bs)
++{
++    BDRVBackupDumpState *s = bs->opaque;
++
++    return s->byte_size;
++}
++
++static coroutine_fn int qemu_backup_dump_co_writev(
++    BlockDriverState *bs,
++    int64_t sector_num,
++    int nb_sectors,
++    QEMUIOVector *qiov,
++    int flags)
++{
++    /* flags can be only values we set in supported_write_flags */
++    assert(flags == 0);
++
++    BDRVBackupDumpState *s = bs->opaque;
++    off_t offset = sector_num * BDRV_SECTOR_SIZE;
++
++    uint64_t written = 0;
++
++    for (int i = 0; i < qiov->niov; ++i) {
++        const struct iovec *v = &qiov->iov[i];
++
++        int rc = s->dump_cb(s->dump_cb_data, offset, v->iov_len, v->iov_base);
++        if (rc < 0) {
++            return rc;
++        }
++
++        if (rc != v->iov_len) {
++            return -EIO;
++        }
++
++        written += v->iov_len;
++        offset += v->iov_len;
++    }
++
++    return written;
++}
++
++static void qemu_backup_dump_child_perm(
++    BlockDriverState *bs,
++    BdrvChild *c,
++    BdrvChildRole role,
++    BlockReopenQueue *reopen_queue,
++    uint64_t perm, uint64_t shared,
++    uint64_t *nperm, uint64_t *nshared)
++{
++    *nperm = BLK_PERM_ALL;
++    *nshared = BLK_PERM_ALL;
++}
++
++static BlockDriver bdrv_backup_dump_drive = {
++    .format_name                  = "backup-dump-drive",
++    .protocol_name                = "backup-dump",
++    .instance_size                = sizeof(BDRVBackupDumpState),
++
++    .bdrv_close                   = qemu_backup_dump_close,
++    .bdrv_has_zero_init           = bdrv_has_zero_init_1,
++    .bdrv_getlength               = qemu_backup_dump_getlength,
++    .bdrv_get_info                = qemu_backup_dump_get_info,
++
++    .bdrv_co_writev               = qemu_backup_dump_co_writev,
++
++    .bdrv_refresh_limits          = qemu_backup_dump_refresh_limits,
++    .bdrv_check_perm              = qemu_backup_dump_check_perm,
++    .bdrv_set_perm                = qemu_backup_dump_set_perm,
++    .bdrv_abort_perm_update       = qemu_backup_dump_abort_perm_update,
++    .bdrv_child_perm              = qemu_backup_dump_child_perm,
++};
++
++static void bdrv_backup_dump_init(void)
++{
++    bdrv_register(&bdrv_backup_dump_drive);
++}
++
++block_init(bdrv_backup_dump_init);
++
++
++BlockDriverState *bdrv_backup_dump_create(
++    int dump_cb_block_size,
++    uint64_t byte_size,
++    BackupDumpFunc *dump_cb,
++    void *dump_cb_data,
++    Error **errp)
++{
++    BDRVBackupDumpState *state;
++    BlockDriverState *bs = bdrv_new_open_driver(
++        &bdrv_backup_dump_drive, NULL, BDRV_O_RDWR, errp);
++
++    if (!bs) {
++        return NULL;
++    }
++
++    bs->total_sectors = byte_size / BDRV_SECTOR_SIZE;
++    bs->opaque = state = g_new0(BDRVBackupDumpState, 1);
++
++    state->dump_cb_block_size = dump_cb_block_size;
++    state->byte_size = byte_size;
++    state->dump_cb = dump_cb;
++    state->dump_cb_data = dump_cb_data;
++
++    return bs;
++}
+diff --git a/block/backup.c b/block/backup.c
+index 47e218857d..4d8fad70c4 100644
+--- a/block/backup.c
++++ b/block/backup.c
+@@ -29,28 +29,6 @@
+ #include "block/copy-before-write.h"
+-typedef struct BackupBlockJob {
+-    BlockJob common;
+-    BlockDriverState *cbw;
+-    BlockDriverState *source_bs;
+-    BlockDriverState *target_bs;
+-
+-    BdrvDirtyBitmap *sync_bitmap;
+-
+-    MirrorSyncMode sync_mode;
+-    BitmapSyncMode bitmap_mode;
+-    BlockdevOnError on_source_error;
+-    BlockdevOnError on_target_error;
+-    uint64_t len;
+-    int64_t cluster_size;
+-    BackupPerf perf;
+-
+-    BlockCopyState *bcs;
+-
+-    bool wait;
+-    BlockCopyCallState *bg_bcs_call;
+-} BackupBlockJob;
+-
+ static const BlockJobDriver backup_job_driver;
+ static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret)
+@@ -455,6 +433,14 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
+     }
+     cluster_size = block_copy_cluster_size(bcs);
++    if (cluster_size < 0) {
++        goto error;
++    }
++
++    BlockDriverInfo bdi;
++    if (bdrv_get_info(bs, &bdi) == 0) {
++        cluster_size = MAX(cluster_size, bdi.cluster_size);
++    }
+     if (perf->max_chunk && perf->max_chunk < cluster_size) {
+         error_setg(errp, "Required max-chunk (%" PRIi64 ") is less than backup "
+diff --git a/block/meson.build b/block/meson.build
+index 72081a9974..7883df047c 100644
+--- a/block/meson.build
++++ b/block/meson.build
+@@ -4,6 +4,7 @@ block_ss.add(files(
+   'aio_task.c',
+   'amend.c',
+   'backup.c',
++  'backup-dump.c',
+   'copy-before-write.c',
+   'blkdebug.c',
+   'blklogwrites.c',
+diff --git a/include/block/block_int.h b/include/block/block_int.h
+index ee0aeb1414..1574b5564b 100644
+--- a/include/block/block_int.h
++++ b/include/block/block_int.h
+@@ -26,6 +26,7 @@
+ #include "block/accounting.h"
+ #include "block/block.h"
++#include "block/block-copy.h"
+ #include "block/aio-wait.h"
+ #include "qemu/queue.h"
+ #include "qemu/coroutine.h"
+@@ -64,6 +65,40 @@
+ #define BLOCK_PROBE_BUF_SIZE        512
++typedef int BackupDumpFunc(void *opaque, uint64_t offset, uint64_t bytes, const void *buf);
++
++BlockDriverState *bdrv_backuo_dump_create(
++    int dump_cb_block_size,
++    uint64_t byte_size,
++    BackupDumpFunc *dump_cb,
++    void *dump_cb_data,
++    Error **errp);
++
++// Needs to be defined here, since it's used in blockdev.c to detect PVE backup
++// jobs with source_bs
++typedef struct BlockCopyState BlockCopyState;
++typedef struct BackupBlockJob {
++    BlockJob common;
++    BlockDriverState *cbw;
++    BlockDriverState *source_bs;
++    BlockDriverState *target_bs;
++
++    BdrvDirtyBitmap *sync_bitmap;
++
++    MirrorSyncMode sync_mode;
++    BitmapSyncMode bitmap_mode;
++    BlockdevOnError on_source_error;
++    BlockdevOnError on_target_error;
++    uint64_t len;
++    int64_t cluster_size;
++    BackupPerf perf;
++
++    BlockCopyState *bcs;
++
++    bool wait;
++    BlockCopyCallState *bg_bcs_call;
++} BackupBlockJob;
++
+ enum BdrvTrackedRequestType {
+     BDRV_TRACKED_READ,
+     BDRV_TRACKED_WRITE,
+diff --git a/job.c b/job.c
+index dbfa67bb0a..af25dd5b98 100644
+--- a/job.c
++++ b/job.c
+@@ -276,7 +276,8 @@ static bool job_started(Job *job)
+     return job->co;
+ }
+-static bool job_should_pause(Job *job)
++bool job_should_pause(Job *job);
++bool job_should_pause(Job *job)
+ {
+     return job->pause_count > 0;
+ }
diff --git a/debian/patches/pve/0027-PVE-Backup-proxmox-backup-patches-for-qemu.patch b/debian/patches/pve/0027-PVE-Backup-proxmox-backup-patches-for-qemu.patch
deleted file mode 100644 (file)
index 8d302b9..0000000
+++ /dev/null
@@ -1,1653 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Dietmar Maurer <dietmar@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:59 +0200
-Subject: [PATCH] PVE-Backup: proxmox backup patches for qemu
-
-Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
-[PVE-Backup: avoid coroutines to fix AIO freeze, cleanups]
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-[add new force parameter to job_cancel_sync calls]
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
----
- block/meson.build              |   5 +
- block/monitor/block-hmp-cmds.c |  33 ++
- blockdev.c                     |   1 +
- hmp-commands-info.hx           |  14 +
- hmp-commands.hx                |  29 +
- include/block/block_int.h      |   2 +-
- include/monitor/hmp.h          |   3 +
- meson.build                    |   1 +
- monitor/hmp-cmds.c             |  44 ++
- proxmox-backup-client.c        | 176 ++++++
- proxmox-backup-client.h        |  59 ++
- pve-backup.c                   | 959 +++++++++++++++++++++++++++++++++
- qapi/block-core.json           | 109 ++++
- qapi/common.json               |  13 +
- qapi/machine.json              |  15 +-
- 15 files changed, 1449 insertions(+), 14 deletions(-)
- create mode 100644 proxmox-backup-client.c
- create mode 100644 proxmox-backup-client.h
- create mode 100644 pve-backup.c
-
-diff --git a/block/meson.build b/block/meson.build
-index 7883df047c..9d3dd5b7c3 100644
---- a/block/meson.build
-+++ b/block/meson.build
-@@ -46,6 +46,11 @@ block_ss.add(files(
- ), zstd, zlib, gnutls)
- block_ss.add(files('../vma-writer.c'), libuuid)
-+block_ss.add(files(
-+  '../proxmox-backup-client.c',
-+  '../pve-backup.c',
-+), libproxmox_backup_qemu)
-+
- softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c'))
-diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
-index 2ac4aedfff..f6668ab01d 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1015,3 +1015,36 @@ void hmp_info_snapshots(Monitor *mon, const QDict *qdict)
-     g_free(sn_tab);
-     g_free(global_snapshots);
- }
-+
-+void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
-+{
-+    Error *error = NULL;
-+
-+    qmp_backup_cancel(&error);
-+
-+    hmp_handle_error(mon, error);
-+}
-+
-+void hmp_backup(Monitor *mon, const QDict *qdict)
-+{
-+    Error *error = NULL;
-+
-+    int dir = qdict_get_try_bool(qdict, "directory", 0);
-+    const char *backup_file = qdict_get_str(qdict, "backupfile");
-+    const char *devlist = qdict_get_try_str(qdict, "devlist");
-+    int64_t speed = qdict_get_try_int(qdict, "speed", 0);
-+
-+    qmp_backup(
-+        backup_file,
-+        false, NULL, // PBS password
-+        false, NULL, // PBS keyfile
-+        false, NULL, // PBS key_password
-+        false, NULL, // PBS fingerprint
-+        false, NULL, // PBS backup-id
-+        false, 0, // PBS backup-time
-+        true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
-+        false, NULL, false, NULL, !!devlist,
-+        devlist, qdict_haskey(qdict, "speed"), speed, &error);
-+
-+    hmp_handle_error(mon, error);
-+}
-diff --git a/blockdev.c b/blockdev.c
-index 4be0863050..29fee73cbd 100644
---- a/blockdev.c
-+++ b/blockdev.c
-@@ -36,6 +36,7 @@
- #include "hw/block/block.h"
- #include "block/blockjob.h"
- #include "block/qdict.h"
-+#include "block/blockjob_int.h"
- #include "block/throttle-groups.h"
- #include "monitor/monitor.h"
- #include "qemu/error-report.h"
-diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
-index 245f8acc55..3e7f2421eb 100644
---- a/hmp-commands-info.hx
-+++ b/hmp-commands-info.hx
-@@ -482,6 +482,20 @@ SRST
-     Show the current VM UUID.
- ERST
-+
-+    {
-+        .name       = "backup",
-+        .args_type  = "",
-+        .params     = "",
-+        .help       = "show backup status",
-+        .cmd        = hmp_info_backup,
-+    },
-+
-+SRST
-+  ``info backup``
-+    Show backup status.
-+ERST
-+
- #if defined(CONFIG_SLIRP)
-     {
-         .name       = "usernet",
-diff --git a/hmp-commands.hx b/hmp-commands.hx
-index 1ad13b668b..d4bb00216e 100644
---- a/hmp-commands.hx
-+++ b/hmp-commands.hx
-@@ -99,6 +99,35 @@ ERST
- SRST
- ``block_stream``
-   Copy data from a backing file into a block device.
-+ERST
-+
-+   {
-+        .name       = "backup",
-+        .args_type  = "directory:-d,backupfile:s,speed:o?,devlist:s?",
-+        .params     = "[-d] backupfile [speed [devlist]]",
-+        .help       = "create a VM Backup."
-+                  "\n\t\t\t Use -d to dump data into a directory instead"
-+                  "\n\t\t\t of using VMA format.",
-+        .cmd = hmp_backup,
-+    },
-+
-+SRST
-+``backup``
-+  Create a VM backup.
-+ERST
-+
-+    {
-+        .name       = "backup_cancel",
-+        .args_type  = "",
-+        .params     = "",
-+        .help       = "cancel the current VM backup",
-+        .cmd        = hmp_backup_cancel,
-+    },
-+
-+SRST
-+``backup_cancel``
-+  Cancel the current VM backup.
-+
- ERST
-     {
-diff --git a/include/block/block_int.h b/include/block/block_int.h
-index 1574b5564b..77076d7be3 100644
---- a/include/block/block_int.h
-+++ b/include/block/block_int.h
-@@ -67,7 +67,7 @@
- typedef int BackupDumpFunc(void *opaque, uint64_t offset, uint64_t bytes, const void *buf);
--BlockDriverState *bdrv_backuo_dump_create(
-+BlockDriverState *bdrv_backup_dump_create(
-     int dump_cb_block_size,
-     uint64_t byte_size,
-     BackupDumpFunc *dump_cb,
-diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h
-index 3a39ba41b5..d269b4c99c 100644
---- a/include/monitor/hmp.h
-+++ b/include/monitor/hmp.h
-@@ -30,6 +30,7 @@ void hmp_info_savevm(Monitor *mon, const QDict *qdict);
- void hmp_info_migrate(Monitor *mon, const QDict *qdict);
- void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict);
- void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict);
-+void hmp_info_backup(Monitor *mon, const QDict *qdict);
- void hmp_info_cpus(Monitor *mon, const QDict *qdict);
- void hmp_info_vnc(Monitor *mon, const QDict *qdict);
- void hmp_info_spice(Monitor *mon, const QDict *qdict);
-@@ -73,6 +74,8 @@ void hmp_x_colo_lost_heartbeat(Monitor *mon, const QDict *qdict);
- void hmp_set_password(Monitor *mon, const QDict *qdict);
- void hmp_expire_password(Monitor *mon, const QDict *qdict);
- void hmp_change(Monitor *mon, const QDict *qdict);
-+void hmp_backup(Monitor *mon, const QDict *qdict);
-+void hmp_backup_cancel(Monitor *mon, const QDict *qdict);
- void hmp_migrate(Monitor *mon, const QDict *qdict);
- void hmp_device_add(Monitor *mon, const QDict *qdict);
- void hmp_device_del(Monitor *mon, const QDict *qdict);
-diff --git a/meson.build b/meson.build
-index 54c23b9567..37dab249cc 100644
---- a/meson.build
-+++ b/meson.build
-@@ -1203,6 +1203,7 @@ keyutils = dependency('libkeyutils', required: false,
- has_gettid = cc.has_function('gettid')
- libuuid = cc.find_library('uuid', required: true)
-+libproxmox_backup_qemu = cc.find_library('proxmox_backup_qemu', required: true)
- # libselinux
- selinux = dependency('libselinux',
-diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
-index 5000ce39d1..b2687eae3a 100644
---- a/monitor/hmp-cmds.c
-+++ b/monitor/hmp-cmds.c
-@@ -195,6 +195,50 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
-     qapi_free_MouseInfoList(mice_list);
- }
-+void hmp_info_backup(Monitor *mon, const QDict *qdict)
-+{
-+    BackupStatus *info;
-+
-+    info = qmp_query_backup(NULL);
-+
-+    if (!info) {
-+       monitor_printf(mon, "Backup status: not initialized\n");
-+       return;
-+    }
-+
-+    if (info->has_status) {
-+        if (info->has_errmsg) {
-+            monitor_printf(mon, "Backup status: %s - %s\n",
-+                           info->status, info->errmsg);
-+        } else {
-+            monitor_printf(mon, "Backup status: %s\n", info->status);
-+        }
-+    }
-+
-+    if (info->has_backup_file) {
-+        monitor_printf(mon, "Start time: %s", ctime(&info->start_time));
-+        if (info->end_time) {
-+            monitor_printf(mon, "End time: %s", ctime(&info->end_time));
-+        }
-+
-+        int per = (info->has_total && info->total &&
-+            info->has_transferred && info->transferred) ?
-+            (info->transferred * 100)/info->total : 0;
-+        int zero_per = (info->has_total && info->total &&
-+                        info->has_zero_bytes && info->zero_bytes) ?
-+            (info->zero_bytes * 100)/info->total : 0;
-+        monitor_printf(mon, "Backup file: %s\n", info->backup_file);
-+        monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
-+        monitor_printf(mon, "Total size: %zd\n", info->total);
-+        monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
-+                       info->transferred, per);
-+        monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
-+                       info->zero_bytes, zero_per);
-+    }
-+
-+    qapi_free_BackupStatus(info);
-+}
-+
- static char *SocketAddress_to_str(SocketAddress *addr)
- {
-     switch (addr->type) {
-diff --git a/proxmox-backup-client.c b/proxmox-backup-client.c
-new file mode 100644
-index 0000000000..a8f6653a81
---- /dev/null
-+++ b/proxmox-backup-client.c
-@@ -0,0 +1,176 @@
-+#include "proxmox-backup-client.h"
-+#include "qemu/main-loop.h"
-+#include "block/aio-wait.h"
-+#include "qapi/error.h"
-+
-+/* Proxmox Backup Server client bindings using coroutines */
-+
-+typedef struct BlockOnCoroutineWrapper {
-+    AioContext *ctx;
-+    CoroutineEntry *entry;
-+    void *entry_arg;
-+    bool finished;
-+} BlockOnCoroutineWrapper;
-+
-+static void coroutine_fn block_on_coroutine_wrapper(void *opaque)
-+{
-+    BlockOnCoroutineWrapper *wrapper = opaque;
-+    wrapper->entry(wrapper->entry_arg);
-+    wrapper->finished = true;
-+    aio_wait_kick();
-+}
-+
-+void block_on_coroutine_fn(CoroutineEntry *entry, void *entry_arg)
-+{
-+    assert(!qemu_in_coroutine());
-+
-+    AioContext *ctx = qemu_get_current_aio_context();
-+    BlockOnCoroutineWrapper wrapper = {
-+        .finished = false,
-+        .entry = entry,
-+        .entry_arg = entry_arg,
-+        .ctx = ctx,
-+    };
-+    Coroutine *wrapper_co = qemu_coroutine_create(block_on_coroutine_wrapper, &wrapper);
-+    aio_co_enter(ctx, wrapper_co);
-+    AIO_WAIT_WHILE(ctx, !wrapper.finished);
-+}
-+
-+// This is called from another thread, so we use aio_co_schedule()
-+static void proxmox_backup_schedule_wake(void *data) {
-+    CoCtxData *waker = (CoCtxData *)data;
-+    aio_co_schedule(waker->ctx, waker->co);
-+}
-+
-+int coroutine_fn
-+proxmox_backup_co_connect(ProxmoxBackupHandle *pbs, Error **errp)
-+{
-+    Coroutine *co = qemu_coroutine_self();
-+    AioContext *ctx = qemu_get_current_aio_context();
-+    CoCtxData waker = { .co = co, .ctx = ctx };
-+    char *pbs_err = NULL;
-+    int pbs_res = -1;
-+
-+    proxmox_backup_connect_async(pbs, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
-+    qemu_coroutine_yield();
-+    if (pbs_res < 0) {
-+        if (errp) error_setg(errp, "backup connect failed: %s", pbs_err ? pbs_err : "unknown error");
-+        if (pbs_err) proxmox_backup_free_error(pbs_err);
-+    }
-+    return pbs_res;
-+}
-+
-+int coroutine_fn
-+proxmox_backup_co_add_config(
-+    ProxmoxBackupHandle *pbs,
-+    const char *name,
-+    const uint8_t *data,
-+    uint64_t size,
-+    Error **errp)
-+{
-+    Coroutine *co = qemu_coroutine_self();
-+    AioContext *ctx = qemu_get_current_aio_context();
-+    CoCtxData waker = { .co = co, .ctx = ctx };
-+    char *pbs_err = NULL;
-+    int pbs_res = -1;
-+
-+    proxmox_backup_add_config_async(
-+        pbs, name, data, size ,proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
-+    qemu_coroutine_yield();
-+    if (pbs_res < 0) {
-+        if (errp) error_setg(errp, "backup add_config %s failed: %s", name, pbs_err ? pbs_err : "unknown error");
-+        if (pbs_err) proxmox_backup_free_error(pbs_err);
-+    }
-+    return pbs_res;
-+}
-+
-+int coroutine_fn
-+proxmox_backup_co_register_image(
-+    ProxmoxBackupHandle *pbs,
-+    const char *device_name,
-+    uint64_t size,
-+    Error **errp)
-+{
-+    Coroutine *co = qemu_coroutine_self();
-+    AioContext *ctx = qemu_get_current_aio_context();
-+    CoCtxData waker = { .co = co, .ctx = ctx };
-+    char *pbs_err = NULL;
-+    int pbs_res = -1;
-+
-+    proxmox_backup_register_image_async(
-+        pbs, device_name, size ,proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
-+    qemu_coroutine_yield();
-+    if (pbs_res < 0) {
-+        if (errp) error_setg(errp, "backup register image failed: %s", pbs_err ? pbs_err : "unknown error");
-+        if (pbs_err) proxmox_backup_free_error(pbs_err);
-+    }
-+    return pbs_res;
-+}
-+
-+int coroutine_fn
-+proxmox_backup_co_finish(
-+    ProxmoxBackupHandle *pbs,
-+    Error **errp)
-+{
-+    Coroutine *co = qemu_coroutine_self();
-+    AioContext *ctx = qemu_get_current_aio_context();
-+    CoCtxData waker = { .co = co, .ctx = ctx };
-+    char *pbs_err = NULL;
-+    int pbs_res = -1;
-+
-+    proxmox_backup_finish_async(
-+        pbs, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
-+    qemu_coroutine_yield();
-+    if (pbs_res < 0) {
-+        if (errp) error_setg(errp, "backup finish failed: %s", pbs_err ? pbs_err : "unknown error");
-+        if (pbs_err) proxmox_backup_free_error(pbs_err);
-+    }
-+    return pbs_res;
-+}
-+
-+int coroutine_fn
-+proxmox_backup_co_close_image(
-+    ProxmoxBackupHandle *pbs,
-+    uint8_t dev_id,
-+    Error **errp)
-+{
-+    Coroutine *co = qemu_coroutine_self();
-+    AioContext *ctx = qemu_get_current_aio_context();
-+    CoCtxData waker = { .co = co, .ctx = ctx };
-+    char *pbs_err = NULL;
-+    int pbs_res = -1;
-+
-+    proxmox_backup_close_image_async(
-+        pbs, dev_id, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
-+    qemu_coroutine_yield();
-+    if (pbs_res < 0) {
-+        if (errp) error_setg(errp, "backup close image failed: %s", pbs_err ? pbs_err : "unknown error");
-+        if (pbs_err) proxmox_backup_free_error(pbs_err);
-+    }
-+    return pbs_res;
-+}
-+
-+int coroutine_fn
-+proxmox_backup_co_write_data(
-+    ProxmoxBackupHandle *pbs,
-+    uint8_t dev_id,
-+    const uint8_t *data,
-+    uint64_t offset,
-+    uint64_t size,
-+    Error **errp)
-+{
-+    Coroutine *co = qemu_coroutine_self();
-+    AioContext *ctx = qemu_get_current_aio_context();
-+    CoCtxData waker = { .co = co, .ctx = ctx };
-+    char *pbs_err = NULL;
-+    int pbs_res = -1;
-+
-+    proxmox_backup_write_data_async(
-+        pbs, dev_id, data, offset, size, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
-+    qemu_coroutine_yield();
-+    if (pbs_res < 0) {
-+        if (errp) error_setg(errp, "backup write data failed: %s", pbs_err ? pbs_err : "unknown error");
-+        if (pbs_err) proxmox_backup_free_error(pbs_err);
-+    }
-+    return pbs_res;
-+}
-diff --git a/proxmox-backup-client.h b/proxmox-backup-client.h
-new file mode 100644
-index 0000000000..1dda8b7d8f
---- /dev/null
-+++ b/proxmox-backup-client.h
-@@ -0,0 +1,59 @@
-+#ifndef PROXMOX_BACKUP_CLIENT_H
-+#define PROXMOX_BACKUP_CLIENT_H
-+
-+#include "qemu/osdep.h"
-+#include "qemu/coroutine.h"
-+#include "proxmox-backup-qemu.h"
-+
-+typedef struct CoCtxData {
-+    Coroutine *co;
-+    AioContext *ctx;
-+    void *data;
-+} CoCtxData;
-+
-+// FIXME: Remove once coroutines are supported for QMP
-+void block_on_coroutine_fn(CoroutineEntry *entry, void *entry_arg);
-+
-+int coroutine_fn
-+proxmox_backup_co_connect(
-+    ProxmoxBackupHandle *pbs,
-+    Error **errp);
-+
-+int coroutine_fn
-+proxmox_backup_co_add_config(
-+    ProxmoxBackupHandle *pbs,
-+    const char *name,
-+    const uint8_t *data,
-+    uint64_t size,
-+    Error **errp);
-+
-+int coroutine_fn
-+proxmox_backup_co_register_image(
-+    ProxmoxBackupHandle *pbs,
-+    const char *device_name,
-+    uint64_t size,
-+    Error **errp);
-+
-+
-+int coroutine_fn
-+proxmox_backup_co_finish(
-+    ProxmoxBackupHandle *pbs,
-+    Error **errp);
-+
-+int coroutine_fn
-+proxmox_backup_co_close_image(
-+    ProxmoxBackupHandle *pbs,
-+    uint8_t dev_id,
-+    Error **errp);
-+
-+int coroutine_fn
-+proxmox_backup_co_write_data(
-+    ProxmoxBackupHandle *pbs,
-+    uint8_t dev_id,
-+    const uint8_t *data,
-+    uint64_t offset,
-+    uint64_t size,
-+    Error **errp);
-+
-+
-+#endif /* PROXMOX_BACKUP_CLIENT_H */
-diff --git a/pve-backup.c b/pve-backup.c
-new file mode 100644
-index 0000000000..88f5ee133f
---- /dev/null
-+++ b/pve-backup.c
-@@ -0,0 +1,959 @@
-+#include "proxmox-backup-client.h"
-+#include "vma.h"
-+
-+#include "qemu/osdep.h"
-+#include "qemu/module.h"
-+#include "sysemu/block-backend.h"
-+#include "sysemu/blockdev.h"
-+#include "block/blockjob.h"
-+#include "qapi/qapi-commands-block.h"
-+#include "qapi/qmp/qerror.h"
-+
-+/* PVE backup state and related function */
-+
-+/*
-+ * Note: A resume from a qemu_coroutine_yield can happen in a different thread,
-+ * so you may not use normal mutexes within coroutines:
-+ *
-+ * ---bad-example---
-+ * qemu_rec_mutex_lock(lock)
-+ * ...
-+ * qemu_coroutine_yield() // wait for something
-+ * // we are now inside a different thread
-+ * qemu_rec_mutex_unlock(lock) // Crash - wrong thread!!
-+ * ---end-bad-example--
-+ *
-+ * ==> Always use CoMutext inside coroutines.
-+ * ==> Never acquire/release AioContext withing coroutines (because that use QemuRecMutex)
-+ *
-+ */
-+
-+static struct PVEBackupState {
-+    struct {
-+        // Everithing accessed from qmp_backup_query command is protected using lock
-+        QemuMutex lock;
-+        Error *error;
-+        time_t start_time;
-+        time_t end_time;
-+        char *backup_file;
-+        uuid_t uuid;
-+        char uuid_str[37];
-+        size_t total;
-+        size_t transferred;
-+        size_t zero_bytes;
-+    } stat;
-+    int64_t speed;
-+    VmaWriter *vmaw;
-+    ProxmoxBackupHandle *pbs;
-+    GList *di_list;
-+    QemuMutex backup_mutex;
-+    CoMutex dump_callback_mutex;
-+} backup_state;
-+
-+static void pvebackup_init(void)
-+{
-+    qemu_mutex_init(&backup_state.stat.lock);
-+    qemu_mutex_init(&backup_state.backup_mutex);
-+    qemu_co_mutex_init(&backup_state.dump_callback_mutex);
-+}
-+
-+// initialize PVEBackupState at startup
-+opts_init(pvebackup_init);
-+
-+typedef struct PVEBackupDevInfo {
-+    BlockDriverState *bs;
-+    size_t size;
-+    uint8_t dev_id;
-+    bool completed;
-+    char targetfile[PATH_MAX];
-+    BlockDriverState *target;
-+} PVEBackupDevInfo;
-+
-+static void pvebackup_run_next_job(void);
-+
-+static BlockJob *
-+lookup_active_block_job(PVEBackupDevInfo *di)
-+{
-+    if (!di->completed && di->bs) {
-+        for (BlockJob *job = block_job_next(NULL); job; job = block_job_next(job)) {
-+            if (job->job.driver->job_type != JOB_TYPE_BACKUP) {
-+                continue;
-+            }
-+
-+            BackupBlockJob *bjob = container_of(job, BackupBlockJob, common);
-+            if (bjob && bjob->source_bs == di->bs) {
-+                return job;
-+            }
-+        }
-+    }
-+    return NULL;
-+}
-+
-+static void pvebackup_propagate_error(Error *err)
-+{
-+    qemu_mutex_lock(&backup_state.stat.lock);
-+    error_propagate(&backup_state.stat.error, err);
-+    qemu_mutex_unlock(&backup_state.stat.lock);
-+}
-+
-+static bool pvebackup_error_or_canceled(void)
-+{
-+    qemu_mutex_lock(&backup_state.stat.lock);
-+    bool error_or_canceled = !!backup_state.stat.error;
-+    qemu_mutex_unlock(&backup_state.stat.lock);
-+
-+    return error_or_canceled;
-+}
-+
-+static void pvebackup_add_transfered_bytes(size_t transferred, size_t zero_bytes)
-+{
-+    qemu_mutex_lock(&backup_state.stat.lock);
-+    backup_state.stat.zero_bytes += zero_bytes;
-+    backup_state.stat.transferred += transferred;
-+    qemu_mutex_unlock(&backup_state.stat.lock);
-+}
-+
-+// This may get called from multiple coroutines in multiple io-threads
-+// Note1: this may get called after job_cancel()
-+static int coroutine_fn
-+pvebackup_co_dump_pbs_cb(
-+    void *opaque,
-+    uint64_t start,
-+    uint64_t bytes,
-+    const void *pbuf)
-+{
-+    assert(qemu_in_coroutine());
-+
-+    const uint64_t size = bytes;
-+    const unsigned char *buf = pbuf;
-+    PVEBackupDevInfo *di = opaque;
-+
-+    assert(backup_state.pbs);
-+
-+    Error *local_err = NULL;
-+    int pbs_res = -1;
-+
-+    qemu_co_mutex_lock(&backup_state.dump_callback_mutex);
-+
-+    // avoid deadlock if job is cancelled
-+    if (pvebackup_error_or_canceled()) {
-+        qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
-+        return -1;
-+    }
-+
-+    pbs_res = proxmox_backup_co_write_data(backup_state.pbs, di->dev_id, buf, start, size, &local_err);
-+    qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
-+
-+    if (pbs_res < 0) {
-+        pvebackup_propagate_error(local_err);
-+        return pbs_res;
-+    } else {
-+        pvebackup_add_transfered_bytes(size, !buf ? size : 0);
-+    }
-+
-+    return size;
-+}
-+
-+// This may get called from multiple coroutines in multiple io-threads
-+static int coroutine_fn
-+pvebackup_co_dump_vma_cb(
-+    void *opaque,
-+    uint64_t start,
-+    uint64_t bytes,
-+    const void *pbuf)
-+{
-+    assert(qemu_in_coroutine());
-+
-+    const uint64_t size = bytes;
-+    const unsigned char *buf = pbuf;
-+    PVEBackupDevInfo *di = opaque;
-+
-+    int ret = -1;
-+
-+    assert(backup_state.vmaw);
-+
-+    uint64_t remaining = size;
-+
-+    uint64_t cluster_num = start / VMA_CLUSTER_SIZE;
-+    if ((cluster_num * VMA_CLUSTER_SIZE) != start) {
-+        Error *local_err = NULL;
-+        error_setg(&local_err,
-+                   "got unaligned write inside backup dump "
-+                   "callback (sector %ld)", start);
-+        pvebackup_propagate_error(local_err);
-+        return -1; // not aligned to cluster size
-+    }
-+
-+    while (remaining > 0) {
-+        qemu_co_mutex_lock(&backup_state.dump_callback_mutex);
-+        // avoid deadlock if job is cancelled
-+        if (pvebackup_error_or_canceled()) {
-+            qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
-+            return -1;
-+        }
-+
-+        size_t zero_bytes = 0;
-+        ret = vma_writer_write(backup_state.vmaw, di->dev_id, cluster_num, buf, &zero_bytes);
-+        qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
-+
-+        ++cluster_num;
-+        if (buf) {
-+            buf += VMA_CLUSTER_SIZE;
-+        }
-+        if (ret < 0) {
-+            Error *local_err = NULL;
-+            vma_writer_error_propagate(backup_state.vmaw, &local_err);
-+            pvebackup_propagate_error(local_err);
-+            return ret;
-+        } else {
-+            if (remaining >= VMA_CLUSTER_SIZE) {
-+                assert(ret == VMA_CLUSTER_SIZE);
-+                pvebackup_add_transfered_bytes(VMA_CLUSTER_SIZE, zero_bytes);
-+                remaining -= VMA_CLUSTER_SIZE;
-+            } else {
-+                assert(ret == remaining);
-+                pvebackup_add_transfered_bytes(remaining, zero_bytes);
-+                remaining = 0;
-+            }
-+        }
-+    }
-+
-+    return size;
-+}
-+
-+// assumes the caller holds backup_mutex
-+static void coroutine_fn pvebackup_co_cleanup(void *unused)
-+{
-+    assert(qemu_in_coroutine());
-+
-+    qemu_mutex_lock(&backup_state.stat.lock);
-+    backup_state.stat.end_time = time(NULL);
-+    qemu_mutex_unlock(&backup_state.stat.lock);
-+
-+    if (backup_state.vmaw) {
-+        Error *local_err = NULL;
-+        vma_writer_close(backup_state.vmaw, &local_err);
-+
-+        if (local_err != NULL) {
-+            pvebackup_propagate_error(local_err);
-+         }
-+
-+        backup_state.vmaw = NULL;
-+    }
-+
-+    if (backup_state.pbs) {
-+        if (!pvebackup_error_or_canceled()) {
-+            Error *local_err = NULL;
-+            proxmox_backup_co_finish(backup_state.pbs, &local_err);
-+            if (local_err != NULL) {
-+                pvebackup_propagate_error(local_err);
-+            }
-+        }
-+
-+        proxmox_backup_disconnect(backup_state.pbs);
-+        backup_state.pbs = NULL;
-+    }
-+
-+    g_list_free(backup_state.di_list);
-+    backup_state.di_list = NULL;
-+}
-+
-+// assumes the caller holds backup_mutex
-+static void coroutine_fn pvebackup_complete_stream(void *opaque)
-+{
-+    PVEBackupDevInfo *di = opaque;
-+
-+    bool error_or_canceled = pvebackup_error_or_canceled();
-+
-+    if (backup_state.vmaw) {
-+        vma_writer_close_stream(backup_state.vmaw, di->dev_id);
-+    }
-+
-+    if (backup_state.pbs && !error_or_canceled) {
-+        Error *local_err = NULL;
-+        proxmox_backup_co_close_image(backup_state.pbs, di->dev_id, &local_err);
-+        if (local_err != NULL) {
-+            pvebackup_propagate_error(local_err);
-+        }
-+    }
-+}
-+
-+static void pvebackup_complete_cb(void *opaque, int ret)
-+{
-+    assert(!qemu_in_coroutine());
-+
-+    PVEBackupDevInfo *di = opaque;
-+
-+    qemu_mutex_lock(&backup_state.backup_mutex);
-+
-+    di->completed = true;
-+
-+    if (ret < 0) {
-+        Error *local_err = NULL;
-+        error_setg(&local_err, "job failed with err %d - %s", ret, strerror(-ret));
-+        pvebackup_propagate_error(local_err);
-+    }
-+
-+    di->bs = NULL;
-+
-+    assert(di->target == NULL);
-+
-+    block_on_coroutine_fn(pvebackup_complete_stream, di);
-+
-+    // remove self from job queue
-+    backup_state.di_list = g_list_remove(backup_state.di_list, di);
-+
-+    g_free(di);
-+
-+    qemu_mutex_unlock(&backup_state.backup_mutex);
-+
-+    pvebackup_run_next_job();
-+}
-+
-+static void pvebackup_cancel(void)
-+{
-+    assert(!qemu_in_coroutine());
-+
-+    Error *cancel_err = NULL;
-+    error_setg(&cancel_err, "backup canceled");
-+    pvebackup_propagate_error(cancel_err);
-+
-+    qemu_mutex_lock(&backup_state.backup_mutex);
-+
-+    if (backup_state.vmaw) {
-+        /* make sure vma writer does not block anymore */
-+        vma_writer_set_error(backup_state.vmaw, "backup canceled");
-+    }
-+
-+    if (backup_state.pbs) {
-+        proxmox_backup_abort(backup_state.pbs, "backup canceled");
-+    }
-+
-+    qemu_mutex_unlock(&backup_state.backup_mutex);
-+
-+    for(;;) {
-+
-+        BlockJob *next_job = NULL;
-+
-+        qemu_mutex_lock(&backup_state.backup_mutex);
-+
-+        GList *l = backup_state.di_list;
-+        while (l) {
-+            PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+            l = g_list_next(l);
-+
-+            BlockJob *job = lookup_active_block_job(di);
-+            if (job != NULL) {
-+                next_job = job;
-+                break;
-+            }
-+        }
-+
-+        qemu_mutex_unlock(&backup_state.backup_mutex);
-+
-+        if (next_job) {
-+            AioContext *aio_context = next_job->job.aio_context;
-+            aio_context_acquire(aio_context);
-+            job_cancel_sync(&next_job->job, true);
-+            aio_context_release(aio_context);
-+        } else {
-+            break;
-+        }
-+    }
-+}
-+
-+void qmp_backup_cancel(Error **errp)
-+{
-+    pvebackup_cancel();
-+}
-+
-+// assumes the caller holds backup_mutex
-+static int coroutine_fn pvebackup_co_add_config(
-+    const char *file,
-+    const char *name,
-+    BackupFormat format,
-+    const char *backup_dir,
-+    VmaWriter *vmaw,
-+    ProxmoxBackupHandle *pbs,
-+    Error **errp)
-+{
-+    int res = 0;
-+
-+    char *cdata = NULL;
-+    gsize clen = 0;
-+    GError *err = NULL;
-+    if (!g_file_get_contents(file, &cdata, &clen, &err)) {
-+        error_setg(errp, "unable to read file '%s'", file);
-+        return 1;
-+    }
-+
-+    char *basename = g_path_get_basename(file);
-+    if (name == NULL) name = basename;
-+
-+    if (format == BACKUP_FORMAT_VMA) {
-+        if (vma_writer_add_config(vmaw, name, cdata, clen) != 0) {
-+            error_setg(errp, "unable to add %s config data to vma archive", file);
-+            goto err;
-+        }
-+    } else if (format == BACKUP_FORMAT_PBS) {
-+        if (proxmox_backup_co_add_config(pbs, name, (unsigned char *)cdata, clen, errp) < 0)
-+            goto err;
-+    } else if (format == BACKUP_FORMAT_DIR) {
-+        char config_path[PATH_MAX];
-+        snprintf(config_path, PATH_MAX, "%s/%s", backup_dir, name);
-+        if (!g_file_set_contents(config_path, cdata, clen, &err)) {
-+            error_setg(errp, "unable to write config file '%s'", config_path);
-+            goto err;
-+        }
-+    }
-+
-+ out:
-+    g_free(basename);
-+    g_free(cdata);
-+    return res;
-+
-+ err:
-+    res = -1;
-+    goto out;
-+}
-+
-+bool job_should_pause(Job *job);
-+
-+static void pvebackup_run_next_job(void)
-+{
-+    assert(!qemu_in_coroutine());
-+
-+    qemu_mutex_lock(&backup_state.backup_mutex);
-+
-+    GList *l = backup_state.di_list;
-+    while (l) {
-+        PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+        l = g_list_next(l);
-+
-+        BlockJob *job = lookup_active_block_job(di);
-+
-+        if (job) {
-+            qemu_mutex_unlock(&backup_state.backup_mutex);
-+
-+            AioContext *aio_context = job->job.aio_context;
-+            aio_context_acquire(aio_context);
-+
-+            if (job_should_pause(&job->job)) {
-+                bool error_or_canceled = pvebackup_error_or_canceled();
-+                if (error_or_canceled) {
-+                    job_cancel_sync(&job->job, true);
-+                } else {
-+                    job_resume(&job->job);
-+                }
-+            }
-+            aio_context_release(aio_context);
-+            return;
-+        }
-+    }
-+
-+    block_on_coroutine_fn(pvebackup_co_cleanup, NULL); // no more jobs, run cleanup
-+
-+    qemu_mutex_unlock(&backup_state.backup_mutex);
-+}
-+
-+static bool create_backup_jobs(void) {
-+
-+    assert(!qemu_in_coroutine());
-+
-+    Error *local_err = NULL;
-+
-+    BackupPerf perf = { .max_workers = 16 };
-+
-+    /* create and start all jobs (paused state) */
-+    GList *l =  backup_state.di_list;
-+    while (l) {
-+        PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+        l = g_list_next(l);
-+
-+        assert(di->target != NULL);
-+
-+        AioContext *aio_context = bdrv_get_aio_context(di->bs);
-+        aio_context_acquire(aio_context);
-+
-+        BlockJob *job = backup_job_create(
-+            NULL, di->bs, di->target, backup_state.speed, MIRROR_SYNC_MODE_FULL, NULL,
-+            BITMAP_SYNC_MODE_NEVER, false, NULL, &perf, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
-+            JOB_DEFAULT, pvebackup_complete_cb, di, NULL, &local_err);
-+
-+        aio_context_release(aio_context);
-+
-+        if (!job || local_err != NULL) {
-+            Error *create_job_err = NULL;
-+            error_setg(&create_job_err, "backup_job_create failed: %s",
-+                       local_err ? error_get_pretty(local_err) : "null");
-+
-+            pvebackup_propagate_error(create_job_err);
-+            break;
-+        }
-+        job_start(&job->job);
-+
-+        bdrv_unref(di->target);
-+        di->target = NULL;
-+    }
-+
-+    bool errors = pvebackup_error_or_canceled();
-+
-+    if (errors) {
-+        l = backup_state.di_list;
-+        while (l) {
-+            PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+            l = g_list_next(l);
-+
-+            if (di->target) {
-+                bdrv_unref(di->target);
-+                di->target = NULL;
-+            }
-+        }
-+    }
-+
-+    return errors;
-+}
-+
-+typedef struct QmpBackupTask {
-+    const char *backup_file;
-+    bool has_password;
-+    const char *password;
-+    bool has_keyfile;
-+    const char *keyfile;
-+    bool has_key_password;
-+    const char *key_password;
-+    bool has_backup_id;
-+    const char *backup_id;
-+    bool has_backup_time;
-+    const char *fingerprint;
-+    bool has_fingerprint;
-+    int64_t backup_time;
-+    bool has_format;
-+    BackupFormat format;
-+    bool has_config_file;
-+    const char *config_file;
-+    bool has_firewall_file;
-+    const char *firewall_file;
-+    bool has_devlist;
-+    const char *devlist;
-+    bool has_speed;
-+    int64_t speed;
-+    Error **errp;
-+    UuidInfo *result;
-+} QmpBackupTask;
-+
-+// assumes the caller holds backup_mutex
-+static void coroutine_fn pvebackup_co_prepare(void *opaque)
-+{
-+    assert(qemu_in_coroutine());
-+
-+    QmpBackupTask *task = opaque;
-+
-+    task->result = NULL; // just to be sure
-+
-+    BlockBackend *blk;
-+    BlockDriverState *bs = NULL;
-+    const char *backup_dir = NULL;
-+    Error *local_err = NULL;
-+    uuid_t uuid;
-+    VmaWriter *vmaw = NULL;
-+    ProxmoxBackupHandle *pbs = NULL;
-+    gchar **devs = NULL;
-+    GList *di_list = NULL;
-+    GList *l;
-+    UuidInfo *uuid_info;
-+
-+    const char *config_name = "qemu-server.conf";
-+    const char *firewall_name = "qemu-server.fw";
-+
-+    if (backup_state.di_list) {
-+         error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
-+                  "previous backup not finished");
-+        return;
-+    }
-+
-+    /* Todo: try to auto-detect format based on file name */
-+    BackupFormat format = task->has_format ? task->format : BACKUP_FORMAT_VMA;
-+
-+    if (task->has_devlist) {
-+        devs = g_strsplit_set(task->devlist, ",;:", -1);
-+
-+        gchar **d = devs;
-+        while (d && *d) {
-+            blk = blk_by_name(*d);
-+            if (blk) {
-+                bs = blk_bs(blk);
-+                if (!bdrv_is_inserted(bs)) {
-+                    error_setg(task->errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
-+                    goto err;
-+                }
-+                PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
-+                di->bs = bs;
-+                di_list = g_list_append(di_list, di);
-+            } else {
-+                error_set(task->errp, ERROR_CLASS_DEVICE_NOT_FOUND,
-+                          "Device '%s' not found", *d);
-+                goto err;
-+            }
-+            d++;
-+        }
-+
-+    } else {
-+        BdrvNextIterator it;
-+
-+        bs = NULL;
-+        for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
-+            if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) {
-+                continue;
-+            }
-+
-+            PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
-+            di->bs = bs;
-+            di_list = g_list_append(di_list, di);
-+        }
-+    }
-+
-+    if (!di_list) {
-+        error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
-+        goto err;
-+    }
-+
-+    size_t total = 0;
-+
-+    l = di_list;
-+    while (l) {
-+        PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+        l = g_list_next(l);
-+        if (bdrv_op_is_blocked(di->bs, BLOCK_OP_TYPE_BACKUP_SOURCE, task->errp)) {
-+            goto err;
-+        }
-+
-+        ssize_t size = bdrv_getlength(di->bs);
-+        if (size < 0) {
-+            error_setg_errno(task->errp, -di->size, "bdrv_getlength failed");
-+            goto err;
-+        }
-+        di->size = size;
-+        total += size;
-+    }
-+
-+    uuid_generate(uuid);
-+
-+    if (format == BACKUP_FORMAT_PBS) {
-+        if (!task->has_password) {
-+            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
-+            goto err;
-+        }
-+        if (!task->has_backup_id) {
-+            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
-+            goto err;
-+        }
-+        if (!task->has_backup_time) {
-+            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-time'");
-+            goto err;
-+        }
-+
-+        int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
-+        firewall_name = "fw.conf";
-+
-+        char *pbs_err = NULL;
-+        pbs = proxmox_backup_new(
-+            task->backup_file,
-+            task->backup_id,
-+            task->backup_time,
-+            dump_cb_block_size,
-+            task->has_password ? task->password : NULL,
-+            task->has_keyfile ? task->keyfile : NULL,
-+            task->has_key_password ? task->key_password : NULL,
-+            task->has_fingerprint ? task->fingerprint : NULL,
-+            &pbs_err);
-+
-+        if (!pbs) {
-+            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
-+                      "proxmox_backup_new failed: %s", pbs_err);
-+            proxmox_backup_free_error(pbs_err);
-+            goto err;
-+        }
-+
-+        if (proxmox_backup_co_connect(pbs, task->errp) < 0)
-+            goto err;
-+
-+        /* register all devices */
-+        l = di_list;
-+        while (l) {
-+            PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+            l = g_list_next(l);
-+
-+            const char *devname = bdrv_get_device_name(di->bs);
-+
-+            int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, task->errp);
-+            if (dev_id < 0)
-+                goto err;
-+
-+            if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, task->errp))) {
-+                goto err;
-+            }
-+
-+            di->dev_id = dev_id;
-+        }
-+    } else if (format == BACKUP_FORMAT_VMA) {
-+        vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
-+        if (!vmaw) {
-+            if (local_err) {
-+                error_propagate(task->errp, local_err);
-+            }
-+            goto err;
-+        }
-+
-+        /* register all devices for vma writer */
-+        l = di_list;
-+        while (l) {
-+            PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+            l = g_list_next(l);
-+
-+            if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, task->errp))) {
-+                goto err;
-+            }
-+
-+            const char *devname = bdrv_get_device_name(di->bs);
-+            di->dev_id = vma_writer_register_stream(vmaw, devname, di->size);
-+            if (di->dev_id <= 0) {
-+                error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
-+                          "register_stream failed");
-+                goto err;
-+            }
-+        }
-+    } else if (format == BACKUP_FORMAT_DIR) {
-+        if (mkdir(task->backup_file, 0640) != 0) {
-+            error_setg_errno(task->errp, errno, "can't create directory '%s'\n",
-+                             task->backup_file);
-+            goto err;
-+        }
-+        backup_dir = task->backup_file;
-+
-+        l = di_list;
-+        while (l) {
-+            PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+            l = g_list_next(l);
-+
-+            const char *devname = bdrv_get_device_name(di->bs);
-+            snprintf(di->targetfile, PATH_MAX, "%s/%s.raw", backup_dir, devname);
-+
-+            int flags = BDRV_O_RDWR;
-+            bdrv_img_create(di->targetfile, "raw", NULL, NULL, NULL,
-+                            di->size, flags, false, &local_err);
-+            if (local_err) {
-+                error_propagate(task->errp, local_err);
-+                goto err;
-+            }
-+
-+            di->target = bdrv_open(di->targetfile, NULL, NULL, flags, &local_err);
-+            if (!di->target) {
-+                error_propagate(task->errp, local_err);
-+                goto err;
-+            }
-+        }
-+    } else {
-+        error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
-+        goto err;
-+    }
-+
-+
-+    /* add configuration file to archive */
-+    if (task->has_config_file) {
-+        if (pvebackup_co_add_config(task->config_file, config_name, format, backup_dir,
-+                                    vmaw, pbs, task->errp) != 0) {
-+            goto err;
-+        }
-+    }
-+
-+    /* add firewall file to archive */
-+    if (task->has_firewall_file) {
-+        if (pvebackup_co_add_config(task->firewall_file, firewall_name, format, backup_dir,
-+                                    vmaw, pbs, task->errp) != 0) {
-+            goto err;
-+        }
-+    }
-+    /* initialize global backup_state now */
-+
-+    qemu_mutex_lock(&backup_state.stat.lock);
-+
-+    if (backup_state.stat.error) {
-+        error_free(backup_state.stat.error);
-+        backup_state.stat.error = NULL;
-+    }
-+
-+    backup_state.stat.start_time = time(NULL);
-+    backup_state.stat.end_time = 0;
-+
-+    if (backup_state.stat.backup_file) {
-+        g_free(backup_state.stat.backup_file);
-+    }
-+    backup_state.stat.backup_file = g_strdup(task->backup_file);
-+
-+    uuid_copy(backup_state.stat.uuid, uuid);
-+    uuid_unparse_lower(uuid, backup_state.stat.uuid_str);
-+    char *uuid_str = g_strdup(backup_state.stat.uuid_str);
-+
-+    backup_state.stat.total = total;
-+    backup_state.stat.transferred = 0;
-+    backup_state.stat.zero_bytes = 0;
-+
-+    qemu_mutex_unlock(&backup_state.stat.lock);
-+
-+    backup_state.speed = (task->has_speed && task->speed > 0) ? task->speed : 0;
-+
-+    backup_state.vmaw = vmaw;
-+    backup_state.pbs = pbs;
-+
-+    backup_state.di_list = di_list;
-+
-+    uuid_info = g_malloc0(sizeof(*uuid_info));
-+    uuid_info->UUID = uuid_str;
-+
-+    task->result = uuid_info;
-+    return;
-+
-+err:
-+
-+    l = di_list;
-+    while (l) {
-+        PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+        l = g_list_next(l);
-+
-+        if (di->target) {
-+            bdrv_unref(di->target);
-+        }
-+
-+        if (di->targetfile[0]) {
-+            unlink(di->targetfile);
-+        }
-+        g_free(di);
-+    }
-+    g_list_free(di_list);
-+
-+    if (devs) {
-+        g_strfreev(devs);
-+    }
-+
-+    if (vmaw) {
-+        Error *err = NULL;
-+        vma_writer_close(vmaw, &err);
-+        unlink(task->backup_file);
-+    }
-+
-+    if (pbs) {
-+        proxmox_backup_disconnect(pbs);
-+    }
-+
-+    if (backup_dir) {
-+        rmdir(backup_dir);
-+    }
-+
-+    task->result = NULL;
-+    return;
-+}
-+
-+UuidInfo *qmp_backup(
-+    const char *backup_file,
-+    bool has_password, const char *password,
-+    bool has_keyfile, const char *keyfile,
-+    bool has_key_password, const char *key_password,
-+    bool has_fingerprint, const char *fingerprint,
-+    bool has_backup_id, const char *backup_id,
-+    bool has_backup_time, int64_t backup_time,
-+    bool has_format, BackupFormat format,
-+    bool has_config_file, const char *config_file,
-+    bool has_firewall_file, const char *firewall_file,
-+    bool has_devlist, const char *devlist,
-+    bool has_speed, int64_t speed, Error **errp)
-+{
-+    QmpBackupTask task = {
-+        .backup_file = backup_file,
-+        .has_password = has_password,
-+        .password = password,
-+        .has_key_password = has_key_password,
-+        .key_password = key_password,
-+        .has_fingerprint = has_fingerprint,
-+        .fingerprint = fingerprint,
-+        .has_backup_id = has_backup_id,
-+        .backup_id = backup_id,
-+        .has_backup_time = has_backup_time,
-+        .backup_time = backup_time,
-+        .has_format = has_format,
-+        .format = format,
-+        .has_config_file = has_config_file,
-+        .config_file = config_file,
-+        .has_firewall_file = has_firewall_file,
-+        .firewall_file = firewall_file,
-+        .has_devlist = has_devlist,
-+        .devlist = devlist,
-+        .has_speed = has_speed,
-+        .speed = speed,
-+        .errp = errp,
-+    };
-+
-+    qemu_mutex_lock(&backup_state.backup_mutex);
-+
-+    block_on_coroutine_fn(pvebackup_co_prepare, &task);
-+
-+    if (*errp == NULL) {
-+        create_backup_jobs();
-+        qemu_mutex_unlock(&backup_state.backup_mutex);
-+        pvebackup_run_next_job();
-+    } else {
-+        qemu_mutex_unlock(&backup_state.backup_mutex);
-+    }
-+
-+    return task.result;
-+}
-+
-+BackupStatus *qmp_query_backup(Error **errp)
-+{
-+    BackupStatus *info = g_malloc0(sizeof(*info));
-+
-+    qemu_mutex_lock(&backup_state.stat.lock);
-+
-+    if (!backup_state.stat.start_time) {
-+        /* not started, return {} */
-+        qemu_mutex_unlock(&backup_state.stat.lock);
-+        return info;
-+    }
-+
-+    info->has_status = true;
-+    info->has_start_time = true;
-+    info->start_time = backup_state.stat.start_time;
-+
-+    if (backup_state.stat.backup_file) {
-+        info->has_backup_file = true;
-+        info->backup_file = g_strdup(backup_state.stat.backup_file);
-+    }
-+
-+    info->has_uuid = true;
-+    info->uuid = g_strdup(backup_state.stat.uuid_str);
-+
-+    if (backup_state.stat.end_time) {
-+        if (backup_state.stat.error) {
-+            info->status = g_strdup("error");
-+            info->has_errmsg = true;
-+            info->errmsg = g_strdup(error_get_pretty(backup_state.stat.error));
-+        } else {
-+            info->status = g_strdup("done");
-+        }
-+        info->has_end_time = true;
-+        info->end_time = backup_state.stat.end_time;
-+    } else {
-+        info->status = g_strdup("active");
-+    }
-+
-+    info->has_total = true;
-+    info->total = backup_state.stat.total;
-+    info->has_zero_bytes = true;
-+    info->zero_bytes = backup_state.stat.zero_bytes;
-+    info->has_transferred = true;
-+    info->transferred = backup_state.stat.transferred;
-+
-+    qemu_mutex_unlock(&backup_state.stat.lock);
-+
-+    return info;
-+}
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index cd69af921e..e4c3de0804 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -744,6 +744,115 @@
- { 'command': 'query-block', 'returns': ['BlockInfo'] }
-+##
-+# @BackupStatus:
-+#
-+# Detailed backup status.
-+#
-+# @status: string describing the current backup status.
-+#          This can be 'active', 'done', 'error'. If this field is not
-+#          returned, no backup process has been initiated
-+#
-+# @errmsg: error message (only returned if status is 'error')
-+#
-+# @total: total amount of bytes involved in the backup process
-+#
-+# @transferred: amount of bytes already backed up.
-+#
-+# @zero-bytes: amount of 'zero' bytes detected.
-+#
-+# @start-time: time (epoch) when backup job started.
-+#
-+# @end-time: time (epoch) when backup job finished.
-+#
-+# @backup-file: backup file name
-+#
-+# @uuid: uuid for this backup job
-+#
-+##
-+{ 'struct': 'BackupStatus',
-+  'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
-+           '*transferred': 'int', '*zero-bytes': 'int',
-+           '*start-time': 'int', '*end-time': 'int',
-+           '*backup-file': 'str', '*uuid': 'str' } }
-+
-+##
-+# @BackupFormat:
-+#
-+# An enumeration of supported backup formats.
-+#
-+# @vma: Proxmox vma backup format
-+##
-+{ 'enum': 'BackupFormat',
-+  'data': [ 'vma', 'dir', 'pbs' ] }
-+
-+##
-+# @backup:
-+#
-+# Starts a VM backup.
-+#
-+# @backup-file: the backup file name
-+#
-+# @format: format of the backup file
-+#
-+# @config-file: a configuration file to include into
-+#               the backup archive.
-+#
-+# @speed: the maximum speed, in bytes per second
-+#
-+# @devlist: list of block device names (separated by ',', ';'
-+#           or ':'). By default the backup includes all writable block devices.
-+#
-+# @password: backup server passsword (required for format 'pbs')
-+#
-+# @keyfile: keyfile used for encryption (optional for format 'pbs')
-+#
-+# @key-password: password for keyfile (optional for format 'pbs')
-+#
-+# @fingerprint: server cert fingerprint (optional for format 'pbs')
-+#
-+# @backup-id: backup ID (required for format 'pbs')
-+#
-+# @backup-time: backup timestamp (Unix epoch, required for format 'pbs')
-+#
-+# Returns: the uuid of the backup job
-+#
-+##
-+{ 'command': 'backup', 'data': { 'backup-file': 'str',
-+                                    '*password': 'str',
-+                                    '*keyfile': 'str',
-+                                    '*key-password': 'str',
-+                                    '*fingerprint': 'str',
-+                                    '*backup-id': 'str',
-+                                    '*backup-time': 'int',
-+                                    '*format': 'BackupFormat',
-+                                    '*config-file': 'str',
-+                                    '*firewall-file': 'str',
-+                                    '*devlist': 'str', '*speed': 'int' },
-+  'returns': 'UuidInfo' }
-+
-+##
-+# @query-backup:
-+#
-+# Returns information about current/last backup task.
-+#
-+# Returns: @BackupStatus
-+#
-+##
-+{ 'command': 'query-backup', 'returns': 'BackupStatus' }
-+
-+##
-+# @backup-cancel:
-+#
-+# Cancel the current executing backup process.
-+#
-+# Returns: nothing on success
-+#
-+# Notes: This command succeeds even if there is no backup process running.
-+#
-+##
-+{ 'command': 'backup-cancel' }
-+
- ##
- # @BlockDeviceTimedStats:
- #
-diff --git a/qapi/common.json b/qapi/common.json
-index 412cc4f5ae..3e7a77ea66 100644
---- a/qapi/common.json
-+++ b/qapi/common.json
-@@ -208,3 +208,16 @@
- ##
- { 'struct': 'HumanReadableText',
-   'data': { 'human-readable-text': 'str' } }
-+
-+##
-+# @UuidInfo:
-+#
-+# Guest UUID information (Universally Unique Identifier).
-+#
-+# @UUID: the UUID of the guest
-+#
-+# Since: 0.14.0
-+#
-+# Notes: If no UUID was specified for the guest, a null UUID is returned.
-+##
-+{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }
-diff --git a/qapi/machine.json b/qapi/machine.json
-index a05c46e253..e2cec7922f 100644
---- a/qapi/machine.json
-+++ b/qapi/machine.json
-@@ -4,6 +4,8 @@
- # This work is licensed under the terms of the GNU GPL, version 2 or later.
- # See the COPYING file in the top-level directory.
-+{ 'include': 'common.json' }
-+
- ##
- # = Machines
- ##
-@@ -229,19 +231,6 @@
- ##
- { 'command': 'query-target', 'returns': 'TargetInfo' }
--##
--# @UuidInfo:
--#
--# Guest UUID information (Universally Unique Identifier).
--#
--# @UUID: the UUID of the guest
--#
--# Since: 0.14
--#
--# Notes: If no UUID was specified for the guest, a null UUID is returned.
--##
--{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }
--
- ##
- # @query-uuid:
- #
diff --git a/debian/patches/pve/0028-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch b/debian/patches/pve/0028-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch
deleted file mode 100644 (file)
index 132d390..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Dietmar Maurer <dietmar@proxmox.com>
-Date: Mon, 6 Apr 2020 12:17:01 +0200
-Subject: [PATCH] PVE-Backup: pbs-restore - new command to restore from proxmox
- backup server
-
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- meson.build   |   4 +
- pbs-restore.c | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++
- 2 files changed, 228 insertions(+)
- create mode 100644 pbs-restore.c
-
-diff --git a/meson.build b/meson.build
-index 37dab249cc..1a4dfab4e2 100644
---- a/meson.build
-+++ b/meson.build
-@@ -3076,6 +3076,10 @@ if have_tools
-   vma = executable('vma', files('vma.c', 'vma-reader.c') + genh,
-                    dependencies: [authz, block, crypto, io, qom], install: true)
-+  pbs_restore = executable('pbs-restore', files('pbs-restore.c') + genh,
-+                  dependencies: [authz, block, crypto, io, qom,
-+                    libproxmox_backup_qemu], install: true)
-+
-   subdir('storage-daemon')
-   subdir('contrib/rdmacm-mux')
-   subdir('contrib/elf2dmp')
-diff --git a/pbs-restore.c b/pbs-restore.c
-new file mode 100644
-index 0000000000..4d3f925a1b
---- /dev/null
-+++ b/pbs-restore.c
-@@ -0,0 +1,224 @@
-+/*
-+ * Qemu image restore helper for Proxmox Backup
-+ *
-+ * Copyright (C) 2019 Proxmox Server Solutions
-+ *
-+ * Authors:
-+ *  Dietmar Maurer (dietmar@proxmox.com)
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ *
-+ */
-+
-+#include "qemu/osdep.h"
-+#include <glib.h>
-+#include <getopt.h>
-+#include <string.h>
-+
-+#include "qemu-common.h"
-+#include "qemu/module.h"
-+#include "qemu/error-report.h"
-+#include "qemu/main-loop.h"
-+#include "qemu/cutils.h"
-+#include "qapi/error.h"
-+#include "qapi/qmp/qdict.h"
-+#include "sysemu/block-backend.h"
-+
-+#include <proxmox-backup-qemu.h>
-+
-+static void help(void)
-+{
-+    const char *help_msg =
-+        "usage: pbs-restore [--repository <repo>] snapshot archive-name target [command options]\n"
-+        ;
-+
-+    printf("%s", help_msg);
-+    exit(1);
-+}
-+
-+typedef struct CallbackData {
-+    BlockBackend *target;
-+    uint64_t last_offset;
-+    bool skip_zero;
-+} CallbackData;
-+
-+static int write_callback(
-+    void *callback_data_ptr,
-+    uint64_t offset,
-+    const unsigned char *data,
-+    uint64_t data_len)
-+{
-+    int res = -1;
-+
-+    CallbackData *callback_data = (CallbackData *)callback_data_ptr;
-+
-+    uint64_t last_offset = callback_data->last_offset;
-+    if (offset > last_offset) callback_data->last_offset = offset;
-+
-+    if (data == NULL) {
-+        if (callback_data->skip_zero && offset > last_offset) {
-+            return 0;
-+        }
-+        res = blk_pwrite_zeroes(callback_data->target, offset, data_len, 0);
-+    } else {
-+        res = blk_pwrite(callback_data->target, offset, data, data_len, 0);
-+    }
-+
-+    if (res < 0) {
-+        fprintf(stderr, "blk_pwrite failed at offset %ld length %ld (%d) - %s\n", offset, data_len, res, strerror(-res));
-+        return res;
-+    }
-+
-+    return 0;
-+}
-+
-+int main(int argc, char **argv)
-+{
-+    Error *main_loop_err = NULL;
-+    const char *format = "raw";
-+    const char *repository = NULL;
-+    const char *keyfile = NULL;
-+    int verbose = false;
-+    bool skip_zero = false;
-+
-+    error_init(argv[0]);
-+
-+    for (;;) {
-+        static const struct option long_options[] = {
-+            {"help", no_argument, 0, 'h'},
-+            {"skip-zero", no_argument, 0, 'S'},
-+            {"verbose", no_argument, 0, 'v'},
-+            {"format", required_argument, 0, 'f'},
-+            {"repository", required_argument, 0, 'r'},
-+            {"keyfile", required_argument, 0, 'k'},
-+            {0, 0, 0, 0}
-+        };
-+        int c = getopt_long(argc, argv, "hvf:r:k:", long_options, NULL);
-+        if (c == -1) {
-+            break;
-+        }
-+        switch (c) {
-+            case ':':
-+                fprintf(stderr, "missing argument for option '%s'\n", argv[optind - 1]);
-+                return -1;
-+            case '?':
-+                fprintf(stderr, "unrecognized option '%s'\n", argv[optind - 1]);
-+                return -1;
-+            case 'f':
-+                format = g_strdup(argv[optind - 1]);
-+                break;
-+            case 'r':
-+                repository = g_strdup(argv[optind - 1]);
-+                break;
-+            case 'k':
-+                keyfile = g_strdup(argv[optind - 1]);
-+                break;
-+            case 'v':
-+                verbose = true;
-+                break;
-+            case 'S':
-+                skip_zero = true;
-+                break;
-+            case 'h':
-+                help();
-+                return 0;
-+        }
-+    }
-+
-+    if (optind >= argc - 2) {
-+        fprintf(stderr, "missing arguments\n");
-+        help();
-+        return -1;
-+    }
-+
-+    if (repository == NULL) {
-+        repository = getenv("PBS_REPOSITORY");
-+    }
-+
-+    if (repository == NULL) {
-+        fprintf(stderr, "no repository specified\n");
-+        help();
-+        return -1;
-+    }
-+
-+    char *snapshot = argv[optind++];
-+    char *archive_name = argv[optind++];
-+    char *target = argv[optind++];
-+
-+    const char *password = getenv("PBS_PASSWORD");
-+    const char *fingerprint = getenv("PBS_FINGERPRINT");
-+    const char *key_password = getenv("PBS_ENCRYPTION_PASSWORD");
-+
-+    if (qemu_init_main_loop(&main_loop_err)) {
-+        g_error("%s", error_get_pretty(main_loop_err));
-+    }
-+
-+    bdrv_init();
-+    module_call_init(MODULE_INIT_QOM);
-+
-+    if (verbose) {
-+        fprintf(stderr, "connecting to repository '%s'\n", repository);
-+    }
-+    char *pbs_error = NULL;
-+    ProxmoxRestoreHandle *conn = proxmox_restore_new(
-+        repository, snapshot, password, keyfile, key_password, fingerprint, &pbs_error);
-+    if (conn == NULL) {
-+        fprintf(stderr, "restore failed: %s\n", pbs_error);
-+        return -1;
-+    }
-+
-+    int res = proxmox_restore_connect(conn, &pbs_error);
-+    if (res < 0 || pbs_error) {
-+        fprintf(stderr, "restore failed (connection error): %s\n", pbs_error);
-+        return -1;
-+    }
-+
-+    QDict *options = qdict_new();
-+
-+    if (format) {
-+        qdict_put_str(options, "driver", format);
-+    }
-+
-+
-+    if (verbose) {
-+        fprintf(stderr, "open block backend for target '%s'\n", target);
-+    }
-+    Error *local_err = NULL;
-+    int flags = BDRV_O_RDWR;
-+    BlockBackend *blk = blk_new_open(target, NULL, options, flags, &local_err);
-+    if (!blk) {
-+        fprintf(stderr, "%s\n", error_get_pretty(local_err));
-+        return -1;
-+    }
-+
-+    CallbackData *callback_data = calloc(sizeof(CallbackData), 1);
-+
-+    callback_data->target = blk;
-+    callback_data->skip_zero = skip_zero;
-+    callback_data->last_offset = 0;
-+
-+    // blk_set_enable_write_cache(blk, !writethrough);
-+
-+    if (verbose) {
-+        fprintf(stderr, "starting to restore snapshot '%s'\n", snapshot);
-+        fflush(stderr); // ensure we do not get printed after the progress log
-+    }
-+    res = proxmox_restore_image(
-+        conn,
-+        archive_name,
-+        write_callback,
-+        callback_data,
-+        &pbs_error,
-+        verbose);
-+
-+    proxmox_restore_disconnect(conn);
-+    blk_unref(blk);
-+
-+    if (res < 0) {
-+        fprintf(stderr, "restore failed: %s\n", pbs_error);
-+        return -1;
-+    }
-+
-+    return 0;
-+}
diff --git a/debian/patches/pve/0028-PVE-Backup-proxmox-backup-patches-for-qemu.patch b/debian/patches/pve/0028-PVE-Backup-proxmox-backup-patches-for-qemu.patch
new file mode 100644 (file)
index 0000000..8d302b9
--- /dev/null
@@ -0,0 +1,1653 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Dietmar Maurer <dietmar@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:59 +0200
+Subject: [PATCH] PVE-Backup: proxmox backup patches for qemu
+
+Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
+[PVE-Backup: avoid coroutines to fix AIO freeze, cleanups]
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[add new force parameter to job_cancel_sync calls]
+Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
+---
+ block/meson.build              |   5 +
+ block/monitor/block-hmp-cmds.c |  33 ++
+ blockdev.c                     |   1 +
+ hmp-commands-info.hx           |  14 +
+ hmp-commands.hx                |  29 +
+ include/block/block_int.h      |   2 +-
+ include/monitor/hmp.h          |   3 +
+ meson.build                    |   1 +
+ monitor/hmp-cmds.c             |  44 ++
+ proxmox-backup-client.c        | 176 ++++++
+ proxmox-backup-client.h        |  59 ++
+ pve-backup.c                   | 959 +++++++++++++++++++++++++++++++++
+ qapi/block-core.json           | 109 ++++
+ qapi/common.json               |  13 +
+ qapi/machine.json              |  15 +-
+ 15 files changed, 1449 insertions(+), 14 deletions(-)
+ create mode 100644 proxmox-backup-client.c
+ create mode 100644 proxmox-backup-client.h
+ create mode 100644 pve-backup.c
+
+diff --git a/block/meson.build b/block/meson.build
+index 7883df047c..9d3dd5b7c3 100644
+--- a/block/meson.build
++++ b/block/meson.build
+@@ -46,6 +46,11 @@ block_ss.add(files(
+ ), zstd, zlib, gnutls)
+ block_ss.add(files('../vma-writer.c'), libuuid)
++block_ss.add(files(
++  '../proxmox-backup-client.c',
++  '../pve-backup.c',
++), libproxmox_backup_qemu)
++
+ softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c'))
+diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
+index 2ac4aedfff..f6668ab01d 100644
+--- a/block/monitor/block-hmp-cmds.c
++++ b/block/monitor/block-hmp-cmds.c
+@@ -1015,3 +1015,36 @@ void hmp_info_snapshots(Monitor *mon, const QDict *qdict)
+     g_free(sn_tab);
+     g_free(global_snapshots);
+ }
++
++void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
++{
++    Error *error = NULL;
++
++    qmp_backup_cancel(&error);
++
++    hmp_handle_error(mon, error);
++}
++
++void hmp_backup(Monitor *mon, const QDict *qdict)
++{
++    Error *error = NULL;
++
++    int dir = qdict_get_try_bool(qdict, "directory", 0);
++    const char *backup_file = qdict_get_str(qdict, "backupfile");
++    const char *devlist = qdict_get_try_str(qdict, "devlist");
++    int64_t speed = qdict_get_try_int(qdict, "speed", 0);
++
++    qmp_backup(
++        backup_file,
++        false, NULL, // PBS password
++        false, NULL, // PBS keyfile
++        false, NULL, // PBS key_password
++        false, NULL, // PBS fingerprint
++        false, NULL, // PBS backup-id
++        false, 0, // PBS backup-time
++        true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
++        false, NULL, false, NULL, !!devlist,
++        devlist, qdict_haskey(qdict, "speed"), speed, &error);
++
++    hmp_handle_error(mon, error);
++}
+diff --git a/blockdev.c b/blockdev.c
+index 4be0863050..29fee73cbd 100644
+--- a/blockdev.c
++++ b/blockdev.c
+@@ -36,6 +36,7 @@
+ #include "hw/block/block.h"
+ #include "block/blockjob.h"
+ #include "block/qdict.h"
++#include "block/blockjob_int.h"
+ #include "block/throttle-groups.h"
+ #include "monitor/monitor.h"
+ #include "qemu/error-report.h"
+diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
+index 245f8acc55..3e7f2421eb 100644
+--- a/hmp-commands-info.hx
++++ b/hmp-commands-info.hx
+@@ -482,6 +482,20 @@ SRST
+     Show the current VM UUID.
+ ERST
++
++    {
++        .name       = "backup",
++        .args_type  = "",
++        .params     = "",
++        .help       = "show backup status",
++        .cmd        = hmp_info_backup,
++    },
++
++SRST
++  ``info backup``
++    Show backup status.
++ERST
++
+ #if defined(CONFIG_SLIRP)
+     {
+         .name       = "usernet",
+diff --git a/hmp-commands.hx b/hmp-commands.hx
+index 1ad13b668b..d4bb00216e 100644
+--- a/hmp-commands.hx
++++ b/hmp-commands.hx
+@@ -99,6 +99,35 @@ ERST
+ SRST
+ ``block_stream``
+   Copy data from a backing file into a block device.
++ERST
++
++   {
++        .name       = "backup",
++        .args_type  = "directory:-d,backupfile:s,speed:o?,devlist:s?",
++        .params     = "[-d] backupfile [speed [devlist]]",
++        .help       = "create a VM Backup."
++                  "\n\t\t\t Use -d to dump data into a directory instead"
++                  "\n\t\t\t of using VMA format.",
++        .cmd = hmp_backup,
++    },
++
++SRST
++``backup``
++  Create a VM backup.
++ERST
++
++    {
++        .name       = "backup_cancel",
++        .args_type  = "",
++        .params     = "",
++        .help       = "cancel the current VM backup",
++        .cmd        = hmp_backup_cancel,
++    },
++
++SRST
++``backup_cancel``
++  Cancel the current VM backup.
++
+ ERST
+     {
+diff --git a/include/block/block_int.h b/include/block/block_int.h
+index 1574b5564b..77076d7be3 100644
+--- a/include/block/block_int.h
++++ b/include/block/block_int.h
+@@ -67,7 +67,7 @@
+ typedef int BackupDumpFunc(void *opaque, uint64_t offset, uint64_t bytes, const void *buf);
+-BlockDriverState *bdrv_backuo_dump_create(
++BlockDriverState *bdrv_backup_dump_create(
+     int dump_cb_block_size,
+     uint64_t byte_size,
+     BackupDumpFunc *dump_cb,
+diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h
+index 3a39ba41b5..d269b4c99c 100644
+--- a/include/monitor/hmp.h
++++ b/include/monitor/hmp.h
+@@ -30,6 +30,7 @@ void hmp_info_savevm(Monitor *mon, const QDict *qdict);
+ void hmp_info_migrate(Monitor *mon, const QDict *qdict);
+ void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict);
+ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict);
++void hmp_info_backup(Monitor *mon, const QDict *qdict);
+ void hmp_info_cpus(Monitor *mon, const QDict *qdict);
+ void hmp_info_vnc(Monitor *mon, const QDict *qdict);
+ void hmp_info_spice(Monitor *mon, const QDict *qdict);
+@@ -73,6 +74,8 @@ void hmp_x_colo_lost_heartbeat(Monitor *mon, const QDict *qdict);
+ void hmp_set_password(Monitor *mon, const QDict *qdict);
+ void hmp_expire_password(Monitor *mon, const QDict *qdict);
+ void hmp_change(Monitor *mon, const QDict *qdict);
++void hmp_backup(Monitor *mon, const QDict *qdict);
++void hmp_backup_cancel(Monitor *mon, const QDict *qdict);
+ void hmp_migrate(Monitor *mon, const QDict *qdict);
+ void hmp_device_add(Monitor *mon, const QDict *qdict);
+ void hmp_device_del(Monitor *mon, const QDict *qdict);
+diff --git a/meson.build b/meson.build
+index 54c23b9567..37dab249cc 100644
+--- a/meson.build
++++ b/meson.build
+@@ -1203,6 +1203,7 @@ keyutils = dependency('libkeyutils', required: false,
+ has_gettid = cc.has_function('gettid')
+ libuuid = cc.find_library('uuid', required: true)
++libproxmox_backup_qemu = cc.find_library('proxmox_backup_qemu', required: true)
+ # libselinux
+ selinux = dependency('libselinux',
+diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
+index 5000ce39d1..b2687eae3a 100644
+--- a/monitor/hmp-cmds.c
++++ b/monitor/hmp-cmds.c
+@@ -195,6 +195,50 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
+     qapi_free_MouseInfoList(mice_list);
+ }
++void hmp_info_backup(Monitor *mon, const QDict *qdict)
++{
++    BackupStatus *info;
++
++    info = qmp_query_backup(NULL);
++
++    if (!info) {
++       monitor_printf(mon, "Backup status: not initialized\n");
++       return;
++    }
++
++    if (info->has_status) {
++        if (info->has_errmsg) {
++            monitor_printf(mon, "Backup status: %s - %s\n",
++                           info->status, info->errmsg);
++        } else {
++            monitor_printf(mon, "Backup status: %s\n", info->status);
++        }
++    }
++
++    if (info->has_backup_file) {
++        monitor_printf(mon, "Start time: %s", ctime(&info->start_time));
++        if (info->end_time) {
++            monitor_printf(mon, "End time: %s", ctime(&info->end_time));
++        }
++
++        int per = (info->has_total && info->total &&
++            info->has_transferred && info->transferred) ?
++            (info->transferred * 100)/info->total : 0;
++        int zero_per = (info->has_total && info->total &&
++                        info->has_zero_bytes && info->zero_bytes) ?
++            (info->zero_bytes * 100)/info->total : 0;
++        monitor_printf(mon, "Backup file: %s\n", info->backup_file);
++        monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
++        monitor_printf(mon, "Total size: %zd\n", info->total);
++        monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
++                       info->transferred, per);
++        monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
++                       info->zero_bytes, zero_per);
++    }
++
++    qapi_free_BackupStatus(info);
++}
++
+ static char *SocketAddress_to_str(SocketAddress *addr)
+ {
+     switch (addr->type) {
+diff --git a/proxmox-backup-client.c b/proxmox-backup-client.c
+new file mode 100644
+index 0000000000..a8f6653a81
+--- /dev/null
++++ b/proxmox-backup-client.c
+@@ -0,0 +1,176 @@
++#include "proxmox-backup-client.h"
++#include "qemu/main-loop.h"
++#include "block/aio-wait.h"
++#include "qapi/error.h"
++
++/* Proxmox Backup Server client bindings using coroutines */
++
++typedef struct BlockOnCoroutineWrapper {
++    AioContext *ctx;
++    CoroutineEntry *entry;
++    void *entry_arg;
++    bool finished;
++} BlockOnCoroutineWrapper;
++
++static void coroutine_fn block_on_coroutine_wrapper(void *opaque)
++{
++    BlockOnCoroutineWrapper *wrapper = opaque;
++    wrapper->entry(wrapper->entry_arg);
++    wrapper->finished = true;
++    aio_wait_kick();
++}
++
++void block_on_coroutine_fn(CoroutineEntry *entry, void *entry_arg)
++{
++    assert(!qemu_in_coroutine());
++
++    AioContext *ctx = qemu_get_current_aio_context();
++    BlockOnCoroutineWrapper wrapper = {
++        .finished = false,
++        .entry = entry,
++        .entry_arg = entry_arg,
++        .ctx = ctx,
++    };
++    Coroutine *wrapper_co = qemu_coroutine_create(block_on_coroutine_wrapper, &wrapper);
++    aio_co_enter(ctx, wrapper_co);
++    AIO_WAIT_WHILE(ctx, !wrapper.finished);
++}
++
++// This is called from another thread, so we use aio_co_schedule()
++static void proxmox_backup_schedule_wake(void *data) {
++    CoCtxData *waker = (CoCtxData *)data;
++    aio_co_schedule(waker->ctx, waker->co);
++}
++
++int coroutine_fn
++proxmox_backup_co_connect(ProxmoxBackupHandle *pbs, Error **errp)
++{
++    Coroutine *co = qemu_coroutine_self();
++    AioContext *ctx = qemu_get_current_aio_context();
++    CoCtxData waker = { .co = co, .ctx = ctx };
++    char *pbs_err = NULL;
++    int pbs_res = -1;
++
++    proxmox_backup_connect_async(pbs, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
++    qemu_coroutine_yield();
++    if (pbs_res < 0) {
++        if (errp) error_setg(errp, "backup connect failed: %s", pbs_err ? pbs_err : "unknown error");
++        if (pbs_err) proxmox_backup_free_error(pbs_err);
++    }
++    return pbs_res;
++}
++
++int coroutine_fn
++proxmox_backup_co_add_config(
++    ProxmoxBackupHandle *pbs,
++    const char *name,
++    const uint8_t *data,
++    uint64_t size,
++    Error **errp)
++{
++    Coroutine *co = qemu_coroutine_self();
++    AioContext *ctx = qemu_get_current_aio_context();
++    CoCtxData waker = { .co = co, .ctx = ctx };
++    char *pbs_err = NULL;
++    int pbs_res = -1;
++
++    proxmox_backup_add_config_async(
++        pbs, name, data, size ,proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
++    qemu_coroutine_yield();
++    if (pbs_res < 0) {
++        if (errp) error_setg(errp, "backup add_config %s failed: %s", name, pbs_err ? pbs_err : "unknown error");
++        if (pbs_err) proxmox_backup_free_error(pbs_err);
++    }
++    return pbs_res;
++}
++
++int coroutine_fn
++proxmox_backup_co_register_image(
++    ProxmoxBackupHandle *pbs,
++    const char *device_name,
++    uint64_t size,
++    Error **errp)
++{
++    Coroutine *co = qemu_coroutine_self();
++    AioContext *ctx = qemu_get_current_aio_context();
++    CoCtxData waker = { .co = co, .ctx = ctx };
++    char *pbs_err = NULL;
++    int pbs_res = -1;
++
++    proxmox_backup_register_image_async(
++        pbs, device_name, size ,proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
++    qemu_coroutine_yield();
++    if (pbs_res < 0) {
++        if (errp) error_setg(errp, "backup register image failed: %s", pbs_err ? pbs_err : "unknown error");
++        if (pbs_err) proxmox_backup_free_error(pbs_err);
++    }
++    return pbs_res;
++}
++
++int coroutine_fn
++proxmox_backup_co_finish(
++    ProxmoxBackupHandle *pbs,
++    Error **errp)
++{
++    Coroutine *co = qemu_coroutine_self();
++    AioContext *ctx = qemu_get_current_aio_context();
++    CoCtxData waker = { .co = co, .ctx = ctx };
++    char *pbs_err = NULL;
++    int pbs_res = -1;
++
++    proxmox_backup_finish_async(
++        pbs, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
++    qemu_coroutine_yield();
++    if (pbs_res < 0) {
++        if (errp) error_setg(errp, "backup finish failed: %s", pbs_err ? pbs_err : "unknown error");
++        if (pbs_err) proxmox_backup_free_error(pbs_err);
++    }
++    return pbs_res;
++}
++
++int coroutine_fn
++proxmox_backup_co_close_image(
++    ProxmoxBackupHandle *pbs,
++    uint8_t dev_id,
++    Error **errp)
++{
++    Coroutine *co = qemu_coroutine_self();
++    AioContext *ctx = qemu_get_current_aio_context();
++    CoCtxData waker = { .co = co, .ctx = ctx };
++    char *pbs_err = NULL;
++    int pbs_res = -1;
++
++    proxmox_backup_close_image_async(
++        pbs, dev_id, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
++    qemu_coroutine_yield();
++    if (pbs_res < 0) {
++        if (errp) error_setg(errp, "backup close image failed: %s", pbs_err ? pbs_err : "unknown error");
++        if (pbs_err) proxmox_backup_free_error(pbs_err);
++    }
++    return pbs_res;
++}
++
++int coroutine_fn
++proxmox_backup_co_write_data(
++    ProxmoxBackupHandle *pbs,
++    uint8_t dev_id,
++    const uint8_t *data,
++    uint64_t offset,
++    uint64_t size,
++    Error **errp)
++{
++    Coroutine *co = qemu_coroutine_self();
++    AioContext *ctx = qemu_get_current_aio_context();
++    CoCtxData waker = { .co = co, .ctx = ctx };
++    char *pbs_err = NULL;
++    int pbs_res = -1;
++
++    proxmox_backup_write_data_async(
++        pbs, dev_id, data, offset, size, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
++    qemu_coroutine_yield();
++    if (pbs_res < 0) {
++        if (errp) error_setg(errp, "backup write data failed: %s", pbs_err ? pbs_err : "unknown error");
++        if (pbs_err) proxmox_backup_free_error(pbs_err);
++    }
++    return pbs_res;
++}
+diff --git a/proxmox-backup-client.h b/proxmox-backup-client.h
+new file mode 100644
+index 0000000000..1dda8b7d8f
+--- /dev/null
++++ b/proxmox-backup-client.h
+@@ -0,0 +1,59 @@
++#ifndef PROXMOX_BACKUP_CLIENT_H
++#define PROXMOX_BACKUP_CLIENT_H
++
++#include "qemu/osdep.h"
++#include "qemu/coroutine.h"
++#include "proxmox-backup-qemu.h"
++
++typedef struct CoCtxData {
++    Coroutine *co;
++    AioContext *ctx;
++    void *data;
++} CoCtxData;
++
++// FIXME: Remove once coroutines are supported for QMP
++void block_on_coroutine_fn(CoroutineEntry *entry, void *entry_arg);
++
++int coroutine_fn
++proxmox_backup_co_connect(
++    ProxmoxBackupHandle *pbs,
++    Error **errp);
++
++int coroutine_fn
++proxmox_backup_co_add_config(
++    ProxmoxBackupHandle *pbs,
++    const char *name,
++    const uint8_t *data,
++    uint64_t size,
++    Error **errp);
++
++int coroutine_fn
++proxmox_backup_co_register_image(
++    ProxmoxBackupHandle *pbs,
++    const char *device_name,
++    uint64_t size,
++    Error **errp);
++
++
++int coroutine_fn
++proxmox_backup_co_finish(
++    ProxmoxBackupHandle *pbs,
++    Error **errp);
++
++int coroutine_fn
++proxmox_backup_co_close_image(
++    ProxmoxBackupHandle *pbs,
++    uint8_t dev_id,
++    Error **errp);
++
++int coroutine_fn
++proxmox_backup_co_write_data(
++    ProxmoxBackupHandle *pbs,
++    uint8_t dev_id,
++    const uint8_t *data,
++    uint64_t offset,
++    uint64_t size,
++    Error **errp);
++
++
++#endif /* PROXMOX_BACKUP_CLIENT_H */
+diff --git a/pve-backup.c b/pve-backup.c
+new file mode 100644
+index 0000000000..88f5ee133f
+--- /dev/null
++++ b/pve-backup.c
+@@ -0,0 +1,959 @@
++#include "proxmox-backup-client.h"
++#include "vma.h"
++
++#include "qemu/osdep.h"
++#include "qemu/module.h"
++#include "sysemu/block-backend.h"
++#include "sysemu/blockdev.h"
++#include "block/blockjob.h"
++#include "qapi/qapi-commands-block.h"
++#include "qapi/qmp/qerror.h"
++
++/* PVE backup state and related function */
++
++/*
++ * Note: A resume from a qemu_coroutine_yield can happen in a different thread,
++ * so you may not use normal mutexes within coroutines:
++ *
++ * ---bad-example---
++ * qemu_rec_mutex_lock(lock)
++ * ...
++ * qemu_coroutine_yield() // wait for something
++ * // we are now inside a different thread
++ * qemu_rec_mutex_unlock(lock) // Crash - wrong thread!!
++ * ---end-bad-example--
++ *
++ * ==> Always use CoMutext inside coroutines.
++ * ==> Never acquire/release AioContext withing coroutines (because that use QemuRecMutex)
++ *
++ */
++
++static struct PVEBackupState {
++    struct {
++        // Everithing accessed from qmp_backup_query command is protected using lock
++        QemuMutex lock;
++        Error *error;
++        time_t start_time;
++        time_t end_time;
++        char *backup_file;
++        uuid_t uuid;
++        char uuid_str[37];
++        size_t total;
++        size_t transferred;
++        size_t zero_bytes;
++    } stat;
++    int64_t speed;
++    VmaWriter *vmaw;
++    ProxmoxBackupHandle *pbs;
++    GList *di_list;
++    QemuMutex backup_mutex;
++    CoMutex dump_callback_mutex;
++} backup_state;
++
++static void pvebackup_init(void)
++{
++    qemu_mutex_init(&backup_state.stat.lock);
++    qemu_mutex_init(&backup_state.backup_mutex);
++    qemu_co_mutex_init(&backup_state.dump_callback_mutex);
++}
++
++// initialize PVEBackupState at startup
++opts_init(pvebackup_init);
++
++typedef struct PVEBackupDevInfo {
++    BlockDriverState *bs;
++    size_t size;
++    uint8_t dev_id;
++    bool completed;
++    char targetfile[PATH_MAX];
++    BlockDriverState *target;
++} PVEBackupDevInfo;
++
++static void pvebackup_run_next_job(void);
++
++static BlockJob *
++lookup_active_block_job(PVEBackupDevInfo *di)
++{
++    if (!di->completed && di->bs) {
++        for (BlockJob *job = block_job_next(NULL); job; job = block_job_next(job)) {
++            if (job->job.driver->job_type != JOB_TYPE_BACKUP) {
++                continue;
++            }
++
++            BackupBlockJob *bjob = container_of(job, BackupBlockJob, common);
++            if (bjob && bjob->source_bs == di->bs) {
++                return job;
++            }
++        }
++    }
++    return NULL;
++}
++
++static void pvebackup_propagate_error(Error *err)
++{
++    qemu_mutex_lock(&backup_state.stat.lock);
++    error_propagate(&backup_state.stat.error, err);
++    qemu_mutex_unlock(&backup_state.stat.lock);
++}
++
++static bool pvebackup_error_or_canceled(void)
++{
++    qemu_mutex_lock(&backup_state.stat.lock);
++    bool error_or_canceled = !!backup_state.stat.error;
++    qemu_mutex_unlock(&backup_state.stat.lock);
++
++    return error_or_canceled;
++}
++
++static void pvebackup_add_transfered_bytes(size_t transferred, size_t zero_bytes)
++{
++    qemu_mutex_lock(&backup_state.stat.lock);
++    backup_state.stat.zero_bytes += zero_bytes;
++    backup_state.stat.transferred += transferred;
++    qemu_mutex_unlock(&backup_state.stat.lock);
++}
++
++// This may get called from multiple coroutines in multiple io-threads
++// Note1: this may get called after job_cancel()
++static int coroutine_fn
++pvebackup_co_dump_pbs_cb(
++    void *opaque,
++    uint64_t start,
++    uint64_t bytes,
++    const void *pbuf)
++{
++    assert(qemu_in_coroutine());
++
++    const uint64_t size = bytes;
++    const unsigned char *buf = pbuf;
++    PVEBackupDevInfo *di = opaque;
++
++    assert(backup_state.pbs);
++
++    Error *local_err = NULL;
++    int pbs_res = -1;
++
++    qemu_co_mutex_lock(&backup_state.dump_callback_mutex);
++
++    // avoid deadlock if job is cancelled
++    if (pvebackup_error_or_canceled()) {
++        qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
++        return -1;
++    }
++
++    pbs_res = proxmox_backup_co_write_data(backup_state.pbs, di->dev_id, buf, start, size, &local_err);
++    qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
++
++    if (pbs_res < 0) {
++        pvebackup_propagate_error(local_err);
++        return pbs_res;
++    } else {
++        pvebackup_add_transfered_bytes(size, !buf ? size : 0);
++    }
++
++    return size;
++}
++
++// This may get called from multiple coroutines in multiple io-threads
++static int coroutine_fn
++pvebackup_co_dump_vma_cb(
++    void *opaque,
++    uint64_t start,
++    uint64_t bytes,
++    const void *pbuf)
++{
++    assert(qemu_in_coroutine());
++
++    const uint64_t size = bytes;
++    const unsigned char *buf = pbuf;
++    PVEBackupDevInfo *di = opaque;
++
++    int ret = -1;
++
++    assert(backup_state.vmaw);
++
++    uint64_t remaining = size;
++
++    uint64_t cluster_num = start / VMA_CLUSTER_SIZE;
++    if ((cluster_num * VMA_CLUSTER_SIZE) != start) {
++        Error *local_err = NULL;
++        error_setg(&local_err,
++                   "got unaligned write inside backup dump "
++                   "callback (sector %ld)", start);
++        pvebackup_propagate_error(local_err);
++        return -1; // not aligned to cluster size
++    }
++
++    while (remaining > 0) {
++        qemu_co_mutex_lock(&backup_state.dump_callback_mutex);
++        // avoid deadlock if job is cancelled
++        if (pvebackup_error_or_canceled()) {
++            qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
++            return -1;
++        }
++
++        size_t zero_bytes = 0;
++        ret = vma_writer_write(backup_state.vmaw, di->dev_id, cluster_num, buf, &zero_bytes);
++        qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
++
++        ++cluster_num;
++        if (buf) {
++            buf += VMA_CLUSTER_SIZE;
++        }
++        if (ret < 0) {
++            Error *local_err = NULL;
++            vma_writer_error_propagate(backup_state.vmaw, &local_err);
++            pvebackup_propagate_error(local_err);
++            return ret;
++        } else {
++            if (remaining >= VMA_CLUSTER_SIZE) {
++                assert(ret == VMA_CLUSTER_SIZE);
++                pvebackup_add_transfered_bytes(VMA_CLUSTER_SIZE, zero_bytes);
++                remaining -= VMA_CLUSTER_SIZE;
++            } else {
++                assert(ret == remaining);
++                pvebackup_add_transfered_bytes(remaining, zero_bytes);
++                remaining = 0;
++            }
++        }
++    }
++
++    return size;
++}
++
++// assumes the caller holds backup_mutex
++static void coroutine_fn pvebackup_co_cleanup(void *unused)
++{
++    assert(qemu_in_coroutine());
++
++    qemu_mutex_lock(&backup_state.stat.lock);
++    backup_state.stat.end_time = time(NULL);
++    qemu_mutex_unlock(&backup_state.stat.lock);
++
++    if (backup_state.vmaw) {
++        Error *local_err = NULL;
++        vma_writer_close(backup_state.vmaw, &local_err);
++
++        if (local_err != NULL) {
++            pvebackup_propagate_error(local_err);
++         }
++
++        backup_state.vmaw = NULL;
++    }
++
++    if (backup_state.pbs) {
++        if (!pvebackup_error_or_canceled()) {
++            Error *local_err = NULL;
++            proxmox_backup_co_finish(backup_state.pbs, &local_err);
++            if (local_err != NULL) {
++                pvebackup_propagate_error(local_err);
++            }
++        }
++
++        proxmox_backup_disconnect(backup_state.pbs);
++        backup_state.pbs = NULL;
++    }
++
++    g_list_free(backup_state.di_list);
++    backup_state.di_list = NULL;
++}
++
++// assumes the caller holds backup_mutex
++static void coroutine_fn pvebackup_complete_stream(void *opaque)
++{
++    PVEBackupDevInfo *di = opaque;
++
++    bool error_or_canceled = pvebackup_error_or_canceled();
++
++    if (backup_state.vmaw) {
++        vma_writer_close_stream(backup_state.vmaw, di->dev_id);
++    }
++
++    if (backup_state.pbs && !error_or_canceled) {
++        Error *local_err = NULL;
++        proxmox_backup_co_close_image(backup_state.pbs, di->dev_id, &local_err);
++        if (local_err != NULL) {
++            pvebackup_propagate_error(local_err);
++        }
++    }
++}
++
++static void pvebackup_complete_cb(void *opaque, int ret)
++{
++    assert(!qemu_in_coroutine());
++
++    PVEBackupDevInfo *di = opaque;
++
++    qemu_mutex_lock(&backup_state.backup_mutex);
++
++    di->completed = true;
++
++    if (ret < 0) {
++        Error *local_err = NULL;
++        error_setg(&local_err, "job failed with err %d - %s", ret, strerror(-ret));
++        pvebackup_propagate_error(local_err);
++    }
++
++    di->bs = NULL;
++
++    assert(di->target == NULL);
++
++    block_on_coroutine_fn(pvebackup_complete_stream, di);
++
++    // remove self from job queue
++    backup_state.di_list = g_list_remove(backup_state.di_list, di);
++
++    g_free(di);
++
++    qemu_mutex_unlock(&backup_state.backup_mutex);
++
++    pvebackup_run_next_job();
++}
++
++static void pvebackup_cancel(void)
++{
++    assert(!qemu_in_coroutine());
++
++    Error *cancel_err = NULL;
++    error_setg(&cancel_err, "backup canceled");
++    pvebackup_propagate_error(cancel_err);
++
++    qemu_mutex_lock(&backup_state.backup_mutex);
++
++    if (backup_state.vmaw) {
++        /* make sure vma writer does not block anymore */
++        vma_writer_set_error(backup_state.vmaw, "backup canceled");
++    }
++
++    if (backup_state.pbs) {
++        proxmox_backup_abort(backup_state.pbs, "backup canceled");
++    }
++
++    qemu_mutex_unlock(&backup_state.backup_mutex);
++
++    for(;;) {
++
++        BlockJob *next_job = NULL;
++
++        qemu_mutex_lock(&backup_state.backup_mutex);
++
++        GList *l = backup_state.di_list;
++        while (l) {
++            PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++            l = g_list_next(l);
++
++            BlockJob *job = lookup_active_block_job(di);
++            if (job != NULL) {
++                next_job = job;
++                break;
++            }
++        }
++
++        qemu_mutex_unlock(&backup_state.backup_mutex);
++
++        if (next_job) {
++            AioContext *aio_context = next_job->job.aio_context;
++            aio_context_acquire(aio_context);
++            job_cancel_sync(&next_job->job, true);
++            aio_context_release(aio_context);
++        } else {
++            break;
++        }
++    }
++}
++
++void qmp_backup_cancel(Error **errp)
++{
++    pvebackup_cancel();
++}
++
++// assumes the caller holds backup_mutex
++static int coroutine_fn pvebackup_co_add_config(
++    const char *file,
++    const char *name,
++    BackupFormat format,
++    const char *backup_dir,
++    VmaWriter *vmaw,
++    ProxmoxBackupHandle *pbs,
++    Error **errp)
++{
++    int res = 0;
++
++    char *cdata = NULL;
++    gsize clen = 0;
++    GError *err = NULL;
++    if (!g_file_get_contents(file, &cdata, &clen, &err)) {
++        error_setg(errp, "unable to read file '%s'", file);
++        return 1;
++    }
++
++    char *basename = g_path_get_basename(file);
++    if (name == NULL) name = basename;
++
++    if (format == BACKUP_FORMAT_VMA) {
++        if (vma_writer_add_config(vmaw, name, cdata, clen) != 0) {
++            error_setg(errp, "unable to add %s config data to vma archive", file);
++            goto err;
++        }
++    } else if (format == BACKUP_FORMAT_PBS) {
++        if (proxmox_backup_co_add_config(pbs, name, (unsigned char *)cdata, clen, errp) < 0)
++            goto err;
++    } else if (format == BACKUP_FORMAT_DIR) {
++        char config_path[PATH_MAX];
++        snprintf(config_path, PATH_MAX, "%s/%s", backup_dir, name);
++        if (!g_file_set_contents(config_path, cdata, clen, &err)) {
++            error_setg(errp, "unable to write config file '%s'", config_path);
++            goto err;
++        }
++    }
++
++ out:
++    g_free(basename);
++    g_free(cdata);
++    return res;
++
++ err:
++    res = -1;
++    goto out;
++}
++
++bool job_should_pause(Job *job);
++
++static void pvebackup_run_next_job(void)
++{
++    assert(!qemu_in_coroutine());
++
++    qemu_mutex_lock(&backup_state.backup_mutex);
++
++    GList *l = backup_state.di_list;
++    while (l) {
++        PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++        l = g_list_next(l);
++
++        BlockJob *job = lookup_active_block_job(di);
++
++        if (job) {
++            qemu_mutex_unlock(&backup_state.backup_mutex);
++
++            AioContext *aio_context = job->job.aio_context;
++            aio_context_acquire(aio_context);
++
++            if (job_should_pause(&job->job)) {
++                bool error_or_canceled = pvebackup_error_or_canceled();
++                if (error_or_canceled) {
++                    job_cancel_sync(&job->job, true);
++                } else {
++                    job_resume(&job->job);
++                }
++            }
++            aio_context_release(aio_context);
++            return;
++        }
++    }
++
++    block_on_coroutine_fn(pvebackup_co_cleanup, NULL); // no more jobs, run cleanup
++
++    qemu_mutex_unlock(&backup_state.backup_mutex);
++}
++
++static bool create_backup_jobs(void) {
++
++    assert(!qemu_in_coroutine());
++
++    Error *local_err = NULL;
++
++    BackupPerf perf = { .max_workers = 16 };
++
++    /* create and start all jobs (paused state) */
++    GList *l =  backup_state.di_list;
++    while (l) {
++        PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++        l = g_list_next(l);
++
++        assert(di->target != NULL);
++
++        AioContext *aio_context = bdrv_get_aio_context(di->bs);
++        aio_context_acquire(aio_context);
++
++        BlockJob *job = backup_job_create(
++            NULL, di->bs, di->target, backup_state.speed, MIRROR_SYNC_MODE_FULL, NULL,
++            BITMAP_SYNC_MODE_NEVER, false, NULL, &perf, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
++            JOB_DEFAULT, pvebackup_complete_cb, di, NULL, &local_err);
++
++        aio_context_release(aio_context);
++
++        if (!job || local_err != NULL) {
++            Error *create_job_err = NULL;
++            error_setg(&create_job_err, "backup_job_create failed: %s",
++                       local_err ? error_get_pretty(local_err) : "null");
++
++            pvebackup_propagate_error(create_job_err);
++            break;
++        }
++        job_start(&job->job);
++
++        bdrv_unref(di->target);
++        di->target = NULL;
++    }
++
++    bool errors = pvebackup_error_or_canceled();
++
++    if (errors) {
++        l = backup_state.di_list;
++        while (l) {
++            PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++            l = g_list_next(l);
++
++            if (di->target) {
++                bdrv_unref(di->target);
++                di->target = NULL;
++            }
++        }
++    }
++
++    return errors;
++}
++
++typedef struct QmpBackupTask {
++    const char *backup_file;
++    bool has_password;
++    const char *password;
++    bool has_keyfile;
++    const char *keyfile;
++    bool has_key_password;
++    const char *key_password;
++    bool has_backup_id;
++    const char *backup_id;
++    bool has_backup_time;
++    const char *fingerprint;
++    bool has_fingerprint;
++    int64_t backup_time;
++    bool has_format;
++    BackupFormat format;
++    bool has_config_file;
++    const char *config_file;
++    bool has_firewall_file;
++    const char *firewall_file;
++    bool has_devlist;
++    const char *devlist;
++    bool has_speed;
++    int64_t speed;
++    Error **errp;
++    UuidInfo *result;
++} QmpBackupTask;
++
++// assumes the caller holds backup_mutex
++static void coroutine_fn pvebackup_co_prepare(void *opaque)
++{
++    assert(qemu_in_coroutine());
++
++    QmpBackupTask *task = opaque;
++
++    task->result = NULL; // just to be sure
++
++    BlockBackend *blk;
++    BlockDriverState *bs = NULL;
++    const char *backup_dir = NULL;
++    Error *local_err = NULL;
++    uuid_t uuid;
++    VmaWriter *vmaw = NULL;
++    ProxmoxBackupHandle *pbs = NULL;
++    gchar **devs = NULL;
++    GList *di_list = NULL;
++    GList *l;
++    UuidInfo *uuid_info;
++
++    const char *config_name = "qemu-server.conf";
++    const char *firewall_name = "qemu-server.fw";
++
++    if (backup_state.di_list) {
++         error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
++                  "previous backup not finished");
++        return;
++    }
++
++    /* Todo: try to auto-detect format based on file name */
++    BackupFormat format = task->has_format ? task->format : BACKUP_FORMAT_VMA;
++
++    if (task->has_devlist) {
++        devs = g_strsplit_set(task->devlist, ",;:", -1);
++
++        gchar **d = devs;
++        while (d && *d) {
++            blk = blk_by_name(*d);
++            if (blk) {
++                bs = blk_bs(blk);
++                if (!bdrv_is_inserted(bs)) {
++                    error_setg(task->errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
++                    goto err;
++                }
++                PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
++                di->bs = bs;
++                di_list = g_list_append(di_list, di);
++            } else {
++                error_set(task->errp, ERROR_CLASS_DEVICE_NOT_FOUND,
++                          "Device '%s' not found", *d);
++                goto err;
++            }
++            d++;
++        }
++
++    } else {
++        BdrvNextIterator it;
++
++        bs = NULL;
++        for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
++            if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) {
++                continue;
++            }
++
++            PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
++            di->bs = bs;
++            di_list = g_list_append(di_list, di);
++        }
++    }
++
++    if (!di_list) {
++        error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
++        goto err;
++    }
++
++    size_t total = 0;
++
++    l = di_list;
++    while (l) {
++        PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++        l = g_list_next(l);
++        if (bdrv_op_is_blocked(di->bs, BLOCK_OP_TYPE_BACKUP_SOURCE, task->errp)) {
++            goto err;
++        }
++
++        ssize_t size = bdrv_getlength(di->bs);
++        if (size < 0) {
++            error_setg_errno(task->errp, -di->size, "bdrv_getlength failed");
++            goto err;
++        }
++        di->size = size;
++        total += size;
++    }
++
++    uuid_generate(uuid);
++
++    if (format == BACKUP_FORMAT_PBS) {
++        if (!task->has_password) {
++            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
++            goto err;
++        }
++        if (!task->has_backup_id) {
++            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
++            goto err;
++        }
++        if (!task->has_backup_time) {
++            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-time'");
++            goto err;
++        }
++
++        int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
++        firewall_name = "fw.conf";
++
++        char *pbs_err = NULL;
++        pbs = proxmox_backup_new(
++            task->backup_file,
++            task->backup_id,
++            task->backup_time,
++            dump_cb_block_size,
++            task->has_password ? task->password : NULL,
++            task->has_keyfile ? task->keyfile : NULL,
++            task->has_key_password ? task->key_password : NULL,
++            task->has_fingerprint ? task->fingerprint : NULL,
++            &pbs_err);
++
++        if (!pbs) {
++            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
++                      "proxmox_backup_new failed: %s", pbs_err);
++            proxmox_backup_free_error(pbs_err);
++            goto err;
++        }
++
++        if (proxmox_backup_co_connect(pbs, task->errp) < 0)
++            goto err;
++
++        /* register all devices */
++        l = di_list;
++        while (l) {
++            PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++            l = g_list_next(l);
++
++            const char *devname = bdrv_get_device_name(di->bs);
++
++            int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, task->errp);
++            if (dev_id < 0)
++                goto err;
++
++            if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, task->errp))) {
++                goto err;
++            }
++
++            di->dev_id = dev_id;
++        }
++    } else if (format == BACKUP_FORMAT_VMA) {
++        vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
++        if (!vmaw) {
++            if (local_err) {
++                error_propagate(task->errp, local_err);
++            }
++            goto err;
++        }
++
++        /* register all devices for vma writer */
++        l = di_list;
++        while (l) {
++            PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++            l = g_list_next(l);
++
++            if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, task->errp))) {
++                goto err;
++            }
++
++            const char *devname = bdrv_get_device_name(di->bs);
++            di->dev_id = vma_writer_register_stream(vmaw, devname, di->size);
++            if (di->dev_id <= 0) {
++                error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
++                          "register_stream failed");
++                goto err;
++            }
++        }
++    } else if (format == BACKUP_FORMAT_DIR) {
++        if (mkdir(task->backup_file, 0640) != 0) {
++            error_setg_errno(task->errp, errno, "can't create directory '%s'\n",
++                             task->backup_file);
++            goto err;
++        }
++        backup_dir = task->backup_file;
++
++        l = di_list;
++        while (l) {
++            PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++            l = g_list_next(l);
++
++            const char *devname = bdrv_get_device_name(di->bs);
++            snprintf(di->targetfile, PATH_MAX, "%s/%s.raw", backup_dir, devname);
++
++            int flags = BDRV_O_RDWR;
++            bdrv_img_create(di->targetfile, "raw", NULL, NULL, NULL,
++                            di->size, flags, false, &local_err);
++            if (local_err) {
++                error_propagate(task->errp, local_err);
++                goto err;
++            }
++
++            di->target = bdrv_open(di->targetfile, NULL, NULL, flags, &local_err);
++            if (!di->target) {
++                error_propagate(task->errp, local_err);
++                goto err;
++            }
++        }
++    } else {
++        error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
++        goto err;
++    }
++
++
++    /* add configuration file to archive */
++    if (task->has_config_file) {
++        if (pvebackup_co_add_config(task->config_file, config_name, format, backup_dir,
++                                    vmaw, pbs, task->errp) != 0) {
++            goto err;
++        }
++    }
++
++    /* add firewall file to archive */
++    if (task->has_firewall_file) {
++        if (pvebackup_co_add_config(task->firewall_file, firewall_name, format, backup_dir,
++                                    vmaw, pbs, task->errp) != 0) {
++            goto err;
++        }
++    }
++    /* initialize global backup_state now */
++
++    qemu_mutex_lock(&backup_state.stat.lock);
++
++    if (backup_state.stat.error) {
++        error_free(backup_state.stat.error);
++        backup_state.stat.error = NULL;
++    }
++
++    backup_state.stat.start_time = time(NULL);
++    backup_state.stat.end_time = 0;
++
++    if (backup_state.stat.backup_file) {
++        g_free(backup_state.stat.backup_file);
++    }
++    backup_state.stat.backup_file = g_strdup(task->backup_file);
++
++    uuid_copy(backup_state.stat.uuid, uuid);
++    uuid_unparse_lower(uuid, backup_state.stat.uuid_str);
++    char *uuid_str = g_strdup(backup_state.stat.uuid_str);
++
++    backup_state.stat.total = total;
++    backup_state.stat.transferred = 0;
++    backup_state.stat.zero_bytes = 0;
++
++    qemu_mutex_unlock(&backup_state.stat.lock);
++
++    backup_state.speed = (task->has_speed && task->speed > 0) ? task->speed : 0;
++
++    backup_state.vmaw = vmaw;
++    backup_state.pbs = pbs;
++
++    backup_state.di_list = di_list;
++
++    uuid_info = g_malloc0(sizeof(*uuid_info));
++    uuid_info->UUID = uuid_str;
++
++    task->result = uuid_info;
++    return;
++
++err:
++
++    l = di_list;
++    while (l) {
++        PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++        l = g_list_next(l);
++
++        if (di->target) {
++            bdrv_unref(di->target);
++        }
++
++        if (di->targetfile[0]) {
++            unlink(di->targetfile);
++        }
++        g_free(di);
++    }
++    g_list_free(di_list);
++
++    if (devs) {
++        g_strfreev(devs);
++    }
++
++    if (vmaw) {
++        Error *err = NULL;
++        vma_writer_close(vmaw, &err);
++        unlink(task->backup_file);
++    }
++
++    if (pbs) {
++        proxmox_backup_disconnect(pbs);
++    }
++
++    if (backup_dir) {
++        rmdir(backup_dir);
++    }
++
++    task->result = NULL;
++    return;
++}
++
++UuidInfo *qmp_backup(
++    const char *backup_file,
++    bool has_password, const char *password,
++    bool has_keyfile, const char *keyfile,
++    bool has_key_password, const char *key_password,
++    bool has_fingerprint, const char *fingerprint,
++    bool has_backup_id, const char *backup_id,
++    bool has_backup_time, int64_t backup_time,
++    bool has_format, BackupFormat format,
++    bool has_config_file, const char *config_file,
++    bool has_firewall_file, const char *firewall_file,
++    bool has_devlist, const char *devlist,
++    bool has_speed, int64_t speed, Error **errp)
++{
++    QmpBackupTask task = {
++        .backup_file = backup_file,
++        .has_password = has_password,
++        .password = password,
++        .has_key_password = has_key_password,
++        .key_password = key_password,
++        .has_fingerprint = has_fingerprint,
++        .fingerprint = fingerprint,
++        .has_backup_id = has_backup_id,
++        .backup_id = backup_id,
++        .has_backup_time = has_backup_time,
++        .backup_time = backup_time,
++        .has_format = has_format,
++        .format = format,
++        .has_config_file = has_config_file,
++        .config_file = config_file,
++        .has_firewall_file = has_firewall_file,
++        .firewall_file = firewall_file,
++        .has_devlist = has_devlist,
++        .devlist = devlist,
++        .has_speed = has_speed,
++        .speed = speed,
++        .errp = errp,
++    };
++
++    qemu_mutex_lock(&backup_state.backup_mutex);
++
++    block_on_coroutine_fn(pvebackup_co_prepare, &task);
++
++    if (*errp == NULL) {
++        create_backup_jobs();
++        qemu_mutex_unlock(&backup_state.backup_mutex);
++        pvebackup_run_next_job();
++    } else {
++        qemu_mutex_unlock(&backup_state.backup_mutex);
++    }
++
++    return task.result;
++}
++
++BackupStatus *qmp_query_backup(Error **errp)
++{
++    BackupStatus *info = g_malloc0(sizeof(*info));
++
++    qemu_mutex_lock(&backup_state.stat.lock);
++
++    if (!backup_state.stat.start_time) {
++        /* not started, return {} */
++        qemu_mutex_unlock(&backup_state.stat.lock);
++        return info;
++    }
++
++    info->has_status = true;
++    info->has_start_time = true;
++    info->start_time = backup_state.stat.start_time;
++
++    if (backup_state.stat.backup_file) {
++        info->has_backup_file = true;
++        info->backup_file = g_strdup(backup_state.stat.backup_file);
++    }
++
++    info->has_uuid = true;
++    info->uuid = g_strdup(backup_state.stat.uuid_str);
++
++    if (backup_state.stat.end_time) {
++        if (backup_state.stat.error) {
++            info->status = g_strdup("error");
++            info->has_errmsg = true;
++            info->errmsg = g_strdup(error_get_pretty(backup_state.stat.error));
++        } else {
++            info->status = g_strdup("done");
++        }
++        info->has_end_time = true;
++        info->end_time = backup_state.stat.end_time;
++    } else {
++        info->status = g_strdup("active");
++    }
++
++    info->has_total = true;
++    info->total = backup_state.stat.total;
++    info->has_zero_bytes = true;
++    info->zero_bytes = backup_state.stat.zero_bytes;
++    info->has_transferred = true;
++    info->transferred = backup_state.stat.transferred;
++
++    qemu_mutex_unlock(&backup_state.stat.lock);
++
++    return info;
++}
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index cd69af921e..e4c3de0804 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -744,6 +744,115 @@
+ { 'command': 'query-block', 'returns': ['BlockInfo'] }
++##
++# @BackupStatus:
++#
++# Detailed backup status.
++#
++# @status: string describing the current backup status.
++#          This can be 'active', 'done', 'error'. If this field is not
++#          returned, no backup process has been initiated
++#
++# @errmsg: error message (only returned if status is 'error')
++#
++# @total: total amount of bytes involved in the backup process
++#
++# @transferred: amount of bytes already backed up.
++#
++# @zero-bytes: amount of 'zero' bytes detected.
++#
++# @start-time: time (epoch) when backup job started.
++#
++# @end-time: time (epoch) when backup job finished.
++#
++# @backup-file: backup file name
++#
++# @uuid: uuid for this backup job
++#
++##
++{ 'struct': 'BackupStatus',
++  'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
++           '*transferred': 'int', '*zero-bytes': 'int',
++           '*start-time': 'int', '*end-time': 'int',
++           '*backup-file': 'str', '*uuid': 'str' } }
++
++##
++# @BackupFormat:
++#
++# An enumeration of supported backup formats.
++#
++# @vma: Proxmox vma backup format
++##
++{ 'enum': 'BackupFormat',
++  'data': [ 'vma', 'dir', 'pbs' ] }
++
++##
++# @backup:
++#
++# Starts a VM backup.
++#
++# @backup-file: the backup file name
++#
++# @format: format of the backup file
++#
++# @config-file: a configuration file to include into
++#               the backup archive.
++#
++# @speed: the maximum speed, in bytes per second
++#
++# @devlist: list of block device names (separated by ',', ';'
++#           or ':'). By default the backup includes all writable block devices.
++#
++# @password: backup server passsword (required for format 'pbs')
++#
++# @keyfile: keyfile used for encryption (optional for format 'pbs')
++#
++# @key-password: password for keyfile (optional for format 'pbs')
++#
++# @fingerprint: server cert fingerprint (optional for format 'pbs')
++#
++# @backup-id: backup ID (required for format 'pbs')
++#
++# @backup-time: backup timestamp (Unix epoch, required for format 'pbs')
++#
++# Returns: the uuid of the backup job
++#
++##
++{ 'command': 'backup', 'data': { 'backup-file': 'str',
++                                    '*password': 'str',
++                                    '*keyfile': 'str',
++                                    '*key-password': 'str',
++                                    '*fingerprint': 'str',
++                                    '*backup-id': 'str',
++                                    '*backup-time': 'int',
++                                    '*format': 'BackupFormat',
++                                    '*config-file': 'str',
++                                    '*firewall-file': 'str',
++                                    '*devlist': 'str', '*speed': 'int' },
++  'returns': 'UuidInfo' }
++
++##
++# @query-backup:
++#
++# Returns information about current/last backup task.
++#
++# Returns: @BackupStatus
++#
++##
++{ 'command': 'query-backup', 'returns': 'BackupStatus' }
++
++##
++# @backup-cancel:
++#
++# Cancel the current executing backup process.
++#
++# Returns: nothing on success
++#
++# Notes: This command succeeds even if there is no backup process running.
++#
++##
++{ 'command': 'backup-cancel' }
++
+ ##
+ # @BlockDeviceTimedStats:
+ #
+diff --git a/qapi/common.json b/qapi/common.json
+index 412cc4f5ae..3e7a77ea66 100644
+--- a/qapi/common.json
++++ b/qapi/common.json
+@@ -208,3 +208,16 @@
+ ##
+ { 'struct': 'HumanReadableText',
+   'data': { 'human-readable-text': 'str' } }
++
++##
++# @UuidInfo:
++#
++# Guest UUID information (Universally Unique Identifier).
++#
++# @UUID: the UUID of the guest
++#
++# Since: 0.14.0
++#
++# Notes: If no UUID was specified for the guest, a null UUID is returned.
++##
++{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }
+diff --git a/qapi/machine.json b/qapi/machine.json
+index a05c46e253..e2cec7922f 100644
+--- a/qapi/machine.json
++++ b/qapi/machine.json
+@@ -4,6 +4,8 @@
+ # This work is licensed under the terms of the GNU GPL, version 2 or later.
+ # See the COPYING file in the top-level directory.
++{ 'include': 'common.json' }
++
+ ##
+ # = Machines
+ ##
+@@ -229,19 +231,6 @@
+ ##
+ { 'command': 'query-target', 'returns': 'TargetInfo' }
+-##
+-# @UuidInfo:
+-#
+-# Guest UUID information (Universally Unique Identifier).
+-#
+-# @UUID: the UUID of the guest
+-#
+-# Since: 0.14
+-#
+-# Notes: If no UUID was specified for the guest, a null UUID is returned.
+-##
+-{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }
+-
+ ##
+ # @query-uuid:
+ #
diff --git a/debian/patches/pve/0029-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch b/debian/patches/pve/0029-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch
deleted file mode 100644 (file)
index ff991d2..0000000
+++ /dev/null
@@ -1,452 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Mon, 29 Jun 2020 11:06:03 +0200
-Subject: [PATCH] PVE-Backup: Add dirty-bitmap tracking for incremental backups
-
-Uses QEMU's existing MIRROR_SYNC_MODE_BITMAP and a dirty-bitmap on top
-of all backed-up drives. This will only execute the data-write callback
-for any changed chunks, the PBS rust code will reuse chunks from the
-previous index for everything it doesn't receive if reuse_index is true.
-
-On error or cancellation, remove all dirty bitmaps to ensure
-consistency.
-
-Add PBS/incremental specific information to query backup info QMP and
-HMP commands.
-
-Only supported for PBS backups.
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- block/monitor/block-hmp-cmds.c |   1 +
- monitor/hmp-cmds.c             |  45 ++++++++++----
- proxmox-backup-client.c        |   3 +-
- proxmox-backup-client.h        |   1 +
- pve-backup.c                   | 103 ++++++++++++++++++++++++++++++---
- qapi/block-core.json           |  12 +++-
- 6 files changed, 142 insertions(+), 23 deletions(-)
-
-diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
-index f6668ab01d..3c06734e6d 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1042,6 +1042,7 @@ void hmp_backup(Monitor *mon, const QDict *qdict)
-         false, NULL, // PBS fingerprint
-         false, NULL, // PBS backup-id
-         false, 0, // PBS backup-time
-+        false, false, // PBS incremental
-         true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
-         false, NULL, false, NULL, !!devlist,
-         devlist, qdict_haskey(qdict, "speed"), speed, &error);
-diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
-index b2687eae3a..cfd7a60f32 100644
---- a/monitor/hmp-cmds.c
-+++ b/monitor/hmp-cmds.c
-@@ -221,19 +221,42 @@ void hmp_info_backup(Monitor *mon, const QDict *qdict)
-             monitor_printf(mon, "End time: %s", ctime(&info->end_time));
-         }
--        int per = (info->has_total && info->total &&
--            info->has_transferred && info->transferred) ?
--            (info->transferred * 100)/info->total : 0;
--        int zero_per = (info->has_total && info->total &&
--                        info->has_zero_bytes && info->zero_bytes) ?
--            (info->zero_bytes * 100)/info->total : 0;
-         monitor_printf(mon, "Backup file: %s\n", info->backup_file);
-         monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
--        monitor_printf(mon, "Total size: %zd\n", info->total);
--        monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
--                       info->transferred, per);
--        monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
--                       info->zero_bytes, zero_per);
-+
-+        if (!(info->has_total && info->total))  {
-+            // this should not happen normally
-+            monitor_printf(mon, "Total size: %d\n", 0);
-+        } else {
-+            bool incremental = false;
-+            size_t total_or_dirty = info->total;
-+            if (info->has_transferred) {
-+                if (info->has_dirty && info->dirty) {
-+                     if (info->dirty < info->total) {
-+                        total_or_dirty = info->dirty;
-+                        incremental = true;
-+                    }
-+                }
-+            }
-+
-+            int per = (info->transferred * 100)/total_or_dirty;
-+
-+            monitor_printf(mon, "Backup mode: %s\n", incremental ? "incremental" : "full");
-+
-+            int zero_per = (info->has_zero_bytes && info->zero_bytes) ?
-+                (info->zero_bytes * 100)/info->total : 0;
-+            monitor_printf(mon, "Total size: %zd\n", info->total);
-+            monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
-+                           info->transferred, per);
-+            monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
-+                           info->zero_bytes, zero_per);
-+
-+            if (info->has_reused) {
-+                int reused_per = (info->reused * 100)/total_or_dirty;
-+                monitor_printf(mon, "Reused bytes: %zd (%d%%)\n",
-+                               info->reused, reused_per);
-+            }
-+        }
-     }
-     qapi_free_BackupStatus(info);
-diff --git a/proxmox-backup-client.c b/proxmox-backup-client.c
-index a8f6653a81..4ce7bc0b5e 100644
---- a/proxmox-backup-client.c
-+++ b/proxmox-backup-client.c
-@@ -89,6 +89,7 @@ proxmox_backup_co_register_image(
-     ProxmoxBackupHandle *pbs,
-     const char *device_name,
-     uint64_t size,
-+    bool incremental,
-     Error **errp)
- {
-     Coroutine *co = qemu_coroutine_self();
-@@ -98,7 +99,7 @@ proxmox_backup_co_register_image(
-     int pbs_res = -1;
-     proxmox_backup_register_image_async(
--        pbs, device_name, size ,proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
-+        pbs, device_name, size, incremental, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
-     qemu_coroutine_yield();
-     if (pbs_res < 0) {
-         if (errp) error_setg(errp, "backup register image failed: %s", pbs_err ? pbs_err : "unknown error");
-diff --git a/proxmox-backup-client.h b/proxmox-backup-client.h
-index 1dda8b7d8f..8cbf645b2c 100644
---- a/proxmox-backup-client.h
-+++ b/proxmox-backup-client.h
-@@ -32,6 +32,7 @@ proxmox_backup_co_register_image(
-     ProxmoxBackupHandle *pbs,
-     const char *device_name,
-     uint64_t size,
-+    bool incremental,
-     Error **errp);
-diff --git a/pve-backup.c b/pve-backup.c
-index 88f5ee133f..1c49cd178d 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -28,6 +28,8 @@
-  *
-  */
-+const char *PBS_BITMAP_NAME = "pbs-incremental-dirty-bitmap";
-+
- static struct PVEBackupState {
-     struct {
-         // Everithing accessed from qmp_backup_query command is protected using lock
-@@ -39,7 +41,9 @@ static struct PVEBackupState {
-         uuid_t uuid;
-         char uuid_str[37];
-         size_t total;
-+        size_t dirty;
-         size_t transferred;
-+        size_t reused;
-         size_t zero_bytes;
-     } stat;
-     int64_t speed;
-@@ -66,6 +70,7 @@ typedef struct PVEBackupDevInfo {
-     uint8_t dev_id;
-     bool completed;
-     char targetfile[PATH_MAX];
-+    BdrvDirtyBitmap *bitmap;
-     BlockDriverState *target;
- } PVEBackupDevInfo;
-@@ -105,11 +110,12 @@ static bool pvebackup_error_or_canceled(void)
-     return error_or_canceled;
- }
--static void pvebackup_add_transfered_bytes(size_t transferred, size_t zero_bytes)
-+static void pvebackup_add_transfered_bytes(size_t transferred, size_t zero_bytes, size_t reused)
- {
-     qemu_mutex_lock(&backup_state.stat.lock);
-     backup_state.stat.zero_bytes += zero_bytes;
-     backup_state.stat.transferred += transferred;
-+    backup_state.stat.reused += reused;
-     qemu_mutex_unlock(&backup_state.stat.lock);
- }
-@@ -148,7 +154,8 @@ pvebackup_co_dump_pbs_cb(
-         pvebackup_propagate_error(local_err);
-         return pbs_res;
-     } else {
--        pvebackup_add_transfered_bytes(size, !buf ? size : 0);
-+        size_t reused = (pbs_res == 0) ? size : 0;
-+        pvebackup_add_transfered_bytes(size, !buf ? size : 0, reused);
-     }
-     return size;
-@@ -208,11 +215,11 @@ pvebackup_co_dump_vma_cb(
-         } else {
-             if (remaining >= VMA_CLUSTER_SIZE) {
-                 assert(ret == VMA_CLUSTER_SIZE);
--                pvebackup_add_transfered_bytes(VMA_CLUSTER_SIZE, zero_bytes);
-+                pvebackup_add_transfered_bytes(VMA_CLUSTER_SIZE, zero_bytes, 0);
-                 remaining -= VMA_CLUSTER_SIZE;
-             } else {
-                 assert(ret == remaining);
--                pvebackup_add_transfered_bytes(remaining, zero_bytes);
-+                pvebackup_add_transfered_bytes(remaining, zero_bytes, 0);
-                 remaining = 0;
-             }
-         }
-@@ -248,6 +255,18 @@ static void coroutine_fn pvebackup_co_cleanup(void *unused)
-             if (local_err != NULL) {
-                 pvebackup_propagate_error(local_err);
-             }
-+        } else {
-+            // on error or cancel we cannot ensure synchronization of dirty
-+            // bitmaps with backup server, so remove all and do full backup next
-+            GList *l = backup_state.di_list;
-+            while (l) {
-+                PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+                l = g_list_next(l);
-+
-+                if (di->bitmap) {
-+                    bdrv_release_dirty_bitmap(di->bitmap);
-+                }
-+            }
-         }
-         proxmox_backup_disconnect(backup_state.pbs);
-@@ -303,6 +322,12 @@ static void pvebackup_complete_cb(void *opaque, int ret)
-     // remove self from job queue
-     backup_state.di_list = g_list_remove(backup_state.di_list, di);
-+    if (di->bitmap && ret < 0) {
-+        // on error or cancel we cannot ensure synchronization of dirty
-+        // bitmaps with backup server, so remove all and do full backup next
-+        bdrv_release_dirty_bitmap(di->bitmap);
-+    }
-+
-     g_free(di);
-     qemu_mutex_unlock(&backup_state.backup_mutex);
-@@ -472,12 +497,18 @@ static bool create_backup_jobs(void) {
-         assert(di->target != NULL);
-+        MirrorSyncMode sync_mode = MIRROR_SYNC_MODE_FULL;
-+        BitmapSyncMode bitmap_mode = BITMAP_SYNC_MODE_NEVER;
-+        if (di->bitmap) {
-+            sync_mode = MIRROR_SYNC_MODE_BITMAP;
-+            bitmap_mode = BITMAP_SYNC_MODE_ON_SUCCESS;
-+        }
-         AioContext *aio_context = bdrv_get_aio_context(di->bs);
-         aio_context_acquire(aio_context);
-         BlockJob *job = backup_job_create(
--            NULL, di->bs, di->target, backup_state.speed, MIRROR_SYNC_MODE_FULL, NULL,
--            BITMAP_SYNC_MODE_NEVER, false, NULL, &perf, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
-+            NULL, di->bs, di->target, backup_state.speed, sync_mode, di->bitmap,
-+            bitmap_mode, false, NULL, &perf, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
-             JOB_DEFAULT, pvebackup_complete_cb, di, NULL, &local_err);
-         aio_context_release(aio_context);
-@@ -528,6 +559,8 @@ typedef struct QmpBackupTask {
-     const char *fingerprint;
-     bool has_fingerprint;
-     int64_t backup_time;
-+    bool has_use_dirty_bitmap;
-+    bool use_dirty_bitmap;
-     bool has_format;
-     BackupFormat format;
-     bool has_config_file;
-@@ -619,6 +652,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     }
-     size_t total = 0;
-+    size_t dirty = 0;
-     l = di_list;
-     while (l) {
-@@ -656,6 +690,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-         int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
-         firewall_name = "fw.conf";
-+        bool use_dirty_bitmap = task->has_use_dirty_bitmap && task->use_dirty_bitmap;
-+
-         char *pbs_err = NULL;
-         pbs = proxmox_backup_new(
-             task->backup_file,
-@@ -675,7 +711,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             goto err;
-         }
--        if (proxmox_backup_co_connect(pbs, task->errp) < 0)
-+        int connect_result = proxmox_backup_co_connect(pbs, task->errp);
-+        if (connect_result < 0)
-             goto err;
-         /* register all devices */
-@@ -686,9 +723,40 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             const char *devname = bdrv_get_device_name(di->bs);
--            int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, task->errp);
--            if (dev_id < 0)
-+            BdrvDirtyBitmap *bitmap = bdrv_find_dirty_bitmap(di->bs, PBS_BITMAP_NAME);
-+            bool expect_only_dirty = false;
-+
-+            if (use_dirty_bitmap) {
-+                if (bitmap == NULL) {
-+                    bitmap = bdrv_create_dirty_bitmap(di->bs, dump_cb_block_size, PBS_BITMAP_NAME, task->errp);
-+                    if (!bitmap) {
-+                        goto err;
-+                    }
-+                } else {
-+                    expect_only_dirty = proxmox_backup_check_incremental(pbs, devname, di->size) != 0;
-+                }
-+
-+                if (expect_only_dirty) {
-+                    dirty += bdrv_get_dirty_count(bitmap);
-+                } else {
-+                    /* mark entire bitmap as dirty to make full backup */
-+                    bdrv_set_dirty_bitmap(bitmap, 0, di->size);
-+                    dirty += di->size;
-+                }
-+                di->bitmap = bitmap;
-+            } else {
-+                dirty += di->size;
-+
-+                /* after a full backup the old dirty bitmap is invalid anyway */
-+                if (bitmap != NULL) {
-+                    bdrv_release_dirty_bitmap(bitmap);
-+                }
-+            }
-+
-+            int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, expect_only_dirty, task->errp);
-+            if (dev_id < 0) {
-                 goto err;
-+            }
-             if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, task->errp))) {
-                 goto err;
-@@ -697,6 +765,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             di->dev_id = dev_id;
-         }
-     } else if (format == BACKUP_FORMAT_VMA) {
-+        dirty = total;
-+
-         vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
-         if (!vmaw) {
-             if (local_err) {
-@@ -724,6 +794,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             }
-         }
-     } else if (format == BACKUP_FORMAT_DIR) {
-+        dirty = total;
-+
-         if (mkdir(task->backup_file, 0640) != 0) {
-             error_setg_errno(task->errp, errno, "can't create directory '%s'\n",
-                              task->backup_file);
-@@ -796,8 +868,10 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     char *uuid_str = g_strdup(backup_state.stat.uuid_str);
-     backup_state.stat.total = total;
-+    backup_state.stat.dirty = dirty;
-     backup_state.stat.transferred = 0;
-     backup_state.stat.zero_bytes = 0;
-+    backup_state.stat.reused = format == BACKUP_FORMAT_PBS && dirty >= total ? 0 : total - dirty;
-     qemu_mutex_unlock(&backup_state.stat.lock);
-@@ -821,6 +895,10 @@ err:
-         PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-         l = g_list_next(l);
-+        if (di->bitmap) {
-+            bdrv_release_dirty_bitmap(di->bitmap);
-+        }
-+
-         if (di->target) {
-             bdrv_unref(di->target);
-         }
-@@ -862,6 +940,7 @@ UuidInfo *qmp_backup(
-     bool has_fingerprint, const char *fingerprint,
-     bool has_backup_id, const char *backup_id,
-     bool has_backup_time, int64_t backup_time,
-+    bool has_use_dirty_bitmap, bool use_dirty_bitmap,
-     bool has_format, BackupFormat format,
-     bool has_config_file, const char *config_file,
-     bool has_firewall_file, const char *firewall_file,
-@@ -880,6 +959,8 @@ UuidInfo *qmp_backup(
-         .backup_id = backup_id,
-         .has_backup_time = has_backup_time,
-         .backup_time = backup_time,
-+        .has_use_dirty_bitmap = has_use_dirty_bitmap,
-+        .use_dirty_bitmap = use_dirty_bitmap,
-         .has_format = has_format,
-         .format = format,
-         .has_config_file = has_config_file,
-@@ -948,10 +1029,14 @@ BackupStatus *qmp_query_backup(Error **errp)
-     info->has_total = true;
-     info->total = backup_state.stat.total;
-+    info->has_dirty = true;
-+    info->dirty = backup_state.stat.dirty;
-     info->has_zero_bytes = true;
-     info->zero_bytes = backup_state.stat.zero_bytes;
-     info->has_transferred = true;
-     info->transferred = backup_state.stat.transferred;
-+    info->has_reused = true;
-+    info->reused = backup_state.stat.reused;
-     qemu_mutex_unlock(&backup_state.stat.lock);
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index e4c3de0804..379a8dd147 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -757,8 +757,13 @@
- #
- # @total: total amount of bytes involved in the backup process
- #
-+# @dirty: with incremental mode (PBS) this is the amount of bytes involved
-+#         in the backup process which are marked dirty.
-+#
- # @transferred: amount of bytes already backed up.
- #
-+# @reused: amount of bytes reused due to deduplication.
-+#
- # @zero-bytes: amount of 'zero' bytes detected.
- #
- # @start-time: time (epoch) when backup job started.
-@@ -771,8 +776,8 @@
- #
- ##
- { 'struct': 'BackupStatus',
--  'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
--           '*transferred': 'int', '*zero-bytes': 'int',
-+  'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int', '*dirty': 'int',
-+           '*transferred': 'int', '*zero-bytes': 'int', '*reused': 'int',
-            '*start-time': 'int', '*end-time': 'int',
-            '*backup-file': 'str', '*uuid': 'str' } }
-@@ -815,6 +820,8 @@
- #
- # @backup-time: backup timestamp (Unix epoch, required for format 'pbs')
- #
-+# @use-dirty-bitmap: use dirty bitmap to detect incremental changes since last job (optional for format 'pbs')
-+#
- # Returns: the uuid of the backup job
- #
- ##
-@@ -825,6 +832,7 @@
-                                     '*fingerprint': 'str',
-                                     '*backup-id': 'str',
-                                     '*backup-time': 'int',
-+                                    '*use-dirty-bitmap': 'bool',
-                                     '*format': 'BackupFormat',
-                                     '*config-file': 'str',
-                                     '*firewall-file': 'str',
diff --git a/debian/patches/pve/0029-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch b/debian/patches/pve/0029-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch
new file mode 100644 (file)
index 0000000..132d390
--- /dev/null
@@ -0,0 +1,258 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Dietmar Maurer <dietmar@proxmox.com>
+Date: Mon, 6 Apr 2020 12:17:01 +0200
+Subject: [PATCH] PVE-Backup: pbs-restore - new command to restore from proxmox
+ backup server
+
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ meson.build   |   4 +
+ pbs-restore.c | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 228 insertions(+)
+ create mode 100644 pbs-restore.c
+
+diff --git a/meson.build b/meson.build
+index 37dab249cc..1a4dfab4e2 100644
+--- a/meson.build
++++ b/meson.build
+@@ -3076,6 +3076,10 @@ if have_tools
+   vma = executable('vma', files('vma.c', 'vma-reader.c') + genh,
+                    dependencies: [authz, block, crypto, io, qom], install: true)
++  pbs_restore = executable('pbs-restore', files('pbs-restore.c') + genh,
++                  dependencies: [authz, block, crypto, io, qom,
++                    libproxmox_backup_qemu], install: true)
++
+   subdir('storage-daemon')
+   subdir('contrib/rdmacm-mux')
+   subdir('contrib/elf2dmp')
+diff --git a/pbs-restore.c b/pbs-restore.c
+new file mode 100644
+index 0000000000..4d3f925a1b
+--- /dev/null
++++ b/pbs-restore.c
+@@ -0,0 +1,224 @@
++/*
++ * Qemu image restore helper for Proxmox Backup
++ *
++ * Copyright (C) 2019 Proxmox Server Solutions
++ *
++ * Authors:
++ *  Dietmar Maurer (dietmar@proxmox.com)
++ *
++ * This work is licensed under the terms of the GNU GPL, version 2 or later.
++ * See the COPYING file in the top-level directory.
++ *
++ */
++
++#include "qemu/osdep.h"
++#include <glib.h>
++#include <getopt.h>
++#include <string.h>
++
++#include "qemu-common.h"
++#include "qemu/module.h"
++#include "qemu/error-report.h"
++#include "qemu/main-loop.h"
++#include "qemu/cutils.h"
++#include "qapi/error.h"
++#include "qapi/qmp/qdict.h"
++#include "sysemu/block-backend.h"
++
++#include <proxmox-backup-qemu.h>
++
++static void help(void)
++{
++    const char *help_msg =
++        "usage: pbs-restore [--repository <repo>] snapshot archive-name target [command options]\n"
++        ;
++
++    printf("%s", help_msg);
++    exit(1);
++}
++
++typedef struct CallbackData {
++    BlockBackend *target;
++    uint64_t last_offset;
++    bool skip_zero;
++} CallbackData;
++
++static int write_callback(
++    void *callback_data_ptr,
++    uint64_t offset,
++    const unsigned char *data,
++    uint64_t data_len)
++{
++    int res = -1;
++
++    CallbackData *callback_data = (CallbackData *)callback_data_ptr;
++
++    uint64_t last_offset = callback_data->last_offset;
++    if (offset > last_offset) callback_data->last_offset = offset;
++
++    if (data == NULL) {
++        if (callback_data->skip_zero && offset > last_offset) {
++            return 0;
++        }
++        res = blk_pwrite_zeroes(callback_data->target, offset, data_len, 0);
++    } else {
++        res = blk_pwrite(callback_data->target, offset, data, data_len, 0);
++    }
++
++    if (res < 0) {
++        fprintf(stderr, "blk_pwrite failed at offset %ld length %ld (%d) - %s\n", offset, data_len, res, strerror(-res));
++        return res;
++    }
++
++    return 0;
++}
++
++int main(int argc, char **argv)
++{
++    Error *main_loop_err = NULL;
++    const char *format = "raw";
++    const char *repository = NULL;
++    const char *keyfile = NULL;
++    int verbose = false;
++    bool skip_zero = false;
++
++    error_init(argv[0]);
++
++    for (;;) {
++        static const struct option long_options[] = {
++            {"help", no_argument, 0, 'h'},
++            {"skip-zero", no_argument, 0, 'S'},
++            {"verbose", no_argument, 0, 'v'},
++            {"format", required_argument, 0, 'f'},
++            {"repository", required_argument, 0, 'r'},
++            {"keyfile", required_argument, 0, 'k'},
++            {0, 0, 0, 0}
++        };
++        int c = getopt_long(argc, argv, "hvf:r:k:", long_options, NULL);
++        if (c == -1) {
++            break;
++        }
++        switch (c) {
++            case ':':
++                fprintf(stderr, "missing argument for option '%s'\n", argv[optind - 1]);
++                return -1;
++            case '?':
++                fprintf(stderr, "unrecognized option '%s'\n", argv[optind - 1]);
++                return -1;
++            case 'f':
++                format = g_strdup(argv[optind - 1]);
++                break;
++            case 'r':
++                repository = g_strdup(argv[optind - 1]);
++                break;
++            case 'k':
++                keyfile = g_strdup(argv[optind - 1]);
++                break;
++            case 'v':
++                verbose = true;
++                break;
++            case 'S':
++                skip_zero = true;
++                break;
++            case 'h':
++                help();
++                return 0;
++        }
++    }
++
++    if (optind >= argc - 2) {
++        fprintf(stderr, "missing arguments\n");
++        help();
++        return -1;
++    }
++
++    if (repository == NULL) {
++        repository = getenv("PBS_REPOSITORY");
++    }
++
++    if (repository == NULL) {
++        fprintf(stderr, "no repository specified\n");
++        help();
++        return -1;
++    }
++
++    char *snapshot = argv[optind++];
++    char *archive_name = argv[optind++];
++    char *target = argv[optind++];
++
++    const char *password = getenv("PBS_PASSWORD");
++    const char *fingerprint = getenv("PBS_FINGERPRINT");
++    const char *key_password = getenv("PBS_ENCRYPTION_PASSWORD");
++
++    if (qemu_init_main_loop(&main_loop_err)) {
++        g_error("%s", error_get_pretty(main_loop_err));
++    }
++
++    bdrv_init();
++    module_call_init(MODULE_INIT_QOM);
++
++    if (verbose) {
++        fprintf(stderr, "connecting to repository '%s'\n", repository);
++    }
++    char *pbs_error = NULL;
++    ProxmoxRestoreHandle *conn = proxmox_restore_new(
++        repository, snapshot, password, keyfile, key_password, fingerprint, &pbs_error);
++    if (conn == NULL) {
++        fprintf(stderr, "restore failed: %s\n", pbs_error);
++        return -1;
++    }
++
++    int res = proxmox_restore_connect(conn, &pbs_error);
++    if (res < 0 || pbs_error) {
++        fprintf(stderr, "restore failed (connection error): %s\n", pbs_error);
++        return -1;
++    }
++
++    QDict *options = qdict_new();
++
++    if (format) {
++        qdict_put_str(options, "driver", format);
++    }
++
++
++    if (verbose) {
++        fprintf(stderr, "open block backend for target '%s'\n", target);
++    }
++    Error *local_err = NULL;
++    int flags = BDRV_O_RDWR;
++    BlockBackend *blk = blk_new_open(target, NULL, options, flags, &local_err);
++    if (!blk) {
++        fprintf(stderr, "%s\n", error_get_pretty(local_err));
++        return -1;
++    }
++
++    CallbackData *callback_data = calloc(sizeof(CallbackData), 1);
++
++    callback_data->target = blk;
++    callback_data->skip_zero = skip_zero;
++    callback_data->last_offset = 0;
++
++    // blk_set_enable_write_cache(blk, !writethrough);
++
++    if (verbose) {
++        fprintf(stderr, "starting to restore snapshot '%s'\n", snapshot);
++        fflush(stderr); // ensure we do not get printed after the progress log
++    }
++    res = proxmox_restore_image(
++        conn,
++        archive_name,
++        write_callback,
++        callback_data,
++        &pbs_error,
++        verbose);
++
++    proxmox_restore_disconnect(conn);
++    blk_unref(blk);
++
++    if (res < 0) {
++        fprintf(stderr, "restore failed: %s\n", pbs_error);
++        return -1;
++    }
++
++    return 0;
++}
diff --git a/debian/patches/pve/0030-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch b/debian/patches/pve/0030-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch
new file mode 100644 (file)
index 0000000..ff991d2
--- /dev/null
@@ -0,0 +1,452 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Mon, 29 Jun 2020 11:06:03 +0200
+Subject: [PATCH] PVE-Backup: Add dirty-bitmap tracking for incremental backups
+
+Uses QEMU's existing MIRROR_SYNC_MODE_BITMAP and a dirty-bitmap on top
+of all backed-up drives. This will only execute the data-write callback
+for any changed chunks, the PBS rust code will reuse chunks from the
+previous index for everything it doesn't receive if reuse_index is true.
+
+On error or cancellation, remove all dirty bitmaps to ensure
+consistency.
+
+Add PBS/incremental specific information to query backup info QMP and
+HMP commands.
+
+Only supported for PBS backups.
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ block/monitor/block-hmp-cmds.c |   1 +
+ monitor/hmp-cmds.c             |  45 ++++++++++----
+ proxmox-backup-client.c        |   3 +-
+ proxmox-backup-client.h        |   1 +
+ pve-backup.c                   | 103 ++++++++++++++++++++++++++++++---
+ qapi/block-core.json           |  12 +++-
+ 6 files changed, 142 insertions(+), 23 deletions(-)
+
+diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
+index f6668ab01d..3c06734e6d 100644
+--- a/block/monitor/block-hmp-cmds.c
++++ b/block/monitor/block-hmp-cmds.c
+@@ -1042,6 +1042,7 @@ void hmp_backup(Monitor *mon, const QDict *qdict)
+         false, NULL, // PBS fingerprint
+         false, NULL, // PBS backup-id
+         false, 0, // PBS backup-time
++        false, false, // PBS incremental
+         true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
+         false, NULL, false, NULL, !!devlist,
+         devlist, qdict_haskey(qdict, "speed"), speed, &error);
+diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
+index b2687eae3a..cfd7a60f32 100644
+--- a/monitor/hmp-cmds.c
++++ b/monitor/hmp-cmds.c
+@@ -221,19 +221,42 @@ void hmp_info_backup(Monitor *mon, const QDict *qdict)
+             monitor_printf(mon, "End time: %s", ctime(&info->end_time));
+         }
+-        int per = (info->has_total && info->total &&
+-            info->has_transferred && info->transferred) ?
+-            (info->transferred * 100)/info->total : 0;
+-        int zero_per = (info->has_total && info->total &&
+-                        info->has_zero_bytes && info->zero_bytes) ?
+-            (info->zero_bytes * 100)/info->total : 0;
+         monitor_printf(mon, "Backup file: %s\n", info->backup_file);
+         monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
+-        monitor_printf(mon, "Total size: %zd\n", info->total);
+-        monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
+-                       info->transferred, per);
+-        monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
+-                       info->zero_bytes, zero_per);
++
++        if (!(info->has_total && info->total))  {
++            // this should not happen normally
++            monitor_printf(mon, "Total size: %d\n", 0);
++        } else {
++            bool incremental = false;
++            size_t total_or_dirty = info->total;
++            if (info->has_transferred) {
++                if (info->has_dirty && info->dirty) {
++                     if (info->dirty < info->total) {
++                        total_or_dirty = info->dirty;
++                        incremental = true;
++                    }
++                }
++            }
++
++            int per = (info->transferred * 100)/total_or_dirty;
++
++            monitor_printf(mon, "Backup mode: %s\n", incremental ? "incremental" : "full");
++
++            int zero_per = (info->has_zero_bytes && info->zero_bytes) ?
++                (info->zero_bytes * 100)/info->total : 0;
++            monitor_printf(mon, "Total size: %zd\n", info->total);
++            monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
++                           info->transferred, per);
++            monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
++                           info->zero_bytes, zero_per);
++
++            if (info->has_reused) {
++                int reused_per = (info->reused * 100)/total_or_dirty;
++                monitor_printf(mon, "Reused bytes: %zd (%d%%)\n",
++                               info->reused, reused_per);
++            }
++        }
+     }
+     qapi_free_BackupStatus(info);
+diff --git a/proxmox-backup-client.c b/proxmox-backup-client.c
+index a8f6653a81..4ce7bc0b5e 100644
+--- a/proxmox-backup-client.c
++++ b/proxmox-backup-client.c
+@@ -89,6 +89,7 @@ proxmox_backup_co_register_image(
+     ProxmoxBackupHandle *pbs,
+     const char *device_name,
+     uint64_t size,
++    bool incremental,
+     Error **errp)
+ {
+     Coroutine *co = qemu_coroutine_self();
+@@ -98,7 +99,7 @@ proxmox_backup_co_register_image(
+     int pbs_res = -1;
+     proxmox_backup_register_image_async(
+-        pbs, device_name, size ,proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
++        pbs, device_name, size, incremental, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
+     qemu_coroutine_yield();
+     if (pbs_res < 0) {
+         if (errp) error_setg(errp, "backup register image failed: %s", pbs_err ? pbs_err : "unknown error");
+diff --git a/proxmox-backup-client.h b/proxmox-backup-client.h
+index 1dda8b7d8f..8cbf645b2c 100644
+--- a/proxmox-backup-client.h
++++ b/proxmox-backup-client.h
+@@ -32,6 +32,7 @@ proxmox_backup_co_register_image(
+     ProxmoxBackupHandle *pbs,
+     const char *device_name,
+     uint64_t size,
++    bool incremental,
+     Error **errp);
+diff --git a/pve-backup.c b/pve-backup.c
+index 88f5ee133f..1c49cd178d 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -28,6 +28,8 @@
+  *
+  */
++const char *PBS_BITMAP_NAME = "pbs-incremental-dirty-bitmap";
++
+ static struct PVEBackupState {
+     struct {
+         // Everithing accessed from qmp_backup_query command is protected using lock
+@@ -39,7 +41,9 @@ static struct PVEBackupState {
+         uuid_t uuid;
+         char uuid_str[37];
+         size_t total;
++        size_t dirty;
+         size_t transferred;
++        size_t reused;
+         size_t zero_bytes;
+     } stat;
+     int64_t speed;
+@@ -66,6 +70,7 @@ typedef struct PVEBackupDevInfo {
+     uint8_t dev_id;
+     bool completed;
+     char targetfile[PATH_MAX];
++    BdrvDirtyBitmap *bitmap;
+     BlockDriverState *target;
+ } PVEBackupDevInfo;
+@@ -105,11 +110,12 @@ static bool pvebackup_error_or_canceled(void)
+     return error_or_canceled;
+ }
+-static void pvebackup_add_transfered_bytes(size_t transferred, size_t zero_bytes)
++static void pvebackup_add_transfered_bytes(size_t transferred, size_t zero_bytes, size_t reused)
+ {
+     qemu_mutex_lock(&backup_state.stat.lock);
+     backup_state.stat.zero_bytes += zero_bytes;
+     backup_state.stat.transferred += transferred;
++    backup_state.stat.reused += reused;
+     qemu_mutex_unlock(&backup_state.stat.lock);
+ }
+@@ -148,7 +154,8 @@ pvebackup_co_dump_pbs_cb(
+         pvebackup_propagate_error(local_err);
+         return pbs_res;
+     } else {
+-        pvebackup_add_transfered_bytes(size, !buf ? size : 0);
++        size_t reused = (pbs_res == 0) ? size : 0;
++        pvebackup_add_transfered_bytes(size, !buf ? size : 0, reused);
+     }
+     return size;
+@@ -208,11 +215,11 @@ pvebackup_co_dump_vma_cb(
+         } else {
+             if (remaining >= VMA_CLUSTER_SIZE) {
+                 assert(ret == VMA_CLUSTER_SIZE);
+-                pvebackup_add_transfered_bytes(VMA_CLUSTER_SIZE, zero_bytes);
++                pvebackup_add_transfered_bytes(VMA_CLUSTER_SIZE, zero_bytes, 0);
+                 remaining -= VMA_CLUSTER_SIZE;
+             } else {
+                 assert(ret == remaining);
+-                pvebackup_add_transfered_bytes(remaining, zero_bytes);
++                pvebackup_add_transfered_bytes(remaining, zero_bytes, 0);
+                 remaining = 0;
+             }
+         }
+@@ -248,6 +255,18 @@ static void coroutine_fn pvebackup_co_cleanup(void *unused)
+             if (local_err != NULL) {
+                 pvebackup_propagate_error(local_err);
+             }
++        } else {
++            // on error or cancel we cannot ensure synchronization of dirty
++            // bitmaps with backup server, so remove all and do full backup next
++            GList *l = backup_state.di_list;
++            while (l) {
++                PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++                l = g_list_next(l);
++
++                if (di->bitmap) {
++                    bdrv_release_dirty_bitmap(di->bitmap);
++                }
++            }
+         }
+         proxmox_backup_disconnect(backup_state.pbs);
+@@ -303,6 +322,12 @@ static void pvebackup_complete_cb(void *opaque, int ret)
+     // remove self from job queue
+     backup_state.di_list = g_list_remove(backup_state.di_list, di);
++    if (di->bitmap && ret < 0) {
++        // on error or cancel we cannot ensure synchronization of dirty
++        // bitmaps with backup server, so remove all and do full backup next
++        bdrv_release_dirty_bitmap(di->bitmap);
++    }
++
+     g_free(di);
+     qemu_mutex_unlock(&backup_state.backup_mutex);
+@@ -472,12 +497,18 @@ static bool create_backup_jobs(void) {
+         assert(di->target != NULL);
++        MirrorSyncMode sync_mode = MIRROR_SYNC_MODE_FULL;
++        BitmapSyncMode bitmap_mode = BITMAP_SYNC_MODE_NEVER;
++        if (di->bitmap) {
++            sync_mode = MIRROR_SYNC_MODE_BITMAP;
++            bitmap_mode = BITMAP_SYNC_MODE_ON_SUCCESS;
++        }
+         AioContext *aio_context = bdrv_get_aio_context(di->bs);
+         aio_context_acquire(aio_context);
+         BlockJob *job = backup_job_create(
+-            NULL, di->bs, di->target, backup_state.speed, MIRROR_SYNC_MODE_FULL, NULL,
+-            BITMAP_SYNC_MODE_NEVER, false, NULL, &perf, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
++            NULL, di->bs, di->target, backup_state.speed, sync_mode, di->bitmap,
++            bitmap_mode, false, NULL, &perf, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
+             JOB_DEFAULT, pvebackup_complete_cb, di, NULL, &local_err);
+         aio_context_release(aio_context);
+@@ -528,6 +559,8 @@ typedef struct QmpBackupTask {
+     const char *fingerprint;
+     bool has_fingerprint;
+     int64_t backup_time;
++    bool has_use_dirty_bitmap;
++    bool use_dirty_bitmap;
+     bool has_format;
+     BackupFormat format;
+     bool has_config_file;
+@@ -619,6 +652,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     }
+     size_t total = 0;
++    size_t dirty = 0;
+     l = di_list;
+     while (l) {
+@@ -656,6 +690,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+         int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
+         firewall_name = "fw.conf";
++        bool use_dirty_bitmap = task->has_use_dirty_bitmap && task->use_dirty_bitmap;
++
+         char *pbs_err = NULL;
+         pbs = proxmox_backup_new(
+             task->backup_file,
+@@ -675,7 +711,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             goto err;
+         }
+-        if (proxmox_backup_co_connect(pbs, task->errp) < 0)
++        int connect_result = proxmox_backup_co_connect(pbs, task->errp);
++        if (connect_result < 0)
+             goto err;
+         /* register all devices */
+@@ -686,9 +723,40 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             const char *devname = bdrv_get_device_name(di->bs);
+-            int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, task->errp);
+-            if (dev_id < 0)
++            BdrvDirtyBitmap *bitmap = bdrv_find_dirty_bitmap(di->bs, PBS_BITMAP_NAME);
++            bool expect_only_dirty = false;
++
++            if (use_dirty_bitmap) {
++                if (bitmap == NULL) {
++                    bitmap = bdrv_create_dirty_bitmap(di->bs, dump_cb_block_size, PBS_BITMAP_NAME, task->errp);
++                    if (!bitmap) {
++                        goto err;
++                    }
++                } else {
++                    expect_only_dirty = proxmox_backup_check_incremental(pbs, devname, di->size) != 0;
++                }
++
++                if (expect_only_dirty) {
++                    dirty += bdrv_get_dirty_count(bitmap);
++                } else {
++                    /* mark entire bitmap as dirty to make full backup */
++                    bdrv_set_dirty_bitmap(bitmap, 0, di->size);
++                    dirty += di->size;
++                }
++                di->bitmap = bitmap;
++            } else {
++                dirty += di->size;
++
++                /* after a full backup the old dirty bitmap is invalid anyway */
++                if (bitmap != NULL) {
++                    bdrv_release_dirty_bitmap(bitmap);
++                }
++            }
++
++            int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, expect_only_dirty, task->errp);
++            if (dev_id < 0) {
+                 goto err;
++            }
+             if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, task->errp))) {
+                 goto err;
+@@ -697,6 +765,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             di->dev_id = dev_id;
+         }
+     } else if (format == BACKUP_FORMAT_VMA) {
++        dirty = total;
++
+         vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
+         if (!vmaw) {
+             if (local_err) {
+@@ -724,6 +794,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             }
+         }
+     } else if (format == BACKUP_FORMAT_DIR) {
++        dirty = total;
++
+         if (mkdir(task->backup_file, 0640) != 0) {
+             error_setg_errno(task->errp, errno, "can't create directory '%s'\n",
+                              task->backup_file);
+@@ -796,8 +868,10 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     char *uuid_str = g_strdup(backup_state.stat.uuid_str);
+     backup_state.stat.total = total;
++    backup_state.stat.dirty = dirty;
+     backup_state.stat.transferred = 0;
+     backup_state.stat.zero_bytes = 0;
++    backup_state.stat.reused = format == BACKUP_FORMAT_PBS && dirty >= total ? 0 : total - dirty;
+     qemu_mutex_unlock(&backup_state.stat.lock);
+@@ -821,6 +895,10 @@ err:
+         PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
+         l = g_list_next(l);
++        if (di->bitmap) {
++            bdrv_release_dirty_bitmap(di->bitmap);
++        }
++
+         if (di->target) {
+             bdrv_unref(di->target);
+         }
+@@ -862,6 +940,7 @@ UuidInfo *qmp_backup(
+     bool has_fingerprint, const char *fingerprint,
+     bool has_backup_id, const char *backup_id,
+     bool has_backup_time, int64_t backup_time,
++    bool has_use_dirty_bitmap, bool use_dirty_bitmap,
+     bool has_format, BackupFormat format,
+     bool has_config_file, const char *config_file,
+     bool has_firewall_file, const char *firewall_file,
+@@ -880,6 +959,8 @@ UuidInfo *qmp_backup(
+         .backup_id = backup_id,
+         .has_backup_time = has_backup_time,
+         .backup_time = backup_time,
++        .has_use_dirty_bitmap = has_use_dirty_bitmap,
++        .use_dirty_bitmap = use_dirty_bitmap,
+         .has_format = has_format,
+         .format = format,
+         .has_config_file = has_config_file,
+@@ -948,10 +1029,14 @@ BackupStatus *qmp_query_backup(Error **errp)
+     info->has_total = true;
+     info->total = backup_state.stat.total;
++    info->has_dirty = true;
++    info->dirty = backup_state.stat.dirty;
+     info->has_zero_bytes = true;
+     info->zero_bytes = backup_state.stat.zero_bytes;
+     info->has_transferred = true;
+     info->transferred = backup_state.stat.transferred;
++    info->has_reused = true;
++    info->reused = backup_state.stat.reused;
+     qemu_mutex_unlock(&backup_state.stat.lock);
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index e4c3de0804..379a8dd147 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -757,8 +757,13 @@
+ #
+ # @total: total amount of bytes involved in the backup process
+ #
++# @dirty: with incremental mode (PBS) this is the amount of bytes involved
++#         in the backup process which are marked dirty.
++#
+ # @transferred: amount of bytes already backed up.
+ #
++# @reused: amount of bytes reused due to deduplication.
++#
+ # @zero-bytes: amount of 'zero' bytes detected.
+ #
+ # @start-time: time (epoch) when backup job started.
+@@ -771,8 +776,8 @@
+ #
+ ##
+ { 'struct': 'BackupStatus',
+-  'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
+-           '*transferred': 'int', '*zero-bytes': 'int',
++  'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int', '*dirty': 'int',
++           '*transferred': 'int', '*zero-bytes': 'int', '*reused': 'int',
+            '*start-time': 'int', '*end-time': 'int',
+            '*backup-file': 'str', '*uuid': 'str' } }
+@@ -815,6 +820,8 @@
+ #
+ # @backup-time: backup timestamp (Unix epoch, required for format 'pbs')
+ #
++# @use-dirty-bitmap: use dirty bitmap to detect incremental changes since last job (optional for format 'pbs')
++#
+ # Returns: the uuid of the backup job
+ #
+ ##
+@@ -825,6 +832,7 @@
+                                     '*fingerprint': 'str',
+                                     '*backup-id': 'str',
+                                     '*backup-time': 'int',
++                                    '*use-dirty-bitmap': 'bool',
+                                     '*format': 'BackupFormat',
+                                     '*config-file': 'str',
+                                     '*firewall-file': 'str',
diff --git a/debian/patches/pve/0030-PVE-various-PBS-fixes.patch b/debian/patches/pve/0030-PVE-various-PBS-fixes.patch
deleted file mode 100644 (file)
index 39a3eb2..0000000
+++ /dev/null
@@ -1,219 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Dietmar Maurer <dietmar@proxmox.com>
-Date: Thu, 9 Jul 2020 12:53:08 +0200
-Subject: [PATCH] PVE: various PBS fixes
-
-pbs: fix crypt and compress parameters
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-
-PVE: handle PBS write callback with big blocks correctly
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-
-PVE: add zero block handling to PBS dump callback
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- block/monitor/block-hmp-cmds.c |  4 ++-
- pve-backup.c                   | 59 ++++++++++++++++++++++++++--------
- qapi/block-core.json           |  6 ++++
- 3 files changed, 55 insertions(+), 14 deletions(-)
-
-diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
-index 3c06734e6d..4481b60a5c 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1042,7 +1042,9 @@ void hmp_backup(Monitor *mon, const QDict *qdict)
-         false, NULL, // PBS fingerprint
-         false, NULL, // PBS backup-id
-         false, 0, // PBS backup-time
--        false, false, // PBS incremental
-+        false, false, // PBS use-dirty-bitmap
-+        false, false, // PBS compress
-+        false, false, // PBS encrypt
-         true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
-         false, NULL, false, NULL, !!devlist,
-         devlist, qdict_haskey(qdict, "speed"), speed, &error);
-diff --git a/pve-backup.c b/pve-backup.c
-index 1c49cd178d..c15abefdda 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -8,6 +8,7 @@
- #include "block/blockjob.h"
- #include "qapi/qapi-commands-block.h"
- #include "qapi/qmp/qerror.h"
-+#include "qemu/cutils.h"
- /* PVE backup state and related function */
-@@ -67,6 +68,7 @@ opts_init(pvebackup_init);
- typedef struct PVEBackupDevInfo {
-     BlockDriverState *bs;
-     size_t size;
-+    uint64_t block_size;
-     uint8_t dev_id;
-     bool completed;
-     char targetfile[PATH_MAX];
-@@ -135,10 +137,13 @@ pvebackup_co_dump_pbs_cb(
-     PVEBackupDevInfo *di = opaque;
-     assert(backup_state.pbs);
-+    assert(buf);
-     Error *local_err = NULL;
-     int pbs_res = -1;
-+    bool is_zero_block = size == di->block_size && buffer_is_zero(buf, size);
-+
-     qemu_co_mutex_lock(&backup_state.dump_callback_mutex);
-     // avoid deadlock if job is cancelled
-@@ -147,16 +152,28 @@ pvebackup_co_dump_pbs_cb(
-         return -1;
-     }
--    pbs_res = proxmox_backup_co_write_data(backup_state.pbs, di->dev_id, buf, start, size, &local_err);
-+    uint64_t transferred = 0;
-+    uint64_t reused = 0;
-+    while (transferred < size) {
-+        uint64_t left = size - transferred;
-+        uint64_t to_transfer = left < di->block_size ? left : di->block_size;
-+
-+        pbs_res = proxmox_backup_co_write_data(backup_state.pbs, di->dev_id,
-+            is_zero_block ? NULL : buf + transferred, start + transferred,
-+            to_transfer, &local_err);
-+        transferred += to_transfer;
-+
-+        if (pbs_res < 0) {
-+            pvebackup_propagate_error(local_err);
-+            qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
-+            return pbs_res;
-+        }
-+
-+        reused += pbs_res == 0 ? to_transfer : 0;
-+    }
-+
-     qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
--
--    if (pbs_res < 0) {
--        pvebackup_propagate_error(local_err);
--        return pbs_res;
--    } else {
--        size_t reused = (pbs_res == 0) ? size : 0;
--        pvebackup_add_transfered_bytes(size, !buf ? size : 0, reused);
--    }
-+    pvebackup_add_transfered_bytes(size, is_zero_block ? size : 0, reused);
-     return size;
- }
-@@ -178,6 +195,7 @@ pvebackup_co_dump_vma_cb(
-     int ret = -1;
-     assert(backup_state.vmaw);
-+    assert(buf);
-     uint64_t remaining = size;
-@@ -204,9 +222,7 @@ pvebackup_co_dump_vma_cb(
-         qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
-         ++cluster_num;
--        if (buf) {
--            buf += VMA_CLUSTER_SIZE;
--        }
-+        buf += VMA_CLUSTER_SIZE;
-         if (ret < 0) {
-             Error *local_err = NULL;
-             vma_writer_error_propagate(backup_state.vmaw, &local_err);
-@@ -569,6 +585,10 @@ typedef struct QmpBackupTask {
-     const char *firewall_file;
-     bool has_devlist;
-     const char *devlist;
-+    bool has_compress;
-+    bool compress;
-+    bool has_encrypt;
-+    bool encrypt;
-     bool has_speed;
-     int64_t speed;
-     Error **errp;
-@@ -692,6 +712,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-         bool use_dirty_bitmap = task->has_use_dirty_bitmap && task->use_dirty_bitmap;
-+
-         char *pbs_err = NULL;
-         pbs = proxmox_backup_new(
-             task->backup_file,
-@@ -701,8 +722,10 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             task->has_password ? task->password : NULL,
-             task->has_keyfile ? task->keyfile : NULL,
-             task->has_key_password ? task->key_password : NULL,
-+            task->has_compress ? task->compress : true,
-+            task->has_encrypt ? task->encrypt : task->has_keyfile,
-             task->has_fingerprint ? task->fingerprint : NULL,
--            &pbs_err);
-+             &pbs_err);
-         if (!pbs) {
-             error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
-@@ -721,6 +744,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-             l = g_list_next(l);
-+            di->block_size = dump_cb_block_size;
-+
-             const char *devname = bdrv_get_device_name(di->bs);
-             BdrvDirtyBitmap *bitmap = bdrv_find_dirty_bitmap(di->bs, PBS_BITMAP_NAME);
-@@ -941,6 +966,8 @@ UuidInfo *qmp_backup(
-     bool has_backup_id, const char *backup_id,
-     bool has_backup_time, int64_t backup_time,
-     bool has_use_dirty_bitmap, bool use_dirty_bitmap,
-+    bool has_compress, bool compress,
-+    bool has_encrypt, bool encrypt,
-     bool has_format, BackupFormat format,
-     bool has_config_file, const char *config_file,
-     bool has_firewall_file, const char *firewall_file,
-@@ -951,6 +978,8 @@ UuidInfo *qmp_backup(
-         .backup_file = backup_file,
-         .has_password = has_password,
-         .password = password,
-+        .has_keyfile = has_keyfile,
-+        .keyfile = keyfile,
-         .has_key_password = has_key_password,
-         .key_password = key_password,
-         .has_fingerprint = has_fingerprint,
-@@ -961,6 +990,10 @@ UuidInfo *qmp_backup(
-         .backup_time = backup_time,
-         .has_use_dirty_bitmap = has_use_dirty_bitmap,
-         .use_dirty_bitmap = use_dirty_bitmap,
-+        .has_compress = has_compress,
-+        .compress = compress,
-+        .has_encrypt = has_encrypt,
-+        .encrypt = encrypt,
-         .has_format = has_format,
-         .format = format,
-         .has_config_file = has_config_file,
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index 379a8dd147..88835ebcf6 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -822,6 +822,10 @@
- #
- # @use-dirty-bitmap: use dirty bitmap to detect incremental changes since last job (optional for format 'pbs')
- #
-+# @compress: use compression (optional for format 'pbs', defaults to true)
-+#
-+# @encrypt: use encryption ((optional for format 'pbs', defaults to true if there is a keyfile)
-+#
- # Returns: the uuid of the backup job
- #
- ##
-@@ -833,6 +837,8 @@
-                                     '*backup-id': 'str',
-                                     '*backup-time': 'int',
-                                     '*use-dirty-bitmap': 'bool',
-+                                    '*compress': 'bool',
-+                                    '*encrypt': 'bool',
-                                     '*format': 'BackupFormat',
-                                     '*config-file': 'str',
-                                     '*firewall-file': 'str',
diff --git a/debian/patches/pve/0031-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch b/debian/patches/pve/0031-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch
deleted file mode 100644 (file)
index 10edf43..0000000
+++ /dev/null
@@ -1,407 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Wed, 8 Jul 2020 09:50:54 +0200
-Subject: [PATCH] PVE: Add PBS block driver to map backup archives into VMs
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-[error cleanups, file_open implementation]
-Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-[adapt to changed function signatures]
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
----
- block/meson.build    |   3 +
- block/pbs.c          | 276 +++++++++++++++++++++++++++++++++++++++++++
- configure            |   9 ++
- meson.build          |   1 +
- qapi/block-core.json |  13 ++
- 5 files changed, 302 insertions(+)
- create mode 100644 block/pbs.c
-
-diff --git a/block/meson.build b/block/meson.build
-index 9d3dd5b7c3..8c758c0218 100644
---- a/block/meson.build
-+++ b/block/meson.build
-@@ -51,6 +51,9 @@ block_ss.add(files(
-   '../pve-backup.c',
- ), libproxmox_backup_qemu)
-+block_ss.add(when: 'CONFIG_PBS_BDRV', if_true: files('pbs.c'))
-+block_ss.add(when: 'CONFIG_PBS_BDRV', if_true: libproxmox_backup_qemu)
-+
- softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c'))
-diff --git a/block/pbs.c b/block/pbs.c
-new file mode 100644
-index 0000000000..0b05ea9080
---- /dev/null
-+++ b/block/pbs.c
-@@ -0,0 +1,276 @@
-+/*
-+ * Proxmox Backup Server read-only block driver
-+ */
-+
-+#include "qemu/osdep.h"
-+#include "qapi/error.h"
-+#include "qapi/qmp/qdict.h"
-+#include "qapi/qmp/qstring.h"
-+#include "qemu/module.h"
-+#include "qemu/option.h"
-+#include "qemu/cutils.h"
-+#include "block/block_int.h"
-+
-+#include <proxmox-backup-qemu.h>
-+
-+#define PBS_OPT_REPOSITORY "repository"
-+#define PBS_OPT_SNAPSHOT "snapshot"
-+#define PBS_OPT_ARCHIVE "archive"
-+#define PBS_OPT_KEYFILE "keyfile"
-+#define PBS_OPT_PASSWORD "password"
-+#define PBS_OPT_FINGERPRINT "fingerprint"
-+#define PBS_OPT_ENCRYPTION_PASSWORD "key_password"
-+
-+typedef struct {
-+    ProxmoxRestoreHandle *conn;
-+    char aid;
-+    int64_t length;
-+
-+    char *repository;
-+    char *snapshot;
-+    char *archive;
-+} BDRVPBSState;
-+
-+static QemuOptsList runtime_opts = {
-+    .name = "pbs",
-+    .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
-+    .desc = {
-+        {
-+            .name = PBS_OPT_REPOSITORY,
-+            .type = QEMU_OPT_STRING,
-+            .help = "The server address and repository to connect to.",
-+        },
-+        {
-+            .name = PBS_OPT_SNAPSHOT,
-+            .type = QEMU_OPT_STRING,
-+            .help = "The snapshot to read.",
-+        },
-+        {
-+            .name = PBS_OPT_ARCHIVE,
-+            .type = QEMU_OPT_STRING,
-+            .help = "Which archive within the snapshot should be accessed.",
-+        },
-+        {
-+            .name = PBS_OPT_PASSWORD,
-+            .type = QEMU_OPT_STRING,
-+            .help = "Server password. Can be passed as env var 'PBS_PASSWORD'.",
-+        },
-+        {
-+            .name = PBS_OPT_FINGERPRINT,
-+            .type = QEMU_OPT_STRING,
-+            .help = "Server fingerprint. Can be passed as env var 'PBS_FINGERPRINT'.",
-+        },
-+        {
-+            .name = PBS_OPT_ENCRYPTION_PASSWORD,
-+            .type = QEMU_OPT_STRING,
-+            .help = "Optional: Key password. Can be passed as env var 'PBS_ENCRYPTION_PASSWORD'.",
-+        },
-+        {
-+            .name = PBS_OPT_KEYFILE,
-+            .type = QEMU_OPT_STRING,
-+            .help = "Optional: The path to the keyfile to use.",
-+        },
-+        { /* end of list */ }
-+    },
-+};
-+
-+
-+// filename format:
-+// pbs:repository=<repo>,snapshot=<snap>,password=<pw>,key_password=<kpw>,fingerprint=<fp>,archive=<archive>
-+static void pbs_parse_filename(const char *filename, QDict *options,
-+                                     Error **errp)
-+{
-+
-+    if (!strstart(filename, "pbs:", &filename)) {
-+        if (errp) error_setg(errp, "pbs_parse_filename failed - missing 'pbs:' prefix");
-+    }
-+
-+
-+    QemuOpts *opts = qemu_opts_parse_noisily(&runtime_opts, filename, false);
-+    if (!opts) {
-+        if (errp) error_setg(errp, "pbs_parse_filename failed");
-+        return;
-+    }
-+
-+    qemu_opts_to_qdict(opts, options);
-+
-+    qemu_opts_del(opts);
-+}
-+
-+static int pbs_open(BlockDriverState *bs, QDict *options, int flags,
-+                    Error **errp)
-+{
-+    QemuOpts *opts;
-+    BDRVPBSState *s = bs->opaque;
-+    char *pbs_error = NULL;
-+
-+    opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
-+    qemu_opts_absorb_qdict(opts, options, &error_abort);
-+
-+    s->repository = g_strdup(qemu_opt_get(opts, PBS_OPT_REPOSITORY));
-+    s->snapshot = g_strdup(qemu_opt_get(opts, PBS_OPT_SNAPSHOT));
-+    s->archive = g_strdup(qemu_opt_get(opts, PBS_OPT_ARCHIVE));
-+    const char *keyfile = qemu_opt_get(opts, PBS_OPT_KEYFILE);
-+    const char *password = qemu_opt_get(opts, PBS_OPT_PASSWORD);
-+    const char *fingerprint = qemu_opt_get(opts, PBS_OPT_FINGERPRINT);
-+    const char *key_password = qemu_opt_get(opts, PBS_OPT_ENCRYPTION_PASSWORD);
-+
-+    if (!password) {
-+        password = getenv("PBS_PASSWORD");
-+    }
-+    if (!fingerprint) {
-+        fingerprint = getenv("PBS_FINGERPRINT");
-+    }
-+    if (!key_password) {
-+        key_password = getenv("PBS_ENCRYPTION_PASSWORD");
-+    }
-+
-+    /* connect to PBS server in read mode */
-+    s->conn = proxmox_restore_new(s->repository, s->snapshot, password,
-+        keyfile, key_password, fingerprint, &pbs_error);
-+
-+    /* invalidates qemu_opt_get char pointers from above */
-+    qemu_opts_del(opts);
-+
-+    if (!s->conn) {
-+        if (pbs_error && errp) error_setg(errp, "PBS restore_new failed: %s", pbs_error);
-+        if (pbs_error) proxmox_backup_free_error(pbs_error);
-+        return -ENOMEM;
-+    }
-+
-+    int ret = proxmox_restore_connect(s->conn, &pbs_error);
-+    if (ret < 0) {
-+        if (pbs_error && errp) error_setg(errp, "PBS connect failed: %s", pbs_error);
-+        if (pbs_error) proxmox_backup_free_error(pbs_error);
-+        return -ECONNREFUSED;
-+    }
-+
-+    /* acquire handle and length */
-+    s->aid = proxmox_restore_open_image(s->conn, s->archive, &pbs_error);
-+    if (s->aid < 0) {
-+        if (pbs_error && errp) error_setg(errp, "PBS open_image failed: %s", pbs_error);
-+        if (pbs_error) proxmox_backup_free_error(pbs_error);
-+        return -ENODEV;
-+    }
-+    s->length = proxmox_restore_get_image_length(s->conn, s->aid, &pbs_error);
-+    if (s->length < 0) {
-+        if (pbs_error && errp) error_setg(errp, "PBS get_image_length failed: %s", pbs_error);
-+        if (pbs_error) proxmox_backup_free_error(pbs_error);
-+        return -EINVAL;
-+    }
-+
-+    return 0;
-+}
-+
-+static int pbs_file_open(BlockDriverState *bs, QDict *options, int flags,
-+                         Error **errp)
-+{
-+    return pbs_open(bs, options, flags, errp);
-+}
-+
-+static void pbs_close(BlockDriverState *bs) {
-+    BDRVPBSState *s = bs->opaque;
-+    g_free(s->repository);
-+    g_free(s->snapshot);
-+    g_free(s->archive);
-+    proxmox_restore_disconnect(s->conn);
-+}
-+
-+static int64_t pbs_getlength(BlockDriverState *bs)
-+{
-+    BDRVPBSState *s = bs->opaque;
-+    return s->length;
-+}
-+
-+typedef struct ReadCallbackData {
-+    Coroutine *co;
-+    AioContext *ctx;
-+} ReadCallbackData;
-+
-+static void read_callback(void *callback_data)
-+{
-+    ReadCallbackData *rcb = callback_data;
-+    aio_co_schedule(rcb->ctx, rcb->co);
-+}
-+
-+static coroutine_fn int pbs_co_preadv(BlockDriverState *bs,
-+                                      int64_t offset, int64_t bytes,
-+                                      QEMUIOVector *qiov, BdrvRequestFlags flags)
-+{
-+    BDRVPBSState *s = bs->opaque;
-+    int ret;
-+    char *pbs_error = NULL;
-+    uint8_t *buf = malloc(bytes);
-+
-+    if (offset < 0 || bytes < 0) {
-+        fprintf(stderr, "unexpected negative 'offset' or 'bytes' value!\n");
-+        return -EINVAL;
-+    }
-+
-+    ReadCallbackData rcb = {
-+        .co = qemu_coroutine_self(),
-+        .ctx = bdrv_get_aio_context(bs),
-+    };
-+
-+    proxmox_restore_read_image_at_async(s->conn, s->aid, buf, (uint64_t)offset, (uint64_t)bytes,
-+                                        read_callback, (void *) &rcb, &ret, &pbs_error);
-+
-+    qemu_coroutine_yield();
-+
-+    if (ret < 0) {
-+        fprintf(stderr, "error during PBS read: %s\n", pbs_error ? pbs_error : "unknown error");
-+        if (pbs_error) proxmox_backup_free_error(pbs_error);
-+        return -EIO;
-+    }
-+
-+    qemu_iovec_from_buf(qiov, 0, buf, bytes);
-+    free(buf);
-+
-+    return ret;
-+}
-+
-+static coroutine_fn int pbs_co_pwritev(BlockDriverState *bs,
-+                                       int64_t offset, int64_t bytes,
-+                                       QEMUIOVector *qiov, BdrvRequestFlags flags)
-+{
-+    fprintf(stderr, "pbs-bdrv: cannot write to backup file, make sure "
-+           "any attached disk devices are set to read-only!\n");
-+    return -EPERM;
-+}
-+
-+static void pbs_refresh_filename(BlockDriverState *bs)
-+{
-+    BDRVPBSState *s = bs->opaque;
-+    snprintf(bs->exact_filename, sizeof(bs->exact_filename), "%s/%s(%s)",
-+             s->repository, s->snapshot, s->archive);
-+}
-+
-+static const char *const pbs_strong_runtime_opts[] = {
-+    NULL
-+};
-+
-+static BlockDriver bdrv_pbs_co = {
-+    .format_name            = "pbs",
-+    .protocol_name          = "pbs",
-+    .instance_size          = sizeof(BDRVPBSState),
-+
-+    .bdrv_parse_filename    = pbs_parse_filename,
-+
-+    .bdrv_file_open         = pbs_file_open,
-+    .bdrv_open              = pbs_open,
-+    .bdrv_close             = pbs_close,
-+    .bdrv_getlength         = pbs_getlength,
-+
-+    .bdrv_co_preadv         = pbs_co_preadv,
-+    .bdrv_co_pwritev        = pbs_co_pwritev,
-+
-+    .bdrv_refresh_filename  = pbs_refresh_filename,
-+    .strong_runtime_opts    = pbs_strong_runtime_opts,
-+};
-+
-+static void bdrv_pbs_init(void)
-+{
-+    bdrv_register(&bdrv_pbs_co);
-+}
-+
-+block_init(bdrv_pbs_init);
-diff --git a/configure b/configure
-index 48c21775f3..eda4e9225a 100755
---- a/configure
-+++ b/configure
-@@ -356,6 +356,7 @@ vdi=${default_feature:-yes}
- vvfat=${default_feature:-yes}
- qed=${default_feature:-yes}
- parallels=${default_feature:-yes}
-+pbs_bdrv="yes"
- debug_mutex="no"
- plugins="$default_feature"
- rng_none="no"
-@@ -1126,6 +1127,10 @@ for opt do
-   ;;
-   --enable-parallels) parallels="yes"
-   ;;
-+  --disable-pbs-bdrv) pbs_bdrv="no"
-+  ;;
-+  --enable-pbs-bdrv) pbs_bdrv="yes"
-+  ;;
-   --disable-vhost-user) vhost_user="no"
-   ;;
-   --enable-vhost-user) vhost_user="yes"
-@@ -1465,6 +1470,7 @@ cat << EOF
-   vvfat           vvfat image format support
-   qed             qed image format support
-   parallels       parallels image format support
-+  pbs-bdrv        Proxmox backup server read-only block driver support
-   crypto-afalg    Linux AF_ALG crypto backend driver
-   debug-mutex     mutex debugging support
-   rng-none        dummy RNG, avoid using /dev/(u)random and getrandom()
-@@ -3534,6 +3540,9 @@ if test "$xen" = "enabled" ; then
-   echo "XEN_CFLAGS=$xen_cflags" >> $config_host_mak
-   echo "XEN_LIBS=$xen_libs" >> $config_host_mak
- fi
-+if test "$pbs_bdrv" = "yes" ; then
-+  echo "CONFIG_PBS_BDRV=y" >> $config_host_mak
-+fi
- if test "$vhost_scsi" = "yes" ; then
-   echo "CONFIG_VHOST_SCSI=y" >> $config_host_mak
- fi
-diff --git a/meson.build b/meson.build
-index 1a4dfab4e2..85b3c63199 100644
---- a/meson.build
-+++ b/meson.build
-@@ -3448,6 +3448,7 @@ summary_info += {'lzfse support':     liblzfse}
- summary_info += {'zstd support':      zstd}
- summary_info += {'NUMA host support': config_host.has_key('CONFIG_NUMA')}
- summary_info += {'libxml2':           libxml2}
-+summary_info += {'PBS bdrv support':  config_host.has_key('CONFIG_PBS_BDRV')}
- summary_info += {'capstone':          capstone_opt == 'internal' ? capstone_opt : capstone}
- summary_info += {'libpmem support':   libpmem}
- summary_info += {'libdaxctl support': libdaxctl}
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index 88835ebcf6..fd1ba7ccac 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -3074,6 +3074,7 @@
-             'luks', 'nbd', 'nfs', 'null-aio', 'null-co', 'nvme', 'parallels',
-             'preallocate', 'qcow', 'qcow2', 'qed', 'quorum', 'raw', 'rbd',
-             { 'name': 'replication', 'if': 'CONFIG_REPLICATION' },
-+            'pbs',
-             'ssh', 'throttle', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat' ] }
- ##
-@@ -3146,6 +3147,17 @@
- { 'struct': 'BlockdevOptionsNull',
-   'data': { '*size': 'int', '*latency-ns': 'uint64', '*read-zeroes': 'bool' } }
-+##
-+# @BlockdevOptionsPbs:
-+#
-+# Driver specific block device options for the PBS backend.
-+#
-+##
-+{ 'struct': 'BlockdevOptionsPbs',
-+  'data': { 'repository': 'str', 'snapshot': 'str', 'archive': 'str',
-+            '*keyfile': 'str', '*password': 'str', '*fingerprint': 'str',
-+            '*key_password': 'str' } }
-+
- ##
- # @BlockdevOptionsNVMe:
- #
-@@ -4388,6 +4400,7 @@
-       'nfs':        'BlockdevOptionsNfs',
-       'null-aio':   'BlockdevOptionsNull',
-       'null-co':    'BlockdevOptionsNull',
-+      'pbs':        'BlockdevOptionsPbs',
-       'nvme':       'BlockdevOptionsNVMe',
-       'parallels':  'BlockdevOptionsGenericFormat',
-       'preallocate':'BlockdevOptionsPreallocate',
diff --git a/debian/patches/pve/0031-PVE-various-PBS-fixes.patch b/debian/patches/pve/0031-PVE-various-PBS-fixes.patch
new file mode 100644 (file)
index 0000000..39a3eb2
--- /dev/null
@@ -0,0 +1,219 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Dietmar Maurer <dietmar@proxmox.com>
+Date: Thu, 9 Jul 2020 12:53:08 +0200
+Subject: [PATCH] PVE: various PBS fixes
+
+pbs: fix crypt and compress parameters
+Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
+
+PVE: handle PBS write callback with big blocks correctly
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+
+PVE: add zero block handling to PBS dump callback
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ block/monitor/block-hmp-cmds.c |  4 ++-
+ pve-backup.c                   | 59 ++++++++++++++++++++++++++--------
+ qapi/block-core.json           |  6 ++++
+ 3 files changed, 55 insertions(+), 14 deletions(-)
+
+diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
+index 3c06734e6d..4481b60a5c 100644
+--- a/block/monitor/block-hmp-cmds.c
++++ b/block/monitor/block-hmp-cmds.c
+@@ -1042,7 +1042,9 @@ void hmp_backup(Monitor *mon, const QDict *qdict)
+         false, NULL, // PBS fingerprint
+         false, NULL, // PBS backup-id
+         false, 0, // PBS backup-time
+-        false, false, // PBS incremental
++        false, false, // PBS use-dirty-bitmap
++        false, false, // PBS compress
++        false, false, // PBS encrypt
+         true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
+         false, NULL, false, NULL, !!devlist,
+         devlist, qdict_haskey(qdict, "speed"), speed, &error);
+diff --git a/pve-backup.c b/pve-backup.c
+index 1c49cd178d..c15abefdda 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -8,6 +8,7 @@
+ #include "block/blockjob.h"
+ #include "qapi/qapi-commands-block.h"
+ #include "qapi/qmp/qerror.h"
++#include "qemu/cutils.h"
+ /* PVE backup state and related function */
+@@ -67,6 +68,7 @@ opts_init(pvebackup_init);
+ typedef struct PVEBackupDevInfo {
+     BlockDriverState *bs;
+     size_t size;
++    uint64_t block_size;
+     uint8_t dev_id;
+     bool completed;
+     char targetfile[PATH_MAX];
+@@ -135,10 +137,13 @@ pvebackup_co_dump_pbs_cb(
+     PVEBackupDevInfo *di = opaque;
+     assert(backup_state.pbs);
++    assert(buf);
+     Error *local_err = NULL;
+     int pbs_res = -1;
++    bool is_zero_block = size == di->block_size && buffer_is_zero(buf, size);
++
+     qemu_co_mutex_lock(&backup_state.dump_callback_mutex);
+     // avoid deadlock if job is cancelled
+@@ -147,16 +152,28 @@ pvebackup_co_dump_pbs_cb(
+         return -1;
+     }
+-    pbs_res = proxmox_backup_co_write_data(backup_state.pbs, di->dev_id, buf, start, size, &local_err);
++    uint64_t transferred = 0;
++    uint64_t reused = 0;
++    while (transferred < size) {
++        uint64_t left = size - transferred;
++        uint64_t to_transfer = left < di->block_size ? left : di->block_size;
++
++        pbs_res = proxmox_backup_co_write_data(backup_state.pbs, di->dev_id,
++            is_zero_block ? NULL : buf + transferred, start + transferred,
++            to_transfer, &local_err);
++        transferred += to_transfer;
++
++        if (pbs_res < 0) {
++            pvebackup_propagate_error(local_err);
++            qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
++            return pbs_res;
++        }
++
++        reused += pbs_res == 0 ? to_transfer : 0;
++    }
++
+     qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
+-
+-    if (pbs_res < 0) {
+-        pvebackup_propagate_error(local_err);
+-        return pbs_res;
+-    } else {
+-        size_t reused = (pbs_res == 0) ? size : 0;
+-        pvebackup_add_transfered_bytes(size, !buf ? size : 0, reused);
+-    }
++    pvebackup_add_transfered_bytes(size, is_zero_block ? size : 0, reused);
+     return size;
+ }
+@@ -178,6 +195,7 @@ pvebackup_co_dump_vma_cb(
+     int ret = -1;
+     assert(backup_state.vmaw);
++    assert(buf);
+     uint64_t remaining = size;
+@@ -204,9 +222,7 @@ pvebackup_co_dump_vma_cb(
+         qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
+         ++cluster_num;
+-        if (buf) {
+-            buf += VMA_CLUSTER_SIZE;
+-        }
++        buf += VMA_CLUSTER_SIZE;
+         if (ret < 0) {
+             Error *local_err = NULL;
+             vma_writer_error_propagate(backup_state.vmaw, &local_err);
+@@ -569,6 +585,10 @@ typedef struct QmpBackupTask {
+     const char *firewall_file;
+     bool has_devlist;
+     const char *devlist;
++    bool has_compress;
++    bool compress;
++    bool has_encrypt;
++    bool encrypt;
+     bool has_speed;
+     int64_t speed;
+     Error **errp;
+@@ -692,6 +712,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+         bool use_dirty_bitmap = task->has_use_dirty_bitmap && task->use_dirty_bitmap;
++
+         char *pbs_err = NULL;
+         pbs = proxmox_backup_new(
+             task->backup_file,
+@@ -701,8 +722,10 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             task->has_password ? task->password : NULL,
+             task->has_keyfile ? task->keyfile : NULL,
+             task->has_key_password ? task->key_password : NULL,
++            task->has_compress ? task->compress : true,
++            task->has_encrypt ? task->encrypt : task->has_keyfile,
+             task->has_fingerprint ? task->fingerprint : NULL,
+-            &pbs_err);
++             &pbs_err);
+         if (!pbs) {
+             error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
+@@ -721,6 +744,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
+             l = g_list_next(l);
++            di->block_size = dump_cb_block_size;
++
+             const char *devname = bdrv_get_device_name(di->bs);
+             BdrvDirtyBitmap *bitmap = bdrv_find_dirty_bitmap(di->bs, PBS_BITMAP_NAME);
+@@ -941,6 +966,8 @@ UuidInfo *qmp_backup(
+     bool has_backup_id, const char *backup_id,
+     bool has_backup_time, int64_t backup_time,
+     bool has_use_dirty_bitmap, bool use_dirty_bitmap,
++    bool has_compress, bool compress,
++    bool has_encrypt, bool encrypt,
+     bool has_format, BackupFormat format,
+     bool has_config_file, const char *config_file,
+     bool has_firewall_file, const char *firewall_file,
+@@ -951,6 +978,8 @@ UuidInfo *qmp_backup(
+         .backup_file = backup_file,
+         .has_password = has_password,
+         .password = password,
++        .has_keyfile = has_keyfile,
++        .keyfile = keyfile,
+         .has_key_password = has_key_password,
+         .key_password = key_password,
+         .has_fingerprint = has_fingerprint,
+@@ -961,6 +990,10 @@ UuidInfo *qmp_backup(
+         .backup_time = backup_time,
+         .has_use_dirty_bitmap = has_use_dirty_bitmap,
+         .use_dirty_bitmap = use_dirty_bitmap,
++        .has_compress = has_compress,
++        .compress = compress,
++        .has_encrypt = has_encrypt,
++        .encrypt = encrypt,
+         .has_format = has_format,
+         .format = format,
+         .has_config_file = has_config_file,
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index 379a8dd147..88835ebcf6 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -822,6 +822,10 @@
+ #
+ # @use-dirty-bitmap: use dirty bitmap to detect incremental changes since last job (optional for format 'pbs')
+ #
++# @compress: use compression (optional for format 'pbs', defaults to true)
++#
++# @encrypt: use encryption ((optional for format 'pbs', defaults to true if there is a keyfile)
++#
+ # Returns: the uuid of the backup job
+ #
+ ##
+@@ -833,6 +837,8 @@
+                                     '*backup-id': 'str',
+                                     '*backup-time': 'int',
+                                     '*use-dirty-bitmap': 'bool',
++                                    '*compress': 'bool',
++                                    '*encrypt': 'bool',
+                                     '*format': 'BackupFormat',
+                                     '*config-file': 'str',
+                                     '*firewall-file': 'str',
diff --git a/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch b/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch
new file mode 100644 (file)
index 0000000..10edf43
--- /dev/null
@@ -0,0 +1,407 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Wed, 8 Jul 2020 09:50:54 +0200
+Subject: [PATCH] PVE: Add PBS block driver to map backup archives into VMs
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+[error cleanups, file_open implementation]
+Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[adapt to changed function signatures]
+Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
+---
+ block/meson.build    |   3 +
+ block/pbs.c          | 276 +++++++++++++++++++++++++++++++++++++++++++
+ configure            |   9 ++
+ meson.build          |   1 +
+ qapi/block-core.json |  13 ++
+ 5 files changed, 302 insertions(+)
+ create mode 100644 block/pbs.c
+
+diff --git a/block/meson.build b/block/meson.build
+index 9d3dd5b7c3..8c758c0218 100644
+--- a/block/meson.build
++++ b/block/meson.build
+@@ -51,6 +51,9 @@ block_ss.add(files(
+   '../pve-backup.c',
+ ), libproxmox_backup_qemu)
++block_ss.add(when: 'CONFIG_PBS_BDRV', if_true: files('pbs.c'))
++block_ss.add(when: 'CONFIG_PBS_BDRV', if_true: libproxmox_backup_qemu)
++
+ softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c'))
+diff --git a/block/pbs.c b/block/pbs.c
+new file mode 100644
+index 0000000000..0b05ea9080
+--- /dev/null
++++ b/block/pbs.c
+@@ -0,0 +1,276 @@
++/*
++ * Proxmox Backup Server read-only block driver
++ */
++
++#include "qemu/osdep.h"
++#include "qapi/error.h"
++#include "qapi/qmp/qdict.h"
++#include "qapi/qmp/qstring.h"
++#include "qemu/module.h"
++#include "qemu/option.h"
++#include "qemu/cutils.h"
++#include "block/block_int.h"
++
++#include <proxmox-backup-qemu.h>
++
++#define PBS_OPT_REPOSITORY "repository"
++#define PBS_OPT_SNAPSHOT "snapshot"
++#define PBS_OPT_ARCHIVE "archive"
++#define PBS_OPT_KEYFILE "keyfile"
++#define PBS_OPT_PASSWORD "password"
++#define PBS_OPT_FINGERPRINT "fingerprint"
++#define PBS_OPT_ENCRYPTION_PASSWORD "key_password"
++
++typedef struct {
++    ProxmoxRestoreHandle *conn;
++    char aid;
++    int64_t length;
++
++    char *repository;
++    char *snapshot;
++    char *archive;
++} BDRVPBSState;
++
++static QemuOptsList runtime_opts = {
++    .name = "pbs",
++    .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
++    .desc = {
++        {
++            .name = PBS_OPT_REPOSITORY,
++            .type = QEMU_OPT_STRING,
++            .help = "The server address and repository to connect to.",
++        },
++        {
++            .name = PBS_OPT_SNAPSHOT,
++            .type = QEMU_OPT_STRING,
++            .help = "The snapshot to read.",
++        },
++        {
++            .name = PBS_OPT_ARCHIVE,
++            .type = QEMU_OPT_STRING,
++            .help = "Which archive within the snapshot should be accessed.",
++        },
++        {
++            .name = PBS_OPT_PASSWORD,
++            .type = QEMU_OPT_STRING,
++            .help = "Server password. Can be passed as env var 'PBS_PASSWORD'.",
++        },
++        {
++            .name = PBS_OPT_FINGERPRINT,
++            .type = QEMU_OPT_STRING,
++            .help = "Server fingerprint. Can be passed as env var 'PBS_FINGERPRINT'.",
++        },
++        {
++            .name = PBS_OPT_ENCRYPTION_PASSWORD,
++            .type = QEMU_OPT_STRING,
++            .help = "Optional: Key password. Can be passed as env var 'PBS_ENCRYPTION_PASSWORD'.",
++        },
++        {
++            .name = PBS_OPT_KEYFILE,
++            .type = QEMU_OPT_STRING,
++            .help = "Optional: The path to the keyfile to use.",
++        },
++        { /* end of list */ }
++    },
++};
++
++
++// filename format:
++// pbs:repository=<repo>,snapshot=<snap>,password=<pw>,key_password=<kpw>,fingerprint=<fp>,archive=<archive>
++static void pbs_parse_filename(const char *filename, QDict *options,
++                                     Error **errp)
++{
++
++    if (!strstart(filename, "pbs:", &filename)) {
++        if (errp) error_setg(errp, "pbs_parse_filename failed - missing 'pbs:' prefix");
++    }
++
++
++    QemuOpts *opts = qemu_opts_parse_noisily(&runtime_opts, filename, false);
++    if (!opts) {
++        if (errp) error_setg(errp, "pbs_parse_filename failed");
++        return;
++    }
++
++    qemu_opts_to_qdict(opts, options);
++
++    qemu_opts_del(opts);
++}
++
++static int pbs_open(BlockDriverState *bs, QDict *options, int flags,
++                    Error **errp)
++{
++    QemuOpts *opts;
++    BDRVPBSState *s = bs->opaque;
++    char *pbs_error = NULL;
++
++    opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
++    qemu_opts_absorb_qdict(opts, options, &error_abort);
++
++    s->repository = g_strdup(qemu_opt_get(opts, PBS_OPT_REPOSITORY));
++    s->snapshot = g_strdup(qemu_opt_get(opts, PBS_OPT_SNAPSHOT));
++    s->archive = g_strdup(qemu_opt_get(opts, PBS_OPT_ARCHIVE));
++    const char *keyfile = qemu_opt_get(opts, PBS_OPT_KEYFILE);
++    const char *password = qemu_opt_get(opts, PBS_OPT_PASSWORD);
++    const char *fingerprint = qemu_opt_get(opts, PBS_OPT_FINGERPRINT);
++    const char *key_password = qemu_opt_get(opts, PBS_OPT_ENCRYPTION_PASSWORD);
++
++    if (!password) {
++        password = getenv("PBS_PASSWORD");
++    }
++    if (!fingerprint) {
++        fingerprint = getenv("PBS_FINGERPRINT");
++    }
++    if (!key_password) {
++        key_password = getenv("PBS_ENCRYPTION_PASSWORD");
++    }
++
++    /* connect to PBS server in read mode */
++    s->conn = proxmox_restore_new(s->repository, s->snapshot, password,
++        keyfile, key_password, fingerprint, &pbs_error);
++
++    /* invalidates qemu_opt_get char pointers from above */
++    qemu_opts_del(opts);
++
++    if (!s->conn) {
++        if (pbs_error && errp) error_setg(errp, "PBS restore_new failed: %s", pbs_error);
++        if (pbs_error) proxmox_backup_free_error(pbs_error);
++        return -ENOMEM;
++    }
++
++    int ret = proxmox_restore_connect(s->conn, &pbs_error);
++    if (ret < 0) {
++        if (pbs_error && errp) error_setg(errp, "PBS connect failed: %s", pbs_error);
++        if (pbs_error) proxmox_backup_free_error(pbs_error);
++        return -ECONNREFUSED;
++    }
++
++    /* acquire handle and length */
++    s->aid = proxmox_restore_open_image(s->conn, s->archive, &pbs_error);
++    if (s->aid < 0) {
++        if (pbs_error && errp) error_setg(errp, "PBS open_image failed: %s", pbs_error);
++        if (pbs_error) proxmox_backup_free_error(pbs_error);
++        return -ENODEV;
++    }
++    s->length = proxmox_restore_get_image_length(s->conn, s->aid, &pbs_error);
++    if (s->length < 0) {
++        if (pbs_error && errp) error_setg(errp, "PBS get_image_length failed: %s", pbs_error);
++        if (pbs_error) proxmox_backup_free_error(pbs_error);
++        return -EINVAL;
++    }
++
++    return 0;
++}
++
++static int pbs_file_open(BlockDriverState *bs, QDict *options, int flags,
++                         Error **errp)
++{
++    return pbs_open(bs, options, flags, errp);
++}
++
++static void pbs_close(BlockDriverState *bs) {
++    BDRVPBSState *s = bs->opaque;
++    g_free(s->repository);
++    g_free(s->snapshot);
++    g_free(s->archive);
++    proxmox_restore_disconnect(s->conn);
++}
++
++static int64_t pbs_getlength(BlockDriverState *bs)
++{
++    BDRVPBSState *s = bs->opaque;
++    return s->length;
++}
++
++typedef struct ReadCallbackData {
++    Coroutine *co;
++    AioContext *ctx;
++} ReadCallbackData;
++
++static void read_callback(void *callback_data)
++{
++    ReadCallbackData *rcb = callback_data;
++    aio_co_schedule(rcb->ctx, rcb->co);
++}
++
++static coroutine_fn int pbs_co_preadv(BlockDriverState *bs,
++                                      int64_t offset, int64_t bytes,
++                                      QEMUIOVector *qiov, BdrvRequestFlags flags)
++{
++    BDRVPBSState *s = bs->opaque;
++    int ret;
++    char *pbs_error = NULL;
++    uint8_t *buf = malloc(bytes);
++
++    if (offset < 0 || bytes < 0) {
++        fprintf(stderr, "unexpected negative 'offset' or 'bytes' value!\n");
++        return -EINVAL;
++    }
++
++    ReadCallbackData rcb = {
++        .co = qemu_coroutine_self(),
++        .ctx = bdrv_get_aio_context(bs),
++    };
++
++    proxmox_restore_read_image_at_async(s->conn, s->aid, buf, (uint64_t)offset, (uint64_t)bytes,
++                                        read_callback, (void *) &rcb, &ret, &pbs_error);
++
++    qemu_coroutine_yield();
++
++    if (ret < 0) {
++        fprintf(stderr, "error during PBS read: %s\n", pbs_error ? pbs_error : "unknown error");
++        if (pbs_error) proxmox_backup_free_error(pbs_error);
++        return -EIO;
++    }
++
++    qemu_iovec_from_buf(qiov, 0, buf, bytes);
++    free(buf);
++
++    return ret;
++}
++
++static coroutine_fn int pbs_co_pwritev(BlockDriverState *bs,
++                                       int64_t offset, int64_t bytes,
++                                       QEMUIOVector *qiov, BdrvRequestFlags flags)
++{
++    fprintf(stderr, "pbs-bdrv: cannot write to backup file, make sure "
++           "any attached disk devices are set to read-only!\n");
++    return -EPERM;
++}
++
++static void pbs_refresh_filename(BlockDriverState *bs)
++{
++    BDRVPBSState *s = bs->opaque;
++    snprintf(bs->exact_filename, sizeof(bs->exact_filename), "%s/%s(%s)",
++             s->repository, s->snapshot, s->archive);
++}
++
++static const char *const pbs_strong_runtime_opts[] = {
++    NULL
++};
++
++static BlockDriver bdrv_pbs_co = {
++    .format_name            = "pbs",
++    .protocol_name          = "pbs",
++    .instance_size          = sizeof(BDRVPBSState),
++
++    .bdrv_parse_filename    = pbs_parse_filename,
++
++    .bdrv_file_open         = pbs_file_open,
++    .bdrv_open              = pbs_open,
++    .bdrv_close             = pbs_close,
++    .bdrv_getlength         = pbs_getlength,
++
++    .bdrv_co_preadv         = pbs_co_preadv,
++    .bdrv_co_pwritev        = pbs_co_pwritev,
++
++    .bdrv_refresh_filename  = pbs_refresh_filename,
++    .strong_runtime_opts    = pbs_strong_runtime_opts,
++};
++
++static void bdrv_pbs_init(void)
++{
++    bdrv_register(&bdrv_pbs_co);
++}
++
++block_init(bdrv_pbs_init);
+diff --git a/configure b/configure
+index 48c21775f3..eda4e9225a 100755
+--- a/configure
++++ b/configure
+@@ -356,6 +356,7 @@ vdi=${default_feature:-yes}
+ vvfat=${default_feature:-yes}
+ qed=${default_feature:-yes}
+ parallels=${default_feature:-yes}
++pbs_bdrv="yes"
+ debug_mutex="no"
+ plugins="$default_feature"
+ rng_none="no"
+@@ -1126,6 +1127,10 @@ for opt do
+   ;;
+   --enable-parallels) parallels="yes"
+   ;;
++  --disable-pbs-bdrv) pbs_bdrv="no"
++  ;;
++  --enable-pbs-bdrv) pbs_bdrv="yes"
++  ;;
+   --disable-vhost-user) vhost_user="no"
+   ;;
+   --enable-vhost-user) vhost_user="yes"
+@@ -1465,6 +1470,7 @@ cat << EOF
+   vvfat           vvfat image format support
+   qed             qed image format support
+   parallels       parallels image format support
++  pbs-bdrv        Proxmox backup server read-only block driver support
+   crypto-afalg    Linux AF_ALG crypto backend driver
+   debug-mutex     mutex debugging support
+   rng-none        dummy RNG, avoid using /dev/(u)random and getrandom()
+@@ -3534,6 +3540,9 @@ if test "$xen" = "enabled" ; then
+   echo "XEN_CFLAGS=$xen_cflags" >> $config_host_mak
+   echo "XEN_LIBS=$xen_libs" >> $config_host_mak
+ fi
++if test "$pbs_bdrv" = "yes" ; then
++  echo "CONFIG_PBS_BDRV=y" >> $config_host_mak
++fi
+ if test "$vhost_scsi" = "yes" ; then
+   echo "CONFIG_VHOST_SCSI=y" >> $config_host_mak
+ fi
+diff --git a/meson.build b/meson.build
+index 1a4dfab4e2..85b3c63199 100644
+--- a/meson.build
++++ b/meson.build
+@@ -3448,6 +3448,7 @@ summary_info += {'lzfse support':     liblzfse}
+ summary_info += {'zstd support':      zstd}
+ summary_info += {'NUMA host support': config_host.has_key('CONFIG_NUMA')}
+ summary_info += {'libxml2':           libxml2}
++summary_info += {'PBS bdrv support':  config_host.has_key('CONFIG_PBS_BDRV')}
+ summary_info += {'capstone':          capstone_opt == 'internal' ? capstone_opt : capstone}
+ summary_info += {'libpmem support':   libpmem}
+ summary_info += {'libdaxctl support': libdaxctl}
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index 88835ebcf6..fd1ba7ccac 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -3074,6 +3074,7 @@
+             'luks', 'nbd', 'nfs', 'null-aio', 'null-co', 'nvme', 'parallels',
+             'preallocate', 'qcow', 'qcow2', 'qed', 'quorum', 'raw', 'rbd',
+             { 'name': 'replication', 'if': 'CONFIG_REPLICATION' },
++            'pbs',
+             'ssh', 'throttle', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat' ] }
+ ##
+@@ -3146,6 +3147,17 @@
+ { 'struct': 'BlockdevOptionsNull',
+   'data': { '*size': 'int', '*latency-ns': 'uint64', '*read-zeroes': 'bool' } }
++##
++# @BlockdevOptionsPbs:
++#
++# Driver specific block device options for the PBS backend.
++#
++##
++{ 'struct': 'BlockdevOptionsPbs',
++  'data': { 'repository': 'str', 'snapshot': 'str', 'archive': 'str',
++            '*keyfile': 'str', '*password': 'str', '*fingerprint': 'str',
++            '*key_password': 'str' } }
++
+ ##
+ # @BlockdevOptionsNVMe:
+ #
+@@ -4388,6 +4400,7 @@
+       'nfs':        'BlockdevOptionsNfs',
+       'null-aio':   'BlockdevOptionsNull',
+       'null-co':    'BlockdevOptionsNull',
++      'pbs':        'BlockdevOptionsPbs',
+       'nvme':       'BlockdevOptionsNVMe',
+       'parallels':  'BlockdevOptionsGenericFormat',
+       'preallocate':'BlockdevOptionsPreallocate',
diff --git a/debian/patches/pve/0032-PVE-add-query_proxmox_support-QMP-command.patch b/debian/patches/pve/0032-PVE-add-query_proxmox_support-QMP-command.patch
deleted file mode 100644 (file)
index 0b92aab..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Wed, 8 Jul 2020 11:57:53 +0200
-Subject: [PATCH] PVE: add query_proxmox_support QMP command
-
-Generic interface for future use, currently used for PBS dirty-bitmap
-backup support.
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-[PVE: query-proxmox-support: include library version]
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
----
- pve-backup.c         |  9 +++++++++
- qapi/block-core.json | 29 +++++++++++++++++++++++++++++
- 2 files changed, 38 insertions(+)
-
-diff --git a/pve-backup.c b/pve-backup.c
-index c15abefdda..4684789813 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -1075,3 +1075,12 @@ BackupStatus *qmp_query_backup(Error **errp)
-     return info;
- }
-+
-+ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp)
-+{
-+    ProxmoxSupportStatus *ret = g_malloc0(sizeof(*ret));
-+    ret->pbs_library_version = g_strdup(proxmox_backup_qemu_version());
-+    ret->pbs_dirty_bitmap = true;
-+    ret->pbs_dirty_bitmap_savevm = true;
-+    return ret;
-+}
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index fd1ba7ccac..fc498b779d 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -867,6 +867,35 @@
- ##
- { 'command': 'backup-cancel' }
-+##
-+# @ProxmoxSupportStatus:
-+#
-+# Contains info about supported features added by Proxmox.
-+#
-+# @pbs-dirty-bitmap: True if dirty-bitmap-incremental backups to PBS are
-+#                    supported.
-+#
-+# @pbs-dirty-bitmap-savevm: True if 'dirty-bitmaps' migration capability can
-+#                           safely be set for savevm-async.
-+#
-+# @pbs-library-version: Running version of libproxmox-backup-qemu0 library.
-+#
-+##
-+{ 'struct': 'ProxmoxSupportStatus',
-+  'data': { 'pbs-dirty-bitmap': 'bool',
-+            'pbs-dirty-bitmap-savevm': 'bool',
-+            'pbs-library-version': 'str' } }
-+
-+##
-+# @query-proxmox-support:
-+#
-+# Returns information about supported features added by Proxmox.
-+#
-+# Returns: @ProxmoxSupportStatus
-+#
-+##
-+{ 'command': 'query-proxmox-support', 'returns': 'ProxmoxSupportStatus' }
-+
- ##
- # @BlockDeviceTimedStats:
- #
diff --git a/debian/patches/pve/0033-PVE-add-query-pbs-bitmap-info-QMP-call.patch b/debian/patches/pve/0033-PVE-add-query-pbs-bitmap-info-QMP-call.patch
deleted file mode 100644 (file)
index a8e3458..0000000
+++ /dev/null
@@ -1,441 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Wed, 19 Aug 2020 17:02:00 +0200
-Subject: [PATCH] PVE: add query-pbs-bitmap-info QMP call
-
-Returns advanced information about dirty bitmaps used (or not used) for
-the latest PBS backup.
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- monitor/hmp-cmds.c   |  28 ++++++-----
- pve-backup.c         | 117 ++++++++++++++++++++++++++++++++-----------
- qapi/block-core.json |  56 +++++++++++++++++++++
- 3 files changed, 159 insertions(+), 42 deletions(-)
-
-diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
-index cfd7a60f32..b613190a3c 100644
---- a/monitor/hmp-cmds.c
-+++ b/monitor/hmp-cmds.c
-@@ -198,6 +198,7 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
- void hmp_info_backup(Monitor *mon, const QDict *qdict)
- {
-     BackupStatus *info;
-+    PBSBitmapInfoList *bitmap_info;
-     info = qmp_query_backup(NULL);
-@@ -228,26 +229,29 @@ void hmp_info_backup(Monitor *mon, const QDict *qdict)
-             // this should not happen normally
-             monitor_printf(mon, "Total size: %d\n", 0);
-         } else {
--            bool incremental = false;
-             size_t total_or_dirty = info->total;
--            if (info->has_transferred) {
--                if (info->has_dirty && info->dirty) {
--                     if (info->dirty < info->total) {
--                        total_or_dirty = info->dirty;
--                        incremental = true;
--                    }
--                }
-+            bitmap_info = qmp_query_pbs_bitmap_info(NULL);
-+
-+            while (bitmap_info) {
-+                monitor_printf(mon, "Drive %s:\n",
-+                        bitmap_info->value->drive);
-+                monitor_printf(mon, "  bitmap action: %s\n",
-+                        PBSBitmapAction_str(bitmap_info->value->action));
-+                monitor_printf(mon, "  size: %zd\n",
-+                        bitmap_info->value->size);
-+                monitor_printf(mon, "  dirty: %zd\n",
-+                        bitmap_info->value->dirty);
-+                bitmap_info = bitmap_info->next;
-             }
--            int per = (info->transferred * 100)/total_or_dirty;
--
--            monitor_printf(mon, "Backup mode: %s\n", incremental ? "incremental" : "full");
-+            qapi_free_PBSBitmapInfoList(bitmap_info);
-             int zero_per = (info->has_zero_bytes && info->zero_bytes) ?
-                 (info->zero_bytes * 100)/info->total : 0;
-             monitor_printf(mon, "Total size: %zd\n", info->total);
-+            int trans_per = (info->transferred * 100)/total_or_dirty;
-             monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
--                           info->transferred, per);
-+                           info->transferred, trans_per);
-             monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
-                            info->zero_bytes, zero_per);
-diff --git a/pve-backup.c b/pve-backup.c
-index 4684789813..f90abaa50a 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -46,6 +46,7 @@ static struct PVEBackupState {
-         size_t transferred;
-         size_t reused;
-         size_t zero_bytes;
-+        GList *bitmap_list;
-     } stat;
-     int64_t speed;
-     VmaWriter *vmaw;
-@@ -672,7 +673,6 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     }
-     size_t total = 0;
--    size_t dirty = 0;
-     l = di_list;
-     while (l) {
-@@ -693,18 +693,33 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     uuid_generate(uuid);
-+    qemu_mutex_lock(&backup_state.stat.lock);
-+    backup_state.stat.reused = 0;
-+
-+    /* clear previous backup's bitmap_list */
-+    if (backup_state.stat.bitmap_list) {
-+        GList *bl = backup_state.stat.bitmap_list;
-+        while (bl) {
-+            g_free(((PBSBitmapInfo *)bl->data)->drive);
-+            g_free(bl->data);
-+            bl = g_list_next(bl);
-+        }
-+        g_list_free(backup_state.stat.bitmap_list);
-+        backup_state.stat.bitmap_list = NULL;
-+    }
-+
-     if (format == BACKUP_FORMAT_PBS) {
-         if (!task->has_password) {
-             error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
--            goto err;
-+            goto err_mutex;
-         }
-         if (!task->has_backup_id) {
-             error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
--            goto err;
-+            goto err_mutex;
-         }
-         if (!task->has_backup_time) {
-             error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-time'");
--            goto err;
-+            goto err_mutex;
-         }
-         int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
-@@ -731,12 +746,12 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
-                       "proxmox_backup_new failed: %s", pbs_err);
-             proxmox_backup_free_error(pbs_err);
--            goto err;
-+            goto err_mutex;
-         }
-         int connect_result = proxmox_backup_co_connect(pbs, task->errp);
-         if (connect_result < 0)
--            goto err;
-+            goto err_mutex;
-         /* register all devices */
-         l = di_list;
-@@ -747,6 +762,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             di->block_size = dump_cb_block_size;
-             const char *devname = bdrv_get_device_name(di->bs);
-+            PBSBitmapAction action = PBS_BITMAP_ACTION_NOT_USED;
-+            size_t dirty = di->size;
-             BdrvDirtyBitmap *bitmap = bdrv_find_dirty_bitmap(di->bs, PBS_BITMAP_NAME);
-             bool expect_only_dirty = false;
-@@ -755,49 +772,59 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-                 if (bitmap == NULL) {
-                     bitmap = bdrv_create_dirty_bitmap(di->bs, dump_cb_block_size, PBS_BITMAP_NAME, task->errp);
-                     if (!bitmap) {
--                        goto err;
-+                        goto err_mutex;
-                     }
-+                    action = PBS_BITMAP_ACTION_NEW;
-                 } else {
-                     expect_only_dirty = proxmox_backup_check_incremental(pbs, devname, di->size) != 0;
-                 }
-                 if (expect_only_dirty) {
--                    dirty += bdrv_get_dirty_count(bitmap);
-+                    /* track clean chunks as reused */
-+                    dirty = MIN(bdrv_get_dirty_count(bitmap), di->size);
-+                    backup_state.stat.reused += di->size - dirty;
-+                    action = PBS_BITMAP_ACTION_USED;
-                 } else {
-                     /* mark entire bitmap as dirty to make full backup */
-                     bdrv_set_dirty_bitmap(bitmap, 0, di->size);
--                    dirty += di->size;
-+                    if (action != PBS_BITMAP_ACTION_NEW) {
-+                        action = PBS_BITMAP_ACTION_INVALID;
-+                    }
-                 }
-                 di->bitmap = bitmap;
-             } else {
--                dirty += di->size;
--
-                 /* after a full backup the old dirty bitmap is invalid anyway */
-                 if (bitmap != NULL) {
-                     bdrv_release_dirty_bitmap(bitmap);
-+                    action = PBS_BITMAP_ACTION_NOT_USED_REMOVED;
-                 }
-             }
-             int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, expect_only_dirty, task->errp);
-             if (dev_id < 0) {
--                goto err;
-+                goto err_mutex;
-             }
-             if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, task->errp))) {
--                goto err;
-+                goto err_mutex;
-             }
-             di->dev_id = dev_id;
-+
-+            PBSBitmapInfo *info = g_malloc(sizeof(*info));
-+            info->drive = g_strdup(devname);
-+            info->action = action;
-+            info->size = di->size;
-+            info->dirty = dirty;
-+            backup_state.stat.bitmap_list = g_list_append(backup_state.stat.bitmap_list, info);
-         }
-     } else if (format == BACKUP_FORMAT_VMA) {
--        dirty = total;
--
-         vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
-         if (!vmaw) {
-             if (local_err) {
-                 error_propagate(task->errp, local_err);
-             }
--            goto err;
-+            goto err_mutex;
-         }
-         /* register all devices for vma writer */
-@@ -807,7 +834,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             l = g_list_next(l);
-             if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, task->errp))) {
--                goto err;
-+                goto err_mutex;
-             }
-             const char *devname = bdrv_get_device_name(di->bs);
-@@ -815,16 +842,14 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             if (di->dev_id <= 0) {
-                 error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
-                           "register_stream failed");
--                goto err;
-+                goto err_mutex;
-             }
-         }
-     } else if (format == BACKUP_FORMAT_DIR) {
--        dirty = total;
--
-         if (mkdir(task->backup_file, 0640) != 0) {
-             error_setg_errno(task->errp, errno, "can't create directory '%s'\n",
-                              task->backup_file);
--            goto err;
-+            goto err_mutex;
-         }
-         backup_dir = task->backup_file;
-@@ -841,18 +866,18 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-                             di->size, flags, false, &local_err);
-             if (local_err) {
-                 error_propagate(task->errp, local_err);
--                goto err;
-+                goto err_mutex;
-             }
-             di->target = bdrv_open(di->targetfile, NULL, NULL, flags, &local_err);
-             if (!di->target) {
-                 error_propagate(task->errp, local_err);
--                goto err;
-+                goto err_mutex;
-             }
-         }
-     } else {
-         error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
--        goto err;
-+        goto err_mutex;
-     }
-@@ -860,7 +885,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     if (task->has_config_file) {
-         if (pvebackup_co_add_config(task->config_file, config_name, format, backup_dir,
-                                     vmaw, pbs, task->errp) != 0) {
--            goto err;
-+            goto err_mutex;
-         }
-     }
-@@ -868,12 +893,11 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     if (task->has_firewall_file) {
-         if (pvebackup_co_add_config(task->firewall_file, firewall_name, format, backup_dir,
-                                     vmaw, pbs, task->errp) != 0) {
--            goto err;
-+            goto err_mutex;
-         }
-     }
-     /* initialize global backup_state now */
--
--    qemu_mutex_lock(&backup_state.stat.lock);
-+    /* note: 'reused' and 'bitmap_list' are initialized earlier */
-     if (backup_state.stat.error) {
-         error_free(backup_state.stat.error);
-@@ -893,10 +917,9 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     char *uuid_str = g_strdup(backup_state.stat.uuid_str);
-     backup_state.stat.total = total;
--    backup_state.stat.dirty = dirty;
-+    backup_state.stat.dirty = total - backup_state.stat.reused;
-     backup_state.stat.transferred = 0;
-     backup_state.stat.zero_bytes = 0;
--    backup_state.stat.reused = format == BACKUP_FORMAT_PBS && dirty >= total ? 0 : total - dirty;
-     qemu_mutex_unlock(&backup_state.stat.lock);
-@@ -913,6 +936,9 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     task->result = uuid_info;
-     return;
-+err_mutex:
-+    qemu_mutex_unlock(&backup_state.stat.lock);
-+
- err:
-     l = di_list;
-@@ -1076,11 +1102,42 @@ BackupStatus *qmp_query_backup(Error **errp)
-     return info;
- }
-+PBSBitmapInfoList *qmp_query_pbs_bitmap_info(Error **errp)
-+{
-+    PBSBitmapInfoList *head = NULL, **p_next = &head;
-+
-+    qemu_mutex_lock(&backup_state.stat.lock);
-+
-+    GList *l = backup_state.stat.bitmap_list;
-+    while (l) {
-+        PBSBitmapInfo *info = (PBSBitmapInfo *)l->data;
-+        l = g_list_next(l);
-+
-+        /* clone bitmap info to avoid auto free after QMP marshalling */
-+        PBSBitmapInfo *info_ret = g_malloc0(sizeof(*info_ret));
-+        info_ret->drive = g_strdup(info->drive);
-+        info_ret->action = info->action;
-+        info_ret->size = info->size;
-+        info_ret->dirty = info->dirty;
-+
-+        PBSBitmapInfoList *info_list = g_malloc0(sizeof(*info_list));
-+        info_list->value = info_ret;
-+
-+        *p_next = info_list;
-+        p_next = &info_list->next;
-+    }
-+
-+    qemu_mutex_unlock(&backup_state.stat.lock);
-+
-+    return head;
-+}
-+
- ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp)
- {
-     ProxmoxSupportStatus *ret = g_malloc0(sizeof(*ret));
-     ret->pbs_library_version = g_strdup(proxmox_backup_qemu_version());
-     ret->pbs_dirty_bitmap = true;
-     ret->pbs_dirty_bitmap_savevm = true;
-+    ret->query_bitmap_info = true;
-     return ret;
- }
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index fc498b779d..7b171fe27c 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -875,6 +875,8 @@
- # @pbs-dirty-bitmap: True if dirty-bitmap-incremental backups to PBS are
- #                    supported.
- #
-+# @query-bitmap-info: True if the 'query-pbs-bitmap-info' QMP call is supported.
-+#
- # @pbs-dirty-bitmap-savevm: True if 'dirty-bitmaps' migration capability can
- #                           safely be set for savevm-async.
- #
-@@ -883,6 +885,7 @@
- ##
- { 'struct': 'ProxmoxSupportStatus',
-   'data': { 'pbs-dirty-bitmap': 'bool',
-+            'query-bitmap-info': 'bool',
-             'pbs-dirty-bitmap-savevm': 'bool',
-             'pbs-library-version': 'str' } }
-@@ -896,6 +899,59 @@
- ##
- { 'command': 'query-proxmox-support', 'returns': 'ProxmoxSupportStatus' }
-+##
-+# @PBSBitmapAction:
-+#
-+# An action taken on a dirty-bitmap when a backup job was started.
-+#
-+# @not-used: Bitmap mode was not enabled.
-+#
-+# @not-used-removed: Bitmap mode was not enabled, but a bitmap from a
-+#                    previous backup still existed and was removed.
-+#
-+# @new: A new bitmap was attached to the drive for this backup.
-+#
-+# @used: An existing bitmap will be used to only backup changed data.
-+#
-+# @invalid: A bitmap existed, but had to be cleared since it's associated
-+#           base snapshot did not match the base given for the current job or
-+#           the crypt mode has changed.
-+#
-+##
-+{ 'enum': 'PBSBitmapAction',
-+  'data': ['not-used', 'not-used-removed', 'new', 'used', 'invalid'] }
-+
-+##
-+# @PBSBitmapInfo:
-+#
-+# Contains information about dirty bitmaps used for each drive in a PBS backup.
-+#
-+# @drive: The underlying drive.
-+#
-+# @action: The action that was taken when the backup started.
-+#
-+# @size: The total size of the drive.
-+#
-+# @dirty: How much of the drive is considered dirty and will be backed up,
-+#         or 'size' if everything will be.
-+#
-+##
-+{ 'struct': 'PBSBitmapInfo',
-+  'data': { 'drive': 'str', 'action': 'PBSBitmapAction', 'size': 'int',
-+            'dirty': 'int' } }
-+
-+##
-+# @query-pbs-bitmap-info:
-+#
-+# Returns information about dirty bitmaps used on the most recently started
-+# backup. Returns nothing when the last backup was not using PBS or if no
-+# backup occured in this session.
-+#
-+# Returns: @PBSBitmapInfo
-+#
-+##
-+{ 'command': 'query-pbs-bitmap-info', 'returns': ['PBSBitmapInfo'] }
-+
- ##
- # @BlockDeviceTimedStats:
- #
diff --git a/debian/patches/pve/0033-PVE-add-query_proxmox_support-QMP-command.patch b/debian/patches/pve/0033-PVE-add-query_proxmox_support-QMP-command.patch
new file mode 100644 (file)
index 0000000..0b92aab
--- /dev/null
@@ -0,0 +1,74 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Wed, 8 Jul 2020 11:57:53 +0200
+Subject: [PATCH] PVE: add query_proxmox_support QMP command
+
+Generic interface for future use, currently used for PBS dirty-bitmap
+backup support.
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[PVE: query-proxmox-support: include library version]
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+---
+ pve-backup.c         |  9 +++++++++
+ qapi/block-core.json | 29 +++++++++++++++++++++++++++++
+ 2 files changed, 38 insertions(+)
+
+diff --git a/pve-backup.c b/pve-backup.c
+index c15abefdda..4684789813 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -1075,3 +1075,12 @@ BackupStatus *qmp_query_backup(Error **errp)
+     return info;
+ }
++
++ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp)
++{
++    ProxmoxSupportStatus *ret = g_malloc0(sizeof(*ret));
++    ret->pbs_library_version = g_strdup(proxmox_backup_qemu_version());
++    ret->pbs_dirty_bitmap = true;
++    ret->pbs_dirty_bitmap_savevm = true;
++    return ret;
++}
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index fd1ba7ccac..fc498b779d 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -867,6 +867,35 @@
+ ##
+ { 'command': 'backup-cancel' }
++##
++# @ProxmoxSupportStatus:
++#
++# Contains info about supported features added by Proxmox.
++#
++# @pbs-dirty-bitmap: True if dirty-bitmap-incremental backups to PBS are
++#                    supported.
++#
++# @pbs-dirty-bitmap-savevm: True if 'dirty-bitmaps' migration capability can
++#                           safely be set for savevm-async.
++#
++# @pbs-library-version: Running version of libproxmox-backup-qemu0 library.
++#
++##
++{ 'struct': 'ProxmoxSupportStatus',
++  'data': { 'pbs-dirty-bitmap': 'bool',
++            'pbs-dirty-bitmap-savevm': 'bool',
++            'pbs-library-version': 'str' } }
++
++##
++# @query-proxmox-support:
++#
++# Returns information about supported features added by Proxmox.
++#
++# Returns: @ProxmoxSupportStatus
++#
++##
++{ 'command': 'query-proxmox-support', 'returns': 'ProxmoxSupportStatus' }
++
+ ##
+ # @BlockDeviceTimedStats:
+ #
diff --git a/debian/patches/pve/0034-PVE-add-query-pbs-bitmap-info-QMP-call.patch b/debian/patches/pve/0034-PVE-add-query-pbs-bitmap-info-QMP-call.patch
new file mode 100644 (file)
index 0000000..a8e3458
--- /dev/null
@@ -0,0 +1,441 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Wed, 19 Aug 2020 17:02:00 +0200
+Subject: [PATCH] PVE: add query-pbs-bitmap-info QMP call
+
+Returns advanced information about dirty bitmaps used (or not used) for
+the latest PBS backup.
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ monitor/hmp-cmds.c   |  28 ++++++-----
+ pve-backup.c         | 117 ++++++++++++++++++++++++++++++++-----------
+ qapi/block-core.json |  56 +++++++++++++++++++++
+ 3 files changed, 159 insertions(+), 42 deletions(-)
+
+diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
+index cfd7a60f32..b613190a3c 100644
+--- a/monitor/hmp-cmds.c
++++ b/monitor/hmp-cmds.c
+@@ -198,6 +198,7 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
+ void hmp_info_backup(Monitor *mon, const QDict *qdict)
+ {
+     BackupStatus *info;
++    PBSBitmapInfoList *bitmap_info;
+     info = qmp_query_backup(NULL);
+@@ -228,26 +229,29 @@ void hmp_info_backup(Monitor *mon, const QDict *qdict)
+             // this should not happen normally
+             monitor_printf(mon, "Total size: %d\n", 0);
+         } else {
+-            bool incremental = false;
+             size_t total_or_dirty = info->total;
+-            if (info->has_transferred) {
+-                if (info->has_dirty && info->dirty) {
+-                     if (info->dirty < info->total) {
+-                        total_or_dirty = info->dirty;
+-                        incremental = true;
+-                    }
+-                }
++            bitmap_info = qmp_query_pbs_bitmap_info(NULL);
++
++            while (bitmap_info) {
++                monitor_printf(mon, "Drive %s:\n",
++                        bitmap_info->value->drive);
++                monitor_printf(mon, "  bitmap action: %s\n",
++                        PBSBitmapAction_str(bitmap_info->value->action));
++                monitor_printf(mon, "  size: %zd\n",
++                        bitmap_info->value->size);
++                monitor_printf(mon, "  dirty: %zd\n",
++                        bitmap_info->value->dirty);
++                bitmap_info = bitmap_info->next;
+             }
+-            int per = (info->transferred * 100)/total_or_dirty;
+-
+-            monitor_printf(mon, "Backup mode: %s\n", incremental ? "incremental" : "full");
++            qapi_free_PBSBitmapInfoList(bitmap_info);
+             int zero_per = (info->has_zero_bytes && info->zero_bytes) ?
+                 (info->zero_bytes * 100)/info->total : 0;
+             monitor_printf(mon, "Total size: %zd\n", info->total);
++            int trans_per = (info->transferred * 100)/total_or_dirty;
+             monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
+-                           info->transferred, per);
++                           info->transferred, trans_per);
+             monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
+                            info->zero_bytes, zero_per);
+diff --git a/pve-backup.c b/pve-backup.c
+index 4684789813..f90abaa50a 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -46,6 +46,7 @@ static struct PVEBackupState {
+         size_t transferred;
+         size_t reused;
+         size_t zero_bytes;
++        GList *bitmap_list;
+     } stat;
+     int64_t speed;
+     VmaWriter *vmaw;
+@@ -672,7 +673,6 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     }
+     size_t total = 0;
+-    size_t dirty = 0;
+     l = di_list;
+     while (l) {
+@@ -693,18 +693,33 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     uuid_generate(uuid);
++    qemu_mutex_lock(&backup_state.stat.lock);
++    backup_state.stat.reused = 0;
++
++    /* clear previous backup's bitmap_list */
++    if (backup_state.stat.bitmap_list) {
++        GList *bl = backup_state.stat.bitmap_list;
++        while (bl) {
++            g_free(((PBSBitmapInfo *)bl->data)->drive);
++            g_free(bl->data);
++            bl = g_list_next(bl);
++        }
++        g_list_free(backup_state.stat.bitmap_list);
++        backup_state.stat.bitmap_list = NULL;
++    }
++
+     if (format == BACKUP_FORMAT_PBS) {
+         if (!task->has_password) {
+             error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
+-            goto err;
++            goto err_mutex;
+         }
+         if (!task->has_backup_id) {
+             error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
+-            goto err;
++            goto err_mutex;
+         }
+         if (!task->has_backup_time) {
+             error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-time'");
+-            goto err;
++            goto err_mutex;
+         }
+         int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
+@@ -731,12 +746,12 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
+                       "proxmox_backup_new failed: %s", pbs_err);
+             proxmox_backup_free_error(pbs_err);
+-            goto err;
++            goto err_mutex;
+         }
+         int connect_result = proxmox_backup_co_connect(pbs, task->errp);
+         if (connect_result < 0)
+-            goto err;
++            goto err_mutex;
+         /* register all devices */
+         l = di_list;
+@@ -747,6 +762,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             di->block_size = dump_cb_block_size;
+             const char *devname = bdrv_get_device_name(di->bs);
++            PBSBitmapAction action = PBS_BITMAP_ACTION_NOT_USED;
++            size_t dirty = di->size;
+             BdrvDirtyBitmap *bitmap = bdrv_find_dirty_bitmap(di->bs, PBS_BITMAP_NAME);
+             bool expect_only_dirty = false;
+@@ -755,49 +772,59 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+                 if (bitmap == NULL) {
+                     bitmap = bdrv_create_dirty_bitmap(di->bs, dump_cb_block_size, PBS_BITMAP_NAME, task->errp);
+                     if (!bitmap) {
+-                        goto err;
++                        goto err_mutex;
+                     }
++                    action = PBS_BITMAP_ACTION_NEW;
+                 } else {
+                     expect_only_dirty = proxmox_backup_check_incremental(pbs, devname, di->size) != 0;
+                 }
+                 if (expect_only_dirty) {
+-                    dirty += bdrv_get_dirty_count(bitmap);
++                    /* track clean chunks as reused */
++                    dirty = MIN(bdrv_get_dirty_count(bitmap), di->size);
++                    backup_state.stat.reused += di->size - dirty;
++                    action = PBS_BITMAP_ACTION_USED;
+                 } else {
+                     /* mark entire bitmap as dirty to make full backup */
+                     bdrv_set_dirty_bitmap(bitmap, 0, di->size);
+-                    dirty += di->size;
++                    if (action != PBS_BITMAP_ACTION_NEW) {
++                        action = PBS_BITMAP_ACTION_INVALID;
++                    }
+                 }
+                 di->bitmap = bitmap;
+             } else {
+-                dirty += di->size;
+-
+                 /* after a full backup the old dirty bitmap is invalid anyway */
+                 if (bitmap != NULL) {
+                     bdrv_release_dirty_bitmap(bitmap);
++                    action = PBS_BITMAP_ACTION_NOT_USED_REMOVED;
+                 }
+             }
+             int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, expect_only_dirty, task->errp);
+             if (dev_id < 0) {
+-                goto err;
++                goto err_mutex;
+             }
+             if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, task->errp))) {
+-                goto err;
++                goto err_mutex;
+             }
+             di->dev_id = dev_id;
++
++            PBSBitmapInfo *info = g_malloc(sizeof(*info));
++            info->drive = g_strdup(devname);
++            info->action = action;
++            info->size = di->size;
++            info->dirty = dirty;
++            backup_state.stat.bitmap_list = g_list_append(backup_state.stat.bitmap_list, info);
+         }
+     } else if (format == BACKUP_FORMAT_VMA) {
+-        dirty = total;
+-
+         vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
+         if (!vmaw) {
+             if (local_err) {
+                 error_propagate(task->errp, local_err);
+             }
+-            goto err;
++            goto err_mutex;
+         }
+         /* register all devices for vma writer */
+@@ -807,7 +834,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             l = g_list_next(l);
+             if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, task->errp))) {
+-                goto err;
++                goto err_mutex;
+             }
+             const char *devname = bdrv_get_device_name(di->bs);
+@@ -815,16 +842,14 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             if (di->dev_id <= 0) {
+                 error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
+                           "register_stream failed");
+-                goto err;
++                goto err_mutex;
+             }
+         }
+     } else if (format == BACKUP_FORMAT_DIR) {
+-        dirty = total;
+-
+         if (mkdir(task->backup_file, 0640) != 0) {
+             error_setg_errno(task->errp, errno, "can't create directory '%s'\n",
+                              task->backup_file);
+-            goto err;
++            goto err_mutex;
+         }
+         backup_dir = task->backup_file;
+@@ -841,18 +866,18 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+                             di->size, flags, false, &local_err);
+             if (local_err) {
+                 error_propagate(task->errp, local_err);
+-                goto err;
++                goto err_mutex;
+             }
+             di->target = bdrv_open(di->targetfile, NULL, NULL, flags, &local_err);
+             if (!di->target) {
+                 error_propagate(task->errp, local_err);
+-                goto err;
++                goto err_mutex;
+             }
+         }
+     } else {
+         error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
+-        goto err;
++        goto err_mutex;
+     }
+@@ -860,7 +885,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     if (task->has_config_file) {
+         if (pvebackup_co_add_config(task->config_file, config_name, format, backup_dir,
+                                     vmaw, pbs, task->errp) != 0) {
+-            goto err;
++            goto err_mutex;
+         }
+     }
+@@ -868,12 +893,11 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     if (task->has_firewall_file) {
+         if (pvebackup_co_add_config(task->firewall_file, firewall_name, format, backup_dir,
+                                     vmaw, pbs, task->errp) != 0) {
+-            goto err;
++            goto err_mutex;
+         }
+     }
+     /* initialize global backup_state now */
+-
+-    qemu_mutex_lock(&backup_state.stat.lock);
++    /* note: 'reused' and 'bitmap_list' are initialized earlier */
+     if (backup_state.stat.error) {
+         error_free(backup_state.stat.error);
+@@ -893,10 +917,9 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     char *uuid_str = g_strdup(backup_state.stat.uuid_str);
+     backup_state.stat.total = total;
+-    backup_state.stat.dirty = dirty;
++    backup_state.stat.dirty = total - backup_state.stat.reused;
+     backup_state.stat.transferred = 0;
+     backup_state.stat.zero_bytes = 0;
+-    backup_state.stat.reused = format == BACKUP_FORMAT_PBS && dirty >= total ? 0 : total - dirty;
+     qemu_mutex_unlock(&backup_state.stat.lock);
+@@ -913,6 +936,9 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     task->result = uuid_info;
+     return;
++err_mutex:
++    qemu_mutex_unlock(&backup_state.stat.lock);
++
+ err:
+     l = di_list;
+@@ -1076,11 +1102,42 @@ BackupStatus *qmp_query_backup(Error **errp)
+     return info;
+ }
++PBSBitmapInfoList *qmp_query_pbs_bitmap_info(Error **errp)
++{
++    PBSBitmapInfoList *head = NULL, **p_next = &head;
++
++    qemu_mutex_lock(&backup_state.stat.lock);
++
++    GList *l = backup_state.stat.bitmap_list;
++    while (l) {
++        PBSBitmapInfo *info = (PBSBitmapInfo *)l->data;
++        l = g_list_next(l);
++
++        /* clone bitmap info to avoid auto free after QMP marshalling */
++        PBSBitmapInfo *info_ret = g_malloc0(sizeof(*info_ret));
++        info_ret->drive = g_strdup(info->drive);
++        info_ret->action = info->action;
++        info_ret->size = info->size;
++        info_ret->dirty = info->dirty;
++
++        PBSBitmapInfoList *info_list = g_malloc0(sizeof(*info_list));
++        info_list->value = info_ret;
++
++        *p_next = info_list;
++        p_next = &info_list->next;
++    }
++
++    qemu_mutex_unlock(&backup_state.stat.lock);
++
++    return head;
++}
++
+ ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp)
+ {
+     ProxmoxSupportStatus *ret = g_malloc0(sizeof(*ret));
+     ret->pbs_library_version = g_strdup(proxmox_backup_qemu_version());
+     ret->pbs_dirty_bitmap = true;
+     ret->pbs_dirty_bitmap_savevm = true;
++    ret->query_bitmap_info = true;
+     return ret;
+ }
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index fc498b779d..7b171fe27c 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -875,6 +875,8 @@
+ # @pbs-dirty-bitmap: True if dirty-bitmap-incremental backups to PBS are
+ #                    supported.
+ #
++# @query-bitmap-info: True if the 'query-pbs-bitmap-info' QMP call is supported.
++#
+ # @pbs-dirty-bitmap-savevm: True if 'dirty-bitmaps' migration capability can
+ #                           safely be set for savevm-async.
+ #
+@@ -883,6 +885,7 @@
+ ##
+ { 'struct': 'ProxmoxSupportStatus',
+   'data': { 'pbs-dirty-bitmap': 'bool',
++            'query-bitmap-info': 'bool',
+             'pbs-dirty-bitmap-savevm': 'bool',
+             'pbs-library-version': 'str' } }
+@@ -896,6 +899,59 @@
+ ##
+ { 'command': 'query-proxmox-support', 'returns': 'ProxmoxSupportStatus' }
++##
++# @PBSBitmapAction:
++#
++# An action taken on a dirty-bitmap when a backup job was started.
++#
++# @not-used: Bitmap mode was not enabled.
++#
++# @not-used-removed: Bitmap mode was not enabled, but a bitmap from a
++#                    previous backup still existed and was removed.
++#
++# @new: A new bitmap was attached to the drive for this backup.
++#
++# @used: An existing bitmap will be used to only backup changed data.
++#
++# @invalid: A bitmap existed, but had to be cleared since it's associated
++#           base snapshot did not match the base given for the current job or
++#           the crypt mode has changed.
++#
++##
++{ 'enum': 'PBSBitmapAction',
++  'data': ['not-used', 'not-used-removed', 'new', 'used', 'invalid'] }
++
++##
++# @PBSBitmapInfo:
++#
++# Contains information about dirty bitmaps used for each drive in a PBS backup.
++#
++# @drive: The underlying drive.
++#
++# @action: The action that was taken when the backup started.
++#
++# @size: The total size of the drive.
++#
++# @dirty: How much of the drive is considered dirty and will be backed up,
++#         or 'size' if everything will be.
++#
++##
++{ 'struct': 'PBSBitmapInfo',
++  'data': { 'drive': 'str', 'action': 'PBSBitmapAction', 'size': 'int',
++            'dirty': 'int' } }
++
++##
++# @query-pbs-bitmap-info:
++#
++# Returns information about dirty bitmaps used on the most recently started
++# backup. Returns nothing when the last backup was not using PBS or if no
++# backup occured in this session.
++#
++# Returns: @PBSBitmapInfo
++#
++##
++{ 'command': 'query-pbs-bitmap-info', 'returns': ['PBSBitmapInfo'] }
++
+ ##
+ # @BlockDeviceTimedStats:
+ #
diff --git a/debian/patches/pve/0034-PVE-redirect-stderr-to-journal-when-daemonized.patch b/debian/patches/pve/0034-PVE-redirect-stderr-to-journal-when-daemonized.patch
deleted file mode 100644 (file)
index 9085387..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Tue, 12 Jan 2021 14:12:20 +0100
-Subject: [PATCH] PVE: redirect stderr to journal when daemonized
-
-QEMU uses the logging for error messages usually, so LOG_ERR is most
-fitting.
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- meson.build | 2 ++
- os-posix.c  | 7 +++++--
- 2 files changed, 7 insertions(+), 2 deletions(-)
-
-diff --git a/meson.build b/meson.build
-index 85b3c63199..31ba7d70d6 100644
---- a/meson.build
-+++ b/meson.build
-@@ -1203,6 +1203,7 @@ keyutils = dependency('libkeyutils', required: false,
- has_gettid = cc.has_function('gettid')
- libuuid = cc.find_library('uuid', required: true)
-+libsystemd = cc.find_library('systemd', required: true)
- libproxmox_backup_qemu = cc.find_library('proxmox_backup_qemu', required: true)
- # libselinux
-@@ -2571,6 +2572,7 @@ if have_block
-   # os-posix.c contains POSIX-specific functions used by qemu-storage-daemon,
-   # os-win32.c does not
-   blockdev_ss.add(when: 'CONFIG_POSIX', if_true: files('os-posix.c'))
-+  blockdev_ss.add(when: 'CONFIG_POSIX', if_true: libsystemd)
-   softmmu_ss.add(when: 'CONFIG_WIN32', if_true: [files('os-win32.c')])
- endif
-diff --git a/os-posix.c b/os-posix.c
-index ae6c9f2a5e..36807806bf 100644
---- a/os-posix.c
-+++ b/os-posix.c
-@@ -28,6 +28,8 @@
- #include <pwd.h>
- #include <grp.h>
- #include <libgen.h>
-+#include <systemd/sd-journal.h>
-+#include <syslog.h>
- #include "qemu-common.h"
- /* Needed early for CONFIG_BSD etc. */
-@@ -291,9 +293,10 @@ void os_setup_post(void)
-         dup2(fd, 0);
-         dup2(fd, 1);
--        /* In case -D is given do not redirect stderr to /dev/null */
-+        /* In case -D is given do not redirect stderr to journal */
-         if (!qemu_logfile) {
--            dup2(fd, 2);
-+            int journal_fd = sd_journal_stream_fd("QEMU", LOG_ERR, 0);
-+            dup2(journal_fd, 2);
-         }
-         close(fd);
diff --git a/debian/patches/pve/0035-PVE-Add-sequential-job-transaction-support.patch b/debian/patches/pve/0035-PVE-Add-sequential-job-transaction-support.patch
deleted file mode 100644 (file)
index 3a56d3c..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Thu, 20 Aug 2020 14:31:59 +0200
-Subject: [PATCH] PVE: Add sequential job transaction support
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- include/qemu/job.h | 12 ++++++++++++
- job.c              | 31 +++++++++++++++++++++++++++++++
- 2 files changed, 43 insertions(+)
-
-diff --git a/include/qemu/job.h b/include/qemu/job.h
-index 6e67b6977f..60376c99ee 100644
---- a/include/qemu/job.h
-+++ b/include/qemu/job.h
-@@ -294,6 +294,18 @@ typedef enum JobCreateFlags {
-  */
- JobTxn *job_txn_new(void);
-+/**
-+ * Create a new transaction and set it to sequential mode, i.e. run all jobs
-+ * one after the other instead of at the same time.
-+ */
-+JobTxn *job_txn_new_seq(void);
-+
-+/**
-+ * Helper method to start the first job in a sequential transaction to kick it
-+ * off. Other jobs will be run after this one completes.
-+ */
-+void job_txn_start_seq(JobTxn *txn);
-+
- /**
-  * Release a reference that was previously acquired with job_txn_add_job or
-  * job_txn_new. If it's the last reference to the object, it will be freed.
-diff --git a/job.c b/job.c
-index af25dd5b98..d0d152e697 100644
---- a/job.c
-+++ b/job.c
-@@ -72,6 +72,8 @@ struct JobTxn {
-     /* Reference count */
-     int refcnt;
-+
-+    bool sequential;
- };
- /* Right now, this mutex is only needed to synchronize accesses to job->busy
-@@ -102,6 +104,25 @@ JobTxn *job_txn_new(void)
-     return txn;
- }
-+JobTxn *job_txn_new_seq(void)
-+{
-+    JobTxn *txn = job_txn_new();
-+    txn->sequential = true;
-+    return txn;
-+}
-+
-+void job_txn_start_seq(JobTxn *txn)
-+{
-+    assert(txn->sequential);
-+    assert(!txn->aborting);
-+
-+    Job *first = QLIST_FIRST(&txn->jobs);
-+    assert(first);
-+    assert(first->status == JOB_STATUS_CREATED);
-+
-+    job_start(first);
-+}
-+
- static void job_txn_ref(JobTxn *txn)
- {
-     txn->refcnt++;
-@@ -888,6 +909,9 @@ static void job_completed_txn_success(Job *job)
-      */
-     QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
-         if (!job_is_completed(other_job)) {
-+            if (txn->sequential) {
-+                job_start(other_job);
-+            }
-             return;
-         }
-         assert(other_job->ret == 0);
-@@ -1082,6 +1106,13 @@ int job_finish_sync(Job *job, void (*finish)(Job *, Error **errp), Error **errp)
-         return -EBUSY;
-     }
-+    /* in a sequential transaction jobs with status CREATED can appear at time
-+     * of cancelling, these have not begun work so job_enter won't do anything,
-+     * let's ensure they are marked as ABORTING if required */
-+    if (job->status == JOB_STATUS_CREATED && job->txn->sequential) {
-+        job_update_rc(job);
-+    }
-+
-     AIO_WAIT_WHILE(job->aio_context,
-                    (job_enter(job), !job_is_completed(job)));
diff --git a/debian/patches/pve/0035-PVE-redirect-stderr-to-journal-when-daemonized.patch b/debian/patches/pve/0035-PVE-redirect-stderr-to-journal-when-daemonized.patch
new file mode 100644 (file)
index 0000000..9085387
--- /dev/null
@@ -0,0 +1,61 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Tue, 12 Jan 2021 14:12:20 +0100
+Subject: [PATCH] PVE: redirect stderr to journal when daemonized
+
+QEMU uses the logging for error messages usually, so LOG_ERR is most
+fitting.
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ meson.build | 2 ++
+ os-posix.c  | 7 +++++--
+ 2 files changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/meson.build b/meson.build
+index 85b3c63199..31ba7d70d6 100644
+--- a/meson.build
++++ b/meson.build
+@@ -1203,6 +1203,7 @@ keyutils = dependency('libkeyutils', required: false,
+ has_gettid = cc.has_function('gettid')
+ libuuid = cc.find_library('uuid', required: true)
++libsystemd = cc.find_library('systemd', required: true)
+ libproxmox_backup_qemu = cc.find_library('proxmox_backup_qemu', required: true)
+ # libselinux
+@@ -2571,6 +2572,7 @@ if have_block
+   # os-posix.c contains POSIX-specific functions used by qemu-storage-daemon,
+   # os-win32.c does not
+   blockdev_ss.add(when: 'CONFIG_POSIX', if_true: files('os-posix.c'))
++  blockdev_ss.add(when: 'CONFIG_POSIX', if_true: libsystemd)
+   softmmu_ss.add(when: 'CONFIG_WIN32', if_true: [files('os-win32.c')])
+ endif
+diff --git a/os-posix.c b/os-posix.c
+index ae6c9f2a5e..36807806bf 100644
+--- a/os-posix.c
++++ b/os-posix.c
+@@ -28,6 +28,8 @@
+ #include <pwd.h>
+ #include <grp.h>
+ #include <libgen.h>
++#include <systemd/sd-journal.h>
++#include <syslog.h>
+ #include "qemu-common.h"
+ /* Needed early for CONFIG_BSD etc. */
+@@ -291,9 +293,10 @@ void os_setup_post(void)
+         dup2(fd, 0);
+         dup2(fd, 1);
+-        /* In case -D is given do not redirect stderr to /dev/null */
++        /* In case -D is given do not redirect stderr to journal */
+         if (!qemu_logfile) {
+-            dup2(fd, 2);
++            int journal_fd = sd_journal_stream_fd("QEMU", LOG_ERR, 0);
++            dup2(journal_fd, 2);
+         }
+         close(fd);
diff --git a/debian/patches/pve/0036-PVE-Add-sequential-job-transaction-support.patch b/debian/patches/pve/0036-PVE-Add-sequential-job-transaction-support.patch
new file mode 100644 (file)
index 0000000..3a56d3c
--- /dev/null
@@ -0,0 +1,98 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Thu, 20 Aug 2020 14:31:59 +0200
+Subject: [PATCH] PVE: Add sequential job transaction support
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ include/qemu/job.h | 12 ++++++++++++
+ job.c              | 31 +++++++++++++++++++++++++++++++
+ 2 files changed, 43 insertions(+)
+
+diff --git a/include/qemu/job.h b/include/qemu/job.h
+index 6e67b6977f..60376c99ee 100644
+--- a/include/qemu/job.h
++++ b/include/qemu/job.h
+@@ -294,6 +294,18 @@ typedef enum JobCreateFlags {
+  */
+ JobTxn *job_txn_new(void);
++/**
++ * Create a new transaction and set it to sequential mode, i.e. run all jobs
++ * one after the other instead of at the same time.
++ */
++JobTxn *job_txn_new_seq(void);
++
++/**
++ * Helper method to start the first job in a sequential transaction to kick it
++ * off. Other jobs will be run after this one completes.
++ */
++void job_txn_start_seq(JobTxn *txn);
++
+ /**
+  * Release a reference that was previously acquired with job_txn_add_job or
+  * job_txn_new. If it's the last reference to the object, it will be freed.
+diff --git a/job.c b/job.c
+index af25dd5b98..d0d152e697 100644
+--- a/job.c
++++ b/job.c
+@@ -72,6 +72,8 @@ struct JobTxn {
+     /* Reference count */
+     int refcnt;
++
++    bool sequential;
+ };
+ /* Right now, this mutex is only needed to synchronize accesses to job->busy
+@@ -102,6 +104,25 @@ JobTxn *job_txn_new(void)
+     return txn;
+ }
++JobTxn *job_txn_new_seq(void)
++{
++    JobTxn *txn = job_txn_new();
++    txn->sequential = true;
++    return txn;
++}
++
++void job_txn_start_seq(JobTxn *txn)
++{
++    assert(txn->sequential);
++    assert(!txn->aborting);
++
++    Job *first = QLIST_FIRST(&txn->jobs);
++    assert(first);
++    assert(first->status == JOB_STATUS_CREATED);
++
++    job_start(first);
++}
++
+ static void job_txn_ref(JobTxn *txn)
+ {
+     txn->refcnt++;
+@@ -888,6 +909,9 @@ static void job_completed_txn_success(Job *job)
+      */
+     QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
+         if (!job_is_completed(other_job)) {
++            if (txn->sequential) {
++                job_start(other_job);
++            }
+             return;
+         }
+         assert(other_job->ret == 0);
+@@ -1082,6 +1106,13 @@ int job_finish_sync(Job *job, void (*finish)(Job *, Error **errp), Error **errp)
+         return -EBUSY;
+     }
++    /* in a sequential transaction jobs with status CREATED can appear at time
++     * of cancelling, these have not begun work so job_enter won't do anything,
++     * let's ensure they are marked as ABORTING if required */
++    if (job->status == JOB_STATUS_CREATED && job->txn->sequential) {
++        job_update_rc(job);
++    }
++
+     AIO_WAIT_WHILE(job->aio_context,
+                    (job_enter(job), !job_is_completed(job)));
diff --git a/debian/patches/pve/0036-PVE-Backup-Use-a-transaction-to-synchronize-job-stat.patch b/debian/patches/pve/0036-PVE-Backup-Use-a-transaction-to-synchronize-job-stat.patch
deleted file mode 100644 (file)
index c7c31de..0000000
+++ /dev/null
@@ -1,296 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Thu, 20 Aug 2020 14:25:00 +0200
-Subject: [PATCH] PVE-Backup: Use a transaction to synchronize job states
-
-By using a JobTxn, we can sync dirty bitmaps only when *all* jobs were
-successful - meaning we don't need to remove them when the backup fails,
-since QEMU's BITMAP_SYNC_MODE_ON_SUCCESS will now handle that for us.
-
-To keep the rate-limiting and IO impact from before, we use a sequential
-transaction, so drives will still be backed up one after the other.
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-[add new force parameter to job_cancel_sync calls]
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
----
- pve-backup.c | 169 +++++++++++++++------------------------------------
- 1 file changed, 50 insertions(+), 119 deletions(-)
-
-diff --git a/pve-backup.c b/pve-backup.c
-index f90abaa50a..63c686463f 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -52,6 +52,7 @@ static struct PVEBackupState {
-     VmaWriter *vmaw;
-     ProxmoxBackupHandle *pbs;
-     GList *di_list;
-+    JobTxn *txn;
-     QemuMutex backup_mutex;
-     CoMutex dump_callback_mutex;
- } backup_state;
-@@ -71,32 +72,12 @@ typedef struct PVEBackupDevInfo {
-     size_t size;
-     uint64_t block_size;
-     uint8_t dev_id;
--    bool completed;
-     char targetfile[PATH_MAX];
-     BdrvDirtyBitmap *bitmap;
-     BlockDriverState *target;
-+    BlockJob *job;
- } PVEBackupDevInfo;
--static void pvebackup_run_next_job(void);
--
--static BlockJob *
--lookup_active_block_job(PVEBackupDevInfo *di)
--{
--    if (!di->completed && di->bs) {
--        for (BlockJob *job = block_job_next(NULL); job; job = block_job_next(job)) {
--            if (job->job.driver->job_type != JOB_TYPE_BACKUP) {
--                continue;
--            }
--
--            BackupBlockJob *bjob = container_of(job, BackupBlockJob, common);
--            if (bjob && bjob->source_bs == di->bs) {
--                return job;
--            }
--        }
--    }
--    return NULL;
--}
--
- static void pvebackup_propagate_error(Error *err)
- {
-     qemu_mutex_lock(&backup_state.stat.lock);
-@@ -272,18 +253,6 @@ static void coroutine_fn pvebackup_co_cleanup(void *unused)
-             if (local_err != NULL) {
-                 pvebackup_propagate_error(local_err);
-             }
--        } else {
--            // on error or cancel we cannot ensure synchronization of dirty
--            // bitmaps with backup server, so remove all and do full backup next
--            GList *l = backup_state.di_list;
--            while (l) {
--                PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
--                l = g_list_next(l);
--
--                if (di->bitmap) {
--                    bdrv_release_dirty_bitmap(di->bitmap);
--                }
--            }
-         }
-         proxmox_backup_disconnect(backup_state.pbs);
-@@ -322,8 +291,6 @@ static void pvebackup_complete_cb(void *opaque, int ret)
-     qemu_mutex_lock(&backup_state.backup_mutex);
--    di->completed = true;
--
-     if (ret < 0) {
-         Error *local_err = NULL;
-         error_setg(&local_err, "job failed with err %d - %s", ret, strerror(-ret));
-@@ -336,20 +303,17 @@ static void pvebackup_complete_cb(void *opaque, int ret)
-     block_on_coroutine_fn(pvebackup_complete_stream, di);
--    // remove self from job queue
-+    // remove self from job list
-     backup_state.di_list = g_list_remove(backup_state.di_list, di);
--    if (di->bitmap && ret < 0) {
--        // on error or cancel we cannot ensure synchronization of dirty
--        // bitmaps with backup server, so remove all and do full backup next
--        bdrv_release_dirty_bitmap(di->bitmap);
--    }
--
-     g_free(di);
--    qemu_mutex_unlock(&backup_state.backup_mutex);
-+    /* call cleanup if we're the last job */
-+    if (!g_list_first(backup_state.di_list)) {
-+        block_on_coroutine_fn(pvebackup_co_cleanup, NULL);
-+    }
--    pvebackup_run_next_job();
-+    qemu_mutex_unlock(&backup_state.backup_mutex);
- }
- static void pvebackup_cancel(void)
-@@ -371,36 +335,28 @@ static void pvebackup_cancel(void)
-         proxmox_backup_abort(backup_state.pbs, "backup canceled");
-     }
-+    /* it's enough to cancel one job in the transaction, the rest will follow
-+     * automatically */
-+    GList *bdi = g_list_first(backup_state.di_list);
-+    BlockJob *cancel_job = bdi && bdi->data ?
-+        ((PVEBackupDevInfo *)bdi->data)->job :
-+        NULL;
-+
-+    /* ref the job before releasing the mutex, just to be safe */
-+    if (cancel_job) {
-+        job_ref(&cancel_job->job);
-+    }
-+
-+    /* job_cancel_sync may enter the job, so we need to release the
-+     * backup_mutex to avoid deadlock */
-     qemu_mutex_unlock(&backup_state.backup_mutex);
--    for(;;) {
--
--        BlockJob *next_job = NULL;
--
--        qemu_mutex_lock(&backup_state.backup_mutex);
--
--        GList *l = backup_state.di_list;
--        while (l) {
--            PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
--            l = g_list_next(l);
--
--            BlockJob *job = lookup_active_block_job(di);
--            if (job != NULL) {
--                next_job = job;
--                break;
--            }
--        }
--
--        qemu_mutex_unlock(&backup_state.backup_mutex);
--
--        if (next_job) {
--            AioContext *aio_context = next_job->job.aio_context;
--            aio_context_acquire(aio_context);
--            job_cancel_sync(&next_job->job, true);
--            aio_context_release(aio_context);
--        } else {
--            break;
--        }
-+    if (cancel_job) {
-+        AioContext *aio_context = cancel_job->job.aio_context;
-+        aio_context_acquire(aio_context);
-+        job_cancel_sync(&cancel_job->job, true);
-+        job_unref(&cancel_job->job);
-+        aio_context_release(aio_context);
-     }
- }
-@@ -459,51 +415,19 @@ static int coroutine_fn pvebackup_co_add_config(
-     goto out;
- }
--bool job_should_pause(Job *job);
--
--static void pvebackup_run_next_job(void)
--{
--    assert(!qemu_in_coroutine());
--
--    qemu_mutex_lock(&backup_state.backup_mutex);
--
--    GList *l = backup_state.di_list;
--    while (l) {
--        PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
--        l = g_list_next(l);
--
--        BlockJob *job = lookup_active_block_job(di);
--
--        if (job) {
--            qemu_mutex_unlock(&backup_state.backup_mutex);
--
--            AioContext *aio_context = job->job.aio_context;
--            aio_context_acquire(aio_context);
--
--            if (job_should_pause(&job->job)) {
--                bool error_or_canceled = pvebackup_error_or_canceled();
--                if (error_or_canceled) {
--                    job_cancel_sync(&job->job, true);
--                } else {
--                    job_resume(&job->job);
--                }
--            }
--            aio_context_release(aio_context);
--            return;
--        }
--    }
--
--    block_on_coroutine_fn(pvebackup_co_cleanup, NULL); // no more jobs, run cleanup
--
--    qemu_mutex_unlock(&backup_state.backup_mutex);
--}
--
- static bool create_backup_jobs(void) {
-     assert(!qemu_in_coroutine());
-     Error *local_err = NULL;
-+    /* create job transaction to synchronize bitmap commit and cancel all
-+     * jobs in case one errors */
-+    if (backup_state.txn) {
-+        job_txn_unref(backup_state.txn);
-+    }
-+    backup_state.txn = job_txn_new_seq();
-+
-     BackupPerf perf = { .max_workers = 16 };
-     /* create and start all jobs (paused state) */
-@@ -526,7 +450,7 @@ static bool create_backup_jobs(void) {
-         BlockJob *job = backup_job_create(
-             NULL, di->bs, di->target, backup_state.speed, sync_mode, di->bitmap,
-             bitmap_mode, false, NULL, &perf, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
--            JOB_DEFAULT, pvebackup_complete_cb, di, NULL, &local_err);
-+            JOB_DEFAULT, pvebackup_complete_cb, di, backup_state.txn, &local_err);
-         aio_context_release(aio_context);
-@@ -538,7 +462,8 @@ static bool create_backup_jobs(void) {
-             pvebackup_propagate_error(create_job_err);
-             break;
-         }
--        job_start(&job->job);
-+
-+        di->job = job;
-         bdrv_unref(di->target);
-         di->target = NULL;
-@@ -556,6 +481,10 @@ static bool create_backup_jobs(void) {
-                 bdrv_unref(di->target);
-                 di->target = NULL;
-             }
-+
-+            if (di->job) {
-+                job_unref(&di->job->job);
-+            }
-         }
-     }
-@@ -946,10 +875,6 @@ err:
-         PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-         l = g_list_next(l);
--        if (di->bitmap) {
--            bdrv_release_dirty_bitmap(di->bitmap);
--        }
--
-         if (di->target) {
-             bdrv_unref(di->target);
-         }
-@@ -1038,9 +963,15 @@ UuidInfo *qmp_backup(
-     block_on_coroutine_fn(pvebackup_co_prepare, &task);
-     if (*errp == NULL) {
--        create_backup_jobs();
-+        bool errors = create_backup_jobs();
-         qemu_mutex_unlock(&backup_state.backup_mutex);
--        pvebackup_run_next_job();
-+
-+        if (!errors) {
-+            /* start the first job in the transaction
-+             * note: this might directly enter the job, so we need to do this
-+             * after unlocking the backup_mutex */
-+            job_txn_start_seq(backup_state.txn);
-+        }
-     } else {
-         qemu_mutex_unlock(&backup_state.backup_mutex);
-     }
diff --git a/debian/patches/pve/0037-PVE-Backup-Don-t-block-on-finishing-and-cleanup-crea.patch b/debian/patches/pve/0037-PVE-Backup-Don-t-block-on-finishing-and-cleanup-crea.patch
deleted file mode 100644 (file)
index e6722ba..0000000
+++ /dev/null
@@ -1,503 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Mon, 28 Sep 2020 13:40:51 +0200
-Subject: [PATCH] PVE-Backup: Don't block on finishing and cleanup
- create_backup_jobs
-
-proxmox_backup_co_finish is already async, but previously we would wait
-for the coroutine using block_on_coroutine_fn(). Avoid this by
-scheduling pvebackup_co_complete_stream (and thus pvebackup_co_cleanup)
-as a real coroutine when calling from pvebackup_complete_cb. This is ok,
-since complete_stream uses the backup_mutex internally to synchronize,
-and other streams can happily continue writing in the meantime anyway.
-
-To accomodate, backup_mutex is converted to a CoMutex. This means
-converting every user to a coroutine. This is not just useful here, but
-will come in handy once this series[0] is merged, and QMP calls can be
-yield-able coroutines too. Then we can also finally get rid of
-block_on_coroutine_fn.
-
-Cases of aio_context_acquire/release from within what is now a coroutine
-are changed to aio_co_reschedule_self, which works since a running
-coroutine always holds the aio lock for the context it is running in.
-
-job_cancel_sync is called from a BH since it can't be run from a
-coroutine (uses AIO_WAIT_WHILE internally).
-
-Same thing for create_backup_jobs, which is converted to a BH too.
-
-To communicate the finishing state, a new property is introduced to
-query-backup: 'finishing'. A new state is explicitly not used, since
-that would break compatibility with older qemu-server versions.
-
-Also fix create_backup_jobs:
-
-No more weird bool returns, just the standard "errp" format used
-everywhere else too. With this, if backup_job_create fails, the error
-message is actually returned over QMP and can be shown to the user.
-
-To facilitate correct cleanup on such an error, we call
-create_backup_jobs as a bottom half directly from pvebackup_co_prepare.
-This additionally allows us to actually hold the backup_mutex during
-operation.
-
-Also add a job_cancel_sync before job_unref, since a job must be in
-STATUS_NULL to be deleted by unref, which could trigger an assert
-before.
-
-[0] https://lists.gnu.org/archive/html/qemu-devel/2020-09/msg03515.html
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-[add new force parameter to job_cancel_sync calls]
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
----
- pve-backup.c         | 217 ++++++++++++++++++++++++++++---------------
- qapi/block-core.json |   5 +-
- 2 files changed, 144 insertions(+), 78 deletions(-)
-
-diff --git a/pve-backup.c b/pve-backup.c
-index 63c686463f..6f05796fad 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -33,7 +33,9 @@ const char *PBS_BITMAP_NAME = "pbs-incremental-dirty-bitmap";
- static struct PVEBackupState {
-     struct {
--        // Everithing accessed from qmp_backup_query command is protected using lock
-+        // Everything accessed from qmp_backup_query command is protected using
-+        // this lock. Do NOT hold this lock for long times, as it is sometimes
-+        // acquired from coroutines, and thus any wait time may block the guest.
-         QemuMutex lock;
-         Error *error;
-         time_t start_time;
-@@ -47,20 +49,22 @@ static struct PVEBackupState {
-         size_t reused;
-         size_t zero_bytes;
-         GList *bitmap_list;
-+        bool finishing;
-+        bool starting;
-     } stat;
-     int64_t speed;
-     VmaWriter *vmaw;
-     ProxmoxBackupHandle *pbs;
-     GList *di_list;
-     JobTxn *txn;
--    QemuMutex backup_mutex;
-+    CoMutex backup_mutex;
-     CoMutex dump_callback_mutex;
- } backup_state;
- static void pvebackup_init(void)
- {
-     qemu_mutex_init(&backup_state.stat.lock);
--    qemu_mutex_init(&backup_state.backup_mutex);
-+    qemu_co_mutex_init(&backup_state.backup_mutex);
-     qemu_co_mutex_init(&backup_state.dump_callback_mutex);
- }
-@@ -72,6 +76,7 @@ typedef struct PVEBackupDevInfo {
-     size_t size;
-     uint64_t block_size;
-     uint8_t dev_id;
-+    int completed_ret; // INT_MAX if not completed
-     char targetfile[PATH_MAX];
-     BdrvDirtyBitmap *bitmap;
-     BlockDriverState *target;
-@@ -227,12 +232,12 @@ pvebackup_co_dump_vma_cb(
- }
- // assumes the caller holds backup_mutex
--static void coroutine_fn pvebackup_co_cleanup(void *unused)
-+static void coroutine_fn pvebackup_co_cleanup(void)
- {
-     assert(qemu_in_coroutine());
-     qemu_mutex_lock(&backup_state.stat.lock);
--    backup_state.stat.end_time = time(NULL);
-+    backup_state.stat.finishing = true;
-     qemu_mutex_unlock(&backup_state.stat.lock);
-     if (backup_state.vmaw) {
-@@ -261,35 +266,29 @@ static void coroutine_fn pvebackup_co_cleanup(void *unused)
-     g_list_free(backup_state.di_list);
-     backup_state.di_list = NULL;
-+
-+    qemu_mutex_lock(&backup_state.stat.lock);
-+    backup_state.stat.end_time = time(NULL);
-+    backup_state.stat.finishing = false;
-+    qemu_mutex_unlock(&backup_state.stat.lock);
- }
--// assumes the caller holds backup_mutex
--static void coroutine_fn pvebackup_complete_stream(void *opaque)
-+static void coroutine_fn pvebackup_co_complete_stream(void *opaque)
- {
-     PVEBackupDevInfo *di = opaque;
-+    int ret = di->completed_ret;
--    bool error_or_canceled = pvebackup_error_or_canceled();
--
--    if (backup_state.vmaw) {
--        vma_writer_close_stream(backup_state.vmaw, di->dev_id);
-+    qemu_mutex_lock(&backup_state.stat.lock);
-+    bool starting = backup_state.stat.starting;
-+    qemu_mutex_unlock(&backup_state.stat.lock);
-+    if (starting) {
-+        /* in 'starting' state, no tasks have been run yet, meaning we can (and
-+         * must) skip all cleanup, as we don't know what has and hasn't been
-+         * initialized yet. */
-+        return;
-     }
--    if (backup_state.pbs && !error_or_canceled) {
--        Error *local_err = NULL;
--        proxmox_backup_co_close_image(backup_state.pbs, di->dev_id, &local_err);
--        if (local_err != NULL) {
--            pvebackup_propagate_error(local_err);
--        }
--    }
--}
--
--static void pvebackup_complete_cb(void *opaque, int ret)
--{
--    assert(!qemu_in_coroutine());
--
--    PVEBackupDevInfo *di = opaque;
--
--    qemu_mutex_lock(&backup_state.backup_mutex);
-+    qemu_co_mutex_lock(&backup_state.backup_mutex);
-     if (ret < 0) {
-         Error *local_err = NULL;
-@@ -301,7 +300,19 @@ static void pvebackup_complete_cb(void *opaque, int ret)
-     assert(di->target == NULL);
--    block_on_coroutine_fn(pvebackup_complete_stream, di);
-+    bool error_or_canceled = pvebackup_error_or_canceled();
-+
-+    if (backup_state.vmaw) {
-+        vma_writer_close_stream(backup_state.vmaw, di->dev_id);
-+    }
-+
-+    if (backup_state.pbs && !error_or_canceled) {
-+        Error *local_err = NULL;
-+        proxmox_backup_co_close_image(backup_state.pbs, di->dev_id, &local_err);
-+        if (local_err != NULL) {
-+            pvebackup_propagate_error(local_err);
-+        }
-+    }
-     // remove self from job list
-     backup_state.di_list = g_list_remove(backup_state.di_list, di);
-@@ -310,21 +321,49 @@ static void pvebackup_complete_cb(void *opaque, int ret)
-     /* call cleanup if we're the last job */
-     if (!g_list_first(backup_state.di_list)) {
--        block_on_coroutine_fn(pvebackup_co_cleanup, NULL);
-+        pvebackup_co_cleanup();
-     }
--    qemu_mutex_unlock(&backup_state.backup_mutex);
-+    qemu_co_mutex_unlock(&backup_state.backup_mutex);
- }
--static void pvebackup_cancel(void)
-+static void pvebackup_complete_cb(void *opaque, int ret)
- {
--    assert(!qemu_in_coroutine());
-+    PVEBackupDevInfo *di = opaque;
-+    di->completed_ret = ret;
-+    /*
-+     * Schedule stream cleanup in async coroutine. close_image and finish might
-+     * take a while, so we can't block on them here. This way it also doesn't
-+     * matter if we're already running in a coroutine or not.
-+     * Note: di is a pointer to an entry in the global backup_state struct, so
-+     * it stays valid.
-+     */
-+    Coroutine *co = qemu_coroutine_create(pvebackup_co_complete_stream, di);
-+    aio_co_enter(qemu_get_aio_context(), co);
-+}
-+
-+/*
-+ * job_cancel(_sync) does not like to be called from coroutines, so defer to
-+ * main loop processing via a bottom half.
-+ */
-+static void job_cancel_bh(void *opaque) {
-+    CoCtxData *data = (CoCtxData*)opaque;
-+    Job *job = (Job*)data->data;
-+    AioContext *job_ctx = job->aio_context;
-+    aio_context_acquire(job_ctx);
-+    job_cancel_sync(job, true);
-+    aio_context_release(job_ctx);
-+    aio_co_enter(data->ctx, data->co);
-+}
-+
-+static void coroutine_fn pvebackup_co_cancel(void *opaque)
-+{
-     Error *cancel_err = NULL;
-     error_setg(&cancel_err, "backup canceled");
-     pvebackup_propagate_error(cancel_err);
--    qemu_mutex_lock(&backup_state.backup_mutex);
-+    qemu_co_mutex_lock(&backup_state.backup_mutex);
-     if (backup_state.vmaw) {
-         /* make sure vma writer does not block anymore */
-@@ -342,27 +381,22 @@ static void pvebackup_cancel(void)
-         ((PVEBackupDevInfo *)bdi->data)->job :
-         NULL;
--    /* ref the job before releasing the mutex, just to be safe */
-     if (cancel_job) {
--        job_ref(&cancel_job->job);
-+        CoCtxData data = {
-+            .ctx = qemu_get_current_aio_context(),
-+            .co = qemu_coroutine_self(),
-+            .data = &cancel_job->job,
-+        };
-+        aio_bh_schedule_oneshot(data.ctx, job_cancel_bh, &data);
-+        qemu_coroutine_yield();
-     }
--    /* job_cancel_sync may enter the job, so we need to release the
--     * backup_mutex to avoid deadlock */
--    qemu_mutex_unlock(&backup_state.backup_mutex);
--
--    if (cancel_job) {
--        AioContext *aio_context = cancel_job->job.aio_context;
--        aio_context_acquire(aio_context);
--        job_cancel_sync(&cancel_job->job, true);
--        job_unref(&cancel_job->job);
--        aio_context_release(aio_context);
--    }
-+    qemu_co_mutex_unlock(&backup_state.backup_mutex);
- }
- void qmp_backup_cancel(Error **errp)
- {
--    pvebackup_cancel();
-+    block_on_coroutine_fn(pvebackup_co_cancel, NULL);
- }
- // assumes the caller holds backup_mutex
-@@ -415,10 +449,18 @@ static int coroutine_fn pvebackup_co_add_config(
-     goto out;
- }
--static bool create_backup_jobs(void) {
-+/*
-+ * backup_job_create can *not* be run from a coroutine (and requires an
-+ * acquired AioContext), so this can't either.
-+ * The caller is responsible that backup_mutex is held nonetheless.
-+ */
-+static void create_backup_jobs_bh(void *opaque) {
-     assert(!qemu_in_coroutine());
-+    CoCtxData *data = (CoCtxData*)opaque;
-+    Error **errp = (Error**)data->data;
-+
-     Error *local_err = NULL;
-     /* create job transaction to synchronize bitmap commit and cancel all
-@@ -454,24 +496,19 @@ static bool create_backup_jobs(void) {
-         aio_context_release(aio_context);
--        if (!job || local_err != NULL) {
--            Error *create_job_err = NULL;
--            error_setg(&create_job_err, "backup_job_create failed: %s",
-+        di->job = job;
-+
-+        if (!job || local_err) {
-+            error_setg(errp, "backup_job_create failed: %s",
-                        local_err ? error_get_pretty(local_err) : "null");
--
--            pvebackup_propagate_error(create_job_err);
-             break;
-         }
--        di->job = job;
--
-         bdrv_unref(di->target);
-         di->target = NULL;
-     }
--    bool errors = pvebackup_error_or_canceled();
--
--    if (errors) {
-+    if (*errp) {
-         l = backup_state.di_list;
-         while (l) {
-             PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-@@ -483,12 +520,17 @@ static bool create_backup_jobs(void) {
-             }
-             if (di->job) {
-+                AioContext *ctx = di->job->job.aio_context;
-+                aio_context_acquire(ctx);
-+                job_cancel_sync(&di->job->job, true);
-                 job_unref(&di->job->job);
-+                aio_context_release(ctx);
-             }
-         }
-     }
--    return errors;
-+    /* return */
-+    aio_co_enter(data->ctx, data->co);
- }
- typedef struct QmpBackupTask {
-@@ -525,11 +567,12 @@ typedef struct QmpBackupTask {
-     UuidInfo *result;
- } QmpBackupTask;
--// assumes the caller holds backup_mutex
- static void coroutine_fn pvebackup_co_prepare(void *opaque)
- {
-     assert(qemu_in_coroutine());
-+    qemu_co_mutex_lock(&backup_state.backup_mutex);
-+
-     QmpBackupTask *task = opaque;
-     task->result = NULL; // just to be sure
-@@ -550,8 +593,9 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     const char *firewall_name = "qemu-server.fw";
-     if (backup_state.di_list) {
--         error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
-+        error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
-                   "previous backup not finished");
-+        qemu_co_mutex_unlock(&backup_state.backup_mutex);
-         return;
-     }
-@@ -618,6 +662,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-         }
-         di->size = size;
-         total += size;
-+
-+        di->completed_ret = INT_MAX;
-     }
-     uuid_generate(uuid);
-@@ -849,6 +895,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     backup_state.stat.dirty = total - backup_state.stat.reused;
-     backup_state.stat.transferred = 0;
-     backup_state.stat.zero_bytes = 0;
-+    backup_state.stat.finishing = false;
-+    backup_state.stat.starting = true;
-     qemu_mutex_unlock(&backup_state.stat.lock);
-@@ -863,6 +911,33 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     uuid_info->UUID = uuid_str;
-     task->result = uuid_info;
-+
-+    /* Run create_backup_jobs_bh outside of coroutine (in BH) but keep
-+    * backup_mutex locked. This is fine, a CoMutex can be held across yield
-+    * points, and we'll release it as soon as the BH reschedules us.
-+    */
-+    CoCtxData waker = {
-+        .co = qemu_coroutine_self(),
-+        .ctx = qemu_get_current_aio_context(),
-+        .data = &local_err,
-+    };
-+    aio_bh_schedule_oneshot(waker.ctx, create_backup_jobs_bh, &waker);
-+    qemu_coroutine_yield();
-+
-+    if (local_err) {
-+        error_propagate(task->errp, local_err);
-+        goto err;
-+    }
-+
-+    qemu_co_mutex_unlock(&backup_state.backup_mutex);
-+
-+    qemu_mutex_lock(&backup_state.stat.lock);
-+    backup_state.stat.starting = false;
-+    qemu_mutex_unlock(&backup_state.stat.lock);
-+
-+    /* start the first job in the transaction */
-+    job_txn_start_seq(backup_state.txn);
-+
-     return;
- err_mutex:
-@@ -885,6 +960,7 @@ err:
-         g_free(di);
-     }
-     g_list_free(di_list);
-+    backup_state.di_list = NULL;
-     if (devs) {
-         g_strfreev(devs);
-@@ -905,6 +981,8 @@ err:
-     }
-     task->result = NULL;
-+
-+    qemu_co_mutex_unlock(&backup_state.backup_mutex);
-     return;
- }
-@@ -958,24 +1036,8 @@ UuidInfo *qmp_backup(
-         .errp = errp,
-     };
--    qemu_mutex_lock(&backup_state.backup_mutex);
--
-     block_on_coroutine_fn(pvebackup_co_prepare, &task);
--    if (*errp == NULL) {
--        bool errors = create_backup_jobs();
--        qemu_mutex_unlock(&backup_state.backup_mutex);
--
--        if (!errors) {
--            /* start the first job in the transaction
--             * note: this might directly enter the job, so we need to do this
--             * after unlocking the backup_mutex */
--            job_txn_start_seq(backup_state.txn);
--        }
--    } else {
--        qemu_mutex_unlock(&backup_state.backup_mutex);
--    }
--
-     return task.result;
- }
-@@ -1027,6 +1089,7 @@ BackupStatus *qmp_query_backup(Error **errp)
-     info->transferred = backup_state.stat.transferred;
-     info->has_reused = true;
-     info->reused = backup_state.stat.reused;
-+    info->finishing = backup_state.stat.finishing;
-     qemu_mutex_unlock(&backup_state.stat.lock);
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index 7b171fe27c..66a0e9fd6c 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -774,12 +774,15 @@
- #
- # @uuid: uuid for this backup job
- #
-+# @finishing: if status='active' and finishing=true, then the backup process is
-+#             waiting for the target to finish.
-+#
- ##
- { 'struct': 'BackupStatus',
-   'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int', '*dirty': 'int',
-            '*transferred': 'int', '*zero-bytes': 'int', '*reused': 'int',
-            '*start-time': 'int', '*end-time': 'int',
--           '*backup-file': 'str', '*uuid': 'str' } }
-+           '*backup-file': 'str', '*uuid': 'str', 'finishing': 'bool' } }
- ##
- # @BackupFormat:
diff --git a/debian/patches/pve/0037-PVE-Backup-Use-a-transaction-to-synchronize-job-stat.patch b/debian/patches/pve/0037-PVE-Backup-Use-a-transaction-to-synchronize-job-stat.patch
new file mode 100644 (file)
index 0000000..c7c31de
--- /dev/null
@@ -0,0 +1,296 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Thu, 20 Aug 2020 14:25:00 +0200
+Subject: [PATCH] PVE-Backup: Use a transaction to synchronize job states
+
+By using a JobTxn, we can sync dirty bitmaps only when *all* jobs were
+successful - meaning we don't need to remove them when the backup fails,
+since QEMU's BITMAP_SYNC_MODE_ON_SUCCESS will now handle that for us.
+
+To keep the rate-limiting and IO impact from before, we use a sequential
+transaction, so drives will still be backed up one after the other.
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[add new force parameter to job_cancel_sync calls]
+Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
+---
+ pve-backup.c | 169 +++++++++++++++------------------------------------
+ 1 file changed, 50 insertions(+), 119 deletions(-)
+
+diff --git a/pve-backup.c b/pve-backup.c
+index f90abaa50a..63c686463f 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -52,6 +52,7 @@ static struct PVEBackupState {
+     VmaWriter *vmaw;
+     ProxmoxBackupHandle *pbs;
+     GList *di_list;
++    JobTxn *txn;
+     QemuMutex backup_mutex;
+     CoMutex dump_callback_mutex;
+ } backup_state;
+@@ -71,32 +72,12 @@ typedef struct PVEBackupDevInfo {
+     size_t size;
+     uint64_t block_size;
+     uint8_t dev_id;
+-    bool completed;
+     char targetfile[PATH_MAX];
+     BdrvDirtyBitmap *bitmap;
+     BlockDriverState *target;
++    BlockJob *job;
+ } PVEBackupDevInfo;
+-static void pvebackup_run_next_job(void);
+-
+-static BlockJob *
+-lookup_active_block_job(PVEBackupDevInfo *di)
+-{
+-    if (!di->completed && di->bs) {
+-        for (BlockJob *job = block_job_next(NULL); job; job = block_job_next(job)) {
+-            if (job->job.driver->job_type != JOB_TYPE_BACKUP) {
+-                continue;
+-            }
+-
+-            BackupBlockJob *bjob = container_of(job, BackupBlockJob, common);
+-            if (bjob && bjob->source_bs == di->bs) {
+-                return job;
+-            }
+-        }
+-    }
+-    return NULL;
+-}
+-
+ static void pvebackup_propagate_error(Error *err)
+ {
+     qemu_mutex_lock(&backup_state.stat.lock);
+@@ -272,18 +253,6 @@ static void coroutine_fn pvebackup_co_cleanup(void *unused)
+             if (local_err != NULL) {
+                 pvebackup_propagate_error(local_err);
+             }
+-        } else {
+-            // on error or cancel we cannot ensure synchronization of dirty
+-            // bitmaps with backup server, so remove all and do full backup next
+-            GList *l = backup_state.di_list;
+-            while (l) {
+-                PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
+-                l = g_list_next(l);
+-
+-                if (di->bitmap) {
+-                    bdrv_release_dirty_bitmap(di->bitmap);
+-                }
+-            }
+         }
+         proxmox_backup_disconnect(backup_state.pbs);
+@@ -322,8 +291,6 @@ static void pvebackup_complete_cb(void *opaque, int ret)
+     qemu_mutex_lock(&backup_state.backup_mutex);
+-    di->completed = true;
+-
+     if (ret < 0) {
+         Error *local_err = NULL;
+         error_setg(&local_err, "job failed with err %d - %s", ret, strerror(-ret));
+@@ -336,20 +303,17 @@ static void pvebackup_complete_cb(void *opaque, int ret)
+     block_on_coroutine_fn(pvebackup_complete_stream, di);
+-    // remove self from job queue
++    // remove self from job list
+     backup_state.di_list = g_list_remove(backup_state.di_list, di);
+-    if (di->bitmap && ret < 0) {
+-        // on error or cancel we cannot ensure synchronization of dirty
+-        // bitmaps with backup server, so remove all and do full backup next
+-        bdrv_release_dirty_bitmap(di->bitmap);
+-    }
+-
+     g_free(di);
+-    qemu_mutex_unlock(&backup_state.backup_mutex);
++    /* call cleanup if we're the last job */
++    if (!g_list_first(backup_state.di_list)) {
++        block_on_coroutine_fn(pvebackup_co_cleanup, NULL);
++    }
+-    pvebackup_run_next_job();
++    qemu_mutex_unlock(&backup_state.backup_mutex);
+ }
+ static void pvebackup_cancel(void)
+@@ -371,36 +335,28 @@ static void pvebackup_cancel(void)
+         proxmox_backup_abort(backup_state.pbs, "backup canceled");
+     }
++    /* it's enough to cancel one job in the transaction, the rest will follow
++     * automatically */
++    GList *bdi = g_list_first(backup_state.di_list);
++    BlockJob *cancel_job = bdi && bdi->data ?
++        ((PVEBackupDevInfo *)bdi->data)->job :
++        NULL;
++
++    /* ref the job before releasing the mutex, just to be safe */
++    if (cancel_job) {
++        job_ref(&cancel_job->job);
++    }
++
++    /* job_cancel_sync may enter the job, so we need to release the
++     * backup_mutex to avoid deadlock */
+     qemu_mutex_unlock(&backup_state.backup_mutex);
+-    for(;;) {
+-
+-        BlockJob *next_job = NULL;
+-
+-        qemu_mutex_lock(&backup_state.backup_mutex);
+-
+-        GList *l = backup_state.di_list;
+-        while (l) {
+-            PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
+-            l = g_list_next(l);
+-
+-            BlockJob *job = lookup_active_block_job(di);
+-            if (job != NULL) {
+-                next_job = job;
+-                break;
+-            }
+-        }
+-
+-        qemu_mutex_unlock(&backup_state.backup_mutex);
+-
+-        if (next_job) {
+-            AioContext *aio_context = next_job->job.aio_context;
+-            aio_context_acquire(aio_context);
+-            job_cancel_sync(&next_job->job, true);
+-            aio_context_release(aio_context);
+-        } else {
+-            break;
+-        }
++    if (cancel_job) {
++        AioContext *aio_context = cancel_job->job.aio_context;
++        aio_context_acquire(aio_context);
++        job_cancel_sync(&cancel_job->job, true);
++        job_unref(&cancel_job->job);
++        aio_context_release(aio_context);
+     }
+ }
+@@ -459,51 +415,19 @@ static int coroutine_fn pvebackup_co_add_config(
+     goto out;
+ }
+-bool job_should_pause(Job *job);
+-
+-static void pvebackup_run_next_job(void)
+-{
+-    assert(!qemu_in_coroutine());
+-
+-    qemu_mutex_lock(&backup_state.backup_mutex);
+-
+-    GList *l = backup_state.di_list;
+-    while (l) {
+-        PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
+-        l = g_list_next(l);
+-
+-        BlockJob *job = lookup_active_block_job(di);
+-
+-        if (job) {
+-            qemu_mutex_unlock(&backup_state.backup_mutex);
+-
+-            AioContext *aio_context = job->job.aio_context;
+-            aio_context_acquire(aio_context);
+-
+-            if (job_should_pause(&job->job)) {
+-                bool error_or_canceled = pvebackup_error_or_canceled();
+-                if (error_or_canceled) {
+-                    job_cancel_sync(&job->job, true);
+-                } else {
+-                    job_resume(&job->job);
+-                }
+-            }
+-            aio_context_release(aio_context);
+-            return;
+-        }
+-    }
+-
+-    block_on_coroutine_fn(pvebackup_co_cleanup, NULL); // no more jobs, run cleanup
+-
+-    qemu_mutex_unlock(&backup_state.backup_mutex);
+-}
+-
+ static bool create_backup_jobs(void) {
+     assert(!qemu_in_coroutine());
+     Error *local_err = NULL;
++    /* create job transaction to synchronize bitmap commit and cancel all
++     * jobs in case one errors */
++    if (backup_state.txn) {
++        job_txn_unref(backup_state.txn);
++    }
++    backup_state.txn = job_txn_new_seq();
++
+     BackupPerf perf = { .max_workers = 16 };
+     /* create and start all jobs (paused state) */
+@@ -526,7 +450,7 @@ static bool create_backup_jobs(void) {
+         BlockJob *job = backup_job_create(
+             NULL, di->bs, di->target, backup_state.speed, sync_mode, di->bitmap,
+             bitmap_mode, false, NULL, &perf, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
+-            JOB_DEFAULT, pvebackup_complete_cb, di, NULL, &local_err);
++            JOB_DEFAULT, pvebackup_complete_cb, di, backup_state.txn, &local_err);
+         aio_context_release(aio_context);
+@@ -538,7 +462,8 @@ static bool create_backup_jobs(void) {
+             pvebackup_propagate_error(create_job_err);
+             break;
+         }
+-        job_start(&job->job);
++
++        di->job = job;
+         bdrv_unref(di->target);
+         di->target = NULL;
+@@ -556,6 +481,10 @@ static bool create_backup_jobs(void) {
+                 bdrv_unref(di->target);
+                 di->target = NULL;
+             }
++
++            if (di->job) {
++                job_unref(&di->job->job);
++            }
+         }
+     }
+@@ -946,10 +875,6 @@ err:
+         PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
+         l = g_list_next(l);
+-        if (di->bitmap) {
+-            bdrv_release_dirty_bitmap(di->bitmap);
+-        }
+-
+         if (di->target) {
+             bdrv_unref(di->target);
+         }
+@@ -1038,9 +963,15 @@ UuidInfo *qmp_backup(
+     block_on_coroutine_fn(pvebackup_co_prepare, &task);
+     if (*errp == NULL) {
+-        create_backup_jobs();
++        bool errors = create_backup_jobs();
+         qemu_mutex_unlock(&backup_state.backup_mutex);
+-        pvebackup_run_next_job();
++
++        if (!errors) {
++            /* start the first job in the transaction
++             * note: this might directly enter the job, so we need to do this
++             * after unlocking the backup_mutex */
++            job_txn_start_seq(backup_state.txn);
++        }
+     } else {
+         qemu_mutex_unlock(&backup_state.backup_mutex);
+     }
diff --git a/debian/patches/pve/0038-PVE-Backup-Don-t-block-on-finishing-and-cleanup-crea.patch b/debian/patches/pve/0038-PVE-Backup-Don-t-block-on-finishing-and-cleanup-crea.patch
new file mode 100644 (file)
index 0000000..e6722ba
--- /dev/null
@@ -0,0 +1,503 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Mon, 28 Sep 2020 13:40:51 +0200
+Subject: [PATCH] PVE-Backup: Don't block on finishing and cleanup
+ create_backup_jobs
+
+proxmox_backup_co_finish is already async, but previously we would wait
+for the coroutine using block_on_coroutine_fn(). Avoid this by
+scheduling pvebackup_co_complete_stream (and thus pvebackup_co_cleanup)
+as a real coroutine when calling from pvebackup_complete_cb. This is ok,
+since complete_stream uses the backup_mutex internally to synchronize,
+and other streams can happily continue writing in the meantime anyway.
+
+To accomodate, backup_mutex is converted to a CoMutex. This means
+converting every user to a coroutine. This is not just useful here, but
+will come in handy once this series[0] is merged, and QMP calls can be
+yield-able coroutines too. Then we can also finally get rid of
+block_on_coroutine_fn.
+
+Cases of aio_context_acquire/release from within what is now a coroutine
+are changed to aio_co_reschedule_self, which works since a running
+coroutine always holds the aio lock for the context it is running in.
+
+job_cancel_sync is called from a BH since it can't be run from a
+coroutine (uses AIO_WAIT_WHILE internally).
+
+Same thing for create_backup_jobs, which is converted to a BH too.
+
+To communicate the finishing state, a new property is introduced to
+query-backup: 'finishing'. A new state is explicitly not used, since
+that would break compatibility with older qemu-server versions.
+
+Also fix create_backup_jobs:
+
+No more weird bool returns, just the standard "errp" format used
+everywhere else too. With this, if backup_job_create fails, the error
+message is actually returned over QMP and can be shown to the user.
+
+To facilitate correct cleanup on such an error, we call
+create_backup_jobs as a bottom half directly from pvebackup_co_prepare.
+This additionally allows us to actually hold the backup_mutex during
+operation.
+
+Also add a job_cancel_sync before job_unref, since a job must be in
+STATUS_NULL to be deleted by unref, which could trigger an assert
+before.
+
+[0] https://lists.gnu.org/archive/html/qemu-devel/2020-09/msg03515.html
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[add new force parameter to job_cancel_sync calls]
+Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
+---
+ pve-backup.c         | 217 ++++++++++++++++++++++++++++---------------
+ qapi/block-core.json |   5 +-
+ 2 files changed, 144 insertions(+), 78 deletions(-)
+
+diff --git a/pve-backup.c b/pve-backup.c
+index 63c686463f..6f05796fad 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -33,7 +33,9 @@ const char *PBS_BITMAP_NAME = "pbs-incremental-dirty-bitmap";
+ static struct PVEBackupState {
+     struct {
+-        // Everithing accessed from qmp_backup_query command is protected using lock
++        // Everything accessed from qmp_backup_query command is protected using
++        // this lock. Do NOT hold this lock for long times, as it is sometimes
++        // acquired from coroutines, and thus any wait time may block the guest.
+         QemuMutex lock;
+         Error *error;
+         time_t start_time;
+@@ -47,20 +49,22 @@ static struct PVEBackupState {
+         size_t reused;
+         size_t zero_bytes;
+         GList *bitmap_list;
++        bool finishing;
++        bool starting;
+     } stat;
+     int64_t speed;
+     VmaWriter *vmaw;
+     ProxmoxBackupHandle *pbs;
+     GList *di_list;
+     JobTxn *txn;
+-    QemuMutex backup_mutex;
++    CoMutex backup_mutex;
+     CoMutex dump_callback_mutex;
+ } backup_state;
+ static void pvebackup_init(void)
+ {
+     qemu_mutex_init(&backup_state.stat.lock);
+-    qemu_mutex_init(&backup_state.backup_mutex);
++    qemu_co_mutex_init(&backup_state.backup_mutex);
+     qemu_co_mutex_init(&backup_state.dump_callback_mutex);
+ }
+@@ -72,6 +76,7 @@ typedef struct PVEBackupDevInfo {
+     size_t size;
+     uint64_t block_size;
+     uint8_t dev_id;
++    int completed_ret; // INT_MAX if not completed
+     char targetfile[PATH_MAX];
+     BdrvDirtyBitmap *bitmap;
+     BlockDriverState *target;
+@@ -227,12 +232,12 @@ pvebackup_co_dump_vma_cb(
+ }
+ // assumes the caller holds backup_mutex
+-static void coroutine_fn pvebackup_co_cleanup(void *unused)
++static void coroutine_fn pvebackup_co_cleanup(void)
+ {
+     assert(qemu_in_coroutine());
+     qemu_mutex_lock(&backup_state.stat.lock);
+-    backup_state.stat.end_time = time(NULL);
++    backup_state.stat.finishing = true;
+     qemu_mutex_unlock(&backup_state.stat.lock);
+     if (backup_state.vmaw) {
+@@ -261,35 +266,29 @@ static void coroutine_fn pvebackup_co_cleanup(void *unused)
+     g_list_free(backup_state.di_list);
+     backup_state.di_list = NULL;
++
++    qemu_mutex_lock(&backup_state.stat.lock);
++    backup_state.stat.end_time = time(NULL);
++    backup_state.stat.finishing = false;
++    qemu_mutex_unlock(&backup_state.stat.lock);
+ }
+-// assumes the caller holds backup_mutex
+-static void coroutine_fn pvebackup_complete_stream(void *opaque)
++static void coroutine_fn pvebackup_co_complete_stream(void *opaque)
+ {
+     PVEBackupDevInfo *di = opaque;
++    int ret = di->completed_ret;
+-    bool error_or_canceled = pvebackup_error_or_canceled();
+-
+-    if (backup_state.vmaw) {
+-        vma_writer_close_stream(backup_state.vmaw, di->dev_id);
++    qemu_mutex_lock(&backup_state.stat.lock);
++    bool starting = backup_state.stat.starting;
++    qemu_mutex_unlock(&backup_state.stat.lock);
++    if (starting) {
++        /* in 'starting' state, no tasks have been run yet, meaning we can (and
++         * must) skip all cleanup, as we don't know what has and hasn't been
++         * initialized yet. */
++        return;
+     }
+-    if (backup_state.pbs && !error_or_canceled) {
+-        Error *local_err = NULL;
+-        proxmox_backup_co_close_image(backup_state.pbs, di->dev_id, &local_err);
+-        if (local_err != NULL) {
+-            pvebackup_propagate_error(local_err);
+-        }
+-    }
+-}
+-
+-static void pvebackup_complete_cb(void *opaque, int ret)
+-{
+-    assert(!qemu_in_coroutine());
+-
+-    PVEBackupDevInfo *di = opaque;
+-
+-    qemu_mutex_lock(&backup_state.backup_mutex);
++    qemu_co_mutex_lock(&backup_state.backup_mutex);
+     if (ret < 0) {
+         Error *local_err = NULL;
+@@ -301,7 +300,19 @@ static void pvebackup_complete_cb(void *opaque, int ret)
+     assert(di->target == NULL);
+-    block_on_coroutine_fn(pvebackup_complete_stream, di);
++    bool error_or_canceled = pvebackup_error_or_canceled();
++
++    if (backup_state.vmaw) {
++        vma_writer_close_stream(backup_state.vmaw, di->dev_id);
++    }
++
++    if (backup_state.pbs && !error_or_canceled) {
++        Error *local_err = NULL;
++        proxmox_backup_co_close_image(backup_state.pbs, di->dev_id, &local_err);
++        if (local_err != NULL) {
++            pvebackup_propagate_error(local_err);
++        }
++    }
+     // remove self from job list
+     backup_state.di_list = g_list_remove(backup_state.di_list, di);
+@@ -310,21 +321,49 @@ static void pvebackup_complete_cb(void *opaque, int ret)
+     /* call cleanup if we're the last job */
+     if (!g_list_first(backup_state.di_list)) {
+-        block_on_coroutine_fn(pvebackup_co_cleanup, NULL);
++        pvebackup_co_cleanup();
+     }
+-    qemu_mutex_unlock(&backup_state.backup_mutex);
++    qemu_co_mutex_unlock(&backup_state.backup_mutex);
+ }
+-static void pvebackup_cancel(void)
++static void pvebackup_complete_cb(void *opaque, int ret)
+ {
+-    assert(!qemu_in_coroutine());
++    PVEBackupDevInfo *di = opaque;
++    di->completed_ret = ret;
++    /*
++     * Schedule stream cleanup in async coroutine. close_image and finish might
++     * take a while, so we can't block on them here. This way it also doesn't
++     * matter if we're already running in a coroutine or not.
++     * Note: di is a pointer to an entry in the global backup_state struct, so
++     * it stays valid.
++     */
++    Coroutine *co = qemu_coroutine_create(pvebackup_co_complete_stream, di);
++    aio_co_enter(qemu_get_aio_context(), co);
++}
++
++/*
++ * job_cancel(_sync) does not like to be called from coroutines, so defer to
++ * main loop processing via a bottom half.
++ */
++static void job_cancel_bh(void *opaque) {
++    CoCtxData *data = (CoCtxData*)opaque;
++    Job *job = (Job*)data->data;
++    AioContext *job_ctx = job->aio_context;
++    aio_context_acquire(job_ctx);
++    job_cancel_sync(job, true);
++    aio_context_release(job_ctx);
++    aio_co_enter(data->ctx, data->co);
++}
++
++static void coroutine_fn pvebackup_co_cancel(void *opaque)
++{
+     Error *cancel_err = NULL;
+     error_setg(&cancel_err, "backup canceled");
+     pvebackup_propagate_error(cancel_err);
+-    qemu_mutex_lock(&backup_state.backup_mutex);
++    qemu_co_mutex_lock(&backup_state.backup_mutex);
+     if (backup_state.vmaw) {
+         /* make sure vma writer does not block anymore */
+@@ -342,27 +381,22 @@ static void pvebackup_cancel(void)
+         ((PVEBackupDevInfo *)bdi->data)->job :
+         NULL;
+-    /* ref the job before releasing the mutex, just to be safe */
+     if (cancel_job) {
+-        job_ref(&cancel_job->job);
++        CoCtxData data = {
++            .ctx = qemu_get_current_aio_context(),
++            .co = qemu_coroutine_self(),
++            .data = &cancel_job->job,
++        };
++        aio_bh_schedule_oneshot(data.ctx, job_cancel_bh, &data);
++        qemu_coroutine_yield();
+     }
+-    /* job_cancel_sync may enter the job, so we need to release the
+-     * backup_mutex to avoid deadlock */
+-    qemu_mutex_unlock(&backup_state.backup_mutex);
+-
+-    if (cancel_job) {
+-        AioContext *aio_context = cancel_job->job.aio_context;
+-        aio_context_acquire(aio_context);
+-        job_cancel_sync(&cancel_job->job, true);
+-        job_unref(&cancel_job->job);
+-        aio_context_release(aio_context);
+-    }
++    qemu_co_mutex_unlock(&backup_state.backup_mutex);
+ }
+ void qmp_backup_cancel(Error **errp)
+ {
+-    pvebackup_cancel();
++    block_on_coroutine_fn(pvebackup_co_cancel, NULL);
+ }
+ // assumes the caller holds backup_mutex
+@@ -415,10 +449,18 @@ static int coroutine_fn pvebackup_co_add_config(
+     goto out;
+ }
+-static bool create_backup_jobs(void) {
++/*
++ * backup_job_create can *not* be run from a coroutine (and requires an
++ * acquired AioContext), so this can't either.
++ * The caller is responsible that backup_mutex is held nonetheless.
++ */
++static void create_backup_jobs_bh(void *opaque) {
+     assert(!qemu_in_coroutine());
++    CoCtxData *data = (CoCtxData*)opaque;
++    Error **errp = (Error**)data->data;
++
+     Error *local_err = NULL;
+     /* create job transaction to synchronize bitmap commit and cancel all
+@@ -454,24 +496,19 @@ static bool create_backup_jobs(void) {
+         aio_context_release(aio_context);
+-        if (!job || local_err != NULL) {
+-            Error *create_job_err = NULL;
+-            error_setg(&create_job_err, "backup_job_create failed: %s",
++        di->job = job;
++
++        if (!job || local_err) {
++            error_setg(errp, "backup_job_create failed: %s",
+                        local_err ? error_get_pretty(local_err) : "null");
+-
+-            pvebackup_propagate_error(create_job_err);
+             break;
+         }
+-        di->job = job;
+-
+         bdrv_unref(di->target);
+         di->target = NULL;
+     }
+-    bool errors = pvebackup_error_or_canceled();
+-
+-    if (errors) {
++    if (*errp) {
+         l = backup_state.di_list;
+         while (l) {
+             PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
+@@ -483,12 +520,17 @@ static bool create_backup_jobs(void) {
+             }
+             if (di->job) {
++                AioContext *ctx = di->job->job.aio_context;
++                aio_context_acquire(ctx);
++                job_cancel_sync(&di->job->job, true);
+                 job_unref(&di->job->job);
++                aio_context_release(ctx);
+             }
+         }
+     }
+-    return errors;
++    /* return */
++    aio_co_enter(data->ctx, data->co);
+ }
+ typedef struct QmpBackupTask {
+@@ -525,11 +567,12 @@ typedef struct QmpBackupTask {
+     UuidInfo *result;
+ } QmpBackupTask;
+-// assumes the caller holds backup_mutex
+ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+ {
+     assert(qemu_in_coroutine());
++    qemu_co_mutex_lock(&backup_state.backup_mutex);
++
+     QmpBackupTask *task = opaque;
+     task->result = NULL; // just to be sure
+@@ -550,8 +593,9 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     const char *firewall_name = "qemu-server.fw";
+     if (backup_state.di_list) {
+-         error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
++        error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
+                   "previous backup not finished");
++        qemu_co_mutex_unlock(&backup_state.backup_mutex);
+         return;
+     }
+@@ -618,6 +662,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+         }
+         di->size = size;
+         total += size;
++
++        di->completed_ret = INT_MAX;
+     }
+     uuid_generate(uuid);
+@@ -849,6 +895,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     backup_state.stat.dirty = total - backup_state.stat.reused;
+     backup_state.stat.transferred = 0;
+     backup_state.stat.zero_bytes = 0;
++    backup_state.stat.finishing = false;
++    backup_state.stat.starting = true;
+     qemu_mutex_unlock(&backup_state.stat.lock);
+@@ -863,6 +911,33 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     uuid_info->UUID = uuid_str;
+     task->result = uuid_info;
++
++    /* Run create_backup_jobs_bh outside of coroutine (in BH) but keep
++    * backup_mutex locked. This is fine, a CoMutex can be held across yield
++    * points, and we'll release it as soon as the BH reschedules us.
++    */
++    CoCtxData waker = {
++        .co = qemu_coroutine_self(),
++        .ctx = qemu_get_current_aio_context(),
++        .data = &local_err,
++    };
++    aio_bh_schedule_oneshot(waker.ctx, create_backup_jobs_bh, &waker);
++    qemu_coroutine_yield();
++
++    if (local_err) {
++        error_propagate(task->errp, local_err);
++        goto err;
++    }
++
++    qemu_co_mutex_unlock(&backup_state.backup_mutex);
++
++    qemu_mutex_lock(&backup_state.stat.lock);
++    backup_state.stat.starting = false;
++    qemu_mutex_unlock(&backup_state.stat.lock);
++
++    /* start the first job in the transaction */
++    job_txn_start_seq(backup_state.txn);
++
+     return;
+ err_mutex:
+@@ -885,6 +960,7 @@ err:
+         g_free(di);
+     }
+     g_list_free(di_list);
++    backup_state.di_list = NULL;
+     if (devs) {
+         g_strfreev(devs);
+@@ -905,6 +981,8 @@ err:
+     }
+     task->result = NULL;
++
++    qemu_co_mutex_unlock(&backup_state.backup_mutex);
+     return;
+ }
+@@ -958,24 +1036,8 @@ UuidInfo *qmp_backup(
+         .errp = errp,
+     };
+-    qemu_mutex_lock(&backup_state.backup_mutex);
+-
+     block_on_coroutine_fn(pvebackup_co_prepare, &task);
+-    if (*errp == NULL) {
+-        bool errors = create_backup_jobs();
+-        qemu_mutex_unlock(&backup_state.backup_mutex);
+-
+-        if (!errors) {
+-            /* start the first job in the transaction
+-             * note: this might directly enter the job, so we need to do this
+-             * after unlocking the backup_mutex */
+-            job_txn_start_seq(backup_state.txn);
+-        }
+-    } else {
+-        qemu_mutex_unlock(&backup_state.backup_mutex);
+-    }
+-
+     return task.result;
+ }
+@@ -1027,6 +1089,7 @@ BackupStatus *qmp_query_backup(Error **errp)
+     info->transferred = backup_state.stat.transferred;
+     info->has_reused = true;
+     info->reused = backup_state.stat.reused;
++    info->finishing = backup_state.stat.finishing;
+     qemu_mutex_unlock(&backup_state.stat.lock);
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index 7b171fe27c..66a0e9fd6c 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -774,12 +774,15 @@
+ #
+ # @uuid: uuid for this backup job
+ #
++# @finishing: if status='active' and finishing=true, then the backup process is
++#             waiting for the target to finish.
++#
+ ##
+ { 'struct': 'BackupStatus',
+   'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int', '*dirty': 'int',
+            '*transferred': 'int', '*zero-bytes': 'int', '*reused': 'int',
+            '*start-time': 'int', '*end-time': 'int',
+-           '*backup-file': 'str', '*uuid': 'str' } }
++           '*backup-file': 'str', '*uuid': 'str', 'finishing': 'bool' } }
+ ##
+ # @BackupFormat:
diff --git a/debian/patches/pve/0038-PVE-Migrate-dirty-bitmap-state-via-savevm.patch b/debian/patches/pve/0038-PVE-Migrate-dirty-bitmap-state-via-savevm.patch
deleted file mode 100644 (file)
index 812343b..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Thu, 22 Oct 2020 17:34:18 +0200
-Subject: [PATCH] PVE: Migrate dirty bitmap state via savevm
-
-QEMU provides 'savevm' registrations as a mechanism for arbitrary state
-to be migrated along with a VM. Use this to send a serialized version of
-dirty bitmap state data from proxmox-backup-qemu, and restore it on the
-target node.
-
-Also add a flag to query-proxmox-support so qemu-server can determine if
-safe migration is possible and makes sense.
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- include/migration/misc.h |   3 ++
- migration/meson.build    |   2 +
- migration/migration.c    |   1 +
- migration/pbs-state.c    | 106 +++++++++++++++++++++++++++++++++++++++
- pve-backup.c             |   1 +
- qapi/block-core.json     |   6 +++
- 6 files changed, 119 insertions(+)
- create mode 100644 migration/pbs-state.c
-
-diff --git a/include/migration/misc.h b/include/migration/misc.h
-index 465906710d..4f0aeceb6f 100644
---- a/include/migration/misc.h
-+++ b/include/migration/misc.h
-@@ -75,4 +75,7 @@ bool migration_in_bg_snapshot(void);
- /* migration/block-dirty-bitmap.c */
- void dirty_bitmap_mig_init(void);
-+/* migration/pbs-state.c */
-+void pbs_state_mig_init(void);
-+
- #endif
-diff --git a/migration/meson.build b/migration/meson.build
-index ea9aedeefc..c27dc9bd97 100644
---- a/migration/meson.build
-+++ b/migration/meson.build
-@@ -7,8 +7,10 @@ migration_files = files(
-   'qemu-file-channel.c',
-   'qemu-file.c',
-   'yank_functions.c',
-+  'pbs-state.c',
- )
- softmmu_ss.add(migration_files)
-+softmmu_ss.add(libproxmox_backup_qemu)
- softmmu_ss.add(files(
-   'block-dirty-bitmap.c',
-diff --git a/migration/migration.c b/migration/migration.c
-index abaf6f9e3d..d925fd7488 100644
---- a/migration/migration.c
-+++ b/migration/migration.c
-@@ -213,6 +213,7 @@ void migration_object_init(void)
-     blk_mig_init();
-     ram_mig_init();
-     dirty_bitmap_mig_init();
-+    pbs_state_mig_init();
- }
- void migration_cancel(const Error *error)
-diff --git a/migration/pbs-state.c b/migration/pbs-state.c
-new file mode 100644
-index 0000000000..29f2b3860d
---- /dev/null
-+++ b/migration/pbs-state.c
-@@ -0,0 +1,106 @@
-+/*
-+ * PBS (dirty-bitmap) state migration
-+ */
-+
-+#include "qemu/osdep.h"
-+#include "migration/misc.h"
-+#include "qemu-file.h"
-+#include "migration/vmstate.h"
-+#include "migration/register.h"
-+#include "proxmox-backup-qemu.h"
-+
-+typedef struct PBSState {
-+    bool active;
-+} PBSState;
-+
-+/* state is accessed via this static variable directly, 'opaque' is NULL */
-+static PBSState pbs_state;
-+
-+static void pbs_state_save_pending(QEMUFile *f, void *opaque,
-+                                      uint64_t max_size,
-+                                      uint64_t *res_precopy_only,
-+                                      uint64_t *res_compatible,
-+                                      uint64_t *res_postcopy_only)
-+{
-+    /* we send everything in save_setup, so nothing is ever pending */
-+}
-+
-+/* receive PBS state via f and deserialize, called on target */
-+static int pbs_state_load(QEMUFile *f, void *opaque, int version_id)
-+{
-+    /* safe cast, we cannot migrate to target with less bits than source */
-+    size_t buf_size = (size_t)qemu_get_be64(f);
-+
-+    uint8_t *buf = (uint8_t *)malloc(buf_size);
-+    size_t read = qemu_get_buffer(f, buf, buf_size);
-+
-+    if (read < buf_size) {
-+        fprintf(stderr, "error receiving PBS state: not enough data\n");
-+        return -EIO;
-+    }
-+
-+    proxmox_import_state(buf, buf_size);
-+
-+    free(buf);
-+    return 0;
-+}
-+
-+/* serialize PBS state and send to target via f, called on source */
-+static int pbs_state_save_setup(QEMUFile *f, void *opaque)
-+{
-+    size_t buf_size;
-+    uint8_t *buf = proxmox_export_state(&buf_size);
-+
-+    /* LV encoding */
-+    qemu_put_be64(f, buf_size);
-+    qemu_put_buffer(f, buf, buf_size);
-+
-+    proxmox_free_state_buf(buf);
-+    pbs_state.active = false;
-+    return 0;
-+}
-+
-+static bool pbs_state_is_active(void *opaque)
-+{
-+    /* we need to return active exactly once, else .save_setup is never called,
-+     * but if we'd just return true the migration doesn't make progress since
-+     * it'd be waiting for us */
-+    return pbs_state.active;
-+}
-+
-+static bool pbs_state_is_active_iterate(void *opaque)
-+{
-+    /* we don't iterate, everything is sent in save_setup */
-+    return pbs_state_is_active(opaque);
-+}
-+
-+static bool pbs_state_has_postcopy(void *opaque)
-+{
-+    /* PBS state can't change during a migration (since that's blocking any
-+     * potential backups), so we can copy everything before the VM is stopped */
-+    return false;
-+}
-+
-+static void pbs_state_save_cleanup(void *opaque)
-+{
-+    /* reset active after migration succeeds or fails */
-+    pbs_state.active = false;
-+}
-+
-+static SaveVMHandlers savevm_pbs_state_handlers = {
-+    .save_setup = pbs_state_save_setup,
-+    .has_postcopy = pbs_state_has_postcopy,
-+    .save_live_pending = pbs_state_save_pending,
-+    .is_active_iterate = pbs_state_is_active_iterate,
-+    .load_state = pbs_state_load,
-+    .is_active = pbs_state_is_active,
-+    .save_cleanup = pbs_state_save_cleanup,
-+};
-+
-+void pbs_state_mig_init(void)
-+{
-+    pbs_state.active = true;
-+    register_savevm_live("pbs-state", 0, 1,
-+                         &savevm_pbs_state_handlers,
-+                         NULL);
-+}
-diff --git a/pve-backup.c b/pve-backup.c
-index 6f05796fad..5fa3cc1352 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -1132,6 +1132,7 @@ ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp)
-     ret->pbs_library_version = g_strdup(proxmox_backup_qemu_version());
-     ret->pbs_dirty_bitmap = true;
-     ret->pbs_dirty_bitmap_savevm = true;
-+    ret->pbs_dirty_bitmap_migration = true;
-     ret->query_bitmap_info = true;
-     return ret;
- }
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index 66a0e9fd6c..f6a5fb263a 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -883,6 +883,11 @@
- # @pbs-dirty-bitmap-savevm: True if 'dirty-bitmaps' migration capability can
- #                           safely be set for savevm-async.
- #
-+# @pbs-dirty-bitmap-migration: True if safe migration of dirty-bitmaps including
-+#                              PBS state is supported. Enabling 'dirty-bitmaps'
-+#                              migration cap if this is false/unset may lead
-+#                              to crashes on migration!
-+#
- # @pbs-library-version: Running version of libproxmox-backup-qemu0 library.
- #
- ##
-@@ -890,6 +895,7 @@
-   'data': { 'pbs-dirty-bitmap': 'bool',
-             'query-bitmap-info': 'bool',
-             'pbs-dirty-bitmap-savevm': 'bool',
-+            'pbs-dirty-bitmap-migration': 'bool',
-             'pbs-library-version': 'str' } }
- ##
diff --git a/debian/patches/pve/0039-PVE-Migrate-dirty-bitmap-state-via-savevm.patch b/debian/patches/pve/0039-PVE-Migrate-dirty-bitmap-state-via-savevm.patch
new file mode 100644 (file)
index 0000000..812343b
--- /dev/null
@@ -0,0 +1,212 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Thu, 22 Oct 2020 17:34:18 +0200
+Subject: [PATCH] PVE: Migrate dirty bitmap state via savevm
+
+QEMU provides 'savevm' registrations as a mechanism for arbitrary state
+to be migrated along with a VM. Use this to send a serialized version of
+dirty bitmap state data from proxmox-backup-qemu, and restore it on the
+target node.
+
+Also add a flag to query-proxmox-support so qemu-server can determine if
+safe migration is possible and makes sense.
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ include/migration/misc.h |   3 ++
+ migration/meson.build    |   2 +
+ migration/migration.c    |   1 +
+ migration/pbs-state.c    | 106 +++++++++++++++++++++++++++++++++++++++
+ pve-backup.c             |   1 +
+ qapi/block-core.json     |   6 +++
+ 6 files changed, 119 insertions(+)
+ create mode 100644 migration/pbs-state.c
+
+diff --git a/include/migration/misc.h b/include/migration/misc.h
+index 465906710d..4f0aeceb6f 100644
+--- a/include/migration/misc.h
++++ b/include/migration/misc.h
+@@ -75,4 +75,7 @@ bool migration_in_bg_snapshot(void);
+ /* migration/block-dirty-bitmap.c */
+ void dirty_bitmap_mig_init(void);
++/* migration/pbs-state.c */
++void pbs_state_mig_init(void);
++
+ #endif
+diff --git a/migration/meson.build b/migration/meson.build
+index ea9aedeefc..c27dc9bd97 100644
+--- a/migration/meson.build
++++ b/migration/meson.build
+@@ -7,8 +7,10 @@ migration_files = files(
+   'qemu-file-channel.c',
+   'qemu-file.c',
+   'yank_functions.c',
++  'pbs-state.c',
+ )
+ softmmu_ss.add(migration_files)
++softmmu_ss.add(libproxmox_backup_qemu)
+ softmmu_ss.add(files(
+   'block-dirty-bitmap.c',
+diff --git a/migration/migration.c b/migration/migration.c
+index abaf6f9e3d..d925fd7488 100644
+--- a/migration/migration.c
++++ b/migration/migration.c
+@@ -213,6 +213,7 @@ void migration_object_init(void)
+     blk_mig_init();
+     ram_mig_init();
+     dirty_bitmap_mig_init();
++    pbs_state_mig_init();
+ }
+ void migration_cancel(const Error *error)
+diff --git a/migration/pbs-state.c b/migration/pbs-state.c
+new file mode 100644
+index 0000000000..29f2b3860d
+--- /dev/null
++++ b/migration/pbs-state.c
+@@ -0,0 +1,106 @@
++/*
++ * PBS (dirty-bitmap) state migration
++ */
++
++#include "qemu/osdep.h"
++#include "migration/misc.h"
++#include "qemu-file.h"
++#include "migration/vmstate.h"
++#include "migration/register.h"
++#include "proxmox-backup-qemu.h"
++
++typedef struct PBSState {
++    bool active;
++} PBSState;
++
++/* state is accessed via this static variable directly, 'opaque' is NULL */
++static PBSState pbs_state;
++
++static void pbs_state_save_pending(QEMUFile *f, void *opaque,
++                                      uint64_t max_size,
++                                      uint64_t *res_precopy_only,
++                                      uint64_t *res_compatible,
++                                      uint64_t *res_postcopy_only)
++{
++    /* we send everything in save_setup, so nothing is ever pending */
++}
++
++/* receive PBS state via f and deserialize, called on target */
++static int pbs_state_load(QEMUFile *f, void *opaque, int version_id)
++{
++    /* safe cast, we cannot migrate to target with less bits than source */
++    size_t buf_size = (size_t)qemu_get_be64(f);
++
++    uint8_t *buf = (uint8_t *)malloc(buf_size);
++    size_t read = qemu_get_buffer(f, buf, buf_size);
++
++    if (read < buf_size) {
++        fprintf(stderr, "error receiving PBS state: not enough data\n");
++        return -EIO;
++    }
++
++    proxmox_import_state(buf, buf_size);
++
++    free(buf);
++    return 0;
++}
++
++/* serialize PBS state and send to target via f, called on source */
++static int pbs_state_save_setup(QEMUFile *f, void *opaque)
++{
++    size_t buf_size;
++    uint8_t *buf = proxmox_export_state(&buf_size);
++
++    /* LV encoding */
++    qemu_put_be64(f, buf_size);
++    qemu_put_buffer(f, buf, buf_size);
++
++    proxmox_free_state_buf(buf);
++    pbs_state.active = false;
++    return 0;
++}
++
++static bool pbs_state_is_active(void *opaque)
++{
++    /* we need to return active exactly once, else .save_setup is never called,
++     * but if we'd just return true the migration doesn't make progress since
++     * it'd be waiting for us */
++    return pbs_state.active;
++}
++
++static bool pbs_state_is_active_iterate(void *opaque)
++{
++    /* we don't iterate, everything is sent in save_setup */
++    return pbs_state_is_active(opaque);
++}
++
++static bool pbs_state_has_postcopy(void *opaque)
++{
++    /* PBS state can't change during a migration (since that's blocking any
++     * potential backups), so we can copy everything before the VM is stopped */
++    return false;
++}
++
++static void pbs_state_save_cleanup(void *opaque)
++{
++    /* reset active after migration succeeds or fails */
++    pbs_state.active = false;
++}
++
++static SaveVMHandlers savevm_pbs_state_handlers = {
++    .save_setup = pbs_state_save_setup,
++    .has_postcopy = pbs_state_has_postcopy,
++    .save_live_pending = pbs_state_save_pending,
++    .is_active_iterate = pbs_state_is_active_iterate,
++    .load_state = pbs_state_load,
++    .is_active = pbs_state_is_active,
++    .save_cleanup = pbs_state_save_cleanup,
++};
++
++void pbs_state_mig_init(void)
++{
++    pbs_state.active = true;
++    register_savevm_live("pbs-state", 0, 1,
++                         &savevm_pbs_state_handlers,
++                         NULL);
++}
+diff --git a/pve-backup.c b/pve-backup.c
+index 6f05796fad..5fa3cc1352 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -1132,6 +1132,7 @@ ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp)
+     ret->pbs_library_version = g_strdup(proxmox_backup_qemu_version());
+     ret->pbs_dirty_bitmap = true;
+     ret->pbs_dirty_bitmap_savevm = true;
++    ret->pbs_dirty_bitmap_migration = true;
+     ret->query_bitmap_info = true;
+     return ret;
+ }
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index 66a0e9fd6c..f6a5fb263a 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -883,6 +883,11 @@
+ # @pbs-dirty-bitmap-savevm: True if 'dirty-bitmaps' migration capability can
+ #                           safely be set for savevm-async.
+ #
++# @pbs-dirty-bitmap-migration: True if safe migration of dirty-bitmaps including
++#                              PBS state is supported. Enabling 'dirty-bitmaps'
++#                              migration cap if this is false/unset may lead
++#                              to crashes on migration!
++#
+ # @pbs-library-version: Running version of libproxmox-backup-qemu0 library.
+ #
+ ##
+@@ -890,6 +895,7 @@
+   'data': { 'pbs-dirty-bitmap': 'bool',
+             'query-bitmap-info': 'bool',
+             'pbs-dirty-bitmap-savevm': 'bool',
++            'pbs-dirty-bitmap-migration': 'bool',
+             'pbs-library-version': 'str' } }
+ ##
diff --git a/debian/patches/pve/0039-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch b/debian/patches/pve/0039-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch
deleted file mode 100644 (file)
index 074daf7..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Tue, 3 Nov 2020 14:57:32 +0100
-Subject: [PATCH] migration/block-dirty-bitmap: migrate other bitmaps even if
- one fails
-
-If the checks in bdrv_dirty_bitmap_check fail, that only means that this
-one specific bitmap cannot be migrated. That is not an error condition
-for any other bitmaps on the same block device.
-
-Fixes dirty-bitmap migration with sync=bitmap, as the bitmaps used for
-that are obviously marked as "busy", which would cause none at all to be
-transferred.
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- migration/block-dirty-bitmap.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c
-index 9aba7d9c22..f4ecf9c9f9 100644
---- a/migration/block-dirty-bitmap.c
-+++ b/migration/block-dirty-bitmap.c
-@@ -538,7 +538,7 @@ static int add_bitmaps_to_list(DBMSaveState *s, BlockDriverState *bs,
-         if (bdrv_dirty_bitmap_check(bitmap, BDRV_BITMAP_DEFAULT, &local_err)) {
-             error_report_err(local_err);
--            return -1;
-+            continue;
-         }
-         if (bitmap_aliases) {
diff --git a/debian/patches/pve/0040-PVE-fall-back-to-open-iscsi-initiatorname.patch b/debian/patches/pve/0040-PVE-fall-back-to-open-iscsi-initiatorname.patch
deleted file mode 100644 (file)
index ae11626..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fabian Ebner <f.ebner@proxmox.com>
-Date: Tue, 17 Nov 2020 10:51:05 +0100
-Subject: [PATCH] PVE: fall back to open-iscsi initiatorname
-
-When no explicit option is given, try reading the initiator name from
-/etc/iscsi/initiatorname.iscsi and only use the generic fallback, i.e.
-iqn.2008-11.org.linux-kvmXXX, as a third alternative.
-
-This avoids the need to add an explicit option for vma and to explicitly set it
-for each call to qemu that deals with iSCSI disks, while still allowing to set
-the option if a different name is needed.
-
-According to RFC 3720, an initiator name is at most 223 bytes long, so the
-4 KiB buffer is big enough, even if many whitespaces are used.
-
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- block/iscsi.c | 30 ++++++++++++++++++++++++++++++
- 1 file changed, 30 insertions(+)
-
-diff --git a/block/iscsi.c b/block/iscsi.c
-index 57aa07a40d..a8902b84d5 100644
---- a/block/iscsi.c
-+++ b/block/iscsi.c
-@@ -1386,12 +1386,42 @@ static char *get_initiator_name(QemuOpts *opts)
-     const char *name;
-     char *iscsi_name;
-     UuidInfo *uuid_info;
-+    FILE *name_fh;
-     name = qemu_opt_get(opts, "initiator-name");
-     if (name) {
-         return g_strdup(name);
-     }
-+    name_fh = fopen("/etc/iscsi/initiatorname.iscsi", "r");
-+    if (name_fh) {
-+        const char *key = "InitiatorName";
-+        char buffer[4096];
-+        char *line;
-+
-+        while ((line = fgets(buffer, sizeof(buffer), name_fh))) {
-+            line = g_strstrip(line);
-+            if (!strncmp(line, key, strlen(key))) {
-+                line = strchr(line, '=');
-+                if (!line || strlen(line) == 1) {
-+                    continue;
-+                }
-+                line++;
-+                g_strstrip(line);
-+                if (!strlen(line)) {
-+                    continue;
-+                }
-+                name = line;
-+                break;
-+            }
-+        }
-+        fclose(name_fh);
-+
-+        if (name) {
-+            return g_strdup(name);
-+        }
-+    }
-+
-     uuid_info = qmp_query_uuid(NULL);
-     if (strcmp(uuid_info->UUID, UUID_NONE) == 0) {
-         name = qemu_get_vm_name();
diff --git a/debian/patches/pve/0040-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch b/debian/patches/pve/0040-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch
new file mode 100644 (file)
index 0000000..074daf7
--- /dev/null
@@ -0,0 +1,33 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Tue, 3 Nov 2020 14:57:32 +0100
+Subject: [PATCH] migration/block-dirty-bitmap: migrate other bitmaps even if
+ one fails
+
+If the checks in bdrv_dirty_bitmap_check fail, that only means that this
+one specific bitmap cannot be migrated. That is not an error condition
+for any other bitmaps on the same block device.
+
+Fixes dirty-bitmap migration with sync=bitmap, as the bitmaps used for
+that are obviously marked as "busy", which would cause none at all to be
+transferred.
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ migration/block-dirty-bitmap.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c
+index 9aba7d9c22..f4ecf9c9f9 100644
+--- a/migration/block-dirty-bitmap.c
++++ b/migration/block-dirty-bitmap.c
+@@ -538,7 +538,7 @@ static int add_bitmaps_to_list(DBMSaveState *s, BlockDriverState *bs,
+         if (bdrv_dirty_bitmap_check(bitmap, BDRV_BITMAP_DEFAULT, &local_err)) {
+             error_report_err(local_err);
+-            return -1;
++            continue;
+         }
+         if (bitmap_aliases) {
diff --git a/debian/patches/pve/0041-PVE-Use-coroutine-QMP-for-backup-cancel_backup.patch b/debian/patches/pve/0041-PVE-Use-coroutine-QMP-for-backup-cancel_backup.patch
deleted file mode 100644 (file)
index 2b97bfb..0000000
+++ /dev/null
@@ -1,598 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Tue, 26 Jan 2021 15:45:30 +0100
-Subject: [PATCH] PVE: Use coroutine QMP for backup/cancel_backup
-
-Finally turn backup QMP calls into coroutines, now that it's possible.
-This has the benefit that calls are asynchronous to the main loop, i.e.
-long running operations like connecting to a PBS server will no longer
-hang the VM.
-
-Additionally, it allows us to get rid of block_on_coroutine_fn, which
-was always a hacky workaround.
-
-While we're already spring cleaning, also remove the QmpBackupTask
-struct, since we can now put the 'prepare' function directly into
-qmp_backup and thus no longer need those giant walls of text.
-
-(Note that for our patches to work with 5.2.0 this change is actually
-required, otherwise monitor_get_fd() fails as we're not in a QMP
-coroutine, but one we start ourselves - we could of course set the
-monitor for that coroutine ourselves, but let's just fix it the right
-way instead)
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- block/monitor/block-hmp-cmds.c |   4 +-
- hmp-commands.hx                |   2 +
- proxmox-backup-client.c        |  31 -----
- pve-backup.c                   | 232 ++++++++++-----------------------
- qapi/block-core.json           |   4 +-
- 5 files changed, 77 insertions(+), 196 deletions(-)
-
-diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
-index 4481b60a5c..c9849a5b29 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1016,7 +1016,7 @@ void hmp_info_snapshots(Monitor *mon, const QDict *qdict)
-     g_free(global_snapshots);
- }
--void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
-+void coroutine_fn hmp_backup_cancel(Monitor *mon, const QDict *qdict)
- {
-     Error *error = NULL;
-@@ -1025,7 +1025,7 @@ void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
-     hmp_handle_error(mon, error);
- }
--void hmp_backup(Monitor *mon, const QDict *qdict)
-+void coroutine_fn hmp_backup(Monitor *mon, const QDict *qdict)
- {
-     Error *error = NULL;
-diff --git a/hmp-commands.hx b/hmp-commands.hx
-index d4bb00216e..4e21911fa6 100644
---- a/hmp-commands.hx
-+++ b/hmp-commands.hx
-@@ -109,6 +109,7 @@ ERST
-                   "\n\t\t\t Use -d to dump data into a directory instead"
-                   "\n\t\t\t of using VMA format.",
-         .cmd = hmp_backup,
-+        .coroutine  = true,
-     },
- SRST
-@@ -122,6 +123,7 @@ ERST
-         .params     = "",
-         .help       = "cancel the current VM backup",
-         .cmd        = hmp_backup_cancel,
-+        .coroutine  = true,
-     },
- SRST
-diff --git a/proxmox-backup-client.c b/proxmox-backup-client.c
-index 4ce7bc0b5e..0923037dec 100644
---- a/proxmox-backup-client.c
-+++ b/proxmox-backup-client.c
-@@ -5,37 +5,6 @@
- /* Proxmox Backup Server client bindings using coroutines */
--typedef struct BlockOnCoroutineWrapper {
--    AioContext *ctx;
--    CoroutineEntry *entry;
--    void *entry_arg;
--    bool finished;
--} BlockOnCoroutineWrapper;
--
--static void coroutine_fn block_on_coroutine_wrapper(void *opaque)
--{
--    BlockOnCoroutineWrapper *wrapper = opaque;
--    wrapper->entry(wrapper->entry_arg);
--    wrapper->finished = true;
--    aio_wait_kick();
--}
--
--void block_on_coroutine_fn(CoroutineEntry *entry, void *entry_arg)
--{
--    assert(!qemu_in_coroutine());
--
--    AioContext *ctx = qemu_get_current_aio_context();
--    BlockOnCoroutineWrapper wrapper = {
--        .finished = false,
--        .entry = entry,
--        .entry_arg = entry_arg,
--        .ctx = ctx,
--    };
--    Coroutine *wrapper_co = qemu_coroutine_create(block_on_coroutine_wrapper, &wrapper);
--    aio_co_enter(ctx, wrapper_co);
--    AIO_WAIT_WHILE(ctx, !wrapper.finished);
--}
--
- // This is called from another thread, so we use aio_co_schedule()
- static void proxmox_backup_schedule_wake(void *data) {
-     CoCtxData *waker = (CoCtxData *)data;
-diff --git a/pve-backup.c b/pve-backup.c
-index 5fa3cc1352..323014744c 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -357,7 +357,7 @@ static void job_cancel_bh(void *opaque) {
-     aio_co_enter(data->ctx, data->co);
- }
--static void coroutine_fn pvebackup_co_cancel(void *opaque)
-+void coroutine_fn qmp_backup_cancel(Error **errp)
- {
-     Error *cancel_err = NULL;
-     error_setg(&cancel_err, "backup canceled");
-@@ -394,11 +394,6 @@ static void coroutine_fn pvebackup_co_cancel(void *opaque)
-     qemu_co_mutex_unlock(&backup_state.backup_mutex);
- }
--void qmp_backup_cancel(Error **errp)
--{
--    block_on_coroutine_fn(pvebackup_co_cancel, NULL);
--}
--
- // assumes the caller holds backup_mutex
- static int coroutine_fn pvebackup_co_add_config(
-     const char *file,
-@@ -533,50 +528,27 @@ static void create_backup_jobs_bh(void *opaque) {
-     aio_co_enter(data->ctx, data->co);
- }
--typedef struct QmpBackupTask {
--    const char *backup_file;
--    bool has_password;
--    const char *password;
--    bool has_keyfile;
--    const char *keyfile;
--    bool has_key_password;
--    const char *key_password;
--    bool has_backup_id;
--    const char *backup_id;
--    bool has_backup_time;
--    const char *fingerprint;
--    bool has_fingerprint;
--    int64_t backup_time;
--    bool has_use_dirty_bitmap;
--    bool use_dirty_bitmap;
--    bool has_format;
--    BackupFormat format;
--    bool has_config_file;
--    const char *config_file;
--    bool has_firewall_file;
--    const char *firewall_file;
--    bool has_devlist;
--    const char *devlist;
--    bool has_compress;
--    bool compress;
--    bool has_encrypt;
--    bool encrypt;
--    bool has_speed;
--    int64_t speed;
--    Error **errp;
--    UuidInfo *result;
--} QmpBackupTask;
--
--static void coroutine_fn pvebackup_co_prepare(void *opaque)
-+UuidInfo coroutine_fn *qmp_backup(
-+    const char *backup_file,
-+    bool has_password, const char *password,
-+    bool has_keyfile, const char *keyfile,
-+    bool has_key_password, const char *key_password,
-+    bool has_fingerprint, const char *fingerprint,
-+    bool has_backup_id, const char *backup_id,
-+    bool has_backup_time, int64_t backup_time,
-+    bool has_use_dirty_bitmap, bool use_dirty_bitmap,
-+    bool has_compress, bool compress,
-+    bool has_encrypt, bool encrypt,
-+    bool has_format, BackupFormat format,
-+    bool has_config_file, const char *config_file,
-+    bool has_firewall_file, const char *firewall_file,
-+    bool has_devlist, const char *devlist,
-+    bool has_speed, int64_t speed, Error **errp)
- {
-     assert(qemu_in_coroutine());
-     qemu_co_mutex_lock(&backup_state.backup_mutex);
--    QmpBackupTask *task = opaque;
--
--    task->result = NULL; // just to be sure
--
-     BlockBackend *blk;
-     BlockDriverState *bs = NULL;
-     const char *backup_dir = NULL;
-@@ -593,17 +565,17 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     const char *firewall_name = "qemu-server.fw";
-     if (backup_state.di_list) {
--        error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
-+        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
-                   "previous backup not finished");
-         qemu_co_mutex_unlock(&backup_state.backup_mutex);
--        return;
-+        return NULL;
-     }
-     /* Todo: try to auto-detect format based on file name */
--    BackupFormat format = task->has_format ? task->format : BACKUP_FORMAT_VMA;
-+    format = has_format ? format : BACKUP_FORMAT_VMA;
--    if (task->has_devlist) {
--        devs = g_strsplit_set(task->devlist, ",;:", -1);
-+    if (has_devlist) {
-+        devs = g_strsplit_set(devlist, ",;:", -1);
-         gchar **d = devs;
-         while (d && *d) {
-@@ -611,14 +583,14 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             if (blk) {
-                 bs = blk_bs(blk);
-                 if (!bdrv_is_inserted(bs)) {
--                    error_setg(task->errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
-+                    error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
-                     goto err;
-                 }
-                 PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
-                 di->bs = bs;
-                 di_list = g_list_append(di_list, di);
-             } else {
--                error_set(task->errp, ERROR_CLASS_DEVICE_NOT_FOUND,
-+                error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
-                           "Device '%s' not found", *d);
-                 goto err;
-             }
-@@ -641,7 +613,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     }
-     if (!di_list) {
--        error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
-+        error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
-         goto err;
-     }
-@@ -651,13 +623,13 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     while (l) {
-         PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-         l = g_list_next(l);
--        if (bdrv_op_is_blocked(di->bs, BLOCK_OP_TYPE_BACKUP_SOURCE, task->errp)) {
-+        if (bdrv_op_is_blocked(di->bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) {
-             goto err;
-         }
-         ssize_t size = bdrv_getlength(di->bs);
-         if (size < 0) {
--            error_setg_errno(task->errp, -di->size, "bdrv_getlength failed");
-+            error_setg_errno(errp, -di->size, "bdrv_getlength failed");
-             goto err;
-         }
-         di->size = size;
-@@ -684,47 +656,44 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     }
-     if (format == BACKUP_FORMAT_PBS) {
--        if (!task->has_password) {
--            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
-+        if (!has_password) {
-+            error_set(errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
-             goto err_mutex;
-         }
--        if (!task->has_backup_id) {
--            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
-+        if (!has_backup_id) {
-+            error_set(errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
-             goto err_mutex;
-         }
--        if (!task->has_backup_time) {
--            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-time'");
-+        if (!has_backup_time) {
-+            error_set(errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-time'");
-             goto err_mutex;
-         }
-         int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
-         firewall_name = "fw.conf";
--        bool use_dirty_bitmap = task->has_use_dirty_bitmap && task->use_dirty_bitmap;
--
--
-         char *pbs_err = NULL;
-         pbs = proxmox_backup_new(
--            task->backup_file,
--            task->backup_id,
--            task->backup_time,
-+            backup_file,
-+            backup_id,
-+            backup_time,
-             dump_cb_block_size,
--            task->has_password ? task->password : NULL,
--            task->has_keyfile ? task->keyfile : NULL,
--            task->has_key_password ? task->key_password : NULL,
--            task->has_compress ? task->compress : true,
--            task->has_encrypt ? task->encrypt : task->has_keyfile,
--            task->has_fingerprint ? task->fingerprint : NULL,
-+            has_password ? password : NULL,
-+            has_keyfile ? keyfile : NULL,
-+            has_key_password ? key_password : NULL,
-+            has_compress ? compress : true,
-+            has_encrypt ? encrypt : has_keyfile,
-+            has_fingerprint ? fingerprint : NULL,
-              &pbs_err);
-         if (!pbs) {
--            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
-+            error_set(errp, ERROR_CLASS_GENERIC_ERROR,
-                       "proxmox_backup_new failed: %s", pbs_err);
-             proxmox_backup_free_error(pbs_err);
-             goto err_mutex;
-         }
--        int connect_result = proxmox_backup_co_connect(pbs, task->errp);
-+        int connect_result = proxmox_backup_co_connect(pbs, errp);
-         if (connect_result < 0)
-             goto err_mutex;
-@@ -743,9 +712,9 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             BdrvDirtyBitmap *bitmap = bdrv_find_dirty_bitmap(di->bs, PBS_BITMAP_NAME);
-             bool expect_only_dirty = false;
--            if (use_dirty_bitmap) {
-+            if (has_use_dirty_bitmap && use_dirty_bitmap) {
-                 if (bitmap == NULL) {
--                    bitmap = bdrv_create_dirty_bitmap(di->bs, dump_cb_block_size, PBS_BITMAP_NAME, task->errp);
-+                    bitmap = bdrv_create_dirty_bitmap(di->bs, dump_cb_block_size, PBS_BITMAP_NAME, errp);
-                     if (!bitmap) {
-                         goto err_mutex;
-                     }
-@@ -775,12 +744,12 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-                 }
-             }
--            int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, expect_only_dirty, task->errp);
-+            int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, expect_only_dirty, errp);
-             if (dev_id < 0) {
-                 goto err_mutex;
-             }
--            if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, task->errp))) {
-+            if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, errp))) {
-                 goto err_mutex;
-             }
-@@ -794,10 +763,10 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             backup_state.stat.bitmap_list = g_list_append(backup_state.stat.bitmap_list, info);
-         }
-     } else if (format == BACKUP_FORMAT_VMA) {
--        vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
-+        vmaw = vma_writer_create(backup_file, uuid, &local_err);
-         if (!vmaw) {
-             if (local_err) {
--                error_propagate(task->errp, local_err);
-+                error_propagate(errp, local_err);
-             }
-             goto err_mutex;
-         }
-@@ -808,25 +777,25 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-             l = g_list_next(l);
--            if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, task->errp))) {
-+            if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, errp))) {
-                 goto err_mutex;
-             }
-             const char *devname = bdrv_get_device_name(di->bs);
-             di->dev_id = vma_writer_register_stream(vmaw, devname, di->size);
-             if (di->dev_id <= 0) {
--                error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
-+                error_set(errp, ERROR_CLASS_GENERIC_ERROR,
-                           "register_stream failed");
-                 goto err_mutex;
-             }
-         }
-     } else if (format == BACKUP_FORMAT_DIR) {
--        if (mkdir(task->backup_file, 0640) != 0) {
--            error_setg_errno(task->errp, errno, "can't create directory '%s'\n",
--                             task->backup_file);
-+        if (mkdir(backup_file, 0640) != 0) {
-+            error_setg_errno(errp, errno, "can't create directory '%s'\n",
-+                             backup_file);
-             goto err_mutex;
-         }
--        backup_dir = task->backup_file;
-+        backup_dir = backup_file;
-         l = di_list;
-         while (l) {
-@@ -840,34 +809,34 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             bdrv_img_create(di->targetfile, "raw", NULL, NULL, NULL,
-                             di->size, flags, false, &local_err);
-             if (local_err) {
--                error_propagate(task->errp, local_err);
-+                error_propagate(errp, local_err);
-                 goto err_mutex;
-             }
-             di->target = bdrv_open(di->targetfile, NULL, NULL, flags, &local_err);
-             if (!di->target) {
--                error_propagate(task->errp, local_err);
-+                error_propagate(errp, local_err);
-                 goto err_mutex;
-             }
-         }
-     } else {
--        error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
-+        error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
-         goto err_mutex;
-     }
-     /* add configuration file to archive */
--    if (task->has_config_file) {
--        if (pvebackup_co_add_config(task->config_file, config_name, format, backup_dir,
--                                    vmaw, pbs, task->errp) != 0) {
-+    if (has_config_file) {
-+        if (pvebackup_co_add_config(config_file, config_name, format, backup_dir,
-+                                    vmaw, pbs, errp) != 0) {
-             goto err_mutex;
-         }
-     }
-     /* add firewall file to archive */
--    if (task->has_firewall_file) {
--        if (pvebackup_co_add_config(task->firewall_file, firewall_name, format, backup_dir,
--                                    vmaw, pbs, task->errp) != 0) {
-+    if (has_firewall_file) {
-+        if (pvebackup_co_add_config(firewall_file, firewall_name, format, backup_dir,
-+                                    vmaw, pbs, errp) != 0) {
-             goto err_mutex;
-         }
-     }
-@@ -885,7 +854,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     if (backup_state.stat.backup_file) {
-         g_free(backup_state.stat.backup_file);
-     }
--    backup_state.stat.backup_file = g_strdup(task->backup_file);
-+    backup_state.stat.backup_file = g_strdup(backup_file);
-     uuid_copy(backup_state.stat.uuid, uuid);
-     uuid_unparse_lower(uuid, backup_state.stat.uuid_str);
-@@ -900,7 +869,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     qemu_mutex_unlock(&backup_state.stat.lock);
--    backup_state.speed = (task->has_speed && task->speed > 0) ? task->speed : 0;
-+    backup_state.speed = (has_speed && speed > 0) ? speed : 0;
-     backup_state.vmaw = vmaw;
-     backup_state.pbs = pbs;
-@@ -910,8 +879,6 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     uuid_info = g_malloc0(sizeof(*uuid_info));
-     uuid_info->UUID = uuid_str;
--    task->result = uuid_info;
--
-     /* Run create_backup_jobs_bh outside of coroutine (in BH) but keep
-     * backup_mutex locked. This is fine, a CoMutex can be held across yield
-     * points, and we'll release it as soon as the BH reschedules us.
-@@ -925,7 +892,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     qemu_coroutine_yield();
-     if (local_err) {
--        error_propagate(task->errp, local_err);
-+        error_propagate(errp, local_err);
-         goto err;
-     }
-@@ -938,7 +905,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     /* start the first job in the transaction */
-     job_txn_start_seq(backup_state.txn);
--    return;
-+    return uuid_info;
- err_mutex:
-     qemu_mutex_unlock(&backup_state.stat.lock);
-@@ -969,7 +936,7 @@ err:
-     if (vmaw) {
-         Error *err = NULL;
-         vma_writer_close(vmaw, &err);
--        unlink(task->backup_file);
-+        unlink(backup_file);
-     }
-     if (pbs) {
-@@ -980,65 +947,8 @@ err:
-         rmdir(backup_dir);
-     }
--    task->result = NULL;
--
-     qemu_co_mutex_unlock(&backup_state.backup_mutex);
--    return;
--}
--
--UuidInfo *qmp_backup(
--    const char *backup_file,
--    bool has_password, const char *password,
--    bool has_keyfile, const char *keyfile,
--    bool has_key_password, const char *key_password,
--    bool has_fingerprint, const char *fingerprint,
--    bool has_backup_id, const char *backup_id,
--    bool has_backup_time, int64_t backup_time,
--    bool has_use_dirty_bitmap, bool use_dirty_bitmap,
--    bool has_compress, bool compress,
--    bool has_encrypt, bool encrypt,
--    bool has_format, BackupFormat format,
--    bool has_config_file, const char *config_file,
--    bool has_firewall_file, const char *firewall_file,
--    bool has_devlist, const char *devlist,
--    bool has_speed, int64_t speed, Error **errp)
--{
--    QmpBackupTask task = {
--        .backup_file = backup_file,
--        .has_password = has_password,
--        .password = password,
--        .has_keyfile = has_keyfile,
--        .keyfile = keyfile,
--        .has_key_password = has_key_password,
--        .key_password = key_password,
--        .has_fingerprint = has_fingerprint,
--        .fingerprint = fingerprint,
--        .has_backup_id = has_backup_id,
--        .backup_id = backup_id,
--        .has_backup_time = has_backup_time,
--        .backup_time = backup_time,
--        .has_use_dirty_bitmap = has_use_dirty_bitmap,
--        .use_dirty_bitmap = use_dirty_bitmap,
--        .has_compress = has_compress,
--        .compress = compress,
--        .has_encrypt = has_encrypt,
--        .encrypt = encrypt,
--        .has_format = has_format,
--        .format = format,
--        .has_config_file = has_config_file,
--        .config_file = config_file,
--        .has_firewall_file = has_firewall_file,
--        .firewall_file = firewall_file,
--        .has_devlist = has_devlist,
--        .devlist = devlist,
--        .has_speed = has_speed,
--        .speed = speed,
--        .errp = errp,
--    };
--
--    block_on_coroutine_fn(pvebackup_co_prepare, &task);
--
--    return task.result;
-+    return NULL;
- }
- BackupStatus *qmp_query_backup(Error **errp)
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index f6a5fb263a..f216035d3c 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -846,7 +846,7 @@
-                                     '*config-file': 'str',
-                                     '*firewall-file': 'str',
-                                     '*devlist': 'str', '*speed': 'int' },
--  'returns': 'UuidInfo' }
-+  'returns': 'UuidInfo', 'coroutine': true }
- ##
- # @query-backup:
-@@ -868,7 +868,7 @@
- # Notes: This command succeeds even if there is no backup process running.
- #
- ##
--{ 'command': 'backup-cancel' }
-+{ 'command': 'backup-cancel', 'coroutine': true }
- ##
- # @ProxmoxSupportStatus:
diff --git a/debian/patches/pve/0041-PVE-fall-back-to-open-iscsi-initiatorname.patch b/debian/patches/pve/0041-PVE-fall-back-to-open-iscsi-initiatorname.patch
new file mode 100644 (file)
index 0000000..ae11626
--- /dev/null
@@ -0,0 +1,69 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Fabian Ebner <f.ebner@proxmox.com>
+Date: Tue, 17 Nov 2020 10:51:05 +0100
+Subject: [PATCH] PVE: fall back to open-iscsi initiatorname
+
+When no explicit option is given, try reading the initiator name from
+/etc/iscsi/initiatorname.iscsi and only use the generic fallback, i.e.
+iqn.2008-11.org.linux-kvmXXX, as a third alternative.
+
+This avoids the need to add an explicit option for vma and to explicitly set it
+for each call to qemu that deals with iSCSI disks, while still allowing to set
+the option if a different name is needed.
+
+According to RFC 3720, an initiator name is at most 223 bytes long, so the
+4 KiB buffer is big enough, even if many whitespaces are used.
+
+Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ block/iscsi.c | 30 ++++++++++++++++++++++++++++++
+ 1 file changed, 30 insertions(+)
+
+diff --git a/block/iscsi.c b/block/iscsi.c
+index 57aa07a40d..a8902b84d5 100644
+--- a/block/iscsi.c
++++ b/block/iscsi.c
+@@ -1386,12 +1386,42 @@ static char *get_initiator_name(QemuOpts *opts)
+     const char *name;
+     char *iscsi_name;
+     UuidInfo *uuid_info;
++    FILE *name_fh;
+     name = qemu_opt_get(opts, "initiator-name");
+     if (name) {
+         return g_strdup(name);
+     }
++    name_fh = fopen("/etc/iscsi/initiatorname.iscsi", "r");
++    if (name_fh) {
++        const char *key = "InitiatorName";
++        char buffer[4096];
++        char *line;
++
++        while ((line = fgets(buffer, sizeof(buffer), name_fh))) {
++            line = g_strstrip(line);
++            if (!strncmp(line, key, strlen(key))) {
++                line = strchr(line, '=');
++                if (!line || strlen(line) == 1) {
++                    continue;
++                }
++                line++;
++                g_strstrip(line);
++                if (!strlen(line)) {
++                    continue;
++                }
++                name = line;
++                break;
++            }
++        }
++        fclose(name_fh);
++
++        if (name) {
++            return g_strdup(name);
++        }
++    }
++
+     uuid_info = qmp_query_uuid(NULL);
+     if (strcmp(uuid_info->UUID, UUID_NONE) == 0) {
+         name = qemu_get_vm_name();
diff --git a/debian/patches/pve/0042-PBS-add-master-key-support.patch b/debian/patches/pve/0042-PBS-add-master-key-support.patch
deleted file mode 100644 (file)
index f14d4d9..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Wed, 10 Feb 2021 11:07:06 +0100
-Subject: [PATCH] PBS: add master key support
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-this requires a new enough libproxmox-backup-qemu0, and allows querying
-from the PVE side to avoid QMP calls with unsupported parameters.
-
-Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- block/monitor/block-hmp-cmds.c | 1 +
- pve-backup.c                   | 3 +++
- qapi/block-core.json           | 7 +++++++
- 3 files changed, 11 insertions(+)
-
-diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
-index c9849a5b29..52ddbf95ad 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1039,6 +1039,7 @@ void coroutine_fn hmp_backup(Monitor *mon, const QDict *qdict)
-         false, NULL, // PBS password
-         false, NULL, // PBS keyfile
-         false, NULL, // PBS key_password
-+        false, NULL, // PBS master_keyfile
-         false, NULL, // PBS fingerprint
-         false, NULL, // PBS backup-id
-         false, 0, // PBS backup-time
-diff --git a/pve-backup.c b/pve-backup.c
-index 323014744c..9f6c04a512 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -533,6 +533,7 @@ UuidInfo coroutine_fn *qmp_backup(
-     bool has_password, const char *password,
-     bool has_keyfile, const char *keyfile,
-     bool has_key_password, const char *key_password,
-+    bool has_master_keyfile, const char *master_keyfile,
-     bool has_fingerprint, const char *fingerprint,
-     bool has_backup_id, const char *backup_id,
-     bool has_backup_time, int64_t backup_time,
-@@ -681,6 +682,7 @@ UuidInfo coroutine_fn *qmp_backup(
-             has_password ? password : NULL,
-             has_keyfile ? keyfile : NULL,
-             has_key_password ? key_password : NULL,
-+            has_master_keyfile ? master_keyfile : NULL,
-             has_compress ? compress : true,
-             has_encrypt ? encrypt : has_keyfile,
-             has_fingerprint ? fingerprint : NULL,
-@@ -1044,5 +1046,6 @@ ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp)
-     ret->pbs_dirty_bitmap_savevm = true;
-     ret->pbs_dirty_bitmap_migration = true;
-     ret->query_bitmap_info = true;
-+    ret->pbs_masterkey = true;
-     return ret;
- }
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index f216035d3c..c5023710f5 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -817,6 +817,8 @@
- #
- # @key-password: password for keyfile (optional for format 'pbs')
- #
-+# @master-keyfile: PEM-formatted master public keyfile (optional for format 'pbs')
-+#
- # @fingerprint: server cert fingerprint (optional for format 'pbs')
- #
- # @backup-id: backup ID (required for format 'pbs')
-@@ -836,6 +838,7 @@
-                                     '*password': 'str',
-                                     '*keyfile': 'str',
-                                     '*key-password': 'str',
-+                                    '*master-keyfile': 'str',
-                                     '*fingerprint': 'str',
-                                     '*backup-id': 'str',
-                                     '*backup-time': 'int',
-@@ -888,6 +891,9 @@
- #                              migration cap if this is false/unset may lead
- #                              to crashes on migration!
- #
-+# @pbs-masterkey: True if the QMP backup call supports the 'master_keyfile'
-+#                 parameter.
-+#
- # @pbs-library-version: Running version of libproxmox-backup-qemu0 library.
- #
- ##
-@@ -896,6 +902,7 @@
-             'query-bitmap-info': 'bool',
-             'pbs-dirty-bitmap-savevm': 'bool',
-             'pbs-dirty-bitmap-migration': 'bool',
-+            'pbs-masterkey': 'bool',
-             'pbs-library-version': 'str' } }
- ##
diff --git a/debian/patches/pve/0042-PVE-Use-coroutine-QMP-for-backup-cancel_backup.patch b/debian/patches/pve/0042-PVE-Use-coroutine-QMP-for-backup-cancel_backup.patch
new file mode 100644 (file)
index 0000000..2b97bfb
--- /dev/null
@@ -0,0 +1,598 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Tue, 26 Jan 2021 15:45:30 +0100
+Subject: [PATCH] PVE: Use coroutine QMP for backup/cancel_backup
+
+Finally turn backup QMP calls into coroutines, now that it's possible.
+This has the benefit that calls are asynchronous to the main loop, i.e.
+long running operations like connecting to a PBS server will no longer
+hang the VM.
+
+Additionally, it allows us to get rid of block_on_coroutine_fn, which
+was always a hacky workaround.
+
+While we're already spring cleaning, also remove the QmpBackupTask
+struct, since we can now put the 'prepare' function directly into
+qmp_backup and thus no longer need those giant walls of text.
+
+(Note that for our patches to work with 5.2.0 this change is actually
+required, otherwise monitor_get_fd() fails as we're not in a QMP
+coroutine, but one we start ourselves - we could of course set the
+monitor for that coroutine ourselves, but let's just fix it the right
+way instead)
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ block/monitor/block-hmp-cmds.c |   4 +-
+ hmp-commands.hx                |   2 +
+ proxmox-backup-client.c        |  31 -----
+ pve-backup.c                   | 232 ++++++++++-----------------------
+ qapi/block-core.json           |   4 +-
+ 5 files changed, 77 insertions(+), 196 deletions(-)
+
+diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
+index 4481b60a5c..c9849a5b29 100644
+--- a/block/monitor/block-hmp-cmds.c
++++ b/block/monitor/block-hmp-cmds.c
+@@ -1016,7 +1016,7 @@ void hmp_info_snapshots(Monitor *mon, const QDict *qdict)
+     g_free(global_snapshots);
+ }
+-void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
++void coroutine_fn hmp_backup_cancel(Monitor *mon, const QDict *qdict)
+ {
+     Error *error = NULL;
+@@ -1025,7 +1025,7 @@ void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
+     hmp_handle_error(mon, error);
+ }
+-void hmp_backup(Monitor *mon, const QDict *qdict)
++void coroutine_fn hmp_backup(Monitor *mon, const QDict *qdict)
+ {
+     Error *error = NULL;
+diff --git a/hmp-commands.hx b/hmp-commands.hx
+index d4bb00216e..4e21911fa6 100644
+--- a/hmp-commands.hx
++++ b/hmp-commands.hx
+@@ -109,6 +109,7 @@ ERST
+                   "\n\t\t\t Use -d to dump data into a directory instead"
+                   "\n\t\t\t of using VMA format.",
+         .cmd = hmp_backup,
++        .coroutine  = true,
+     },
+ SRST
+@@ -122,6 +123,7 @@ ERST
+         .params     = "",
+         .help       = "cancel the current VM backup",
+         .cmd        = hmp_backup_cancel,
++        .coroutine  = true,
+     },
+ SRST
+diff --git a/proxmox-backup-client.c b/proxmox-backup-client.c
+index 4ce7bc0b5e..0923037dec 100644
+--- a/proxmox-backup-client.c
++++ b/proxmox-backup-client.c
+@@ -5,37 +5,6 @@
+ /* Proxmox Backup Server client bindings using coroutines */
+-typedef struct BlockOnCoroutineWrapper {
+-    AioContext *ctx;
+-    CoroutineEntry *entry;
+-    void *entry_arg;
+-    bool finished;
+-} BlockOnCoroutineWrapper;
+-
+-static void coroutine_fn block_on_coroutine_wrapper(void *opaque)
+-{
+-    BlockOnCoroutineWrapper *wrapper = opaque;
+-    wrapper->entry(wrapper->entry_arg);
+-    wrapper->finished = true;
+-    aio_wait_kick();
+-}
+-
+-void block_on_coroutine_fn(CoroutineEntry *entry, void *entry_arg)
+-{
+-    assert(!qemu_in_coroutine());
+-
+-    AioContext *ctx = qemu_get_current_aio_context();
+-    BlockOnCoroutineWrapper wrapper = {
+-        .finished = false,
+-        .entry = entry,
+-        .entry_arg = entry_arg,
+-        .ctx = ctx,
+-    };
+-    Coroutine *wrapper_co = qemu_coroutine_create(block_on_coroutine_wrapper, &wrapper);
+-    aio_co_enter(ctx, wrapper_co);
+-    AIO_WAIT_WHILE(ctx, !wrapper.finished);
+-}
+-
+ // This is called from another thread, so we use aio_co_schedule()
+ static void proxmox_backup_schedule_wake(void *data) {
+     CoCtxData *waker = (CoCtxData *)data;
+diff --git a/pve-backup.c b/pve-backup.c
+index 5fa3cc1352..323014744c 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -357,7 +357,7 @@ static void job_cancel_bh(void *opaque) {
+     aio_co_enter(data->ctx, data->co);
+ }
+-static void coroutine_fn pvebackup_co_cancel(void *opaque)
++void coroutine_fn qmp_backup_cancel(Error **errp)
+ {
+     Error *cancel_err = NULL;
+     error_setg(&cancel_err, "backup canceled");
+@@ -394,11 +394,6 @@ static void coroutine_fn pvebackup_co_cancel(void *opaque)
+     qemu_co_mutex_unlock(&backup_state.backup_mutex);
+ }
+-void qmp_backup_cancel(Error **errp)
+-{
+-    block_on_coroutine_fn(pvebackup_co_cancel, NULL);
+-}
+-
+ // assumes the caller holds backup_mutex
+ static int coroutine_fn pvebackup_co_add_config(
+     const char *file,
+@@ -533,50 +528,27 @@ static void create_backup_jobs_bh(void *opaque) {
+     aio_co_enter(data->ctx, data->co);
+ }
+-typedef struct QmpBackupTask {
+-    const char *backup_file;
+-    bool has_password;
+-    const char *password;
+-    bool has_keyfile;
+-    const char *keyfile;
+-    bool has_key_password;
+-    const char *key_password;
+-    bool has_backup_id;
+-    const char *backup_id;
+-    bool has_backup_time;
+-    const char *fingerprint;
+-    bool has_fingerprint;
+-    int64_t backup_time;
+-    bool has_use_dirty_bitmap;
+-    bool use_dirty_bitmap;
+-    bool has_format;
+-    BackupFormat format;
+-    bool has_config_file;
+-    const char *config_file;
+-    bool has_firewall_file;
+-    const char *firewall_file;
+-    bool has_devlist;
+-    const char *devlist;
+-    bool has_compress;
+-    bool compress;
+-    bool has_encrypt;
+-    bool encrypt;
+-    bool has_speed;
+-    int64_t speed;
+-    Error **errp;
+-    UuidInfo *result;
+-} QmpBackupTask;
+-
+-static void coroutine_fn pvebackup_co_prepare(void *opaque)
++UuidInfo coroutine_fn *qmp_backup(
++    const char *backup_file,
++    bool has_password, const char *password,
++    bool has_keyfile, const char *keyfile,
++    bool has_key_password, const char *key_password,
++    bool has_fingerprint, const char *fingerprint,
++    bool has_backup_id, const char *backup_id,
++    bool has_backup_time, int64_t backup_time,
++    bool has_use_dirty_bitmap, bool use_dirty_bitmap,
++    bool has_compress, bool compress,
++    bool has_encrypt, bool encrypt,
++    bool has_format, BackupFormat format,
++    bool has_config_file, const char *config_file,
++    bool has_firewall_file, const char *firewall_file,
++    bool has_devlist, const char *devlist,
++    bool has_speed, int64_t speed, Error **errp)
+ {
+     assert(qemu_in_coroutine());
+     qemu_co_mutex_lock(&backup_state.backup_mutex);
+-    QmpBackupTask *task = opaque;
+-
+-    task->result = NULL; // just to be sure
+-
+     BlockBackend *blk;
+     BlockDriverState *bs = NULL;
+     const char *backup_dir = NULL;
+@@ -593,17 +565,17 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     const char *firewall_name = "qemu-server.fw";
+     if (backup_state.di_list) {
+-        error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
++        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+                   "previous backup not finished");
+         qemu_co_mutex_unlock(&backup_state.backup_mutex);
+-        return;
++        return NULL;
+     }
+     /* Todo: try to auto-detect format based on file name */
+-    BackupFormat format = task->has_format ? task->format : BACKUP_FORMAT_VMA;
++    format = has_format ? format : BACKUP_FORMAT_VMA;
+-    if (task->has_devlist) {
+-        devs = g_strsplit_set(task->devlist, ",;:", -1);
++    if (has_devlist) {
++        devs = g_strsplit_set(devlist, ",;:", -1);
+         gchar **d = devs;
+         while (d && *d) {
+@@ -611,14 +583,14 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             if (blk) {
+                 bs = blk_bs(blk);
+                 if (!bdrv_is_inserted(bs)) {
+-                    error_setg(task->errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
++                    error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
+                     goto err;
+                 }
+                 PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
+                 di->bs = bs;
+                 di_list = g_list_append(di_list, di);
+             } else {
+-                error_set(task->errp, ERROR_CLASS_DEVICE_NOT_FOUND,
++                error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
+                           "Device '%s' not found", *d);
+                 goto err;
+             }
+@@ -641,7 +613,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     }
+     if (!di_list) {
+-        error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
++        error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
+         goto err;
+     }
+@@ -651,13 +623,13 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     while (l) {
+         PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
+         l = g_list_next(l);
+-        if (bdrv_op_is_blocked(di->bs, BLOCK_OP_TYPE_BACKUP_SOURCE, task->errp)) {
++        if (bdrv_op_is_blocked(di->bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) {
+             goto err;
+         }
+         ssize_t size = bdrv_getlength(di->bs);
+         if (size < 0) {
+-            error_setg_errno(task->errp, -di->size, "bdrv_getlength failed");
++            error_setg_errno(errp, -di->size, "bdrv_getlength failed");
+             goto err;
+         }
+         di->size = size;
+@@ -684,47 +656,44 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     }
+     if (format == BACKUP_FORMAT_PBS) {
+-        if (!task->has_password) {
+-            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
++        if (!has_password) {
++            error_set(errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
+             goto err_mutex;
+         }
+-        if (!task->has_backup_id) {
+-            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
++        if (!has_backup_id) {
++            error_set(errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
+             goto err_mutex;
+         }
+-        if (!task->has_backup_time) {
+-            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-time'");
++        if (!has_backup_time) {
++            error_set(errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-time'");
+             goto err_mutex;
+         }
+         int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
+         firewall_name = "fw.conf";
+-        bool use_dirty_bitmap = task->has_use_dirty_bitmap && task->use_dirty_bitmap;
+-
+-
+         char *pbs_err = NULL;
+         pbs = proxmox_backup_new(
+-            task->backup_file,
+-            task->backup_id,
+-            task->backup_time,
++            backup_file,
++            backup_id,
++            backup_time,
+             dump_cb_block_size,
+-            task->has_password ? task->password : NULL,
+-            task->has_keyfile ? task->keyfile : NULL,
+-            task->has_key_password ? task->key_password : NULL,
+-            task->has_compress ? task->compress : true,
+-            task->has_encrypt ? task->encrypt : task->has_keyfile,
+-            task->has_fingerprint ? task->fingerprint : NULL,
++            has_password ? password : NULL,
++            has_keyfile ? keyfile : NULL,
++            has_key_password ? key_password : NULL,
++            has_compress ? compress : true,
++            has_encrypt ? encrypt : has_keyfile,
++            has_fingerprint ? fingerprint : NULL,
+              &pbs_err);
+         if (!pbs) {
+-            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
++            error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+                       "proxmox_backup_new failed: %s", pbs_err);
+             proxmox_backup_free_error(pbs_err);
+             goto err_mutex;
+         }
+-        int connect_result = proxmox_backup_co_connect(pbs, task->errp);
++        int connect_result = proxmox_backup_co_connect(pbs, errp);
+         if (connect_result < 0)
+             goto err_mutex;
+@@ -743,9 +712,9 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             BdrvDirtyBitmap *bitmap = bdrv_find_dirty_bitmap(di->bs, PBS_BITMAP_NAME);
+             bool expect_only_dirty = false;
+-            if (use_dirty_bitmap) {
++            if (has_use_dirty_bitmap && use_dirty_bitmap) {
+                 if (bitmap == NULL) {
+-                    bitmap = bdrv_create_dirty_bitmap(di->bs, dump_cb_block_size, PBS_BITMAP_NAME, task->errp);
++                    bitmap = bdrv_create_dirty_bitmap(di->bs, dump_cb_block_size, PBS_BITMAP_NAME, errp);
+                     if (!bitmap) {
+                         goto err_mutex;
+                     }
+@@ -775,12 +744,12 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+                 }
+             }
+-            int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, expect_only_dirty, task->errp);
++            int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, expect_only_dirty, errp);
+             if (dev_id < 0) {
+                 goto err_mutex;
+             }
+-            if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, task->errp))) {
++            if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, errp))) {
+                 goto err_mutex;
+             }
+@@ -794,10 +763,10 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             backup_state.stat.bitmap_list = g_list_append(backup_state.stat.bitmap_list, info);
+         }
+     } else if (format == BACKUP_FORMAT_VMA) {
+-        vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
++        vmaw = vma_writer_create(backup_file, uuid, &local_err);
+         if (!vmaw) {
+             if (local_err) {
+-                error_propagate(task->errp, local_err);
++                error_propagate(errp, local_err);
+             }
+             goto err_mutex;
+         }
+@@ -808,25 +777,25 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
+             l = g_list_next(l);
+-            if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, task->errp))) {
++            if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, errp))) {
+                 goto err_mutex;
+             }
+             const char *devname = bdrv_get_device_name(di->bs);
+             di->dev_id = vma_writer_register_stream(vmaw, devname, di->size);
+             if (di->dev_id <= 0) {
+-                error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
++                error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+                           "register_stream failed");
+                 goto err_mutex;
+             }
+         }
+     } else if (format == BACKUP_FORMAT_DIR) {
+-        if (mkdir(task->backup_file, 0640) != 0) {
+-            error_setg_errno(task->errp, errno, "can't create directory '%s'\n",
+-                             task->backup_file);
++        if (mkdir(backup_file, 0640) != 0) {
++            error_setg_errno(errp, errno, "can't create directory '%s'\n",
++                             backup_file);
+             goto err_mutex;
+         }
+-        backup_dir = task->backup_file;
++        backup_dir = backup_file;
+         l = di_list;
+         while (l) {
+@@ -840,34 +809,34 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+             bdrv_img_create(di->targetfile, "raw", NULL, NULL, NULL,
+                             di->size, flags, false, &local_err);
+             if (local_err) {
+-                error_propagate(task->errp, local_err);
++                error_propagate(errp, local_err);
+                 goto err_mutex;
+             }
+             di->target = bdrv_open(di->targetfile, NULL, NULL, flags, &local_err);
+             if (!di->target) {
+-                error_propagate(task->errp, local_err);
++                error_propagate(errp, local_err);
+                 goto err_mutex;
+             }
+         }
+     } else {
+-        error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
++        error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
+         goto err_mutex;
+     }
+     /* add configuration file to archive */
+-    if (task->has_config_file) {
+-        if (pvebackup_co_add_config(task->config_file, config_name, format, backup_dir,
+-                                    vmaw, pbs, task->errp) != 0) {
++    if (has_config_file) {
++        if (pvebackup_co_add_config(config_file, config_name, format, backup_dir,
++                                    vmaw, pbs, errp) != 0) {
+             goto err_mutex;
+         }
+     }
+     /* add firewall file to archive */
+-    if (task->has_firewall_file) {
+-        if (pvebackup_co_add_config(task->firewall_file, firewall_name, format, backup_dir,
+-                                    vmaw, pbs, task->errp) != 0) {
++    if (has_firewall_file) {
++        if (pvebackup_co_add_config(firewall_file, firewall_name, format, backup_dir,
++                                    vmaw, pbs, errp) != 0) {
+             goto err_mutex;
+         }
+     }
+@@ -885,7 +854,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     if (backup_state.stat.backup_file) {
+         g_free(backup_state.stat.backup_file);
+     }
+-    backup_state.stat.backup_file = g_strdup(task->backup_file);
++    backup_state.stat.backup_file = g_strdup(backup_file);
+     uuid_copy(backup_state.stat.uuid, uuid);
+     uuid_unparse_lower(uuid, backup_state.stat.uuid_str);
+@@ -900,7 +869,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     qemu_mutex_unlock(&backup_state.stat.lock);
+-    backup_state.speed = (task->has_speed && task->speed > 0) ? task->speed : 0;
++    backup_state.speed = (has_speed && speed > 0) ? speed : 0;
+     backup_state.vmaw = vmaw;
+     backup_state.pbs = pbs;
+@@ -910,8 +879,6 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     uuid_info = g_malloc0(sizeof(*uuid_info));
+     uuid_info->UUID = uuid_str;
+-    task->result = uuid_info;
+-
+     /* Run create_backup_jobs_bh outside of coroutine (in BH) but keep
+     * backup_mutex locked. This is fine, a CoMutex can be held across yield
+     * points, and we'll release it as soon as the BH reschedules us.
+@@ -925,7 +892,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     qemu_coroutine_yield();
+     if (local_err) {
+-        error_propagate(task->errp, local_err);
++        error_propagate(errp, local_err);
+         goto err;
+     }
+@@ -938,7 +905,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
+     /* start the first job in the transaction */
+     job_txn_start_seq(backup_state.txn);
+-    return;
++    return uuid_info;
+ err_mutex:
+     qemu_mutex_unlock(&backup_state.stat.lock);
+@@ -969,7 +936,7 @@ err:
+     if (vmaw) {
+         Error *err = NULL;
+         vma_writer_close(vmaw, &err);
+-        unlink(task->backup_file);
++        unlink(backup_file);
+     }
+     if (pbs) {
+@@ -980,65 +947,8 @@ err:
+         rmdir(backup_dir);
+     }
+-    task->result = NULL;
+-
+     qemu_co_mutex_unlock(&backup_state.backup_mutex);
+-    return;
+-}
+-
+-UuidInfo *qmp_backup(
+-    const char *backup_file,
+-    bool has_password, const char *password,
+-    bool has_keyfile, const char *keyfile,
+-    bool has_key_password, const char *key_password,
+-    bool has_fingerprint, const char *fingerprint,
+-    bool has_backup_id, const char *backup_id,
+-    bool has_backup_time, int64_t backup_time,
+-    bool has_use_dirty_bitmap, bool use_dirty_bitmap,
+-    bool has_compress, bool compress,
+-    bool has_encrypt, bool encrypt,
+-    bool has_format, BackupFormat format,
+-    bool has_config_file, const char *config_file,
+-    bool has_firewall_file, const char *firewall_file,
+-    bool has_devlist, const char *devlist,
+-    bool has_speed, int64_t speed, Error **errp)
+-{
+-    QmpBackupTask task = {
+-        .backup_file = backup_file,
+-        .has_password = has_password,
+-        .password = password,
+-        .has_keyfile = has_keyfile,
+-        .keyfile = keyfile,
+-        .has_key_password = has_key_password,
+-        .key_password = key_password,
+-        .has_fingerprint = has_fingerprint,
+-        .fingerprint = fingerprint,
+-        .has_backup_id = has_backup_id,
+-        .backup_id = backup_id,
+-        .has_backup_time = has_backup_time,
+-        .backup_time = backup_time,
+-        .has_use_dirty_bitmap = has_use_dirty_bitmap,
+-        .use_dirty_bitmap = use_dirty_bitmap,
+-        .has_compress = has_compress,
+-        .compress = compress,
+-        .has_encrypt = has_encrypt,
+-        .encrypt = encrypt,
+-        .has_format = has_format,
+-        .format = format,
+-        .has_config_file = has_config_file,
+-        .config_file = config_file,
+-        .has_firewall_file = has_firewall_file,
+-        .firewall_file = firewall_file,
+-        .has_devlist = has_devlist,
+-        .devlist = devlist,
+-        .has_speed = has_speed,
+-        .speed = speed,
+-        .errp = errp,
+-    };
+-
+-    block_on_coroutine_fn(pvebackup_co_prepare, &task);
+-
+-    return task.result;
++    return NULL;
+ }
+ BackupStatus *qmp_query_backup(Error **errp)
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index f6a5fb263a..f216035d3c 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -846,7 +846,7 @@
+                                     '*config-file': 'str',
+                                     '*firewall-file': 'str',
+                                     '*devlist': 'str', '*speed': 'int' },
+-  'returns': 'UuidInfo' }
++  'returns': 'UuidInfo', 'coroutine': true }
+ ##
+ # @query-backup:
+@@ -868,7 +868,7 @@
+ # Notes: This command succeeds even if there is no backup process running.
+ #
+ ##
+-{ 'command': 'backup-cancel' }
++{ 'command': 'backup-cancel', 'coroutine': true }
+ ##
+ # @ProxmoxSupportStatus:
diff --git a/debian/patches/pve/0043-PBS-add-master-key-support.patch b/debian/patches/pve/0043-PBS-add-master-key-support.patch
new file mode 100644 (file)
index 0000000..f14d4d9
--- /dev/null
@@ -0,0 +1,98 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Wed, 10 Feb 2021 11:07:06 +0100
+Subject: [PATCH] PBS: add master key support
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+this requires a new enough libproxmox-backup-qemu0, and allows querying
+from the PVE side to avoid QMP calls with unsupported parameters.
+
+Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ block/monitor/block-hmp-cmds.c | 1 +
+ pve-backup.c                   | 3 +++
+ qapi/block-core.json           | 7 +++++++
+ 3 files changed, 11 insertions(+)
+
+diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
+index c9849a5b29..52ddbf95ad 100644
+--- a/block/monitor/block-hmp-cmds.c
++++ b/block/monitor/block-hmp-cmds.c
+@@ -1039,6 +1039,7 @@ void coroutine_fn hmp_backup(Monitor *mon, const QDict *qdict)
+         false, NULL, // PBS password
+         false, NULL, // PBS keyfile
+         false, NULL, // PBS key_password
++        false, NULL, // PBS master_keyfile
+         false, NULL, // PBS fingerprint
+         false, NULL, // PBS backup-id
+         false, 0, // PBS backup-time
+diff --git a/pve-backup.c b/pve-backup.c
+index 323014744c..9f6c04a512 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -533,6 +533,7 @@ UuidInfo coroutine_fn *qmp_backup(
+     bool has_password, const char *password,
+     bool has_keyfile, const char *keyfile,
+     bool has_key_password, const char *key_password,
++    bool has_master_keyfile, const char *master_keyfile,
+     bool has_fingerprint, const char *fingerprint,
+     bool has_backup_id, const char *backup_id,
+     bool has_backup_time, int64_t backup_time,
+@@ -681,6 +682,7 @@ UuidInfo coroutine_fn *qmp_backup(
+             has_password ? password : NULL,
+             has_keyfile ? keyfile : NULL,
+             has_key_password ? key_password : NULL,
++            has_master_keyfile ? master_keyfile : NULL,
+             has_compress ? compress : true,
+             has_encrypt ? encrypt : has_keyfile,
+             has_fingerprint ? fingerprint : NULL,
+@@ -1044,5 +1046,6 @@ ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp)
+     ret->pbs_dirty_bitmap_savevm = true;
+     ret->pbs_dirty_bitmap_migration = true;
+     ret->query_bitmap_info = true;
++    ret->pbs_masterkey = true;
+     return ret;
+ }
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index f216035d3c..c5023710f5 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -817,6 +817,8 @@
+ #
+ # @key-password: password for keyfile (optional for format 'pbs')
+ #
++# @master-keyfile: PEM-formatted master public keyfile (optional for format 'pbs')
++#
+ # @fingerprint: server cert fingerprint (optional for format 'pbs')
+ #
+ # @backup-id: backup ID (required for format 'pbs')
+@@ -836,6 +838,7 @@
+                                     '*password': 'str',
+                                     '*keyfile': 'str',
+                                     '*key-password': 'str',
++                                    '*master-keyfile': 'str',
+                                     '*fingerprint': 'str',
+                                     '*backup-id': 'str',
+                                     '*backup-time': 'int',
+@@ -888,6 +891,9 @@
+ #                              migration cap if this is false/unset may lead
+ #                              to crashes on migration!
+ #
++# @pbs-masterkey: True if the QMP backup call supports the 'master_keyfile'
++#                 parameter.
++#
+ # @pbs-library-version: Running version of libproxmox-backup-qemu0 library.
+ #
+ ##
+@@ -896,6 +902,7 @@
+             'query-bitmap-info': 'bool',
+             'pbs-dirty-bitmap-savevm': 'bool',
+             'pbs-dirty-bitmap-migration': 'bool',
++            'pbs-masterkey': 'bool',
+             'pbs-library-version': 'str' } }
+ ##
diff --git a/debian/patches/pve/0043-PVE-block-pbs-fast-path-reads-without-allocation-if-.patch b/debian/patches/pve/0043-PVE-block-pbs-fast-path-reads-without-allocation-if-.patch
deleted file mode 100644 (file)
index 893104a..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Wed, 9 Dec 2020 11:46:57 +0100
-Subject: [PATCH] PVE: block/pbs: fast-path reads without allocation if
- possible
-
-...and switch over to g_malloc/g_free while at it to align with other
-QEMU code.
-
-Tracing shows the fast-path is taken almost all the time, though not
-100% so the slow one is still necessary.
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- block/pbs.c | 17 ++++++++++++++---
- 1 file changed, 14 insertions(+), 3 deletions(-)
-
-diff --git a/block/pbs.c b/block/pbs.c
-index 0b05ea9080..c5eb4d5bad 100644
---- a/block/pbs.c
-+++ b/block/pbs.c
-@@ -200,7 +200,16 @@ static coroutine_fn int pbs_co_preadv(BlockDriverState *bs,
-     BDRVPBSState *s = bs->opaque;
-     int ret;
-     char *pbs_error = NULL;
--    uint8_t *buf = malloc(bytes);
-+    uint8_t *buf;
-+    bool inline_buf = true;
-+
-+    /* for single-buffer IO vectors we can fast-path the write directly to it */
-+    if (qiov->niov == 1 && qiov->iov->iov_len >= bytes) {
-+        buf = qiov->iov->iov_base;
-+    } else {
-+        inline_buf = false;
-+        buf = g_malloc(bytes);
-+    }
-     if (offset < 0 || bytes < 0) {
-         fprintf(stderr, "unexpected negative 'offset' or 'bytes' value!\n");
-@@ -223,8 +232,10 @@ static coroutine_fn int pbs_co_preadv(BlockDriverState *bs,
-         return -EIO;
-     }
--    qemu_iovec_from_buf(qiov, 0, buf, bytes);
--    free(buf);
-+    if (!inline_buf) {
-+        qemu_iovec_from_buf(qiov, 0, buf, bytes);
-+        g_free(buf);
-+    }
-     return ret;
- }
diff --git a/debian/patches/pve/0044-PVE-block-pbs-fast-path-reads-without-allocation-if-.patch b/debian/patches/pve/0044-PVE-block-pbs-fast-path-reads-without-allocation-if-.patch
new file mode 100644 (file)
index 0000000..893104a
--- /dev/null
@@ -0,0 +1,53 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Wed, 9 Dec 2020 11:46:57 +0100
+Subject: [PATCH] PVE: block/pbs: fast-path reads without allocation if
+ possible
+
+...and switch over to g_malloc/g_free while at it to align with other
+QEMU code.
+
+Tracing shows the fast-path is taken almost all the time, though not
+100% so the slow one is still necessary.
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ block/pbs.c | 17 ++++++++++++++---
+ 1 file changed, 14 insertions(+), 3 deletions(-)
+
+diff --git a/block/pbs.c b/block/pbs.c
+index 0b05ea9080..c5eb4d5bad 100644
+--- a/block/pbs.c
++++ b/block/pbs.c
+@@ -200,7 +200,16 @@ static coroutine_fn int pbs_co_preadv(BlockDriverState *bs,
+     BDRVPBSState *s = bs->opaque;
+     int ret;
+     char *pbs_error = NULL;
+-    uint8_t *buf = malloc(bytes);
++    uint8_t *buf;
++    bool inline_buf = true;
++
++    /* for single-buffer IO vectors we can fast-path the write directly to it */
++    if (qiov->niov == 1 && qiov->iov->iov_len >= bytes) {
++        buf = qiov->iov->iov_base;
++    } else {
++        inline_buf = false;
++        buf = g_malloc(bytes);
++    }
+     if (offset < 0 || bytes < 0) {
+         fprintf(stderr, "unexpected negative 'offset' or 'bytes' value!\n");
+@@ -223,8 +232,10 @@ static coroutine_fn int pbs_co_preadv(BlockDriverState *bs,
+         return -EIO;
+     }
+-    qemu_iovec_from_buf(qiov, 0, buf, bytes);
+-    free(buf);
++    if (!inline_buf) {
++        qemu_iovec_from_buf(qiov, 0, buf, bytes);
++        g_free(buf);
++    }
+     return ret;
+ }
diff --git a/debian/patches/pve/0044-PVE-block-stream-increase-chunk-size.patch b/debian/patches/pve/0044-PVE-block-stream-increase-chunk-size.patch
deleted file mode 100644 (file)
index fe0ec25..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Tue, 2 Mar 2021 16:34:28 +0100
-Subject: [PATCH] PVE: block/stream: increase chunk size
-
-Ceph favors bigger chunks, so increase to 4M.
-
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- block/stream.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/block/stream.c b/block/stream.c
-index e45113aed6..c3c0c5febe 100644
---- a/block/stream.c
-+++ b/block/stream.c
-@@ -28,7 +28,7 @@ enum {
-      * large enough to process multiple clusters in a single call, so
-      * that populating contiguous regions of the image is efficient.
-      */
--    STREAM_CHUNK = 512 * 1024, /* in bytes */
-+    STREAM_CHUNK = 4 * 1024 * 1024, /* in bytes */
- };
- typedef struct StreamBlockJob {
diff --git a/debian/patches/pve/0045-PVE-block-stream-increase-chunk-size.patch b/debian/patches/pve/0045-PVE-block-stream-increase-chunk-size.patch
new file mode 100644 (file)
index 0000000..fe0ec25
--- /dev/null
@@ -0,0 +1,25 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Tue, 2 Mar 2021 16:34:28 +0100
+Subject: [PATCH] PVE: block/stream: increase chunk size
+
+Ceph favors bigger chunks, so increase to 4M.
+
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ block/stream.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/block/stream.c b/block/stream.c
+index e45113aed6..c3c0c5febe 100644
+--- a/block/stream.c
++++ b/block/stream.c
+@@ -28,7 +28,7 @@ enum {
+      * large enough to process multiple clusters in a single call, so
+      * that populating contiguous regions of the image is efficient.
+      */
+-    STREAM_CHUNK = 512 * 1024, /* in bytes */
++    STREAM_CHUNK = 4 * 1024 * 1024, /* in bytes */
+ };
+ typedef struct StreamBlockJob {
diff --git a/debian/patches/pve/0045-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch b/debian/patches/pve/0045-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch
deleted file mode 100644 (file)
index 49f0f9d..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Tue, 2 Mar 2021 16:11:54 +0100
-Subject: [PATCH] block/io: accept NULL qiov in bdrv_pad_request
-
-Some operations, e.g. block-stream, perform reads while discarding the
-results (only copy-on-read matters). In this case they will pass NULL as
-the target QEMUIOVector, which will however trip bdrv_pad_request, since
-it wants to extend its passed vector.
-
-Simply check for NULL and do nothing, there's no reason to pad the
-target if it will be discarded anyway.
-
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- block/io.c | 4 ++++
- 1 file changed, 4 insertions(+)
-
-diff --git a/block/io.c b/block/io.c
-index 4e4cb556c5..04061f1e68 100644
---- a/block/io.c
-+++ b/block/io.c
-@@ -1765,6 +1765,10 @@ static int bdrv_pad_request(BlockDriverState *bs,
- {
-     int ret;
-+    if (!qiov) {
-+        return 0;
-+    }
-+
-     bdrv_check_qiov_request(*offset, *bytes, *qiov, *qiov_offset, &error_abort);
-     if (!bdrv_init_padding(bs, *offset, *bytes, pad)) {
diff --git a/debian/patches/pve/0046-block-add-alloc-track-driver.patch b/debian/patches/pve/0046-block-add-alloc-track-driver.patch
deleted file mode 100644 (file)
index 71790d8..0000000
+++ /dev/null
@@ -1,402 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Mon, 7 Dec 2020 15:21:03 +0100
-Subject: [PATCH] block: add alloc-track driver
-
-Add a new filter node 'alloc-track', which seperates reads and writes to
-different children, thus allowing to put a backing image behind any
-blockdev (regardless of driver support). Since we can't detect any
-pre-allocated blocks, we can only track new writes, hence the write
-target ('file') for this node must always be empty.
-
-Intended use case is for live restoring, i.e. add a backup image as a
-block device into a VM, then put an alloc-track on the restore target
-and set the backup as backing. With this, one can use a regular
-'block-stream' to restore the image, while the VM can already run in the
-background. Copy-on-read will help make progress as the VM reads as
-well.
-
-This only worked if the target supports backing images, so up until now
-only for qcow2, with alloc-track any driver for the target can be used.
-
-If 'auto-remove' is set, alloc-track will automatically detach itself
-once the backing image is removed. It will be replaced by 'file'.
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-[adapt to changed function signatures]
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
----
- block/alloc-track.c | 350 ++++++++++++++++++++++++++++++++++++++++++++
- block/meson.build   |   1 +
- 2 files changed, 351 insertions(+)
- create mode 100644 block/alloc-track.c
-
-diff --git a/block/alloc-track.c b/block/alloc-track.c
-new file mode 100644
-index 0000000000..6b50fbe537
---- /dev/null
-+++ b/block/alloc-track.c
-@@ -0,0 +1,350 @@
-+/*
-+ * Node to allow backing images to be applied to any node. Assumes a blank
-+ * image to begin with, only new writes are tracked as allocated, thus this
-+ * must never be put on a node that already contains data.
-+ *
-+ * Copyright (c) 2020 Proxmox Server Solutions GmbH
-+ * Copyright (c) 2020 Stefan Reiter <s.reiter@proxmox.com>
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ */
-+
-+#include "qemu/osdep.h"
-+#include "qapi/error.h"
-+#include "block/block_int.h"
-+#include "qapi/qmp/qdict.h"
-+#include "qapi/qmp/qstring.h"
-+#include "qemu/cutils.h"
-+#include "qemu/option.h"
-+#include "qemu/module.h"
-+#include "sysemu/block-backend.h"
-+
-+#define TRACK_OPT_AUTO_REMOVE "auto-remove"
-+
-+typedef enum DropState {
-+    DropNone,
-+    DropRequested,
-+    DropInProgress,
-+} DropState;
-+
-+typedef struct {
-+    BdrvDirtyBitmap *bitmap;
-+    DropState drop_state;
-+    bool auto_remove;
-+} BDRVAllocTrackState;
-+
-+static QemuOptsList runtime_opts = {
-+    .name = "alloc-track",
-+    .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
-+    .desc = {
-+        {
-+            .name = TRACK_OPT_AUTO_REMOVE,
-+            .type = QEMU_OPT_BOOL,
-+            .help = "automatically replace this node with 'file' when 'backing'"
-+                    "is detached",
-+        },
-+        { /* end of list */ }
-+    },
-+};
-+
-+static void track_refresh_limits(BlockDriverState *bs, Error **errp)
-+{
-+    BlockDriverInfo bdi;
-+
-+    if (!bs->file) {
-+        return;
-+    }
-+
-+    /* always use alignment from underlying write device so RMW cycle for
-+     * bdrv_pwritev reads data from our backing via track_co_preadv (no partial
-+     * cluster allocation in 'file') */
-+    bdrv_get_info(bs->file->bs, &bdi);
-+    bs->bl.request_alignment = MAX(bs->file->bs->bl.request_alignment,
-+                                   MAX(bdi.cluster_size, BDRV_SECTOR_SIZE));
-+}
-+
-+static int track_open(BlockDriverState *bs, QDict *options, int flags,
-+                      Error **errp)
-+{
-+    BDRVAllocTrackState *s = bs->opaque;
-+    QemuOpts *opts;
-+    Error *local_err = NULL;
-+    int ret = 0;
-+
-+    opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
-+    qemu_opts_absorb_qdict(opts, options, &local_err);
-+    if (local_err) {
-+        error_propagate(errp, local_err);
-+        ret = -EINVAL;
-+        goto fail;
-+    }
-+
-+    s->auto_remove = qemu_opt_get_bool(opts, TRACK_OPT_AUTO_REMOVE, false);
-+
-+    /* open the target (write) node, backing will be attached by block layer */
-+    bs->file = bdrv_open_child(NULL, options, "file", bs, &child_of_bds,
-+                               BDRV_CHILD_DATA | BDRV_CHILD_METADATA, false,
-+                               &local_err);
-+    if (local_err) {
-+        ret = -EINVAL;
-+        error_propagate(errp, local_err);
-+        goto fail;
-+    }
-+
-+    track_refresh_limits(bs, errp);
-+    uint64_t gran = bs->bl.request_alignment;
-+    s->bitmap = bdrv_create_dirty_bitmap(bs->file->bs, gran, NULL, &local_err);
-+    if (local_err) {
-+        ret = -EIO;
-+        error_propagate(errp, local_err);
-+        goto fail;
-+    }
-+
-+    s->drop_state = DropNone;
-+
-+fail:
-+    if (ret < 0) {
-+        bdrv_unref_child(bs, bs->file);
-+        if (s->bitmap) {
-+            bdrv_release_dirty_bitmap(s->bitmap);
-+        }
-+    }
-+    qemu_opts_del(opts);
-+    return ret;
-+}
-+
-+static void track_close(BlockDriverState *bs)
-+{
-+    BDRVAllocTrackState *s = bs->opaque;
-+    if (s->bitmap) {
-+        bdrv_release_dirty_bitmap(s->bitmap);
-+    }
-+}
-+
-+static int64_t track_getlength(BlockDriverState *bs)
-+{
-+    return bdrv_getlength(bs->file->bs);
-+}
-+
-+static int coroutine_fn track_co_preadv(BlockDriverState *bs,
-+    int64_t offset, int64_t bytes, QEMUIOVector *qiov, BdrvRequestFlags flags)
-+{
-+    BDRVAllocTrackState *s = bs->opaque;
-+    QEMUIOVector local_qiov;
-+    int ret;
-+
-+    /* 'cur_offset' is relative to 'offset', 'local_offset' to image start */
-+    uint64_t cur_offset, local_offset;
-+    int64_t local_bytes;
-+    bool alloc;
-+
-+    if (offset < 0 || bytes < 0) {
-+        fprintf(stderr, "unexpected negative 'offset' or 'bytes' value!\n");
-+        return -EINVAL;
-+    }
-+
-+    /* a read request can span multiple granularity-sized chunks, and can thus
-+     * contain blocks with different allocation status - we could just iterate
-+     * granularity-wise, but for better performance use bdrv_dirty_bitmap_next_X
-+     * to find the next flip and consider everything up to that in one go */
-+    for (cur_offset = 0; cur_offset < bytes; cur_offset += local_bytes) {
-+        local_offset = offset + cur_offset;
-+        alloc = bdrv_dirty_bitmap_get(s->bitmap, local_offset);
-+        if (alloc) {
-+            local_bytes = bdrv_dirty_bitmap_next_zero(s->bitmap, local_offset,
-+                                                      bytes - cur_offset);
-+        } else {
-+            local_bytes = bdrv_dirty_bitmap_next_dirty(s->bitmap, local_offset,
-+                                                       bytes - cur_offset);
-+        }
-+
-+        /* _bitmap_next_X return is -1 if no end found within limit, otherwise
-+         * offset of next flip (to start of image) */
-+        local_bytes = local_bytes < 0 ?
-+            bytes - cur_offset :
-+            local_bytes - local_offset;
-+
-+        qemu_iovec_init_slice(&local_qiov, qiov, cur_offset, local_bytes);
-+
-+        if (alloc) {
-+            ret = bdrv_co_preadv(bs->file, local_offset, local_bytes,
-+                                 &local_qiov, flags);
-+        } else if (bs->backing) {
-+            ret = bdrv_co_preadv(bs->backing, local_offset, local_bytes,
-+                                 &local_qiov, flags);
-+        } else {
-+            ret = qemu_iovec_memset(&local_qiov, cur_offset, 0, local_bytes);
-+        }
-+
-+        if (ret != 0) {
-+            break;
-+        }
-+    }
-+
-+    return ret;
-+}
-+
-+static int coroutine_fn track_co_pwritev(BlockDriverState *bs,
-+    int64_t offset, int64_t bytes, QEMUIOVector *qiov, BdrvRequestFlags flags)
-+{
-+    return bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags);
-+}
-+
-+static int coroutine_fn track_co_pwrite_zeroes(BlockDriverState *bs,
-+    int64_t offset, int64_t bytes, BdrvRequestFlags flags)
-+{
-+    return bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags);
-+}
-+
-+static int coroutine_fn track_co_pdiscard(BlockDriverState *bs,
-+    int64_t offset, int64_t bytes)
-+{
-+    return bdrv_co_pdiscard(bs->file, offset, bytes);
-+}
-+
-+static coroutine_fn int track_co_flush(BlockDriverState *bs)
-+{
-+    return bdrv_co_flush(bs->file->bs);
-+}
-+
-+static int coroutine_fn track_co_block_status(BlockDriverState *bs,
-+                                              bool want_zero,
-+                                              int64_t offset,
-+                                              int64_t bytes,
-+                                              int64_t *pnum,
-+                                              int64_t *map,
-+                                              BlockDriverState **file)
-+{
-+    BDRVAllocTrackState *s = bs->opaque;
-+
-+    bool alloc = bdrv_dirty_bitmap_get(s->bitmap, offset);
-+    int64_t next_flipped;
-+    if (alloc) {
-+        next_flipped = bdrv_dirty_bitmap_next_zero(s->bitmap, offset, bytes);
-+    } else {
-+        next_flipped = bdrv_dirty_bitmap_next_dirty(s->bitmap, offset, bytes);
-+    }
-+
-+    /* in case not the entire region has the same state, we need to set pnum to
-+     * indicate for how many bytes our result is valid */
-+    *pnum = next_flipped == -1 ? bytes : next_flipped - offset;
-+    *map = offset;
-+
-+    if (alloc) {
-+        *file = bs->file->bs;
-+        return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID;
-+    } else if (bs->backing) {
-+        *file = bs->backing->bs;
-+    }
-+    return 0;
-+}
-+
-+static void track_child_perm(BlockDriverState *bs, BdrvChild *c,
-+                             BdrvChildRole role, BlockReopenQueue *reopen_queue,
-+                             uint64_t perm, uint64_t shared,
-+                             uint64_t *nperm, uint64_t *nshared)
-+{
-+    BDRVAllocTrackState *s = bs->opaque;
-+
-+    *nshared = BLK_PERM_ALL;
-+
-+    /* in case we're currently dropping ourselves, claim to not use any
-+     * permissions at all - which is fine, since from this point on we will
-+     * never issue a read or write anymore */
-+    if (s->drop_state == DropInProgress) {
-+        *nperm = 0;
-+        return;
-+    }
-+
-+    if (role & BDRV_CHILD_DATA) {
-+        *nperm = perm & DEFAULT_PERM_PASSTHROUGH;
-+    } else {
-+        /* 'backing' is also a child of our BDS, but we don't expect it to be
-+         * writeable, so we only forward 'consistent read' */
-+        *nperm = perm & BLK_PERM_CONSISTENT_READ;
-+    }
-+}
-+
-+static void track_drop(void *opaque)
-+{
-+    BlockDriverState *bs = (BlockDriverState*)opaque;
-+    BlockDriverState *file = bs->file->bs;
-+    BDRVAllocTrackState *s = bs->opaque;
-+
-+    assert(file);
-+
-+    /* we rely on the fact that we're not used anywhere else, so let's wait
-+     * until we're only used once - in the drive connected to the guest (and one
-+     * ref is held by bdrv_ref in track_change_backing_file) */
-+    if (bs->refcnt > 2) {
-+        aio_bh_schedule_oneshot(qemu_get_aio_context(), track_drop, opaque);
-+        return;
-+    }
-+    AioContext *aio_context = bdrv_get_aio_context(bs);
-+    aio_context_acquire(aio_context);
-+
-+    bdrv_drained_begin(bs);
-+
-+    /* now that we're drained, we can safely set 'DropInProgress' */
-+    s->drop_state = DropInProgress;
-+    bdrv_child_refresh_perms(bs, bs->file, &error_abort);
-+
-+    bdrv_replace_node(bs, file, &error_abort);
-+    bdrv_set_backing_hd(bs, NULL, &error_abort);
-+    bdrv_drained_end(bs);
-+    bdrv_unref(bs);
-+    aio_context_release(aio_context);
-+}
-+
-+static int track_change_backing_file(BlockDriverState *bs,
-+                                     const char *backing_file,
-+                                     const char *backing_fmt)
-+{
-+    BDRVAllocTrackState *s = bs->opaque;
-+    if (s->auto_remove && s->drop_state == DropNone &&
-+        backing_file == NULL && backing_fmt == NULL)
-+    {
-+        /* backing file has been disconnected, there's no longer any use for
-+         * this node, so let's remove ourselves from the block graph - we need
-+         * to schedule this for later however, since when this function is
-+         * called, the blockjob modifying us is probably not done yet and has a
-+         * blocker on 'bs' */
-+        s->drop_state = DropRequested;
-+        bdrv_ref(bs);
-+        aio_bh_schedule_oneshot(qemu_get_aio_context(), track_drop, (void*)bs);
-+    }
-+
-+    return 0;
-+}
-+
-+static BlockDriver bdrv_alloc_track = {
-+    .format_name                      = "alloc-track",
-+    .instance_size                    = sizeof(BDRVAllocTrackState),
-+
-+    .bdrv_file_open                   = track_open,
-+    .bdrv_close                       = track_close,
-+    .bdrv_getlength                   = track_getlength,
-+    .bdrv_child_perm                  = track_child_perm,
-+    .bdrv_refresh_limits              = track_refresh_limits,
-+
-+    .bdrv_co_pwrite_zeroes            = track_co_pwrite_zeroes,
-+    .bdrv_co_pwritev                  = track_co_pwritev,
-+    .bdrv_co_preadv                   = track_co_preadv,
-+    .bdrv_co_pdiscard                 = track_co_pdiscard,
-+
-+    .bdrv_co_flush                    = track_co_flush,
-+    .bdrv_co_flush_to_disk            = track_co_flush,
-+
-+    .supports_backing                 = true,
-+
-+    .bdrv_co_block_status             = track_co_block_status,
-+    .bdrv_change_backing_file         = track_change_backing_file,
-+};
-+
-+static void bdrv_alloc_track_init(void)
-+{
-+    bdrv_register(&bdrv_alloc_track);
-+}
-+
-+block_init(bdrv_alloc_track_init);
-diff --git a/block/meson.build b/block/meson.build
-index 8c758c0218..45b72e10f1 100644
---- a/block/meson.build
-+++ b/block/meson.build
-@@ -2,6 +2,7 @@ block_ss.add(genh)
- block_ss.add(files(
-   'accounting.c',
-   'aio_task.c',
-+  'alloc-track.c',
-   'amend.c',
-   'backup.c',
-   'backup-dump.c',
diff --git a/debian/patches/pve/0046-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch b/debian/patches/pve/0046-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch
new file mode 100644 (file)
index 0000000..49f0f9d
--- /dev/null
@@ -0,0 +1,33 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Tue, 2 Mar 2021 16:11:54 +0100
+Subject: [PATCH] block/io: accept NULL qiov in bdrv_pad_request
+
+Some operations, e.g. block-stream, perform reads while discarding the
+results (only copy-on-read matters). In this case they will pass NULL as
+the target QEMUIOVector, which will however trip bdrv_pad_request, since
+it wants to extend its passed vector.
+
+Simply check for NULL and do nothing, there's no reason to pad the
+target if it will be discarded anyway.
+
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ block/io.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/block/io.c b/block/io.c
+index 4e4cb556c5..04061f1e68 100644
+--- a/block/io.c
++++ b/block/io.c
+@@ -1765,6 +1765,10 @@ static int bdrv_pad_request(BlockDriverState *bs,
+ {
+     int ret;
++    if (!qiov) {
++        return 0;
++    }
++
+     bdrv_check_qiov_request(*offset, *bytes, *qiov, *qiov_offset, &error_abort);
+     if (!bdrv_init_padding(bs, *offset, *bytes, pad)) {
diff --git a/debian/patches/pve/0047-PVE-whitelist-invalid-QAPI-names-for-backwards-compa.patch b/debian/patches/pve/0047-PVE-whitelist-invalid-QAPI-names-for-backwards-compa.patch
deleted file mode 100644 (file)
index ced1323..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Wed, 26 May 2021 15:26:30 +0200
-Subject: [PATCH] PVE: whitelist 'invalid' QAPI names for backwards compat
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- qapi/pragma.json | 5 ++++-
- 1 file changed, 4 insertions(+), 1 deletion(-)
-
-diff --git a/qapi/pragma.json b/qapi/pragma.json
-index 7c91ea3685..c3888d654c 100644
---- a/qapi/pragma.json
-+++ b/qapi/pragma.json
-@@ -12,6 +12,7 @@
-         'device_add',
-         'device_del',
-         'expire_password',
-+        'get_link_status',
-         'migrate_cancel',
-         'netdev_add',
-         'netdev_del',
-@@ -60,6 +61,8 @@
-         'SysEmuTarget',             # query-cpu-fast, query-target
-         'UuidInfo',                 # query-uuid
-         'VncClientInfo',            # query-vnc, query-vnc-servers, ...
--        'X86CPURegister32'          # qom-get of x86 CPU properties
-+        'X86CPURegister32',         # qom-get of x86 CPU properties
-                                     # feature-words, filtered-features
-+        'BlockdevOptionsPbs',       # for PBS backwards compat
-+        'BalloonInfo'
-     ] } }
diff --git a/debian/patches/pve/0047-block-add-alloc-track-driver.patch b/debian/patches/pve/0047-block-add-alloc-track-driver.patch
new file mode 100644 (file)
index 0000000..71790d8
--- /dev/null
@@ -0,0 +1,402 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Mon, 7 Dec 2020 15:21:03 +0100
+Subject: [PATCH] block: add alloc-track driver
+
+Add a new filter node 'alloc-track', which seperates reads and writes to
+different children, thus allowing to put a backing image behind any
+blockdev (regardless of driver support). Since we can't detect any
+pre-allocated blocks, we can only track new writes, hence the write
+target ('file') for this node must always be empty.
+
+Intended use case is for live restoring, i.e. add a backup image as a
+block device into a VM, then put an alloc-track on the restore target
+and set the backup as backing. With this, one can use a regular
+'block-stream' to restore the image, while the VM can already run in the
+background. Copy-on-read will help make progress as the VM reads as
+well.
+
+This only worked if the target supports backing images, so up until now
+only for qcow2, with alloc-track any driver for the target can be used.
+
+If 'auto-remove' is set, alloc-track will automatically detach itself
+once the backing image is removed. It will be replaced by 'file'.
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[adapt to changed function signatures]
+Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
+---
+ block/alloc-track.c | 350 ++++++++++++++++++++++++++++++++++++++++++++
+ block/meson.build   |   1 +
+ 2 files changed, 351 insertions(+)
+ create mode 100644 block/alloc-track.c
+
+diff --git a/block/alloc-track.c b/block/alloc-track.c
+new file mode 100644
+index 0000000000..6b50fbe537
+--- /dev/null
++++ b/block/alloc-track.c
+@@ -0,0 +1,350 @@
++/*
++ * Node to allow backing images to be applied to any node. Assumes a blank
++ * image to begin with, only new writes are tracked as allocated, thus this
++ * must never be put on a node that already contains data.
++ *
++ * Copyright (c) 2020 Proxmox Server Solutions GmbH
++ * Copyright (c) 2020 Stefan Reiter <s.reiter@proxmox.com>
++ *
++ * This work is licensed under the terms of the GNU GPL, version 2 or later.
++ * See the COPYING file in the top-level directory.
++ */
++
++#include "qemu/osdep.h"
++#include "qapi/error.h"
++#include "block/block_int.h"
++#include "qapi/qmp/qdict.h"
++#include "qapi/qmp/qstring.h"
++#include "qemu/cutils.h"
++#include "qemu/option.h"
++#include "qemu/module.h"
++#include "sysemu/block-backend.h"
++
++#define TRACK_OPT_AUTO_REMOVE "auto-remove"
++
++typedef enum DropState {
++    DropNone,
++    DropRequested,
++    DropInProgress,
++} DropState;
++
++typedef struct {
++    BdrvDirtyBitmap *bitmap;
++    DropState drop_state;
++    bool auto_remove;
++} BDRVAllocTrackState;
++
++static QemuOptsList runtime_opts = {
++    .name = "alloc-track",
++    .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
++    .desc = {
++        {
++            .name = TRACK_OPT_AUTO_REMOVE,
++            .type = QEMU_OPT_BOOL,
++            .help = "automatically replace this node with 'file' when 'backing'"
++                    "is detached",
++        },
++        { /* end of list */ }
++    },
++};
++
++static void track_refresh_limits(BlockDriverState *bs, Error **errp)
++{
++    BlockDriverInfo bdi;
++
++    if (!bs->file) {
++        return;
++    }
++
++    /* always use alignment from underlying write device so RMW cycle for
++     * bdrv_pwritev reads data from our backing via track_co_preadv (no partial
++     * cluster allocation in 'file') */
++    bdrv_get_info(bs->file->bs, &bdi);
++    bs->bl.request_alignment = MAX(bs->file->bs->bl.request_alignment,
++                                   MAX(bdi.cluster_size, BDRV_SECTOR_SIZE));
++}
++
++static int track_open(BlockDriverState *bs, QDict *options, int flags,
++                      Error **errp)
++{
++    BDRVAllocTrackState *s = bs->opaque;
++    QemuOpts *opts;
++    Error *local_err = NULL;
++    int ret = 0;
++
++    opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
++    qemu_opts_absorb_qdict(opts, options, &local_err);
++    if (local_err) {
++        error_propagate(errp, local_err);
++        ret = -EINVAL;
++        goto fail;
++    }
++
++    s->auto_remove = qemu_opt_get_bool(opts, TRACK_OPT_AUTO_REMOVE, false);
++
++    /* open the target (write) node, backing will be attached by block layer */
++    bs->file = bdrv_open_child(NULL, options, "file", bs, &child_of_bds,
++                               BDRV_CHILD_DATA | BDRV_CHILD_METADATA, false,
++                               &local_err);
++    if (local_err) {
++        ret = -EINVAL;
++        error_propagate(errp, local_err);
++        goto fail;
++    }
++
++    track_refresh_limits(bs, errp);
++    uint64_t gran = bs->bl.request_alignment;
++    s->bitmap = bdrv_create_dirty_bitmap(bs->file->bs, gran, NULL, &local_err);
++    if (local_err) {
++        ret = -EIO;
++        error_propagate(errp, local_err);
++        goto fail;
++    }
++
++    s->drop_state = DropNone;
++
++fail:
++    if (ret < 0) {
++        bdrv_unref_child(bs, bs->file);
++        if (s->bitmap) {
++            bdrv_release_dirty_bitmap(s->bitmap);
++        }
++    }
++    qemu_opts_del(opts);
++    return ret;
++}
++
++static void track_close(BlockDriverState *bs)
++{
++    BDRVAllocTrackState *s = bs->opaque;
++    if (s->bitmap) {
++        bdrv_release_dirty_bitmap(s->bitmap);
++    }
++}
++
++static int64_t track_getlength(BlockDriverState *bs)
++{
++    return bdrv_getlength(bs->file->bs);
++}
++
++static int coroutine_fn track_co_preadv(BlockDriverState *bs,
++    int64_t offset, int64_t bytes, QEMUIOVector *qiov, BdrvRequestFlags flags)
++{
++    BDRVAllocTrackState *s = bs->opaque;
++    QEMUIOVector local_qiov;
++    int ret;
++
++    /* 'cur_offset' is relative to 'offset', 'local_offset' to image start */
++    uint64_t cur_offset, local_offset;
++    int64_t local_bytes;
++    bool alloc;
++
++    if (offset < 0 || bytes < 0) {
++        fprintf(stderr, "unexpected negative 'offset' or 'bytes' value!\n");
++        return -EINVAL;
++    }
++
++    /* a read request can span multiple granularity-sized chunks, and can thus
++     * contain blocks with different allocation status - we could just iterate
++     * granularity-wise, but for better performance use bdrv_dirty_bitmap_next_X
++     * to find the next flip and consider everything up to that in one go */
++    for (cur_offset = 0; cur_offset < bytes; cur_offset += local_bytes) {
++        local_offset = offset + cur_offset;
++        alloc = bdrv_dirty_bitmap_get(s->bitmap, local_offset);
++        if (alloc) {
++            local_bytes = bdrv_dirty_bitmap_next_zero(s->bitmap, local_offset,
++                                                      bytes - cur_offset);
++        } else {
++            local_bytes = bdrv_dirty_bitmap_next_dirty(s->bitmap, local_offset,
++                                                       bytes - cur_offset);
++        }
++
++        /* _bitmap_next_X return is -1 if no end found within limit, otherwise
++         * offset of next flip (to start of image) */
++        local_bytes = local_bytes < 0 ?
++            bytes - cur_offset :
++            local_bytes - local_offset;
++
++        qemu_iovec_init_slice(&local_qiov, qiov, cur_offset, local_bytes);
++
++        if (alloc) {
++            ret = bdrv_co_preadv(bs->file, local_offset, local_bytes,
++                                 &local_qiov, flags);
++        } else if (bs->backing) {
++            ret = bdrv_co_preadv(bs->backing, local_offset, local_bytes,
++                                 &local_qiov, flags);
++        } else {
++            ret = qemu_iovec_memset(&local_qiov, cur_offset, 0, local_bytes);
++        }
++
++        if (ret != 0) {
++            break;
++        }
++    }
++
++    return ret;
++}
++
++static int coroutine_fn track_co_pwritev(BlockDriverState *bs,
++    int64_t offset, int64_t bytes, QEMUIOVector *qiov, BdrvRequestFlags flags)
++{
++    return bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags);
++}
++
++static int coroutine_fn track_co_pwrite_zeroes(BlockDriverState *bs,
++    int64_t offset, int64_t bytes, BdrvRequestFlags flags)
++{
++    return bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags);
++}
++
++static int coroutine_fn track_co_pdiscard(BlockDriverState *bs,
++    int64_t offset, int64_t bytes)
++{
++    return bdrv_co_pdiscard(bs->file, offset, bytes);
++}
++
++static coroutine_fn int track_co_flush(BlockDriverState *bs)
++{
++    return bdrv_co_flush(bs->file->bs);
++}
++
++static int coroutine_fn track_co_block_status(BlockDriverState *bs,
++                                              bool want_zero,
++                                              int64_t offset,
++                                              int64_t bytes,
++                                              int64_t *pnum,
++                                              int64_t *map,
++                                              BlockDriverState **file)
++{
++    BDRVAllocTrackState *s = bs->opaque;
++
++    bool alloc = bdrv_dirty_bitmap_get(s->bitmap, offset);
++    int64_t next_flipped;
++    if (alloc) {
++        next_flipped = bdrv_dirty_bitmap_next_zero(s->bitmap, offset, bytes);
++    } else {
++        next_flipped = bdrv_dirty_bitmap_next_dirty(s->bitmap, offset, bytes);
++    }
++
++    /* in case not the entire region has the same state, we need to set pnum to
++     * indicate for how many bytes our result is valid */
++    *pnum = next_flipped == -1 ? bytes : next_flipped - offset;
++    *map = offset;
++
++    if (alloc) {
++        *file = bs->file->bs;
++        return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID;
++    } else if (bs->backing) {
++        *file = bs->backing->bs;
++    }
++    return 0;
++}
++
++static void track_child_perm(BlockDriverState *bs, BdrvChild *c,
++                             BdrvChildRole role, BlockReopenQueue *reopen_queue,
++                             uint64_t perm, uint64_t shared,
++                             uint64_t *nperm, uint64_t *nshared)
++{
++    BDRVAllocTrackState *s = bs->opaque;
++
++    *nshared = BLK_PERM_ALL;
++
++    /* in case we're currently dropping ourselves, claim to not use any
++     * permissions at all - which is fine, since from this point on we will
++     * never issue a read or write anymore */
++    if (s->drop_state == DropInProgress) {
++        *nperm = 0;
++        return;
++    }
++
++    if (role & BDRV_CHILD_DATA) {
++        *nperm = perm & DEFAULT_PERM_PASSTHROUGH;
++    } else {
++        /* 'backing' is also a child of our BDS, but we don't expect it to be
++         * writeable, so we only forward 'consistent read' */
++        *nperm = perm & BLK_PERM_CONSISTENT_READ;
++    }
++}
++
++static void track_drop(void *opaque)
++{
++    BlockDriverState *bs = (BlockDriverState*)opaque;
++    BlockDriverState *file = bs->file->bs;
++    BDRVAllocTrackState *s = bs->opaque;
++
++    assert(file);
++
++    /* we rely on the fact that we're not used anywhere else, so let's wait
++     * until we're only used once - in the drive connected to the guest (and one
++     * ref is held by bdrv_ref in track_change_backing_file) */
++    if (bs->refcnt > 2) {
++        aio_bh_schedule_oneshot(qemu_get_aio_context(), track_drop, opaque);
++        return;
++    }
++    AioContext *aio_context = bdrv_get_aio_context(bs);
++    aio_context_acquire(aio_context);
++
++    bdrv_drained_begin(bs);
++
++    /* now that we're drained, we can safely set 'DropInProgress' */
++    s->drop_state = DropInProgress;
++    bdrv_child_refresh_perms(bs, bs->file, &error_abort);
++
++    bdrv_replace_node(bs, file, &error_abort);
++    bdrv_set_backing_hd(bs, NULL, &error_abort);
++    bdrv_drained_end(bs);
++    bdrv_unref(bs);
++    aio_context_release(aio_context);
++}
++
++static int track_change_backing_file(BlockDriverState *bs,
++                                     const char *backing_file,
++                                     const char *backing_fmt)
++{
++    BDRVAllocTrackState *s = bs->opaque;
++    if (s->auto_remove && s->drop_state == DropNone &&
++        backing_file == NULL && backing_fmt == NULL)
++    {
++        /* backing file has been disconnected, there's no longer any use for
++         * this node, so let's remove ourselves from the block graph - we need
++         * to schedule this for later however, since when this function is
++         * called, the blockjob modifying us is probably not done yet and has a
++         * blocker on 'bs' */
++        s->drop_state = DropRequested;
++        bdrv_ref(bs);
++        aio_bh_schedule_oneshot(qemu_get_aio_context(), track_drop, (void*)bs);
++    }
++
++    return 0;
++}
++
++static BlockDriver bdrv_alloc_track = {
++    .format_name                      = "alloc-track",
++    .instance_size                    = sizeof(BDRVAllocTrackState),
++
++    .bdrv_file_open                   = track_open,
++    .bdrv_close                       = track_close,
++    .bdrv_getlength                   = track_getlength,
++    .bdrv_child_perm                  = track_child_perm,
++    .bdrv_refresh_limits              = track_refresh_limits,
++
++    .bdrv_co_pwrite_zeroes            = track_co_pwrite_zeroes,
++    .bdrv_co_pwritev                  = track_co_pwritev,
++    .bdrv_co_preadv                   = track_co_preadv,
++    .bdrv_co_pdiscard                 = track_co_pdiscard,
++
++    .bdrv_co_flush                    = track_co_flush,
++    .bdrv_co_flush_to_disk            = track_co_flush,
++
++    .supports_backing                 = true,
++
++    .bdrv_co_block_status             = track_co_block_status,
++    .bdrv_change_backing_file         = track_change_backing_file,
++};
++
++static void bdrv_alloc_track_init(void)
++{
++    bdrv_register(&bdrv_alloc_track);
++}
++
++block_init(bdrv_alloc_track_init);
+diff --git a/block/meson.build b/block/meson.build
+index 8c758c0218..45b72e10f1 100644
+--- a/block/meson.build
++++ b/block/meson.build
+@@ -2,6 +2,7 @@ block_ss.add(genh)
+ block_ss.add(files(
+   'accounting.c',
+   'aio_task.c',
++  'alloc-track.c',
+   'amend.c',
+   'backup.c',
+   'backup-dump.c',
diff --git a/debian/patches/pve/0048-PVE-savevm-async-register-yank-before-migration_inco.patch b/debian/patches/pve/0048-PVE-savevm-async-register-yank-before-migration_inco.patch
deleted file mode 100644 (file)
index 1359424..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Wed, 26 May 2021 17:36:55 +0200
-Subject: [PATCH] PVE: savevm-async: register yank before
- migration_incoming_state_destroy
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- migration/savevm-async.c | 5 +++++
- 1 file changed, 5 insertions(+)
-
-diff --git a/migration/savevm-async.c b/migration/savevm-async.c
-index 970ee3b3fc..b3ccc069f1 100644
---- a/migration/savevm-async.c
-+++ b/migration/savevm-async.c
-@@ -19,6 +19,7 @@
- #include "qemu/timer.h"
- #include "qemu/main-loop.h"
- #include "qemu/rcu.h"
-+#include "qemu/yank.h"
- /* #define DEBUG_SAVEVM_STATE */
-@@ -580,6 +581,10 @@ int load_snapshot_from_blockdev(const char *filename, Error **errp)
-     dirty_bitmap_mig_before_vm_start();
-     qemu_fclose(f);
-+
-+    /* state_destroy assumes a real migration which would have added a yank */
-+    yank_register_instance(MIGRATION_YANK_INSTANCE, &error_abort);
-+
-     migration_incoming_state_destroy();
-     if (ret < 0) {
-         error_setg_errno(errp, -ret, "Error while loading VM state");
diff --git a/debian/patches/pve/0048-PVE-whitelist-invalid-QAPI-names-for-backwards-compa.patch b/debian/patches/pve/0048-PVE-whitelist-invalid-QAPI-names-for-backwards-compa.patch
new file mode 100644 (file)
index 0000000..ced1323
--- /dev/null
@@ -0,0 +1,33 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Wed, 26 May 2021 15:26:30 +0200
+Subject: [PATCH] PVE: whitelist 'invalid' QAPI names for backwards compat
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ qapi/pragma.json | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/qapi/pragma.json b/qapi/pragma.json
+index 7c91ea3685..c3888d654c 100644
+--- a/qapi/pragma.json
++++ b/qapi/pragma.json
+@@ -12,6 +12,7 @@
+         'device_add',
+         'device_del',
+         'expire_password',
++        'get_link_status',
+         'migrate_cancel',
+         'netdev_add',
+         'netdev_del',
+@@ -60,6 +61,8 @@
+         'SysEmuTarget',             # query-cpu-fast, query-target
+         'UuidInfo',                 # query-uuid
+         'VncClientInfo',            # query-vnc, query-vnc-servers, ...
+-        'X86CPURegister32'          # qom-get of x86 CPU properties
++        'X86CPURegister32',         # qom-get of x86 CPU properties
+                                     # feature-words, filtered-features
++        'BlockdevOptionsPbs',       # for PBS backwards compat
++        'BalloonInfo'
+     ] } }
diff --git a/debian/patches/pve/0049-PVE-savevm-async-register-yank-before-migration_inco.patch b/debian/patches/pve/0049-PVE-savevm-async-register-yank-before-migration_inco.patch
new file mode 100644 (file)
index 0000000..1359424
--- /dev/null
@@ -0,0 +1,35 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Stefan Reiter <s.reiter@proxmox.com>
+Date: Wed, 26 May 2021 17:36:55 +0200
+Subject: [PATCH] PVE: savevm-async: register yank before
+ migration_incoming_state_destroy
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ migration/savevm-async.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/migration/savevm-async.c b/migration/savevm-async.c
+index 970ee3b3fc..b3ccc069f1 100644
+--- a/migration/savevm-async.c
++++ b/migration/savevm-async.c
+@@ -19,6 +19,7 @@
+ #include "qemu/timer.h"
+ #include "qemu/main-loop.h"
+ #include "qemu/rcu.h"
++#include "qemu/yank.h"
+ /* #define DEBUG_SAVEVM_STATE */
+@@ -580,6 +581,10 @@ int load_snapshot_from_blockdev(const char *filename, Error **errp)
+     dirty_bitmap_mig_before_vm_start();
+     qemu_fclose(f);
++
++    /* state_destroy assumes a real migration which would have added a yank */
++    yank_register_instance(MIGRATION_YANK_INSTANCE, &error_abort);
++
+     migration_incoming_state_destroy();
+     if (ret < 0) {
+         error_setg_errno(errp, -ret, "Error while loading VM state");
diff --git a/debian/patches/pve/0049-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch b/debian/patches/pve/0049-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch
deleted file mode 100644 (file)
index f239cd4..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fabian Ebner <f.ebner@proxmox.com>
-Date: Mon, 7 Feb 2022 14:21:01 +0100
-Subject: [PATCH] qemu-img: dd: add -l option for loading a snapshot
-
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
----
- docs/tools/qemu-img.rst |  6 +++---
- qemu-img-cmds.hx        |  4 ++--
- qemu-img.c              | 33 +++++++++++++++++++++++++++++++--
- 3 files changed, 36 insertions(+), 7 deletions(-)
-
-diff --git a/docs/tools/qemu-img.rst b/docs/tools/qemu-img.rst
-index a49badb158..1039aec01c 100644
---- a/docs/tools/qemu-img.rst
-+++ b/docs/tools/qemu-img.rst
-@@ -492,10 +492,10 @@ Command description:
-   it doesn't need to be specified separately in this case.
--.. option:: dd [--image-opts] [-U] [-f FMT] [-O OUTPUT_FMT] [-n] [bs=BLOCK_SIZE] [count=BLOCKS] [skip=BLOCKS] if=INPUT of=OUTPUT
-+.. option:: dd [--image-opts] [-U] [-f FMT] [-O OUTPUT_FMT] [-n] [-l SNAPSHOT_PARAM] [bs=BLOCK_SIZE] [count=BLOCKS] [skip=BLOCKS] if=INPUT of=OUTPUT
--  dd copies from *INPUT* file to *OUTPUT* file converting it from
--  *FMT* format to *OUTPUT_FMT* format.
-+  dd copies from *INPUT* file or snapshot *SNAPSHOT_PARAM* to *OUTPUT* file
-+  converting it from *FMT* format to *OUTPUT_FMT* format.
-   The data is by default read and written using blocks of 512 bytes but can be
-   modified by specifying *BLOCK_SIZE*. If count=\ *BLOCKS* is specified
-diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx
-index f3b2b1b4de..e77ed9347f 100644
---- a/qemu-img-cmds.hx
-+++ b/qemu-img-cmds.hx
-@@ -58,9 +58,9 @@ SRST
- ERST
- DEF("dd", img_dd,
--    "dd [--image-opts] [-U] [-f fmt] [-O output_fmt] [-n] [bs=block_size] [count=blocks] [skip=blocks] [osize=output_size] if=input of=output")
-+    "dd [--image-opts] [-U] [-f fmt] [-O output_fmt] [-n] [-l snapshot_param] [bs=block_size] [count=blocks] [skip=blocks] [osize=output_size] if=input of=output")
- SRST
--.. option:: dd [--image-opts] [-U] [-f FMT] [-O OUTPUT_FMT] [-n] [bs=BLOCK_SIZE] [count=BLOCKS] [skip=BLOCKS] [osize=OUTPUT_SIZE] if=INPUT of=OUTPUT
-+.. option:: dd [--image-opts] [-U] [-f FMT] [-O OUTPUT_FMT] [-n] [-l SNAPSHOT_PARAM] [bs=BLOCK_SIZE] [count=BLOCKS] [skip=BLOCKS] [osize=OUTPUT_SIZE] if=INPUT of=OUTPUT
- ERST
- DEF("info", img_info,
-diff --git a/qemu-img.c b/qemu-img.c
-index 015d6d2ce4..7031195e32 100644
---- a/qemu-img.c
-+++ b/qemu-img.c
-@@ -4922,6 +4922,7 @@ static int img_dd(int argc, char **argv)
-     BlockDriver *drv = NULL, *proto_drv = NULL;
-     BlockBackend *blk1 = NULL, *blk2 = NULL;
-     QemuOpts *opts = NULL;
-+    QemuOpts *sn_opts = NULL;
-     QemuOptsList *create_opts = NULL;
-     Error *local_err = NULL;
-     bool image_opts = false;
-@@ -4931,6 +4932,7 @@ static int img_dd(int argc, char **argv)
-     int64_t size = 0, readsize = 0;
-     int64_t block_count = 0, out_pos, in_pos;
-     bool force_share = false, skip_create = false;
-+    const char *snapshot_name = NULL;
-     struct DdInfo dd = {
-         .flags = 0,
-         .count = 0,
-@@ -4968,7 +4970,7 @@ static int img_dd(int argc, char **argv)
-         { 0, 0, 0, 0 }
-     };
--    while ((c = getopt_long(argc, argv, ":hf:O:Un", long_options, NULL))) {
-+    while ((c = getopt_long(argc, argv, ":hf:O:l:Un", long_options, NULL))) {
-         if (c == EOF) {
-             break;
-         }
-@@ -4991,6 +4993,19 @@ static int img_dd(int argc, char **argv)
-         case 'n':
-             skip_create = true;
-             break;
-+        case 'l':
-+            if (strstart(optarg, SNAPSHOT_OPT_BASE, NULL)) {
-+                sn_opts = qemu_opts_parse_noisily(&internal_snapshot_opts,
-+                                                  optarg, false);
-+                if (!sn_opts) {
-+                    error_report("Failed in parsing snapshot param '%s'",
-+                                 optarg);
-+                    goto out;
-+                }
-+            } else {
-+                snapshot_name = optarg;
-+            }
-+            break;
-         case 'U':
-             force_share = true;
-             break;
-@@ -5050,11 +5065,24 @@ static int img_dd(int argc, char **argv)
-     if (dd.flags & C_IF) {
-         blk1 = img_open(image_opts, in.filename, fmt, 0, false, false,
-                         force_share);
--
-         if (!blk1) {
-             ret = -1;
-             goto out;
-         }
-+        if (sn_opts) {
-+            bdrv_snapshot_load_tmp(blk_bs(blk1),
-+                                   qemu_opt_get(sn_opts, SNAPSHOT_OPT_ID),
-+                                   qemu_opt_get(sn_opts, SNAPSHOT_OPT_NAME),
-+                                   &local_err);
-+        } else if (snapshot_name != NULL) {
-+            bdrv_snapshot_load_tmp_by_id_or_name(blk_bs(blk1), snapshot_name,
-+                                                 &local_err);
-+        }
-+        if (local_err) {
-+            error_reportf_err(local_err, "Failed to load snapshot: ");
-+            ret = -1;
-+            goto out;
-+        }
-     }
-     if (dd.flags & C_OSIZE) {
-@@ -5203,6 +5231,7 @@ static int img_dd(int argc, char **argv)
- out:
-     g_free(arg);
-     qemu_opts_del(opts);
-+    qemu_opts_del(sn_opts);
-     qemu_opts_free(create_opts);
-     blk_unref(blk1);
-     blk_unref(blk2);
diff --git a/debian/patches/pve/0050-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch b/debian/patches/pve/0050-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch
new file mode 100644 (file)
index 0000000..f239cd4
--- /dev/null
@@ -0,0 +1,129 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Fabian Ebner <f.ebner@proxmox.com>
+Date: Mon, 7 Feb 2022 14:21:01 +0100
+Subject: [PATCH] qemu-img: dd: add -l option for loading a snapshot
+
+Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
+---
+ docs/tools/qemu-img.rst |  6 +++---
+ qemu-img-cmds.hx        |  4 ++--
+ qemu-img.c              | 33 +++++++++++++++++++++++++++++++--
+ 3 files changed, 36 insertions(+), 7 deletions(-)
+
+diff --git a/docs/tools/qemu-img.rst b/docs/tools/qemu-img.rst
+index a49badb158..1039aec01c 100644
+--- a/docs/tools/qemu-img.rst
++++ b/docs/tools/qemu-img.rst
+@@ -492,10 +492,10 @@ Command description:
+   it doesn't need to be specified separately in this case.
+-.. option:: dd [--image-opts] [-U] [-f FMT] [-O OUTPUT_FMT] [-n] [bs=BLOCK_SIZE] [count=BLOCKS] [skip=BLOCKS] if=INPUT of=OUTPUT
++.. option:: dd [--image-opts] [-U] [-f FMT] [-O OUTPUT_FMT] [-n] [-l SNAPSHOT_PARAM] [bs=BLOCK_SIZE] [count=BLOCKS] [skip=BLOCKS] if=INPUT of=OUTPUT
+-  dd copies from *INPUT* file to *OUTPUT* file converting it from
+-  *FMT* format to *OUTPUT_FMT* format.
++  dd copies from *INPUT* file or snapshot *SNAPSHOT_PARAM* to *OUTPUT* file
++  converting it from *FMT* format to *OUTPUT_FMT* format.
+   The data is by default read and written using blocks of 512 bytes but can be
+   modified by specifying *BLOCK_SIZE*. If count=\ *BLOCKS* is specified
+diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx
+index f3b2b1b4de..e77ed9347f 100644
+--- a/qemu-img-cmds.hx
++++ b/qemu-img-cmds.hx
+@@ -58,9 +58,9 @@ SRST
+ ERST
+ DEF("dd", img_dd,
+-    "dd [--image-opts] [-U] [-f fmt] [-O output_fmt] [-n] [bs=block_size] [count=blocks] [skip=blocks] [osize=output_size] if=input of=output")
++    "dd [--image-opts] [-U] [-f fmt] [-O output_fmt] [-n] [-l snapshot_param] [bs=block_size] [count=blocks] [skip=blocks] [osize=output_size] if=input of=output")
+ SRST
+-.. option:: dd [--image-opts] [-U] [-f FMT] [-O OUTPUT_FMT] [-n] [bs=BLOCK_SIZE] [count=BLOCKS] [skip=BLOCKS] [osize=OUTPUT_SIZE] if=INPUT of=OUTPUT
++.. option:: dd [--image-opts] [-U] [-f FMT] [-O OUTPUT_FMT] [-n] [-l SNAPSHOT_PARAM] [bs=BLOCK_SIZE] [count=BLOCKS] [skip=BLOCKS] [osize=OUTPUT_SIZE] if=INPUT of=OUTPUT
+ ERST
+ DEF("info", img_info,
+diff --git a/qemu-img.c b/qemu-img.c
+index 015d6d2ce4..7031195e32 100644
+--- a/qemu-img.c
++++ b/qemu-img.c
+@@ -4922,6 +4922,7 @@ static int img_dd(int argc, char **argv)
+     BlockDriver *drv = NULL, *proto_drv = NULL;
+     BlockBackend *blk1 = NULL, *blk2 = NULL;
+     QemuOpts *opts = NULL;
++    QemuOpts *sn_opts = NULL;
+     QemuOptsList *create_opts = NULL;
+     Error *local_err = NULL;
+     bool image_opts = false;
+@@ -4931,6 +4932,7 @@ static int img_dd(int argc, char **argv)
+     int64_t size = 0, readsize = 0;
+     int64_t block_count = 0, out_pos, in_pos;
+     bool force_share = false, skip_create = false;
++    const char *snapshot_name = NULL;
+     struct DdInfo dd = {
+         .flags = 0,
+         .count = 0,
+@@ -4968,7 +4970,7 @@ static int img_dd(int argc, char **argv)
+         { 0, 0, 0, 0 }
+     };
+-    while ((c = getopt_long(argc, argv, ":hf:O:Un", long_options, NULL))) {
++    while ((c = getopt_long(argc, argv, ":hf:O:l:Un", long_options, NULL))) {
+         if (c == EOF) {
+             break;
+         }
+@@ -4991,6 +4993,19 @@ static int img_dd(int argc, char **argv)
+         case 'n':
+             skip_create = true;
+             break;
++        case 'l':
++            if (strstart(optarg, SNAPSHOT_OPT_BASE, NULL)) {
++                sn_opts = qemu_opts_parse_noisily(&internal_snapshot_opts,
++                                                  optarg, false);
++                if (!sn_opts) {
++                    error_report("Failed in parsing snapshot param '%s'",
++                                 optarg);
++                    goto out;
++                }
++            } else {
++                snapshot_name = optarg;
++            }
++            break;
+         case 'U':
+             force_share = true;
+             break;
+@@ -5050,11 +5065,24 @@ static int img_dd(int argc, char **argv)
+     if (dd.flags & C_IF) {
+         blk1 = img_open(image_opts, in.filename, fmt, 0, false, false,
+                         force_share);
+-
+         if (!blk1) {
+             ret = -1;
+             goto out;
+         }
++        if (sn_opts) {
++            bdrv_snapshot_load_tmp(blk_bs(blk1),
++                                   qemu_opt_get(sn_opts, SNAPSHOT_OPT_ID),
++                                   qemu_opt_get(sn_opts, SNAPSHOT_OPT_NAME),
++                                   &local_err);
++        } else if (snapshot_name != NULL) {
++            bdrv_snapshot_load_tmp_by_id_or_name(blk_bs(blk1), snapshot_name,
++                                                 &local_err);
++        }
++        if (local_err) {
++            error_reportf_err(local_err, "Failed to load snapshot: ");
++            ret = -1;
++            goto out;
++        }
+     }
+     if (dd.flags & C_OSIZE) {
+@@ -5203,6 +5231,7 @@ static int img_dd(int argc, char **argv)
+ out:
+     g_free(arg);
+     qemu_opts_del(opts);
++    qemu_opts_del(sn_opts);
+     qemu_opts_free(create_opts);
+     blk_unref(blk1);
+     blk_unref(blk2);
index a02a2d70e10433090304a962f200bdd577102805..88d23329c4c865a612c62699feb9a2b4f7a53417 100644 (file)
@@ -37,28 +37,29 @@ pve/0021-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch
 pve/0022-PVE-monitor-disable-oob-capability.patch
 pve/0023-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch
 pve/0024-PVE-Allow-version-code-in-machine-type.patch
-pve/0025-PVE-Backup-add-vma-backup-format-code.patch
-pve/0026-PVE-Backup-add-backup-dump-block-driver.patch
-pve/0027-PVE-Backup-proxmox-backup-patches-for-qemu.patch
-pve/0028-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch
-pve/0029-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch
-pve/0030-PVE-various-PBS-fixes.patch
-pve/0031-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch
-pve/0032-PVE-add-query_proxmox_support-QMP-command.patch
-pve/0033-PVE-add-query-pbs-bitmap-info-QMP-call.patch
-pve/0034-PVE-redirect-stderr-to-journal-when-daemonized.patch
-pve/0035-PVE-Add-sequential-job-transaction-support.patch
-pve/0036-PVE-Backup-Use-a-transaction-to-synchronize-job-stat.patch
-pve/0037-PVE-Backup-Don-t-block-on-finishing-and-cleanup-crea.patch
-pve/0038-PVE-Migrate-dirty-bitmap-state-via-savevm.patch
-pve/0039-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch
-pve/0040-PVE-fall-back-to-open-iscsi-initiatorname.patch
-pve/0041-PVE-Use-coroutine-QMP-for-backup-cancel_backup.patch
-pve/0042-PBS-add-master-key-support.patch
-pve/0043-PVE-block-pbs-fast-path-reads-without-allocation-if-.patch
-pve/0044-PVE-block-stream-increase-chunk-size.patch
-pve/0045-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch
-pve/0046-block-add-alloc-track-driver.patch
-pve/0047-PVE-whitelist-invalid-QAPI-names-for-backwards-compa.patch
-pve/0048-PVE-savevm-async-register-yank-before-migration_inco.patch
-pve/0049-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch
+pve/0025-block-backup-move-bcs-bitmap-initialization-to-job-c.patch
+pve/0026-PVE-Backup-add-vma-backup-format-code.patch
+pve/0027-PVE-Backup-add-backup-dump-block-driver.patch
+pve/0028-PVE-Backup-proxmox-backup-patches-for-qemu.patch
+pve/0029-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch
+pve/0030-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch
+pve/0031-PVE-various-PBS-fixes.patch
+pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch
+pve/0033-PVE-add-query_proxmox_support-QMP-command.patch
+pve/0034-PVE-add-query-pbs-bitmap-info-QMP-call.patch
+pve/0035-PVE-redirect-stderr-to-journal-when-daemonized.patch
+pve/0036-PVE-Add-sequential-job-transaction-support.patch
+pve/0037-PVE-Backup-Use-a-transaction-to-synchronize-job-stat.patch
+pve/0038-PVE-Backup-Don-t-block-on-finishing-and-cleanup-crea.patch
+pve/0039-PVE-Migrate-dirty-bitmap-state-via-savevm.patch
+pve/0040-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch
+pve/0041-PVE-fall-back-to-open-iscsi-initiatorname.patch
+pve/0042-PVE-Use-coroutine-QMP-for-backup-cancel_backup.patch
+pve/0043-PBS-add-master-key-support.patch
+pve/0044-PVE-block-pbs-fast-path-reads-without-allocation-if-.patch
+pve/0045-PVE-block-stream-increase-chunk-size.patch
+pve/0046-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch
+pve/0047-block-add-alloc-track-driver.patch
+pve/0048-PVE-whitelist-invalid-QAPI-names-for-backwards-compa.patch
+pve/0049-PVE-savevm-async-register-yank-before-migration_inco.patch
+pve/0050-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch