]> git.proxmox.com Git - pve-qemu.git/commitdiff
squash related patches
authorFiona Ebner <f.ebner@proxmox.com>
Mon, 15 May 2023 13:39:56 +0000 (15:39 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Mon, 22 May 2023 13:09:14 +0000 (15:09 +0200)
where there is no good reason to keep them separate. It's a pain
during rebase if there are multiple patches changing the same code
over and over again. This was especially bad for the backup-related
patches. If the history of patches really is needed, it can be
extracted via git. Additionally, compilation with partial application
of patches was broken since a long time, because one of the master key
changes became part of an earlier patch during a past rebase.

If only the same files were changed by a subsequent patch and the
changes felt to belong together (obvious for later bug fixes, but also
done for features e.g. adding master key support for PBS), the patches
were squashed together.

The PBS namespace support patch was split into the individual parts
it changes, i.e. PBS block driver, pbs-restore binary and QMP backup
infrastructure, and squashed into the respective patches.

No code change is intended, git diff in the submodule should not show
any difference between applying all patches before this commit and
applying all patches after this commit.

The query-proxmox-support QMP function has been left as part of the
"PVE-Backup: Proxmox backup patches for QEMU" patch, because it's
currently only used there. If it ever is used elsewhere too, it can
be split out from there.

The recent alloc-track and BQL-related savevm-async changes have been
left separate for now, because it's not 100% clear they are the best
approach yet. This depends on what upstream decides about the BQL
stuff and whether and what kind of issues with the changes pop up.

The qemu-img dd snapshot patch has been re-ordered to after the other
qemu-img dd patches.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
89 files changed:
debian/patches/pve/0013-PVE-virtio-balloon-improve-query-balloon.patch [deleted file]
debian/patches/pve/0013-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch [new file with mode: 0644]
debian/patches/pve/0014-PVE-qapi-modify-query-machines.patch [deleted file]
debian/patches/pve/0014-PVE-virtio-balloon-improve-query-balloon.patch [new file with mode: 0644]
debian/patches/pve/0015-PVE-qapi-modify-query-machines.patch [new file with mode: 0644]
debian/patches/pve/0015-PVE-qapi-modify-spice-query.patch [deleted file]
debian/patches/pve/0016-PVE-add-IOChannel-implementation-for-savevm-async.patch [deleted file]
debian/patches/pve/0016-PVE-qapi-modify-spice-query.patch [new file with mode: 0644]
debian/patches/pve/0017-PVE-add-IOChannel-implementation-for-savevm-async.patch [new file with mode: 0644]
debian/patches/pve/0017-PVE-add-savevm-async-for-background-state-snapshots.patch [deleted file]
debian/patches/pve/0018-PVE-add-optional-buffer-size-to-QEMUFile.patch [deleted file]
debian/patches/pve/0018-PVE-add-savevm-async-for-background-state-snapshots.patch [new file with mode: 0644]
debian/patches/pve/0019-PVE-add-optional-buffer-size-to-QEMUFile.patch [new file with mode: 0644]
debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch [deleted file]
debian/patches/pve/0020-PVE-Add-dummy-id-command-line-parameter.patch [deleted file]
debian/patches/pve/0020-PVE-block-add-the-zeroinit-block-driver-filter.patch [new file with mode: 0644]
debian/patches/pve/0021-PVE-Add-dummy-id-command-line-parameter.patch [new file with mode: 0644]
debian/patches/pve/0021-PVE-Config-Revert-target-i386-disable-LINT0-after-re.patch [deleted file]
debian/patches/pve/0022-PVE-Config-Revert-target-i386-disable-LINT0-after-re.patch [new file with mode: 0644]
debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch [deleted file]
debian/patches/pve/0023-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch [new file with mode: 0644]
debian/patches/pve/0023-PVE-monitor-disable-oob-capability.patch [deleted file]
debian/patches/pve/0024-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch [deleted file]
debian/patches/pve/0024-PVE-monitor-disable-oob-capability.patch [new file with mode: 0644]
debian/patches/pve/0025-PVE-Allow-version-code-in-machine-type.patch [deleted file]
debian/patches/pve/0025-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch [new file with mode: 0644]
debian/patches/pve/0026-PVE-Allow-version-code-in-machine-type.patch [new file with mode: 0644]
debian/patches/pve/0026-block-backup-move-bcs-bitmap-initialization-to-job-c.patch [deleted file]
debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch [deleted file]
debian/patches/pve/0027-block-backup-move-bcs-bitmap-initialization-to-job-c.patch [new file with mode: 0644]
debian/patches/pve/0028-PVE-Backup-add-backup-dump-block-driver.patch [deleted file]
debian/patches/pve/0028-PVE-Backup-add-vma-backup-format-code.patch [new file with mode: 0644]
debian/patches/pve/0029-PVE-Backup-add-backup-dump-block-driver.patch [new file with mode: 0644]
debian/patches/pve/0029-PVE-Backup-proxmox-backup-patches-for-qemu.patch [deleted file]
debian/patches/pve/0030-PVE-Add-sequential-job-transaction-support.patch [new file with mode: 0644]
debian/patches/pve/0030-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch [deleted file]
debian/patches/pve/0031-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch [deleted file]
debian/patches/pve/0031-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch [new file with mode: 0644]
debian/patches/pve/0032-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch [new file with mode: 0644]
debian/patches/pve/0032-PVE-various-PBS-fixes.patch [deleted file]
debian/patches/pve/0033-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch
debian/patches/pve/0034-PVE-add-query_proxmox_support-QMP-command.patch [deleted file]
debian/patches/pve/0034-PVE-redirect-stderr-to-journal-when-daemonized.patch [new file with mode: 0644]
debian/patches/pve/0035-PVE-Migrate-dirty-bitmap-state-via-savevm.patch [new file with mode: 0644]
debian/patches/pve/0035-PVE-add-query-pbs-bitmap-info-QMP-call.patch [deleted file]
debian/patches/pve/0036-PVE-redirect-stderr-to-journal-when-daemonized.patch [deleted file]
debian/patches/pve/0036-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch [new file with mode: 0644]
debian/patches/pve/0037-PVE-Add-sequential-job-transaction-support.patch [deleted file]
debian/patches/pve/0037-PVE-fall-back-to-open-iscsi-initiatorname.patch [new file with mode: 0644]
debian/patches/pve/0038-PVE-Backup-Use-a-transaction-to-synchronize-job-stat.patch [deleted file]
debian/patches/pve/0038-PVE-block-stream-increase-chunk-size.patch [new file with mode: 0644]
debian/patches/pve/0039-PVE-Backup-Don-t-block-on-finishing-and-cleanup-crea.patch [deleted file]
debian/patches/pve/0039-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch [new file with mode: 0644]
debian/patches/pve/0040-PVE-Migrate-dirty-bitmap-state-via-savevm.patch [deleted file]
debian/patches/pve/0040-block-add-alloc-track-driver.patch [new file with mode: 0644]
debian/patches/pve/0041-Revert-block-rbd-workaround-for-ceph-issue-53784.patch [new file with mode: 0644]
debian/patches/pve/0041-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch [deleted file]
debian/patches/pve/0042-PVE-fall-back-to-open-iscsi-initiatorname.patch [deleted file]
debian/patches/pve/0042-Revert-block-rbd-fix-handling-of-holes-in-.bdrv_co_b.patch [new file with mode: 0644]
debian/patches/pve/0043-PVE-Use-coroutine-QMP-for-backup-cancel_backup.patch [deleted file]
debian/patches/pve/0043-Revert-block-rbd-implement-bdrv_co_block_status.patch [new file with mode: 0644]
debian/patches/pve/0044-PBS-add-master-key-support.patch [deleted file]
debian/patches/pve/0044-alloc-track-fix-deadlock-during-drop.patch [new file with mode: 0644]
debian/patches/pve/0045-PVE-block-pbs-fast-path-reads-without-allocation-if-.patch [deleted file]
debian/patches/pve/0045-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch [new file with mode: 0644]
debian/patches/pve/0046-PVE-block-stream-increase-chunk-size.patch [deleted file]
debian/patches/pve/0046-savevm-async-don-t-hold-BQL-during-setup.patch [new file with mode: 0644]
debian/patches/pve/0047-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch [deleted file]
debian/patches/pve/0048-block-add-alloc-track-driver.patch [deleted file]
debian/patches/pve/0049-PVE-savevm-async-register-yank-before-migration_inco.patch [deleted file]
debian/patches/pve/0050-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch [deleted file]
debian/patches/pve/0051-vma-allow-partial-restore.patch [deleted file]
debian/patches/pve/0052-pbs-namespace-support.patch [deleted file]
debian/patches/pve/0053-Revert-block-rbd-workaround-for-ceph-issue-53784.patch [deleted file]
debian/patches/pve/0054-Revert-block-rbd-fix-handling-of-holes-in-.bdrv_co_b.patch [deleted file]
debian/patches/pve/0055-Revert-block-rbd-implement-bdrv_co_block_status.patch [deleted file]
debian/patches/pve/0056-PVE-Backup-create-jobs-correctly-cancel-in-error-sce.patch [deleted file]
debian/patches/pve/0057-PVE-Backup-ensure-jobs-in-di_list-are-referenced.patch [deleted file]
debian/patches/pve/0058-PVE-Backup-avoid-segfault-issues-upon-backup-cancel.patch [deleted file]
debian/patches/pve/0059-vma-create-support-64KiB-unaligned-input-images.patch [deleted file]
debian/patches/pve/0060-vma-create-avoid-triggering-assertion-in-error-case.patch [deleted file]
debian/patches/pve/0061-block-alloc-track-avoid-premature-break.patch [deleted file]
debian/patches/pve/0062-PVE-Backup-allow-passing-max-workers-performance-set.patch [deleted file]
debian/patches/pve/0063-alloc-track-fix-deadlock-during-drop.patch [deleted file]
debian/patches/pve/0064-savevm-async-optimize-querying-pending.patch [deleted file]
debian/patches/pve/0065-savevm-async-also-initialize-compression-counters.patch [deleted file]
debian/patches/pve/0066-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch [deleted file]
debian/patches/pve/0067-savevm-async-don-t-hold-BQL-during-setup.patch [deleted file]
debian/patches/series

diff --git a/debian/patches/pve/0013-PVE-virtio-balloon-improve-query-balloon.patch b/debian/patches/pve/0013-PVE-virtio-balloon-improve-query-balloon.patch
deleted file mode 100644 (file)
index 183c9dc..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:43 +0200
-Subject: [PATCH] PVE: virtio-balloon: improve query-balloon
-
-Actually provide memory information via the query-balloon
-command.
-
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-[FE: add BalloonInfo to member name exceptions list
-     rebase for 8.0 - moved to hw/core/machine-hmp-cmds.c]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- hw/core/machine-hmp-cmds.c | 30 +++++++++++++++++++++++++++++-
- hw/virtio/virtio-balloon.c | 33 +++++++++++++++++++++++++++++++--
- qapi/machine.json          | 22 +++++++++++++++++++++-
- qapi/pragma.json           |  1 +
- 4 files changed, 82 insertions(+), 4 deletions(-)
-
-diff --git a/hw/core/machine-hmp-cmds.c b/hw/core/machine-hmp-cmds.c
-index c3e55ef9e9..0e32e6201f 100644
---- a/hw/core/machine-hmp-cmds.c
-+++ b/hw/core/machine-hmp-cmds.c
-@@ -169,7 +169,35 @@ void hmp_info_balloon(Monitor *mon, const QDict *qdict)
-         return;
-     }
--    monitor_printf(mon, "balloon: actual=%" PRId64 "\n", info->actual >> 20);
-+    monitor_printf(mon, "balloon: actual=%" PRId64, info->actual >> 20);
-+    monitor_printf(mon, " max_mem=%" PRId64, info->max_mem >> 20);
-+    if (info->has_total_mem) {
-+        monitor_printf(mon, " total_mem=%" PRId64, info->total_mem >> 20);
-+    }
-+    if (info->has_free_mem) {
-+        monitor_printf(mon, " free_mem=%" PRId64, info->free_mem >> 20);
-+    }
-+
-+    if (info->has_mem_swapped_in) {
-+        monitor_printf(mon, " mem_swapped_in=%" PRId64, info->mem_swapped_in);
-+    }
-+    if (info->has_mem_swapped_out) {
-+        monitor_printf(mon, " mem_swapped_out=%" PRId64, info->mem_swapped_out);
-+    }
-+    if (info->has_major_page_faults) {
-+        monitor_printf(mon, " major_page_faults=%" PRId64,
-+                       info->major_page_faults);
-+    }
-+    if (info->has_minor_page_faults) {
-+        monitor_printf(mon, " minor_page_faults=%" PRId64,
-+                       info->minor_page_faults);
-+    }
-+    if (info->has_last_update) {
-+        monitor_printf(mon, " last_update=%" PRId64,
-+                       info->last_update);
-+    }
-+
-+    monitor_printf(mon, "\n");
-     qapi_free_BalloonInfo(info);
- }
-diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
-index 746f07c4d2..a41854b902 100644
---- a/hw/virtio/virtio-balloon.c
-+++ b/hw/virtio/virtio-balloon.c
-@@ -804,8 +804,37 @@ static uint64_t virtio_balloon_get_features(VirtIODevice *vdev, uint64_t f,
- static void virtio_balloon_stat(void *opaque, BalloonInfo *info)
- {
-     VirtIOBalloon *dev = opaque;
--    info->actual = get_current_ram_size() - ((uint64_t) dev->actual <<
--                                             VIRTIO_BALLOON_PFN_SHIFT);
-+    ram_addr_t ram_size = get_current_ram_size();
-+    info->actual = ram_size - ((uint64_t) dev->actual <<
-+                               VIRTIO_BALLOON_PFN_SHIFT);
-+
-+    info->max_mem = ram_size;
-+
-+    if (!(balloon_stats_enabled(dev) && balloon_stats_supported(dev) &&
-+           dev->stats_last_update)) {
-+       return;
-+    }
-+
-+    info->last_update = dev->stats_last_update;
-+    info->has_last_update = true;
-+
-+    info->mem_swapped_in = dev->stats[VIRTIO_BALLOON_S_SWAP_IN];
-+    info->has_mem_swapped_in = info->mem_swapped_in >= 0 ? true : false;
-+
-+    info->mem_swapped_out = dev->stats[VIRTIO_BALLOON_S_SWAP_OUT];
-+    info->has_mem_swapped_out = info->mem_swapped_out >= 0 ? true : false;
-+
-+    info->major_page_faults = dev->stats[VIRTIO_BALLOON_S_MAJFLT];
-+    info->has_major_page_faults = info->major_page_faults >= 0 ? true : false;
-+
-+    info->minor_page_faults = dev->stats[VIRTIO_BALLOON_S_MINFLT];
-+    info->has_minor_page_faults = info->minor_page_faults >= 0 ? true : false;
-+
-+    info->free_mem = dev->stats[VIRTIO_BALLOON_S_MEMFREE];
-+    info->has_free_mem = info->free_mem >= 0 ? true : false;
-+
-+    info->total_mem = dev->stats[VIRTIO_BALLOON_S_MEMTOT];
-+    info->has_total_mem = info->total_mem >= 0 ? true : false;
- }
- static void virtio_balloon_to_target(void *opaque, ram_addr_t target)
-diff --git a/qapi/machine.json b/qapi/machine.json
-index 604b686e59..15f5f86683 100644
---- a/qapi/machine.json
-+++ b/qapi/machine.json
-@@ -1056,9 +1056,29 @@
- # @actual: the logical size of the VM in bytes
- #          Formula used: logical_vm_size = vm_ram_size - balloon_size
- #
-+# @last_update: time when stats got updated from guest
-+#
-+# @mem_swapped_in: number of pages swapped in within the guest
-+# 
-+# @mem_swapped_out: number of pages swapped out within the guest
-+#
-+# @major_page_faults: number of major page faults within the guest
-+#
-+# @minor_page_faults: number of minor page faults within the guest
-+# 
-+# @free_mem: amount of memory (in bytes) free in the guest
-+#
-+# @total_mem: amount of memory (in bytes) visible to the guest
-+#
-+# @max_mem: amount of memory (in bytes) assigned to the guest
-+#
- # Since: 0.14
- ##
--{ 'struct': 'BalloonInfo', 'data': {'actual': 'int' } }
-+{ 'struct': 'BalloonInfo',
-+  'data': {'actual': 'int', '*last_update': 'int', '*mem_swapped_in': 'int',
-+           '*mem_swapped_out': 'int', '*major_page_faults': 'int',
-+           '*minor_page_faults': 'int', '*free_mem': 'int',
-+           '*total_mem': 'int', 'max_mem': 'int' } }
- ##
- # @query-balloon:
-diff --git a/qapi/pragma.json b/qapi/pragma.json
-index 29233db825..f2097b9020 100644
---- a/qapi/pragma.json
-+++ b/qapi/pragma.json
-@@ -37,6 +37,7 @@
-     'member-name-exceptions': [     # visible in:
-         'ACPISlotType',             # query-acpi-ospm-status
-         'AcpiTableOptions',         # -acpitable
-+        'BalloonInfo',              # query-balloon
-         'BlkdebugEvent',            # blockdev-add, -blockdev
-         'BlkdebugSetStateOptions',  # blockdev-add, -blockdev
-         'BlockDeviceInfo',          # query-block
diff --git a/debian/patches/pve/0013-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch b/debian/patches/pve/0013-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch
new file mode 100644 (file)
index 0000000..debfc54
--- /dev/null
@@ -0,0 +1,130 @@
+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: Fiona Ebner <f.ebner@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@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 5e713e231d..9390d5e5cf 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 b5b0bb4467..36f97e1f19 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 06d814e39c..e2c06c496d 100644
+--- a/qemu-img.c
++++ b/qemu-img.c
+@@ -5002,6 +5002,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;
+@@ -5011,6 +5012,7 @@ static int img_dd(int argc, char **argv)
+     int64_t size = 0, readsize = 0;
+     int64_t out_pos, in_pos;
+     bool force_share = false, skip_create = false;
++    const char *snapshot_name = NULL;
+     struct DdInfo dd = {
+         .flags = 0,
+         .count = 0,
+@@ -5048,7 +5050,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;
+         }
+@@ -5071,6 +5073,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;
+@@ -5130,11 +5145,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) {
+@@ -5289,6 +5317,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/0014-PVE-qapi-modify-query-machines.patch b/debian/patches/pve/0014-PVE-qapi-modify-query-machines.patch
deleted file mode 100644 (file)
index e40d67f..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Dietmar Maurer <dietmar@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:44 +0200
-Subject: [PATCH] PVE: qapi: modify query machines
-
-provide '*is-current' in MachineInfo struct
-
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
----
- hw/core/machine-qmp-cmds.c | 6 ++++++
- qapi/machine.json          | 4 +++-
- 2 files changed, 9 insertions(+), 1 deletion(-)
-
-diff --git a/hw/core/machine-qmp-cmds.c b/hw/core/machine-qmp-cmds.c
-index b98ff15089..24595f618c 100644
---- a/hw/core/machine-qmp-cmds.c
-+++ b/hw/core/machine-qmp-cmds.c
-@@ -103,6 +103,12 @@ MachineInfoList *qmp_query_machines(Error **errp)
-         info->numa_mem_supported = mc->numa_mem_supported;
-         info->deprecated = !!mc->deprecation_reason;
-         info->acpi = !!object_class_property_find(OBJECT_CLASS(mc), "acpi");
-+
-+        if (strcmp(mc->name, MACHINE_GET_CLASS(current_machine)->name) == 0) {
-+            info->has_is_current = true;
-+            info->is_current = true;
-+        }
-+
-         if (mc->default_cpu_type) {
-             info->default_cpu_type = g_strdup(mc->default_cpu_type);
-         }
-diff --git a/qapi/machine.json b/qapi/machine.json
-index 15f5f86683..c904280085 100644
---- a/qapi/machine.json
-+++ b/qapi/machine.json
-@@ -138,6 +138,8 @@
- #
- # @is-default: whether the machine is default
- #
-+# @is-current: whether this machine is currently used
-+#
- # @cpu-max: maximum number of CPUs supported by the machine type
- #           (since 1.5)
- #
-@@ -161,7 +163,7 @@
- ##
- { 'struct': 'MachineInfo',
-   'data': { 'name': 'str', '*alias': 'str',
--            '*is-default': 'bool', 'cpu-max': 'int',
-+            '*is-default': 'bool', '*is-current': 'bool', 'cpu-max': 'int',
-             'hotpluggable-cpus': 'bool',  'numa-mem-supported': 'bool',
-             'deprecated': 'bool', '*default-cpu-type': 'str',
-             '*default-ram-id': 'str', 'acpi': 'bool' } }
diff --git a/debian/patches/pve/0014-PVE-virtio-balloon-improve-query-balloon.patch b/debian/patches/pve/0014-PVE-virtio-balloon-improve-query-balloon.patch
new file mode 100644 (file)
index 0000000..183c9dc
--- /dev/null
@@ -0,0 +1,151 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:43 +0200
+Subject: [PATCH] PVE: virtio-balloon: improve query-balloon
+
+Actually provide memory information via the query-balloon
+command.
+
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[FE: add BalloonInfo to member name exceptions list
+     rebase for 8.0 - moved to hw/core/machine-hmp-cmds.c]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ hw/core/machine-hmp-cmds.c | 30 +++++++++++++++++++++++++++++-
+ hw/virtio/virtio-balloon.c | 33 +++++++++++++++++++++++++++++++--
+ qapi/machine.json          | 22 +++++++++++++++++++++-
+ qapi/pragma.json           |  1 +
+ 4 files changed, 82 insertions(+), 4 deletions(-)
+
+diff --git a/hw/core/machine-hmp-cmds.c b/hw/core/machine-hmp-cmds.c
+index c3e55ef9e9..0e32e6201f 100644
+--- a/hw/core/machine-hmp-cmds.c
++++ b/hw/core/machine-hmp-cmds.c
+@@ -169,7 +169,35 @@ void hmp_info_balloon(Monitor *mon, const QDict *qdict)
+         return;
+     }
+-    monitor_printf(mon, "balloon: actual=%" PRId64 "\n", info->actual >> 20);
++    monitor_printf(mon, "balloon: actual=%" PRId64, info->actual >> 20);
++    monitor_printf(mon, " max_mem=%" PRId64, info->max_mem >> 20);
++    if (info->has_total_mem) {
++        monitor_printf(mon, " total_mem=%" PRId64, info->total_mem >> 20);
++    }
++    if (info->has_free_mem) {
++        monitor_printf(mon, " free_mem=%" PRId64, info->free_mem >> 20);
++    }
++
++    if (info->has_mem_swapped_in) {
++        monitor_printf(mon, " mem_swapped_in=%" PRId64, info->mem_swapped_in);
++    }
++    if (info->has_mem_swapped_out) {
++        monitor_printf(mon, " mem_swapped_out=%" PRId64, info->mem_swapped_out);
++    }
++    if (info->has_major_page_faults) {
++        monitor_printf(mon, " major_page_faults=%" PRId64,
++                       info->major_page_faults);
++    }
++    if (info->has_minor_page_faults) {
++        monitor_printf(mon, " minor_page_faults=%" PRId64,
++                       info->minor_page_faults);
++    }
++    if (info->has_last_update) {
++        monitor_printf(mon, " last_update=%" PRId64,
++                       info->last_update);
++    }
++
++    monitor_printf(mon, "\n");
+     qapi_free_BalloonInfo(info);
+ }
+diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
+index 746f07c4d2..a41854b902 100644
+--- a/hw/virtio/virtio-balloon.c
++++ b/hw/virtio/virtio-balloon.c
+@@ -804,8 +804,37 @@ static uint64_t virtio_balloon_get_features(VirtIODevice *vdev, uint64_t f,
+ static void virtio_balloon_stat(void *opaque, BalloonInfo *info)
+ {
+     VirtIOBalloon *dev = opaque;
+-    info->actual = get_current_ram_size() - ((uint64_t) dev->actual <<
+-                                             VIRTIO_BALLOON_PFN_SHIFT);
++    ram_addr_t ram_size = get_current_ram_size();
++    info->actual = ram_size - ((uint64_t) dev->actual <<
++                               VIRTIO_BALLOON_PFN_SHIFT);
++
++    info->max_mem = ram_size;
++
++    if (!(balloon_stats_enabled(dev) && balloon_stats_supported(dev) &&
++           dev->stats_last_update)) {
++       return;
++    }
++
++    info->last_update = dev->stats_last_update;
++    info->has_last_update = true;
++
++    info->mem_swapped_in = dev->stats[VIRTIO_BALLOON_S_SWAP_IN];
++    info->has_mem_swapped_in = info->mem_swapped_in >= 0 ? true : false;
++
++    info->mem_swapped_out = dev->stats[VIRTIO_BALLOON_S_SWAP_OUT];
++    info->has_mem_swapped_out = info->mem_swapped_out >= 0 ? true : false;
++
++    info->major_page_faults = dev->stats[VIRTIO_BALLOON_S_MAJFLT];
++    info->has_major_page_faults = info->major_page_faults >= 0 ? true : false;
++
++    info->minor_page_faults = dev->stats[VIRTIO_BALLOON_S_MINFLT];
++    info->has_minor_page_faults = info->minor_page_faults >= 0 ? true : false;
++
++    info->free_mem = dev->stats[VIRTIO_BALLOON_S_MEMFREE];
++    info->has_free_mem = info->free_mem >= 0 ? true : false;
++
++    info->total_mem = dev->stats[VIRTIO_BALLOON_S_MEMTOT];
++    info->has_total_mem = info->total_mem >= 0 ? true : false;
+ }
+ static void virtio_balloon_to_target(void *opaque, ram_addr_t target)
+diff --git a/qapi/machine.json b/qapi/machine.json
+index 604b686e59..15f5f86683 100644
+--- a/qapi/machine.json
++++ b/qapi/machine.json
+@@ -1056,9 +1056,29 @@
+ # @actual: the logical size of the VM in bytes
+ #          Formula used: logical_vm_size = vm_ram_size - balloon_size
+ #
++# @last_update: time when stats got updated from guest
++#
++# @mem_swapped_in: number of pages swapped in within the guest
++# 
++# @mem_swapped_out: number of pages swapped out within the guest
++#
++# @major_page_faults: number of major page faults within the guest
++#
++# @minor_page_faults: number of minor page faults within the guest
++# 
++# @free_mem: amount of memory (in bytes) free in the guest
++#
++# @total_mem: amount of memory (in bytes) visible to the guest
++#
++# @max_mem: amount of memory (in bytes) assigned to the guest
++#
+ # Since: 0.14
+ ##
+-{ 'struct': 'BalloonInfo', 'data': {'actual': 'int' } }
++{ 'struct': 'BalloonInfo',
++  'data': {'actual': 'int', '*last_update': 'int', '*mem_swapped_in': 'int',
++           '*mem_swapped_out': 'int', '*major_page_faults': 'int',
++           '*minor_page_faults': 'int', '*free_mem': 'int',
++           '*total_mem': 'int', 'max_mem': 'int' } }
+ ##
+ # @query-balloon:
+diff --git a/qapi/pragma.json b/qapi/pragma.json
+index 29233db825..f2097b9020 100644
+--- a/qapi/pragma.json
++++ b/qapi/pragma.json
+@@ -37,6 +37,7 @@
+     'member-name-exceptions': [     # visible in:
+         'ACPISlotType',             # query-acpi-ospm-status
+         'AcpiTableOptions',         # -acpitable
++        'BalloonInfo',              # query-balloon
+         'BlkdebugEvent',            # blockdev-add, -blockdev
+         'BlkdebugSetStateOptions',  # blockdev-add, -blockdev
+         'BlockDeviceInfo',          # query-block
diff --git a/debian/patches/pve/0015-PVE-qapi-modify-query-machines.patch b/debian/patches/pve/0015-PVE-qapi-modify-query-machines.patch
new file mode 100644 (file)
index 0000000..e40d67f
--- /dev/null
@@ -0,0 +1,53 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Dietmar Maurer <dietmar@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:44 +0200
+Subject: [PATCH] PVE: qapi: modify query machines
+
+provide '*is-current' in MachineInfo struct
+
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
+---
+ hw/core/machine-qmp-cmds.c | 6 ++++++
+ qapi/machine.json          | 4 +++-
+ 2 files changed, 9 insertions(+), 1 deletion(-)
+
+diff --git a/hw/core/machine-qmp-cmds.c b/hw/core/machine-qmp-cmds.c
+index b98ff15089..24595f618c 100644
+--- a/hw/core/machine-qmp-cmds.c
++++ b/hw/core/machine-qmp-cmds.c
+@@ -103,6 +103,12 @@ MachineInfoList *qmp_query_machines(Error **errp)
+         info->numa_mem_supported = mc->numa_mem_supported;
+         info->deprecated = !!mc->deprecation_reason;
+         info->acpi = !!object_class_property_find(OBJECT_CLASS(mc), "acpi");
++
++        if (strcmp(mc->name, MACHINE_GET_CLASS(current_machine)->name) == 0) {
++            info->has_is_current = true;
++            info->is_current = true;
++        }
++
+         if (mc->default_cpu_type) {
+             info->default_cpu_type = g_strdup(mc->default_cpu_type);
+         }
+diff --git a/qapi/machine.json b/qapi/machine.json
+index 15f5f86683..c904280085 100644
+--- a/qapi/machine.json
++++ b/qapi/machine.json
+@@ -138,6 +138,8 @@
+ #
+ # @is-default: whether the machine is default
+ #
++# @is-current: whether this machine is currently used
++#
+ # @cpu-max: maximum number of CPUs supported by the machine type
+ #           (since 1.5)
+ #
+@@ -161,7 +163,7 @@
+ ##
+ { 'struct': 'MachineInfo',
+   'data': { 'name': 'str', '*alias': 'str',
+-            '*is-default': 'bool', 'cpu-max': 'int',
++            '*is-default': 'bool', '*is-current': 'bool', 'cpu-max': 'int',
+             'hotpluggable-cpus': 'bool',  'numa-mem-supported': 'bool',
+             'deprecated': 'bool', '*default-cpu-type': 'str',
+             '*default-ram-id': 'str', 'acpi': 'bool' } }
diff --git a/debian/patches/pve/0015-PVE-qapi-modify-spice-query.patch b/debian/patches/pve/0015-PVE-qapi-modify-spice-query.patch
deleted file mode 100644 (file)
index df551da..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:45 +0200
-Subject: [PATCH] PVE: qapi: modify spice query
-
-Provide the last ticket in the SpiceInfo struct optionally.
-
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-[FE: adapt to QAPI change]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- qapi/ui.json    | 3 +++
- ui/spice-core.c | 4 ++++
- 2 files changed, 7 insertions(+)
-
-diff --git a/qapi/ui.json b/qapi/ui.json
-index 98322342f7..316d4dc933 100644
---- a/qapi/ui.json
-+++ b/qapi/ui.json
-@@ -310,11 +310,14 @@
- #
- # @channels: a list of @SpiceChannel for each active spice channel
- #
-+# @ticket: The last ticket set with set_password
-+#
- # Since: 0.14
- ##
- { 'struct': 'SpiceInfo',
-   'data': {'enabled': 'bool', 'migrated': 'bool', '*host': 'str', '*port': 'int',
-            '*tls-port': 'int', '*auth': 'str', '*compiled-version': 'str',
-+           '*ticket': 'str',
-            'mouse-mode': 'SpiceQueryMouseMode', '*channels': ['SpiceChannel']},
-   'if': 'CONFIG_SPICE' }
-diff --git a/ui/spice-core.c b/ui/spice-core.c
-index b20c25aee0..26baeb7846 100644
---- a/ui/spice-core.c
-+++ b/ui/spice-core.c
-@@ -548,6 +548,10 @@ static SpiceInfo *qmp_query_spice_real(Error **errp)
-     micro = SPICE_SERVER_VERSION & 0xff;
-     info->compiled_version = g_strdup_printf("%d.%d.%d", major, minor, micro);
-+    if (auth_passwd) {
-+        info->ticket =  g_strdup(auth_passwd);
-+    }
-+
-     if (port) {
-         info->has_port = true;
-         info->port = port;
diff --git a/debian/patches/pve/0016-PVE-add-IOChannel-implementation-for-savevm-async.patch b/debian/patches/pve/0016-PVE-add-IOChannel-implementation-for-savevm-async.patch
deleted file mode 100644 (file)
index ce12543..0000000
+++ /dev/null
@@ -1,282 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fiona Ebner <f.ebner@proxmox.com>
-Date: Thu, 13 Oct 2022 11:33:50 +0200
-Subject: [PATCH] PVE: add IOChannel implementation for savevm-async
-
-based on migration/channel-block.c and the implementation that was
-present in migration/savevm-async.c before QEMU 7.1.
-
-Passes along read/write requests to the given BlockBackend, while
-ensuring that a read request going beyond the end results in a
-graceful short read.
-
-Additionally, allows tracking the current position from the outside
-(intended to be used for progress tracking).
-
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- migration/channel-savevm-async.c | 183 +++++++++++++++++++++++++++++++
- migration/channel-savevm-async.h |  51 +++++++++
- migration/meson.build            |   1 +
- 3 files changed, 235 insertions(+)
- create mode 100644 migration/channel-savevm-async.c
- create mode 100644 migration/channel-savevm-async.h
-
-diff --git a/migration/channel-savevm-async.c b/migration/channel-savevm-async.c
-new file mode 100644
-index 0000000000..aab081ce07
---- /dev/null
-+++ b/migration/channel-savevm-async.c
-@@ -0,0 +1,183 @@
-+/*
-+ * QIO Channel implementation to be used by savevm-async QMP calls
-+ */
-+#include "qemu/osdep.h"
-+#include "migration/channel-savevm-async.h"
-+#include "qapi/error.h"
-+#include "sysemu/block-backend.h"
-+#include "trace.h"
-+
-+QIOChannelSavevmAsync *
-+qio_channel_savevm_async_new(BlockBackend *be, size_t *bs_pos)
-+{
-+    QIOChannelSavevmAsync *ioc;
-+
-+    ioc = QIO_CHANNEL_SAVEVM_ASYNC(object_new(TYPE_QIO_CHANNEL_SAVEVM_ASYNC));
-+
-+    bdrv_ref(blk_bs(be));
-+    ioc->be = be;
-+    ioc->bs_pos = bs_pos;
-+
-+    return ioc;
-+}
-+
-+
-+static void
-+qio_channel_savevm_async_finalize(Object *obj)
-+{
-+    QIOChannelSavevmAsync *ioc = QIO_CHANNEL_SAVEVM_ASYNC(obj);
-+
-+    if (ioc->be) {
-+        bdrv_unref(blk_bs(ioc->be));
-+        ioc->be = NULL;
-+    }
-+    ioc->bs_pos = NULL;
-+}
-+
-+
-+static ssize_t
-+qio_channel_savevm_async_readv(QIOChannel *ioc,
-+                               const struct iovec *iov,
-+                               size_t niov,
-+                               int **fds,
-+                               size_t *nfds,
-+                               int flags,
-+                               Error **errp)
-+{
-+    QIOChannelSavevmAsync *saioc = QIO_CHANNEL_SAVEVM_ASYNC(ioc);
-+    BlockBackend *be = saioc->be;
-+    int64_t maxlen = blk_getlength(be);
-+    QEMUIOVector qiov;
-+    size_t size;
-+    int ret;
-+
-+    qemu_iovec_init_external(&qiov, (struct iovec *)iov, niov);
-+
-+    if (*saioc->bs_pos >= maxlen) {
-+        error_setg(errp, "cannot read beyond maxlen");
-+        return -1;
-+    }
-+
-+    if (maxlen - *saioc->bs_pos < qiov.size) {
-+        size = maxlen - *saioc->bs_pos;
-+    } else {
-+        size = qiov.size;
-+    }
-+
-+    // returns 0 on success
-+    ret = blk_preadv(be, *saioc->bs_pos, size, &qiov, 0);
-+    if (ret < 0) {
-+        error_setg_errno(errp, -ret, "blk_preadv failed");
-+        return -1;
-+    }
-+
-+    *saioc->bs_pos += size;
-+    return size;
-+}
-+
-+
-+static ssize_t
-+qio_channel_savevm_async_writev(QIOChannel *ioc,
-+                                const struct iovec *iov,
-+                                size_t niov,
-+                                int *fds,
-+                                size_t nfds,
-+                                int flags,
-+                                Error **errp)
-+{
-+    QIOChannelSavevmAsync *saioc = QIO_CHANNEL_SAVEVM_ASYNC(ioc);
-+    BlockBackend *be = saioc->be;
-+    QEMUIOVector qiov;
-+    int ret;
-+
-+    qemu_iovec_init_external(&qiov, (struct iovec *)iov, niov);
-+
-+    if (qemu_in_coroutine()) {
-+        ret = blk_co_pwritev(be, *saioc->bs_pos, qiov.size, &qiov, 0);
-+        aio_wait_kick();
-+    } else {
-+        ret = blk_pwritev(be, *saioc->bs_pos, qiov.size, &qiov, 0);
-+    }
-+
-+    if (ret < 0) {
-+        error_setg_errno(errp, -ret, "blk(_co)_pwritev failed");
-+        return -1;
-+    }
-+
-+    *saioc->bs_pos += qiov.size;
-+    return qiov.size;
-+}
-+
-+
-+static int
-+qio_channel_savevm_async_set_blocking(QIOChannel *ioc,
-+                                      bool enabled,
-+                                      Error **errp)
-+{
-+    if (!enabled) {
-+        error_setg(errp, "Non-blocking mode not supported for savevm-async");
-+        return -1;
-+    }
-+    return 0;
-+}
-+
-+
-+static int
-+qio_channel_savevm_async_close(QIOChannel *ioc,
-+                               Error **errp)
-+{
-+    QIOChannelSavevmAsync *saioc = QIO_CHANNEL_SAVEVM_ASYNC(ioc);
-+    int rv = bdrv_flush(blk_bs(saioc->be));
-+
-+    if (rv < 0) {
-+        error_setg_errno(errp, -rv, "Unable to flush VMState");
-+        return -1;
-+    }
-+
-+    bdrv_unref(blk_bs(saioc->be));
-+    saioc->be = NULL;
-+    saioc->bs_pos = NULL;
-+
-+    return 0;
-+}
-+
-+
-+static void
-+qio_channel_savevm_async_set_aio_fd_handler(QIOChannel *ioc,
-+                                            AioContext *ctx,
-+                                            IOHandler *io_read,
-+                                            IOHandler *io_write,
-+                                            void *opaque)
-+{
-+    // if channel-block starts doing something, check if this needs adaptation
-+}
-+
-+
-+static void
-+qio_channel_savevm_async_class_init(ObjectClass *klass,
-+                             void *class_data G_GNUC_UNUSED)
-+{
-+    QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
-+
-+    ioc_klass->io_writev = qio_channel_savevm_async_writev;
-+    ioc_klass->io_readv = qio_channel_savevm_async_readv;
-+    ioc_klass->io_set_blocking = qio_channel_savevm_async_set_blocking;
-+    ioc_klass->io_close = qio_channel_savevm_async_close;
-+    ioc_klass->io_set_aio_fd_handler = qio_channel_savevm_async_set_aio_fd_handler;
-+}
-+
-+static const TypeInfo qio_channel_savevm_async_info = {
-+    .parent = TYPE_QIO_CHANNEL,
-+    .name = TYPE_QIO_CHANNEL_SAVEVM_ASYNC,
-+    .instance_size = sizeof(QIOChannelSavevmAsync),
-+    .instance_finalize = qio_channel_savevm_async_finalize,
-+    .class_init = qio_channel_savevm_async_class_init,
-+};
-+
-+static void
-+qio_channel_savevm_async_register_types(void)
-+{
-+    type_register_static(&qio_channel_savevm_async_info);
-+}
-+
-+type_init(qio_channel_savevm_async_register_types);
-diff --git a/migration/channel-savevm-async.h b/migration/channel-savevm-async.h
-new file mode 100644
-index 0000000000..17ae2cb261
---- /dev/null
-+++ b/migration/channel-savevm-async.h
-@@ -0,0 +1,51 @@
-+/*
-+ * QEMU I/O channels driver for savevm-async.c
-+ *
-+ * Copyright (c) 2022 Proxmox Server Solutions
-+ *
-+ * Authors:
-+ *  Fiona Ebner (f.ebner@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 QIO_CHANNEL_SAVEVM_ASYNC_H
-+#define QIO_CHANNEL_SAVEVM_ASYNC_H
-+
-+#include "io/channel.h"
-+#include "qom/object.h"
-+
-+#define TYPE_QIO_CHANNEL_SAVEVM_ASYNC "qio-channel-savevm-async"
-+OBJECT_DECLARE_SIMPLE_TYPE(QIOChannelSavevmAsync, QIO_CHANNEL_SAVEVM_ASYNC)
-+
-+
-+/**
-+ * QIOChannelSavevmAsync:
-+ *
-+ * The QIOChannelBlock object provides a channel implementation that is able to
-+ * perform I/O on any BlockBackend whose BlockDriverState directly contains a
-+ * VMState (as opposed to indirectly, like qcow2). It allows tracking the
-+ * current position from the outside.
-+ */
-+struct QIOChannelSavevmAsync {
-+    QIOChannel parent;
-+    BlockBackend *be;
-+    size_t *bs_pos;
-+};
-+
-+
-+/**
-+ * qio_channel_savevm_async_new:
-+ * @be: the block backend
-+ * @bs_pos: used to keep track of the IOChannels current position
-+ *
-+ * Create a new IO channel object that can perform I/O on a BlockBackend object
-+ * whose BlockDriverState directly contains a VMState.
-+ *
-+ * Returns: the new channel object
-+ */
-+QIOChannelSavevmAsync *
-+qio_channel_savevm_async_new(BlockBackend *be, size_t *bs_pos);
-+
-+#endif /* QIO_CHANNEL_SAVEVM_ASYNC_H */
-diff --git a/migration/meson.build b/migration/meson.build
-index 0d1bb9f96e..8a142fc7a9 100644
---- a/migration/meson.build
-+++ b/migration/meson.build
-@@ -13,6 +13,7 @@ softmmu_ss.add(files(
-   'block-dirty-bitmap.c',
-   'channel.c',
-   'channel-block.c',
-+  'channel-savevm-async.c',
-   'colo-failover.c',
-   'colo.c',
-   'exec.c',
diff --git a/debian/patches/pve/0016-PVE-qapi-modify-spice-query.patch b/debian/patches/pve/0016-PVE-qapi-modify-spice-query.patch
new file mode 100644 (file)
index 0000000..df551da
--- /dev/null
@@ -0,0 +1,49 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:45 +0200
+Subject: [PATCH] PVE: qapi: modify spice query
+
+Provide the last ticket in the SpiceInfo struct optionally.
+
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[FE: adapt to QAPI change]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ qapi/ui.json    | 3 +++
+ ui/spice-core.c | 4 ++++
+ 2 files changed, 7 insertions(+)
+
+diff --git a/qapi/ui.json b/qapi/ui.json
+index 98322342f7..316d4dc933 100644
+--- a/qapi/ui.json
++++ b/qapi/ui.json
+@@ -310,11 +310,14 @@
+ #
+ # @channels: a list of @SpiceChannel for each active spice channel
+ #
++# @ticket: The last ticket set with set_password
++#
+ # Since: 0.14
+ ##
+ { 'struct': 'SpiceInfo',
+   'data': {'enabled': 'bool', 'migrated': 'bool', '*host': 'str', '*port': 'int',
+            '*tls-port': 'int', '*auth': 'str', '*compiled-version': 'str',
++           '*ticket': 'str',
+            'mouse-mode': 'SpiceQueryMouseMode', '*channels': ['SpiceChannel']},
+   'if': 'CONFIG_SPICE' }
+diff --git a/ui/spice-core.c b/ui/spice-core.c
+index b20c25aee0..26baeb7846 100644
+--- a/ui/spice-core.c
++++ b/ui/spice-core.c
+@@ -548,6 +548,10 @@ static SpiceInfo *qmp_query_spice_real(Error **errp)
+     micro = SPICE_SERVER_VERSION & 0xff;
+     info->compiled_version = g_strdup_printf("%d.%d.%d", major, minor, micro);
++    if (auth_passwd) {
++        info->ticket =  g_strdup(auth_passwd);
++    }
++
+     if (port) {
+         info->has_port = true;
+         info->port = port;
diff --git a/debian/patches/pve/0017-PVE-add-IOChannel-implementation-for-savevm-async.patch b/debian/patches/pve/0017-PVE-add-IOChannel-implementation-for-savevm-async.patch
new file mode 100644 (file)
index 0000000..ce12543
--- /dev/null
@@ -0,0 +1,282 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Fiona Ebner <f.ebner@proxmox.com>
+Date: Thu, 13 Oct 2022 11:33:50 +0200
+Subject: [PATCH] PVE: add IOChannel implementation for savevm-async
+
+based on migration/channel-block.c and the implementation that was
+present in migration/savevm-async.c before QEMU 7.1.
+
+Passes along read/write requests to the given BlockBackend, while
+ensuring that a read request going beyond the end results in a
+graceful short read.
+
+Additionally, allows tracking the current position from the outside
+(intended to be used for progress tracking).
+
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ migration/channel-savevm-async.c | 183 +++++++++++++++++++++++++++++++
+ migration/channel-savevm-async.h |  51 +++++++++
+ migration/meson.build            |   1 +
+ 3 files changed, 235 insertions(+)
+ create mode 100644 migration/channel-savevm-async.c
+ create mode 100644 migration/channel-savevm-async.h
+
+diff --git a/migration/channel-savevm-async.c b/migration/channel-savevm-async.c
+new file mode 100644
+index 0000000000..aab081ce07
+--- /dev/null
++++ b/migration/channel-savevm-async.c
+@@ -0,0 +1,183 @@
++/*
++ * QIO Channel implementation to be used by savevm-async QMP calls
++ */
++#include "qemu/osdep.h"
++#include "migration/channel-savevm-async.h"
++#include "qapi/error.h"
++#include "sysemu/block-backend.h"
++#include "trace.h"
++
++QIOChannelSavevmAsync *
++qio_channel_savevm_async_new(BlockBackend *be, size_t *bs_pos)
++{
++    QIOChannelSavevmAsync *ioc;
++
++    ioc = QIO_CHANNEL_SAVEVM_ASYNC(object_new(TYPE_QIO_CHANNEL_SAVEVM_ASYNC));
++
++    bdrv_ref(blk_bs(be));
++    ioc->be = be;
++    ioc->bs_pos = bs_pos;
++
++    return ioc;
++}
++
++
++static void
++qio_channel_savevm_async_finalize(Object *obj)
++{
++    QIOChannelSavevmAsync *ioc = QIO_CHANNEL_SAVEVM_ASYNC(obj);
++
++    if (ioc->be) {
++        bdrv_unref(blk_bs(ioc->be));
++        ioc->be = NULL;
++    }
++    ioc->bs_pos = NULL;
++}
++
++
++static ssize_t
++qio_channel_savevm_async_readv(QIOChannel *ioc,
++                               const struct iovec *iov,
++                               size_t niov,
++                               int **fds,
++                               size_t *nfds,
++                               int flags,
++                               Error **errp)
++{
++    QIOChannelSavevmAsync *saioc = QIO_CHANNEL_SAVEVM_ASYNC(ioc);
++    BlockBackend *be = saioc->be;
++    int64_t maxlen = blk_getlength(be);
++    QEMUIOVector qiov;
++    size_t size;
++    int ret;
++
++    qemu_iovec_init_external(&qiov, (struct iovec *)iov, niov);
++
++    if (*saioc->bs_pos >= maxlen) {
++        error_setg(errp, "cannot read beyond maxlen");
++        return -1;
++    }
++
++    if (maxlen - *saioc->bs_pos < qiov.size) {
++        size = maxlen - *saioc->bs_pos;
++    } else {
++        size = qiov.size;
++    }
++
++    // returns 0 on success
++    ret = blk_preadv(be, *saioc->bs_pos, size, &qiov, 0);
++    if (ret < 0) {
++        error_setg_errno(errp, -ret, "blk_preadv failed");
++        return -1;
++    }
++
++    *saioc->bs_pos += size;
++    return size;
++}
++
++
++static ssize_t
++qio_channel_savevm_async_writev(QIOChannel *ioc,
++                                const struct iovec *iov,
++                                size_t niov,
++                                int *fds,
++                                size_t nfds,
++                                int flags,
++                                Error **errp)
++{
++    QIOChannelSavevmAsync *saioc = QIO_CHANNEL_SAVEVM_ASYNC(ioc);
++    BlockBackend *be = saioc->be;
++    QEMUIOVector qiov;
++    int ret;
++
++    qemu_iovec_init_external(&qiov, (struct iovec *)iov, niov);
++
++    if (qemu_in_coroutine()) {
++        ret = blk_co_pwritev(be, *saioc->bs_pos, qiov.size, &qiov, 0);
++        aio_wait_kick();
++    } else {
++        ret = blk_pwritev(be, *saioc->bs_pos, qiov.size, &qiov, 0);
++    }
++
++    if (ret < 0) {
++        error_setg_errno(errp, -ret, "blk(_co)_pwritev failed");
++        return -1;
++    }
++
++    *saioc->bs_pos += qiov.size;
++    return qiov.size;
++}
++
++
++static int
++qio_channel_savevm_async_set_blocking(QIOChannel *ioc,
++                                      bool enabled,
++                                      Error **errp)
++{
++    if (!enabled) {
++        error_setg(errp, "Non-blocking mode not supported for savevm-async");
++        return -1;
++    }
++    return 0;
++}
++
++
++static int
++qio_channel_savevm_async_close(QIOChannel *ioc,
++                               Error **errp)
++{
++    QIOChannelSavevmAsync *saioc = QIO_CHANNEL_SAVEVM_ASYNC(ioc);
++    int rv = bdrv_flush(blk_bs(saioc->be));
++
++    if (rv < 0) {
++        error_setg_errno(errp, -rv, "Unable to flush VMState");
++        return -1;
++    }
++
++    bdrv_unref(blk_bs(saioc->be));
++    saioc->be = NULL;
++    saioc->bs_pos = NULL;
++
++    return 0;
++}
++
++
++static void
++qio_channel_savevm_async_set_aio_fd_handler(QIOChannel *ioc,
++                                            AioContext *ctx,
++                                            IOHandler *io_read,
++                                            IOHandler *io_write,
++                                            void *opaque)
++{
++    // if channel-block starts doing something, check if this needs adaptation
++}
++
++
++static void
++qio_channel_savevm_async_class_init(ObjectClass *klass,
++                             void *class_data G_GNUC_UNUSED)
++{
++    QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
++
++    ioc_klass->io_writev = qio_channel_savevm_async_writev;
++    ioc_klass->io_readv = qio_channel_savevm_async_readv;
++    ioc_klass->io_set_blocking = qio_channel_savevm_async_set_blocking;
++    ioc_klass->io_close = qio_channel_savevm_async_close;
++    ioc_klass->io_set_aio_fd_handler = qio_channel_savevm_async_set_aio_fd_handler;
++}
++
++static const TypeInfo qio_channel_savevm_async_info = {
++    .parent = TYPE_QIO_CHANNEL,
++    .name = TYPE_QIO_CHANNEL_SAVEVM_ASYNC,
++    .instance_size = sizeof(QIOChannelSavevmAsync),
++    .instance_finalize = qio_channel_savevm_async_finalize,
++    .class_init = qio_channel_savevm_async_class_init,
++};
++
++static void
++qio_channel_savevm_async_register_types(void)
++{
++    type_register_static(&qio_channel_savevm_async_info);
++}
++
++type_init(qio_channel_savevm_async_register_types);
+diff --git a/migration/channel-savevm-async.h b/migration/channel-savevm-async.h
+new file mode 100644
+index 0000000000..17ae2cb261
+--- /dev/null
++++ b/migration/channel-savevm-async.h
+@@ -0,0 +1,51 @@
++/*
++ * QEMU I/O channels driver for savevm-async.c
++ *
++ * Copyright (c) 2022 Proxmox Server Solutions
++ *
++ * Authors:
++ *  Fiona Ebner (f.ebner@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 QIO_CHANNEL_SAVEVM_ASYNC_H
++#define QIO_CHANNEL_SAVEVM_ASYNC_H
++
++#include "io/channel.h"
++#include "qom/object.h"
++
++#define TYPE_QIO_CHANNEL_SAVEVM_ASYNC "qio-channel-savevm-async"
++OBJECT_DECLARE_SIMPLE_TYPE(QIOChannelSavevmAsync, QIO_CHANNEL_SAVEVM_ASYNC)
++
++
++/**
++ * QIOChannelSavevmAsync:
++ *
++ * The QIOChannelBlock object provides a channel implementation that is able to
++ * perform I/O on any BlockBackend whose BlockDriverState directly contains a
++ * VMState (as opposed to indirectly, like qcow2). It allows tracking the
++ * current position from the outside.
++ */
++struct QIOChannelSavevmAsync {
++    QIOChannel parent;
++    BlockBackend *be;
++    size_t *bs_pos;
++};
++
++
++/**
++ * qio_channel_savevm_async_new:
++ * @be: the block backend
++ * @bs_pos: used to keep track of the IOChannels current position
++ *
++ * Create a new IO channel object that can perform I/O on a BlockBackend object
++ * whose BlockDriverState directly contains a VMState.
++ *
++ * Returns: the new channel object
++ */
++QIOChannelSavevmAsync *
++qio_channel_savevm_async_new(BlockBackend *be, size_t *bs_pos);
++
++#endif /* QIO_CHANNEL_SAVEVM_ASYNC_H */
+diff --git a/migration/meson.build b/migration/meson.build
+index 0d1bb9f96e..8a142fc7a9 100644
+--- a/migration/meson.build
++++ b/migration/meson.build
+@@ -13,6 +13,7 @@ softmmu_ss.add(files(
+   'block-dirty-bitmap.c',
+   'channel.c',
+   'channel-block.c',
++  'channel-savevm-async.c',
+   'colo-failover.c',
+   'colo.c',
+   'exec.c',
diff --git a/debian/patches/pve/0017-PVE-add-savevm-async-for-background-state-snapshots.patch b/debian/patches/pve/0017-PVE-add-savevm-async-for-background-state-snapshots.patch
deleted file mode 100644 (file)
index 7c3f2ce..0000000
+++ /dev/null
@@ -1,916 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Dietmar Maurer <dietmar@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:46 +0200
-Subject: [PATCH] PVE: add savevm-async for background state snapshots
-
-Put qemu_savevm_state_{header,setup} into the main loop and the rest
-of the iteration into a coroutine. The former need to lock the
-iothread (and we can't unlock it in the coroutine), and the latter
-can't deal with being in a separate thread, so a coroutine it must
-be.
-
-Truncate output file at 1024 boundary.
-
-Do not block the VM and save the state on aborting a snapshot, as the
-snapshot will be invalid anyway.
-
-Also, when aborting, wait for the target file to be closed, otherwise a
-client might run into race-conditions when trying to remove the file
-still opened by QEMU.
-
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-[improve aborting]
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-[FE: further improve aborting
-     adapt to removal of QEMUFileOps
-     improve condition for entering final stage
-     adapt to QAPI and other changes for 8.0]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- hmp-commands-info.hx         |  13 +
- hmp-commands.hx              |  33 +++
- include/migration/snapshot.h |   2 +
- include/monitor/hmp.h        |   5 +
- migration/meson.build        |   1 +
- migration/savevm-async.c     | 535 +++++++++++++++++++++++++++++++++++
- monitor/hmp-cmds.c           |  58 ++++
- qapi/migration.json          |  34 +++
- qapi/misc.json               |  32 +++
- qemu-options.hx              |  12 +
- softmmu/vl.c                 |  10 +
- 11 files changed, 735 insertions(+)
- create mode 100644 migration/savevm-async.c
-
-diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
-index 47d63d26db..a166bff3d5 100644
---- a/hmp-commands-info.hx
-+++ b/hmp-commands-info.hx
-@@ -540,6 +540,19 @@ SRST
-     Show current migration parameters.
- ERST
-+    {
-+        .name       = "savevm",
-+        .args_type  = "",
-+        .params     = "",
-+        .help       = "show savevm status",
-+        .cmd = hmp_info_savevm,
-+    },
-+
-+SRST
-+  ``info savevm``
-+    Show savevm status.
-+ERST
-+
-     {
-         .name       = "balloon",
-         .args_type  = "",
-diff --git a/hmp-commands.hx b/hmp-commands.hx
-index bb85ee1d26..b66d7fc4ab 100644
---- a/hmp-commands.hx
-+++ b/hmp-commands.hx
-@@ -1846,3 +1846,36 @@ SRST
-   List event channels in the guest
- ERST
- #endif
-+
-+    {
-+        .name       = "savevm-start",
-+        .args_type  = "statefile:s?",
-+        .params     = "[statefile]",
-+        .help       = "Prepare for snapshot and halt VM. Save VM state to statefile.",
-+        .cmd = hmp_savevm_start,
-+    },
-+
-+    {
-+        .name       = "snapshot-drive",
-+        .args_type  = "device:s,name:s",
-+        .params     = "device name",
-+        .help       = "Create internal snapshot.",
-+        .cmd = hmp_snapshot_drive,
-+    },
-+
-+    {
-+        .name       = "delete-drive-snapshot",
-+        .args_type  = "device:s,name:s",
-+        .params     = "device name",
-+        .help       = "Delete internal snapshot.",
-+        .cmd = hmp_delete_drive_snapshot,
-+    },
-+
-+    {
-+        .name       = "savevm-end",
-+        .args_type  = "",
-+        .params     = "",
-+        .help       = "Resume VM after snaphot.",
-+        .cmd        = hmp_savevm_end,
-+        .coroutine  = true,
-+    },
-diff --git a/include/migration/snapshot.h b/include/migration/snapshot.h
-index e72083b117..c846d37806 100644
---- a/include/migration/snapshot.h
-+++ b/include/migration/snapshot.h
-@@ -61,4 +61,6 @@ bool delete_snapshot(const char *name,
-                     bool has_devices, strList *devices,
-                     Error **errp);
-+int load_snapshot_from_blockdev(const char *filename, Error **errp);
-+
- #endif
-diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h
-index fdb69b7f9c..c012bad741 100644
---- a/include/monitor/hmp.h
-+++ b/include/monitor/hmp.h
-@@ -28,6 +28,7 @@ void hmp_info_status(Monitor *mon, const QDict *qdict);
- void hmp_info_uuid(Monitor *mon, const QDict *qdict);
- void hmp_info_chardev(Monitor *mon, const QDict *qdict);
- void hmp_info_mice(Monitor *mon, const QDict *qdict);
-+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);
-@@ -94,6 +95,10 @@ void hmp_closefd(Monitor *mon, const QDict *qdict);
- void hmp_mouse_move(Monitor *mon, const QDict *qdict);
- void hmp_mouse_button(Monitor *mon, const QDict *qdict);
- void hmp_mouse_set(Monitor *mon, const QDict *qdict);
-+void hmp_savevm_start(Monitor *mon, const QDict *qdict);
-+void hmp_snapshot_drive(Monitor *mon, const QDict *qdict);
-+void hmp_delete_drive_snapshot(Monitor *mon, const QDict *qdict);
-+void hmp_savevm_end(Monitor *mon, const QDict *qdict);
- void hmp_sendkey(Monitor *mon, const QDict *qdict);
- void coroutine_fn hmp_screendump(Monitor *mon, const QDict *qdict);
- void hmp_chardev_add(Monitor *mon, const QDict *qdict);
-diff --git a/migration/meson.build b/migration/meson.build
-index 8a142fc7a9..a7824b5266 100644
---- a/migration/meson.build
-+++ b/migration/meson.build
-@@ -25,6 +25,7 @@ softmmu_ss.add(files(
-   'multifd-zlib.c',
-   'postcopy-ram.c',
-   'savevm.c',
-+  'savevm-async.c',
-   'socket.c',
-   'tls.c',
-   'threadinfo.c',
-diff --git a/migration/savevm-async.c b/migration/savevm-async.c
-new file mode 100644
-index 0000000000..24660af014
---- /dev/null
-+++ b/migration/savevm-async.c
-@@ -0,0 +1,535 @@
-+#include "qemu/osdep.h"
-+#include "migration/channel-savevm-async.h"
-+#include "migration/migration.h"
-+#include "migration/savevm.h"
-+#include "migration/snapshot.h"
-+#include "migration/global_state.h"
-+#include "migration/ram.h"
-+#include "migration/qemu-file.h"
-+#include "sysemu/sysemu.h"
-+#include "sysemu/runstate.h"
-+#include "block/block.h"
-+#include "sysemu/block-backend.h"
-+#include "qapi/error.h"
-+#include "qapi/qmp/qerror.h"
-+#include "qapi/qmp/qdict.h"
-+#include "qapi/qapi-commands-migration.h"
-+#include "qapi/qapi-commands-misc.h"
-+#include "qapi/qapi-commands-block.h"
-+#include "qemu/cutils.h"
-+#include "qemu/timer.h"
-+#include "qemu/main-loop.h"
-+#include "qemu/rcu.h"
-+
-+/* #define DEBUG_SAVEVM_STATE */
-+
-+#ifdef DEBUG_SAVEVM_STATE
-+#define DPRINTF(fmt, ...) \
-+    do { printf("savevm-async: " fmt, ## __VA_ARGS__); } while (0)
-+#else
-+#define DPRINTF(fmt, ...) \
-+    do { } while (0)
-+#endif
-+
-+enum {
-+    SAVE_STATE_DONE,
-+    SAVE_STATE_ERROR,
-+    SAVE_STATE_ACTIVE,
-+    SAVE_STATE_COMPLETED,
-+    SAVE_STATE_CANCELLED
-+};
-+
-+
-+static struct SnapshotState {
-+    BlockBackend *target;
-+    size_t bs_pos;
-+    int state;
-+    Error *error;
-+    Error *blocker;
-+    int saved_vm_running;
-+    QEMUFile *file;
-+    int64_t total_time;
-+    QEMUBH *finalize_bh;
-+    Coroutine *co;
-+    QemuCoSleep target_close_wait;
-+} snap_state;
-+
-+static bool savevm_aborted(void)
-+{
-+    return snap_state.state == SAVE_STATE_CANCELLED ||
-+        snap_state.state == SAVE_STATE_ERROR;
-+}
-+
-+SaveVMInfo *qmp_query_savevm(Error **errp)
-+{
-+    SaveVMInfo *info = g_malloc0(sizeof(*info));
-+    struct SnapshotState *s = &snap_state;
-+
-+    if (s->state != SAVE_STATE_DONE) {
-+        info->has_bytes = true;
-+        info->bytes = s->bs_pos;
-+        switch (s->state) {
-+        case SAVE_STATE_ERROR:
-+            info->status = g_strdup("failed");
-+            info->has_total_time = true;
-+            info->total_time = s->total_time;
-+            if (s->error) {
-+                info->error = g_strdup(error_get_pretty(s->error));
-+            }
-+            break;
-+        case SAVE_STATE_ACTIVE:
-+            info->status = g_strdup("active");
-+            info->has_total_time = true;
-+            info->total_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME)
-+                - s->total_time;
-+            break;
-+        case SAVE_STATE_COMPLETED:
-+            info->status = g_strdup("completed");
-+            info->has_total_time = true;
-+            info->total_time = s->total_time;
-+            break;
-+        }
-+    }
-+
-+    return info;
-+}
-+
-+static int save_snapshot_cleanup(void)
-+{
-+    int ret = 0;
-+
-+    DPRINTF("save_snapshot_cleanup\n");
-+
-+    snap_state.total_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) -
-+        snap_state.total_time;
-+
-+    if (snap_state.file) {
-+        ret = qemu_fclose(snap_state.file);
-+        snap_state.file = NULL;
-+    }
-+
-+    if (snap_state.target) {
-+        if (!savevm_aborted()) {
-+            /* try to truncate, but ignore errors (will fail on block devices).
-+            * note1: bdrv_read() need whole blocks, so we need to round up
-+            * note2: PVE requires 1024 (BDRV_SECTOR_SIZE*2) alignment
-+            */
-+            size_t size = QEMU_ALIGN_UP(snap_state.bs_pos, BDRV_SECTOR_SIZE*2);
-+            blk_truncate(snap_state.target, size, false, PREALLOC_MODE_OFF, 0, NULL);
-+        }
-+        blk_op_unblock_all(snap_state.target, snap_state.blocker);
-+        error_free(snap_state.blocker);
-+        snap_state.blocker = NULL;
-+        blk_unref(snap_state.target);
-+        snap_state.target = NULL;
-+
-+        qemu_co_sleep_wake(&snap_state.target_close_wait);
-+    }
-+
-+    return ret;
-+}
-+
-+static void save_snapshot_error(const char *fmt, ...)
-+{
-+    va_list ap;
-+    char *msg;
-+
-+    va_start(ap, fmt);
-+    msg = g_strdup_vprintf(fmt, ap);
-+    va_end(ap);
-+
-+    DPRINTF("save_snapshot_error: %s\n", msg);
-+
-+    if (!snap_state.error) {
-+        error_set(&snap_state.error, ERROR_CLASS_GENERIC_ERROR, "%s", msg);
-+    }
-+
-+    g_free (msg);
-+
-+    snap_state.state = SAVE_STATE_ERROR;
-+}
-+
-+static void process_savevm_finalize(void *opaque)
-+{
-+    int ret;
-+    AioContext *iohandler_ctx = iohandler_get_aio_context();
-+    MigrationState *ms = migrate_get_current();
-+
-+    bool aborted = savevm_aborted();
-+
-+#ifdef DEBUG_SAVEVM_STATE
-+    int64_t start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
-+#endif
-+
-+    qemu_bh_delete(snap_state.finalize_bh);
-+    snap_state.finalize_bh = NULL;
-+    snap_state.co = NULL;
-+
-+    /* We need to own the target bdrv's context for the following functions,
-+     * so move it back. It can stay in the main context and live out its live
-+     * there, since we're done with it after this method ends anyway.
-+     */
-+    aio_context_acquire(iohandler_ctx);
-+    blk_set_aio_context(snap_state.target, qemu_get_aio_context(), NULL);
-+    aio_context_release(iohandler_ctx);
-+
-+    ret = vm_stop_force_state(RUN_STATE_FINISH_MIGRATE);
-+    if (ret < 0) {
-+        save_snapshot_error("vm_stop_force_state error %d", ret);
-+    }
-+
-+    if (!aborted) {
-+        /* skip state saving if we aborted, snapshot will be invalid anyway */
-+        (void)qemu_savevm_state_complete_precopy(snap_state.file, false, false);
-+        ret = qemu_file_get_error(snap_state.file);
-+        if (ret < 0) {
-+            save_snapshot_error("qemu_savevm_state_complete_precopy error %d", ret);
-+        }
-+    }
-+
-+    DPRINTF("state saving complete\n");
-+    DPRINTF("timing: process_savevm_finalize (state saving) took %ld ms\n",
-+        qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - start_time);
-+
-+    /* clear migration state */
-+    migrate_set_state(&ms->state, MIGRATION_STATUS_SETUP,
-+        ret || aborted ? MIGRATION_STATUS_FAILED : MIGRATION_STATUS_COMPLETED);
-+    ms->to_dst_file = NULL;
-+
-+    qemu_savevm_state_cleanup();
-+
-+    ret = save_snapshot_cleanup();
-+    if (ret < 0) {
-+        save_snapshot_error("save_snapshot_cleanup error %d", ret);
-+    } else if (snap_state.state == SAVE_STATE_ACTIVE) {
-+        snap_state.state = SAVE_STATE_COMPLETED;
-+    } else if (aborted) {
-+        /*
-+         * If there was an error, there's no need to set a new one here.
-+         * If the snapshot was canceled, leave setting the state to
-+         * qmp_savevm_end(), which is waked by save_snapshot_cleanup().
-+         */
-+    } else {
-+        save_snapshot_error("process_savevm_cleanup: invalid state: %d",
-+                            snap_state.state);
-+    }
-+    if (snap_state.saved_vm_running) {
-+        vm_start();
-+        snap_state.saved_vm_running = false;
-+    }
-+
-+    DPRINTF("timing: process_savevm_finalize (full) took %ld ms\n",
-+        qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - start_time);
-+}
-+
-+static void coroutine_fn process_savevm_co(void *opaque)
-+{
-+    int ret;
-+    int64_t maxlen;
-+    BdrvNextIterator it;
-+    BlockDriverState *bs = NULL;
-+
-+#ifdef DEBUG_SAVEVM_STATE
-+    int64_t start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
-+#endif
-+
-+    ret = qemu_file_get_error(snap_state.file);
-+    if (ret < 0) {
-+        save_snapshot_error("qemu_savevm_state_setup failed");
-+        return;
-+    }
-+
-+    while (snap_state.state == SAVE_STATE_ACTIVE) {
-+        uint64_t pending_size, pend_precopy, pend_postcopy;
-+
-+        /* pending is expected to be called without iothread lock */
-+        qemu_mutex_unlock_iothread();
-+        qemu_savevm_state_pending_exact(&pend_precopy, &pend_postcopy);
-+        qemu_mutex_lock_iothread();
-+
-+        pending_size = pend_precopy + pend_postcopy;
-+
-+        /*
-+         * A guest reaching this cutoff is dirtying lots of RAM. It should be
-+         * large enough so that the guest can't dirty this much between the
-+         * check and the guest actually being stopped, but it should be small
-+         * enough to avoid long downtimes for non-hibernation snapshots.
-+         */
-+        maxlen = blk_getlength(snap_state.target) - 100*1024*1024;
-+
-+        /* Note that there is no progress for pend_postcopy when iterating */
-+        if (pending_size - pend_postcopy > 400000 && snap_state.bs_pos + pending_size < maxlen) {
-+            ret = qemu_savevm_state_iterate(snap_state.file, false);
-+            if (ret < 0) {
-+                save_snapshot_error("qemu_savevm_state_iterate error %d", ret);
-+                break;
-+            }
-+            DPRINTF("savevm iterate pending size %lu ret %d\n", pending_size, ret);
-+        } else {
-+            qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
-+            ret = global_state_store();
-+            if (ret) {
-+                save_snapshot_error("global_state_store error %d", ret);
-+                break;
-+            }
-+
-+            DPRINTF("savevm iterate complete\n");
-+            break;
-+        }
-+    }
-+
-+    DPRINTF("timing: process_savevm_co took %ld ms\n",
-+        qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - start_time);
-+
-+#ifdef DEBUG_SAVEVM_STATE
-+    int64_t start_time_flush = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
-+#endif
-+    /* If a drive runs in an IOThread we can flush it async, and only
-+     * need to sync-flush whatever IO happens between now and
-+     * vm_stop_force_state. bdrv_next can only be called from main AioContext,
-+     * so move there now and after every flush.
-+     */
-+    aio_co_reschedule_self(qemu_get_aio_context());
-+    for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
-+        /* target has BDRV_O_NO_FLUSH, no sense calling bdrv_flush on it */
-+        if (bs == blk_bs(snap_state.target)) {
-+            continue;
-+        }
-+
-+        AioContext *bs_ctx = bdrv_get_aio_context(bs);
-+        if (bs_ctx != qemu_get_aio_context()) {
-+            DPRINTF("savevm: async flushing drive %s\n", bs->filename);
-+            aio_co_reschedule_self(bs_ctx);
-+            bdrv_graph_co_rdlock();
-+            bdrv_flush(bs);
-+            bdrv_graph_co_rdunlock();
-+            aio_co_reschedule_self(qemu_get_aio_context());
-+        }
-+    }
-+
-+    DPRINTF("timing: async flushing took %ld ms\n",
-+        qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - start_time_flush);
-+
-+    qemu_bh_schedule(snap_state.finalize_bh);
-+}
-+
-+void qmp_savevm_start(const char *statefile, Error **errp)
-+{
-+    Error *local_err = NULL;
-+    MigrationState *ms = migrate_get_current();
-+    AioContext *iohandler_ctx = iohandler_get_aio_context();
-+
-+    int bdrv_oflags = BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_NO_FLUSH;
-+
-+    if (snap_state.state != SAVE_STATE_DONE) {
-+        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
-+                  "VM snapshot already started\n");
-+        return;
-+    }
-+
-+    if (migration_is_running(ms->state)) {
-+        error_set(errp, ERROR_CLASS_GENERIC_ERROR, QERR_MIGRATION_ACTIVE);
-+        return;
-+    }
-+
-+    if (migrate_use_block()) {
-+        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
-+                  "Block migration and snapshots are incompatible");
-+        return;
-+    }
-+
-+    /* initialize snapshot info */
-+    snap_state.saved_vm_running = runstate_is_running();
-+    snap_state.bs_pos = 0;
-+    snap_state.total_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
-+    snap_state.blocker = NULL;
-+    snap_state.target_close_wait = (QemuCoSleep){ .to_wake = NULL };
-+
-+    if (snap_state.error) {
-+        error_free(snap_state.error);
-+        snap_state.error = NULL;
-+    }
-+
-+    if (!statefile) {
-+        vm_stop(RUN_STATE_SAVE_VM);
-+        snap_state.state = SAVE_STATE_COMPLETED;
-+        return;
-+    }
-+
-+    if (qemu_savevm_state_blocked(errp)) {
-+        return;
-+    }
-+
-+    /* Open the image */
-+    QDict *options = NULL;
-+    options = qdict_new();
-+    qdict_put_str(options, "driver", "raw");
-+    snap_state.target = blk_new_open(statefile, NULL, options, bdrv_oflags, &local_err);
-+    if (!snap_state.target) {
-+        error_set(errp, ERROR_CLASS_GENERIC_ERROR, "failed to open '%s'", statefile);
-+        goto restart;
-+    }
-+
-+    QIOChannel *ioc = QIO_CHANNEL(qio_channel_savevm_async_new(snap_state.target,
-+                                                               &snap_state.bs_pos));
-+    snap_state.file = qemu_file_new_output(ioc);
-+
-+    if (!snap_state.file) {
-+        error_set(errp, ERROR_CLASS_GENERIC_ERROR, "failed to open '%s'", statefile);
-+        goto restart;
-+    }
-+
-+    /*
-+     * qemu_savevm_* paths use migration code and expect a migration state.
-+     * State is cleared in process_savevm_co, but has to be initialized
-+     * here (blocking main thread, from QMP) to avoid race conditions.
-+     */
-+    migrate_init(ms);
-+    memset(&ram_counters, 0, sizeof(ram_counters));
-+    ms->to_dst_file = snap_state.file;
-+
-+    error_setg(&snap_state.blocker, "block device is in use by savevm");
-+    blk_op_block_all(snap_state.target, snap_state.blocker);
-+
-+    snap_state.state = SAVE_STATE_ACTIVE;
-+    snap_state.finalize_bh = qemu_bh_new(process_savevm_finalize, &snap_state);
-+    snap_state.co = qemu_coroutine_create(&process_savevm_co, NULL);
-+    qemu_mutex_unlock_iothread();
-+    qemu_savevm_state_header(snap_state.file);
-+    qemu_savevm_state_setup(snap_state.file);
-+    qemu_mutex_lock_iothread();
-+
-+    /* Async processing from here on out happens in iohandler context, so let
-+     * the target bdrv have its home there.
-+     */
-+    blk_set_aio_context(snap_state.target, iohandler_ctx, &local_err);
-+
-+    aio_co_schedule(iohandler_ctx, snap_state.co);
-+
-+    return;
-+
-+restart:
-+
-+    save_snapshot_error("setup failed");
-+
-+    if (snap_state.saved_vm_running) {
-+        vm_start();
-+        snap_state.saved_vm_running = false;
-+    }
-+}
-+
-+void coroutine_fn qmp_savevm_end(Error **errp)
-+{
-+    int64_t timeout;
-+
-+    if (snap_state.state == SAVE_STATE_DONE) {
-+        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
-+                  "VM snapshot not started\n");
-+        return;
-+    }
-+
-+    if (snap_state.state == SAVE_STATE_ACTIVE) {
-+        snap_state.state = SAVE_STATE_CANCELLED;
-+        goto wait_for_close;
-+    }
-+
-+    if (snap_state.saved_vm_running) {
-+        vm_start();
-+        snap_state.saved_vm_running = false;
-+    }
-+
-+    snap_state.state = SAVE_STATE_DONE;
-+
-+wait_for_close:
-+    if (!snap_state.target) {
-+        DPRINTF("savevm-end: no target file open\n");
-+        return;
-+    }
-+
-+    /* wait until cleanup is done before returning, this ensures that after this
-+     * call exits the statefile will be closed and can be removed immediately */
-+    DPRINTF("savevm-end: waiting for cleanup\n");
-+    timeout = 30L * 1000 * 1000 * 1000;
-+    qemu_co_sleep_ns_wakeable(&snap_state.target_close_wait,
-+                              QEMU_CLOCK_REALTIME, timeout);
-+    if (snap_state.target) {
-+        save_snapshot_error("timeout waiting for target file close in "
-+                            "qmp_savevm_end");
-+        /* we cannot assume the snapshot finished in this case, so leave the
-+         * state alone - caller has to figure something out */
-+        return;
-+    }
-+
-+    // File closed and no other error, so ensure next snapshot can be started.
-+    if (snap_state.state != SAVE_STATE_ERROR) {
-+        snap_state.state = SAVE_STATE_DONE;
-+    }
-+
-+    DPRINTF("savevm-end: cleanup done\n");
-+}
-+
-+// FIXME: Deprecated
-+void qmp_snapshot_drive(const char *device, const char *name, Error **errp)
-+{
-+    // Compatibility to older qemu-server.
-+    qmp_blockdev_snapshot_internal_sync(device, name, errp);
-+}
-+
-+// FIXME: Deprecated
-+void qmp_delete_drive_snapshot(const char *device, const char *name,
-+                               Error **errp)
-+{
-+    // Compatibility to older qemu-server.
-+    (void)qmp_blockdev_snapshot_delete_internal_sync(device, NULL, name, errp);
-+}
-+
-+int load_snapshot_from_blockdev(const char *filename, Error **errp)
-+{
-+    BlockBackend *be;
-+    Error *local_err = NULL;
-+    Error *blocker = NULL;
-+
-+    QEMUFile *f;
-+    size_t bs_pos = 0;
-+    int ret = -EINVAL;
-+
-+    be = blk_new_open(filename, NULL, NULL, 0, &local_err);
-+
-+    if (!be) {
-+        error_setg(errp, "Could not open VM state file");
-+        goto the_end;
-+    }
-+
-+    error_setg(&blocker, "block device is in use by load state");
-+    blk_op_block_all(be, blocker);
-+
-+    /* restore the VM state */
-+    f = qemu_file_new_input(QIO_CHANNEL(qio_channel_savevm_async_new(be, &bs_pos)));
-+    if (!f) {
-+        error_setg(errp, "Could not open VM state file");
-+        goto the_end;
-+    }
-+
-+    qemu_system_reset(SHUTDOWN_CAUSE_NONE);
-+    ret = qemu_loadvm_state(f);
-+
-+    /* dirty bitmap migration has a special case we need to trigger manually */
-+    dirty_bitmap_mig_before_vm_start();
-+
-+    qemu_fclose(f);
-+    migration_incoming_state_destroy();
-+    if (ret < 0) {
-+        error_setg_errno(errp, -ret, "Error while loading VM state");
-+        goto the_end;
-+    }
-+
-+    ret = 0;
-+
-+ the_end:
-+    if (be) {
-+        blk_op_unblock_all(be, blocker);
-+        error_free(blocker);
-+        blk_unref(be);
-+    }
-+    return ret;
-+}
-diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
-index 6c559b48c8..435f9334f9 100644
---- a/monitor/hmp-cmds.c
-+++ b/monitor/hmp-cmds.c
-@@ -22,6 +22,7 @@
- #include "monitor/monitor-internal.h"
- #include "qapi/error.h"
- #include "qapi/qapi-commands-control.h"
-+#include "qapi/qapi-commands-migration.h"
- #include "qapi/qapi-commands-misc.h"
- #include "qapi/qmp/qdict.h"
- #include "qapi/qmp/qerror.h"
-@@ -443,3 +444,60 @@ void hmp_info_mtree(Monitor *mon, const QDict *qdict)
-     mtree_info(flatview, dispatch_tree, owner, disabled);
- }
-+
-+void hmp_savevm_start(Monitor *mon, const QDict *qdict)
-+{
-+    Error *errp = NULL;
-+    const char *statefile = qdict_get_try_str(qdict, "statefile");
-+
-+    qmp_savevm_start(statefile, &errp);
-+    hmp_handle_error(mon, errp);
-+}
-+
-+void hmp_snapshot_drive(Monitor *mon, const QDict *qdict)
-+{
-+    Error *errp = NULL;
-+    const char *name = qdict_get_str(qdict, "name");
-+    const char *device = qdict_get_str(qdict, "device");
-+
-+    qmp_snapshot_drive(device, name, &errp);
-+    hmp_handle_error(mon, errp);
-+}
-+
-+void hmp_delete_drive_snapshot(Monitor *mon, const QDict *qdict)
-+{
-+    Error *errp = NULL;
-+    const char *name = qdict_get_str(qdict, "name");
-+    const char *device = qdict_get_str(qdict, "device");
-+
-+    qmp_delete_drive_snapshot(device, name, &errp);
-+    hmp_handle_error(mon, errp);
-+}
-+
-+void coroutine_fn hmp_savevm_end(Monitor *mon, const QDict *qdict)
-+{
-+    Error *errp = NULL;
-+
-+    qmp_savevm_end(&errp);
-+    hmp_handle_error(mon, errp);
-+}
-+
-+void hmp_info_savevm(Monitor *mon, const QDict *qdict)
-+{
-+    SaveVMInfo *info;
-+    info = qmp_query_savevm(NULL);
-+
-+    if (info->status) {
-+        monitor_printf(mon, "savevm status: %s\n", info->status);
-+        monitor_printf(mon, "total time: %" PRIu64 " milliseconds\n",
-+                       info->total_time);
-+    } else {
-+        monitor_printf(mon, "savevm status: not running\n");
-+    }
-+    if (info->has_bytes) {
-+        monitor_printf(mon, "Bytes saved: %"PRIu64"\n", info->bytes);
-+    }
-+    if (info->error) {
-+        monitor_printf(mon, "Error: %s\n", info->error);
-+    }
-+}
-diff --git a/qapi/migration.json b/qapi/migration.json
-index c84fa10e86..1702b92553 100644
---- a/qapi/migration.json
-+++ b/qapi/migration.json
-@@ -261,6 +261,40 @@
-            '*compression': 'CompressionStats',
-            '*socket-address': ['SocketAddress'] } }
-+##
-+# @SaveVMInfo:
-+#
-+# Information about current migration process.
-+#
-+# @status: string describing the current savevm status.
-+#          This can be 'active', 'completed', 'failed'.
-+#          If this field is not returned, no savevm process
-+#          has been initiated
-+#
-+# @error: string containing error message is status is failed.
-+#
-+# @total-time: total amount of milliseconds since savevm started.
-+#              If savevm has ended, it returns the total save time
-+#
-+# @bytes: total amount of data transfered
-+#
-+# Since: 1.3
-+##
-+{ 'struct': 'SaveVMInfo',
-+  'data': {'*status': 'str', '*error': 'str',
-+           '*total-time': 'int', '*bytes': 'int'} }
-+
-+##
-+# @query-savevm:
-+#
-+# Returns information about current savevm process.
-+#
-+# Returns: @SaveVMInfo
-+#
-+# Since: 1.3
-+##
-+{ 'command': 'query-savevm', 'returns': 'SaveVMInfo' }
-+
- ##
- # @query-migrate:
- #
-diff --git a/qapi/misc.json b/qapi/misc.json
-index 6ddd16ea28..098c9bbe93 100644
---- a/qapi/misc.json
-+++ b/qapi/misc.json
-@@ -469,6 +469,38 @@
- ##
- { 'command': 'query-fdsets', 'returns': ['FdsetInfo'] }
-+##
-+# @savevm-start:
-+#
-+# Prepare for snapshot and halt VM. Save VM state to statefile.
-+#
-+##
-+{ 'command': 'savevm-start', 'data': { '*statefile': 'str' } }
-+
-+##
-+# @snapshot-drive:
-+#
-+# Create an internal drive snapshot.
-+#
-+##
-+{ 'command': 'snapshot-drive', 'data': { 'device': 'str', 'name': 'str' } }
-+
-+##
-+# @delete-drive-snapshot:
-+#
-+# Delete a drive snapshot.
-+#
-+##
-+{ 'command': 'delete-drive-snapshot', 'data': { 'device': 'str', 'name': 'str' } }
-+
-+##
-+# @savevm-end:
-+#
-+# Resume VM after a snapshot.
-+#
-+##
-+{ 'command': 'savevm-end', 'coroutine': true }
-+
- ##
- # @CommandLineParameterType:
- #
-diff --git a/qemu-options.hx b/qemu-options.hx
-index 59bdf67a2c..fc6cb23dd9 100644
---- a/qemu-options.hx
-+++ b/qemu-options.hx
-@@ -4378,6 +4378,18 @@ SRST
-     Start right away with a saved state (``loadvm`` in monitor)
- ERST
-+DEF("loadstate", HAS_ARG, QEMU_OPTION_loadstate, \
-+    "-loadstate file\n" \
-+    "                start right away with a saved state\n",
-+    QEMU_ARCH_ALL)
-+SRST
-+``-loadstate file``
-+  Start right away with a saved state. This option does not rollback
-+  disk state like @code{loadvm}, so user must make sure that disk
-+  have correct state. @var{file} can be any valid device URL. See the section
-+  for "Device URL Syntax" for more information.
-+ERST
-+
- #ifndef _WIN32
- DEF("daemonize", 0, QEMU_OPTION_daemonize, \
-     "-daemonize      daemonize QEMU after initializing\n", QEMU_ARCH_ALL)
-diff --git a/softmmu/vl.c b/softmmu/vl.c
-index ea20b23e4c..0eabc71b68 100644
---- a/softmmu/vl.c
-+++ b/softmmu/vl.c
-@@ -164,6 +164,7 @@ static const char *accelerators;
- static bool have_custom_ram_size;
- static const char *ram_memdev_id;
- static QDict *machine_opts_dict;
-+static const char *loadstate;
- static QTAILQ_HEAD(, ObjectOption) object_opts = QTAILQ_HEAD_INITIALIZER(object_opts);
- static QTAILQ_HEAD(, DeviceOption) device_opts = QTAILQ_HEAD_INITIALIZER(device_opts);
- static int display_remote;
-@@ -2612,6 +2613,12 @@ void qmp_x_exit_preconfig(Error **errp)
-     if (loadvm) {
-         load_snapshot(loadvm, NULL, false, NULL, &error_fatal);
-+    } else if (loadstate) {
-+        Error *local_err = NULL;
-+        if (load_snapshot_from_blockdev(loadstate, &local_err) < 0) {
-+            error_report_err(local_err);
-+            autostart = 0;
-+        }
-     }
-     if (replay_mode != REPLAY_MODE_NONE) {
-         replay_vmstate_init();
-@@ -3159,6 +3166,9 @@ void qemu_init(int argc, char **argv)
-             case QEMU_OPTION_loadvm:
-                 loadvm = optarg;
-                 break;
-+            case QEMU_OPTION_loadstate:
-+                loadstate = optarg;
-+                break;
-             case QEMU_OPTION_full_screen:
-                 dpy.has_full_screen = true;
-                 dpy.full_screen = true;
diff --git a/debian/patches/pve/0018-PVE-add-optional-buffer-size-to-QEMUFile.patch b/debian/patches/pve/0018-PVE-add-optional-buffer-size-to-QEMUFile.patch
deleted file mode 100644 (file)
index e638f0e..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Mon, 4 May 2020 11:05:08 +0200
-Subject: [PATCH] PVE: add optional buffer size to QEMUFile
-
-So we can use a 4M buffer for savevm-async which should
-increase performance storing the state onto ceph.
-
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-[increase max IOV count in QEMUFile to actually write more data]
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-[FE: adapt to removal of QEMUFileOps]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- migration/qemu-file.c    | 49 +++++++++++++++++++++++++++-------------
- migration/qemu-file.h    |  2 ++
- migration/savevm-async.c |  5 ++--
- 3 files changed, 38 insertions(+), 18 deletions(-)
-
-diff --git a/migration/qemu-file.c b/migration/qemu-file.c
-index 102ab3b439..5ced17aba4 100644
---- a/migration/qemu-file.c
-+++ b/migration/qemu-file.c
-@@ -31,8 +31,8 @@
- #include "trace.h"
- #include "qapi/error.h"
--#define IO_BUF_SIZE 32768
--#define MAX_IOV_SIZE MIN_CONST(IOV_MAX, 64)
-+#define DEFAULT_IO_BUF_SIZE 32768
-+#define MAX_IOV_SIZE MIN_CONST(IOV_MAX, 256)
- struct QEMUFile {
-     const QEMUFileHooks *hooks;
-@@ -55,7 +55,8 @@ struct QEMUFile {
-     int buf_index;
-     int buf_size; /* 0 when writing */
--    uint8_t buf[IO_BUF_SIZE];
-+    size_t buf_allocated_size;
-+    uint8_t *buf;
-     DECLARE_BITMAP(may_free, MAX_IOV_SIZE);
-     struct iovec iov[MAX_IOV_SIZE];
-@@ -127,7 +128,9 @@ bool qemu_file_mode_is_not_valid(const char *mode)
-     return false;
- }
--static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable)
-+static QEMUFile *qemu_file_new_impl(QIOChannel *ioc,
-+                                    bool is_writable,
-+                                    size_t buffer_size)
- {
-     QEMUFile *f;
-@@ -136,6 +139,8 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable)
-     object_ref(ioc);
-     f->ioc = ioc;
-     f->is_writable = is_writable;
-+    f->buf_allocated_size = buffer_size;
-+    f->buf = malloc(buffer_size);
-     return f;
- }
-@@ -146,17 +151,27 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable)
-  */
- QEMUFile *qemu_file_get_return_path(QEMUFile *f)
- {
--    return qemu_file_new_impl(f->ioc, !f->is_writable);
-+    return qemu_file_new_impl(f->ioc, !f->is_writable, DEFAULT_IO_BUF_SIZE);
- }
- QEMUFile *qemu_file_new_output(QIOChannel *ioc)
- {
--    return qemu_file_new_impl(ioc, true);
-+    return qemu_file_new_impl(ioc, true, DEFAULT_IO_BUF_SIZE);
-+}
-+
-+QEMUFile *qemu_file_new_output_sized(QIOChannel *ioc, size_t buffer_size)
-+{
-+    return qemu_file_new_impl(ioc, true, buffer_size);
- }
- QEMUFile *qemu_file_new_input(QIOChannel *ioc)
- {
--    return qemu_file_new_impl(ioc, false);
-+    return qemu_file_new_impl(ioc, false, DEFAULT_IO_BUF_SIZE);
-+}
-+
-+QEMUFile *qemu_file_new_input_sized(QIOChannel *ioc, size_t buffer_size)
-+{
-+    return qemu_file_new_impl(ioc, false, buffer_size);
- }
- void qemu_file_set_hooks(QEMUFile *f, const QEMUFileHooks *hooks)
-@@ -414,7 +429,7 @@ static ssize_t qemu_fill_buffer(QEMUFile *f)
-     do {
-         len = qio_channel_read(f->ioc,
-                                (char *)f->buf + pending,
--                               IO_BUF_SIZE - pending,
-+                               f->buf_allocated_size - pending,
-                                &local_error);
-         if (len == QIO_CHANNEL_ERR_BLOCK) {
-             if (qemu_in_coroutine()) {
-@@ -464,6 +479,8 @@ int qemu_fclose(QEMUFile *f)
-     }
-     g_clear_pointer(&f->ioc, object_unref);
-+    free(f->buf);
-+
-     /* If any error was spotted before closing, we should report it
-      * instead of the close() return value.
-      */
-@@ -518,7 +535,7 @@ static void add_buf_to_iovec(QEMUFile *f, size_t len)
- {
-     if (!add_to_iovec(f, f->buf + f->buf_index, len, false)) {
-         f->buf_index += len;
--        if (f->buf_index == IO_BUF_SIZE) {
-+        if (f->buf_index == f->buf_allocated_size) {
-             qemu_fflush(f);
-         }
-     }
-@@ -544,7 +561,7 @@ void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, size_t size)
-     }
-     while (size > 0) {
--        l = IO_BUF_SIZE - f->buf_index;
-+        l = f->buf_allocated_size - f->buf_index;
-         if (l > size) {
-             l = size;
-         }
-@@ -591,8 +608,8 @@ size_t qemu_peek_buffer(QEMUFile *f, uint8_t **buf, size_t size, size_t offset)
-     size_t index;
-     assert(!qemu_file_is_writable(f));
--    assert(offset < IO_BUF_SIZE);
--    assert(size <= IO_BUF_SIZE - offset);
-+    assert(offset < f->buf_allocated_size);
-+    assert(size <= f->buf_allocated_size - offset);
-     /* The 1st byte to read from */
-     index = f->buf_index + offset;
-@@ -642,7 +659,7 @@ size_t qemu_get_buffer(QEMUFile *f, uint8_t *buf, size_t size)
-         size_t res;
-         uint8_t *src;
--        res = qemu_peek_buffer(f, &src, MIN(pending, IO_BUF_SIZE), 0);
-+        res = qemu_peek_buffer(f, &src, MIN(pending, f->buf_allocated_size), 0);
-         if (res == 0) {
-             return done;
-         }
-@@ -676,7 +693,7 @@ size_t qemu_get_buffer(QEMUFile *f, uint8_t *buf, size_t size)
-  */
- size_t qemu_get_buffer_in_place(QEMUFile *f, uint8_t **buf, size_t size)
- {
--    if (size < IO_BUF_SIZE) {
-+    if (size < f->buf_allocated_size) {
-         size_t res;
-         uint8_t *src = NULL;
-@@ -701,7 +718,7 @@ int qemu_peek_byte(QEMUFile *f, int offset)
-     int index = f->buf_index + offset;
-     assert(!qemu_file_is_writable(f));
--    assert(offset < IO_BUF_SIZE);
-+    assert(offset < f->buf_allocated_size);
-     if (index >= f->buf_size) {
-         qemu_fill_buffer(f);
-@@ -853,7 +870,7 @@ static int qemu_compress_data(z_stream *stream, uint8_t *dest, size_t dest_len,
- ssize_t qemu_put_compression_data(QEMUFile *f, z_stream *stream,
-                                   const uint8_t *p, size_t size)
- {
--    ssize_t blen = IO_BUF_SIZE - f->buf_index - sizeof(int32_t);
-+    ssize_t blen = f->buf_allocated_size - f->buf_index - sizeof(int32_t);
-     if (blen < compressBound(size)) {
-         return -1;
-diff --git a/migration/qemu-file.h b/migration/qemu-file.h
-index 9d0155a2a1..cc06240e8d 100644
---- a/migration/qemu-file.h
-+++ b/migration/qemu-file.h
-@@ -63,7 +63,9 @@ typedef struct QEMUFileHooks {
- } QEMUFileHooks;
- QEMUFile *qemu_file_new_input(QIOChannel *ioc);
-+QEMUFile *qemu_file_new_input_sized(QIOChannel *ioc, size_t buffer_size);
- QEMUFile *qemu_file_new_output(QIOChannel *ioc);
-+QEMUFile *qemu_file_new_output_sized(QIOChannel *ioc, size_t buffer_size);
- void qemu_file_set_hooks(QEMUFile *f, const QEMUFileHooks *hooks);
- int qemu_fclose(QEMUFile *f);
-diff --git a/migration/savevm-async.c b/migration/savevm-async.c
-index 24660af014..70273a2996 100644
---- a/migration/savevm-async.c
-+++ b/migration/savevm-async.c
-@@ -372,7 +372,7 @@ void qmp_savevm_start(const char *statefile, Error **errp)
-     QIOChannel *ioc = QIO_CHANNEL(qio_channel_savevm_async_new(snap_state.target,
-                                                                &snap_state.bs_pos));
--    snap_state.file = qemu_file_new_output(ioc);
-+    snap_state.file = qemu_file_new_output_sized(ioc, 4 * 1024 * 1024);
-     if (!snap_state.file) {
-         error_set(errp, ERROR_CLASS_GENERIC_ERROR, "failed to open '%s'", statefile);
-@@ -504,7 +504,8 @@ int load_snapshot_from_blockdev(const char *filename, Error **errp)
-     blk_op_block_all(be, blocker);
-     /* restore the VM state */
--    f = qemu_file_new_input(QIO_CHANNEL(qio_channel_savevm_async_new(be, &bs_pos)));
-+    f = qemu_file_new_input_sized(QIO_CHANNEL(qio_channel_savevm_async_new(be, &bs_pos)),
-+                                  4 * 1024 * 1024);
-     if (!f) {
-         error_setg(errp, "Could not open VM state file");
-         goto the_end;
diff --git a/debian/patches/pve/0018-PVE-add-savevm-async-for-background-state-snapshots.patch b/debian/patches/pve/0018-PVE-add-savevm-async-for-background-state-snapshots.patch
new file mode 100644 (file)
index 0000000..d42d0f0
--- /dev/null
@@ -0,0 +1,930 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Dietmar Maurer <dietmar@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:46 +0200
+Subject: [PATCH] PVE: add savevm-async for background state snapshots
+
+Put qemu_savevm_state_{header,setup} into the main loop and the rest
+of the iteration into a coroutine. The former need to lock the
+iothread (and we can't unlock it in the coroutine), and the latter
+can't deal with being in a separate thread, so a coroutine it must
+be.
+
+Truncate output file at 1024 boundary.
+
+Do not block the VM and save the state on aborting a snapshot, as the
+snapshot will be invalid anyway.
+
+Also, when aborting, wait for the target file to be closed, otherwise a
+client might run into race-conditions when trying to remove the file
+still opened by QEMU.
+
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
+Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
+[SR: improve aborting
+     register yank before migration_incoming_state_destroy]
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+[FE: further improve aborting
+     adapt to removal of QEMUFileOps
+     improve condition for entering final stage
+     adapt to QAPI and other changes for 8.0]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ hmp-commands-info.hx         |  13 +
+ hmp-commands.hx              |  33 +++
+ include/migration/snapshot.h |   2 +
+ include/monitor/hmp.h        |   5 +
+ migration/meson.build        |   1 +
+ migration/savevm-async.c     | 548 +++++++++++++++++++++++++++++++++++
+ monitor/hmp-cmds.c           |  58 ++++
+ qapi/migration.json          |  34 +++
+ qapi/misc.json               |  32 ++
+ qemu-options.hx              |  12 +
+ softmmu/vl.c                 |  10 +
+ 11 files changed, 748 insertions(+)
+ create mode 100644 migration/savevm-async.c
+
+diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
+index 47d63d26db..a166bff3d5 100644
+--- a/hmp-commands-info.hx
++++ b/hmp-commands-info.hx
+@@ -540,6 +540,19 @@ SRST
+     Show current migration parameters.
+ ERST
++    {
++        .name       = "savevm",
++        .args_type  = "",
++        .params     = "",
++        .help       = "show savevm status",
++        .cmd = hmp_info_savevm,
++    },
++
++SRST
++  ``info savevm``
++    Show savevm status.
++ERST
++
+     {
+         .name       = "balloon",
+         .args_type  = "",
+diff --git a/hmp-commands.hx b/hmp-commands.hx
+index bb85ee1d26..b66d7fc4ab 100644
+--- a/hmp-commands.hx
++++ b/hmp-commands.hx
+@@ -1846,3 +1846,36 @@ SRST
+   List event channels in the guest
+ ERST
+ #endif
++
++    {
++        .name       = "savevm-start",
++        .args_type  = "statefile:s?",
++        .params     = "[statefile]",
++        .help       = "Prepare for snapshot and halt VM. Save VM state to statefile.",
++        .cmd = hmp_savevm_start,
++    },
++
++    {
++        .name       = "snapshot-drive",
++        .args_type  = "device:s,name:s",
++        .params     = "device name",
++        .help       = "Create internal snapshot.",
++        .cmd = hmp_snapshot_drive,
++    },
++
++    {
++        .name       = "delete-drive-snapshot",
++        .args_type  = "device:s,name:s",
++        .params     = "device name",
++        .help       = "Delete internal snapshot.",
++        .cmd = hmp_delete_drive_snapshot,
++    },
++
++    {
++        .name       = "savevm-end",
++        .args_type  = "",
++        .params     = "",
++        .help       = "Resume VM after snaphot.",
++        .cmd        = hmp_savevm_end,
++        .coroutine  = true,
++    },
+diff --git a/include/migration/snapshot.h b/include/migration/snapshot.h
+index e72083b117..c846d37806 100644
+--- a/include/migration/snapshot.h
++++ b/include/migration/snapshot.h
+@@ -61,4 +61,6 @@ bool delete_snapshot(const char *name,
+                     bool has_devices, strList *devices,
+                     Error **errp);
++int load_snapshot_from_blockdev(const char *filename, Error **errp);
++
+ #endif
+diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h
+index fdb69b7f9c..c012bad741 100644
+--- a/include/monitor/hmp.h
++++ b/include/monitor/hmp.h
+@@ -28,6 +28,7 @@ void hmp_info_status(Monitor *mon, const QDict *qdict);
+ void hmp_info_uuid(Monitor *mon, const QDict *qdict);
+ void hmp_info_chardev(Monitor *mon, const QDict *qdict);
+ void hmp_info_mice(Monitor *mon, const QDict *qdict);
++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);
+@@ -94,6 +95,10 @@ void hmp_closefd(Monitor *mon, const QDict *qdict);
+ void hmp_mouse_move(Monitor *mon, const QDict *qdict);
+ void hmp_mouse_button(Monitor *mon, const QDict *qdict);
+ void hmp_mouse_set(Monitor *mon, const QDict *qdict);
++void hmp_savevm_start(Monitor *mon, const QDict *qdict);
++void hmp_snapshot_drive(Monitor *mon, const QDict *qdict);
++void hmp_delete_drive_snapshot(Monitor *mon, const QDict *qdict);
++void hmp_savevm_end(Monitor *mon, const QDict *qdict);
+ void hmp_sendkey(Monitor *mon, const QDict *qdict);
+ void coroutine_fn hmp_screendump(Monitor *mon, const QDict *qdict);
+ void hmp_chardev_add(Monitor *mon, const QDict *qdict);
+diff --git a/migration/meson.build b/migration/meson.build
+index 8a142fc7a9..a7824b5266 100644
+--- a/migration/meson.build
++++ b/migration/meson.build
+@@ -25,6 +25,7 @@ softmmu_ss.add(files(
+   'multifd-zlib.c',
+   'postcopy-ram.c',
+   'savevm.c',
++  'savevm-async.c',
+   'socket.c',
+   'tls.c',
+   'threadinfo.c',
+diff --git a/migration/savevm-async.c b/migration/savevm-async.c
+new file mode 100644
+index 0000000000..c5db9e9c1e
+--- /dev/null
++++ b/migration/savevm-async.c
+@@ -0,0 +1,548 @@
++#include "qemu/osdep.h"
++#include "migration/channel-savevm-async.h"
++#include "migration/migration.h"
++#include "migration/savevm.h"
++#include "migration/snapshot.h"
++#include "migration/global_state.h"
++#include "migration/ram.h"
++#include "migration/qemu-file.h"
++#include "sysemu/sysemu.h"
++#include "sysemu/runstate.h"
++#include "block/block.h"
++#include "sysemu/block-backend.h"
++#include "qapi/error.h"
++#include "qapi/qmp/qerror.h"
++#include "qapi/qmp/qdict.h"
++#include "qapi/qapi-commands-migration.h"
++#include "qapi/qapi-commands-misc.h"
++#include "qapi/qapi-commands-block.h"
++#include "qemu/cutils.h"
++#include "qemu/timer.h"
++#include "qemu/main-loop.h"
++#include "qemu/rcu.h"
++#include "qemu/yank.h"
++
++/* #define DEBUG_SAVEVM_STATE */
++
++#ifdef DEBUG_SAVEVM_STATE
++#define DPRINTF(fmt, ...) \
++    do { printf("savevm-async: " fmt, ## __VA_ARGS__); } while (0)
++#else
++#define DPRINTF(fmt, ...) \
++    do { } while (0)
++#endif
++
++enum {
++    SAVE_STATE_DONE,
++    SAVE_STATE_ERROR,
++    SAVE_STATE_ACTIVE,
++    SAVE_STATE_COMPLETED,
++    SAVE_STATE_CANCELLED
++};
++
++
++static struct SnapshotState {
++    BlockBackend *target;
++    size_t bs_pos;
++    int state;
++    Error *error;
++    Error *blocker;
++    int saved_vm_running;
++    QEMUFile *file;
++    int64_t total_time;
++    QEMUBH *finalize_bh;
++    Coroutine *co;
++    QemuCoSleep target_close_wait;
++} snap_state;
++
++static bool savevm_aborted(void)
++{
++    return snap_state.state == SAVE_STATE_CANCELLED ||
++        snap_state.state == SAVE_STATE_ERROR;
++}
++
++SaveVMInfo *qmp_query_savevm(Error **errp)
++{
++    SaveVMInfo *info = g_malloc0(sizeof(*info));
++    struct SnapshotState *s = &snap_state;
++
++    if (s->state != SAVE_STATE_DONE) {
++        info->has_bytes = true;
++        info->bytes = s->bs_pos;
++        switch (s->state) {
++        case SAVE_STATE_ERROR:
++            info->status = g_strdup("failed");
++            info->has_total_time = true;
++            info->total_time = s->total_time;
++            if (s->error) {
++                info->error = g_strdup(error_get_pretty(s->error));
++            }
++            break;
++        case SAVE_STATE_ACTIVE:
++            info->status = g_strdup("active");
++            info->has_total_time = true;
++            info->total_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME)
++                - s->total_time;
++            break;
++        case SAVE_STATE_COMPLETED:
++            info->status = g_strdup("completed");
++            info->has_total_time = true;
++            info->total_time = s->total_time;
++            break;
++        }
++    }
++
++    return info;
++}
++
++static int save_snapshot_cleanup(void)
++{
++    int ret = 0;
++
++    DPRINTF("save_snapshot_cleanup\n");
++
++    snap_state.total_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) -
++        snap_state.total_time;
++
++    if (snap_state.file) {
++        ret = qemu_fclose(snap_state.file);
++        snap_state.file = NULL;
++    }
++
++    if (snap_state.target) {
++        if (!savevm_aborted()) {
++            /* try to truncate, but ignore errors (will fail on block devices).
++            * note1: bdrv_read() need whole blocks, so we need to round up
++            * note2: PVE requires 1024 (BDRV_SECTOR_SIZE*2) alignment
++            */
++            size_t size = QEMU_ALIGN_UP(snap_state.bs_pos, BDRV_SECTOR_SIZE*2);
++            blk_truncate(snap_state.target, size, false, PREALLOC_MODE_OFF, 0, NULL);
++        }
++        blk_op_unblock_all(snap_state.target, snap_state.blocker);
++        error_free(snap_state.blocker);
++        snap_state.blocker = NULL;
++        blk_unref(snap_state.target);
++        snap_state.target = NULL;
++
++        qemu_co_sleep_wake(&snap_state.target_close_wait);
++    }
++
++    return ret;
++}
++
++static void save_snapshot_error(const char *fmt, ...)
++{
++    va_list ap;
++    char *msg;
++
++    va_start(ap, fmt);
++    msg = g_strdup_vprintf(fmt, ap);
++    va_end(ap);
++
++    DPRINTF("save_snapshot_error: %s\n", msg);
++
++    if (!snap_state.error) {
++        error_set(&snap_state.error, ERROR_CLASS_GENERIC_ERROR, "%s", msg);
++    }
++
++    g_free (msg);
++
++    snap_state.state = SAVE_STATE_ERROR;
++}
++
++static void process_savevm_finalize(void *opaque)
++{
++    int ret;
++    AioContext *iohandler_ctx = iohandler_get_aio_context();
++    MigrationState *ms = migrate_get_current();
++
++    bool aborted = savevm_aborted();
++
++#ifdef DEBUG_SAVEVM_STATE
++    int64_t start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
++#endif
++
++    qemu_bh_delete(snap_state.finalize_bh);
++    snap_state.finalize_bh = NULL;
++    snap_state.co = NULL;
++
++    /* We need to own the target bdrv's context for the following functions,
++     * so move it back. It can stay in the main context and live out its live
++     * there, since we're done with it after this method ends anyway.
++     */
++    aio_context_acquire(iohandler_ctx);
++    blk_set_aio_context(snap_state.target, qemu_get_aio_context(), NULL);
++    aio_context_release(iohandler_ctx);
++
++    ret = vm_stop_force_state(RUN_STATE_FINISH_MIGRATE);
++    if (ret < 0) {
++        save_snapshot_error("vm_stop_force_state error %d", ret);
++    }
++
++    if (!aborted) {
++        /* skip state saving if we aborted, snapshot will be invalid anyway */
++        (void)qemu_savevm_state_complete_precopy(snap_state.file, false, false);
++        ret = qemu_file_get_error(snap_state.file);
++        if (ret < 0) {
++            save_snapshot_error("qemu_savevm_state_complete_precopy error %d", ret);
++        }
++    }
++
++    DPRINTF("state saving complete\n");
++    DPRINTF("timing: process_savevm_finalize (state saving) took %ld ms\n",
++        qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - start_time);
++
++    /* clear migration state */
++    migrate_set_state(&ms->state, MIGRATION_STATUS_SETUP,
++        ret || aborted ? MIGRATION_STATUS_FAILED : MIGRATION_STATUS_COMPLETED);
++    ms->to_dst_file = NULL;
++
++    qemu_savevm_state_cleanup();
++
++    ret = save_snapshot_cleanup();
++    if (ret < 0) {
++        save_snapshot_error("save_snapshot_cleanup error %d", ret);
++    } else if (snap_state.state == SAVE_STATE_ACTIVE) {
++        snap_state.state = SAVE_STATE_COMPLETED;
++    } else if (aborted) {
++        /*
++         * If there was an error, there's no need to set a new one here.
++         * If the snapshot was canceled, leave setting the state to
++         * qmp_savevm_end(), which is waked by save_snapshot_cleanup().
++         */
++    } else {
++        save_snapshot_error("process_savevm_cleanup: invalid state: %d",
++                            snap_state.state);
++    }
++    if (snap_state.saved_vm_running) {
++        vm_start();
++        snap_state.saved_vm_running = false;
++    }
++
++    DPRINTF("timing: process_savevm_finalize (full) took %ld ms\n",
++        qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - start_time);
++}
++
++static void coroutine_fn process_savevm_co(void *opaque)
++{
++    int ret;
++    int64_t maxlen;
++    BdrvNextIterator it;
++    BlockDriverState *bs = NULL;
++
++#ifdef DEBUG_SAVEVM_STATE
++    int64_t start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
++#endif
++
++    ret = qemu_file_get_error(snap_state.file);
++    if (ret < 0) {
++        save_snapshot_error("qemu_savevm_state_setup failed");
++        return;
++    }
++
++    while (snap_state.state == SAVE_STATE_ACTIVE) {
++        uint64_t pending_size, pend_precopy, pend_postcopy;
++        uint64_t threshold = 400 * 1000;
++
++        /*
++         * pending_{estimate,exact} are expected to be called without iothread
++         * lock. Similar to what is done in migration.c, call the exact variant
++         * only once pend_precopy in the estimate is below the threshold.
++         */
++        qemu_mutex_unlock_iothread();
++        qemu_savevm_state_pending_estimate(&pend_precopy, &pend_postcopy);
++        if (pend_precopy <= threshold) {
++            qemu_savevm_state_pending_exact(&pend_precopy, &pend_postcopy);
++        }
++        qemu_mutex_lock_iothread();
++        pending_size = pend_precopy + pend_postcopy;
++
++        /*
++         * A guest reaching this cutoff is dirtying lots of RAM. It should be
++         * large enough so that the guest can't dirty this much between the
++         * check and the guest actually being stopped, but it should be small
++         * enough to avoid long downtimes for non-hibernation snapshots.
++         */
++        maxlen = blk_getlength(snap_state.target) - 100*1024*1024;
++
++        /* Note that there is no progress for pend_postcopy when iterating */
++        if (pend_precopy > threshold && snap_state.bs_pos + pending_size < maxlen) {
++            ret = qemu_savevm_state_iterate(snap_state.file, false);
++            if (ret < 0) {
++                save_snapshot_error("qemu_savevm_state_iterate error %d", ret);
++                break;
++            }
++            DPRINTF("savevm iterate pending size %lu ret %d\n", pending_size, ret);
++        } else {
++            qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
++            ret = global_state_store();
++            if (ret) {
++                save_snapshot_error("global_state_store error %d", ret);
++                break;
++            }
++
++            DPRINTF("savevm iterate complete\n");
++            break;
++        }
++    }
++
++    DPRINTF("timing: process_savevm_co took %ld ms\n",
++        qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - start_time);
++
++#ifdef DEBUG_SAVEVM_STATE
++    int64_t start_time_flush = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
++#endif
++    /* If a drive runs in an IOThread we can flush it async, and only
++     * need to sync-flush whatever IO happens between now and
++     * vm_stop_force_state. bdrv_next can only be called from main AioContext,
++     * so move there now and after every flush.
++     */
++    aio_co_reschedule_self(qemu_get_aio_context());
++    for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
++        /* target has BDRV_O_NO_FLUSH, no sense calling bdrv_flush on it */
++        if (bs == blk_bs(snap_state.target)) {
++            continue;
++        }
++
++        AioContext *bs_ctx = bdrv_get_aio_context(bs);
++        if (bs_ctx != qemu_get_aio_context()) {
++            DPRINTF("savevm: async flushing drive %s\n", bs->filename);
++            aio_co_reschedule_self(bs_ctx);
++            bdrv_graph_co_rdlock();
++            bdrv_flush(bs);
++            bdrv_graph_co_rdunlock();
++            aio_co_reschedule_self(qemu_get_aio_context());
++        }
++    }
++
++    DPRINTF("timing: async flushing took %ld ms\n",
++        qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - start_time_flush);
++
++    qemu_bh_schedule(snap_state.finalize_bh);
++}
++
++void qmp_savevm_start(const char *statefile, Error **errp)
++{
++    Error *local_err = NULL;
++    MigrationState *ms = migrate_get_current();
++    AioContext *iohandler_ctx = iohandler_get_aio_context();
++
++    int bdrv_oflags = BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_NO_FLUSH;
++
++    if (snap_state.state != SAVE_STATE_DONE) {
++        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
++                  "VM snapshot already started\n");
++        return;
++    }
++
++    if (migration_is_running(ms->state)) {
++        error_set(errp, ERROR_CLASS_GENERIC_ERROR, QERR_MIGRATION_ACTIVE);
++        return;
++    }
++
++    if (migrate_use_block()) {
++        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
++                  "Block migration and snapshots are incompatible");
++        return;
++    }
++
++    /* initialize snapshot info */
++    snap_state.saved_vm_running = runstate_is_running();
++    snap_state.bs_pos = 0;
++    snap_state.total_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
++    snap_state.blocker = NULL;
++    snap_state.target_close_wait = (QemuCoSleep){ .to_wake = NULL };
++
++    if (snap_state.error) {
++        error_free(snap_state.error);
++        snap_state.error = NULL;
++    }
++
++    if (!statefile) {
++        vm_stop(RUN_STATE_SAVE_VM);
++        snap_state.state = SAVE_STATE_COMPLETED;
++        return;
++    }
++
++    if (qemu_savevm_state_blocked(errp)) {
++        return;
++    }
++
++    /* Open the image */
++    QDict *options = NULL;
++    options = qdict_new();
++    qdict_put_str(options, "driver", "raw");
++    snap_state.target = blk_new_open(statefile, NULL, options, bdrv_oflags, &local_err);
++    if (!snap_state.target) {
++        error_set(errp, ERROR_CLASS_GENERIC_ERROR, "failed to open '%s'", statefile);
++        goto restart;
++    }
++
++    QIOChannel *ioc = QIO_CHANNEL(qio_channel_savevm_async_new(snap_state.target,
++                                                               &snap_state.bs_pos));
++    snap_state.file = qemu_file_new_output(ioc);
++
++    if (!snap_state.file) {
++        error_set(errp, ERROR_CLASS_GENERIC_ERROR, "failed to open '%s'", statefile);
++        goto restart;
++    }
++
++    /*
++     * qemu_savevm_* paths use migration code and expect a migration state.
++     * State is cleared in process_savevm_co, but has to be initialized
++     * here (blocking main thread, from QMP) to avoid race conditions.
++     */
++    migrate_init(ms);
++    memset(&ram_counters, 0, sizeof(ram_counters));
++    memset(&compression_counters, 0, sizeof(compression_counters));
++    ms->to_dst_file = snap_state.file;
++
++    error_setg(&snap_state.blocker, "block device is in use by savevm");
++    blk_op_block_all(snap_state.target, snap_state.blocker);
++
++    snap_state.state = SAVE_STATE_ACTIVE;
++    snap_state.finalize_bh = qemu_bh_new(process_savevm_finalize, &snap_state);
++    snap_state.co = qemu_coroutine_create(&process_savevm_co, NULL);
++    qemu_mutex_unlock_iothread();
++    qemu_savevm_state_header(snap_state.file);
++    qemu_savevm_state_setup(snap_state.file);
++    qemu_mutex_lock_iothread();
++
++    /* Async processing from here on out happens in iohandler context, so let
++     * the target bdrv have its home there.
++     */
++    blk_set_aio_context(snap_state.target, iohandler_ctx, &local_err);
++
++    aio_co_schedule(iohandler_ctx, snap_state.co);
++
++    return;
++
++restart:
++
++    save_snapshot_error("setup failed");
++
++    if (snap_state.saved_vm_running) {
++        vm_start();
++        snap_state.saved_vm_running = false;
++    }
++}
++
++void coroutine_fn qmp_savevm_end(Error **errp)
++{
++    int64_t timeout;
++
++    if (snap_state.state == SAVE_STATE_DONE) {
++        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
++                  "VM snapshot not started\n");
++        return;
++    }
++
++    if (snap_state.state == SAVE_STATE_ACTIVE) {
++        snap_state.state = SAVE_STATE_CANCELLED;
++        goto wait_for_close;
++    }
++
++    if (snap_state.saved_vm_running) {
++        vm_start();
++        snap_state.saved_vm_running = false;
++    }
++
++    snap_state.state = SAVE_STATE_DONE;
++
++wait_for_close:
++    if (!snap_state.target) {
++        DPRINTF("savevm-end: no target file open\n");
++        return;
++    }
++
++    /* wait until cleanup is done before returning, this ensures that after this
++     * call exits the statefile will be closed and can be removed immediately */
++    DPRINTF("savevm-end: waiting for cleanup\n");
++    timeout = 30L * 1000 * 1000 * 1000;
++    qemu_co_sleep_ns_wakeable(&snap_state.target_close_wait,
++                              QEMU_CLOCK_REALTIME, timeout);
++    if (snap_state.target) {
++        save_snapshot_error("timeout waiting for target file close in "
++                            "qmp_savevm_end");
++        /* we cannot assume the snapshot finished in this case, so leave the
++         * state alone - caller has to figure something out */
++        return;
++    }
++
++    // File closed and no other error, so ensure next snapshot can be started.
++    if (snap_state.state != SAVE_STATE_ERROR) {
++        snap_state.state = SAVE_STATE_DONE;
++    }
++
++    DPRINTF("savevm-end: cleanup done\n");
++}
++
++// FIXME: Deprecated
++void qmp_snapshot_drive(const char *device, const char *name, Error **errp)
++{
++    // Compatibility to older qemu-server.
++    qmp_blockdev_snapshot_internal_sync(device, name, errp);
++}
++
++// FIXME: Deprecated
++void qmp_delete_drive_snapshot(const char *device, const char *name,
++                               Error **errp)
++{
++    // Compatibility to older qemu-server.
++    (void)qmp_blockdev_snapshot_delete_internal_sync(device, NULL, name, errp);
++}
++
++int load_snapshot_from_blockdev(const char *filename, Error **errp)
++{
++    BlockBackend *be;
++    Error *local_err = NULL;
++    Error *blocker = NULL;
++
++    QEMUFile *f;
++    size_t bs_pos = 0;
++    int ret = -EINVAL;
++
++    be = blk_new_open(filename, NULL, NULL, 0, &local_err);
++
++    if (!be) {
++        error_setg(errp, "Could not open VM state file");
++        goto the_end;
++    }
++
++    error_setg(&blocker, "block device is in use by load state");
++    blk_op_block_all(be, blocker);
++
++    /* restore the VM state */
++    f = qemu_file_new_input(QIO_CHANNEL(qio_channel_savevm_async_new(be, &bs_pos)));
++    if (!f) {
++        error_setg(errp, "Could not open VM state file");
++        goto the_end;
++    }
++
++    qemu_system_reset(SHUTDOWN_CAUSE_NONE);
++    ret = qemu_loadvm_state(f);
++
++    /* dirty bitmap migration has a special case we need to trigger manually */
++    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");
++        goto the_end;
++    }
++
++    ret = 0;
++
++ the_end:
++    if (be) {
++        blk_op_unblock_all(be, blocker);
++        error_free(blocker);
++        blk_unref(be);
++    }
++    return ret;
++}
+diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
+index 6c559b48c8..435f9334f9 100644
+--- a/monitor/hmp-cmds.c
++++ b/monitor/hmp-cmds.c
+@@ -22,6 +22,7 @@
+ #include "monitor/monitor-internal.h"
+ #include "qapi/error.h"
+ #include "qapi/qapi-commands-control.h"
++#include "qapi/qapi-commands-migration.h"
+ #include "qapi/qapi-commands-misc.h"
+ #include "qapi/qmp/qdict.h"
+ #include "qapi/qmp/qerror.h"
+@@ -443,3 +444,60 @@ void hmp_info_mtree(Monitor *mon, const QDict *qdict)
+     mtree_info(flatview, dispatch_tree, owner, disabled);
+ }
++
++void hmp_savevm_start(Monitor *mon, const QDict *qdict)
++{
++    Error *errp = NULL;
++    const char *statefile = qdict_get_try_str(qdict, "statefile");
++
++    qmp_savevm_start(statefile, &errp);
++    hmp_handle_error(mon, errp);
++}
++
++void hmp_snapshot_drive(Monitor *mon, const QDict *qdict)
++{
++    Error *errp = NULL;
++    const char *name = qdict_get_str(qdict, "name");
++    const char *device = qdict_get_str(qdict, "device");
++
++    qmp_snapshot_drive(device, name, &errp);
++    hmp_handle_error(mon, errp);
++}
++
++void hmp_delete_drive_snapshot(Monitor *mon, const QDict *qdict)
++{
++    Error *errp = NULL;
++    const char *name = qdict_get_str(qdict, "name");
++    const char *device = qdict_get_str(qdict, "device");
++
++    qmp_delete_drive_snapshot(device, name, &errp);
++    hmp_handle_error(mon, errp);
++}
++
++void coroutine_fn hmp_savevm_end(Monitor *mon, const QDict *qdict)
++{
++    Error *errp = NULL;
++
++    qmp_savevm_end(&errp);
++    hmp_handle_error(mon, errp);
++}
++
++void hmp_info_savevm(Monitor *mon, const QDict *qdict)
++{
++    SaveVMInfo *info;
++    info = qmp_query_savevm(NULL);
++
++    if (info->status) {
++        monitor_printf(mon, "savevm status: %s\n", info->status);
++        monitor_printf(mon, "total time: %" PRIu64 " milliseconds\n",
++                       info->total_time);
++    } else {
++        monitor_printf(mon, "savevm status: not running\n");
++    }
++    if (info->has_bytes) {
++        monitor_printf(mon, "Bytes saved: %"PRIu64"\n", info->bytes);
++    }
++    if (info->error) {
++        monitor_printf(mon, "Error: %s\n", info->error);
++    }
++}
+diff --git a/qapi/migration.json b/qapi/migration.json
+index c84fa10e86..1702b92553 100644
+--- a/qapi/migration.json
++++ b/qapi/migration.json
+@@ -261,6 +261,40 @@
+            '*compression': 'CompressionStats',
+            '*socket-address': ['SocketAddress'] } }
++##
++# @SaveVMInfo:
++#
++# Information about current migration process.
++#
++# @status: string describing the current savevm status.
++#          This can be 'active', 'completed', 'failed'.
++#          If this field is not returned, no savevm process
++#          has been initiated
++#
++# @error: string containing error message is status is failed.
++#
++# @total-time: total amount of milliseconds since savevm started.
++#              If savevm has ended, it returns the total save time
++#
++# @bytes: total amount of data transfered
++#
++# Since: 1.3
++##
++{ 'struct': 'SaveVMInfo',
++  'data': {'*status': 'str', '*error': 'str',
++           '*total-time': 'int', '*bytes': 'int'} }
++
++##
++# @query-savevm:
++#
++# Returns information about current savevm process.
++#
++# Returns: @SaveVMInfo
++#
++# Since: 1.3
++##
++{ 'command': 'query-savevm', 'returns': 'SaveVMInfo' }
++
+ ##
+ # @query-migrate:
+ #
+diff --git a/qapi/misc.json b/qapi/misc.json
+index 6ddd16ea28..098c9bbe93 100644
+--- a/qapi/misc.json
++++ b/qapi/misc.json
+@@ -469,6 +469,38 @@
+ ##
+ { 'command': 'query-fdsets', 'returns': ['FdsetInfo'] }
++##
++# @savevm-start:
++#
++# Prepare for snapshot and halt VM. Save VM state to statefile.
++#
++##
++{ 'command': 'savevm-start', 'data': { '*statefile': 'str' } }
++
++##
++# @snapshot-drive:
++#
++# Create an internal drive snapshot.
++#
++##
++{ 'command': 'snapshot-drive', 'data': { 'device': 'str', 'name': 'str' } }
++
++##
++# @delete-drive-snapshot:
++#
++# Delete a drive snapshot.
++#
++##
++{ 'command': 'delete-drive-snapshot', 'data': { 'device': 'str', 'name': 'str' } }
++
++##
++# @savevm-end:
++#
++# Resume VM after a snapshot.
++#
++##
++{ 'command': 'savevm-end', 'coroutine': true }
++
+ ##
+ # @CommandLineParameterType:
+ #
+diff --git a/qemu-options.hx b/qemu-options.hx
+index 59bdf67a2c..fc6cb23dd9 100644
+--- a/qemu-options.hx
++++ b/qemu-options.hx
+@@ -4378,6 +4378,18 @@ SRST
+     Start right away with a saved state (``loadvm`` in monitor)
+ ERST
++DEF("loadstate", HAS_ARG, QEMU_OPTION_loadstate, \
++    "-loadstate file\n" \
++    "                start right away with a saved state\n",
++    QEMU_ARCH_ALL)
++SRST
++``-loadstate file``
++  Start right away with a saved state. This option does not rollback
++  disk state like @code{loadvm}, so user must make sure that disk
++  have correct state. @var{file} can be any valid device URL. See the section
++  for "Device URL Syntax" for more information.
++ERST
++
+ #ifndef _WIN32
+ DEF("daemonize", 0, QEMU_OPTION_daemonize, \
+     "-daemonize      daemonize QEMU after initializing\n", QEMU_ARCH_ALL)
+diff --git a/softmmu/vl.c b/softmmu/vl.c
+index ea20b23e4c..0eabc71b68 100644
+--- a/softmmu/vl.c
++++ b/softmmu/vl.c
+@@ -164,6 +164,7 @@ static const char *accelerators;
+ static bool have_custom_ram_size;
+ static const char *ram_memdev_id;
+ static QDict *machine_opts_dict;
++static const char *loadstate;
+ static QTAILQ_HEAD(, ObjectOption) object_opts = QTAILQ_HEAD_INITIALIZER(object_opts);
+ static QTAILQ_HEAD(, DeviceOption) device_opts = QTAILQ_HEAD_INITIALIZER(device_opts);
+ static int display_remote;
+@@ -2612,6 +2613,12 @@ void qmp_x_exit_preconfig(Error **errp)
+     if (loadvm) {
+         load_snapshot(loadvm, NULL, false, NULL, &error_fatal);
++    } else if (loadstate) {
++        Error *local_err = NULL;
++        if (load_snapshot_from_blockdev(loadstate, &local_err) < 0) {
++            error_report_err(local_err);
++            autostart = 0;
++        }
+     }
+     if (replay_mode != REPLAY_MODE_NONE) {
+         replay_vmstate_init();
+@@ -3159,6 +3166,9 @@ void qemu_init(int argc, char **argv)
+             case QEMU_OPTION_loadvm:
+                 loadvm = optarg;
+                 break;
++            case QEMU_OPTION_loadstate:
++                loadstate = optarg;
++                break;
+             case QEMU_OPTION_full_screen:
+                 dpy.has_full_screen = true;
+                 dpy.full_screen = true;
diff --git a/debian/patches/pve/0019-PVE-add-optional-buffer-size-to-QEMUFile.patch b/debian/patches/pve/0019-PVE-add-optional-buffer-size-to-QEMUFile.patch
new file mode 100644 (file)
index 0000000..cd7b508
--- /dev/null
@@ -0,0 +1,216 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Mon, 4 May 2020 11:05:08 +0200
+Subject: [PATCH] PVE: add optional buffer size to QEMUFile
+
+So we can use a 4M buffer for savevm-async which should
+increase performance storing the state onto ceph.
+
+Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
+[increase max IOV count in QEMUFile to actually write more data]
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[FE: adapt to removal of QEMUFileOps]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ migration/qemu-file.c    | 49 +++++++++++++++++++++++++++-------------
+ migration/qemu-file.h    |  2 ++
+ migration/savevm-async.c |  5 ++--
+ 3 files changed, 38 insertions(+), 18 deletions(-)
+
+diff --git a/migration/qemu-file.c b/migration/qemu-file.c
+index 102ab3b439..5ced17aba4 100644
+--- a/migration/qemu-file.c
++++ b/migration/qemu-file.c
+@@ -31,8 +31,8 @@
+ #include "trace.h"
+ #include "qapi/error.h"
+-#define IO_BUF_SIZE 32768
+-#define MAX_IOV_SIZE MIN_CONST(IOV_MAX, 64)
++#define DEFAULT_IO_BUF_SIZE 32768
++#define MAX_IOV_SIZE MIN_CONST(IOV_MAX, 256)
+ struct QEMUFile {
+     const QEMUFileHooks *hooks;
+@@ -55,7 +55,8 @@ struct QEMUFile {
+     int buf_index;
+     int buf_size; /* 0 when writing */
+-    uint8_t buf[IO_BUF_SIZE];
++    size_t buf_allocated_size;
++    uint8_t *buf;
+     DECLARE_BITMAP(may_free, MAX_IOV_SIZE);
+     struct iovec iov[MAX_IOV_SIZE];
+@@ -127,7 +128,9 @@ bool qemu_file_mode_is_not_valid(const char *mode)
+     return false;
+ }
+-static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable)
++static QEMUFile *qemu_file_new_impl(QIOChannel *ioc,
++                                    bool is_writable,
++                                    size_t buffer_size)
+ {
+     QEMUFile *f;
+@@ -136,6 +139,8 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable)
+     object_ref(ioc);
+     f->ioc = ioc;
+     f->is_writable = is_writable;
++    f->buf_allocated_size = buffer_size;
++    f->buf = malloc(buffer_size);
+     return f;
+ }
+@@ -146,17 +151,27 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable)
+  */
+ QEMUFile *qemu_file_get_return_path(QEMUFile *f)
+ {
+-    return qemu_file_new_impl(f->ioc, !f->is_writable);
++    return qemu_file_new_impl(f->ioc, !f->is_writable, DEFAULT_IO_BUF_SIZE);
+ }
+ QEMUFile *qemu_file_new_output(QIOChannel *ioc)
+ {
+-    return qemu_file_new_impl(ioc, true);
++    return qemu_file_new_impl(ioc, true, DEFAULT_IO_BUF_SIZE);
++}
++
++QEMUFile *qemu_file_new_output_sized(QIOChannel *ioc, size_t buffer_size)
++{
++    return qemu_file_new_impl(ioc, true, buffer_size);
+ }
+ QEMUFile *qemu_file_new_input(QIOChannel *ioc)
+ {
+-    return qemu_file_new_impl(ioc, false);
++    return qemu_file_new_impl(ioc, false, DEFAULT_IO_BUF_SIZE);
++}
++
++QEMUFile *qemu_file_new_input_sized(QIOChannel *ioc, size_t buffer_size)
++{
++    return qemu_file_new_impl(ioc, false, buffer_size);
+ }
+ void qemu_file_set_hooks(QEMUFile *f, const QEMUFileHooks *hooks)
+@@ -414,7 +429,7 @@ static ssize_t qemu_fill_buffer(QEMUFile *f)
+     do {
+         len = qio_channel_read(f->ioc,
+                                (char *)f->buf + pending,
+-                               IO_BUF_SIZE - pending,
++                               f->buf_allocated_size - pending,
+                                &local_error);
+         if (len == QIO_CHANNEL_ERR_BLOCK) {
+             if (qemu_in_coroutine()) {
+@@ -464,6 +479,8 @@ int qemu_fclose(QEMUFile *f)
+     }
+     g_clear_pointer(&f->ioc, object_unref);
++    free(f->buf);
++
+     /* If any error was spotted before closing, we should report it
+      * instead of the close() return value.
+      */
+@@ -518,7 +535,7 @@ static void add_buf_to_iovec(QEMUFile *f, size_t len)
+ {
+     if (!add_to_iovec(f, f->buf + f->buf_index, len, false)) {
+         f->buf_index += len;
+-        if (f->buf_index == IO_BUF_SIZE) {
++        if (f->buf_index == f->buf_allocated_size) {
+             qemu_fflush(f);
+         }
+     }
+@@ -544,7 +561,7 @@ void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, size_t size)
+     }
+     while (size > 0) {
+-        l = IO_BUF_SIZE - f->buf_index;
++        l = f->buf_allocated_size - f->buf_index;
+         if (l > size) {
+             l = size;
+         }
+@@ -591,8 +608,8 @@ size_t qemu_peek_buffer(QEMUFile *f, uint8_t **buf, size_t size, size_t offset)
+     size_t index;
+     assert(!qemu_file_is_writable(f));
+-    assert(offset < IO_BUF_SIZE);
+-    assert(size <= IO_BUF_SIZE - offset);
++    assert(offset < f->buf_allocated_size);
++    assert(size <= f->buf_allocated_size - offset);
+     /* The 1st byte to read from */
+     index = f->buf_index + offset;
+@@ -642,7 +659,7 @@ size_t qemu_get_buffer(QEMUFile *f, uint8_t *buf, size_t size)
+         size_t res;
+         uint8_t *src;
+-        res = qemu_peek_buffer(f, &src, MIN(pending, IO_BUF_SIZE), 0);
++        res = qemu_peek_buffer(f, &src, MIN(pending, f->buf_allocated_size), 0);
+         if (res == 0) {
+             return done;
+         }
+@@ -676,7 +693,7 @@ size_t qemu_get_buffer(QEMUFile *f, uint8_t *buf, size_t size)
+  */
+ size_t qemu_get_buffer_in_place(QEMUFile *f, uint8_t **buf, size_t size)
+ {
+-    if (size < IO_BUF_SIZE) {
++    if (size < f->buf_allocated_size) {
+         size_t res;
+         uint8_t *src = NULL;
+@@ -701,7 +718,7 @@ int qemu_peek_byte(QEMUFile *f, int offset)
+     int index = f->buf_index + offset;
+     assert(!qemu_file_is_writable(f));
+-    assert(offset < IO_BUF_SIZE);
++    assert(offset < f->buf_allocated_size);
+     if (index >= f->buf_size) {
+         qemu_fill_buffer(f);
+@@ -853,7 +870,7 @@ static int qemu_compress_data(z_stream *stream, uint8_t *dest, size_t dest_len,
+ ssize_t qemu_put_compression_data(QEMUFile *f, z_stream *stream,
+                                   const uint8_t *p, size_t size)
+ {
+-    ssize_t blen = IO_BUF_SIZE - f->buf_index - sizeof(int32_t);
++    ssize_t blen = f->buf_allocated_size - f->buf_index - sizeof(int32_t);
+     if (blen < compressBound(size)) {
+         return -1;
+diff --git a/migration/qemu-file.h b/migration/qemu-file.h
+index 9d0155a2a1..cc06240e8d 100644
+--- a/migration/qemu-file.h
++++ b/migration/qemu-file.h
+@@ -63,7 +63,9 @@ typedef struct QEMUFileHooks {
+ } QEMUFileHooks;
+ QEMUFile *qemu_file_new_input(QIOChannel *ioc);
++QEMUFile *qemu_file_new_input_sized(QIOChannel *ioc, size_t buffer_size);
+ QEMUFile *qemu_file_new_output(QIOChannel *ioc);
++QEMUFile *qemu_file_new_output_sized(QIOChannel *ioc, size_t buffer_size);
+ void qemu_file_set_hooks(QEMUFile *f, const QEMUFileHooks *hooks);
+ int qemu_fclose(QEMUFile *f);
+diff --git a/migration/savevm-async.c b/migration/savevm-async.c
+index c5db9e9c1e..effe6d1e0d 100644
+--- a/migration/savevm-async.c
++++ b/migration/savevm-async.c
+@@ -380,7 +380,7 @@ void qmp_savevm_start(const char *statefile, Error **errp)
+     QIOChannel *ioc = QIO_CHANNEL(qio_channel_savevm_async_new(snap_state.target,
+                                                                &snap_state.bs_pos));
+-    snap_state.file = qemu_file_new_output(ioc);
++    snap_state.file = qemu_file_new_output_sized(ioc, 4 * 1024 * 1024);
+     if (!snap_state.file) {
+         error_set(errp, ERROR_CLASS_GENERIC_ERROR, "failed to open '%s'", statefile);
+@@ -513,7 +513,8 @@ int load_snapshot_from_blockdev(const char *filename, Error **errp)
+     blk_op_block_all(be, blocker);
+     /* restore the VM state */
+-    f = qemu_file_new_input(QIO_CHANNEL(qio_channel_savevm_async_new(be, &bs_pos)));
++    f = qemu_file_new_input_sized(QIO_CHANNEL(qio_channel_savevm_async_new(be, &bs_pos)),
++                                  4 * 1024 * 1024);
+     if (!f) {
+         error_setg(errp, "Could not open VM state file");
+         goto the_end;
diff --git a/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch b/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch
deleted file mode 100644 (file)
index 39264ee..0000000
+++ /dev/null
@@ -1,232 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:47 +0200
-Subject: [PATCH] PVE: block: add the zeroinit block driver filter
-
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-[FE: adapt to changed function signatures]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- block/meson.build |   1 +
- block/zeroinit.c  | 200 ++++++++++++++++++++++++++++++++++++++++++++++
- 2 files changed, 201 insertions(+)
- create mode 100644 block/zeroinit.c
-
-diff --git a/block/meson.build b/block/meson.build
-index 382bec0e7d..253fe49fa2 100644
---- a/block/meson.build
-+++ b/block/meson.build
-@@ -44,6 +44,7 @@ block_ss.add(files(
-   'vmdk.c',
-   'vpc.c',
-   'write-threshold.c',
-+  'zeroinit.c',
- ), zstd, zlib, gnutls)
- softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c'))
-diff --git a/block/zeroinit.c b/block/zeroinit.c
-new file mode 100644
-index 0000000000..1257342724
---- /dev/null
-+++ b/block/zeroinit.c
-@@ -0,0 +1,200 @@
-+/*
-+ * Filter to fake a zero-initialized block device.
-+ *
-+ * Copyright (c) 2016 Wolfgang Bumiller <w.bumiller@proxmox.com>
-+ * Copyright (c) 2016 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 "qapi/error.h"
-+#include "block/block_int.h"
-+#include "block/block-io.h"
-+#include "qapi/qmp/qdict.h"
-+#include "qapi/qmp/qstring.h"
-+#include "qemu/cutils.h"
-+#include "qemu/option.h"
-+#include "qemu/module.h"
-+
-+typedef struct {
-+    bool has_zero_init;
-+    int64_t extents;
-+} BDRVZeroinitState;
-+
-+/* Valid blkverify filenames look like blkverify:path/to/raw_image:path/to/image */
-+static void zeroinit_parse_filename(const char *filename, QDict *options,
-+                                     Error **errp)
-+{
-+    QString *raw_path;
-+
-+    /* Parse the blkverify: prefix */
-+    if (!strstart(filename, "zeroinit:", &filename)) {
-+        /* There was no prefix; therefore, all options have to be already
-+           present in the QDict (except for the filename) */
-+        return;
-+    }
-+
-+    raw_path = qstring_from_str(filename);
-+    qdict_put(options, "x-next", raw_path);
-+}
-+
-+static QemuOptsList runtime_opts = {
-+    .name = "zeroinit",
-+    .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
-+    .desc = {
-+        {
-+            .name = "x-next",
-+            .type = QEMU_OPT_STRING,
-+            .help = "[internal use only, will be removed]",
-+        },
-+        {
-+            .name = "x-zeroinit",
-+            .type = QEMU_OPT_BOOL,
-+            .help = "set has_initialized_zero flag",
-+        },
-+        { /* end of list */ }
-+    },
-+};
-+
-+static int zeroinit_open(BlockDriverState *bs, QDict *options, int flags,
-+                          Error **errp)
-+{
-+    BDRVZeroinitState *s = bs->opaque;
-+    QemuOpts *opts;
-+    Error *local_err = NULL;
-+    int ret;
-+
-+    s->extents = 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;
-+    }
-+
-+    /* Open the raw file */
-+    bs->file = bdrv_open_child(qemu_opt_get(opts, "x-next"), options, "next",
-+                               bs, &child_of_bds,
-+                               BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
-+                               false, &local_err);
-+    if (local_err) {
-+        ret = -EINVAL;
-+        error_propagate(errp, local_err);
-+        goto fail;
-+    }
-+
-+    /* set the options */
-+    s->has_zero_init = qemu_opt_get_bool(opts, "x-zeroinit", true);
-+
-+    ret = 0;
-+fail:
-+    if (ret < 0) {
-+        bdrv_unref_child(bs, bs->file);
-+    }
-+    qemu_opts_del(opts);
-+    return ret;
-+}
-+
-+static void zeroinit_close(BlockDriverState *bs)
-+{
-+    BDRVZeroinitState *s = bs->opaque;
-+    (void)s;
-+}
-+
-+static coroutine_fn int64_t zeroinit_co_getlength(BlockDriverState *bs)
-+{
-+    return bdrv_co_getlength(bs->file->bs);
-+}
-+
-+static int coroutine_fn zeroinit_co_preadv(BlockDriverState *bs,
-+    int64_t offset, int64_t bytes, QEMUIOVector *qiov, BdrvRequestFlags flags)
-+{
-+    return bdrv_co_preadv(bs->file, offset, bytes, qiov, flags);
-+}
-+
-+static int coroutine_fn zeroinit_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
-+                                                 int64_t bytes, BdrvRequestFlags flags)
-+{
-+    BDRVZeroinitState *s = bs->opaque;
-+    if (offset >= s->extents)
-+        return 0;
-+    return bdrv_pwrite_zeroes(bs->file, offset, bytes, flags);
-+}
-+
-+static int coroutine_fn zeroinit_co_pwritev(BlockDriverState *bs,
-+    int64_t offset, int64_t bytes, QEMUIOVector *qiov, BdrvRequestFlags flags)
-+{
-+    BDRVZeroinitState *s = bs->opaque;
-+    int64_t extents = offset + bytes;
-+    if (extents > s->extents)
-+        s->extents = extents;
-+    return bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags);
-+}
-+
-+static coroutine_fn int zeroinit_co_flush(BlockDriverState *bs)
-+{
-+    return bdrv_co_flush(bs->file->bs);
-+}
-+
-+static int zeroinit_has_zero_init(BlockDriverState *bs)
-+{
-+    BDRVZeroinitState *s = bs->opaque;
-+    return s->has_zero_init;
-+}
-+
-+static int coroutine_fn zeroinit_co_pdiscard(BlockDriverState *bs,
-+                                             int64_t offset, int64_t bytes)
-+{
-+    return bdrv_co_pdiscard(bs->file, offset, bytes);
-+}
-+
-+static int zeroinit_co_truncate(BlockDriverState *bs, int64_t offset,
-+                                _Bool exact, PreallocMode prealloc,
-+                                BdrvRequestFlags req_flags, Error **errp)
-+{
-+    return bdrv_co_truncate(bs->file, offset, exact, prealloc, req_flags, errp);
-+}
-+
-+static coroutine_fn int zeroinit_co_get_info(BlockDriverState *bs,
-+                                             BlockDriverInfo *bdi)
-+{
-+    return bdrv_co_get_info(bs->file->bs, bdi);
-+}
-+
-+static BlockDriver bdrv_zeroinit = {
-+    .format_name                      = "zeroinit",
-+    .protocol_name                    = "zeroinit",
-+    .instance_size                    = sizeof(BDRVZeroinitState),
-+
-+    .bdrv_parse_filename              = zeroinit_parse_filename,
-+    .bdrv_file_open                   = zeroinit_open,
-+    .bdrv_close                       = zeroinit_close,
-+    .bdrv_co_getlength                = zeroinit_co_getlength,
-+    .bdrv_child_perm                  = bdrv_default_perms,
-+    .bdrv_co_flush_to_disk            = zeroinit_co_flush,
-+
-+    .bdrv_co_pwrite_zeroes            = zeroinit_co_pwrite_zeroes,
-+    .bdrv_co_pwritev                  = zeroinit_co_pwritev,
-+    .bdrv_co_preadv                   = zeroinit_co_preadv,
-+    .bdrv_co_flush                    = zeroinit_co_flush,
-+
-+    .is_filter                        = true,
-+
-+    .bdrv_has_zero_init               = zeroinit_has_zero_init,
-+
-+    .bdrv_co_pdiscard                 = zeroinit_co_pdiscard,
-+
-+    .bdrv_co_truncate                 = zeroinit_co_truncate,
-+    .bdrv_co_get_info                 = zeroinit_co_get_info,
-+};
-+
-+static void bdrv_zeroinit_init(void)
-+{
-+    bdrv_register(&bdrv_zeroinit);
-+}
-+
-+block_init(bdrv_zeroinit_init);
diff --git a/debian/patches/pve/0020-PVE-Add-dummy-id-command-line-parameter.patch b/debian/patches/pve/0020-PVE-Add-dummy-id-command-line-parameter.patch
deleted file mode 100644 (file)
index 3b79a3a..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:48 +0200
-Subject: [PATCH] PVE: Add dummy -id command line parameter
-
-This used to be part of the qemu-side PVE authentication for
-VNC. Now this does nothing.
-
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- qemu-options.hx | 3 +++
- softmmu/vl.c    | 8 ++++++++
- 2 files changed, 11 insertions(+)
-
-diff --git a/qemu-options.hx b/qemu-options.hx
-index fc6cb23dd9..3ccedf7c45 100644
---- a/qemu-options.hx
-+++ b/qemu-options.hx
-@@ -1150,6 +1150,9 @@ backend describes how QEMU handles the data.
- ERST
-+DEF("id", HAS_ARG, QEMU_OPTION_id,
-+    "-id n           set the VMID", QEMU_ARCH_ALL)
-+
- DEF("fda", HAS_ARG, QEMU_OPTION_fda,
-     "-fda/-fdb file  use 'file' as floppy disk 0/1 image\n", QEMU_ARCH_ALL)
- DEF("fdb", HAS_ARG, QEMU_OPTION_fdb, "", QEMU_ARCH_ALL)
-diff --git a/softmmu/vl.c b/softmmu/vl.c
-index 0eabc71b68..323f6a23d4 100644
---- a/softmmu/vl.c
-+++ b/softmmu/vl.c
-@@ -2648,6 +2648,7 @@ void qemu_init(int argc, char **argv)
-     MachineClass *machine_class;
-     bool userconfig = true;
-     FILE *vmstate_dump_file = NULL;
-+    long vm_id;
-     qemu_add_opts(&qemu_drive_opts);
-     qemu_add_drive_opts(&qemu_legacy_drive_opts);
-@@ -3271,6 +3272,13 @@ void qemu_init(int argc, char **argv)
-                 machine_parse_property_opt(qemu_find_opts("smp-opts"),
-                                            "smp", optarg);
-                 break;
-+            case QEMU_OPTION_id:
-+                vm_id = strtol(optarg, (char **)&optarg, 10);
-+                if (*optarg != 0 || vm_id < 100 || vm_id > INT_MAX) {
-+                    error_report("invalid -id argument %s", optarg);
-+                    exit(1);
-+                }
-+                break;
-             case QEMU_OPTION_vnc:
-                 vnc_parse(optarg);
-                 break;
diff --git a/debian/patches/pve/0020-PVE-block-add-the-zeroinit-block-driver-filter.patch b/debian/patches/pve/0020-PVE-block-add-the-zeroinit-block-driver-filter.patch
new file mode 100644 (file)
index 0000000..39264ee
--- /dev/null
@@ -0,0 +1,232 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:47 +0200
+Subject: [PATCH] PVE: block: add the zeroinit block driver filter
+
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[FE: adapt to changed function signatures]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ block/meson.build |   1 +
+ block/zeroinit.c  | 200 ++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 201 insertions(+)
+ create mode 100644 block/zeroinit.c
+
+diff --git a/block/meson.build b/block/meson.build
+index 382bec0e7d..253fe49fa2 100644
+--- a/block/meson.build
++++ b/block/meson.build
+@@ -44,6 +44,7 @@ block_ss.add(files(
+   'vmdk.c',
+   'vpc.c',
+   'write-threshold.c',
++  'zeroinit.c',
+ ), zstd, zlib, gnutls)
+ softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c'))
+diff --git a/block/zeroinit.c b/block/zeroinit.c
+new file mode 100644
+index 0000000000..1257342724
+--- /dev/null
++++ b/block/zeroinit.c
+@@ -0,0 +1,200 @@
++/*
++ * Filter to fake a zero-initialized block device.
++ *
++ * Copyright (c) 2016 Wolfgang Bumiller <w.bumiller@proxmox.com>
++ * Copyright (c) 2016 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 "qapi/error.h"
++#include "block/block_int.h"
++#include "block/block-io.h"
++#include "qapi/qmp/qdict.h"
++#include "qapi/qmp/qstring.h"
++#include "qemu/cutils.h"
++#include "qemu/option.h"
++#include "qemu/module.h"
++
++typedef struct {
++    bool has_zero_init;
++    int64_t extents;
++} BDRVZeroinitState;
++
++/* Valid blkverify filenames look like blkverify:path/to/raw_image:path/to/image */
++static void zeroinit_parse_filename(const char *filename, QDict *options,
++                                     Error **errp)
++{
++    QString *raw_path;
++
++    /* Parse the blkverify: prefix */
++    if (!strstart(filename, "zeroinit:", &filename)) {
++        /* There was no prefix; therefore, all options have to be already
++           present in the QDict (except for the filename) */
++        return;
++    }
++
++    raw_path = qstring_from_str(filename);
++    qdict_put(options, "x-next", raw_path);
++}
++
++static QemuOptsList runtime_opts = {
++    .name = "zeroinit",
++    .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
++    .desc = {
++        {
++            .name = "x-next",
++            .type = QEMU_OPT_STRING,
++            .help = "[internal use only, will be removed]",
++        },
++        {
++            .name = "x-zeroinit",
++            .type = QEMU_OPT_BOOL,
++            .help = "set has_initialized_zero flag",
++        },
++        { /* end of list */ }
++    },
++};
++
++static int zeroinit_open(BlockDriverState *bs, QDict *options, int flags,
++                          Error **errp)
++{
++    BDRVZeroinitState *s = bs->opaque;
++    QemuOpts *opts;
++    Error *local_err = NULL;
++    int ret;
++
++    s->extents = 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;
++    }
++
++    /* Open the raw file */
++    bs->file = bdrv_open_child(qemu_opt_get(opts, "x-next"), options, "next",
++                               bs, &child_of_bds,
++                               BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
++                               false, &local_err);
++    if (local_err) {
++        ret = -EINVAL;
++        error_propagate(errp, local_err);
++        goto fail;
++    }
++
++    /* set the options */
++    s->has_zero_init = qemu_opt_get_bool(opts, "x-zeroinit", true);
++
++    ret = 0;
++fail:
++    if (ret < 0) {
++        bdrv_unref_child(bs, bs->file);
++    }
++    qemu_opts_del(opts);
++    return ret;
++}
++
++static void zeroinit_close(BlockDriverState *bs)
++{
++    BDRVZeroinitState *s = bs->opaque;
++    (void)s;
++}
++
++static coroutine_fn int64_t zeroinit_co_getlength(BlockDriverState *bs)
++{
++    return bdrv_co_getlength(bs->file->bs);
++}
++
++static int coroutine_fn zeroinit_co_preadv(BlockDriverState *bs,
++    int64_t offset, int64_t bytes, QEMUIOVector *qiov, BdrvRequestFlags flags)
++{
++    return bdrv_co_preadv(bs->file, offset, bytes, qiov, flags);
++}
++
++static int coroutine_fn zeroinit_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
++                                                 int64_t bytes, BdrvRequestFlags flags)
++{
++    BDRVZeroinitState *s = bs->opaque;
++    if (offset >= s->extents)
++        return 0;
++    return bdrv_pwrite_zeroes(bs->file, offset, bytes, flags);
++}
++
++static int coroutine_fn zeroinit_co_pwritev(BlockDriverState *bs,
++    int64_t offset, int64_t bytes, QEMUIOVector *qiov, BdrvRequestFlags flags)
++{
++    BDRVZeroinitState *s = bs->opaque;
++    int64_t extents = offset + bytes;
++    if (extents > s->extents)
++        s->extents = extents;
++    return bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags);
++}
++
++static coroutine_fn int zeroinit_co_flush(BlockDriverState *bs)
++{
++    return bdrv_co_flush(bs->file->bs);
++}
++
++static int zeroinit_has_zero_init(BlockDriverState *bs)
++{
++    BDRVZeroinitState *s = bs->opaque;
++    return s->has_zero_init;
++}
++
++static int coroutine_fn zeroinit_co_pdiscard(BlockDriverState *bs,
++                                             int64_t offset, int64_t bytes)
++{
++    return bdrv_co_pdiscard(bs->file, offset, bytes);
++}
++
++static int zeroinit_co_truncate(BlockDriverState *bs, int64_t offset,
++                                _Bool exact, PreallocMode prealloc,
++                                BdrvRequestFlags req_flags, Error **errp)
++{
++    return bdrv_co_truncate(bs->file, offset, exact, prealloc, req_flags, errp);
++}
++
++static coroutine_fn int zeroinit_co_get_info(BlockDriverState *bs,
++                                             BlockDriverInfo *bdi)
++{
++    return bdrv_co_get_info(bs->file->bs, bdi);
++}
++
++static BlockDriver bdrv_zeroinit = {
++    .format_name                      = "zeroinit",
++    .protocol_name                    = "zeroinit",
++    .instance_size                    = sizeof(BDRVZeroinitState),
++
++    .bdrv_parse_filename              = zeroinit_parse_filename,
++    .bdrv_file_open                   = zeroinit_open,
++    .bdrv_close                       = zeroinit_close,
++    .bdrv_co_getlength                = zeroinit_co_getlength,
++    .bdrv_child_perm                  = bdrv_default_perms,
++    .bdrv_co_flush_to_disk            = zeroinit_co_flush,
++
++    .bdrv_co_pwrite_zeroes            = zeroinit_co_pwrite_zeroes,
++    .bdrv_co_pwritev                  = zeroinit_co_pwritev,
++    .bdrv_co_preadv                   = zeroinit_co_preadv,
++    .bdrv_co_flush                    = zeroinit_co_flush,
++
++    .is_filter                        = true,
++
++    .bdrv_has_zero_init               = zeroinit_has_zero_init,
++
++    .bdrv_co_pdiscard                 = zeroinit_co_pdiscard,
++
++    .bdrv_co_truncate                 = zeroinit_co_truncate,
++    .bdrv_co_get_info                 = zeroinit_co_get_info,
++};
++
++static void bdrv_zeroinit_init(void)
++{
++    bdrv_register(&bdrv_zeroinit);
++}
++
++block_init(bdrv_zeroinit_init);
diff --git a/debian/patches/pve/0021-PVE-Add-dummy-id-command-line-parameter.patch b/debian/patches/pve/0021-PVE-Add-dummy-id-command-line-parameter.patch
new file mode 100644 (file)
index 0000000..3b79a3a
--- /dev/null
@@ -0,0 +1,55 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:48 +0200
+Subject: [PATCH] PVE: Add dummy -id command line parameter
+
+This used to be part of the qemu-side PVE authentication for
+VNC. Now this does nothing.
+
+Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ qemu-options.hx | 3 +++
+ softmmu/vl.c    | 8 ++++++++
+ 2 files changed, 11 insertions(+)
+
+diff --git a/qemu-options.hx b/qemu-options.hx
+index fc6cb23dd9..3ccedf7c45 100644
+--- a/qemu-options.hx
++++ b/qemu-options.hx
+@@ -1150,6 +1150,9 @@ backend describes how QEMU handles the data.
+ ERST
++DEF("id", HAS_ARG, QEMU_OPTION_id,
++    "-id n           set the VMID", QEMU_ARCH_ALL)
++
+ DEF("fda", HAS_ARG, QEMU_OPTION_fda,
+     "-fda/-fdb file  use 'file' as floppy disk 0/1 image\n", QEMU_ARCH_ALL)
+ DEF("fdb", HAS_ARG, QEMU_OPTION_fdb, "", QEMU_ARCH_ALL)
+diff --git a/softmmu/vl.c b/softmmu/vl.c
+index 0eabc71b68..323f6a23d4 100644
+--- a/softmmu/vl.c
++++ b/softmmu/vl.c
+@@ -2648,6 +2648,7 @@ void qemu_init(int argc, char **argv)
+     MachineClass *machine_class;
+     bool userconfig = true;
+     FILE *vmstate_dump_file = NULL;
++    long vm_id;
+     qemu_add_opts(&qemu_drive_opts);
+     qemu_add_drive_opts(&qemu_legacy_drive_opts);
+@@ -3271,6 +3272,13 @@ void qemu_init(int argc, char **argv)
+                 machine_parse_property_opt(qemu_find_opts("smp-opts"),
+                                            "smp", optarg);
+                 break;
++            case QEMU_OPTION_id:
++                vm_id = strtol(optarg, (char **)&optarg, 10);
++                if (*optarg != 0 || vm_id < 100 || vm_id > INT_MAX) {
++                    error_report("invalid -id argument %s", optarg);
++                    exit(1);
++                }
++                break;
+             case QEMU_OPTION_vnc:
+                 vnc_parse(optarg);
+                 break;
diff --git a/debian/patches/pve/0021-PVE-Config-Revert-target-i386-disable-LINT0-after-re.patch b/debian/patches/pve/0021-PVE-Config-Revert-target-i386-disable-LINT0-after-re.patch
deleted file mode 100644 (file)
index 3c28401..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:49 +0200
-Subject: [PATCH] PVE: [Config] Revert "target-i386: disable LINT0 after reset"
-
-This reverts commit b8eb5512fd8a115f164edbbe897cdf8884920ccb.
-
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- hw/intc/apic_common.c | 9 +++++++++
- 1 file changed, 9 insertions(+)
-
-diff --git a/hw/intc/apic_common.c b/hw/intc/apic_common.c
-index 4a34f03047..59b917e50c 100644
---- a/hw/intc/apic_common.c
-+++ b/hw/intc/apic_common.c
-@@ -252,6 +252,15 @@ static void apic_reset_common(DeviceState *dev)
-     info->vapic_base_update(s);
-     apic_init_reset(dev);
-+
-+    if (bsp) {
-+        /*
-+         * LINT0 delivery mode on CPU #0 is set to ExtInt at initialization
-+         * time typically by BIOS, so PIC interrupt can be delivered to the
-+         * processor when local APIC is enabled.
-+         */
-+        s->lvt[APIC_LVT_LINT0] = 0x700;
-+    }
- }
- static const VMStateDescription vmstate_apic_common;
diff --git a/debian/patches/pve/0022-PVE-Config-Revert-target-i386-disable-LINT0-after-re.patch b/debian/patches/pve/0022-PVE-Config-Revert-target-i386-disable-LINT0-after-re.patch
new file mode 100644 (file)
index 0000000..3c28401
--- /dev/null
@@ -0,0 +1,32 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:49 +0200
+Subject: [PATCH] PVE: [Config] Revert "target-i386: disable LINT0 after reset"
+
+This reverts commit b8eb5512fd8a115f164edbbe897cdf8884920ccb.
+
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ hw/intc/apic_common.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/hw/intc/apic_common.c b/hw/intc/apic_common.c
+index 4a34f03047..59b917e50c 100644
+--- a/hw/intc/apic_common.c
++++ b/hw/intc/apic_common.c
+@@ -252,6 +252,15 @@ static void apic_reset_common(DeviceState *dev)
+     info->vapic_base_update(s);
+     apic_init_reset(dev);
++
++    if (bsp) {
++        /*
++         * LINT0 delivery mode on CPU #0 is set to ExtInt at initialization
++         * time typically by BIOS, so PIC interrupt can be delivered to the
++         * processor when local APIC is enabled.
++         */
++        s->lvt[APIC_LVT_LINT0] = 0x700;
++    }
+ }
+ static const VMStateDescription vmstate_apic_common;
diff --git a/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch b/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch
deleted file mode 100644 (file)
index e9e9f12..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:50 +0200
-Subject: [PATCH] PVE: [Up+Config] file-posix: make locking optiono on create
-
-Otherwise creating images on nfs/cifs can be problematic.
-
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- block/file-posix.c   | 59 ++++++++++++++++++++++++++++++--------------
- qapi/block-core.json |  3 ++-
- 2 files changed, 42 insertions(+), 20 deletions(-)
-
-diff --git a/block/file-posix.c b/block/file-posix.c
-index 9681bd0434..044890822d 100644
---- a/block/file-posix.c
-+++ b/block/file-posix.c
-@@ -2483,6 +2483,7 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp)
-     int fd;
-     uint64_t perm, shared;
-     int result = 0;
-+    bool locked = false;
-     /* Validate options and set default values */
-     assert(options->driver == BLOCKDEV_DRIVER_FILE);
-@@ -2523,19 +2524,22 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp)
-     perm = BLK_PERM_WRITE | BLK_PERM_RESIZE;
-     shared = BLK_PERM_ALL & ~BLK_PERM_RESIZE;
--    /* Step one: Take locks */
--    result = raw_apply_lock_bytes(NULL, fd, perm, ~shared, false, errp);
--    if (result < 0) {
--        goto out_close;
--    }
-+    if (file_opts->locking != ON_OFF_AUTO_OFF) {
-+        /* Step one: Take locks */
-+        result = raw_apply_lock_bytes(NULL, fd, perm, ~shared, false, errp);
-+        if (result < 0) {
-+            goto out_close;
-+        }
-+        locked = true;
--    /* Step two: Check that nobody else has taken conflicting locks */
--    result = raw_check_lock_bytes(fd, perm, shared, errp);
--    if (result < 0) {
--        error_append_hint(errp,
--                          "Is another process using the image [%s]?\n",
--                          file_opts->filename);
--        goto out_unlock;
-+        /* Step two: Check that nobody else has taken conflicting locks */
-+        result = raw_check_lock_bytes(fd, perm, shared, errp);
-+        if (result < 0) {
-+            error_append_hint(errp,
-+                              "Is another process using the image [%s]?\n",
-+                              file_opts->filename);
-+            goto out_unlock;
-+        }
-     }
-     /* Clear the file by truncating it to 0 */
-@@ -2589,13 +2593,15 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp)
-     }
- out_unlock:
--    raw_apply_lock_bytes(NULL, fd, 0, 0, true, &local_err);
--    if (local_err) {
--        /* The above call should not fail, and if it does, that does
--         * not mean the whole creation operation has failed.  So
--         * report it the user for their convenience, but do not report
--         * it to the caller. */
--        warn_report_err(local_err);
-+    if (locked) {
-+        raw_apply_lock_bytes(NULL, fd, 0, 0, true, &local_err);
-+        if (local_err) {
-+            /* The above call should not fail, and if it does, that does
-+             * not mean the whole creation operation has failed.  So
-+             * report it the user for their convenience, but do not report
-+             * it to the caller. */
-+            warn_report_err(local_err);
-+        }
-     }
- out_close:
-@@ -2619,6 +2625,7 @@ raw_co_create_opts(BlockDriver *drv, const char *filename,
-     PreallocMode prealloc;
-     char *buf = NULL;
-     Error *local_err = NULL;
-+    OnOffAuto locking;
-     /* Skip file: protocol prefix */
-     strstart(filename, "file:", &filename);
-@@ -2641,6 +2648,18 @@ raw_co_create_opts(BlockDriver *drv, const char *filename,
-         return -EINVAL;
-     }
-+    locking = qapi_enum_parse(&OnOffAuto_lookup,
-+                              qemu_opt_get(opts, "locking"),
-+                              ON_OFF_AUTO_AUTO, &local_err);
-+    if (local_err) {
-+        error_propagate(errp, local_err);
-+        return -EINVAL;
-+    }
-+
-+    if (locking == ON_OFF_AUTO_AUTO) {
-+        locking = ON_OFF_AUTO_OFF;
-+    }
-+
-     options = (BlockdevCreateOptions) {
-         .driver     = BLOCKDEV_DRIVER_FILE,
-         .u.file     = {
-@@ -2652,6 +2671,8 @@ raw_co_create_opts(BlockDriver *drv, const char *filename,
-             .nocow              = nocow,
-             .has_extent_size_hint = has_extent_size_hint,
-             .extent_size_hint   = extent_size_hint,
-+            .has_locking        = true,
-+            .locking            = locking,
-         },
-     };
-     return raw_co_create(&options, errp);
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index 3c945c1f93..542add004b 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -4740,7 +4740,8 @@
-             'size':                 'size',
-             '*preallocation':       'PreallocMode',
-             '*nocow':               'bool',
--            '*extent-size-hint':    'size'} }
-+            '*extent-size-hint':    'size',
-+            '*locking':         'OnOffAuto' } }
- ##
- # @BlockdevCreateOptionsGluster:
diff --git a/debian/patches/pve/0023-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch b/debian/patches/pve/0023-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch
new file mode 100644 (file)
index 0000000..e9e9f12
--- /dev/null
@@ -0,0 +1,134 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:50 +0200
+Subject: [PATCH] PVE: [Up+Config] file-posix: make locking optiono on create
+
+Otherwise creating images on nfs/cifs can be problematic.
+
+Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ block/file-posix.c   | 59 ++++++++++++++++++++++++++++++--------------
+ qapi/block-core.json |  3 ++-
+ 2 files changed, 42 insertions(+), 20 deletions(-)
+
+diff --git a/block/file-posix.c b/block/file-posix.c
+index 9681bd0434..044890822d 100644
+--- a/block/file-posix.c
++++ b/block/file-posix.c
+@@ -2483,6 +2483,7 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp)
+     int fd;
+     uint64_t perm, shared;
+     int result = 0;
++    bool locked = false;
+     /* Validate options and set default values */
+     assert(options->driver == BLOCKDEV_DRIVER_FILE);
+@@ -2523,19 +2524,22 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp)
+     perm = BLK_PERM_WRITE | BLK_PERM_RESIZE;
+     shared = BLK_PERM_ALL & ~BLK_PERM_RESIZE;
+-    /* Step one: Take locks */
+-    result = raw_apply_lock_bytes(NULL, fd, perm, ~shared, false, errp);
+-    if (result < 0) {
+-        goto out_close;
+-    }
++    if (file_opts->locking != ON_OFF_AUTO_OFF) {
++        /* Step one: Take locks */
++        result = raw_apply_lock_bytes(NULL, fd, perm, ~shared, false, errp);
++        if (result < 0) {
++            goto out_close;
++        }
++        locked = true;
+-    /* Step two: Check that nobody else has taken conflicting locks */
+-    result = raw_check_lock_bytes(fd, perm, shared, errp);
+-    if (result < 0) {
+-        error_append_hint(errp,
+-                          "Is another process using the image [%s]?\n",
+-                          file_opts->filename);
+-        goto out_unlock;
++        /* Step two: Check that nobody else has taken conflicting locks */
++        result = raw_check_lock_bytes(fd, perm, shared, errp);
++        if (result < 0) {
++            error_append_hint(errp,
++                              "Is another process using the image [%s]?\n",
++                              file_opts->filename);
++            goto out_unlock;
++        }
+     }
+     /* Clear the file by truncating it to 0 */
+@@ -2589,13 +2593,15 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp)
+     }
+ out_unlock:
+-    raw_apply_lock_bytes(NULL, fd, 0, 0, true, &local_err);
+-    if (local_err) {
+-        /* The above call should not fail, and if it does, that does
+-         * not mean the whole creation operation has failed.  So
+-         * report it the user for their convenience, but do not report
+-         * it to the caller. */
+-        warn_report_err(local_err);
++    if (locked) {
++        raw_apply_lock_bytes(NULL, fd, 0, 0, true, &local_err);
++        if (local_err) {
++            /* The above call should not fail, and if it does, that does
++             * not mean the whole creation operation has failed.  So
++             * report it the user for their convenience, but do not report
++             * it to the caller. */
++            warn_report_err(local_err);
++        }
+     }
+ out_close:
+@@ -2619,6 +2625,7 @@ raw_co_create_opts(BlockDriver *drv, const char *filename,
+     PreallocMode prealloc;
+     char *buf = NULL;
+     Error *local_err = NULL;
++    OnOffAuto locking;
+     /* Skip file: protocol prefix */
+     strstart(filename, "file:", &filename);
+@@ -2641,6 +2648,18 @@ raw_co_create_opts(BlockDriver *drv, const char *filename,
+         return -EINVAL;
+     }
++    locking = qapi_enum_parse(&OnOffAuto_lookup,
++                              qemu_opt_get(opts, "locking"),
++                              ON_OFF_AUTO_AUTO, &local_err);
++    if (local_err) {
++        error_propagate(errp, local_err);
++        return -EINVAL;
++    }
++
++    if (locking == ON_OFF_AUTO_AUTO) {
++        locking = ON_OFF_AUTO_OFF;
++    }
++
+     options = (BlockdevCreateOptions) {
+         .driver     = BLOCKDEV_DRIVER_FILE,
+         .u.file     = {
+@@ -2652,6 +2671,8 @@ raw_co_create_opts(BlockDriver *drv, const char *filename,
+             .nocow              = nocow,
+             .has_extent_size_hint = has_extent_size_hint,
+             .extent_size_hint   = extent_size_hint,
++            .has_locking        = true,
++            .locking            = locking,
+         },
+     };
+     return raw_co_create(&options, errp);
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index 3c945c1f93..542add004b 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -4740,7 +4740,8 @@
+             'size':                 'size',
+             '*preallocation':       'PreallocMode',
+             '*nocow':               'bool',
+-            '*extent-size-hint':    'size'} }
++            '*extent-size-hint':    'size',
++            '*locking':         'OnOffAuto' } }
+ ##
+ # @BlockdevCreateOptionsGluster:
diff --git a/debian/patches/pve/0023-PVE-monitor-disable-oob-capability.patch b/debian/patches/pve/0023-PVE-monitor-disable-oob-capability.patch
deleted file mode 100644 (file)
index 9abde33..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:53 +0200
-Subject: [PATCH] PVE: monitor: disable oob capability
-
-A bisect revealed that commit 8258292e18c3
-("monitor: Remove "x-oob", offer capability "oob" unconditionally")
-causes unexpected hangs when restoring live snapshots from some
-types of block devices (particularly RBD).
-We need to figure out what's happnening there. For now, since we
-had this disabled before and probably don't need it now either,
-disable oob, so we can get a functioning qemu out...
-
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- monitor/qmp.c | 3 +--
- 1 file changed, 1 insertion(+), 2 deletions(-)
-
-diff --git a/monitor/qmp.c b/monitor/qmp.c
-index 6b8cfcf6d8..3ec67e32d3 100644
---- a/monitor/qmp.c
-+++ b/monitor/qmp.c
-@@ -519,8 +519,7 @@ void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp)
-     qemu_chr_fe_set_echo(&mon->common.chr, true);
-     /* Note: we run QMP monitor in I/O thread when @chr supports that */
--    monitor_data_init(&mon->common, true, false,
--                      qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_GCONTEXT));
-+    monitor_data_init(&mon->common, true, false, false);
-     mon->pretty = pretty;
diff --git a/debian/patches/pve/0024-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch b/debian/patches/pve/0024-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch
deleted file mode 100644 (file)
index 8c76070..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Thomas Lamprecht <t.lamprecht@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:54 +0200
-Subject: [PATCH] PVE: [Compat]: 4.0 used balloon qemu-4-0-config-size false
- here
-
-The underlying issue why this change from upstream to us arised in
-the first place is that QEMU 4.0 was already released at the point we
-run into this migration issue, so we did the then obvious fallback to
-false for virtio-balloon-device qemu-4-0-config-size.
-
-QEMU made that switch back in 4.1, where it now uses a backward
-compatible mechanism to detect if the bigger CFG sizes should be
-used, i.e., checking the VIRTIO_BALLOON_F_PAGE_POISON or
-VIRTIO_BALLOON_F_FREE_PAGE_HINT balloon feature flags.
-As for them, upstream released version 4.0 had this to true they keep
-it to true in their compatibility record for the 4.0 machine, to
-allow live migrations from 4.0 to 4.1.
-As for us, downstream released version 4.0 (first public release of
-this QEMU) had this to false, we change it back to false again, for
-the same reason.
-
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- hw/core/machine.c | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/hw/core/machine.c b/hw/core/machine.c
-index 5060119952..9d1c358c12 100644
---- a/hw/core/machine.c
-+++ b/hw/core/machine.c
-@@ -142,7 +142,8 @@ GlobalProperty hw_compat_4_0[] = {
-     { "virtio-vga",     "edid", "false" },
-     { "virtio-gpu-device", "edid", "false" },
-     { "virtio-device", "use-started", "false" },
--    { "virtio-balloon-device", "qemu-4-0-config-size", "true" },
-+    // PVE differed from upstream for 4.0 balloon cfg size
-+    { "virtio-balloon-device", "qemu-4-0-config-size", "false" },
-     { "pl031", "migrate-tick-offset", "false" },
- };
- const size_t hw_compat_4_0_len = G_N_ELEMENTS(hw_compat_4_0);
diff --git a/debian/patches/pve/0024-PVE-monitor-disable-oob-capability.patch b/debian/patches/pve/0024-PVE-monitor-disable-oob-capability.patch
new file mode 100644 (file)
index 0000000..9abde33
--- /dev/null
@@ -0,0 +1,33 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:53 +0200
+Subject: [PATCH] PVE: monitor: disable oob capability
+
+A bisect revealed that commit 8258292e18c3
+("monitor: Remove "x-oob", offer capability "oob" unconditionally")
+causes unexpected hangs when restoring live snapshots from some
+types of block devices (particularly RBD).
+We need to figure out what's happnening there. For now, since we
+had this disabled before and probably don't need it now either,
+disable oob, so we can get a functioning qemu out...
+
+Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ monitor/qmp.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/monitor/qmp.c b/monitor/qmp.c
+index 6b8cfcf6d8..3ec67e32d3 100644
+--- a/monitor/qmp.c
++++ b/monitor/qmp.c
+@@ -519,8 +519,7 @@ void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp)
+     qemu_chr_fe_set_echo(&mon->common.chr, true);
+     /* Note: we run QMP monitor in I/O thread when @chr supports that */
+-    monitor_data_init(&mon->common, true, false,
+-                      qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_GCONTEXT));
++    monitor_data_init(&mon->common, true, false, false);
+     mon->pretty = pretty;
diff --git a/debian/patches/pve/0025-PVE-Allow-version-code-in-machine-type.patch b/debian/patches/pve/0025-PVE-Allow-version-code-in-machine-type.patch
deleted file mode 100644 (file)
index d88d1d0..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Dietmar Maurer <dietmar@proxmox.com>
-Date: Mon, 6 Apr 2020 12:16:55 +0200
-Subject: [PATCH] PVE: Allow version code in machine type
-
-E.g. pc-i440fx-4.0+pve3 would print 'pve3' as version code while
-selecting pc-i440fx-4.0 as machine type.
-
-Version is made available as 'pve-version' in query-machines (same as,
-and only if 'is-current').
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
-[FE: adapt to QAPI changes]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- hw/core/machine-qmp-cmds.c |  5 +++++
- include/hw/boards.h        |  2 ++
- qapi/machine.json          |  4 +++-
- softmmu/vl.c               | 25 +++++++++++++++++++++++++
- 4 files changed, 35 insertions(+), 1 deletion(-)
-
-diff --git a/hw/core/machine-qmp-cmds.c b/hw/core/machine-qmp-cmds.c
-index 24595f618c..ee9cb0cd04 100644
---- a/hw/core/machine-qmp-cmds.c
-+++ b/hw/core/machine-qmp-cmds.c
-@@ -107,6 +107,11 @@ MachineInfoList *qmp_query_machines(Error **errp)
-         if (strcmp(mc->name, MACHINE_GET_CLASS(current_machine)->name) == 0) {
-             info->has_is_current = true;
-             info->is_current = true;
-+
-+            // PVE version string only exists for current machine
-+            if (mc->pve_version) {
-+                info->pve_version = g_strdup(mc->pve_version);
-+            }
-         }
-         if (mc->default_cpu_type) {
-diff --git a/include/hw/boards.h b/include/hw/boards.h
-index 6fbbfd56c8..61a526e97d 100644
---- a/include/hw/boards.h
-+++ b/include/hw/boards.h
-@@ -232,6 +232,8 @@ struct MachineClass {
-     const char *desc;
-     const char *deprecation_reason;
-+    const char *pve_version;
-+
-     void (*init)(MachineState *state);
-     void (*reset)(MachineState *state, ShutdownCause reason);
-     void (*wakeup)(MachineState *state);
-diff --git a/qapi/machine.json b/qapi/machine.json
-index c904280085..47f3facdb2 100644
---- a/qapi/machine.json
-+++ b/qapi/machine.json
-@@ -159,6 +159,8 @@
- #
- # @acpi: machine type supports ACPI (since 8.0)
- #
-+# @pve-version: custom PVE version suffix specified as 'machine+pveN'
-+#
- # Since: 1.2
- ##
- { 'struct': 'MachineInfo',
-@@ -166,7 +168,7 @@
-             '*is-default': 'bool', '*is-current': 'bool', 'cpu-max': 'int',
-             'hotpluggable-cpus': 'bool',  'numa-mem-supported': 'bool',
-             'deprecated': 'bool', '*default-cpu-type': 'str',
--            '*default-ram-id': 'str', 'acpi': 'bool' } }
-+            '*default-ram-id': 'str', 'acpi': 'bool', '*pve-version': 'str' } }
- ##
- # @query-machines:
-diff --git a/softmmu/vl.c b/softmmu/vl.c
-index 323f6a23d4..25abdc9da7 100644
---- a/softmmu/vl.c
-+++ b/softmmu/vl.c
-@@ -1578,6 +1578,7 @@ static const QEMUOption *lookup_opt(int argc, char **argv,
- static MachineClass *select_machine(QDict *qdict, Error **errp)
- {
-     const char *optarg = qdict_get_try_str(qdict, "type");
-+    const char *pvever = qdict_get_try_str(qdict, "pvever");
-     GSList *machines = object_class_get_list(TYPE_MACHINE, false);
-     MachineClass *machine_class;
-     Error *local_err = NULL;
-@@ -1595,6 +1596,11 @@ static MachineClass *select_machine(QDict *qdict, Error **errp)
-         }
-     }
-+    if (machine_class) {
-+        machine_class->pve_version = g_strdup(pvever);
-+        qdict_del(qdict, "pvever");
-+    }
-+
-     g_slist_free(machines);
-     if (local_err) {
-         error_append_hint(&local_err, "Use -machine help to list supported machines\n");
-@@ -3213,12 +3219,31 @@ void qemu_init(int argc, char **argv)
-             case QEMU_OPTION_machine:
-                 {
-                     bool help;
-+                    size_t pvever_index, name_len;
-+                    const gchar *name;
-+                    gchar *name_clean, *pvever;
-                     keyval_parse_into(machine_opts_dict, optarg, "type", &help, &error_fatal);
-                     if (help) {
-                         machine_help_func(machine_opts_dict);
-                         exit(EXIT_SUCCESS);
-                     }
-+
-+                    // PVE version is specified with '+' as seperator, e.g. pc-i440fx+pvever
-+                    name = qdict_get_try_str(machine_opts_dict, "type");
-+                    if (name != NULL) {
-+                        name_len = strlen(name);
-+                        pvever_index = strcspn(name, "+");
-+                        if (pvever_index < name_len) {
-+                            name_clean = g_strndup(name, pvever_index);
-+                            pvever = g_strndup(name + pvever_index + 1, name_len - pvever_index - 1);
-+                            qdict_put_str(machine_opts_dict, "pvever", pvever);
-+                            qdict_put_str(machine_opts_dict, "type", name_clean);
-+                            g_free(name_clean);
-+                            g_free(pvever);
-+                        }
-+                    }
-+
-                     break;
-                 }
-             case QEMU_OPTION_accel:
diff --git a/debian/patches/pve/0025-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch b/debian/patches/pve/0025-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch
new file mode 100644 (file)
index 0000000..8c76070
--- /dev/null
@@ -0,0 +1,41 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Thomas Lamprecht <t.lamprecht@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:54 +0200
+Subject: [PATCH] PVE: [Compat]: 4.0 used balloon qemu-4-0-config-size false
+ here
+
+The underlying issue why this change from upstream to us arised in
+the first place is that QEMU 4.0 was already released at the point we
+run into this migration issue, so we did the then obvious fallback to
+false for virtio-balloon-device qemu-4-0-config-size.
+
+QEMU made that switch back in 4.1, where it now uses a backward
+compatible mechanism to detect if the bigger CFG sizes should be
+used, i.e., checking the VIRTIO_BALLOON_F_PAGE_POISON or
+VIRTIO_BALLOON_F_FREE_PAGE_HINT balloon feature flags.
+As for them, upstream released version 4.0 had this to true they keep
+it to true in their compatibility record for the 4.0 machine, to
+allow live migrations from 4.0 to 4.1.
+As for us, downstream released version 4.0 (first public release of
+this QEMU) had this to false, we change it back to false again, for
+the same reason.
+
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ hw/core/machine.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/hw/core/machine.c b/hw/core/machine.c
+index 5060119952..9d1c358c12 100644
+--- a/hw/core/machine.c
++++ b/hw/core/machine.c
+@@ -142,7 +142,8 @@ GlobalProperty hw_compat_4_0[] = {
+     { "virtio-vga",     "edid", "false" },
+     { "virtio-gpu-device", "edid", "false" },
+     { "virtio-device", "use-started", "false" },
+-    { "virtio-balloon-device", "qemu-4-0-config-size", "true" },
++    // PVE differed from upstream for 4.0 balloon cfg size
++    { "virtio-balloon-device", "qemu-4-0-config-size", "false" },
+     { "pl031", "migrate-tick-offset", "false" },
+ };
+ const size_t hw_compat_4_0_len = G_N_ELEMENTS(hw_compat_4_0);
diff --git a/debian/patches/pve/0026-PVE-Allow-version-code-in-machine-type.patch b/debian/patches/pve/0026-PVE-Allow-version-code-in-machine-type.patch
new file mode 100644 (file)
index 0000000..d88d1d0
--- /dev/null
@@ -0,0 +1,129 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Dietmar Maurer <dietmar@proxmox.com>
+Date: Mon, 6 Apr 2020 12:16:55 +0200
+Subject: [PATCH] PVE: Allow version code in machine type
+
+E.g. pc-i440fx-4.0+pve3 would print 'pve3' as version code while
+selecting pc-i440fx-4.0 as machine type.
+
+Version is made available as 'pve-version' in query-machines (same as,
+and only if 'is-current').
+
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[FE: adapt to QAPI changes]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ hw/core/machine-qmp-cmds.c |  5 +++++
+ include/hw/boards.h        |  2 ++
+ qapi/machine.json          |  4 +++-
+ softmmu/vl.c               | 25 +++++++++++++++++++++++++
+ 4 files changed, 35 insertions(+), 1 deletion(-)
+
+diff --git a/hw/core/machine-qmp-cmds.c b/hw/core/machine-qmp-cmds.c
+index 24595f618c..ee9cb0cd04 100644
+--- a/hw/core/machine-qmp-cmds.c
++++ b/hw/core/machine-qmp-cmds.c
+@@ -107,6 +107,11 @@ MachineInfoList *qmp_query_machines(Error **errp)
+         if (strcmp(mc->name, MACHINE_GET_CLASS(current_machine)->name) == 0) {
+             info->has_is_current = true;
+             info->is_current = true;
++
++            // PVE version string only exists for current machine
++            if (mc->pve_version) {
++                info->pve_version = g_strdup(mc->pve_version);
++            }
+         }
+         if (mc->default_cpu_type) {
+diff --git a/include/hw/boards.h b/include/hw/boards.h
+index 6fbbfd56c8..61a526e97d 100644
+--- a/include/hw/boards.h
++++ b/include/hw/boards.h
+@@ -232,6 +232,8 @@ struct MachineClass {
+     const char *desc;
+     const char *deprecation_reason;
++    const char *pve_version;
++
+     void (*init)(MachineState *state);
+     void (*reset)(MachineState *state, ShutdownCause reason);
+     void (*wakeup)(MachineState *state);
+diff --git a/qapi/machine.json b/qapi/machine.json
+index c904280085..47f3facdb2 100644
+--- a/qapi/machine.json
++++ b/qapi/machine.json
+@@ -159,6 +159,8 @@
+ #
+ # @acpi: machine type supports ACPI (since 8.0)
+ #
++# @pve-version: custom PVE version suffix specified as 'machine+pveN'
++#
+ # Since: 1.2
+ ##
+ { 'struct': 'MachineInfo',
+@@ -166,7 +168,7 @@
+             '*is-default': 'bool', '*is-current': 'bool', 'cpu-max': 'int',
+             'hotpluggable-cpus': 'bool',  'numa-mem-supported': 'bool',
+             'deprecated': 'bool', '*default-cpu-type': 'str',
+-            '*default-ram-id': 'str', 'acpi': 'bool' } }
++            '*default-ram-id': 'str', 'acpi': 'bool', '*pve-version': 'str' } }
+ ##
+ # @query-machines:
+diff --git a/softmmu/vl.c b/softmmu/vl.c
+index 323f6a23d4..25abdc9da7 100644
+--- a/softmmu/vl.c
++++ b/softmmu/vl.c
+@@ -1578,6 +1578,7 @@ static const QEMUOption *lookup_opt(int argc, char **argv,
+ static MachineClass *select_machine(QDict *qdict, Error **errp)
+ {
+     const char *optarg = qdict_get_try_str(qdict, "type");
++    const char *pvever = qdict_get_try_str(qdict, "pvever");
+     GSList *machines = object_class_get_list(TYPE_MACHINE, false);
+     MachineClass *machine_class;
+     Error *local_err = NULL;
+@@ -1595,6 +1596,11 @@ static MachineClass *select_machine(QDict *qdict, Error **errp)
+         }
+     }
++    if (machine_class) {
++        machine_class->pve_version = g_strdup(pvever);
++        qdict_del(qdict, "pvever");
++    }
++
+     g_slist_free(machines);
+     if (local_err) {
+         error_append_hint(&local_err, "Use -machine help to list supported machines\n");
+@@ -3213,12 +3219,31 @@ void qemu_init(int argc, char **argv)
+             case QEMU_OPTION_machine:
+                 {
+                     bool help;
++                    size_t pvever_index, name_len;
++                    const gchar *name;
++                    gchar *name_clean, *pvever;
+                     keyval_parse_into(machine_opts_dict, optarg, "type", &help, &error_fatal);
+                     if (help) {
+                         machine_help_func(machine_opts_dict);
+                         exit(EXIT_SUCCESS);
+                     }
++
++                    // PVE version is specified with '+' as seperator, e.g. pc-i440fx+pvever
++                    name = qdict_get_try_str(machine_opts_dict, "type");
++                    if (name != NULL) {
++                        name_len = strlen(name);
++                        pvever_index = strcspn(name, "+");
++                        if (pvever_index < name_len) {
++                            name_clean = g_strndup(name, pvever_index);
++                            pvever = g_strndup(name + pvever_index + 1, name_len - pvever_index - 1);
++                            qdict_put_str(machine_opts_dict, "pvever", pvever);
++                            qdict_put_str(machine_opts_dict, "type", name_clean);
++                            g_free(name_clean);
++                            g_free(pvever);
++                        }
++                    }
++
+                     break;
+                 }
+             case QEMU_OPTION_accel:
diff --git a/debian/patches/pve/0026-block-backup-move-bcs-bitmap-initialization-to-job-c.patch b/debian/patches/pve/0026-block-backup-move-bcs-bitmap-initialization-to-job-c.patch
deleted file mode 100644 (file)
index ef3fb89..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-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>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- block/backup.c | 8 ++++----
- 1 file changed, 4 insertions(+), 4 deletions(-)
-
-diff --git a/block/backup.c b/block/backup.c
-index db3791f4d1..39410dcf8d 100644
---- a/block/backup.c
-+++ b/block/backup.c
-@@ -237,8 +237,8 @@ static void backup_init_bcs_bitmap(BackupBlockJob *job)
-                                          true);
-     } 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);
-     }
-@@ -252,8 +252,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;
-@@ -495,6 +493,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/0027-PVE-Backup-add-vma-backup-format-code.patch b/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch
deleted file mode 100644 (file)
index fd0c8e6..0000000
+++ /dev/null
@@ -1,2730 +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      | 859 ++++++++++++++++++++++++++++++++++++++++++++++
- vma-writer.c      | 791 ++++++++++++++++++++++++++++++++++++++++++
- vma.c             | 849 +++++++++++++++++++++++++++++++++++++++++++++
- vma.h             | 150 ++++++++
- 6 files changed, 2656 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 253fe49fa2..744b698a82 100644
---- a/block/meson.build
-+++ b/block/meson.build
-@@ -47,6 +47,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'))
- softmmu_ss.add(files('block-ram-registrar.c'))
-diff --git a/meson.build b/meson.build
-index d964e741e7..603cdb97bb 100644
---- a/meson.build
-+++ b/meson.build
-@@ -1527,6 +1527,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'),
-@@ -3646,6 +3648,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..e65f1e8415
---- /dev/null
-+++ b/vma-reader.c
-@@ -0,0 +1,859 @@
-+/*
-+ * 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/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->rstate[i].target) {
-+            blk_unref(vmar->rstate[i].target);
-+        }
-+    }
-+
-+    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, nb_sectors * BDRV_SECTOR_SIZE, buf, 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..df4b20793d
---- /dev/null
-+++ b/vma-writer.c
-@@ -0,0 +1,791 @@
-+/*
-+ * 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"
-+#include "qemu/memalign.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, NULL, qemu_coroutine_self());
-+    qemu_coroutine_yield();
-+    aio_set_fd_handler(ctx, fd, false, NULL, 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..e8dffb43e0
---- /dev/null
-+++ b/vma.c
-@@ -0,0 +1,849 @@
-+/*
-+ * 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/module.h"
-+#include "qemu/error-report.h"
-+#include "qemu/main-loop.h"
-+#include "qemu/cutils.h"
-+#include "qemu/memalign.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;
-+
-+    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;
-+
-+            BlockBackend *blk = NULL;
-+
-+            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);
-+
-+    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-block-backup-move-bcs-bitmap-initialization-to-job-c.patch b/debian/patches/pve/0027-block-backup-move-bcs-bitmap-initialization-to-job-c.patch
new file mode 100644 (file)
index 0000000..ef3fb89
--- /dev/null
@@ -0,0 +1,59 @@
+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>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+---
+ block/backup.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/block/backup.c b/block/backup.c
+index db3791f4d1..39410dcf8d 100644
+--- a/block/backup.c
++++ b/block/backup.c
+@@ -237,8 +237,8 @@ static void backup_init_bcs_bitmap(BackupBlockJob *job)
+                                          true);
+     } 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);
+     }
+@@ -252,8 +252,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;
+@@ -495,6 +493,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/0028-PVE-Backup-add-backup-dump-block-driver.patch b/debian/patches/pve/0028-PVE-Backup-add-backup-dump-block-driver.patch
deleted file mode 100644 (file)
index cc4a679..0000000
+++ /dev/null
@@ -1,323 +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>
-[FE: adapt to coroutine changes]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- block/backup-dump.c              | 168 +++++++++++++++++++++++++++++++
- block/backup.c                   |  30 ++----
- block/meson.build                |   1 +
- include/block/block_int-common.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..232a094426
---- /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 "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 coroutine_fn int qemu_backup_dump_co_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 coroutine_fn int64_t qemu_backup_dump_co_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_co_getlength            = qemu_backup_dump_co_getlength,
-+    .bdrv_co_get_info             = qemu_backup_dump_co_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 39410dcf8d..af87fa6aa9 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 744b698a82..f580f95395 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-common.h b/include/block/block_int-common.h
-index f01bb8b617..d7ffd1826e 100644
---- a/include/block/block_int-common.h
-+++ b/include/block/block_int-common.h
-@@ -26,6 +26,7 @@
- #include "block/aio.h"
- #include "block/block-common.h"
-+#include "block/block-copy.h"
- #include "block/block-global-state.h"
- #include "block/snapshot.h"
- #include "qemu/iov.h"
-@@ -60,6 +61,40 @@
- #define BLOCK_PROBE_BUF_SIZE        512
-+typedef int BackupDumpFunc(void *opaque, uint64_t offset, uint64_t bytes, const void *buf);
-+
-+BlockDriverState *bdrv_backup_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 72d57f0934..93e22d180b 100644
---- a/job.c
-+++ b/job.c
-@@ -330,7 +330,8 @@ static bool job_started_locked(Job *job)
- }
- /* Called with job_mutex held. */
--static bool job_should_pause_locked(Job *job)
-+bool job_should_pause_locked(Job *job);
-+bool job_should_pause_locked(Job *job)
- {
-     return job->pause_count > 0;
- }
diff --git a/debian/patches/pve/0028-PVE-Backup-add-vma-backup-format-code.patch b/debian/patches/pve/0028-PVE-Backup-add-vma-backup-format-code.patch
new file mode 100644 (file)
index 0000000..6a343c2
--- /dev/null
@@ -0,0 +1,2775 @@
+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
+
+Notes about partial restoring: skipping a certain drive is done via a
+map line of the form skip=drive-scsi0. Since in PVE, most archives are
+compressed and piped to vma for restore, it's not easily possible to
+skip reads.
+
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[FE: improvements during create
+     allow partial restore]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ block/meson.build |   2 +
+ meson.build       |   5 +
+ vma-reader.c      | 867 +++++++++++++++++++++++++++++++++++++++++++++
+ vma-writer.c      | 793 +++++++++++++++++++++++++++++++++++++++++
+ vma.c             | 878 ++++++++++++++++++++++++++++++++++++++++++++++
+ vma.h             | 150 ++++++++
+ 6 files changed, 2695 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 253fe49fa2..744b698a82 100644
+--- a/block/meson.build
++++ b/block/meson.build
+@@ -47,6 +47,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'))
+ softmmu_ss.add(files('block-ram-registrar.c'))
+diff --git a/meson.build b/meson.build
+index d964e741e7..603cdb97bb 100644
+--- a/meson.build
++++ b/meson.build
+@@ -1527,6 +1527,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'),
+@@ -3646,6 +3648,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..81a891c6b1
+--- /dev/null
++++ b/vma-reader.c
+@@ -0,0 +1,867 @@
++/*
++ * 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/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;
++    bool skip;
++}  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->rstate[i].target) {
++            blk_unref(vmar->rstate[i].target);
++        }
++    }
++
++    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, bool skip)
++{
++    assert(vmar);
++    assert(dev_id);
++
++    vmar->rstate[dev_id].target = target;
++    vmar->rstate[dev_id].write_zeroes = write_zeroes;
++    vmar->rstate[dev_id].skip = skip;
++
++    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, bool skip, Error **errp)
++{
++    assert(vmar);
++    assert(target != NULL || skip);
++    assert(dev_id);
++    assert(vmar->rstate[dev_id].target == NULL && !vmar->rstate[dev_id].skip);
++
++    if (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, skip);
++
++    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, nb_sectors * BDRV_SECTOR_SIZE, buf, 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;
++
++        bool skip = rstate->skip;
++
++        if (dev_id != vmar->vmstate_stream) {
++            target = rstate->target;
++            if (!verify && !target && !skip) {
++                error_setg(errp, "got wrong dev id %d", dev_id);
++                return -1;
++            }
++
++            if (!skip) {
++                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 && !skip) {
++                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 && !skip) {
++                        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 && !skip) {
++                            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, 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..ac7da237d0
+--- /dev/null
++++ b/vma-writer.c
+@@ -0,0 +1,793 @@
++/*
++ * 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"
++#include "qemu/memalign.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, NULL, qemu_coroutine_self());
++    qemu_coroutine_yield();
++    aio_set_fd_handler(ctx, fd, false, NULL, 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_free(*errp);
++            *errp = NULL;
++            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..304f02bc84
+--- /dev/null
++++ b/vma.c
+@@ -0,0 +1,878 @@
++/*
++ * 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/module.h"
++#include "qemu/error-report.h"
++#include "qemu/main-loop.h"
++#include "qemu/cutils.h"
++#include "qemu/memalign.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;
++    bool skip;
++} 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;
++            char *devname = NULL;
++            bool skip = false;
++            uint64_t bps_value = 0;
++            const char *path = NULL;
++            bool write_zero = true;
++
++            if (!line || line[0] == '\0' || !strcmp(line, "done\n")) {
++                break;
++            }
++            int len = strlen(line);
++            if (line[len - 1] == '\n') {
++                line[len - 1] = '\0';
++                len = len - 1;
++                if (len == 0) {
++                    break;
++                }
++            }
++
++            if (strncmp(line, "skip", 4) == 0) {
++                if (len < 6 || line[4] != '=') {
++                    g_error("read map failed - option 'skip' has no value ('%s')",
++                            inbuf);
++                } else {
++                    devname = line + 5;
++                    skip = true;
++                }
++            } else {
++                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;
++                    }
++                }
++
++                if (bps) {
++                    bps_value = verify_u64(bps);
++                    g_free(bps);
++                }
++
++                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);
++                }
++
++                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;
++            map->skip = skip;
++
++            g_hash_table_insert(devmap, map->devname, map);
++
++        };
++    }
++
++    int i;
++    int vmstate_fd = -1;
++    guint8 vmstate_stream = 0;
++
++    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;
++            bool skip = false;
++
++            BlockBackend *blk = NULL;
++
++            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;
++                skip = map->skip;
++            } 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;
++            }
++
++            if (!skip) {
++                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, skip, &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);
++
++    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, readlen;
++    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);
++
++        if (start + 1 == end) {
++            memset(buf, 0, VMA_CLUSTER_SIZE);
++            readlen = job->len - start * VMA_CLUSTER_SIZE;
++            assert(readlen > 0 && readlen <= VMA_CLUSTER_SIZE);
++        } else {
++            readlen = VMA_CLUSTER_SIZE;
++        }
++
++        ret = blk_co_preadv(job->target, start * VMA_CLUSTER_SIZE,
++                            readlen, &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..1b62859165
+--- /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,
++                           bool skip, 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/0029-PVE-Backup-add-backup-dump-block-driver.patch b/debian/patches/pve/0029-PVE-Backup-add-backup-dump-block-driver.patch
new file mode 100644 (file)
index 0000000..cc4a679
--- /dev/null
@@ -0,0 +1,323 @@
+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>
+[FE: adapt to coroutine changes]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ block/backup-dump.c              | 168 +++++++++++++++++++++++++++++++
+ block/backup.c                   |  30 ++----
+ block/meson.build                |   1 +
+ include/block/block_int-common.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..232a094426
+--- /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 "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 coroutine_fn int qemu_backup_dump_co_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 coroutine_fn int64_t qemu_backup_dump_co_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_co_getlength            = qemu_backup_dump_co_getlength,
++    .bdrv_co_get_info             = qemu_backup_dump_co_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 39410dcf8d..af87fa6aa9 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 744b698a82..f580f95395 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-common.h b/include/block/block_int-common.h
+index f01bb8b617..d7ffd1826e 100644
+--- a/include/block/block_int-common.h
++++ b/include/block/block_int-common.h
+@@ -26,6 +26,7 @@
+ #include "block/aio.h"
+ #include "block/block-common.h"
++#include "block/block-copy.h"
+ #include "block/block-global-state.h"
+ #include "block/snapshot.h"
+ #include "qemu/iov.h"
+@@ -60,6 +61,40 @@
+ #define BLOCK_PROBE_BUF_SIZE        512
++typedef int BackupDumpFunc(void *opaque, uint64_t offset, uint64_t bytes, const void *buf);
++
++BlockDriverState *bdrv_backup_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 72d57f0934..93e22d180b 100644
+--- a/job.c
++++ b/job.c
+@@ -330,7 +330,8 @@ static bool job_started_locked(Job *job)
+ }
+ /* Called with job_mutex held. */
+-static bool job_should_pause_locked(Job *job)
++bool job_should_pause_locked(Job *job);
++bool job_should_pause_locked(Job *job)
+ {
+     return job->pause_count > 0;
+ }
diff --git a/debian/patches/pve/0029-PVE-Backup-proxmox-backup-patches-for-qemu.patch b/debian/patches/pve/0029-PVE-Backup-proxmox-backup-patches-for-qemu.patch
deleted file mode 100644 (file)
index 3aae91f..0000000
+++ /dev/null
@@ -1,1628 +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>
-[FE: add new force parameter to job_cancel_sync calls
-     adapt for new job lock mechanism replacing AioContext locks
-     adapt to QAPI changes]
-Signed-off-by: Fiona 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/monitor/hmp.h          |   3 +
- meson.build                    |   1 +
- monitor/hmp-cmds.c             |  45 ++
- proxmox-backup-client.c        | 176 +++++++
- proxmox-backup-client.h        |  59 +++
- pve-backup.c                   | 938 +++++++++++++++++++++++++++++++++
- qapi/block-core.json           | 109 ++++
- qapi/common.json               |  13 +
- qapi/machine.json              |  15 +-
- 14 files changed, 1428 insertions(+), 13 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 f580f95395..5bcebb934b 100644
---- a/block/meson.build
-+++ b/block/meson.build
-@@ -49,6 +49,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'))
- softmmu_ss.add(files('block-ram-registrar.c'))
-diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
-index ca2599de44..d50e99df26 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1029,3 +1029,36 @@ void hmp_change_medium(Monitor *mon, const char *device, const char *target,
-     qmp_blockdev_change_medium(device, NULL, target, arg, true, force,
-                                !!read_only, read_only_mode, errp);
- }
-+
-+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,
-+        NULL, // PBS password
-+        NULL, // PBS keyfile
-+        NULL, // PBS key_password
-+        NULL, // PBS fingerprint
-+        NULL, // PBS backup-id
-+        false, 0, // PBS backup-time
-+        true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
-+        NULL, NULL,
-+        devlist, qdict_haskey(qdict, "speed"), speed, &error);
-+
-+    hmp_handle_error(mon, error);
-+}
-diff --git a/blockdev.c b/blockdev.c
-index 9a010f3a86..b9505c95d3 100644
---- a/blockdev.c
-+++ b/blockdev.c
-@@ -37,6 +37,7 @@
- #include "block/blockjob.h"
- #include "block/dirty-bitmap.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 a166bff3d5..4b75966c2e 100644
---- a/hmp-commands-info.hx
-+++ b/hmp-commands-info.hx
-@@ -486,6 +486,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 b66d7fc4ab..9b6b8e2e9c 100644
---- a/hmp-commands.hx
-+++ b/hmp-commands.hx
-@@ -101,6 +101,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/monitor/hmp.h b/include/monitor/hmp.h
-index c012bad741..2e504db706 100644
---- a/include/monitor/hmp.h
-+++ b/include/monitor/hmp.h
-@@ -32,6 +32,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);
-@@ -84,6 +85,8 @@ void hmp_change_vnc(Monitor *mon, const char *device, const char *target,
- void hmp_change_medium(Monitor *mon, const char *device, const char *target,
-                        const char *arg, const char *read_only, bool force,
-                        Error **errp);
-+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 603cdb97bb..d307d8eabf 100644
---- a/meson.build
-+++ b/meson.build
-@@ -1528,6 +1528,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 435f9334f9..9e1bd57aeb 100644
---- a/monitor/hmp-cmds.c
-+++ b/monitor/hmp-cmds.c
-@@ -21,6 +21,7 @@
- #include "qemu/help_option.h"
- #include "monitor/monitor-internal.h"
- #include "qapi/error.h"
-+#include "qapi/qapi-commands-block-core.h"
- #include "qapi/qapi-commands-control.h"
- #include "qapi/qapi-commands-migration.h"
- #include "qapi/qapi-commands-misc.h"
-@@ -144,6 +145,50 @@ void hmp_sync_profile(Monitor *mon, const QDict *qdict)
-     }
- }
-+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->status) {
-+        if (info->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->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);
-+}
-+
- void hmp_exit_preconfig(Monitor *mon, const QDict *qdict)
- {
-     Error *err = NULL;
-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..f77892a509
---- /dev/null
-+++ b/pve-backup.c
-@@ -0,0 +1,938 @@
-+#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/block_int-global-state.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) {
-+        WITH_JOB_LOCK_GUARD() {
-+            for (BlockJob *job = block_job_next_locked(NULL); job; job = block_job_next_locked(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) {
-+            job_cancel_sync(&next_job->job, true);
-+        } 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_locked(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);
-+
-+            WITH_JOB_LOCK_GUARD() {
-+                if (job_should_pause_locked(&job->job)) {
-+                    bool error_or_canceled = pvebackup_error_or_canceled();
-+                    if (error_or_canceled) {
-+                        job_cancel_sync_locked(&job->job, true);
-+                    } else {
-+                        job_resume_locked(&job->job);
-+                    }
-+                }
-+            }
-+            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;
-+    const char *password;
-+    const char *keyfile;
-+    const char *key_password;
-+    const char *backup_id;
-+    bool has_backup_time;
-+    const char *fingerprint;
-+    int64_t backup_time;
-+    bool has_format;
-+    BackupFormat format;
-+    const char *config_file;
-+    const char *firewall_file;
-+    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->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, -size, "bdrv_getlength failed");
-+            goto err;
-+        }
-+        di->size = size;
-+        total += size;
-+    }
-+
-+    uuid_generate(uuid);
-+
-+    if (format == BACKUP_FORMAT_PBS) {
-+        if (!task->password) {
-+            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
-+            goto err;
-+        }
-+        if (!task->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->password,
-+            task->keyfile,
-+            task->key_password,
-+            task->fingerprint,
-+            &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_co_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->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->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_co_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,
-+    const char *password,
-+    const char *keyfile,
-+    const char *key_password,
-+    const char *fingerprint,
-+    const char *backup_id,
-+    bool has_backup_time, int64_t backup_time,
-+    bool has_format, BackupFormat format,
-+    const char *config_file,
-+    const char *firewall_file,
-+    const char *devlist,
-+    bool has_speed, int64_t speed, Error **errp)
-+{
-+    QmpBackupTask task = {
-+        .backup_file = backup_file,
-+        .password = password,
-+        .key_password = key_password,
-+        .fingerprint = fingerprint,
-+        .backup_id = backup_id,
-+        .has_backup_time = has_backup_time,
-+        .backup_time = backup_time,
-+        .has_format = has_format,
-+        .format = format,
-+        .config_file = config_file,
-+        .firewall_file = firewall_file,
-+        .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_start_time = true;
-+    info->start_time = backup_state.stat.start_time;
-+
-+    if (backup_state.stat.backup_file) {
-+        info->backup_file = g_strdup(backup_state.stat.backup_file);
-+    }
-+
-+    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->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 542add004b..16fb4c5ea0 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -835,6 +835,115 @@
- { 'command': 'query-block', 'returns': ['BlockInfo'],
-   'allow-preconfig': true }
-+##
-+# @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 356db3f670..aae8a3b682 100644
---- a/qapi/common.json
-+++ b/qapi/common.json
-@@ -206,3 +206,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 47f3facdb2..46760978ae 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
- ##
-@@ -228,19 +230,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/0030-PVE-Add-sequential-job-transaction-support.patch b/debian/patches/pve/0030-PVE-Add-sequential-job-transaction-support.patch
new file mode 100644 (file)
index 0000000..fbf610e
--- /dev/null
@@ -0,0 +1,101 @@
+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              | 34 ++++++++++++++++++++++++++++++++++
+ 2 files changed, 46 insertions(+)
+
+diff --git a/include/qemu/job.h b/include/qemu/job.h
+index e502787dd8..963cf2bef5 100644
+--- a/include/qemu/job.h
++++ b/include/qemu/job.h
+@@ -381,6 +381,18 @@ void job_unlock(void);
+  */
+ 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 93e22d180b..2b31f1e14f 100644
+--- a/job.c
++++ b/job.c
+@@ -93,6 +93,8 @@ struct JobTxn {
+     /* Reference count */
+     int refcnt;
++
++    bool sequential;
+ };
+ void job_lock(void)
+@@ -118,6 +120,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);
++}
++
+ /* Called with job_mutex held. */
+ static void job_txn_ref_locked(JobTxn *txn)
+ {
+@@ -1057,6 +1078,12 @@ static void job_completed_txn_success_locked(Job *job)
+      */
+     QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
+         if (!job_is_completed_locked(other_job)) {
++            if (txn->sequential) {
++                job_unlock();
++                /* Needs to be called without holding the job lock */
++                job_start(other_job);
++                job_lock();
++            }
+             return;
+         }
+         assert(other_job->ret == 0);
+@@ -1268,6 +1295,13 @@ int job_finish_sync_locked(Job *job,
+         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_locked(job);
++    }
++
+     job_unlock();
+     AIO_WAIT_WHILE_UNLOCKED(job->aio_context,
+                             (job_enter(job), !job_is_completed(job)));
diff --git a/debian/patches/pve/0030-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch b/debian/patches/pve/0030-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch
deleted file mode 100644 (file)
index 4ee6100..0000000
+++ /dev/null
@@ -1,257 +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 | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++
- 2 files changed, 227 insertions(+)
- create mode 100644 pbs-restore.c
-
-diff --git a/meson.build b/meson.build
-index d307d8eabf..afd105001e 100644
---- a/meson.build
-+++ b/meson.build
-@@ -3652,6 +3652,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..2f834cf42e
---- /dev/null
-+++ b/pbs-restore.c
-@@ -0,0 +1,223 @@
-+/*
-+ * 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/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_len, data, 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/0031-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch b/debian/patches/pve/0031-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch
deleted file mode 100644 (file)
index 1708021..0000000
+++ /dev/null
@@ -1,460 +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                   | 104 ++++++++++++++++++++++++++++++---
- qapi/block-core.json           |  12 +++-
- 6 files changed, 143 insertions(+), 23 deletions(-)
-
-diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
-index d50e99df26..cda5de792b 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1056,6 +1056,7 @@ void hmp_backup(Monitor *mon, const QDict *qdict)
-         NULL, // PBS fingerprint
-         NULL, // PBS backup-id
-         false, 0, // PBS backup-time
-+        false, false, // PBS incremental
-         true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
-         NULL, NULL,
-         devlist, qdict_haskey(qdict, "speed"), speed, &error);
-diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
-index 9e1bd57aeb..087161a967 100644
---- a/monitor/hmp-cmds.c
-+++ b/monitor/hmp-cmds.c
-@@ -171,19 +171,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 f77892a509..d9942a14a1 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -7,6 +7,7 @@
- #include "sysemu/blockdev.h"
- #include "block/block_int-global-state.h"
- #include "block/blockjob.h"
-+#include "block/dirty-bitmap.h"
- #include "qapi/qapi-commands-block.h"
- #include "qapi/qmp/qerror.h"
-@@ -29,6 +30,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
-@@ -40,7 +43,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;
-@@ -67,6 +72,7 @@ typedef struct PVEBackupDevInfo {
-     uint8_t dev_id;
-     bool completed;
-     char targetfile[PATH_MAX];
-+    BdrvDirtyBitmap *bitmap;
-     BlockDriverState *target;
- } PVEBackupDevInfo;
-@@ -108,11 +114,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);
- }
-@@ -151,7 +158,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;
-@@ -211,11 +219,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;
-             }
-         }
-@@ -251,6 +259,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);
-@@ -306,6 +326,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);
-@@ -470,12 +496,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);
-@@ -521,6 +553,8 @@ typedef struct QmpBackupTask {
-     bool has_backup_time;
-     const char *fingerprint;
-     int64_t backup_time;
-+    bool has_use_dirty_bitmap;
-+    bool use_dirty_bitmap;
-     bool has_format;
-     BackupFormat format;
-     const char *config_file;
-@@ -609,6 +643,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     }
-     size_t total = 0;
-+    size_t dirty = 0;
-     l = di_list;
-     while (l) {
-@@ -646,6 +681,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,
-@@ -665,7 +702,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 */
-@@ -676,9 +714,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;
-@@ -687,6 +756,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) {
-@@ -714,6 +785,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);
-@@ -786,8 +859,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);
-@@ -811,6 +886,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_co_unref(di->target);
-         }
-@@ -852,6 +931,7 @@ UuidInfo *qmp_backup(
-     const char *fingerprint,
-     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,
-     const char *config_file,
-     const char *firewall_file,
-@@ -866,6 +946,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,
-         .config_file = config_file,
-@@ -927,10 +1009,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 16fb4c5ea0..92f90a898a 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -848,8 +848,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.
-@@ -862,8 +867,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' } }
-@@ -906,6 +911,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
- #
- ##
-@@ -916,6 +923,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/0031-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch b/debian/patches/pve/0031-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch
new file mode 100644 (file)
index 0000000..99b2a08
--- /dev/null
@@ -0,0 +1,1981 @@
+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
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+For PBS, using dirty bitmaps is supported via QEMU's
+MIRROR_SYNC_MODE_BITMAP. When the feature is used, the data-write
+callback is only executed 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, all dirty
+bitmaps are removed to ensure consistency.
+
+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. A sequential transaction is used, so drives will be backed up
+one after the other.
+
+The backup and backup-cancel QMP calls are coroutines. 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.
+
+backup_job_create() and job_cancel_sync() cannot be run from a
+coroutine and requires an acuqired AioContext, so the job creation and
+canceling are extracted as bottom halves and called from the
+respective QMP coroutines.
+
+To communicate the finishing state, a dedicated property is used for
+query-backup: 'finishing'. A dedicated state is explicitly not used,
+since that would break compatibility with older qemu-server versions.
+
+The first call to job_cancel_sync() will cancel and free all jobs in
+the transaction, but it is necessary to pick a job that is:
+1. still referenced. For this, there is a job_ref directly after job
+creation paired with a job_unref in cleanup paths.
+2. not yet finalized. In job_cancel_bh(), the first job that's not
+completed yet is used. This is not necessarily the first job in the
+list, because pvebackup_co_complete_stream() might not yet have
+removed a completed job when job_cancel_bh() runs. Why even bother
+with the bottom half at all and not use job_cancel() in
+qmp_backup_cancel() directly? The reason is that qmp_backup_cancel()
+is a coroutine, so it will hang when reaching AIO_WAIT_WHILE() and
+job_cancel() might end up calling that.
+
+Regarding BackupPerf performance settings. For now, only the
+max-workers setting is exposed, because:
+1. use-copy-range would need to be implemented in backup-dump and the
+feature was actually turned off by default in QEMU itself, because it
+didn't provide the expected benefit, see commit 6a30f663d4 ("qapi:
+backup: disable copy_range by default").
+2. max-chunk: enforced to be at least the backup cluster size (4 MiB
+for PBS) and otherwise maximum of source and target cluster size.
+And block-copy has a maximum buffer size of 1 MiB, so setting a larger
+max-chunk doesn't even have an effect. To make the setting sensibly
+usable the check would need to be removed and optionally the
+block-copy max buffer size would need to be bumped. I tried doing just
+that, and tested different source/target combinations with different
+max-chunk settings, but there were no noticable improvements over the
+default "unlimited" (resulting in 1 MiB for block-copy).
+
+Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
+[SR: Add dirty-bitmap tracking for incremental backups
+     Add query_proxmox_support and query-pbs-bitmap-info QMP calls
+     Use a transaction to synchronize job states
+     Co-routine and async-related improvements
+     Improve finishing backups/cleanups
+     Various other improvements]
+Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+[FG: add master key support]
+Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[WB: add PBS namespace support]
+Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
+[FE: add new force parameter to job_cancel_sync calls
+     adapt for new job lock mechanism replacing AioContext locks
+     adapt to QAPI changes
+     improve canceling
+     allow passing max-workers setting]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ block/meson.build              |    5 +
+ block/monitor/block-hmp-cmds.c |   40 ++
+ blockdev.c                     |    1 +
+ hmp-commands-info.hx           |   14 +
+ hmp-commands.hx                |   31 +
+ include/monitor/hmp.h          |    3 +
+ meson.build                    |    1 +
+ monitor/hmp-cmds.c             |   72 +++
+ proxmox-backup-client.c        |  146 +++++
+ proxmox-backup-client.h        |   60 ++
+ pve-backup.c                   | 1097 ++++++++++++++++++++++++++++++++
+ qapi/block-core.json           |  226 +++++++
+ qapi/common.json               |   13 +
+ qapi/machine.json              |   15 +-
+ 14 files changed, 1711 insertions(+), 13 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 f580f95395..5bcebb934b 100644
+--- a/block/meson.build
++++ b/block/meson.build
+@@ -49,6 +49,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'))
+ softmmu_ss.add(files('block-ram-registrar.c'))
+diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
+index ca2599de44..636509b83e 100644
+--- a/block/monitor/block-hmp-cmds.c
++++ b/block/monitor/block-hmp-cmds.c
+@@ -1029,3 +1029,43 @@ void hmp_change_medium(Monitor *mon, const char *device, const char *target,
+     qmp_blockdev_change_medium(device, NULL, target, arg, true, force,
+                                !!read_only, read_only_mode, errp);
+ }
++
++void coroutine_fn hmp_backup_cancel(Monitor *mon, const QDict *qdict)
++{
++    Error *error = NULL;
++
++    qmp_backup_cancel(&error);
++
++    hmp_handle_error(mon, error);
++}
++
++void coroutine_fn 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,
++        NULL, // PBS password
++        NULL, // PBS keyfile
++        NULL, // PBS key_password
++        NULL, // PBS master_keyfile
++        NULL, // PBS fingerprint
++        NULL, // PBS backup-ns
++        NULL, // PBS backup-id
++        false, 0, // PBS backup-time
++        false, false, // PBS use-dirty-bitmap
++        false, false, // PBS compress
++        false, false, // PBS encrypt
++        true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
++        NULL, NULL,
++        devlist, qdict_haskey(qdict, "speed"), speed,
++        false, 0, // BackupPerf max-workers
++        &error);
++
++    hmp_handle_error(mon, error);
++}
+diff --git a/blockdev.c b/blockdev.c
+index 9a010f3a86..b9505c95d3 100644
+--- a/blockdev.c
++++ b/blockdev.c
+@@ -37,6 +37,7 @@
+ #include "block/blockjob.h"
+ #include "block/dirty-bitmap.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 a166bff3d5..4b75966c2e 100644
+--- a/hmp-commands-info.hx
++++ b/hmp-commands-info.hx
+@@ -486,6 +486,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 b66d7fc4ab..896430dae8 100644
+--- a/hmp-commands.hx
++++ b/hmp-commands.hx
+@@ -101,6 +101,37 @@ 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,
++        .coroutine  = true,
++    },
++
++SRST
++``backup``
++  Create a VM backup.
++ERST
++
++    {
++        .name       = "backup_cancel",
++        .args_type  = "",
++        .params     = "",
++        .help       = "cancel the current VM backup",
++        .cmd        = hmp_backup_cancel,
++        .coroutine  = true,
++    },
++
++SRST
++``backup_cancel``
++  Cancel the current VM backup.
++
+ ERST
+     {
+diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h
+index c012bad741..2e504db706 100644
+--- a/include/monitor/hmp.h
++++ b/include/monitor/hmp.h
+@@ -32,6 +32,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);
+@@ -84,6 +85,8 @@ void hmp_change_vnc(Monitor *mon, const char *device, const char *target,
+ void hmp_change_medium(Monitor *mon, const char *device, const char *target,
+                        const char *arg, const char *read_only, bool force,
+                        Error **errp);
++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 603cdb97bb..d307d8eabf 100644
+--- a/meson.build
++++ b/meson.build
+@@ -1528,6 +1528,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 435f9334f9..9a67e544ce 100644
+--- a/monitor/hmp-cmds.c
++++ b/monitor/hmp-cmds.c
+@@ -21,6 +21,7 @@
+ #include "qemu/help_option.h"
+ #include "monitor/monitor-internal.h"
+ #include "qapi/error.h"
++#include "qapi/qapi-commands-block-core.h"
+ #include "qapi/qapi-commands-control.h"
+ #include "qapi/qapi-commands-migration.h"
+ #include "qapi/qapi-commands-misc.h"
+@@ -144,6 +145,77 @@ void hmp_sync_profile(Monitor *mon, const QDict *qdict)
+     }
+ }
++void hmp_info_backup(Monitor *mon, const QDict *qdict)
++{
++    BackupStatus *info;
++    PBSBitmapInfoList *bitmap_info;
++
++    info = qmp_query_backup(NULL);
++
++    if (!info) {
++       monitor_printf(mon, "Backup status: not initialized\n");
++       return;
++    }
++
++    if (info->status) {
++        if (info->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->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));
++        }
++
++        monitor_printf(mon, "Backup file: %s\n", info->backup_file);
++        monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
++
++        if (!(info->has_total && info->total))  {
++            // this should not happen normally
++            monitor_printf(mon, "Total size: %d\n", 0);
++        } else {
++            size_t total_or_dirty = info->total;
++            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;
++            }
++
++            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, trans_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);
++}
++
+ void hmp_exit_preconfig(Monitor *mon, const QDict *qdict)
+ {
+     Error *err = NULL;
+diff --git a/proxmox-backup-client.c b/proxmox-backup-client.c
+new file mode 100644
+index 0000000000..0923037dec
+--- /dev/null
++++ b/proxmox-backup-client.c
+@@ -0,0 +1,146 @@
++#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 */
++
++// 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,
++    bool incremental,
++    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, 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");
++        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..8cbf645b2c
+--- /dev/null
++++ b/proxmox-backup-client.h
+@@ -0,0 +1,60 @@
++#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,
++    bool incremental,
++    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..dd72ee0ed6
+--- /dev/null
++++ b/pve-backup.c
+@@ -0,0 +1,1097 @@
++#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/block_int-global-state.h"
++#include "block/blockjob.h"
++#include "block/dirty-bitmap.h"
++#include "qapi/qapi-commands-block.h"
++#include "qapi/qmp/qerror.h"
++#include "qemu/cutils.h"
++
++#include <proxmox-backup-qemu.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)
++ *
++ */
++
++const char *PBS_BITMAP_NAME = "pbs-incremental-dirty-bitmap";
++
++static struct PVEBackupState {
++    struct {
++        // 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;
++        time_t end_time;
++        char *backup_file;
++        uuid_t uuid;
++        char uuid_str[37];
++        size_t total;
++        size_t dirty;
++        size_t transferred;
++        size_t reused;
++        size_t zero_bytes;
++        GList *bitmap_list;
++        bool finishing;
++        bool starting;
++    } stat;
++    int64_t speed;
++    BackupPerf perf;
++    VmaWriter *vmaw;
++    ProxmoxBackupHandle *pbs;
++    GList *di_list;
++    JobTxn *txn;
++    CoMutex backup_mutex;
++    CoMutex dump_callback_mutex;
++} backup_state;
++
++static void pvebackup_init(void)
++{
++    qemu_mutex_init(&backup_state.stat.lock);
++    qemu_co_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;
++    uint64_t block_size;
++    uint8_t dev_id;
++    int completed_ret; // INT_MAX if not completed
++    char targetfile[PATH_MAX];
++    BdrvDirtyBitmap *bitmap;
++    BlockDriverState *target;
++    BlockJob *job;
++} PVEBackupDevInfo;
++
++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, 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);
++}
++
++// 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);
++    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
++    if (pvebackup_error_or_canceled()) {
++        qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
++        return -1;
++    }
++
++    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);
++    pvebackup_add_transfered_bytes(size, is_zero_block ? size : 0, reused);
++
++    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);
++    assert(buf);
++
++    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;
++        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, 0);
++                remaining -= VMA_CLUSTER_SIZE;
++            } else {
++                assert(ret == remaining);
++                pvebackup_add_transfered_bytes(remaining, zero_bytes, 0);
++                remaining = 0;
++            }
++        }
++    }
++
++    return size;
++}
++
++// assumes the caller holds backup_mutex
++static void coroutine_fn pvebackup_co_cleanup(void)
++{
++    assert(qemu_in_coroutine());
++
++    qemu_mutex_lock(&backup_state.stat.lock);
++    backup_state.stat.finishing = true;
++    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;
++
++    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);
++}
++
++static void coroutine_fn pvebackup_co_complete_stream(void *opaque)
++{
++    PVEBackupDevInfo *di = opaque;
++    int ret = di->completed_ret;
++
++    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;
++    }
++
++    qemu_co_mutex_lock(&backup_state.backup_mutex);
++
++    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);
++
++    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);
++        }
++    }
++
++    if (di->job) {
++        WITH_JOB_LOCK_GUARD() {
++            job_unref_locked(&di->job->job);
++            di->job = NULL;
++        }
++    }
++
++    // remove self from job list
++    backup_state.di_list = g_list_remove(backup_state.di_list, di);
++
++    g_free(di);
++
++    /* call cleanup if we're the last job */
++    if (!g_list_first(backup_state.di_list)) {
++        pvebackup_co_cleanup();
++    }
++
++    qemu_co_mutex_unlock(&backup_state.backup_mutex);
++}
++
++static void pvebackup_complete_cb(void *opaque, int ret)
++{
++    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. Assumes that caller holds
++ * backup_mutex.
++ */
++static void job_cancel_bh(void *opaque) {
++    CoCtxData *data = (CoCtxData*)opaque;
++
++    /*
++     * Be careful to pick a valid job to cancel:
++     * 1. job_cancel_sync() does not expect the job to be finalized already.
++     * 2. job_exit() might run between scheduling and running job_cancel_bh()
++     *    and pvebackup_co_complete_stream() might not have removed the job from
++     *    the list yet (in fact, cannot, because it waits for the backup_mutex).
++     * Requiring !job_is_completed() ensures that no finalized job is picked.
++     */
++    GList *bdi = g_list_first(backup_state.di_list);
++    while (bdi) {
++        if (bdi->data) {
++            BlockJob *bj = ((PVEBackupDevInfo *)bdi->data)->job;
++            if (bj) {
++                Job *job = &bj->job;
++                WITH_JOB_LOCK_GUARD() {
++                    if (!job_is_completed_locked(job)) {
++                        job_cancel_sync_locked(job, true);
++                        /*
++                         * It's enough to cancel one job in the transaction, the
++                         * rest will follow automatically.
++                         */
++                        break;
++                    }
++                }
++            }
++        }
++        bdi = g_list_next(bdi);
++    }
++
++    aio_co_enter(data->ctx, data->co);
++}
++
++void coroutine_fn qmp_backup_cancel(Error **errp)
++{
++    Error *cancel_err = NULL;
++    error_setg(&cancel_err, "backup canceled");
++    pvebackup_propagate_error(cancel_err);
++
++    qemu_co_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");
++    }
++
++    CoCtxData data = {
++        .ctx = qemu_get_current_aio_context(),
++        .co = qemu_coroutine_self(),
++    };
++    aio_bh_schedule_oneshot(data.ctx, job_cancel_bh, &data);
++    qemu_coroutine_yield();
++
++    qemu_co_mutex_unlock(&backup_state.backup_mutex);
++}
++
++// 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;
++}
++
++/*
++ * 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
++     * jobs in case one errors */
++    if (backup_state.txn) {
++        job_txn_unref(backup_state.txn);
++    }
++    backup_state.txn = job_txn_new_seq();
++
++    /* 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);
++
++        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, sync_mode, di->bitmap,
++            bitmap_mode, false, NULL, &backup_state.perf, BLOCKDEV_ON_ERROR_REPORT,
++            BLOCKDEV_ON_ERROR_REPORT, JOB_DEFAULT, pvebackup_complete_cb, di, backup_state.txn,
++            &local_err);
++
++        aio_context_release(aio_context);
++
++        di->job = job;
++        if (job) {
++            WITH_JOB_LOCK_GUARD() {
++                job_ref_locked(&job->job);
++            }
++        }
++
++        if (!job || local_err) {
++            error_setg(errp, "backup_job_create failed: %s",
++                       local_err ? error_get_pretty(local_err) : "null");
++            break;
++        }
++
++        bdrv_unref(di->target);
++        di->target = NULL;
++    }
++
++    if (*errp) {
++        /*
++         * It's enough to cancel one job in the transaction, the rest will
++         * follow automatically.
++         */
++        bool canceled = false;
++        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;
++            }
++
++            if (di->job) {
++                WITH_JOB_LOCK_GUARD() {
++                    if (!canceled) {
++                        job_cancel_sync_locked(&di->job->job, true);
++                        canceled = true;
++                    }
++                    job_unref_locked(&di->job->job);
++                    di->job = NULL;
++                }
++            }
++        }
++    }
++
++    /* return */
++    aio_co_enter(data->ctx, data->co);
++}
++
++UuidInfo coroutine_fn *qmp_backup(
++    const char *backup_file,
++    const char *password,
++    const char *keyfile,
++    const char *key_password,
++    const char *master_keyfile,
++    const char *fingerprint,
++    const char *backup_ns,
++    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,
++    const char *config_file,
++    const char *firewall_file,
++    const char *devlist,
++    bool has_speed, int64_t speed,
++    bool has_max_workers, int64_t max_workers,
++    Error **errp)
++{
++    assert(qemu_in_coroutine());
++
++    qemu_co_mutex_lock(&backup_state.backup_mutex);
++
++    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(errp, ERROR_CLASS_GENERIC_ERROR,
++                  "previous backup not finished");
++        qemu_co_mutex_unlock(&backup_state.backup_mutex);
++        return NULL;
++    }
++
++    /* Todo: try to auto-detect format based on file name */
++    format = has_format ? format : BACKUP_FORMAT_VMA;
++
++    if (devlist) {
++        devs = g_strsplit_set(devlist, ",;:", -1);
++
++        gchar **d = devs;
++        while (d && *d) {
++            blk = blk_by_name(*d);
++            if (blk) {
++                bs = blk_bs(blk);
++                if (!bdrv_co_is_inserted(bs)) {
++                    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(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_co_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(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, errp)) {
++            goto err;
++        }
++
++        ssize_t size = bdrv_getlength(di->bs);
++        if (size < 0) {
++            error_setg_errno(errp, -size, "bdrv_getlength failed");
++            goto err;
++        }
++        di->size = size;
++        total += size;
++
++        di->completed_ret = INT_MAX;
++    }
++
++    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 (!password) {
++            error_set(errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
++            goto err_mutex;
++        }
++        if (!backup_id) {
++            error_set(errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
++            goto err_mutex;
++        }
++        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";
++
++        char *pbs_err = NULL;
++        pbs = proxmox_backup_new_ns(
++            backup_file,
++            backup_ns,
++            backup_id,
++            backup_time,
++            dump_cb_block_size,
++            password,
++            keyfile,
++            key_password,
++            master_keyfile,
++            has_compress ? compress : true,
++            has_encrypt ? encrypt : !!keyfile,
++            fingerprint,
++            &pbs_err);
++
++        if (!pbs) {
++            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, errp);
++        if (connect_result < 0)
++            goto err_mutex;
++
++        /* register all devices */
++        l = di_list;
++        while (l) {
++            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);
++            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;
++
++            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, errp);
++                    if (!bitmap) {
++                        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) {
++                    /* 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);
++                    if (action != PBS_BITMAP_ACTION_NEW) {
++                        action = PBS_BITMAP_ACTION_INVALID;
++                    }
++                }
++                di->bitmap = bitmap;
++            } else {
++                /* 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, 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, errp))) {
++                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) {
++        vmaw = vma_writer_create(backup_file, uuid, &local_err);
++        if (!vmaw) {
++            if (local_err) {
++                error_propagate(errp, local_err);
++            }
++            goto err_mutex;
++        }
++
++        /* 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, 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(errp, ERROR_CLASS_GENERIC_ERROR,
++                          "register_stream failed");
++                goto err_mutex;
++            }
++        }
++    } else if (format == BACKUP_FORMAT_DIR) {
++        if (mkdir(backup_file, 0640) != 0) {
++            error_setg_errno(errp, errno, "can't create directory '%s'\n",
++                             backup_file);
++            goto err_mutex;
++        }
++        backup_dir = 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(errp, local_err);
++                goto err_mutex;
++            }
++
++            di->target = bdrv_co_open(di->targetfile, NULL, NULL, flags, &local_err);
++            if (!di->target) {
++                error_propagate(errp, local_err);
++                goto err_mutex;
++            }
++        }
++    } else {
++        error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
++        goto err_mutex;
++    }
++
++
++    /* add configuration file to archive */
++    if (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 (firewall_file) {
++        if (pvebackup_co_add_config(firewall_file, firewall_name, format, backup_dir,
++                                    vmaw, pbs, errp) != 0) {
++            goto err_mutex;
++        }
++    }
++    /* initialize global backup_state now */
++    /* note: 'reused' and 'bitmap_list' are initialized earlier */
++
++    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(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.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);
++
++    backup_state.speed = (has_speed && speed > 0) ? speed : 0;
++
++    backup_state.perf = (BackupPerf){ .max_workers = 16 };
++    if (has_max_workers) {
++        backup_state.perf.max_workers = max_workers;
++    }
++
++    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;
++
++    /* 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(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 uuid_info;
++
++err_mutex:
++    qemu_mutex_unlock(&backup_state.stat.lock);
++
++err:
++
++    l = di_list;
++    while (l) {
++        PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++        l = g_list_next(l);
++
++        if (di->target) {
++            bdrv_co_unref(di->target);
++        }
++
++        if (di->targetfile[0]) {
++            unlink(di->targetfile);
++        }
++        g_free(di);
++    }
++    g_list_free(di_list);
++    backup_state.di_list = NULL;
++
++    if (devs) {
++        g_strfreev(devs);
++    }
++
++    if (vmaw) {
++        Error *err = NULL;
++        vma_writer_close(vmaw, &err);
++        unlink(backup_file);
++    }
++
++    if (pbs) {
++        proxmox_backup_disconnect(pbs);
++        backup_state.pbs = NULL;
++    }
++
++    if (backup_dir) {
++        rmdir(backup_dir);
++    }
++
++    qemu_co_mutex_unlock(&backup_state.backup_mutex);
++    return NULL;
++}
++
++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_start_time = true;
++    info->start_time = backup_state.stat.start_time;
++
++    if (backup_state.stat.backup_file) {
++        info->backup_file = g_strdup(backup_state.stat.backup_file);
++    }
++
++    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->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_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;
++    info->finishing = backup_state.stat.finishing;
++
++    qemu_mutex_unlock(&backup_state.stat.lock);
++
++    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;
++    ret->pbs_masterkey = true;
++    ret->backup_max_workers = true;
++    return ret;
++}
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index 542add004b..4ec70acf95 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -835,6 +835,232 @@
+ { 'command': 'query-block', 'returns': ['BlockInfo'],
+   'allow-preconfig': true }
++##
++# @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
++#
++# @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.
++#
++# @end-time: time (epoch) when backup job finished.
++#
++# @backup-file: backup file name
++#
++# @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', 'finishing': 'bool' } }
++
++##
++# @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')
++#
++# @master-keyfile: PEM-formatted master public keyfile (optional for format 'pbs')
++#
++# @fingerprint: server cert fingerprint (optional for format 'pbs')
++#
++# @backup-ns: backup namespace (required for format 'pbs')
++#
++# @backup-id: backup ID (required for format 'pbs')
++#
++# @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')
++#
++# @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)
++#
++# @max-workers: see @BackupPerf for details. Default 16.
++#
++# Returns: the uuid of the backup job
++#
++##
++{ 'command': 'backup', 'data': { 'backup-file': 'str',
++                                    '*password': 'str',
++                                    '*keyfile': 'str',
++                                    '*key-password': 'str',
++                                    '*master-keyfile': 'str',
++                                    '*fingerprint': 'str',
++                                    '*backup-ns': 'str',
++                                    '*backup-id': 'str',
++                                    '*backup-time': 'int',
++                                    '*use-dirty-bitmap': 'bool',
++                                    '*compress': 'bool',
++                                    '*encrypt': 'bool',
++                                    '*format': 'BackupFormat',
++                                    '*config-file': 'str',
++                                    '*firewall-file': 'str',
++                                    '*devlist': 'str',
++                                    '*speed': 'int',
++                                    '*max-workers': 'int' },
++  'returns': 'UuidInfo', 'coroutine': true }
++
++##
++# @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', 'coroutine': true }
++
++##
++# @ProxmoxSupportStatus:
++#
++# Contains info about supported features added by Proxmox.
++#
++# @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.
++#
++# @pbs-masterkey: True if the QMP backup call supports the 'master_keyfile'
++#                 parameter.
++#
++# @pbs-library-version: Running version of libproxmox-backup-qemu0 library.
++#
++##
++{ 'struct': 'ProxmoxSupportStatus',
++  'data': { 'pbs-dirty-bitmap': 'bool',
++            'query-bitmap-info': 'bool',
++            'pbs-dirty-bitmap-savevm': 'bool',
++            'pbs-masterkey': 'bool',
++            'pbs-library-version': 'str',
++            'backup-max-workers': 'bool' } }
++
++##
++# @query-proxmox-support:
++#
++# Returns information about supported features added by Proxmox.
++#
++# Returns: @ProxmoxSupportStatus
++#
++##
++{ '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/qapi/common.json b/qapi/common.json
+index 356db3f670..aae8a3b682 100644
+--- a/qapi/common.json
++++ b/qapi/common.json
+@@ -206,3 +206,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 47f3facdb2..46760978ae 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
+ ##
+@@ -228,19 +230,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/0032-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch b/debian/patches/pve/0032-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch
new file mode 100644 (file)
index 0000000..1acb93d
--- /dev/null
@@ -0,0 +1,272 @@
+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>
+[WB: add namespace support]
+Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
+---
+ meson.build   |   4 +
+ pbs-restore.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 240 insertions(+)
+ create mode 100644 pbs-restore.c
+
+diff --git a/meson.build b/meson.build
+index d307d8eabf..afd105001e 100644
+--- a/meson.build
++++ b/meson.build
+@@ -3652,6 +3652,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..f03d9bab8d
+--- /dev/null
++++ b/pbs-restore.c
+@@ -0,0 +1,236 @@
++/*
++ * 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/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>] [--ns namespace] 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_len, data, 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 *backup_ns = 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'},
++            {"ns", required_argument, 0, 'n'},
++            {"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 'n':
++                backup_ns = 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_ns(
++        repository,
++        snapshot,
++        backup_ns,
++        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/0032-PVE-various-PBS-fixes.patch b/debian/patches/pve/0032-PVE-various-PBS-fixes.patch
deleted file mode 100644 (file)
index c46a307..0000000
+++ /dev/null
@@ -1,217 +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>
-[FE: adapt to QAPI change dropping redundant has_*]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- block/monitor/block-hmp-cmds.c |  4 ++-
- pve-backup.c                   | 54 +++++++++++++++++++++++++++-------
- qapi/block-core.json           |  6 ++++
- 3 files changed, 52 insertions(+), 12 deletions(-)
-
-diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
-index cda5de792b..ecbebd39ac 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1056,7 +1056,9 @@ void hmp_backup(Monitor *mon, const QDict *qdict)
-         NULL, // PBS fingerprint
-         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,
-         NULL, NULL,
-         devlist, qdict_haskey(qdict, "speed"), speed, &error);
-diff --git a/pve-backup.c b/pve-backup.c
-index d9942a14a1..8f18145255 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -10,6 +10,7 @@
- #include "block/dirty-bitmap.h"
- #include "qapi/qapi-commands-block.h"
- #include "qapi/qmp/qerror.h"
-+#include "qemu/cutils.h"
- /* PVE backup state and related function */
-@@ -69,6 +70,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];
-@@ -139,10 +141,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
-@@ -151,17 +156,29 @@ 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);
--    qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
-+    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;
--    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);
-+        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);
-+    pvebackup_add_transfered_bytes(size, is_zero_block ? size : 0, reused);
-+
-     return size;
- }
-@@ -182,6 +199,7 @@ pvebackup_co_dump_vma_cb(
-     int ret = -1;
-     assert(backup_state.vmaw);
-+    assert(buf);
-     uint64_t remaining = size;
-@@ -208,9 +226,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);
-@@ -560,6 +576,10 @@ typedef struct QmpBackupTask {
-     const char *config_file;
-     const char *firewall_file;
-     const char *devlist;
-+    bool has_compress;
-+    bool compress;
-+    bool has_encrypt;
-+    bool encrypt;
-     bool has_speed;
-     int64_t speed;
-     Error **errp;
-@@ -683,6 +703,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,
-@@ -692,6 +713,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-             task->password,
-             task->keyfile,
-             task->key_password,
-+            task->has_compress ? task->compress : true,
-+            task->has_encrypt ? task->encrypt : !!task->keyfile,
-             task->fingerprint,
-             &pbs_err);
-@@ -712,6 +735,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);
-@@ -932,6 +957,8 @@ UuidInfo *qmp_backup(
-     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,
-     const char *config_file,
-     const char *firewall_file,
-@@ -941,6 +968,7 @@ UuidInfo *qmp_backup(
-     QmpBackupTask task = {
-         .backup_file = backup_file,
-         .password = password,
-+        .keyfile = keyfile,
-         .key_password = key_password,
-         .fingerprint = fingerprint,
-         .backup_id = backup_id,
-@@ -948,6 +976,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,
-         .config_file = config_file,
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index 92f90a898a..864b8ce97c 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -913,6 +913,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
- #
- ##
-@@ -924,6 +928,8 @@
-                                     '*backup-id': 'str',
-                                     '*backup-time': 'int',
-                                     '*use-dirty-bitmap': 'bool',
-+                                    '*compress': 'bool',
-+                                    '*encrypt': 'bool',
-                                     '*format': 'BackupFormat',
-                                     '*config-file': 'str',
-                                     '*firewall-file': 'str',
index d7868e1140e8b9f3af9bbfd95edc5b2f81eb13d1..9c9dc26af6141741c8dbb47603246468d84cfb04 100644 (file)
@@ -7,18 +7,20 @@ 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>
+[WB: add namespace support]
+Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
 [FE: adapt to changed function signatures
      make pbs_co_preadv return values consistent with QEMU
      getlength is now a coroutine function]
 Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
 ---
  block/meson.build    |   3 +
- block/pbs.c          | 277 +++++++++++++++++++++++++++++++++++++++++++
+ block/pbs.c          | 305 +++++++++++++++++++++++++++++++++++++++++++
  configure            |   9 ++
  meson.build          |   2 +-
  qapi/block-core.json |  13 ++
  qapi/pragma.json     |   1 +
- 6 files changed, 304 insertions(+), 1 deletion(-)
+ 6 files changed, 332 insertions(+), 1 deletion(-)
  create mode 100644 block/pbs.c
 
 diff --git a/block/meson.build b/block/meson.build
@@ -37,10 +39,10 @@ index 5bcebb934b..eece0d5743 100644
  softmmu_ss.add(files('block-ram-registrar.c'))
 diff --git a/block/pbs.c b/block/pbs.c
 new file mode 100644
-index 0000000000..43e69ada46
+index 0000000000..a2211e0f3b
 --- /dev/null
 +++ b/block/pbs.c
-@@ -0,0 +1,277 @@
+@@ -0,0 +1,305 @@
 +/*
 + * Proxmox Backup Server read-only block driver
 + */
@@ -58,6 +60,7 @@ index 0000000000..43e69ada46
 +#include <proxmox-backup-qemu.h>
 +
 +#define PBS_OPT_REPOSITORY "repository"
++#define PBS_OPT_NAMESPACE "namespace"
 +#define PBS_OPT_SNAPSHOT "snapshot"
 +#define PBS_OPT_ARCHIVE "archive"
 +#define PBS_OPT_KEYFILE "keyfile"
@@ -71,6 +74,7 @@ index 0000000000..43e69ada46
 +    int64_t length;
 +
 +    char *repository;
++    char *namespace;
 +    char *snapshot;
 +    char *archive;
 +} BDRVPBSState;
@@ -85,6 +89,11 @@ index 0000000000..43e69ada46
 +            .help = "The server address and repository to connect to.",
 +        },
 +        {
++            .name = PBS_OPT_NAMESPACE,
++            .type = QEMU_OPT_STRING,
++            .help = "Optional: The snapshot's namespace.",
++        },
++        {
 +            .name = PBS_OPT_SNAPSHOT,
 +            .type = QEMU_OPT_STRING,
 +            .help = "The snapshot to read.",
@@ -120,7 +129,7 @@ index 0000000000..43e69ada46
 +
 +
 +// filename format:
-+// pbs:repository=<repo>,snapshot=<snap>,password=<pw>,key_password=<kpw>,fingerprint=<fp>,archive=<archive>
++// pbs:repository=<repo>,namespace=<ns>,snapshot=<snap>,password=<pw>,key_password=<kpw>,fingerprint=<fp>,archive=<archive>
 +static void pbs_parse_filename(const char *filename, QDict *options,
 +                                     Error **errp)
 +{
@@ -156,6 +165,7 @@ index 0000000000..43e69ada46
 +    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 *namespace = qemu_opt_get(opts, PBS_OPT_NAMESPACE);
 +    const char *fingerprint = qemu_opt_get(opts, PBS_OPT_FINGERPRINT);
 +    const char *key_password = qemu_opt_get(opts, PBS_OPT_ENCRYPTION_PASSWORD);
 +
@@ -168,9 +178,12 @@ index 0000000000..43e69ada46
 +    if (!key_password) {
 +        key_password = getenv("PBS_ENCRYPTION_PASSWORD");
 +    }
++    if (namespace) {
++        s->namespace = g_strdup(namespace);
++    }
 +
 +    /* connect to PBS server in read mode */
-+    s->conn = proxmox_restore_new(s->repository, s->snapshot, password,
++    s->conn = proxmox_restore_new_ns(s->repository, s->snapshot, s->namespace, password,
 +        keyfile, key_password, fingerprint, &pbs_error);
 +
 +    /* invalidates qemu_opt_get char pointers from above */
@@ -215,6 +228,7 @@ index 0000000000..43e69ada46
 +static void pbs_close(BlockDriverState *bs) {
 +    BDRVPBSState *s = bs->opaque;
 +    g_free(s->repository);
++    g_free(s->namespace);
 +    g_free(s->snapshot);
 +    g_free(s->archive);
 +    proxmox_restore_disconnect(s->conn);
@@ -244,7 +258,16 @@ index 0000000000..43e69ada46
 +    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");
@@ -267,8 +290,10 @@ index 0000000000..43e69ada46
 +        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 0;
 +}
@@ -285,8 +310,13 @@ index 0000000000..43e69ada46
 +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);
++    if (s->namespace) {
++        snprintf(bs->exact_filename, sizeof(bs->exact_filename), "%s/%s:%s(%s)",
++                 s->repository, s->namespace, s->snapshot, s->archive);
++    } else {
++        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[] = {
@@ -373,10 +403,10 @@ index afd105001e..d01ee5d489 100644
  summary_info += {'libdaxctl support': libdaxctl}
  summary_info += {'libudev':           libudev}
 diff --git a/qapi/block-core.json b/qapi/block-core.json
-index 864b8ce97c..705a65ab1a 100644
+index 4ec70acf95..47118bf83e 100644
 --- a/qapi/block-core.json
 +++ b/qapi/block-core.json
-@@ -3198,6 +3198,7 @@
+@@ -3301,6 +3301,7 @@
              'parallels', 'preallocate', 'qcow', 'qcow2', 'qed', 'quorum',
              'raw', 'rbd',
              { 'name': 'replication', 'if': 'CONFIG_REPLICATION' },
@@ -384,7 +414,7 @@ index 864b8ce97c..705a65ab1a 100644
              'ssh', 'throttle', 'vdi', 'vhdx',
              { 'name': 'virtio-blk-vfio-pci', 'if': 'CONFIG_BLKIO' },
              { 'name': 'virtio-blk-vhost-user', 'if': 'CONFIG_BLKIO' },
-@@ -3274,6 +3275,17 @@
+@@ -3377,6 +3378,17 @@
  { 'struct': 'BlockdevOptionsNull',
    'data': { '*size': 'int', '*latency-ns': 'uint64', '*read-zeroes': 'bool' } }
  
@@ -397,12 +427,12 @@ index 864b8ce97c..705a65ab1a 100644
 +{ 'struct': 'BlockdevOptionsPbs',
 +  'data': { 'repository': 'str', 'snapshot': 'str', 'archive': 'str',
 +            '*keyfile': 'str', '*password': 'str', '*fingerprint': 'str',
-+            '*key_password': 'str' } }
++            '*key_password': 'str', '*namespace': 'str' } }
 +
  ##
  # @BlockdevOptionsNVMe:
  #
-@@ -4647,6 +4659,7 @@
+@@ -4750,6 +4762,7 @@
        'nfs':        'BlockdevOptionsNfs',
        'null-aio':   'BlockdevOptionsNull',
        'null-co':    'BlockdevOptionsNull',
diff --git a/debian/patches/pve/0034-PVE-add-query_proxmox_support-QMP-command.patch b/debian/patches/pve/0034-PVE-add-query_proxmox_support-QMP-command.patch
deleted file mode 100644 (file)
index 0b14e6f..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 8f18145255..1400c21c49 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -1054,3 +1054,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 705a65ab1a..1ac535fcf2 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -958,6 +958,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-redirect-stderr-to-journal-when-daemonized.patch b/debian/patches/pve/0034-PVE-redirect-stderr-to-journal-when-daemonized.patch
new file mode 100644 (file)
index 0000000..98e02a0
--- /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 d01ee5d489..6129c3ce0c 100644
+--- a/meson.build
++++ b/meson.build
+@@ -1528,6 +1528,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
+@@ -3144,6 +3145,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 5adc69f560..80c7777f10 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>
+ /* Needed early for CONFIG_BSD etc. */
+ #include "net/slirp.h"
+@@ -287,9 +289,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_log_enabled()) {
+-            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-Migrate-dirty-bitmap-state-via-savevm.patch b/debian/patches/pve/0035-PVE-Migrate-dirty-bitmap-state-via-savevm.patch
new file mode 100644 (file)
index 0000000..a3d7386
--- /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>
+[FE: split up state_pending for 8.0]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ include/migration/misc.h |   3 ++
+ migration/meson.build    |   2 +
+ migration/migration.c    |   1 +
+ migration/pbs-state.c    | 104 +++++++++++++++++++++++++++++++++++++++
+ pve-backup.c             |   1 +
+ qapi/block-core.json     |   6 +++
+ 6 files changed, 117 insertions(+)
+ create mode 100644 migration/pbs-state.c
+
+diff --git a/include/migration/misc.h b/include/migration/misc.h
+index 8b49841016..78f63ca400 100644
+--- a/include/migration/misc.h
++++ b/include/migration/misc.h
+@@ -77,4 +77,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 a7824b5266..de6a271b58 100644
+--- a/migration/meson.build
++++ b/migration/meson.build
+@@ -6,8 +6,10 @@ migration_files = files(
+   'vmstate.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 bda4789193..c8318aa258 100644
+--- a/migration/migration.c
++++ b/migration/migration.c
+@@ -245,6 +245,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..887e998b9e
+--- /dev/null
++++ b/migration/pbs-state.c
+@@ -0,0 +1,104 @@
++/*
++ * 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_pending(void *opaque, uint64_t *must_precopy,
++                              uint64_t *can_postcopy)
++{
++    /* 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,
++    .state_pending_exact = pbs_state_pending,
++    .state_pending_estimate = pbs_state_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 dd72ee0ed6..cb5312fff3 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -1090,6 +1090,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;
+     ret->pbs_masterkey = true;
+     ret->backup_max_workers = true;
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index 47118bf83e..809f3c61bc 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -984,6 +984,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-masterkey: True if the QMP backup call supports the 'master_keyfile'
+ #                 parameter.
+ #
+@@ -994,6 +999,7 @@
+   'data': { 'pbs-dirty-bitmap': 'bool',
+             'query-bitmap-info': 'bool',
+             'pbs-dirty-bitmap-savevm': 'bool',
++            'pbs-dirty-bitmap-migration': 'bool',
+             'pbs-masterkey': 'bool',
+             'pbs-library-version': 'str',
+             'backup-max-workers': 'bool' } }
diff --git a/debian/patches/pve/0035-PVE-add-query-pbs-bitmap-info-QMP-call.patch b/debian/patches/pve/0035-PVE-add-query-pbs-bitmap-info-QMP-call.patch
deleted file mode 100644 (file)
index 2e0f235..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 087161a967..9a67e544ce 100644
---- a/monitor/hmp-cmds.c
-+++ b/monitor/hmp-cmds.c
-@@ -148,6 +148,7 @@ void hmp_sync_profile(Monitor *mon, const QDict *qdict)
- void hmp_info_backup(Monitor *mon, const QDict *qdict)
- {
-     BackupStatus *info;
-+    PBSBitmapInfoList *bitmap_info;
-     info = qmp_query_backup(NULL);
-@@ -178,26 +179,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 1400c21c49..0a0996b971 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -48,6 +48,7 @@ static struct PVEBackupState {
-         size_t transferred;
-         size_t reused;
-         size_t zero_bytes;
-+        GList *bitmap_list;
-     } stat;
-     int64_t speed;
-     VmaWriter *vmaw;
-@@ -663,7 +664,6 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     }
-     size_t total = 0;
--    size_t dirty = 0;
-     l = di_list;
-     while (l) {
-@@ -684,18 +684,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->password) {
-             error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
--            goto err;
-+            goto err_mutex;
-         }
-         if (!task->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)
-@@ -722,12 +737,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;
-@@ -738,6 +753,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;
-@@ -746,49 +763,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 */
-@@ -798,7 +825,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);
-@@ -806,16 +833,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;
-@@ -832,18 +857,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_co_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;
-     }
-@@ -851,7 +876,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     if (task->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;
-         }
-     }
-@@ -859,12 +884,11 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     if (task->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);
-@@ -884,10 +908,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);
-@@ -904,6 +927,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;
-@@ -1055,11 +1081,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 1ac535fcf2..130d5f065f 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -966,6 +966,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.
- #
-@@ -974,6 +976,7 @@
- ##
- { 'struct': 'ProxmoxSupportStatus',
-   'data': { 'pbs-dirty-bitmap': 'bool',
-+            'query-bitmap-info': 'bool',
-             'pbs-dirty-bitmap-savevm': 'bool',
-             'pbs-library-version': 'str' } }
-@@ -987,6 +990,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/0036-PVE-redirect-stderr-to-journal-when-daemonized.patch b/debian/patches/pve/0036-PVE-redirect-stderr-to-journal-when-daemonized.patch
deleted file mode 100644 (file)
index 98e02a0..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 d01ee5d489..6129c3ce0c 100644
---- a/meson.build
-+++ b/meson.build
-@@ -1528,6 +1528,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
-@@ -3144,6 +3145,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 5adc69f560..80c7777f10 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>
- /* Needed early for CONFIG_BSD etc. */
- #include "net/slirp.h"
-@@ -287,9 +289,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_log_enabled()) {
--            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-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch b/debian/patches/pve/0036-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch
new file mode 100644 (file)
index 0000000..0e3f38d
--- /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 fe73aa94b1..a6440929fa 100644
+--- a/migration/block-dirty-bitmap.c
++++ b/migration/block-dirty-bitmap.c
+@@ -539,7 +539,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/0037-PVE-Add-sequential-job-transaction-support.patch b/debian/patches/pve/0037-PVE-Add-sequential-job-transaction-support.patch
deleted file mode 100644 (file)
index fbf610e..0000000
+++ /dev/null
@@ -1,101 +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              | 34 ++++++++++++++++++++++++++++++++++
- 2 files changed, 46 insertions(+)
-
-diff --git a/include/qemu/job.h b/include/qemu/job.h
-index e502787dd8..963cf2bef5 100644
---- a/include/qemu/job.h
-+++ b/include/qemu/job.h
-@@ -381,6 +381,18 @@ void job_unlock(void);
-  */
- 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 93e22d180b..2b31f1e14f 100644
---- a/job.c
-+++ b/job.c
-@@ -93,6 +93,8 @@ struct JobTxn {
-     /* Reference count */
-     int refcnt;
-+
-+    bool sequential;
- };
- void job_lock(void)
-@@ -118,6 +120,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);
-+}
-+
- /* Called with job_mutex held. */
- static void job_txn_ref_locked(JobTxn *txn)
- {
-@@ -1057,6 +1078,12 @@ static void job_completed_txn_success_locked(Job *job)
-      */
-     QLIST_FOREACH(other_job, &txn->jobs, txn_list) {
-         if (!job_is_completed_locked(other_job)) {
-+            if (txn->sequential) {
-+                job_unlock();
-+                /* Needs to be called without holding the job lock */
-+                job_start(other_job);
-+                job_lock();
-+            }
-             return;
-         }
-         assert(other_job->ret == 0);
-@@ -1268,6 +1295,13 @@ int job_finish_sync_locked(Job *job,
-         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_locked(job);
-+    }
-+
-     job_unlock();
-     AIO_WAIT_WHILE_UNLOCKED(job->aio_context,
-                             (job_enter(job), !job_is_completed(job)));
diff --git a/debian/patches/pve/0037-PVE-fall-back-to-open-iscsi-initiatorname.patch b/debian/patches/pve/0037-PVE-fall-back-to-open-iscsi-initiatorname.patch
new file mode 100644 (file)
index 0000000..0e50c93
--- /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 9fc0bed90b..1d40933165 100644
+--- a/block/iscsi.c
++++ b/block/iscsi.c
+@@ -1392,12 +1392,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/0038-PVE-Backup-Use-a-transaction-to-synchronize-job-stat.patch b/debian/patches/pve/0038-PVE-Backup-Use-a-transaction-to-synchronize-job-stat.patch
deleted file mode 100644 (file)
index 7a5cda5..0000000
+++ /dev/null
@@ -1,293 +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>
-[FE: add new force parameter to job_cancel_sync calls
-     adapt for new job lock mechanism replacing AioContext locks]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- pve-backup.c | 163 ++++++++++++++++-----------------------------------
- 1 file changed, 50 insertions(+), 113 deletions(-)
-
-diff --git a/pve-backup.c b/pve-backup.c
-index 0a0996b971..629da3e6c7 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -54,6 +54,7 @@ static struct PVEBackupState {
-     VmaWriter *vmaw;
-     ProxmoxBackupHandle *pbs;
-     GList *di_list;
-+    JobTxn *txn;
-     QemuMutex backup_mutex;
-     CoMutex dump_callback_mutex;
- } backup_state;
-@@ -73,34 +74,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) {
--        WITH_JOB_LOCK_GUARD() {
--            for (BlockJob *job = block_job_next_locked(NULL); job; job = block_job_next_locked(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);
-@@ -276,18 +255,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);
-@@ -326,8 +293,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));
-@@ -340,20 +305,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)
-@@ -375,32 +337,28 @@ static void pvebackup_cancel(void)
-         proxmox_backup_abort(backup_state.pbs, "backup canceled");
-     }
--    qemu_mutex_unlock(&backup_state.backup_mutex);
--
--    for(;;) {
--
--        BlockJob *next_job = NULL;
-+    /* 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;
--        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;
--            }
-+    /* ref the job before releasing the mutex, just to be safe */
-+    if (cancel_job) {
-+        WITH_JOB_LOCK_GUARD() {
-+            job_ref_locked(&cancel_job->job);
-         }
-+    }
--        qemu_mutex_unlock(&backup_state.backup_mutex);
-+    /* 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 (next_job) {
--            job_cancel_sync(&next_job->job, true);
--        } else {
--            break;
-+    if (cancel_job) {
-+        WITH_JOB_LOCK_GUARD() {
-+            job_cancel_sync_locked(&cancel_job->job, true);
-+            job_unref_locked(&cancel_job->job);
-         }
-     }
- }
-@@ -460,49 +418,19 @@ static int coroutine_fn pvebackup_co_add_config(
-     goto out;
- }
--bool job_should_pause_locked(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);
--
--            WITH_JOB_LOCK_GUARD() {
--                if (job_should_pause_locked(&job->job)) {
--                    bool error_or_canceled = pvebackup_error_or_canceled();
--                    if (error_or_canceled) {
--                        job_cancel_sync_locked(&job->job, true);
--                    } else {
--                        job_resume_locked(&job->job);
--                    }
--                }
--            }
--            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) */
-@@ -525,7 +453,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);
-@@ -537,7 +465,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;
-@@ -555,6 +484,12 @@ static bool create_backup_jobs(void) {
-                 bdrv_unref(di->target);
-                 di->target = NULL;
-             }
-+
-+            if (di->job) {
-+                WITH_JOB_LOCK_GUARD() {
-+                    job_unref_locked(&di->job->job);
-+                }
-+            }
-         }
-     }
-@@ -937,10 +872,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_co_unref(di->target);
-         }
-@@ -1021,9 +952,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-block-stream-increase-chunk-size.patch b/debian/patches/pve/0038-PVE-block-stream-increase-chunk-size.patch
new file mode 100644 (file)
index 0000000..5707bee
--- /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 7f9e1ecdbb..6a29d80398 100644
+--- a/block/stream.c
++++ b/block/stream.c
+@@ -27,7 +27,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/0039-PVE-Backup-Don-t-block-on-finishing-and-cleanup-crea.patch b/debian/patches/pve/0039-PVE-Backup-Don-t-block-on-finishing-and-cleanup-crea.patch
deleted file mode 100644 (file)
index 15eb190..0000000
+++ /dev/null
@@ -1,499 +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>
-[FE: add new force parameter to job_cancel_sync calls]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- pve-backup.c         | 212 +++++++++++++++++++++++++++----------------
- qapi/block-core.json |   5 +-
- 2 files changed, 138 insertions(+), 79 deletions(-)
-
-diff --git a/pve-backup.c b/pve-backup.c
-index 629da3e6c7..1da9dd9edc 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -35,7 +35,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;
-@@ -49,20 +51,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);
- }
-@@ -74,6 +78,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;
-@@ -229,12 +234,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) {
-@@ -263,35 +268,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;
-@@ -303,7 +302,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);
-@@ -312,21 +323,46 @@ 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;
-+    job_cancel_sync(job, true);
-+    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 */
-@@ -344,28 +380,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) {
--        WITH_JOB_LOCK_GUARD() {
--            job_ref_locked(&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) {
--        WITH_JOB_LOCK_GUARD() {
--            job_cancel_sync_locked(&cancel_job->job, true);
--            job_unref_locked(&cancel_job->job);
--        }
--    }
-+    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
-@@ -418,10 +448,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
-@@ -457,24 +495,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",
--                       local_err ? error_get_pretty(local_err) : "null");
-+        di->job = job;
--            pvebackup_propagate_error(create_job_err);
-+        if (!job || local_err) {
-+            error_setg(errp, "backup_job_create failed: %s",
-+                       local_err ? error_get_pretty(local_err) : "null");
-             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;
-@@ -487,13 +520,15 @@ static bool create_backup_jobs(void) {
-             if (di->job) {
-                 WITH_JOB_LOCK_GUARD() {
-+                    job_cancel_sync_locked(&di->job->job, true);
-                     job_unref_locked(&di->job->job);
-                 }
-             }
-         }
-     }
--    return errors;
-+    /* return */
-+    aio_co_enter(data->ctx, data->co);
- }
- typedef struct QmpBackupTask {
-@@ -522,11 +557,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
-@@ -547,8 +583,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;
-     }
-@@ -615,6 +652,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-         }
-         di->size = size;
-         total += size;
-+
-+        di->completed_ret = INT_MAX;
-     }
-     uuid_generate(uuid);
-@@ -846,6 +885,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);
-@@ -860,6 +901,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:
-@@ -882,6 +950,7 @@ err:
-         g_free(di);
-     }
-     g_list_free(di_list);
-+    backup_state.di_list = NULL;
-     if (devs) {
-         g_strfreev(devs);
-@@ -902,6 +971,8 @@ err:
-     }
-     task->result = NULL;
-+
-+    qemu_co_mutex_unlock(&backup_state.backup_mutex);
-     return;
- }
-@@ -947,24 +1018,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;
- }
-@@ -1012,6 +1067,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 130d5f065f..43838212e3 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -865,12 +865,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/0039-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch b/debian/patches/pve/0039-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch
new file mode 100644 (file)
index 0000000..d1bd74d
--- /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 2e267a85ab..449a44bf20 100644
+--- a/block/io.c
++++ b/block/io.c
+@@ -1576,6 +1576,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/0040-PVE-Migrate-dirty-bitmap-state-via-savevm.patch b/debian/patches/pve/0040-PVE-Migrate-dirty-bitmap-state-via-savevm.patch
deleted file mode 100644 (file)
index 644afc2..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>
-[FE: split up state_pending for 8.0]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- include/migration/misc.h |   3 ++
- migration/meson.build    |   2 +
- migration/migration.c    |   1 +
- migration/pbs-state.c    | 104 +++++++++++++++++++++++++++++++++++++++
- pve-backup.c             |   1 +
- qapi/block-core.json     |   6 +++
- 6 files changed, 117 insertions(+)
- create mode 100644 migration/pbs-state.c
-
-diff --git a/include/migration/misc.h b/include/migration/misc.h
-index 8b49841016..78f63ca400 100644
---- a/include/migration/misc.h
-+++ b/include/migration/misc.h
-@@ -77,4 +77,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 a7824b5266..de6a271b58 100644
---- a/migration/meson.build
-+++ b/migration/meson.build
-@@ -6,8 +6,10 @@ migration_files = files(
-   'vmstate.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 bda4789193..c8318aa258 100644
---- a/migration/migration.c
-+++ b/migration/migration.c
-@@ -245,6 +245,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..887e998b9e
---- /dev/null
-+++ b/migration/pbs-state.c
-@@ -0,0 +1,104 @@
-+/*
-+ * 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_pending(void *opaque, uint64_t *must_precopy,
-+                              uint64_t *can_postcopy)
-+{
-+    /* 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,
-+    .state_pending_exact = pbs_state_pending,
-+    .state_pending_estimate = pbs_state_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 1da9dd9edc..e0e38063a8 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -1110,6 +1110,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 43838212e3..e7412f6322 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -974,6 +974,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.
- #
- ##
-@@ -981,6 +986,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/0040-block-add-alloc-track-driver.patch b/debian/patches/pve/0040-block-add-alloc-track-driver.patch
new file mode 100644 (file)
index 0000000..823be87
--- /dev/null
@@ -0,0 +1,406 @@
+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>
+[FE: adapt to changed function signatures
+     make error return value consistent with QEMU
+     avoid premature break during read]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ block/alloc-track.c | 352 ++++++++++++++++++++++++++++++++++++++++++++
+ block/meson.build   |   1 +
+ 2 files changed, 353 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..b75d7c6460
+--- /dev/null
++++ b/block/alloc-track.c
+@@ -0,0 +1,352 @@
++/*
++ * 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 "block/dirty-bitmap.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 coroutine_fn int64_t track_co_getlength(BlockDriverState *bs)
++{
++    return bdrv_co_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 -EIO;
++    }
++
++    /* 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 {
++            qemu_iovec_memset(&local_qiov, cur_offset, 0, local_bytes);
++            ret = 0;
++        }
++
++        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_co_getlength                = track_co_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 eece0d5743..8a68162cc0 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/0041-Revert-block-rbd-workaround-for-ceph-issue-53784.patch b/debian/patches/pve/0041-Revert-block-rbd-workaround-for-ceph-issue-53784.patch
new file mode 100644 (file)
index 0000000..e60e74f
--- /dev/null
@@ -0,0 +1,80 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Fabian Ebner <f.ebner@proxmox.com>
+Date: Thu, 23 Jun 2022 14:00:05 +0200
+Subject: [PATCH] Revert "block/rbd: workaround for ceph issue #53784"
+
+This reverts commit fc176116cdea816ceb8dd969080b2b95f58edbc0 in
+preparation to revert 0347a8fd4c3faaedf119be04c197804be40a384b.
+
+Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
+---
+ block/rbd.c | 42 ++----------------------------------------
+ 1 file changed, 2 insertions(+), 40 deletions(-)
+
+diff --git a/block/rbd.c b/block/rbd.c
+index a4749f3b1b..53e0396b51 100644
+--- a/block/rbd.c
++++ b/block/rbd.c
+@@ -1511,7 +1511,6 @@ static int coroutine_fn qemu_rbd_co_block_status(BlockDriverState *bs,
+     int status, r;
+     RBDDiffIterateReq req = { .offs = offset };
+     uint64_t features, flags;
+-    uint64_t head = 0;
+     assert(offset + bytes <= s->image_size);
+@@ -1539,43 +1538,7 @@ static int coroutine_fn qemu_rbd_co_block_status(BlockDriverState *bs,
+         return status;
+     }
+-#if LIBRBD_VERSION_CODE < LIBRBD_VERSION(1, 17, 0)
+-    /*
+-     * librbd had a bug until early 2022 that affected all versions of ceph that
+-     * supported fast-diff. This bug results in reporting of incorrect offsets
+-     * if the offset parameter to rbd_diff_iterate2 is not object aligned.
+-     * Work around this bug by rounding down the offset to object boundaries.
+-     * This is OK because we call rbd_diff_iterate2 with whole_object = true.
+-     * However, this workaround only works for non cloned images with default
+-     * striping.
+-     *
+-     * See: https://tracker.ceph.com/issues/53784
+-     */
+-
+-    /* check if RBD image has non-default striping enabled */
+-    if (features & RBD_FEATURE_STRIPINGV2) {
+-        return status;
+-    }
+-
+-#pragma GCC diagnostic push
+-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+-    /*
+-     * check if RBD image is a clone (= has a parent).
+-     *
+-     * rbd_get_parent_info is deprecated from Nautilus onwards, but the
+-     * replacement rbd_get_parent is not present in Luminous and Mimic.
+-     */
+-    if (rbd_get_parent_info(s->image, NULL, 0, NULL, 0, NULL, 0) != -ENOENT) {
+-        return status;
+-    }
+-#pragma GCC diagnostic pop
+-
+-    head = req.offs & (s->object_size - 1);
+-    req.offs -= head;
+-    bytes += head;
+-#endif
+-
+-    r = rbd_diff_iterate2(s->image, NULL, req.offs, bytes, true, true,
++    r = rbd_diff_iterate2(s->image, NULL, offset, bytes, true, true,
+                           qemu_rbd_diff_iterate_cb, &req);
+     if (r < 0 && r != QEMU_RBD_EXIT_DIFF_ITERATE2) {
+         return status;
+@@ -1594,8 +1557,7 @@ static int coroutine_fn qemu_rbd_co_block_status(BlockDriverState *bs,
+         status = BDRV_BLOCK_ZERO | BDRV_BLOCK_OFFSET_VALID;
+     }
+-    assert(req.bytes > head);
+-    *pnum = req.bytes - head;
++    *pnum = req.bytes;
+     return status;
+ }
diff --git a/debian/patches/pve/0041-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch b/debian/patches/pve/0041-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch
deleted file mode 100644 (file)
index 0e3f38d..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 fe73aa94b1..a6440929fa 100644
---- a/migration/block-dirty-bitmap.c
-+++ b/migration/block-dirty-bitmap.c
-@@ -539,7 +539,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/0042-PVE-fall-back-to-open-iscsi-initiatorname.patch b/debian/patches/pve/0042-PVE-fall-back-to-open-iscsi-initiatorname.patch
deleted file mode 100644 (file)
index 0e50c93..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 9fc0bed90b..1d40933165 100644
---- a/block/iscsi.c
-+++ b/block/iscsi.c
-@@ -1392,12 +1392,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-Revert-block-rbd-fix-handling-of-holes-in-.bdrv_co_b.patch b/debian/patches/pve/0042-Revert-block-rbd-fix-handling-of-holes-in-.bdrv_co_b.patch
new file mode 100644 (file)
index 0000000..e5fad08
--- /dev/null
@@ -0,0 +1,35 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Fabian Ebner <f.ebner@proxmox.com>
+Date: Thu, 23 Jun 2022 14:00:07 +0200
+Subject: [PATCH] Revert "block/rbd: fix handling of holes in
+ .bdrv_co_block_status"
+
+This reverts commit 9e302f64bb407a9bb097b626da97228c2654cfee in
+preparation to revert 0347a8fd4c3faaedf119be04c197804be40a384b.
+
+Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
+---
+ block/rbd.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/block/rbd.c b/block/rbd.c
+index 53e0396b51..0913a0af39 100644
+--- a/block/rbd.c
++++ b/block/rbd.c
+@@ -1470,11 +1470,11 @@ static int qemu_rbd_diff_iterate_cb(uint64_t offs, size_t len,
+     RBDDiffIterateReq *req = opaque;
+     assert(req->offs + req->bytes <= offs);
+-
+-    /* treat a hole like an unallocated area and bail out */
+-    if (!exists) {
+-        return 0;
+-    }
++    /*
++     * we do not diff against a snapshot so we should never receive a callback
++     * for a hole.
++     */
++    assert(exists);
+     if (!req->exists && offs > req->offs) {
+         /*
diff --git a/debian/patches/pve/0043-PVE-Use-coroutine-QMP-for-backup-cancel_backup.patch b/debian/patches/pve/0043-PVE-Use-coroutine-QMP-for-backup-cancel_backup.patch
deleted file mode 100644 (file)
index b38c633..0000000
+++ /dev/null
@@ -1,595 +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>
-[FE: adapt to QAPI changes
-     call coroutine version of is_inserted()]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- block/monitor/block-hmp-cmds.c |   4 +-
- hmp-commands.hx                |   2 +
- proxmox-backup-client.c        |  31 -----
- pve-backup.c                   | 220 +++++++++++----------------------
- qapi/block-core.json           |   4 +-
- 5 files changed, 79 insertions(+), 182 deletions(-)
-
-diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
-index ecbebd39ac..56f39b14d4 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1030,7 +1030,7 @@ void hmp_change_medium(Monitor *mon, const char *device, const char *target,
-                                !!read_only, read_only_mode, errp);
- }
--void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
-+void coroutine_fn hmp_backup_cancel(Monitor *mon, const QDict *qdict)
- {
-     Error *error = NULL;
-@@ -1039,7 +1039,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 9b6b8e2e9c..896430dae8 100644
---- a/hmp-commands.hx
-+++ b/hmp-commands.hx
-@@ -111,6 +111,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
-@@ -124,6 +125,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 e0e38063a8..88e507b3c2 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -356,7 +356,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");
-@@ -393,11 +393,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,
-@@ -531,42 +526,27 @@ static void create_backup_jobs_bh(void *opaque) {
-     aio_co_enter(data->ctx, data->co);
- }
--typedef struct QmpBackupTask {
--    const char *backup_file;
--    const char *password;
--    const char *keyfile;
--    const char *key_password;
--    const char *backup_id;
--    bool has_backup_time;
--    const char *fingerprint;
--    int64_t backup_time;
--    bool has_use_dirty_bitmap;
--    bool use_dirty_bitmap;
--    bool has_format;
--    BackupFormat format;
--    const char *config_file;
--    const char *firewall_file;
--    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,
-+    const char *password,
-+    const char *keyfile,
-+    const char *key_password,
-+    const char *fingerprint,
-+    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,
-+    const char *config_file,
-+    const char *firewall_file,
-+    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;
-@@ -583,32 +563,32 @@ 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->devlist) {
--        devs = g_strsplit_set(task->devlist, ",;:", -1);
-+    if (devlist) {
-+        devs = g_strsplit_set(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);
-+                if (!bdrv_co_is_inserted(bs)) {
-+                    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;
-             }
-@@ -620,7 +600,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-         bs = NULL;
-         for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
--            if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) {
-+            if (!bdrv_co_is_inserted(bs) || bdrv_is_read_only(bs)) {
-                 continue;
-             }
-@@ -631,7 +611,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;
-     }
-@@ -641,13 +621,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, -size, "bdrv_getlength failed");
-+            error_setg_errno(errp, -size, "bdrv_getlength failed");
-             goto err;
-         }
-         di->size = size;
-@@ -674,47 +654,44 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-     }
-     if (format == BACKUP_FORMAT_PBS) {
--        if (!task->password) {
--            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
-+        if (!password) {
-+            error_set(errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
-             goto err_mutex;
-         }
--        if (!task->backup_id) {
--            error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
-+        if (!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->password,
--            task->keyfile,
--            task->key_password,
--            task->has_compress ? task->compress : true,
--            task->has_encrypt ? task->encrypt : !!task->keyfile,
--            task->fingerprint,
-+            password,
-+            keyfile,
-+            key_password,
-+            has_compress ? compress : true,
-+            has_encrypt ? encrypt : !!keyfile,
-+            fingerprint,
-             &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;
-@@ -733,9 +710,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;
-                     }
-@@ -765,12 +742,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;
-             }
-@@ -784,10 +761,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;
-         }
-@@ -798,25 +775,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) {
-@@ -830,34 +807,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_co_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->config_file) {
--        if (pvebackup_co_add_config(task->config_file, config_name, format, backup_dir,
--                                    vmaw, pbs, task->errp) != 0) {
-+    if (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->firewall_file) {
--        if (pvebackup_co_add_config(task->firewall_file, firewall_name, format, backup_dir,
--                                    vmaw, pbs, task->errp) != 0) {
-+    if (firewall_file) {
-+        if (pvebackup_co_add_config(firewall_file, firewall_name, format, backup_dir,
-+                                    vmaw, pbs, errp) != 0) {
-             goto err_mutex;
-         }
-     }
-@@ -875,7 +852,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);
-@@ -890,7 +867,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;
-@@ -900,8 +877,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.
-@@ -915,7 +890,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;
-     }
-@@ -928,7 +903,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);
-@@ -959,7 +934,7 @@ err:
-     if (vmaw) {
-         Error *err = NULL;
-         vma_writer_close(vmaw, &err);
--        unlink(task->backup_file);
-+        unlink(backup_file);
-     }
-     if (pbs) {
-@@ -970,57 +945,8 @@ err:
-         rmdir(backup_dir);
-     }
--    task->result = NULL;
--
-     qemu_co_mutex_unlock(&backup_state.backup_mutex);
--    return;
--}
--
--UuidInfo *qmp_backup(
--    const char *backup_file,
--    const char *password,
--    const char *keyfile,
--    const char *key_password,
--    const char *fingerprint,
--    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,
--    const char *config_file,
--    const char *firewall_file,
--    const char *devlist,
--    bool has_speed, int64_t speed, Error **errp)
--{
--    QmpBackupTask task = {
--        .backup_file = backup_file,
--        .password = password,
--        .keyfile = keyfile,
--        .key_password = key_password,
--        .fingerprint = fingerprint,
--        .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,
--        .config_file = config_file,
--        .firewall_file = firewall_file,
--        .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 e7412f6322..93d924ef79 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -937,7 +937,7 @@
-                                     '*config-file': 'str',
-                                     '*firewall-file': 'str',
-                                     '*devlist': 'str', '*speed': 'int' },
--  'returns': 'UuidInfo' }
-+  'returns': 'UuidInfo', 'coroutine': true }
- ##
- # @query-backup:
-@@ -959,7 +959,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-Revert-block-rbd-implement-bdrv_co_block_status.patch b/debian/patches/pve/0043-Revert-block-rbd-implement-bdrv_co_block_status.patch
new file mode 100644 (file)
index 0000000..7e1ee52
--- /dev/null
@@ -0,0 +1,161 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Fabian Ebner <f.ebner@proxmox.com>
+Date: Tue, 17 May 2022 09:46:02 +0200
+Subject: [PATCH] Revert "block/rbd: implement bdrv_co_block_status"
+
+During backup, bdrv_co_block_status is called for each block copy
+chunk. When RBD is used, the current implementation with
+rbd_diff_iterate2() using whole_object=true takes about linearly more
+time, depending on the image size. Since there are linearly more
+chunks, the slowdown is quadratic, becoming unacceptable for large
+images (starting somewhere between 500-1000 GiB in my testing).
+
+This reverts commit 0347a8fd4c3faaedf119be04c197804be40a384b as a
+stop-gap measure, until it's clear how to make the implemenation
+more efficient.
+
+Upstream bug report:
+https://gitlab.com/qemu-project/qemu/-/issues/1026
+
+Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
+---
+ block/rbd.c | 112 ----------------------------------------------------
+ 1 file changed, 112 deletions(-)
+
+diff --git a/block/rbd.c b/block/rbd.c
+index 0913a0af39..1dab254517 100644
+--- a/block/rbd.c
++++ b/block/rbd.c
+@@ -108,12 +108,6 @@ typedef struct RBDTask {
+     int64_t ret;
+ } RBDTask;
+-typedef struct RBDDiffIterateReq {
+-    uint64_t offs;
+-    uint64_t bytes;
+-    bool exists;
+-} RBDDiffIterateReq;
+-
+ static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
+                             BlockdevOptionsRbd *opts, bool cache,
+                             const char *keypairs, const char *secretid,
+@@ -1456,111 +1450,6 @@ static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
+     return spec_info;
+ }
+-/*
+- * rbd_diff_iterate2 allows to interrupt the exection by returning a negative
+- * value in the callback routine. Choose a value that does not conflict with
+- * an existing exitcode and return it if we want to prematurely stop the
+- * execution because we detected a change in the allocation status.
+- */
+-#define QEMU_RBD_EXIT_DIFF_ITERATE2 -9000
+-
+-static int qemu_rbd_diff_iterate_cb(uint64_t offs, size_t len,
+-                                    int exists, void *opaque)
+-{
+-    RBDDiffIterateReq *req = opaque;
+-
+-    assert(req->offs + req->bytes <= offs);
+-    /*
+-     * we do not diff against a snapshot so we should never receive a callback
+-     * for a hole.
+-     */
+-    assert(exists);
+-
+-    if (!req->exists && offs > req->offs) {
+-        /*
+-         * we started in an unallocated area and hit the first allocated
+-         * block. req->bytes must be set to the length of the unallocated area
+-         * before the allocated area. stop further processing.
+-         */
+-        req->bytes = offs - req->offs;
+-        return QEMU_RBD_EXIT_DIFF_ITERATE2;
+-    }
+-
+-    if (req->exists && offs > req->offs + req->bytes) {
+-        /*
+-         * we started in an allocated area and jumped over an unallocated area,
+-         * req->bytes contains the length of the allocated area before the
+-         * unallocated area. stop further processing.
+-         */
+-        return QEMU_RBD_EXIT_DIFF_ITERATE2;
+-    }
+-
+-    req->bytes += len;
+-    req->exists = true;
+-
+-    return 0;
+-}
+-
+-static int coroutine_fn qemu_rbd_co_block_status(BlockDriverState *bs,
+-                                                 bool want_zero, int64_t offset,
+-                                                 int64_t bytes, int64_t *pnum,
+-                                                 int64_t *map,
+-                                                 BlockDriverState **file)
+-{
+-    BDRVRBDState *s = bs->opaque;
+-    int status, r;
+-    RBDDiffIterateReq req = { .offs = offset };
+-    uint64_t features, flags;
+-
+-    assert(offset + bytes <= s->image_size);
+-
+-    /* default to all sectors allocated */
+-    status = BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID;
+-    *map = offset;
+-    *file = bs;
+-    *pnum = bytes;
+-
+-    /* check if RBD image supports fast-diff */
+-    r = rbd_get_features(s->image, &features);
+-    if (r < 0) {
+-        return status;
+-    }
+-    if (!(features & RBD_FEATURE_FAST_DIFF)) {
+-        return status;
+-    }
+-
+-    /* check if RBD fast-diff result is valid */
+-    r = rbd_get_flags(s->image, &flags);
+-    if (r < 0) {
+-        return status;
+-    }
+-    if (flags & RBD_FLAG_FAST_DIFF_INVALID) {
+-        return status;
+-    }
+-
+-    r = rbd_diff_iterate2(s->image, NULL, offset, bytes, true, true,
+-                          qemu_rbd_diff_iterate_cb, &req);
+-    if (r < 0 && r != QEMU_RBD_EXIT_DIFF_ITERATE2) {
+-        return status;
+-    }
+-    assert(req.bytes <= bytes);
+-    if (!req.exists) {
+-        if (r == 0) {
+-            /*
+-             * rbd_diff_iterate2 does not invoke callbacks for unallocated
+-             * areas. This here catches the case where no callback was
+-             * invoked at all (req.bytes == 0).
+-             */
+-            assert(req.bytes == 0);
+-            req.bytes = bytes;
+-        }
+-        status = BDRV_BLOCK_ZERO | BDRV_BLOCK_OFFSET_VALID;
+-    }
+-
+-    *pnum = req.bytes;
+-    return status;
+-}
+-
+ static int64_t coroutine_fn qemu_rbd_co_getlength(BlockDriverState *bs)
+ {
+     BDRVRBDState *s = bs->opaque;
+@@ -1796,7 +1685,6 @@ static BlockDriver bdrv_rbd = {
+ #ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
+     .bdrv_co_pwrite_zeroes  = qemu_rbd_co_pwrite_zeroes,
+ #endif
+-    .bdrv_co_block_status   = qemu_rbd_co_block_status,
+     .bdrv_snapshot_create   = qemu_rbd_snap_create,
+     .bdrv_snapshot_delete   = qemu_rbd_snap_remove,
diff --git a/debian/patches/pve/0044-PBS-add-master-key-support.patch b/debian/patches/pve/0044-PBS-add-master-key-support.patch
deleted file mode 100644 (file)
index 4498723..0000000
+++ /dev/null
@@ -1,100 +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>
-[FE: adapt to QAPI change dropping redundant has_*]
-Signed-off-by: Fiona Ebner <f.ebner@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 56f39b14d4..f852c70611 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1053,6 +1053,7 @@ void coroutine_fn hmp_backup(Monitor *mon, const QDict *qdict)
-         NULL, // PBS password
-         NULL, // PBS keyfile
-         NULL, // PBS key_password
-+        NULL, // PBS master_keyfile
-         NULL, // PBS fingerprint
-         NULL, // PBS backup-id
-         false, 0, // PBS backup-time
-diff --git a/pve-backup.c b/pve-backup.c
-index 88e507b3c2..04c5f561cd 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -531,6 +531,7 @@ UuidInfo coroutine_fn *qmp_backup(
-     const char *password,
-     const char *keyfile,
-     const char *key_password,
-+    const char *master_keyfile,
-     const char *fingerprint,
-     const char *backup_id,
-     bool has_backup_time, int64_t backup_time,
-@@ -679,6 +680,7 @@ UuidInfo coroutine_fn *qmp_backup(
-             password,
-             keyfile,
-             key_password,
-+            master_keyfile,
-             has_compress ? compress : true,
-             has_encrypt ? encrypt : !!keyfile,
-             fingerprint,
-@@ -1038,5 +1040,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 93d924ef79..568feb63ad 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -908,6 +908,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')
-@@ -927,6 +929,7 @@
-                                     '*password': 'str',
-                                     '*keyfile': 'str',
-                                     '*key-password': 'str',
-+                                    '*master-keyfile': 'str',
-                                     '*fingerprint': 'str',
-                                     '*backup-id': 'str',
-                                     '*backup-time': 'int',
-@@ -979,6 +982,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.
- #
- ##
-@@ -987,6 +993,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/0044-alloc-track-fix-deadlock-during-drop.patch b/debian/patches/pve/0044-alloc-track-fix-deadlock-during-drop.patch
new file mode 100644 (file)
index 0000000..8169445
--- /dev/null
@@ -0,0 +1,153 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Fiona Ebner <f.ebner@proxmox.com>
+Date: Thu, 6 Apr 2023 14:59:31 +0200
+Subject: [PATCH] alloc-track: fix deadlock during drop
+
+by replacing the block node directly after changing the backing file
+instead of rescheduling it.
+
+With changes in QEMU 8.0, calling bdrv_get_info (and bdrv_unref)
+during drop can lead to a deadlock when using iothread (only triggered
+with multiple disks, except during debugging where it also triggered
+with one disk sometimes):
+1. job_unref_locked acquires the AioContext and calls job->driver->free
+2. track_drop gets scheduled
+3. bdrv_graph_wrlock is called and polls which leads to track_drop being
+   called
+4. track_drop acquires the AioContext recursively
+5. bdrv_get_info is a wrapped coroutine (since 8.0) and thus polls for
+   bdrv_co_get_info. This releases the AioContext, but only once! The
+   documentation for the AIO_WAIT_WHILE macro states that the
+   AioContext lock needs to be acquired exactly once, but there does
+   not seem to be a way for track_drop to know if it acquired the lock
+   recursively or not (without adding further hacks).
+6. Because the AioContext is still held by the main thread once, it can't
+   be acquired before entering bdrv_co_get_info in co_schedule_bh_cb
+   which happens in the iothread
+
+When doing the operation in change_backing_file, the AioContext has
+already been acquired by the caller, so the issue with the recursive
+lock goes away.
+
+The comment explaining why delaying the replace is necessary is
+> 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'
+
+However, there is no check for blockers in bdrv_replace_node. It would
+need to be done by us, the caller, with check_to_replace_node.
+Furthermore, the mirror job also does its call to bdrv_replace_node
+while there is an active blocker (inserted by mirror itself) and they
+use a specialized version to check for blockers instead of
+check_to_replace_node there. Alloc-track could also do something
+similar to check for other blockers, but it should be fine to rely on
+Proxmox VE that no other operation with the blockdev is going on.
+
+Mirror also drains the target before replacing the node, but the
+target can have other users. In case of alloc-track the file child
+should not be accessible by anybody else and so there can't be an
+in-flight operation for the file child when alloc-track is drained.
+
+The rescheduling based on refcounting is a hack and it doesn't seem to
+be necessary anymore. It's not clear what the original issue from the
+comment was. Testing with older builds with track_drop done directly
+without rescheduling also didn't lead to any noticable issue for me.
+
+One issue it might have been is the one fixed by b1e1af394d
+("block/stream: Drain subtree around graph change"), where
+block-stream had a use-after-free if the base node changed at an
+inconvenient time (which alloc-track's auto-drop does).
+
+It's also not possible to just not auto-replace the alloc-track. Not
+replacing it at all leads to other operations like block resize
+hanging, and there is no good way to replace it manually via QMP
+(there is x-blockdev-change, but it is experimental and doesn't
+implement the required operation yet). Also, it's just cleaner in
+general to not leave unnecessary block nodes lying around.
+
+Suggested-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ block/alloc-track.c | 54 ++++++++++++++-------------------------------
+ 1 file changed, 16 insertions(+), 38 deletions(-)
+
+diff --git a/block/alloc-track.c b/block/alloc-track.c
+index b75d7c6460..76da140a68 100644
+--- a/block/alloc-track.c
++++ b/block/alloc-track.c
+@@ -25,7 +25,6 @@
+ typedef enum DropState {
+     DropNone,
+-    DropRequested,
+     DropInProgress,
+ } DropState;
+@@ -268,37 +267,6 @@ static void track_child_perm(BlockDriverState *bs, BdrvChild *c,
+     }
+ }
+-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)
+@@ -308,13 +276,23 @@ static int track_change_backing_file(BlockDriverState *bs,
+         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;
++         * this node, so let's remove ourselves from the block graph */
++        BlockDriverState *file = bs->file->bs;
++
++        /* Just to be sure, because bdrv_replace_node unrefs it */
+         bdrv_ref(bs);
+-        aio_bh_schedule_oneshot(qemu_get_aio_context(), track_drop, (void*)bs);
++        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);
+     }
+     return 0;
diff --git a/debian/patches/pve/0045-PVE-block-pbs-fast-path-reads-without-allocation-if-.patch b/debian/patches/pve/0045-PVE-block-pbs-fast-path-reads-without-allocation-if-.patch
deleted file mode 100644 (file)
index 6b18def..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 43e69ada46..5d20789084 100644
---- a/block/pbs.c
-+++ b/block/pbs.c
-@@ -201,7 +201,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");
-@@ -224,8 +233,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 0;
- }
diff --git a/debian/patches/pve/0045-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch b/debian/patches/pve/0045-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch
new file mode 100644 (file)
index 0000000..cbc39cc
--- /dev/null
@@ -0,0 +1,190 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Fiona Ebner <f.ebner@proxmox.com>
+Date: Fri, 5 May 2023 13:39:53 +0200
+Subject: [PATCH] migration: for snapshots, hold the BQL during setup callbacks
+
+In spirit, this is a partial revert of commit 9b09503752 ("migration:
+run setup callbacks out of big lock"), but only for the snapshot case.
+
+For snapshots, the bdrv_writev_vmstate() function is used during setup
+(in QIOChannelBlock backing the QEMUFile), but not holding the BQL
+while calling it could lead to an assertion failure. To understand
+how, first note the following:
+
+1. Generated coroutine wrappers for block layer functions spawn the
+coroutine and use AIO_WAIT_WHILE()/aio_poll() to wait for it.
+2. If the host OS switches threads at an inconvenient time, it can
+happen that a bottom half scheduled for the main thread's AioContext
+is executed as part of a vCPU thread's aio_poll().
+
+An example leading to the assertion failure is as follows:
+
+main thread:
+1. A snapshot-save QMP command gets issued.
+2. snapshot_save_job_bh() is scheduled.
+
+vCPU thread:
+3. aio_poll() for the main thread's AioContext is called (e.g. when
+the guest writes to a pflash device, as part of blk_pwrite which is a
+generated coroutine wrapper).
+4. snapshot_save_job_bh() is executed as part of aio_poll().
+3. qemu_savevm_state() is called.
+4. qemu_mutex_unlock_iothread() is called. Now
+qemu_get_current_aio_context() returns 0x0.
+5. bdrv_writev_vmstate() is executed during the usual savevm setup.
+But this function is a generated coroutine wrapper, so it uses
+AIO_WAIT_WHILE. There, the assertion
+assert(qemu_get_current_aio_context() == qemu_get_aio_context());
+will fail.
+
+To fix it, ensure that the BQL is held during setup. To avoid changing
+the behavior for migration too, introduce conditionals for the setup
+callbacks that need the BQL and only take the lock if it's not already
+held.
+
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ include/migration/register.h   |  2 +-
+ migration/block-dirty-bitmap.c | 15 ++++++++++++---
+ migration/block.c              | 15 ++++++++++++---
+ migration/ram.c                | 16 +++++++++++++---
+ migration/savevm.c             |  2 --
+ 5 files changed, 38 insertions(+), 12 deletions(-)
+
+diff --git a/include/migration/register.h b/include/migration/register.h
+index a8dfd8fefd..fa9b0b0f10 100644
+--- a/include/migration/register.h
++++ b/include/migration/register.h
+@@ -43,9 +43,9 @@ typedef struct SaveVMHandlers {
+      * by other locks.
+      */
+     int (*save_live_iterate)(QEMUFile *f, void *opaque);
++    int (*save_setup)(QEMUFile *f, void *opaque);
+     /* This runs outside the iothread lock!  */
+-    int (*save_setup)(QEMUFile *f, void *opaque);
+     /* Note for save_live_pending:
+      * must_precopy:
+      * - must be migrated in precopy or in stopped state
+diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c
+index a6440929fa..69fab3275c 100644
+--- a/migration/block-dirty-bitmap.c
++++ b/migration/block-dirty-bitmap.c
+@@ -1214,10 +1214,17 @@ static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque)
+ {
+     DBMSaveState *s = &((DBMState *)opaque)->save;
+     SaveBitmapState *dbms = NULL;
++    bool release_lock = false;
+-    qemu_mutex_lock_iothread();
++    /* For snapshots, the BQL is held during setup. */
++    if (!qemu_mutex_iothread_locked()) {
++        qemu_mutex_lock_iothread();
++        release_lock = true;
++    }
+     if (init_dirty_bitmap_migration(s) < 0) {
+-        qemu_mutex_unlock_iothread();
++        if (release_lock) {
++            qemu_mutex_unlock_iothread();
++        }
+         return -1;
+     }
+@@ -1225,7 +1232,9 @@ static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque)
+         send_bitmap_start(f, s, dbms);
+     }
+     qemu_put_bitmap_flags(f, DIRTY_BITMAP_MIG_FLAG_EOS);
+-    qemu_mutex_unlock_iothread();
++    if (release_lock) {
++        qemu_mutex_unlock_iothread();
++    }
+     return 0;
+ }
+diff --git a/migration/block.c b/migration/block.c
+index b2497bbd32..c9d55be642 100644
+--- a/migration/block.c
++++ b/migration/block.c
+@@ -716,21 +716,30 @@ static void block_migration_cleanup(void *opaque)
+ static int block_save_setup(QEMUFile *f, void *opaque)
+ {
+     int ret;
++    bool release_lock = false;
+     trace_migration_block_save("setup", block_mig_state.submitted,
+                                block_mig_state.transferred);
+-    qemu_mutex_lock_iothread();
++    /* For snapshots, the BQL is held during setup. */
++    if (!qemu_mutex_iothread_locked()) {
++        qemu_mutex_lock_iothread();
++        release_lock = true;
++    }
+     ret = init_blk_migration(f);
+     if (ret < 0) {
+-        qemu_mutex_unlock_iothread();
++        if (release_lock) {
++            qemu_mutex_unlock_iothread();
++        }
+         return ret;
+     }
+     /* start track dirty blocks */
+     ret = set_dirty_tracking();
+-    qemu_mutex_unlock_iothread();
++    if (release_lock) {
++        qemu_mutex_unlock_iothread();
++    }
+     if (ret) {
+         return ret;
+diff --git a/migration/ram.c b/migration/ram.c
+index 79d881f735..0ecbbc3202 100644
+--- a/migration/ram.c
++++ b/migration/ram.c
+@@ -3117,8 +3117,16 @@ static void migration_bitmap_clear_discarded_pages(RAMState *rs)
+ static void ram_init_bitmaps(RAMState *rs)
+ {
+-    /* For memory_global_dirty_log_start below.  */
+-    qemu_mutex_lock_iothread();
++    bool release_lock = false;
++
++    /*
++     * For memory_global_dirty_log_start below.
++     * For snapshots, the BQL is held during setup.
++     */
++    if (!qemu_mutex_iothread_locked()) {
++        qemu_mutex_lock_iothread();
++        release_lock = true;
++    }
+     qemu_mutex_lock_ramlist();
+     WITH_RCU_READ_LOCK_GUARD() {
+@@ -3130,7 +3138,9 @@ static void ram_init_bitmaps(RAMState *rs)
+         }
+     }
+     qemu_mutex_unlock_ramlist();
+-    qemu_mutex_unlock_iothread();
++    if (release_lock) {
++        qemu_mutex_unlock_iothread();
++    }
+     /*
+      * After an eventual first bitmap sync, fixup the initial bitmap
+diff --git a/migration/savevm.c b/migration/savevm.c
+index aa54a67fda..fc6a82a555 100644
+--- a/migration/savevm.c
++++ b/migration/savevm.c
+@@ -1621,10 +1621,8 @@ static int qemu_savevm_state(QEMUFile *f, Error **errp)
+     memset(&compression_counters, 0, sizeof(compression_counters));
+     ms->to_dst_file = f;
+-    qemu_mutex_unlock_iothread();
+     qemu_savevm_state_header(f);
+     qemu_savevm_state_setup(f);
+-    qemu_mutex_lock_iothread();
+     while (qemu_file_get_error(f) == 0) {
+         if (qemu_savevm_state_iterate(f, false) > 0) {
diff --git a/debian/patches/pve/0046-PVE-block-stream-increase-chunk-size.patch b/debian/patches/pve/0046-PVE-block-stream-increase-chunk-size.patch
deleted file mode 100644 (file)
index 5707bee..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 7f9e1ecdbb..6a29d80398 100644
---- a/block/stream.c
-+++ b/block/stream.c
-@@ -27,7 +27,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/0046-savevm-async-don-t-hold-BQL-during-setup.patch b/debian/patches/pve/0046-savevm-async-don-t-hold-BQL-during-setup.patch
new file mode 100644 (file)
index 0000000..ce5e14e
--- /dev/null
@@ -0,0 +1,29 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Fiona Ebner <f.ebner@proxmox.com>
+Date: Fri, 5 May 2023 15:30:16 +0200
+Subject: [PATCH] savevm-async: don't hold BQL during setup
+
+See commit "migration: for snapshots, hold the BQL during setup
+callbacks" for why. This is separate, because a version of that one
+will hopefully land upstream.
+
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+---
+ migration/savevm-async.c | 2 --
+ 1 file changed, 2 deletions(-)
+
+diff --git a/migration/savevm-async.c b/migration/savevm-async.c
+index effe6d1e0d..f006a8e4d4 100644
+--- a/migration/savevm-async.c
++++ b/migration/savevm-async.c
+@@ -403,10 +403,8 @@ void qmp_savevm_start(const char *statefile, Error **errp)
+     snap_state.state = SAVE_STATE_ACTIVE;
+     snap_state.finalize_bh = qemu_bh_new(process_savevm_finalize, &snap_state);
+     snap_state.co = qemu_coroutine_create(&process_savevm_co, NULL);
+-    qemu_mutex_unlock_iothread();
+     qemu_savevm_state_header(snap_state.file);
+     qemu_savevm_state_setup(snap_state.file);
+-    qemu_mutex_lock_iothread();
+     /* Async processing from here on out happens in iohandler context, so let
+      * the target bdrv have its home there.
diff --git a/debian/patches/pve/0047-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch b/debian/patches/pve/0047-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch
deleted file mode 100644 (file)
index d1bd74d..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 2e267a85ab..449a44bf20 100644
---- a/block/io.c
-+++ b/block/io.c
-@@ -1576,6 +1576,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/0048-block-add-alloc-track-driver.patch b/debian/patches/pve/0048-block-add-alloc-track-driver.patch
deleted file mode 100644 (file)
index fc724c9..0000000
+++ /dev/null
@@ -1,404 +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>
-[FE: adapt to changed function signatures
-     make error return value consistent with QEMU]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- block/alloc-track.c | 351 ++++++++++++++++++++++++++++++++++++++++++++
- block/meson.build   |   1 +
- 2 files changed, 352 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..113bbd7058
---- /dev/null
-+++ b/block/alloc-track.c
-@@ -0,0 +1,351 @@
-+/*
-+ * 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 "block/dirty-bitmap.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 coroutine_fn int64_t track_co_getlength(BlockDriverState *bs)
-+{
-+    return bdrv_co_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 -EIO;
-+    }
-+
-+    /* 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_co_getlength                = track_co_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 eece0d5743..8a68162cc0 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/0049-PVE-savevm-async-register-yank-before-migration_inco.patch b/debian/patches/pve/0049-PVE-savevm-async-register-yank-before-migration_inco.patch
deleted file mode 100644 (file)
index 6646d97..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 70273a2996..fab1dc32d2 100644
---- a/migration/savevm-async.c
-+++ b/migration/savevm-async.c
-@@ -20,6 +20,7 @@
- #include "qemu/timer.h"
- #include "qemu/main-loop.h"
- #include "qemu/rcu.h"
-+#include "qemu/yank.h"
- /* #define DEBUG_SAVEVM_STATE */
-@@ -518,6 +519,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/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
deleted file mode 100644 (file)
index 7701b55..0000000
+++ /dev/null
@@ -1,130 +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>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@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 5e713e231d..9390d5e5cf 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 b5b0bb4467..36f97e1f19 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 06d814e39c..e2c06c496d 100644
---- a/qemu-img.c
-+++ b/qemu-img.c
-@@ -5002,6 +5002,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;
-@@ -5011,6 +5012,7 @@ static int img_dd(int argc, char **argv)
-     int64_t size = 0, readsize = 0;
-     int64_t out_pos, in_pos;
-     bool force_share = false, skip_create = false;
-+    const char *snapshot_name = NULL;
-     struct DdInfo dd = {
-         .flags = 0,
-         .count = 0,
-@@ -5048,7 +5050,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;
-         }
-@@ -5071,6 +5073,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;
-@@ -5130,11 +5145,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) {
-@@ -5289,6 +5317,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/0051-vma-allow-partial-restore.patch b/debian/patches/pve/0051-vma-allow-partial-restore.patch
deleted file mode 100644 (file)
index f1fd7cb..0000000
+++ /dev/null
@@ -1,407 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fabian Ebner <f.ebner@proxmox.com>
-Date: Thu, 21 Apr 2022 13:26:48 +0200
-Subject: [PATCH] vma: allow partial restore
-
-Introduce a new map line for skipping a certain drive, of the form
-skip=drive-scsi0
-
-Since in PVE, most archives are compressed and piped to vma for
-restore, it's not easily possible to skip reads.
-
-For the reader, a new skip flag for VmaRestoreState is added and the
-target is allowed to be NULL if skip is specified when registering. If
-the skip flag is set, no writes will be made as well as no check for
-duplicate clusters. Therefore, the flag is not set for verify.
-
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
-Acked-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- vma-reader.c |  64 ++++++++++++---------
- vma.c        | 157 +++++++++++++++++++++++++++++----------------------
- vma.h        |   2 +-
- 3 files changed, 126 insertions(+), 97 deletions(-)
-
-diff --git a/vma-reader.c b/vma-reader.c
-index e65f1e8415..81a891c6b1 100644
---- a/vma-reader.c
-+++ b/vma-reader.c
-@@ -28,6 +28,7 @@ typedef struct VmaRestoreState {
-     bool write_zeroes;
-     unsigned long *bitmap;
-     int bitmap_size;
-+    bool skip;
- }  VmaRestoreState;
- struct VmaReader {
-@@ -425,13 +426,14 @@ VmaDeviceInfo *vma_reader_get_device_info(VmaReader *vmar, guint8 dev_id)
- }
- static void allocate_rstate(VmaReader *vmar,  guint8 dev_id,
--                            BlockBackend *target, bool write_zeroes)
-+                            BlockBackend *target, bool write_zeroes, bool skip)
- {
-     assert(vmar);
-     assert(dev_id);
-     vmar->rstate[dev_id].target = target;
-     vmar->rstate[dev_id].write_zeroes = write_zeroes;
-+    vmar->rstate[dev_id].skip = skip;
-     int64_t size = vmar->devinfo[dev_id].size;
-@@ -446,28 +448,30 @@ static void allocate_rstate(VmaReader *vmar,  guint8 dev_id,
- }
- int vma_reader_register_bs(VmaReader *vmar, guint8 dev_id, BlockBackend *target,
--                           bool write_zeroes, Error **errp)
-+                           bool write_zeroes, bool skip, Error **errp)
- {
-     assert(vmar);
--    assert(target != NULL);
-+    assert(target != NULL || skip);
-     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;
-+    assert(vmar->rstate[dev_id].target == NULL && !vmar->rstate[dev_id].skip);
-+
-+    if (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);
-+    allocate_rstate(vmar, dev_id, target, write_zeroes, skip);
-     return 0;
- }
-@@ -560,19 +564,23 @@ static int restore_extent(VmaReader *vmar, unsigned char *buf,
-         VmaRestoreState *rstate = &vmar->rstate[dev_id];
-         BlockBackend *target = NULL;
-+        bool skip = rstate->skip;
-+
-         if (dev_id != vmar->vmstate_stream) {
-             target = rstate->target;
--            if (!verify && !target) {
-+            if (!verify && !target && !skip) {
-                 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;
-+            if (!skip) {
-+                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);
-             }
--            vma_reader_set_bitmap(rstate, cluster_num, 1);
-             max_sector = vmar->devinfo[dev_id].size/BDRV_SECTOR_SIZE;
-         } else {
-@@ -618,7 +626,7 @@ static int restore_extent(VmaReader *vmar, unsigned char *buf,
-                 return -1;
-             }
--            if (!verify) {
-+            if (!verify && !skip) {
-                 int nb_sectors = end_sector - sector_num;
-                 if (restore_write_data(vmar, dev_id, target, vmstate_fd,
-                                        buf + start, sector_num, nb_sectors,
-@@ -654,7 +662,7 @@ static int restore_extent(VmaReader *vmar, unsigned char *buf,
-                         return -1;
-                     }
--                    if (!verify) {
-+                    if (!verify && !skip) {
-                         int nb_sectors = end_sector - sector_num;
-                         if (restore_write_data(vmar, dev_id, target, vmstate_fd,
-                                                buf + start, sector_num,
-@@ -679,7 +687,7 @@ static int restore_extent(VmaReader *vmar, unsigned char *buf,
-                             vmar->partial_zero_cluster_data += zero_size;
-                         }
--                        if (rstate->write_zeroes && !verify) {
-+                        if (rstate->write_zeroes && !verify && !skip) {
-                             if (restore_write_data(vmar, dev_id, target, vmstate_fd,
-                                                    zero_vma_block, sector_num,
-                                                    nb_sectors, errp) < 0) {
-@@ -850,7 +858,7 @@ int vma_reader_verify(VmaReader *vmar, bool verbose, Error **errp)
-     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);
-+            allocate_rstate(vmar, dev_id, NULL, false, false);
-         }
-     }
-diff --git a/vma.c b/vma.c
-index e8dffb43e0..e6e9ffc7fe 100644
---- a/vma.c
-+++ b/vma.c
-@@ -138,6 +138,7 @@ typedef struct RestoreMap {
-     char *throttling_group;
-     char *cache;
-     bool write_zero;
-+    bool skip;
- } RestoreMap;
- static bool try_parse_option(char **line, const char *optname, char **out, const char *inbuf) {
-@@ -245,47 +246,61 @@ static int extract_content(int argc, char **argv)
-             char *bps = NULL;
-             char *group = NULL;
-             char *cache = NULL;
-+            char *devname = NULL;
-+            bool skip = false;
-+            uint64_t bps_value = 0;
-+            const char *path = NULL;
-+            bool write_zero = true;
-+
-             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) {
-+                len = len - 1;
-+                if (len == 0) {
-                     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;
-+            if (strncmp(line, "skip", 4) == 0) {
-+                if (len < 6 || line[4] != '=') {
-+                    g_error("read map failed - option 'skip' has no value ('%s')",
-+                            inbuf);
-+                } else {
-+                    devname = line + 5;
-+                    skip = true;
-+                }
-+            } else {
-+                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);
--            }
-+                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);
-+                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);
-+                }
-+
-+                path = extract_devname(path, &devname, -1);
-             }
--            char *devname = NULL;
--            path = extract_devname(path, &devname, -1);
-             if (!devname) {
-                 g_error("read map failed - no dev name specified ('%s')",
-                         inbuf);
-@@ -299,6 +314,7 @@ static int extract_content(int argc, char **argv)
-             map->throttling_group = group;
-             map->cache = cache;
-             map->write_zero = write_zero;
-+            map->skip = skip;
-             g_hash_table_insert(devmap, map->devname, map);
-@@ -328,6 +344,7 @@ static int extract_content(int argc, char **argv)
-             const char *cache = NULL;
-             int flags = BDRV_O_RDWR;
-             bool write_zero = true;
-+            bool skip = false;
-             BlockBackend *blk = NULL;
-@@ -343,6 +360,7 @@ static int extract_content(int argc, char **argv)
-                 throttling_group = map->throttling_group;
-                 cache = map->cache;
-                 write_zero = map->write_zero;
-+                skip = map->skip;
-             } else {
-                 devfn = g_strdup_printf("%s/tmp-disk-%s.raw",
-                                         dirname, di->devname);
-@@ -361,57 +379,60 @@ static int extract_content(int argc, char **argv)
-                 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 (!skip) {
-+                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 (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 && bdrv_parse_cache_mode(cache, &flags, &writethrough)) {
-+                    g_error("invalid cache option: %s\n", cache);
-+                }
--            if (cache) {
--                blk_set_enable_write_cache(blk, !writethrough);
--            }
-+                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 (throttling_group) {
--                blk_io_limits_enable(blk, throttling_group);
--            }
-+                if (cache) {
-+                    blk_set_enable_write_cache(blk, !writethrough);
-+                }
--            if (throttling_bps) {
--                if (!throttling_group) {
--                    blk_io_limits_enable(blk, devfn);
-+                if (throttling_group) {
-+                    blk_io_limits_enable(blk, throttling_group);
-                 }
--                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");
-+                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);
-                 }
--                blk_set_io_limits(blk, &cfg);
-             }
--            if (vma_reader_register_bs(vmar, i, blk, write_zero, &errp) < 0) {
-+            if (vma_reader_register_bs(vmar, i, blk, write_zero, skip, &errp) < 0) {
-                 g_error("%s", error_get_pretty(errp));
-             }
-diff --git a/vma.h b/vma.h
-index c895c97f6d..1b62859165 100644
---- a/vma.h
-+++ b/vma.h
-@@ -142,7 +142,7 @@ 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);
-+                           bool skip, 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);
diff --git a/debian/patches/pve/0052-pbs-namespace-support.patch b/debian/patches/pve/0052-pbs-namespace-support.patch
deleted file mode 100644 (file)
index b0bb9a6..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Tue, 26 Apr 2022 16:06:28 +0200
-Subject: [PATCH] pbs: namespace support
-
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-[FE: adapt to QAPI change dropping redundant has_*]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- block/monitor/block-hmp-cmds.c |  1 +
- block/pbs.c                    | 25 +++++++++++++++++++++----
- pbs-restore.c                  | 19 ++++++++++++++++---
- pve-backup.c                   |  6 +++++-
- qapi/block-core.json           |  5 ++++-
- 5 files changed, 47 insertions(+), 9 deletions(-)
-
-diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
-index f852c70611..ac23f21eef 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1055,6 +1055,7 @@ void coroutine_fn hmp_backup(Monitor *mon, const QDict *qdict)
-         NULL, // PBS key_password
-         NULL, // PBS master_keyfile
-         NULL, // PBS fingerprint
-+        NULL, // PBS backup-ns
-         NULL, // PBS backup-id
-         false, 0, // PBS backup-time
-         false, false, // PBS use-dirty-bitmap
-diff --git a/block/pbs.c b/block/pbs.c
-index 5d20789084..a2211e0f3b 100644
---- a/block/pbs.c
-+++ b/block/pbs.c
-@@ -15,6 +15,7 @@
- #include <proxmox-backup-qemu.h>
- #define PBS_OPT_REPOSITORY "repository"
-+#define PBS_OPT_NAMESPACE "namespace"
- #define PBS_OPT_SNAPSHOT "snapshot"
- #define PBS_OPT_ARCHIVE "archive"
- #define PBS_OPT_KEYFILE "keyfile"
-@@ -28,6 +29,7 @@ typedef struct {
-     int64_t length;
-     char *repository;
-+    char *namespace;
-     char *snapshot;
-     char *archive;
- } BDRVPBSState;
-@@ -41,6 +43,11 @@ static QemuOptsList runtime_opts = {
-             .type = QEMU_OPT_STRING,
-             .help = "The server address and repository to connect to.",
-         },
-+        {
-+            .name = PBS_OPT_NAMESPACE,
-+            .type = QEMU_OPT_STRING,
-+            .help = "Optional: The snapshot's namespace.",
-+        },
-         {
-             .name = PBS_OPT_SNAPSHOT,
-             .type = QEMU_OPT_STRING,
-@@ -77,7 +84,7 @@ static QemuOptsList runtime_opts = {
- // filename format:
--// pbs:repository=<repo>,snapshot=<snap>,password=<pw>,key_password=<kpw>,fingerprint=<fp>,archive=<archive>
-+// pbs:repository=<repo>,namespace=<ns>,snapshot=<snap>,password=<pw>,key_password=<kpw>,fingerprint=<fp>,archive=<archive>
- static void pbs_parse_filename(const char *filename, QDict *options,
-                                      Error **errp)
- {
-@@ -113,6 +120,7 @@ static int pbs_open(BlockDriverState *bs, QDict *options, int flags,
-     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 *namespace = qemu_opt_get(opts, PBS_OPT_NAMESPACE);
-     const char *fingerprint = qemu_opt_get(opts, PBS_OPT_FINGERPRINT);
-     const char *key_password = qemu_opt_get(opts, PBS_OPT_ENCRYPTION_PASSWORD);
-@@ -125,9 +133,12 @@ static int pbs_open(BlockDriverState *bs, QDict *options, int flags,
-     if (!key_password) {
-         key_password = getenv("PBS_ENCRYPTION_PASSWORD");
-     }
-+    if (namespace) {
-+        s->namespace = g_strdup(namespace);
-+    }
-     /* connect to PBS server in read mode */
--    s->conn = proxmox_restore_new(s->repository, s->snapshot, password,
-+    s->conn = proxmox_restore_new_ns(s->repository, s->snapshot, s->namespace, password,
-         keyfile, key_password, fingerprint, &pbs_error);
-     /* invalidates qemu_opt_get char pointers from above */
-@@ -172,6 +183,7 @@ static int pbs_file_open(BlockDriverState *bs, QDict *options, int flags,
- static void pbs_close(BlockDriverState *bs) {
-     BDRVPBSState *s = bs->opaque;
-     g_free(s->repository);
-+    g_free(s->namespace);
-     g_free(s->snapshot);
-     g_free(s->archive);
-     proxmox_restore_disconnect(s->conn);
-@@ -253,8 +265,13 @@ static coroutine_fn int pbs_co_pwritev(BlockDriverState *bs,
- 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);
-+    if (s->namespace) {
-+        snprintf(bs->exact_filename, sizeof(bs->exact_filename), "%s/%s:%s(%s)",
-+                 s->repository, s->namespace, s->snapshot, s->archive);
-+    } else {
-+        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[] = {
-diff --git a/pbs-restore.c b/pbs-restore.c
-index 2f834cf42e..f03d9bab8d 100644
---- a/pbs-restore.c
-+++ b/pbs-restore.c
-@@ -29,7 +29,7 @@
- static void help(void)
- {
-     const char *help_msg =
--        "usage: pbs-restore [--repository <repo>] snapshot archive-name target [command options]\n"
-+        "usage: pbs-restore [--repository <repo>] [--ns namespace] snapshot archive-name target [command options]\n"
-         ;
-     printf("%s", help_msg);
-@@ -77,6 +77,7 @@ int main(int argc, char **argv)
-     Error *main_loop_err = NULL;
-     const char *format = "raw";
-     const char *repository = NULL;
-+    const char *backup_ns = NULL;
-     const char *keyfile = NULL;
-     int verbose = false;
-     bool skip_zero = false;
-@@ -90,6 +91,7 @@ int main(int argc, char **argv)
-             {"verbose", no_argument, 0, 'v'},
-             {"format", required_argument, 0, 'f'},
-             {"repository", required_argument, 0, 'r'},
-+            {"ns", required_argument, 0, 'n'},
-             {"keyfile", required_argument, 0, 'k'},
-             {0, 0, 0, 0}
-         };
-@@ -110,6 +112,9 @@ int main(int argc, char **argv)
-             case 'r':
-                 repository = g_strdup(argv[optind - 1]);
-                 break;
-+            case 'n':
-+                backup_ns = g_strdup(argv[optind - 1]);
-+                break;
-             case 'k':
-                 keyfile = g_strdup(argv[optind - 1]);
-                 break;
-@@ -160,8 +165,16 @@ int main(int argc, char **argv)
-         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);
-+    ProxmoxRestoreHandle *conn = proxmox_restore_new_ns(
-+        repository,
-+        snapshot,
-+        backup_ns,
-+        password,
-+        keyfile,
-+        key_password,
-+        fingerprint,
-+        &pbs_error
-+    );
-     if (conn == NULL) {
-         fprintf(stderr, "restore failed: %s\n", pbs_error);
-         return -1;
-diff --git a/pve-backup.c b/pve-backup.c
-index 04c5f561cd..08dfb9cbda 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -12,6 +12,8 @@
- #include "qapi/qmp/qerror.h"
- #include "qemu/cutils.h"
-+#include <proxmox-backup-qemu.h>
-+
- /* PVE backup state and related function */
- /*
-@@ -533,6 +535,7 @@ UuidInfo coroutine_fn *qmp_backup(
-     const char *key_password,
-     const char *master_keyfile,
-     const char *fingerprint,
-+    const char *backup_ns,
-     const char *backup_id,
-     bool has_backup_time, int64_t backup_time,
-     bool has_use_dirty_bitmap, bool use_dirty_bitmap,
-@@ -672,8 +675,9 @@ UuidInfo coroutine_fn *qmp_backup(
-         firewall_name = "fw.conf";
-         char *pbs_err = NULL;
--        pbs = proxmox_backup_new(
-+        pbs = proxmox_backup_new_ns(
-             backup_file,
-+            backup_ns,
-             backup_id,
-             backup_time,
-             dump_cb_block_size,
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index 568feb63ad..9edeb33d82 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -912,6 +912,8 @@
- #
- # @fingerprint: server cert fingerprint (optional for format 'pbs')
- #
-+# @backup-ns: backup namespace (required for format 'pbs')
-+#
- # @backup-id: backup ID (required for format 'pbs')
- #
- # @backup-time: backup timestamp (Unix epoch, required for format 'pbs')
-@@ -931,6 +933,7 @@
-                                     '*key-password': 'str',
-                                     '*master-keyfile': 'str',
-                                     '*fingerprint': 'str',
-+                                    '*backup-ns': 'str',
-                                     '*backup-id': 'str',
-                                     '*backup-time': 'int',
-                                     '*use-dirty-bitmap': 'bool',
-@@ -3385,7 +3388,7 @@
- { 'struct': 'BlockdevOptionsPbs',
-   'data': { 'repository': 'str', 'snapshot': 'str', 'archive': 'str',
-             '*keyfile': 'str', '*password': 'str', '*fingerprint': 'str',
--            '*key_password': 'str' } }
-+            '*key_password': 'str', '*namespace': 'str' } }
- ##
- # @BlockdevOptionsNVMe:
diff --git a/debian/patches/pve/0053-Revert-block-rbd-workaround-for-ceph-issue-53784.patch b/debian/patches/pve/0053-Revert-block-rbd-workaround-for-ceph-issue-53784.patch
deleted file mode 100644 (file)
index e60e74f..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fabian Ebner <f.ebner@proxmox.com>
-Date: Thu, 23 Jun 2022 14:00:05 +0200
-Subject: [PATCH] Revert "block/rbd: workaround for ceph issue #53784"
-
-This reverts commit fc176116cdea816ceb8dd969080b2b95f58edbc0 in
-preparation to revert 0347a8fd4c3faaedf119be04c197804be40a384b.
-
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
----
- block/rbd.c | 42 ++----------------------------------------
- 1 file changed, 2 insertions(+), 40 deletions(-)
-
-diff --git a/block/rbd.c b/block/rbd.c
-index a4749f3b1b..53e0396b51 100644
---- a/block/rbd.c
-+++ b/block/rbd.c
-@@ -1511,7 +1511,6 @@ static int coroutine_fn qemu_rbd_co_block_status(BlockDriverState *bs,
-     int status, r;
-     RBDDiffIterateReq req = { .offs = offset };
-     uint64_t features, flags;
--    uint64_t head = 0;
-     assert(offset + bytes <= s->image_size);
-@@ -1539,43 +1538,7 @@ static int coroutine_fn qemu_rbd_co_block_status(BlockDriverState *bs,
-         return status;
-     }
--#if LIBRBD_VERSION_CODE < LIBRBD_VERSION(1, 17, 0)
--    /*
--     * librbd had a bug until early 2022 that affected all versions of ceph that
--     * supported fast-diff. This bug results in reporting of incorrect offsets
--     * if the offset parameter to rbd_diff_iterate2 is not object aligned.
--     * Work around this bug by rounding down the offset to object boundaries.
--     * This is OK because we call rbd_diff_iterate2 with whole_object = true.
--     * However, this workaround only works for non cloned images with default
--     * striping.
--     *
--     * See: https://tracker.ceph.com/issues/53784
--     */
--
--    /* check if RBD image has non-default striping enabled */
--    if (features & RBD_FEATURE_STRIPINGV2) {
--        return status;
--    }
--
--#pragma GCC diagnostic push
--#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
--    /*
--     * check if RBD image is a clone (= has a parent).
--     *
--     * rbd_get_parent_info is deprecated from Nautilus onwards, but the
--     * replacement rbd_get_parent is not present in Luminous and Mimic.
--     */
--    if (rbd_get_parent_info(s->image, NULL, 0, NULL, 0, NULL, 0) != -ENOENT) {
--        return status;
--    }
--#pragma GCC diagnostic pop
--
--    head = req.offs & (s->object_size - 1);
--    req.offs -= head;
--    bytes += head;
--#endif
--
--    r = rbd_diff_iterate2(s->image, NULL, req.offs, bytes, true, true,
-+    r = rbd_diff_iterate2(s->image, NULL, offset, bytes, true, true,
-                           qemu_rbd_diff_iterate_cb, &req);
-     if (r < 0 && r != QEMU_RBD_EXIT_DIFF_ITERATE2) {
-         return status;
-@@ -1594,8 +1557,7 @@ static int coroutine_fn qemu_rbd_co_block_status(BlockDriverState *bs,
-         status = BDRV_BLOCK_ZERO | BDRV_BLOCK_OFFSET_VALID;
-     }
--    assert(req.bytes > head);
--    *pnum = req.bytes - head;
-+    *pnum = req.bytes;
-     return status;
- }
diff --git a/debian/patches/pve/0054-Revert-block-rbd-fix-handling-of-holes-in-.bdrv_co_b.patch b/debian/patches/pve/0054-Revert-block-rbd-fix-handling-of-holes-in-.bdrv_co_b.patch
deleted file mode 100644 (file)
index e5fad08..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fabian Ebner <f.ebner@proxmox.com>
-Date: Thu, 23 Jun 2022 14:00:07 +0200
-Subject: [PATCH] Revert "block/rbd: fix handling of holes in
- .bdrv_co_block_status"
-
-This reverts commit 9e302f64bb407a9bb097b626da97228c2654cfee in
-preparation to revert 0347a8fd4c3faaedf119be04c197804be40a384b.
-
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
----
- block/rbd.c | 10 +++++-----
- 1 file changed, 5 insertions(+), 5 deletions(-)
-
-diff --git a/block/rbd.c b/block/rbd.c
-index 53e0396b51..0913a0af39 100644
---- a/block/rbd.c
-+++ b/block/rbd.c
-@@ -1470,11 +1470,11 @@ static int qemu_rbd_diff_iterate_cb(uint64_t offs, size_t len,
-     RBDDiffIterateReq *req = opaque;
-     assert(req->offs + req->bytes <= offs);
--
--    /* treat a hole like an unallocated area and bail out */
--    if (!exists) {
--        return 0;
--    }
-+    /*
-+     * we do not diff against a snapshot so we should never receive a callback
-+     * for a hole.
-+     */
-+    assert(exists);
-     if (!req->exists && offs > req->offs) {
-         /*
diff --git a/debian/patches/pve/0055-Revert-block-rbd-implement-bdrv_co_block_status.patch b/debian/patches/pve/0055-Revert-block-rbd-implement-bdrv_co_block_status.patch
deleted file mode 100644 (file)
index 7e1ee52..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fabian Ebner <f.ebner@proxmox.com>
-Date: Tue, 17 May 2022 09:46:02 +0200
-Subject: [PATCH] Revert "block/rbd: implement bdrv_co_block_status"
-
-During backup, bdrv_co_block_status is called for each block copy
-chunk. When RBD is used, the current implementation with
-rbd_diff_iterate2() using whole_object=true takes about linearly more
-time, depending on the image size. Since there are linearly more
-chunks, the slowdown is quadratic, becoming unacceptable for large
-images (starting somewhere between 500-1000 GiB in my testing).
-
-This reverts commit 0347a8fd4c3faaedf119be04c197804be40a384b as a
-stop-gap measure, until it's clear how to make the implemenation
-more efficient.
-
-Upstream bug report:
-https://gitlab.com/qemu-project/qemu/-/issues/1026
-
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
----
- block/rbd.c | 112 ----------------------------------------------------
- 1 file changed, 112 deletions(-)
-
-diff --git a/block/rbd.c b/block/rbd.c
-index 0913a0af39..1dab254517 100644
---- a/block/rbd.c
-+++ b/block/rbd.c
-@@ -108,12 +108,6 @@ typedef struct RBDTask {
-     int64_t ret;
- } RBDTask;
--typedef struct RBDDiffIterateReq {
--    uint64_t offs;
--    uint64_t bytes;
--    bool exists;
--} RBDDiffIterateReq;
--
- static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
-                             BlockdevOptionsRbd *opts, bool cache,
-                             const char *keypairs, const char *secretid,
-@@ -1456,111 +1450,6 @@ static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
-     return spec_info;
- }
--/*
-- * rbd_diff_iterate2 allows to interrupt the exection by returning a negative
-- * value in the callback routine. Choose a value that does not conflict with
-- * an existing exitcode and return it if we want to prematurely stop the
-- * execution because we detected a change in the allocation status.
-- */
--#define QEMU_RBD_EXIT_DIFF_ITERATE2 -9000
--
--static int qemu_rbd_diff_iterate_cb(uint64_t offs, size_t len,
--                                    int exists, void *opaque)
--{
--    RBDDiffIterateReq *req = opaque;
--
--    assert(req->offs + req->bytes <= offs);
--    /*
--     * we do not diff against a snapshot so we should never receive a callback
--     * for a hole.
--     */
--    assert(exists);
--
--    if (!req->exists && offs > req->offs) {
--        /*
--         * we started in an unallocated area and hit the first allocated
--         * block. req->bytes must be set to the length of the unallocated area
--         * before the allocated area. stop further processing.
--         */
--        req->bytes = offs - req->offs;
--        return QEMU_RBD_EXIT_DIFF_ITERATE2;
--    }
--
--    if (req->exists && offs > req->offs + req->bytes) {
--        /*
--         * we started in an allocated area and jumped over an unallocated area,
--         * req->bytes contains the length of the allocated area before the
--         * unallocated area. stop further processing.
--         */
--        return QEMU_RBD_EXIT_DIFF_ITERATE2;
--    }
--
--    req->bytes += len;
--    req->exists = true;
--
--    return 0;
--}
--
--static int coroutine_fn qemu_rbd_co_block_status(BlockDriverState *bs,
--                                                 bool want_zero, int64_t offset,
--                                                 int64_t bytes, int64_t *pnum,
--                                                 int64_t *map,
--                                                 BlockDriverState **file)
--{
--    BDRVRBDState *s = bs->opaque;
--    int status, r;
--    RBDDiffIterateReq req = { .offs = offset };
--    uint64_t features, flags;
--
--    assert(offset + bytes <= s->image_size);
--
--    /* default to all sectors allocated */
--    status = BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID;
--    *map = offset;
--    *file = bs;
--    *pnum = bytes;
--
--    /* check if RBD image supports fast-diff */
--    r = rbd_get_features(s->image, &features);
--    if (r < 0) {
--        return status;
--    }
--    if (!(features & RBD_FEATURE_FAST_DIFF)) {
--        return status;
--    }
--
--    /* check if RBD fast-diff result is valid */
--    r = rbd_get_flags(s->image, &flags);
--    if (r < 0) {
--        return status;
--    }
--    if (flags & RBD_FLAG_FAST_DIFF_INVALID) {
--        return status;
--    }
--
--    r = rbd_diff_iterate2(s->image, NULL, offset, bytes, true, true,
--                          qemu_rbd_diff_iterate_cb, &req);
--    if (r < 0 && r != QEMU_RBD_EXIT_DIFF_ITERATE2) {
--        return status;
--    }
--    assert(req.bytes <= bytes);
--    if (!req.exists) {
--        if (r == 0) {
--            /*
--             * rbd_diff_iterate2 does not invoke callbacks for unallocated
--             * areas. This here catches the case where no callback was
--             * invoked at all (req.bytes == 0).
--             */
--            assert(req.bytes == 0);
--            req.bytes = bytes;
--        }
--        status = BDRV_BLOCK_ZERO | BDRV_BLOCK_OFFSET_VALID;
--    }
--
--    *pnum = req.bytes;
--    return status;
--}
--
- static int64_t coroutine_fn qemu_rbd_co_getlength(BlockDriverState *bs)
- {
-     BDRVRBDState *s = bs->opaque;
-@@ -1796,7 +1685,6 @@ static BlockDriver bdrv_rbd = {
- #ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
-     .bdrv_co_pwrite_zeroes  = qemu_rbd_co_pwrite_zeroes,
- #endif
--    .bdrv_co_block_status   = qemu_rbd_co_block_status,
-     .bdrv_snapshot_create   = qemu_rbd_snap_create,
-     .bdrv_snapshot_delete   = qemu_rbd_snap_remove,
diff --git a/debian/patches/pve/0056-PVE-Backup-create-jobs-correctly-cancel-in-error-sce.patch b/debian/patches/pve/0056-PVE-Backup-create-jobs-correctly-cancel-in-error-sce.patch
deleted file mode 100644 (file)
index 5dee746..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fabian Ebner <f.ebner@proxmox.com>
-Date: Wed, 25 May 2022 13:59:37 +0200
-Subject: [PATCH] PVE-Backup: create jobs: correctly cancel in error scenario
-
-The first call to job_cancel_sync() will cancel and free all jobs in
-the transaction, so ensure that it's called only once and get rid of
-the job_unref() that would operate on freed memory.
-
-It's also necessary to NULL backup_state.pbs in the error scenario,
-because a subsequent backup_cancel QMP call (as happens in PVE when
-the backup QMP command fails) would try to call proxmox_backup_abort()
-and run into a segfault.
-
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-[FE: adapt for new job lock mechanism replacing AioContext locks]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- pve-backup.c | 10 ++++++++--
- 1 file changed, 8 insertions(+), 2 deletions(-)
-
-diff --git a/pve-backup.c b/pve-backup.c
-index 08dfb9cbda..79d14d6a0b 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -505,6 +505,11 @@ static void create_backup_jobs_bh(void *opaque) {
-     }
-     if (*errp) {
-+        /*
-+         * It's enough to cancel one job in the transaction, the rest will
-+         * follow automatically.
-+         */
-+        bool canceled = false;
-         l = backup_state.di_list;
-         while (l) {
-             PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-@@ -515,11 +520,11 @@ static void create_backup_jobs_bh(void *opaque) {
-                 di->target = NULL;
-             }
--            if (di->job) {
-+            if (!canceled && di->job) {
-                 WITH_JOB_LOCK_GUARD() {
-                     job_cancel_sync_locked(&di->job->job, true);
--                    job_unref_locked(&di->job->job);
-                 }
-+                canceled = true;
-             }
-         }
-     }
-@@ -945,6 +950,7 @@ err:
-     if (pbs) {
-         proxmox_backup_disconnect(pbs);
-+        backup_state.pbs = NULL;
-     }
-     if (backup_dir) {
diff --git a/debian/patches/pve/0057-PVE-Backup-ensure-jobs-in-di_list-are-referenced.patch b/debian/patches/pve/0057-PVE-Backup-ensure-jobs-in-di_list-are-referenced.patch
deleted file mode 100644 (file)
index 4beed97..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fabian Ebner <f.ebner@proxmox.com>
-Date: Wed, 25 May 2022 13:59:38 +0200
-Subject: [PATCH] PVE-Backup: ensure jobs in di_list are referenced
-
-Ensures that qmp_backup_cancel doesn't pick a job that's already been
-freed. With unlucky timings it seems possible that:
-1. job_exit -> job_completed -> job_finalize_single starts
-2. pvebackup_co_complete_stream gets spawned in completion callback
-3. job finalize_single finishes -> job's refcount hits zero -> job is
-   freed
-4. qmp_backup_cancel comes in and locks backup_state.backup_mutex
-   before pvebackup_co_complete_stream can remove the job from the
-   di_list
-5. qmp_backup_cancel will pick a job that's already been freed
-
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-[FE: adapt for new job lock mechanism replacing AioContext locks]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- pve-backup.c | 22 +++++++++++++++++++---
- 1 file changed, 19 insertions(+), 3 deletions(-)
-
-diff --git a/pve-backup.c b/pve-backup.c
-index 79d14d6a0b..67e2b99d74 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -318,6 +318,13 @@ static void coroutine_fn pvebackup_co_complete_stream(void *opaque)
-         }
-     }
-+    if (di->job) {
-+        WITH_JOB_LOCK_GUARD() {
-+            job_unref_locked(&di->job->job);
-+            di->job = NULL;
-+        }
-+    }
-+
-     // remove self from job list
-     backup_state.di_list = g_list_remove(backup_state.di_list, di);
-@@ -493,6 +500,11 @@ static void create_backup_jobs_bh(void *opaque) {
-         aio_context_release(aio_context);
-         di->job = job;
-+        if (job) {
-+            WITH_JOB_LOCK_GUARD() {
-+                job_ref_locked(&job->job);
-+            }
-+        }
-         if (!job || local_err) {
-             error_setg(errp, "backup_job_create failed: %s",
-@@ -520,11 +532,15 @@ static void create_backup_jobs_bh(void *opaque) {
-                 di->target = NULL;
-             }
--            if (!canceled && di->job) {
-+            if (di->job) {
-                 WITH_JOB_LOCK_GUARD() {
--                    job_cancel_sync_locked(&di->job->job, true);
-+                    if (!canceled) {
-+                        job_cancel_sync_locked(&di->job->job, true);
-+                        canceled = true;
-+                    }
-+                    job_unref_locked(&di->job->job);
-+                    di->job = NULL;
-                 }
--                canceled = true;
-             }
-         }
-     }
diff --git a/debian/patches/pve/0058-PVE-Backup-avoid-segfault-issues-upon-backup-cancel.patch b/debian/patches/pve/0058-PVE-Backup-avoid-segfault-issues-upon-backup-cancel.patch
deleted file mode 100644 (file)
index 68c261e..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fabian Ebner <f.ebner@proxmox.com>
-Date: Wed, 25 May 2022 13:59:39 +0200
-Subject: [PATCH] PVE-Backup: avoid segfault issues upon backup-cancel
-
-When canceling a backup in PVE via a signal it's easy to run into a
-situation where the job is already failing when the backup_cancel QMP
-command comes in. With a bit of unlucky timing on top, it can happen
-that job_exit() runs between schedulung of job_cancel_bh() and
-execution of job_cancel_bh(). But job_cancel_sync() does not expect
-that the job is already finalized (in fact, the job might've been
-freed already, but even if it isn't, job_cancel_sync() would try to
-deref job->txn which would be NULL at that point).
-
-It is not possible to simply use the job_cancel() (which is advertised
-as being async but isn't in all cases) in qmp_backup_cancel() for the
-same reason job_cancel_sync() cannot be used. Namely, because it can
-invoke job_finish_sync() (which uses AIO_WAIT_WHILE and thus hangs if
-called from a coroutine). This happens when there's multiple jobs in
-the transaction and job->deferred_to_main_loop is true (is set before
-scheduling job_exit()) or if the job was not started yet.
-
-Fix the issue by selecting the job to cancel in job_cancel_bh() itself
-using the first job that's not completed yet. This is not necessarily
-the first job in the list, because pvebackup_co_complete_stream()
-might not yet have removed a completed job when job_cancel_bh() runs.
-
-An alternative would be to continue using only the first job and
-checking against JOB_STATUS_CONCLUDED or JOB_STATUS_NULL to decide if
-it's still necessary and possible to cancel, but the approach with
-using the first non-completed job seemed more robust.
-
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-[FE: adapt for new job lock mechanism replacing AioContext locks]
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- pve-backup.c | 57 ++++++++++++++++++++++++++++++++++------------------
- 1 file changed, 38 insertions(+), 19 deletions(-)
-
-diff --git a/pve-backup.c b/pve-backup.c
-index 67e2b99d74..7a8240363d 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -356,12 +356,41 @@ static void pvebackup_complete_cb(void *opaque, int ret)
- /*
-  * job_cancel(_sync) does not like to be called from coroutines, so defer to
-- * main loop processing via a bottom half.
-+ * main loop processing via a bottom half. Assumes that caller holds
-+ * backup_mutex.
-  */
- static void job_cancel_bh(void *opaque) {
-     CoCtxData *data = (CoCtxData*)opaque;
--    Job *job = (Job*)data->data;
--    job_cancel_sync(job, true);
-+
-+    /*
-+     * Be careful to pick a valid job to cancel:
-+     * 1. job_cancel_sync() does not expect the job to be finalized already.
-+     * 2. job_exit() might run between scheduling and running job_cancel_bh()
-+     *    and pvebackup_co_complete_stream() might not have removed the job from
-+     *    the list yet (in fact, cannot, because it waits for the backup_mutex).
-+     * Requiring !job_is_completed() ensures that no finalized job is picked.
-+     */
-+    GList *bdi = g_list_first(backup_state.di_list);
-+    while (bdi) {
-+        if (bdi->data) {
-+            BlockJob *bj = ((PVEBackupDevInfo *)bdi->data)->job;
-+            if (bj) {
-+                Job *job = &bj->job;
-+                WITH_JOB_LOCK_GUARD() {
-+                    if (!job_is_completed_locked(job)) {
-+                        job_cancel_sync_locked(job, true);
-+                        /*
-+                         * It's enough to cancel one job in the transaction, the
-+                         * rest will follow automatically.
-+                         */
-+                        break;
-+                    }
-+                }
-+            }
-+        }
-+        bdi = g_list_next(bdi);
-+    }
-+
-     aio_co_enter(data->ctx, data->co);
- }
-@@ -382,22 +411,12 @@ void coroutine_fn qmp_backup_cancel(Error **errp)
-         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;
--
--    if (cancel_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();
--    }
-+    CoCtxData data = {
-+        .ctx = qemu_get_current_aio_context(),
-+        .co = qemu_coroutine_self(),
-+    };
-+    aio_bh_schedule_oneshot(data.ctx, job_cancel_bh, &data);
-+    qemu_coroutine_yield();
-     qemu_co_mutex_unlock(&backup_state.backup_mutex);
- }
diff --git a/debian/patches/pve/0059-vma-create-support-64KiB-unaligned-input-images.patch b/debian/patches/pve/0059-vma-create-support-64KiB-unaligned-input-images.patch
deleted file mode 100644 (file)
index ffa8d0e..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fabian Ebner <f.ebner@proxmox.com>
-Date: Wed, 22 Jun 2022 10:45:11 +0200
-Subject: [PATCH] vma: create: support 64KiB-unaligned input images
-
-which fixes backing up templates with such disks in PVE, for example
-efitype=4m EFI disks on a file-based storage (size = 540672).
-
-If there is not enough left to read, blk_co_preadv will return -EIO,
-so limit the size in the last iteration.
-
-For writing, an unaligned end is already handled correctly.
-
-The call to memset is not strictly necessary, because writing also
-checks that it doesn't write data beyond the end of the image. But
-there are two reasons to do it:
-1. It's cleaner that way.
-2. It allows detecting when the final piece is all zeroes, which might
-   not happen if the buffer still contains data from the previous
-   iteration.
-
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
----
- vma.c | 12 ++++++++++--
- 1 file changed, 10 insertions(+), 2 deletions(-)
-
-diff --git a/vma.c b/vma.c
-index e6e9ffc7fe..304f02bc84 100644
---- a/vma.c
-+++ b/vma.c
-@@ -548,7 +548,7 @@ static void coroutine_fn backup_run(void *opaque)
-     struct iovec iov;
-     QEMUIOVector qiov;
--    int64_t start, end;
-+    int64_t start, end, readlen;
-     int ret = 0;
-     unsigned char *buf = blk_blockalign(job->target, VMA_CLUSTER_SIZE);
-@@ -562,8 +562,16 @@ static void coroutine_fn backup_run(void *opaque)
-         iov.iov_len = VMA_CLUSTER_SIZE;
-         qemu_iovec_init_external(&qiov, &iov, 1);
-+        if (start + 1 == end) {
-+            memset(buf, 0, VMA_CLUSTER_SIZE);
-+            readlen = job->len - start * VMA_CLUSTER_SIZE;
-+            assert(readlen > 0 && readlen <= VMA_CLUSTER_SIZE);
-+        } else {
-+            readlen = VMA_CLUSTER_SIZE;
-+        }
-+
-         ret = blk_co_preadv(job->target, start * VMA_CLUSTER_SIZE,
--                            VMA_CLUSTER_SIZE, &qiov, 0);
-+                            readlen, &qiov, 0);
-         if (ret < 0) {
-             vma_writer_set_error(job->vmaw, "read error", -1);
-             goto out;
diff --git a/debian/patches/pve/0060-vma-create-avoid-triggering-assertion-in-error-case.patch b/debian/patches/pve/0060-vma-create-avoid-triggering-assertion-in-error-case.patch
deleted file mode 100644 (file)
index c7961ae..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fabian Ebner <f.ebner@proxmox.com>
-Date: Wed, 22 Jun 2022 10:45:12 +0200
-Subject: [PATCH] vma: create: avoid triggering assertion in error case
-
-error_setg expects its argument to not be initialized yet.
-
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
----
- vma-writer.c | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/vma-writer.c b/vma-writer.c
-index df4b20793d..ac7da237d0 100644
---- a/vma-writer.c
-+++ b/vma-writer.c
-@@ -311,6 +311,8 @@ VmaWriter *vma_writer_create(const char *filename, uuid_t uuid, Error **errp)
-         }
-         if (vmaw->fd < 0) {
-+            error_free(*errp);
-+            *errp = NULL;
-             error_setg(errp, "can't open file %s - %s\n", filename,
-                        g_strerror(errno));
-             goto err;
diff --git a/debian/patches/pve/0061-block-alloc-track-avoid-premature-break.patch b/debian/patches/pve/0061-block-alloc-track-avoid-premature-break.patch
deleted file mode 100644 (file)
index a1c3d11..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fabian Ebner <f.ebner@proxmox.com>
-Date: Wed, 22 Jun 2022 10:45:13 +0200
-Subject: [PATCH] block: alloc-track: avoid premature break
-
-While the bdrv_co_preadv() calls are expected to return 0 on success,
-qemu_iovec_memset() will return the number of bytes set (will be
-local_bytes, because the slice with that size was just initialized).
-
-Don't break out of the loop after the branch with qemu_iovec_memset(),
-because there might still be work to do. Additionally, ret is an int,
-which on 64-bit platforms is too small to hold the size_t returned by
-qemu_iovec_memset().
-
-The branch seems to be difficult to reach in practice, because the
-whole point of alloc-track is to be used with a backing device.
-
-Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
----
- block/alloc-track.c | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/block/alloc-track.c b/block/alloc-track.c
-index 113bbd7058..b75d7c6460 100644
---- a/block/alloc-track.c
-+++ b/block/alloc-track.c
-@@ -175,7 +175,8 @@ static int coroutine_fn track_co_preadv(BlockDriverState *bs,
-             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);
-+            qemu_iovec_memset(&local_qiov, cur_offset, 0, local_bytes);
-+            ret = 0;
-         }
-         if (ret != 0) {
diff --git a/debian/patches/pve/0062-PVE-Backup-allow-passing-max-workers-performance-set.patch b/debian/patches/pve/0062-PVE-Backup-allow-passing-max-workers-performance-set.patch
deleted file mode 100644 (file)
index 8fd63c0..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fiona Ebner <f.ebner@proxmox.com>
-Date: Mon, 3 Oct 2022 15:52:04 +0200
-Subject: [PATCH] PVE Backup: allow passing max-workers performance setting
-
-For query-proxmox-support, add an indication that it's possible to use
-the setting.
-
-For now, the other two BackupPerf settings are not exposed:
-
-* use-copy-range: would need to be implemented by the backup-dump
-block driver first, and in fact, the default for backup was changed,
-because it wasn't as fast for backup in QEMU, see commit
-6a30f663d4c0b3c45a544d541e0c4e214b2473a1.
-
-* max-chunk: enforced to be at least the backup cluster size, which is
-4 MiB for PBS and otherwise maximum of source and target cluster size.
-And block-copy has a maximum buffer size of 1 MiB, so setting a larger
-max-chunk doesn't even have an effect. To make the setting sensibly
-usable the check would need to be removed and optionally the
-block-copy max buffer size would need to be bumped. I tried doing just
-that, and tested different source/target combinations with different
-max-chunk settings, but there were no noticable improvements over the
-default "unlimited" (resulting in 1 MiB for block-copy).
-
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- block/monitor/block-hmp-cmds.c |  4 +++-
- pve-backup.c                   | 18 +++++++++++++-----
- qapi/block-core.json           |  9 +++++++--
- 3 files changed, 23 insertions(+), 8 deletions(-)
-
-diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
-index ac23f21eef..636509b83e 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1063,7 +1063,9 @@ void coroutine_fn hmp_backup(Monitor *mon, const QDict *qdict)
-         false, false, // PBS encrypt
-         true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
-         NULL, NULL,
--        devlist, qdict_haskey(qdict, "speed"), speed, &error);
-+        devlist, qdict_haskey(qdict, "speed"), speed,
-+        false, 0, // BackupPerf max-workers
-+        &error);
-     hmp_handle_error(mon, error);
- }
-diff --git a/pve-backup.c b/pve-backup.c
-index 7a8240363d..cb5312fff3 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -57,6 +57,7 @@ static struct PVEBackupState {
-         bool starting;
-     } stat;
-     int64_t speed;
-+    BackupPerf perf;
-     VmaWriter *vmaw;
-     ProxmoxBackupHandle *pbs;
-     GList *di_list;
-@@ -492,8 +493,6 @@ static void create_backup_jobs_bh(void *opaque) {
-     }
-     backup_state.txn = job_txn_new_seq();
--    BackupPerf perf = { .max_workers = 16 };
--
-     /* create and start all jobs (paused state) */
-     GList *l =  backup_state.di_list;
-     while (l) {
-@@ -513,8 +512,9 @@ static void create_backup_jobs_bh(void *opaque) {
-         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, backup_state.txn, &local_err);
-+            bitmap_mode, false, NULL, &backup_state.perf, BLOCKDEV_ON_ERROR_REPORT,
-+            BLOCKDEV_ON_ERROR_REPORT, JOB_DEFAULT, pvebackup_complete_cb, di, backup_state.txn,
-+            &local_err);
-         aio_context_release(aio_context);
-@@ -585,7 +585,9 @@ UuidInfo coroutine_fn *qmp_backup(
-     const char *config_file,
-     const char *firewall_file,
-     const char *devlist,
--    bool has_speed, int64_t speed, Error **errp)
-+    bool has_speed, int64_t speed,
-+    bool has_max_workers, int64_t max_workers,
-+    Error **errp)
- {
-     assert(qemu_in_coroutine());
-@@ -915,6 +917,11 @@ UuidInfo coroutine_fn *qmp_backup(
-     backup_state.speed = (has_speed && speed > 0) ? speed : 0;
-+    backup_state.perf = (BackupPerf){ .max_workers = 16 };
-+    if (has_max_workers) {
-+        backup_state.perf.max_workers = max_workers;
-+    }
-+
-     backup_state.vmaw = vmaw;
-     backup_state.pbs = pbs;
-@@ -1086,5 +1093,6 @@ ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp)
-     ret->pbs_dirty_bitmap_migration = true;
-     ret->query_bitmap_info = true;
-     ret->pbs_masterkey = true;
-+    ret->backup_max_workers = true;
-     return ret;
- }
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index 9edeb33d82..809f3c61bc 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -924,6 +924,8 @@
- #
- # @encrypt: use encryption ((optional for format 'pbs', defaults to true if there is a keyfile)
- #
-+# @max-workers: see @BackupPerf for details. Default 16.
-+#
- # Returns: the uuid of the backup job
- #
- ##
-@@ -942,7 +944,9 @@
-                                     '*format': 'BackupFormat',
-                                     '*config-file': 'str',
-                                     '*firewall-file': 'str',
--                                    '*devlist': 'str', '*speed': 'int' },
-+                                    '*devlist': 'str',
-+                                    '*speed': 'int',
-+                                    '*max-workers': 'int' },
-   'returns': 'UuidInfo', 'coroutine': true }
- ##
-@@ -997,7 +1001,8 @@
-             'pbs-dirty-bitmap-savevm': 'bool',
-             'pbs-dirty-bitmap-migration': 'bool',
-             'pbs-masterkey': 'bool',
--            'pbs-library-version': 'str' } }
-+            'pbs-library-version': 'str',
-+            'backup-max-workers': 'bool' } }
- ##
- # @query-proxmox-support:
diff --git a/debian/patches/pve/0063-alloc-track-fix-deadlock-during-drop.patch b/debian/patches/pve/0063-alloc-track-fix-deadlock-during-drop.patch
deleted file mode 100644 (file)
index 8169445..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fiona Ebner <f.ebner@proxmox.com>
-Date: Thu, 6 Apr 2023 14:59:31 +0200
-Subject: [PATCH] alloc-track: fix deadlock during drop
-
-by replacing the block node directly after changing the backing file
-instead of rescheduling it.
-
-With changes in QEMU 8.0, calling bdrv_get_info (and bdrv_unref)
-during drop can lead to a deadlock when using iothread (only triggered
-with multiple disks, except during debugging where it also triggered
-with one disk sometimes):
-1. job_unref_locked acquires the AioContext and calls job->driver->free
-2. track_drop gets scheduled
-3. bdrv_graph_wrlock is called and polls which leads to track_drop being
-   called
-4. track_drop acquires the AioContext recursively
-5. bdrv_get_info is a wrapped coroutine (since 8.0) and thus polls for
-   bdrv_co_get_info. This releases the AioContext, but only once! The
-   documentation for the AIO_WAIT_WHILE macro states that the
-   AioContext lock needs to be acquired exactly once, but there does
-   not seem to be a way for track_drop to know if it acquired the lock
-   recursively or not (without adding further hacks).
-6. Because the AioContext is still held by the main thread once, it can't
-   be acquired before entering bdrv_co_get_info in co_schedule_bh_cb
-   which happens in the iothread
-
-When doing the operation in change_backing_file, the AioContext has
-already been acquired by the caller, so the issue with the recursive
-lock goes away.
-
-The comment explaining why delaying the replace is necessary is
-> 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'
-
-However, there is no check for blockers in bdrv_replace_node. It would
-need to be done by us, the caller, with check_to_replace_node.
-Furthermore, the mirror job also does its call to bdrv_replace_node
-while there is an active blocker (inserted by mirror itself) and they
-use a specialized version to check for blockers instead of
-check_to_replace_node there. Alloc-track could also do something
-similar to check for other blockers, but it should be fine to rely on
-Proxmox VE that no other operation with the blockdev is going on.
-
-Mirror also drains the target before replacing the node, but the
-target can have other users. In case of alloc-track the file child
-should not be accessible by anybody else and so there can't be an
-in-flight operation for the file child when alloc-track is drained.
-
-The rescheduling based on refcounting is a hack and it doesn't seem to
-be necessary anymore. It's not clear what the original issue from the
-comment was. Testing with older builds with track_drop done directly
-without rescheduling also didn't lead to any noticable issue for me.
-
-One issue it might have been is the one fixed by b1e1af394d
-("block/stream: Drain subtree around graph change"), where
-block-stream had a use-after-free if the base node changed at an
-inconvenient time (which alloc-track's auto-drop does).
-
-It's also not possible to just not auto-replace the alloc-track. Not
-replacing it at all leads to other operations like block resize
-hanging, and there is no good way to replace it manually via QMP
-(there is x-blockdev-change, but it is experimental and doesn't
-implement the required operation yet). Also, it's just cleaner in
-general to not leave unnecessary block nodes lying around.
-
-Suggested-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- block/alloc-track.c | 54 ++++++++++++++-------------------------------
- 1 file changed, 16 insertions(+), 38 deletions(-)
-
-diff --git a/block/alloc-track.c b/block/alloc-track.c
-index b75d7c6460..76da140a68 100644
---- a/block/alloc-track.c
-+++ b/block/alloc-track.c
-@@ -25,7 +25,6 @@
- typedef enum DropState {
-     DropNone,
--    DropRequested,
-     DropInProgress,
- } DropState;
-@@ -268,37 +267,6 @@ static void track_child_perm(BlockDriverState *bs, BdrvChild *c,
-     }
- }
--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)
-@@ -308,13 +276,23 @@ static int track_change_backing_file(BlockDriverState *bs,
-         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;
-+         * this node, so let's remove ourselves from the block graph */
-+        BlockDriverState *file = bs->file->bs;
-+
-+        /* Just to be sure, because bdrv_replace_node unrefs it */
-         bdrv_ref(bs);
--        aio_bh_schedule_oneshot(qemu_get_aio_context(), track_drop, (void*)bs);
-+        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);
-     }
-     return 0;
diff --git a/debian/patches/pve/0064-savevm-async-optimize-querying-pending.patch b/debian/patches/pve/0064-savevm-async-optimize-querying-pending.patch
deleted file mode 100644 (file)
index fb99782..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fiona Ebner <f.ebner@proxmox.com>
-Date: Fri, 31 Mar 2023 14:13:01 +0200
-Subject: [PATCH] savevm-async: optimize querying pending
-
-by using the estimate variant until the precopy estimate is below the
-threshold. This is similar to what is done in migration.c
-
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- migration/savevm-async.c | 15 +++++++++++----
- 1 file changed, 11 insertions(+), 4 deletions(-)
-
-diff --git a/migration/savevm-async.c b/migration/savevm-async.c
-index fab1dc32d2..de91506821 100644
---- a/migration/savevm-async.c
-+++ b/migration/savevm-async.c
-@@ -242,12 +242,19 @@ static void coroutine_fn process_savevm_co(void *opaque)
-     while (snap_state.state == SAVE_STATE_ACTIVE) {
-         uint64_t pending_size, pend_precopy, pend_postcopy;
-+        uint64_t threshold = 400 * 1000;
--        /* pending is expected to be called without iothread lock */
-+        /*
-+         * pending_{estimate,exact} are expected to be called without iothread
-+         * lock. Similar to what is done in migration.c, call the exact variant
-+         * only once pend_precopy in the estimate is below the threshold.
-+         */
-         qemu_mutex_unlock_iothread();
--        qemu_savevm_state_pending_exact(&pend_precopy, &pend_postcopy);
-+        qemu_savevm_state_pending_estimate(&pend_precopy, &pend_postcopy);
-+        if (pend_precopy <= threshold) {
-+            qemu_savevm_state_pending_exact(&pend_precopy, &pend_postcopy);
-+        }
-         qemu_mutex_lock_iothread();
--
-         pending_size = pend_precopy + pend_postcopy;
-         /*
-@@ -259,7 +266,7 @@ static void coroutine_fn process_savevm_co(void *opaque)
-         maxlen = blk_getlength(snap_state.target) - 100*1024*1024;
-         /* Note that there is no progress for pend_postcopy when iterating */
--        if (pending_size - pend_postcopy > 400000 && snap_state.bs_pos + pending_size < maxlen) {
-+        if (pend_precopy > threshold && snap_state.bs_pos + pending_size < maxlen) {
-             ret = qemu_savevm_state_iterate(snap_state.file, false);
-             if (ret < 0) {
-                 save_snapshot_error("qemu_savevm_state_iterate error %d", ret);
diff --git a/debian/patches/pve/0065-savevm-async-also-initialize-compression-counters.patch b/debian/patches/pve/0065-savevm-async-also-initialize-compression-counters.patch
deleted file mode 100644 (file)
index ae2b463..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fiona Ebner <f.ebner@proxmox.com>
-Date: Thu, 27 Apr 2023 10:28:08 +0200
-Subject: [PATCH] savevm-async: also initialize compression counters
-
-Like is done in the migration code. While the compression migration
-capability is not used by Proxmox VE currently, it's still worth to
-be prepared for the future.
-
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- migration/savevm-async.c | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/migration/savevm-async.c b/migration/savevm-async.c
-index de91506821..effe6d1e0d 100644
---- a/migration/savevm-async.c
-+++ b/migration/savevm-async.c
-@@ -394,6 +394,7 @@ void qmp_savevm_start(const char *statefile, Error **errp)
-      */
-     migrate_init(ms);
-     memset(&ram_counters, 0, sizeof(ram_counters));
-+    memset(&compression_counters, 0, sizeof(compression_counters));
-     ms->to_dst_file = snap_state.file;
-     error_setg(&snap_state.blocker, "block device is in use by savevm");
diff --git a/debian/patches/pve/0066-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch b/debian/patches/pve/0066-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch
deleted file mode 100644 (file)
index cbc39cc..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fiona Ebner <f.ebner@proxmox.com>
-Date: Fri, 5 May 2023 13:39:53 +0200
-Subject: [PATCH] migration: for snapshots, hold the BQL during setup callbacks
-
-In spirit, this is a partial revert of commit 9b09503752 ("migration:
-run setup callbacks out of big lock"), but only for the snapshot case.
-
-For snapshots, the bdrv_writev_vmstate() function is used during setup
-(in QIOChannelBlock backing the QEMUFile), but not holding the BQL
-while calling it could lead to an assertion failure. To understand
-how, first note the following:
-
-1. Generated coroutine wrappers for block layer functions spawn the
-coroutine and use AIO_WAIT_WHILE()/aio_poll() to wait for it.
-2. If the host OS switches threads at an inconvenient time, it can
-happen that a bottom half scheduled for the main thread's AioContext
-is executed as part of a vCPU thread's aio_poll().
-
-An example leading to the assertion failure is as follows:
-
-main thread:
-1. A snapshot-save QMP command gets issued.
-2. snapshot_save_job_bh() is scheduled.
-
-vCPU thread:
-3. aio_poll() for the main thread's AioContext is called (e.g. when
-the guest writes to a pflash device, as part of blk_pwrite which is a
-generated coroutine wrapper).
-4. snapshot_save_job_bh() is executed as part of aio_poll().
-3. qemu_savevm_state() is called.
-4. qemu_mutex_unlock_iothread() is called. Now
-qemu_get_current_aio_context() returns 0x0.
-5. bdrv_writev_vmstate() is executed during the usual savevm setup.
-But this function is a generated coroutine wrapper, so it uses
-AIO_WAIT_WHILE. There, the assertion
-assert(qemu_get_current_aio_context() == qemu_get_aio_context());
-will fail.
-
-To fix it, ensure that the BQL is held during setup. To avoid changing
-the behavior for migration too, introduce conditionals for the setup
-callbacks that need the BQL and only take the lock if it's not already
-held.
-
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- include/migration/register.h   |  2 +-
- migration/block-dirty-bitmap.c | 15 ++++++++++++---
- migration/block.c              | 15 ++++++++++++---
- migration/ram.c                | 16 +++++++++++++---
- migration/savevm.c             |  2 --
- 5 files changed, 38 insertions(+), 12 deletions(-)
-
-diff --git a/include/migration/register.h b/include/migration/register.h
-index a8dfd8fefd..fa9b0b0f10 100644
---- a/include/migration/register.h
-+++ b/include/migration/register.h
-@@ -43,9 +43,9 @@ typedef struct SaveVMHandlers {
-      * by other locks.
-      */
-     int (*save_live_iterate)(QEMUFile *f, void *opaque);
-+    int (*save_setup)(QEMUFile *f, void *opaque);
-     /* This runs outside the iothread lock!  */
--    int (*save_setup)(QEMUFile *f, void *opaque);
-     /* Note for save_live_pending:
-      * must_precopy:
-      * - must be migrated in precopy or in stopped state
-diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c
-index a6440929fa..69fab3275c 100644
---- a/migration/block-dirty-bitmap.c
-+++ b/migration/block-dirty-bitmap.c
-@@ -1214,10 +1214,17 @@ static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque)
- {
-     DBMSaveState *s = &((DBMState *)opaque)->save;
-     SaveBitmapState *dbms = NULL;
-+    bool release_lock = false;
--    qemu_mutex_lock_iothread();
-+    /* For snapshots, the BQL is held during setup. */
-+    if (!qemu_mutex_iothread_locked()) {
-+        qemu_mutex_lock_iothread();
-+        release_lock = true;
-+    }
-     if (init_dirty_bitmap_migration(s) < 0) {
--        qemu_mutex_unlock_iothread();
-+        if (release_lock) {
-+            qemu_mutex_unlock_iothread();
-+        }
-         return -1;
-     }
-@@ -1225,7 +1232,9 @@ static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque)
-         send_bitmap_start(f, s, dbms);
-     }
-     qemu_put_bitmap_flags(f, DIRTY_BITMAP_MIG_FLAG_EOS);
--    qemu_mutex_unlock_iothread();
-+    if (release_lock) {
-+        qemu_mutex_unlock_iothread();
-+    }
-     return 0;
- }
-diff --git a/migration/block.c b/migration/block.c
-index b2497bbd32..c9d55be642 100644
---- a/migration/block.c
-+++ b/migration/block.c
-@@ -716,21 +716,30 @@ static void block_migration_cleanup(void *opaque)
- static int block_save_setup(QEMUFile *f, void *opaque)
- {
-     int ret;
-+    bool release_lock = false;
-     trace_migration_block_save("setup", block_mig_state.submitted,
-                                block_mig_state.transferred);
--    qemu_mutex_lock_iothread();
-+    /* For snapshots, the BQL is held during setup. */
-+    if (!qemu_mutex_iothread_locked()) {
-+        qemu_mutex_lock_iothread();
-+        release_lock = true;
-+    }
-     ret = init_blk_migration(f);
-     if (ret < 0) {
--        qemu_mutex_unlock_iothread();
-+        if (release_lock) {
-+            qemu_mutex_unlock_iothread();
-+        }
-         return ret;
-     }
-     /* start track dirty blocks */
-     ret = set_dirty_tracking();
--    qemu_mutex_unlock_iothread();
-+    if (release_lock) {
-+        qemu_mutex_unlock_iothread();
-+    }
-     if (ret) {
-         return ret;
-diff --git a/migration/ram.c b/migration/ram.c
-index 79d881f735..0ecbbc3202 100644
---- a/migration/ram.c
-+++ b/migration/ram.c
-@@ -3117,8 +3117,16 @@ static void migration_bitmap_clear_discarded_pages(RAMState *rs)
- static void ram_init_bitmaps(RAMState *rs)
- {
--    /* For memory_global_dirty_log_start below.  */
--    qemu_mutex_lock_iothread();
-+    bool release_lock = false;
-+
-+    /*
-+     * For memory_global_dirty_log_start below.
-+     * For snapshots, the BQL is held during setup.
-+     */
-+    if (!qemu_mutex_iothread_locked()) {
-+        qemu_mutex_lock_iothread();
-+        release_lock = true;
-+    }
-     qemu_mutex_lock_ramlist();
-     WITH_RCU_READ_LOCK_GUARD() {
-@@ -3130,7 +3138,9 @@ static void ram_init_bitmaps(RAMState *rs)
-         }
-     }
-     qemu_mutex_unlock_ramlist();
--    qemu_mutex_unlock_iothread();
-+    if (release_lock) {
-+        qemu_mutex_unlock_iothread();
-+    }
-     /*
-      * After an eventual first bitmap sync, fixup the initial bitmap
-diff --git a/migration/savevm.c b/migration/savevm.c
-index aa54a67fda..fc6a82a555 100644
---- a/migration/savevm.c
-+++ b/migration/savevm.c
-@@ -1621,10 +1621,8 @@ static int qemu_savevm_state(QEMUFile *f, Error **errp)
-     memset(&compression_counters, 0, sizeof(compression_counters));
-     ms->to_dst_file = f;
--    qemu_mutex_unlock_iothread();
-     qemu_savevm_state_header(f);
-     qemu_savevm_state_setup(f);
--    qemu_mutex_lock_iothread();
-     while (qemu_file_get_error(f) == 0) {
-         if (qemu_savevm_state_iterate(f, false) > 0) {
diff --git a/debian/patches/pve/0067-savevm-async-don-t-hold-BQL-during-setup.patch b/debian/patches/pve/0067-savevm-async-don-t-hold-BQL-during-setup.patch
deleted file mode 100644 (file)
index ce5e14e..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Fiona Ebner <f.ebner@proxmox.com>
-Date: Fri, 5 May 2023 15:30:16 +0200
-Subject: [PATCH] savevm-async: don't hold BQL during setup
-
-See commit "migration: for snapshots, hold the BQL during setup
-callbacks" for why. This is separate, because a version of that one
-will hopefully land upstream.
-
-Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
----
- migration/savevm-async.c | 2 --
- 1 file changed, 2 deletions(-)
-
-diff --git a/migration/savevm-async.c b/migration/savevm-async.c
-index effe6d1e0d..f006a8e4d4 100644
---- a/migration/savevm-async.c
-+++ b/migration/savevm-async.c
-@@ -403,10 +403,8 @@ void qmp_savevm_start(const char *statefile, Error **errp)
-     snap_state.state = SAVE_STATE_ACTIVE;
-     snap_state.finalize_bh = qemu_bh_new(process_savevm_finalize, &snap_state);
-     snap_state.co = qemu_coroutine_create(&process_savevm_co, NULL);
--    qemu_mutex_unlock_iothread();
-     qemu_savevm_state_header(snap_state.file);
-     qemu_savevm_state_setup(snap_state.file);
--    qemu_mutex_lock_iothread();
-     /* Async processing from here on out happens in iohandler context, so let
-      * the target bdrv have its home there.
index 90d794324541a346f5a2442030e66bc155fb47b9..375b2dbfffeb9a28112fad47a606e3944b097387 100644 (file)
@@ -36,58 +36,37 @@ pve/0009-PVE-Up-qemu-img-return-success-on-info-without-snaps.patch
 pve/0010-PVE-Up-qemu-img-dd-add-osize-and-read-from-to-stdin-.patch
 pve/0011-PVE-Up-qemu-img-dd-add-isize-parameter.patch
 pve/0012-PVE-Up-qemu-img-dd-add-n-skip_create.patch
-pve/0013-PVE-virtio-balloon-improve-query-balloon.patch
-pve/0014-PVE-qapi-modify-query-machines.patch
-pve/0015-PVE-qapi-modify-spice-query.patch
-pve/0016-PVE-add-IOChannel-implementation-for-savevm-async.patch
-pve/0017-PVE-add-savevm-async-for-background-state-snapshots.patch
-pve/0018-PVE-add-optional-buffer-size-to-QEMUFile.patch
-pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch
-pve/0020-PVE-Add-dummy-id-command-line-parameter.patch
-pve/0021-PVE-Config-Revert-target-i386-disable-LINT0-after-re.patch
-pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch
-pve/0023-PVE-monitor-disable-oob-capability.patch
-pve/0024-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch
-pve/0025-PVE-Allow-version-code-in-machine-type.patch
-pve/0026-block-backup-move-bcs-bitmap-initialization-to-job-c.patch
-pve/0027-PVE-Backup-add-vma-backup-format-code.patch
-pve/0028-PVE-Backup-add-backup-dump-block-driver.patch
-pve/0029-PVE-Backup-proxmox-backup-patches-for-qemu.patch
-pve/0030-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch
-pve/0031-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch
-pve/0032-PVE-various-PBS-fixes.patch
+pve/0013-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch
+pve/0014-PVE-virtio-balloon-improve-query-balloon.patch
+pve/0015-PVE-qapi-modify-query-machines.patch
+pve/0016-PVE-qapi-modify-spice-query.patch
+pve/0017-PVE-add-IOChannel-implementation-for-savevm-async.patch
+pve/0018-PVE-add-savevm-async-for-background-state-snapshots.patch
+pve/0019-PVE-add-optional-buffer-size-to-QEMUFile.patch
+pve/0020-PVE-block-add-the-zeroinit-block-driver-filter.patch
+pve/0021-PVE-Add-dummy-id-command-line-parameter.patch
+pve/0022-PVE-Config-Revert-target-i386-disable-LINT0-after-re.patch
+pve/0023-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch
+pve/0024-PVE-monitor-disable-oob-capability.patch
+pve/0025-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch
+pve/0026-PVE-Allow-version-code-in-machine-type.patch
+pve/0027-block-backup-move-bcs-bitmap-initialization-to-job-c.patch
+pve/0028-PVE-Backup-add-vma-backup-format-code.patch
+pve/0029-PVE-Backup-add-backup-dump-block-driver.patch
+pve/0030-PVE-Add-sequential-job-transaction-support.patch
+pve/0031-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch
+pve/0032-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch
 pve/0033-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch
-pve/0034-PVE-add-query_proxmox_support-QMP-command.patch
-pve/0035-PVE-add-query-pbs-bitmap-info-QMP-call.patch
-pve/0036-PVE-redirect-stderr-to-journal-when-daemonized.patch
-pve/0037-PVE-Add-sequential-job-transaction-support.patch
-pve/0038-PVE-Backup-Use-a-transaction-to-synchronize-job-stat.patch
-pve/0039-PVE-Backup-Don-t-block-on-finishing-and-cleanup-crea.patch
-pve/0040-PVE-Migrate-dirty-bitmap-state-via-savevm.patch
-pve/0041-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch
-pve/0042-PVE-fall-back-to-open-iscsi-initiatorname.patch
-pve/0043-PVE-Use-coroutine-QMP-for-backup-cancel_backup.patch
-pve/0044-PBS-add-master-key-support.patch
-pve/0045-PVE-block-pbs-fast-path-reads-without-allocation-if-.patch
-pve/0046-PVE-block-stream-increase-chunk-size.patch
-pve/0047-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch
-pve/0048-block-add-alloc-track-driver.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
-pve/0051-vma-allow-partial-restore.patch
-pve/0052-pbs-namespace-support.patch
-pve/0053-Revert-block-rbd-workaround-for-ceph-issue-53784.patch
-pve/0054-Revert-block-rbd-fix-handling-of-holes-in-.bdrv_co_b.patch
-pve/0055-Revert-block-rbd-implement-bdrv_co_block_status.patch
-pve/0056-PVE-Backup-create-jobs-correctly-cancel-in-error-sce.patch
-pve/0057-PVE-Backup-ensure-jobs-in-di_list-are-referenced.patch
-pve/0058-PVE-Backup-avoid-segfault-issues-upon-backup-cancel.patch
-pve/0059-vma-create-support-64KiB-unaligned-input-images.patch
-pve/0060-vma-create-avoid-triggering-assertion-in-error-case.patch
-pve/0061-block-alloc-track-avoid-premature-break.patch
-pve/0062-PVE-Backup-allow-passing-max-workers-performance-set.patch
-pve/0063-alloc-track-fix-deadlock-during-drop.patch
-pve/0064-savevm-async-optimize-querying-pending.patch
-pve/0065-savevm-async-also-initialize-compression-counters.patch
-pve/0066-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch
-pve/0067-savevm-async-don-t-hold-BQL-during-setup.patch
+pve/0034-PVE-redirect-stderr-to-journal-when-daemonized.patch
+pve/0035-PVE-Migrate-dirty-bitmap-state-via-savevm.patch
+pve/0036-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch
+pve/0037-PVE-fall-back-to-open-iscsi-initiatorname.patch
+pve/0038-PVE-block-stream-increase-chunk-size.patch
+pve/0039-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch
+pve/0040-block-add-alloc-track-driver.patch
+pve/0041-Revert-block-rbd-workaround-for-ceph-issue-53784.patch
+pve/0042-Revert-block-rbd-fix-handling-of-holes-in-.bdrv_co_b.patch
+pve/0043-Revert-block-rbd-implement-bdrv_co_block_status.patch
+pve/0044-alloc-track-fix-deadlock-during-drop.patch
+pve/0045-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch
+pve/0046-savevm-async-don-t-hold-BQL-during-setup.patch