Subject: [PATCH] PVE: [Up] qmp: add get_link_status
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[FE: add get_link_status to command name exceptions]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
net/net.c | 27 +++++++++++++++++++++++++++
qapi/net.json | 15 +++++++++++++++
- qapi/pragma.json | 1 +
- 3 files changed, 43 insertions(+)
+ qapi/pragma.json | 2 ++
+ 3 files changed, 44 insertions(+)
diff --git a/net/net.c b/net/net.c
index 840ad9dca5..28e97c5d85 100644
# @netdev_add:
#
diff --git a/qapi/pragma.json b/qapi/pragma.json
-index 7f810b0e97..a2358e303a 100644
+index 7f810b0e97..29233db825 100644
--- a/qapi/pragma.json
+++ b/qapi/pragma.json
-@@ -26,6 +26,7 @@
+@@ -15,6 +15,7 @@
+ 'device_add',
+ 'device_del',
+ 'expire_password',
++ 'get_link_status',
+ 'migrate_cancel',
+ 'netdev_add',
+ 'netdev_del',
+@@ -26,6 +27,7 @@
'system_wakeup' ],
# Commands allowed to return a non-dictionary
'command-returns-exceptions': [
command.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+[FE: add BalloonInfo to member name exceptions list]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
hw/virtio/virtio-balloon.c | 33 +++++++++++++++++++++++++++++++--
monitor/hmp-cmds.c | 30 +++++++++++++++++++++++++++++-
qapi/machine.json | 22 +++++++++++++++++++++-
- 3 files changed, 81 insertions(+), 4 deletions(-)
+ qapi/pragma.json | 1 +
+ 4 files changed, 82 insertions(+), 4 deletions(-)
diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
index 73ac5eb675..bbfe7eca62 100644
##
# @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
configure | 9 ++
meson.build | 2 +-
qapi/block-core.json | 13 ++
- 5 files changed, 302 insertions(+), 1 deletion(-)
+ qapi/pragma.json | 1 +
+ 6 files changed, 303 insertions(+), 1 deletion(-)
create mode 100644 block/pbs.c
diff --git a/block/meson.build b/block/meson.build
'nvme': 'BlockdevOptionsNVMe',
'nvme-io_uring': { 'type': 'BlockdevOptionsNvmeIoUring',
'if': 'CONFIG_BLKIO' },
+diff --git a/qapi/pragma.json b/qapi/pragma.json
+index f2097b9020..5ab1890519 100644
+--- a/qapi/pragma.json
++++ b/qapi/pragma.json
+@@ -47,6 +47,7 @@
+ 'BlockInfo', # query-block
+ 'BlockdevAioOptions', # blockdev-add, -blockdev
+ 'BlockdevDriver', # blockdev-add, query-blockstats, ...
++ 'BlockdevOptionsPbs', # for PBS backwards compat
+ 'BlockdevVmdkAdapterType', # blockdev-create (to match VMDK spec)
+ 'BlockdevVmdkSubformat', # blockdev-create (to match VMDK spec)
+ 'ColoCompareProperties', # object_add, -object
--- /dev/null
+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 bafe6ae5eb..da3634048f 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 */
+
+@@ -514,6 +515,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");
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Wed, 26 May 2021 15:26:30 +0200
-Subject: [PATCH] PVE: whitelist 'invalid' QAPI names for backwards compat
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- qapi/pragma.json | 5 ++++-
- 1 file changed, 4 insertions(+), 1 deletion(-)
-
-diff --git a/qapi/pragma.json b/qapi/pragma.json
-index a2358e303a..9ff5c84ffd 100644
---- a/qapi/pragma.json
-+++ b/qapi/pragma.json
-@@ -15,6 +15,7 @@
- 'device_add',
- 'device_del',
- 'expire_password',
-+ 'get_link_status',
- 'migrate_cancel',
- 'netdev_add',
- 'netdev_del',
-@@ -64,6 +65,8 @@
- 'SysEmuTarget', # query-cpu-fast, query-target
- 'UuidInfo', # query-uuid
- 'VncClientInfo', # query-vnc, query-vnc-servers, ...
-- 'X86CPURegister32' # qom-get of x86 CPU properties
-+ 'X86CPURegister32', # qom-get of x86 CPU properties
- # feature-words, filtered-features
-+ 'BlockdevOptionsPbs', # for PBS backwards compat
-+ 'BalloonInfo'
- ] } }
+++ /dev/null
-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 bafe6ae5eb..da3634048f 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 */
-
-@@ -514,6 +515,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");
--- /dev/null
+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 59c403373b..065a54cc42 100644
+--- a/qemu-img.c
++++ b/qemu-img.c
+@@ -4946,6 +4946,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;
+@@ -4955,6 +4956,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,
+@@ -4992,7 +4994,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;
+ }
+@@ -5015,6 +5017,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;
+@@ -5074,11 +5089,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) {
+@@ -5233,6 +5261,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);
+++ /dev/null
-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 59c403373b..065a54cc42 100644
---- a/qemu-img.c
-+++ b/qemu-img.c
-@@ -4946,6 +4946,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;
-@@ -4955,6 +4956,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,
-@@ -4992,7 +4994,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;
- }
-@@ -5015,6 +5017,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;
-@@ -5074,11 +5089,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) {
-@@ -5233,6 +5261,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);
--- /dev/null
+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);
--- /dev/null
+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>
+---
+ 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 c7468e5d3b..57b2457f1e 100644
+--- a/block/monitor/block-hmp-cmds.c
++++ b/block/monitor/block-hmp-cmds.c
+@@ -1041,6 +1041,7 @@ void coroutine_fn hmp_backup(Monitor *mon, const QDict *qdict)
+ false, NULL, // PBS key_password
+ false, NULL, // PBS master_keyfile
+ false, NULL, // PBS fingerprint
++ false, NULL, // PBS backup-ns
+ false, 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 ce9a870885..9192f3e41b 100644
+--- a/block/pbs.c
++++ b/block/pbs.c
+@@ -14,6 +14,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"
+@@ -27,6 +28,7 @@ typedef struct {
+ int64_t length;
+
+ char *repository;
++ char *namespace;
+ char *snapshot;
+ char *archive;
+ } BDRVPBSState;
+@@ -40,6 +42,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,
+@@ -76,7 +83,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)
+ {
+@@ -112,6 +119,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);
+
+@@ -124,9 +132,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 */
+@@ -171,6 +182,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);
+@@ -252,8 +264,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 4b5134ed27..262e7d3894 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -10,6 +10,8 @@
+ #include "qapi/qmp/qerror.h"
+ #include "qemu/cutils.h"
+
++#include <proxmox-backup-qemu.h>
++
+ /* PVE backup state and related function */
+
+ /*
+@@ -531,6 +533,7 @@ UuidInfo coroutine_fn *qmp_backup(
+ bool has_key_password, const char *key_password,
+ bool has_master_keyfile, const char *master_keyfile,
+ bool has_fingerprint, const char *fingerprint,
++ bool has_backup_ns, const char *backup_ns,
+ bool has_backup_id, const char *backup_id,
+ bool has_backup_time, int64_t backup_time,
+ bool has_use_dirty_bitmap, bool use_dirty_bitmap,
+@@ -670,8 +673,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,
++ has_backup_ns ? backup_ns : NULL,
+ backup_id,
+ backup_time,
+ dump_cb_block_size,
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index d8c7331090..889726fc26 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -817,6 +817,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')
+@@ -836,6 +838,7 @@
+ '*key-password': 'str',
+ '*master-keyfile': 'str',
+ '*fingerprint': 'str',
++ '*backup-ns': 'str',
+ '*backup-id': 'str',
+ '*backup-time': 'int',
+ '*use-dirty-bitmap': 'bool',
+@@ -3290,7 +3293,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:
+++ /dev/null
-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);
--- /dev/null
+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 64a8d7d48b..9fc6dcb957 100644
+--- a/block/rbd.c
++++ b/block/rbd.c
+@@ -1348,7 +1348,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);
+
+@@ -1376,43 +1375,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;
+@@ -1431,8 +1394,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;
+ }
+
+++ /dev/null
-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>
----
- 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 c7468e5d3b..57b2457f1e 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1041,6 +1041,7 @@ void coroutine_fn hmp_backup(Monitor *mon, const QDict *qdict)
- false, NULL, // PBS key_password
- false, NULL, // PBS master_keyfile
- false, NULL, // PBS fingerprint
-+ false, NULL, // PBS backup-ns
- false, 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 ce9a870885..9192f3e41b 100644
---- a/block/pbs.c
-+++ b/block/pbs.c
-@@ -14,6 +14,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"
-@@ -27,6 +28,7 @@ typedef struct {
- int64_t length;
-
- char *repository;
-+ char *namespace;
- char *snapshot;
- char *archive;
- } BDRVPBSState;
-@@ -40,6 +42,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,
-@@ -76,7 +83,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)
- {
-@@ -112,6 +119,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);
-
-@@ -124,9 +132,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 */
-@@ -171,6 +182,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);
-@@ -252,8 +264,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 4b5134ed27..262e7d3894 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -10,6 +10,8 @@
- #include "qapi/qmp/qerror.h"
- #include "qemu/cutils.h"
-
-+#include <proxmox-backup-qemu.h>
-+
- /* PVE backup state and related function */
-
- /*
-@@ -531,6 +533,7 @@ UuidInfo coroutine_fn *qmp_backup(
- bool has_key_password, const char *key_password,
- bool has_master_keyfile, const char *master_keyfile,
- bool has_fingerprint, const char *fingerprint,
-+ bool has_backup_ns, const char *backup_ns,
- bool has_backup_id, const char *backup_id,
- bool has_backup_time, int64_t backup_time,
- bool has_use_dirty_bitmap, bool use_dirty_bitmap,
-@@ -670,8 +673,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,
-+ has_backup_ns ? backup_ns : NULL,
- backup_id,
- backup_time,
- dump_cb_block_size,
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index d8c7331090..889726fc26 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -817,6 +817,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')
-@@ -836,6 +838,7 @@
- '*key-password': 'str',
- '*master-keyfile': 'str',
- '*fingerprint': 'str',
-+ '*backup-ns': 'str',
- '*backup-id': 'str',
- '*backup-time': 'int',
- '*use-dirty-bitmap': 'bool',
-@@ -3290,7 +3293,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:
--- /dev/null
+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 9fc6dcb957..98f4ba2620 100644
+--- a/block/rbd.c
++++ b/block/rbd.c
+@@ -1307,11 +1307,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) {
+ /*
+++ /dev/null
-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 64a8d7d48b..9fc6dcb957 100644
---- a/block/rbd.c
-+++ b/block/rbd.c
-@@ -1348,7 +1348,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);
-
-@@ -1376,43 +1375,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;
-@@ -1431,8 +1394,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;
- }
-
+++ /dev/null
-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 9fc6dcb957..98f4ba2620 100644
---- a/block/rbd.c
-+++ b/block/rbd.c
-@@ -1307,11 +1307,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) {
- /*
--- /dev/null
+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 98f4ba2620..efcbbe5949 100644
+--- a/block/rbd.c
++++ b/block/rbd.c
+@@ -97,12 +97,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,
+@@ -1293,111 +1287,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 qemu_rbd_getlength(BlockDriverState *bs)
+ {
+ BDRVRBDState *s = bs->opaque;
+@@ -1633,7 +1522,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,
--- /dev/null
+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 262e7d3894..fde3554133 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -503,6 +503,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;
+@@ -513,11 +518,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;
+ }
+ }
+ }
+@@ -943,6 +948,7 @@ err:
+
+ if (pbs) {
+ proxmox_backup_disconnect(pbs);
++ backup_state.pbs = NULL;
+ }
+
+ if (backup_dir) {
+++ /dev/null
-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 98f4ba2620..efcbbe5949 100644
---- a/block/rbd.c
-+++ b/block/rbd.c
-@@ -97,12 +97,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,
-@@ -1293,111 +1287,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 qemu_rbd_getlength(BlockDriverState *bs)
- {
- BDRVRBDState *s = bs->opaque;
-@@ -1633,7 +1522,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,
+++ /dev/null
-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 262e7d3894..fde3554133 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -503,6 +503,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;
-@@ -513,11 +518,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;
- }
- }
- }
-@@ -943,6 +948,7 @@ err:
-
- if (pbs) {
- proxmox_backup_disconnect(pbs);
-+ backup_state.pbs = NULL;
- }
-
- if (backup_dir) {
--- /dev/null
+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 fde3554133..0cf30e1ced 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -316,6 +316,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);
+
+@@ -491,6 +498,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",
+@@ -518,11 +530,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;
+ }
+ }
+ }
--- /dev/null
+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 0cf30e1ced..4067018dbe 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -354,12 +354,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);
+ }
+
+@@ -380,22 +409,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);
+ }
+++ /dev/null
-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 fde3554133..0cf30e1ced 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -316,6 +316,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);
-
-@@ -491,6 +498,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",
-@@ -518,11 +530,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;
- }
- }
- }
+++ /dev/null
-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 0cf30e1ced..4067018dbe 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -354,12 +354,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);
- }
-
-@@ -380,22 +409,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);
- }
--- /dev/null
+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;
--- /dev/null
+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;
+++ /dev/null
-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;
--- /dev/null
+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 43d40d11af..95c9c67cd8 100644
+--- a/block/alloc-track.c
++++ b/block/alloc-track.c
+@@ -174,7 +174,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) {
+++ /dev/null
-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;
--- /dev/null
+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 57b2457f1e..ab0c988ae9 100644
+--- a/block/monitor/block-hmp-cmds.c
++++ b/block/monitor/block-hmp-cmds.c
+@@ -1049,7 +1049,9 @@ void coroutine_fn hmp_backup(Monitor *mon, const QDict *qdict)
+ false, false, // PBS encrypt
+ true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
+ false, NULL, false, NULL, !!devlist,
+- 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 4067018dbe..3ca4f74cb8 100644
+--- a/pve-backup.c
++++ b/pve-backup.c
+@@ -55,6 +55,7 @@ static struct PVEBackupState {
+ bool starting;
+ } stat;
+ int64_t speed;
++ BackupPerf perf;
+ VmaWriter *vmaw;
+ ProxmoxBackupHandle *pbs;
+ GList *di_list;
+@@ -490,8 +491,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) {
+@@ -511,8 +510,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);
+
+@@ -583,7 +583,9 @@ UuidInfo coroutine_fn *qmp_backup(
+ bool has_config_file, const char *config_file,
+ bool has_firewall_file, const char *firewall_file,
+ bool has_devlist, const char *devlist,
+- bool has_speed, int64_t speed, Error **errp)
++ bool has_speed, int64_t speed,
++ bool has_max_workers, int64_t max_workers,
++ Error **errp)
+ {
+ assert(qemu_in_coroutine());
+
+@@ -913,6 +915,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;
+
+@@ -1088,5 +1095,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 889726fc26..65795b7204 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -829,6 +829,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
+ #
+ ##
+@@ -847,7 +849,9 @@
+ '*format': 'BackupFormat',
+ '*config-file': 'str',
+ '*firewall-file': 'str',
+- '*devlist': 'str', '*speed': 'int' },
++ '*devlist': 'str',
++ '*speed': 'int',
++ '*max-workers': 'int' },
+ 'returns': 'UuidInfo', 'coroutine': true }
+
+ ##
+@@ -902,7 +906,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:
+++ /dev/null
-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 43d40d11af..95c9c67cd8 100644
---- a/block/alloc-track.c
-+++ b/block/alloc-track.c
-@@ -174,7 +174,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) {
+++ /dev/null
-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 57b2457f1e..ab0c988ae9 100644
---- a/block/monitor/block-hmp-cmds.c
-+++ b/block/monitor/block-hmp-cmds.c
-@@ -1049,7 +1049,9 @@ void coroutine_fn hmp_backup(Monitor *mon, const QDict *qdict)
- false, false, // PBS encrypt
- true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
- false, NULL, false, NULL, !!devlist,
-- 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 4067018dbe..3ca4f74cb8 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -55,6 +55,7 @@ static struct PVEBackupState {
- bool starting;
- } stat;
- int64_t speed;
-+ BackupPerf perf;
- VmaWriter *vmaw;
- ProxmoxBackupHandle *pbs;
- GList *di_list;
-@@ -490,8 +491,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) {
-@@ -511,8 +510,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);
-
-@@ -583,7 +583,9 @@ UuidInfo coroutine_fn *qmp_backup(
- bool has_config_file, const char *config_file,
- bool has_firewall_file, const char *firewall_file,
- bool has_devlist, const char *devlist,
-- bool has_speed, int64_t speed, Error **errp)
-+ bool has_speed, int64_t speed,
-+ bool has_max_workers, int64_t max_workers,
-+ Error **errp)
- {
- assert(qemu_in_coroutine());
-
-@@ -913,6 +915,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;
-
-@@ -1088,5 +1095,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 889726fc26..65795b7204 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -829,6 +829,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
- #
- ##
-@@ -847,7 +849,9 @@
- '*format': 'BackupFormat',
- '*config-file': 'str',
- '*firewall-file': 'str',
-- '*devlist': 'str', '*speed': 'int' },
-+ '*devlist': 'str',
-+ '*speed': 'int',
-+ '*max-workers': 'int' },
- 'returns': 'UuidInfo', 'coroutine': true }
-
- ##
-@@ -902,7 +906,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:
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-whitelist-invalid-QAPI-names-for-backwards-compa.patch
-pve/0050-PVE-savevm-async-register-yank-before-migration_inco.patch
-pve/0051-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch
-pve/0052-vma-allow-partial-restore.patch
-pve/0053-pbs-namespace-support.patch
-pve/0054-Revert-block-rbd-workaround-for-ceph-issue-53784.patch
-pve/0055-Revert-block-rbd-fix-handling-of-holes-in-.bdrv_co_b.patch
-pve/0056-Revert-block-rbd-implement-bdrv_co_block_status.patch
-pve/0057-PVE-Backup-create-jobs-correctly-cancel-in-error-sce.patch
-pve/0058-PVE-Backup-ensure-jobs-in-di_list-are-referenced.patch
-pve/0059-PVE-Backup-avoid-segfault-issues-upon-backup-cancel.patch
-pve/0060-vma-create-support-64KiB-unaligned-input-images.patch
-pve/0061-vma-create-avoid-triggering-assertion-in-error-case.patch
-pve/0062-block-alloc-track-avoid-premature-break.patch
-pve/0063-PVE-Backup-allow-passing-max-workers-performance-set.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