# also update debian/changelog
-KVMVER=2.9.1
-KVMPKGREL=9
+KVMVER=2.11.1
+KVMPKGREL=1
KVMPACKAGE = pve-qemu-kvm
KVMSRC = qemu
.PHONY: deb kvm
deb kvm: $(DEBS)
$(DEB_DBG): $(DEB)
-$(DEB): | submodule
+$(DEB): keycodemapdb | submodule
rm -f *.deb
rm -rf $(BUILDSRC)
mkdir $(BUILDSRC)
cp -a $(KVMSRC)/* $(BUILDSRC)/
cp -a debian $(BUILDSRC)/debian
+ rm -rf $(BUILDSRC)/ui/keycodemapdb
+ cp -a keycodemapdb $(BUILDSRC)/ui/
echo "git clone git://git.proxmox.com/git/pve-qemu-kvm.git\\ngit checkout $(GITVERSION)" > $(BUILDSRC)/debian/SOURCE
# set package version
sed -i 's/^pkgversion="".*/pkgversion="${KVMPACKAGE}_${KVMVER}-${KVMPKGREL}"/' $(BUILDSRC)/configure
cd $(BUILDSRC); dpkg-buildpackage -b -rfakeroot -us -uc
lintian $(DEBS) || true
+.PHONY: update
+update:
+ cd $(KVMSRC) && git submodule deinit ui/keycodemapdb || true
+ rm -rf $(KVMSRC)/ui/keycodemapdb
+ mkdir $(KVMSRC)/ui/keycodemapdb
+ cd $(KVMSRC) && git submodule update --init ui/keycodemapdb
+ rm -rf keycodemapdb
+ mkdir keycodemapdb
+ cp -R $(KVMSRC)/ui/keycodemapdb/* keycodemapdb/
+ git add keycodemapdb
+
.PHONY: upload
upload: $(DEBS)
tar cf - ${DEBS} | ssh repoman@repo.proxmox.com upload --product pve --dist stretch
+pve-qemu-kvm (2.11.1-1) stable; urgency=medium
+
+ * update to 2.11.1
+
+ -- Proxmox Support Team <support@proxmox.com> Thu, 22 Feb 2018 12:34:43 +0100
+
pve-qemu-kvm (2.9.1-9) stable; urgency=medium
* add EPYC and EPYC-IPBP cpu models
1 file changed, 9 insertions(+)
diff --git a/hw/intc/apic_common.c b/hw/intc/apic_common.c
-index 1ef56f8d10..31fb73e9fb 100644
+index 78903ea909..cdfbec5e47 100644
--- a/hw/intc/apic_common.c
+++ b/hw/intc/apic_common.c
@@ -257,6 +257,15 @@ static void apic_reset_common(DeviceState *dev)
--- /dev/null
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Tue, 6 Feb 2018 11:34:34 +0100
+Subject: [PATCH] ratelimit: don't align wait time with slices
+
+It is possible for rate limited writes to keep overshooting a slice's
+quota by a tiny amount causing the slice-aligned waiting period to
+effectively halve the rate.
+
+Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
+---
+ include/qemu/ratelimit.h | 11 +++++------
+ 1 file changed, 5 insertions(+), 6 deletions(-)
+
+diff --git a/include/qemu/ratelimit.h b/include/qemu/ratelimit.h
+index 8dece483f5..1b38291823 100644
+--- a/include/qemu/ratelimit.h
++++ b/include/qemu/ratelimit.h
+@@ -36,7 +36,7 @@ typedef struct {
+ static inline int64_t ratelimit_calculate_delay(RateLimit *limit, uint64_t n)
+ {
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
+- uint64_t delay_slices;
++ double delay_slices;
+
+ assert(limit->slice_quota && limit->slice_ns);
+
+@@ -55,12 +55,11 @@ static inline int64_t ratelimit_calculate_delay(RateLimit *limit, uint64_t n)
+ return 0;
+ }
+
+- /* Quota exceeded. Calculate the next time slice we may start
+- * sending data again. */
+- delay_slices = (limit->dispatched + limit->slice_quota - 1) /
+- limit->slice_quota;
++ /* Quota exceeded. Wait based on the excess amount and then start a new
++ * slice. */
++ delay_slices = (double)limit->dispatched / limit->slice_quota;
+ limit->slice_end_time = limit->slice_start_time +
+- delay_slices * limit->slice_ns;
++ (uint64_t)(delay_slices * limit->slice_ns);
+ return limit->slice_end_time - now;
+ }
+
+--
+2.11.0
+
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Hajnoczi <stefanha@redhat.com>
-Date: Fri, 2 Jun 2017 10:54:24 +0100
-Subject: [PATCH] virtio-serial: fix segfault on disconnect
-
-Since commit d4c19cdeeb2f1e474bc426a6da261f1d7346eb5b ("virtio-serial:
-add missing virtio_detach_element() call") the following commands may
-cause QEMU to segfault:
-
- $ qemu -M accel=kvm -cpu host -m 1G \
- -drive if=virtio,file=test.img,format=raw \
- -device virtio-serial-pci,id=virtio-serial0 \
- -chardev socket,id=channel1,path=/tmp/chardev.sock,server,nowait \
- -device virtserialport,chardev=channel1,bus=virtio-serial0.0,id=port1
- $ nc -U /tmp/chardev.sock
- ^C
-
- (guest)$ cat /dev/zero >/dev/vport0p1
-
-The segfault is non-deterministic: if the event loop notices the socket
-has been closed then there is no crash. The disconnect has to happen
-right before QEMU attempts to write data to the socket.
-
-The backtrace is as follows:
-
- Thread 1 "qemu-system-x86" received signal SIGSEGV, Segmentation fault.
- 0x00005555557e0698 in do_flush_queued_data (port=0x5555582cedf0, vq=0x7fffcc854290, vdev=0x55555807b1d0) at hw/char/virtio-serial-bus.c:180
- 180 for (i = port->iov_idx; i < port->elem->out_num; i++) {
- #1 0x000055555580d363 in virtio_queue_notify_vq (vq=0x7fffcc854290) at hw/virtio/virtio.c:1524
- #2 0x000055555580d363 in virtio_queue_host_notifier_read (n=0x7fffcc8542f8) at hw/virtio/virtio.c:2430
- #3 0x0000555555b3482c in aio_dispatch_handlers (ctx=ctx@entry=0x5555566b8c80) at util/aio-posix.c:399
- #4 0x0000555555b350d8 in aio_dispatch (ctx=0x5555566b8c80) at util/aio-posix.c:430
- #5 0x0000555555b3212e in aio_ctx_dispatch (source=<optimized out>, callback=<optimized out>, user_data=<optimized out>) at util/async.c:261
- #6 0x00007fffde71de52 in g_main_context_dispatch () at /lib64/libglib-2.0.so.0
- #7 0x0000555555b34353 in glib_pollfds_poll () at util/main-loop.c:213
- #8 0x0000555555b34353 in os_host_main_loop_wait (timeout=<optimized out>) at util/main-loop.c:261
- #9 0x0000555555b34353 in main_loop_wait (nonblocking=<optimized out>) at util/main-loop.c:517
- #10 0x0000555555773207 in main_loop () at vl.c:1917
- #11 0x0000555555773207 in main (argc=<optimized out>, argv=<optimized out>, envp=<optimized out>) at vl.c:4751
-
-The do_flush_queued_data() function does not anticipate chardev close
-events during vsc->have_data(). It expects port->elem to remain
-non-NULL for the duration its for loop.
-
-The fix is simply to return from do_flush_queued_data() if the port
-closes because the close event already frees port->elem and drains the
-virtqueue - there is nothing left for do_flush_queued_data() to do.
-
-Reported-by: Sitong Liu <siliu@redhat.com>
-Reported-by: Min Deng <mdeng@redhat.com>
-Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
-Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
-Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
----
- hw/char/virtio-serial-bus.c | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/hw/char/virtio-serial-bus.c b/hw/char/virtio-serial-bus.c
-index aa9c11ae92..f5bc173844 100644
---- a/hw/char/virtio-serial-bus.c
-+++ b/hw/char/virtio-serial-bus.c
-@@ -186,6 +186,9 @@ static void do_flush_queued_data(VirtIOSerialPort *port, VirtQueue *vq,
- port->elem->out_sg[i].iov_base
- + port->iov_offset,
- buf_size);
-+ if (!port->elem) { /* bail if we got disconnected */
-+ return;
-+ }
- if (port->throttled) {
- port->iov_idx = i;
- if (ret > 0) {
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Paolo Bonzini <pbonzini@redhat.com>
-Date: Thu, 1 Jun 2017 17:26:14 +0200
-Subject: [PATCH] megasas: always store SCSIRequest* into MegasasCmd
-
-This ensures that the request is unref'ed properly, and avoids a
-segmentation fault in the new qtest testcase that is added.
-This is CVE-2017-9503.
-
-Reported-by: Zhangyanyu <zyy4013@stu.ouc.edu.cn>
-Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
----
- hw/scsi/megasas.c | 31 ++++++++++++++++---------------
- 1 file changed, 16 insertions(+), 15 deletions(-)
-
-diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c
-index 135662df31..734fdaef90 100644
---- a/hw/scsi/megasas.c
-+++ b/hw/scsi/megasas.c
-@@ -609,6 +609,9 @@ static void megasas_reset_frames(MegasasState *s)
- static void megasas_abort_command(MegasasCmd *cmd)
- {
- /* Never abort internal commands. */
-+ if (cmd->dcmd_opcode != -1) {
-+ return;
-+ }
- if (cmd->req != NULL) {
- scsi_req_cancel(cmd->req);
- }
-@@ -1017,7 +1020,6 @@ static int megasas_pd_get_info_submit(SCSIDevice *sdev, int lun,
- uint64_t pd_size;
- uint16_t pd_id = ((sdev->id & 0xFF) << 8) | (lun & 0xFF);
- uint8_t cmdbuf[6];
-- SCSIRequest *req;
- size_t len, resid;
-
- if (!cmd->iov_buf) {
-@@ -1026,8 +1028,8 @@ static int megasas_pd_get_info_submit(SCSIDevice *sdev, int lun,
- info->inquiry_data[0] = 0x7f; /* Force PQual 0x3, PType 0x1f */
- info->vpd_page83[0] = 0x7f;
- megasas_setup_inquiry(cmdbuf, 0, sizeof(info->inquiry_data));
-- req = scsi_req_new(sdev, cmd->index, lun, cmdbuf, cmd);
-- if (!req) {
-+ cmd->req = scsi_req_new(sdev, cmd->index, lun, cmdbuf, cmd);
-+ if (!cmd->req) {
- trace_megasas_dcmd_req_alloc_failed(cmd->index,
- "PD get info std inquiry");
- g_free(cmd->iov_buf);
-@@ -1036,26 +1038,26 @@ static int megasas_pd_get_info_submit(SCSIDevice *sdev, int lun,
- }
- trace_megasas_dcmd_internal_submit(cmd->index,
- "PD get info std inquiry", lun);
-- len = scsi_req_enqueue(req);
-+ len = scsi_req_enqueue(cmd->req);
- if (len > 0) {
- cmd->iov_size = len;
-- scsi_req_continue(req);
-+ scsi_req_continue(cmd->req);
- }
- return MFI_STAT_INVALID_STATUS;
- } else if (info->inquiry_data[0] != 0x7f && info->vpd_page83[0] == 0x7f) {
- megasas_setup_inquiry(cmdbuf, 0x83, sizeof(info->vpd_page83));
-- req = scsi_req_new(sdev, cmd->index, lun, cmdbuf, cmd);
-- if (!req) {
-+ cmd->req = scsi_req_new(sdev, cmd->index, lun, cmdbuf, cmd);
-+ if (!cmd->req) {
- trace_megasas_dcmd_req_alloc_failed(cmd->index,
- "PD get info vpd inquiry");
- return MFI_STAT_FLASH_ALLOC_FAIL;
- }
- trace_megasas_dcmd_internal_submit(cmd->index,
- "PD get info vpd inquiry", lun);
-- len = scsi_req_enqueue(req);
-+ len = scsi_req_enqueue(cmd->req);
- if (len > 0) {
- cmd->iov_size = len;
-- scsi_req_continue(req);
-+ scsi_req_continue(cmd->req);
- }
- return MFI_STAT_INVALID_STATUS;
- }
-@@ -1217,7 +1219,6 @@ static int megasas_ld_get_info_submit(SCSIDevice *sdev, int lun,
- struct mfi_ld_info *info = cmd->iov_buf;
- size_t dcmd_size = sizeof(struct mfi_ld_info);
- uint8_t cdb[6];
-- SCSIRequest *req;
- ssize_t len, resid;
- uint16_t sdev_id = ((sdev->id & 0xFF) << 8) | (lun & 0xFF);
- uint64_t ld_size;
-@@ -1226,8 +1227,8 @@ static int megasas_ld_get_info_submit(SCSIDevice *sdev, int lun,
- cmd->iov_buf = g_malloc0(dcmd_size);
- info = cmd->iov_buf;
- megasas_setup_inquiry(cdb, 0x83, sizeof(info->vpd_page83));
-- req = scsi_req_new(sdev, cmd->index, lun, cdb, cmd);
-- if (!req) {
-+ cmd->req = scsi_req_new(sdev, cmd->index, lun, cdb, cmd);
-+ if (!cmd->req) {
- trace_megasas_dcmd_req_alloc_failed(cmd->index,
- "LD get info vpd inquiry");
- g_free(cmd->iov_buf);
-@@ -1236,10 +1237,10 @@ static int megasas_ld_get_info_submit(SCSIDevice *sdev, int lun,
- }
- trace_megasas_dcmd_internal_submit(cmd->index,
- "LD get info vpd inquiry", lun);
-- len = scsi_req_enqueue(req);
-+ len = scsi_req_enqueue(cmd->req);
- if (len > 0) {
- cmd->iov_size = len;
-- scsi_req_continue(req);
-+ scsi_req_continue(cmd->req);
- }
- return MFI_STAT_INVALID_STATUS;
- }
-@@ -1851,7 +1852,7 @@ static void megasas_command_complete(SCSIRequest *req, uint32_t status,
- return;
- }
-
-- if (cmd->req == NULL) {
-+ if (cmd->dcmd_opcode != -1) {
- /*
- * Internal command complete
- */
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Prasad J Pandit <pjp@fedoraproject.org>
-Date: Mon, 17 Jul 2017 17:33:26 +0530
-Subject: [PATCH] slirp: check len against dhcp options array end
-
-While parsing dhcp options string in 'dhcp_decode', if an options'
-length 'len' appeared towards the end of 'bp_vend' array, ensuing
-read could lead to an OOB memory access issue. Add check to avoid it.
-
-This is CVE-2017-11434.
-
-Reported-by: Reno Robert <renorobert@gmail.com>
-Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
-Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
----
- slirp/bootp.c | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/slirp/bootp.c b/slirp/bootp.c
-index 5a4646c182..5dd1a415b5 100644
---- a/slirp/bootp.c
-+++ b/slirp/bootp.c
-@@ -123,6 +123,9 @@ static void dhcp_decode(const struct bootp_t *bp, int *pmsg_type,
- if (p >= p_end)
- break;
- len = *p++;
-+ if (p + len > p_end) {
-+ break;
-+ }
- DPRINTF("dhcp: tag=%d len=%d\n", tag, len);
-
- switch(tag) {
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Hajnoczi <stefanha@redhat.com>
-Date: Wed, 9 Aug 2017 17:02:11 +0100
-Subject: [PATCH] IDE: Do not flush empty CDROM drives
-
-The block backend changed in a way that flushing empty CDROM drives now
-crashes. Amend IDE to avoid doing so until the root problem can be
-addressed for 2.11.
-
-Original patch by John Snow <jsnow@redhat.com>.
-
-Reported-by: Kieron Shorrock <kshorrock@paloaltonetworks.com>
-Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
-Reviewed-by: Eric Blake <eblake@redhat.com>
-Message-id: 20170809160212.29976-2-stefanha@redhat.com
-Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
----
- hw/ide/core.c | 10 +++++++++-
- 1 file changed, 9 insertions(+), 1 deletion(-)
-
-diff --git a/hw/ide/core.c b/hw/ide/core.c
-index 0b48b64d3a..bea39536b0 100644
---- a/hw/ide/core.c
-+++ b/hw/ide/core.c
-@@ -1063,7 +1063,15 @@ static void ide_flush_cache(IDEState *s)
- s->status |= BUSY_STAT;
- ide_set_retry(s);
- block_acct_start(blk_get_stats(s->blk), &s->acct, 0, BLOCK_ACCT_FLUSH);
-- s->pio_aiocb = blk_aio_flush(s->blk, ide_flush_cb, s);
-+
-+ if (blk_bs(s->blk)) {
-+ s->pio_aiocb = blk_aio_flush(s->blk, ide_flush_cb, s);
-+ } else {
-+ /* XXX blk_aio_flush() crashes when blk_bs(blk) is NULL, remove this
-+ * temporary workaround when blk_aio_*() functions handle NULL blk_bs.
-+ */
-+ ide_flush_cb(s, 0);
-+ }
- }
-
- static void ide_cfata_metadata_inquiry(IDEState *s)
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Gerd Hoffmann <kraxel@redhat.com>
-Date: Fri, 21 Apr 2017 11:16:24 +0200
-Subject: [PATCH] bitmap: add bitmap_copy_and_clear_atomic
-
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
-Message-id: 20170421091632.30900-2-kraxel@redhat.com
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
----
- include/qemu/bitmap.h | 2 ++
- util/bitmap.c | 11 +++++++++++
- 2 files changed, 13 insertions(+)
-
-diff --git a/include/qemu/bitmap.h b/include/qemu/bitmap.h
-index 63ea2d0b1e..c318da12d7 100644
---- a/include/qemu/bitmap.h
-+++ b/include/qemu/bitmap.h
-@@ -220,6 +220,8 @@ void bitmap_set(unsigned long *map, long i, long len);
- void bitmap_set_atomic(unsigned long *map, long i, long len);
- void bitmap_clear(unsigned long *map, long start, long nr);
- bool bitmap_test_and_clear_atomic(unsigned long *map, long start, long nr);
-+void bitmap_copy_and_clear_atomic(unsigned long *dst, unsigned long *src,
-+ long nr);
- unsigned long bitmap_find_next_zero_area(unsigned long *map,
- unsigned long size,
- unsigned long start,
-diff --git a/util/bitmap.c b/util/bitmap.c
-index c1a84ca5e3..efced9a7d8 100644
---- a/util/bitmap.c
-+++ b/util/bitmap.c
-@@ -287,6 +287,17 @@ bool bitmap_test_and_clear_atomic(unsigned long *map, long start, long nr)
- return dirty != 0;
- }
-
-+void bitmap_copy_and_clear_atomic(unsigned long *dst, unsigned long *src,
-+ long nr)
-+{
-+ while (nr > 0) {
-+ *dst = atomic_xchg(src, 0);
-+ dst++;
-+ src++;
-+ nr -= BITS_PER_LONG;
-+ }
-+}
-+
- #define ALIGN_MASK(x,mask) (((x)+(mask))&~(mask))
-
- /**
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Gerd Hoffmann <kraxel@redhat.com>
-Date: Fri, 21 Apr 2017 11:16:25 +0200
-Subject: [PATCH] memory: add support getting and using a dirty bitmap copy.
-
-This patch adds support for getting and using a local copy of the dirty
-bitmap.
-
-memory_region_snapshot_and_clear_dirty() will create a snapshot of the
-dirty bitmap for the specified range, clear the dirty bitmap and return
-the copy. The returned bitmap can be a bit larger than requested, the
-range is expanded so the code can copy unsigned longs from the bitmap
-and avoid atomic bit update operations.
-
-memory_region_snapshot_get_dirty() will return the dirty status of
-pages, pretty much like memory_region_get_dirty(), but using the copy
-returned by memory_region_copy_and_clear_dirty().
-
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
-Message-id: 20170421091632.30900-3-kraxel@redhat.com
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
----
- exec.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++
- include/exec/memory.h | 47 +++++++++++++++++++++++++++++++
- include/exec/ram_addr.h | 7 +++++
- include/qemu/typedefs.h | 1 +
- memory.c | 17 +++++++++++
- 5 files changed, 147 insertions(+)
-
-diff --git a/exec.c b/exec.c
-index fcb5b16131..07c2c8ea88 100644
---- a/exec.c
-+++ b/exec.c
-@@ -223,6 +223,12 @@ struct CPUAddressSpace {
- MemoryListener tcg_as_listener;
- };
-
-+struct DirtyBitmapSnapshot {
-+ ram_addr_t start;
-+ ram_addr_t end;
-+ unsigned long dirty[];
-+};
-+
- #endif
-
- #if !defined(CONFIG_USER_ONLY)
-@@ -1061,6 +1067,75 @@ bool cpu_physical_memory_test_and_clear_dirty(ram_addr_t start,
- return dirty;
- }
-
-+DirtyBitmapSnapshot *cpu_physical_memory_snapshot_and_clear_dirty
-+ (ram_addr_t start, ram_addr_t length, unsigned client)
-+{
-+ DirtyMemoryBlocks *blocks;
-+ unsigned long align = 1UL << (TARGET_PAGE_BITS + BITS_PER_LEVEL);
-+ ram_addr_t first = QEMU_ALIGN_DOWN(start, align);
-+ ram_addr_t last = QEMU_ALIGN_UP(start + length, align);
-+ DirtyBitmapSnapshot *snap;
-+ unsigned long page, end, dest;
-+
-+ snap = g_malloc0(sizeof(*snap) +
-+ ((last - first) >> (TARGET_PAGE_BITS + 3)));
-+ snap->start = first;
-+ snap->end = last;
-+
-+ page = first >> TARGET_PAGE_BITS;
-+ end = last >> TARGET_PAGE_BITS;
-+ dest = 0;
-+
-+ rcu_read_lock();
-+
-+ blocks = atomic_rcu_read(&ram_list.dirty_memory[client]);
-+
-+ while (page < end) {
-+ unsigned long idx = page / DIRTY_MEMORY_BLOCK_SIZE;
-+ unsigned long offset = page % DIRTY_MEMORY_BLOCK_SIZE;
-+ unsigned long num = MIN(end - page, DIRTY_MEMORY_BLOCK_SIZE - offset);
-+
-+ assert(QEMU_IS_ALIGNED(offset, (1 << BITS_PER_LEVEL)));
-+ assert(QEMU_IS_ALIGNED(num, (1 << BITS_PER_LEVEL)));
-+ offset >>= BITS_PER_LEVEL;
-+
-+ bitmap_copy_and_clear_atomic(snap->dirty + dest,
-+ blocks->blocks[idx] + offset,
-+ num);
-+ page += num;
-+ dest += num >> BITS_PER_LEVEL;
-+ }
-+
-+ rcu_read_unlock();
-+
-+ if (tcg_enabled()) {
-+ tlb_reset_dirty_range_all(start, length);
-+ }
-+
-+ return snap;
-+}
-+
-+bool cpu_physical_memory_snapshot_get_dirty(DirtyBitmapSnapshot *snap,
-+ ram_addr_t start,
-+ ram_addr_t length)
-+{
-+ unsigned long page, end;
-+
-+ assert(start >= snap->start);
-+ assert(start + length <= snap->end);
-+
-+ end = TARGET_PAGE_ALIGN(start + length - snap->start) >> TARGET_PAGE_BITS;
-+ page = (start - snap->start) >> TARGET_PAGE_BITS;
-+
-+ while (page < end) {
-+ if (test_bit(page, snap->dirty)) {
-+ return true;
-+ }
-+ page++;
-+ }
-+ return false;
-+}
-+
- /* Called from RCU critical section */
- hwaddr memory_region_section_get_iotlb(CPUState *cpu,
- MemoryRegionSection *section,
-diff --git a/include/exec/memory.h b/include/exec/memory.h
-index f20b191793..1e15e79d00 100644
---- a/include/exec/memory.h
-+++ b/include/exec/memory.h
-@@ -871,6 +871,53 @@ void memory_region_set_dirty(MemoryRegion *mr, hwaddr addr,
- */
- bool memory_region_test_and_clear_dirty(MemoryRegion *mr, hwaddr addr,
- hwaddr size, unsigned client);
-+
-+/**
-+ * memory_region_snapshot_and_clear_dirty: Get a snapshot of the dirty
-+ * bitmap and clear it.
-+ *
-+ * Creates a snapshot of the dirty bitmap, clears the dirty bitmap and
-+ * returns the snapshot. The snapshot can then be used to query dirty
-+ * status, using memory_region_snapshot_get_dirty. Unlike
-+ * memory_region_test_and_clear_dirty this allows to query the same
-+ * page multiple times, which is especially useful for display updates
-+ * where the scanlines often are not page aligned.
-+ *
-+ * The dirty bitmap region which gets copyed into the snapshot (and
-+ * cleared afterwards) can be larger than requested. The boundaries
-+ * are rounded up/down so complete bitmap longs (covering 64 pages on
-+ * 64bit hosts) can be copied over into the bitmap snapshot. Which
-+ * isn't a problem for display updates as the extra pages are outside
-+ * the visible area, and in case the visible area changes a full
-+ * display redraw is due anyway. Should other use cases for this
-+ * function emerge we might have to revisit this implementation
-+ * detail.
-+ *
-+ * Use g_free to release DirtyBitmapSnapshot.
-+ *
-+ * @mr: the memory region being queried.
-+ * @addr: the address (relative to the start of the region) being queried.
-+ * @size: the size of the range being queried.
-+ * @client: the user of the logging information; typically %DIRTY_MEMORY_VGA.
-+ */
-+DirtyBitmapSnapshot *memory_region_snapshot_and_clear_dirty(MemoryRegion *mr,
-+ hwaddr addr,
-+ hwaddr size,
-+ unsigned client);
-+
-+/**
-+ * memory_region_snapshot_get_dirty: Check whether a range of bytes is dirty
-+ * in the specified dirty bitmap snapshot.
-+ *
-+ * @mr: the memory region being queried.
-+ * @snap: the dirty bitmap snapshot
-+ * @addr: the address (relative to the start of the region) being queried.
-+ * @size: the size of the range being queried.
-+ */
-+bool memory_region_snapshot_get_dirty(MemoryRegion *mr,
-+ DirtyBitmapSnapshot *snap,
-+ hwaddr addr, hwaddr size);
-+
- /**
- * memory_region_sync_dirty_bitmap: Synchronize a region's dirty bitmap with
- * any external TLBs (e.g. kvm)
-diff --git a/include/exec/ram_addr.h b/include/exec/ram_addr.h
-index b05dc84ab9..2b63d7f59e 100644
---- a/include/exec/ram_addr.h
-+++ b/include/exec/ram_addr.h
-@@ -343,6 +343,13 @@ bool cpu_physical_memory_test_and_clear_dirty(ram_addr_t start,
- ram_addr_t length,
- unsigned client);
-
-+DirtyBitmapSnapshot *cpu_physical_memory_snapshot_and_clear_dirty
-+ (ram_addr_t start, ram_addr_t length, unsigned client);
-+
-+bool cpu_physical_memory_snapshot_get_dirty(DirtyBitmapSnapshot *snap,
-+ ram_addr_t start,
-+ ram_addr_t length);
-+
- static inline void cpu_physical_memory_clear_dirty_range(ram_addr_t start,
- ram_addr_t length)
- {
-diff --git a/include/qemu/typedefs.h b/include/qemu/typedefs.h
-index e95f28cfec..f08d327aec 100644
---- a/include/qemu/typedefs.h
-+++ b/include/qemu/typedefs.h
-@@ -23,6 +23,7 @@ typedef struct CPUAddressSpace CPUAddressSpace;
- typedef struct CPUState CPUState;
- typedef struct DeviceListener DeviceListener;
- typedef struct DeviceState DeviceState;
-+typedef struct DirtyBitmapSnapshot DirtyBitmapSnapshot;
- typedef struct DisplayChangeListener DisplayChangeListener;
- typedef struct DisplayState DisplayState;
- typedef struct DisplaySurface DisplaySurface;
-diff --git a/memory.c b/memory.c
-index 4c95aaf39c..8a0648551f 100644
---- a/memory.c
-+++ b/memory.c
-@@ -1716,6 +1716,23 @@ bool memory_region_test_and_clear_dirty(MemoryRegion *mr, hwaddr addr,
- memory_region_get_ram_addr(mr) + addr, size, client);
- }
-
-+DirtyBitmapSnapshot *memory_region_snapshot_and_clear_dirty(MemoryRegion *mr,
-+ hwaddr addr,
-+ hwaddr size,
-+ unsigned client)
-+{
-+ assert(mr->ram_block);
-+ return cpu_physical_memory_snapshot_and_clear_dirty(
-+ memory_region_get_ram_addr(mr) + addr, size, client);
-+}
-+
-+bool memory_region_snapshot_get_dirty(MemoryRegion *mr, DirtyBitmapSnapshot *snap,
-+ hwaddr addr, hwaddr size)
-+{
-+ assert(mr->ram_block);
-+ return cpu_physical_memory_snapshot_get_dirty(snap,
-+ memory_region_get_ram_addr(mr) + addr, size);
-+}
-
- void memory_region_sync_dirty_bitmap(MemoryRegion *mr)
- {
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Gerd Hoffmann <kraxel@redhat.com>
-Date: Fri, 21 Apr 2017 11:16:26 +0200
-Subject: [PATCH] vga: add vga_scanline_invalidated helper
-
-Add vga_scanline_invalidated helper to check whenever a scanline was
-invalidated. Add a sanity check to fix OOB read access for display
-heights larger than 2048.
-
-Only cirrus uses this, for hardware cursor rendering, so having this
-work properly for the first 2048 scanlines only shouldn't be a problem
-as the cirrus can't handle large resolutions anyway. Also changing the
-invalidated_y_table size would break live migration.
-
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
-Message-id: 20170421091632.30900-4-kraxel@redhat.com
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
----
- hw/display/vga.c | 14 +++++++++++---
- 1 file changed, 11 insertions(+), 3 deletions(-)
-
-diff --git a/hw/display/vga.c b/hw/display/vga.c
-index 69c3e1d674..3991b88aac 100644
---- a/hw/display/vga.c
-+++ b/hw/display/vga.c
-@@ -1434,6 +1434,14 @@ void vga_invalidate_scanlines(VGACommonState *s, int y1, int y2)
- }
- }
-
-+static bool vga_scanline_invalidated(VGACommonState *s, int y)
-+{
-+ if (y >= VGA_MAX_HEIGHT) {
-+ return false;
-+ }
-+ return s->invalidated_y_table[y >> 5] & (1 << (y & 0x1f));
-+}
-+
- void vga_sync_dirty_bitmap(VGACommonState *s)
- {
- memory_region_sync_dirty_bitmap(&s->vram);
-@@ -1638,8 +1646,8 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- page1 = addr + bwidth - 1;
- update |= memory_region_get_dirty(&s->vram, page0, page1 - page0,
- DIRTY_MEMORY_VGA);
-- /* explicit invalidation for the hardware cursor */
-- update |= (s->invalidated_y_table[y >> 5] >> (y & 0x1f)) & 1;
-+ /* explicit invalidation for the hardware cursor (cirrus only) */
-+ update |= vga_scanline_invalidated(s, y);
- if (update) {
- if (y_start < 0)
- y_start = y;
-@@ -1686,7 +1694,7 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- page_max - page_min,
- DIRTY_MEMORY_VGA);
- }
-- memset(s->invalidated_y_table, 0, ((height + 31) >> 5) * 4);
-+ memset(s->invalidated_y_table, 0, sizeof(s->invalidated_y_table));
- }
-
- static void vga_draw_blank(VGACommonState *s, int full_update)
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Gerd Hoffmann <kraxel@redhat.com>
-Date: Fri, 21 Apr 2017 11:16:27 +0200
-Subject: [PATCH] vga: make display updates thread safe.
-
-The vga code clears the dirty bits *after* reading the framebuffer
-memory. So if the guest framebuffer updates hits the race window
-between vga reading the framebuffer and vga clearing the dirty bits
-vga will miss that update
-
-Fix it by using the new memory_region_copy_and_clear_dirty()
-memory_region_copy_get_dirty() functions. That way we clear the
-dirty bitmap before reading the framebuffer. Any guest display
-updates happening in parallel will be properly tracked in the
-dirty bitmap then and the next display refresh will pick them up.
-
-Problem triggers with mttcg only. Before mttcg was merged tcg
-never ran in parallel to vga emulation. Using kvm will hide the
-problem too, due to qemu operating on a userspace copy of the
-kernel's dirty bitmap.
-
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
-Message-id: 20170421091632.30900-5-kraxel@redhat.com
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
----
- hw/display/vga.c | 36 +++++++++++++++++-------------------
- 1 file changed, 17 insertions(+), 19 deletions(-)
-
-diff --git a/hw/display/vga.c b/hw/display/vga.c
-index 3991b88aac..b2516c8d21 100644
---- a/hw/display/vga.c
-+++ b/hw/display/vga.c
-@@ -1465,7 +1465,8 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- DisplaySurface *surface = qemu_console_surface(s->con);
- int y1, y, update, linesize, y_start, double_scan, mask, depth;
- int width, height, shift_control, line_offset, bwidth, bits;
-- ram_addr_t page0, page1, page_min, page_max;
-+ ram_addr_t page0, page1;
-+ DirtyBitmapSnapshot *snap = NULL;
- int disp_width, multi_scan, multi_run;
- uint8_t *d;
- uint32_t v, addr1, addr;
-@@ -1480,9 +1481,6 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
-
- full_update |= update_basic_params(s);
-
-- if (!full_update)
-- vga_sync_dirty_bitmap(s);
--
- s->get_resolution(s, &width, &height);
- disp_width = width;
-
-@@ -1625,11 +1623,17 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- addr1 = (s->start_addr * 4);
- bwidth = (width * bits + 7) / 8;
- y_start = -1;
-- page_min = -1;
-- page_max = 0;
- d = surface_data(surface);
- linesize = surface_stride(surface);
- y1 = 0;
-+
-+ if (!full_update) {
-+ vga_sync_dirty_bitmap(s);
-+ snap = memory_region_snapshot_and_clear_dirty(&s->vram, addr1,
-+ bwidth * height,
-+ DIRTY_MEMORY_VGA);
-+ }
-+
- for(y = 0; y < height; y++) {
- addr = addr1;
- if (!(s->cr[VGA_CRTC_MODE] & 1)) {
-@@ -1644,17 +1648,17 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- update = full_update;
- page0 = addr;
- page1 = addr + bwidth - 1;
-- update |= memory_region_get_dirty(&s->vram, page0, page1 - page0,
-- DIRTY_MEMORY_VGA);
-+ if (full_update) {
-+ update = 1;
-+ } else {
-+ update = memory_region_snapshot_get_dirty(&s->vram, snap,
-+ page0, page1 - page0);
-+ }
- /* explicit invalidation for the hardware cursor (cirrus only) */
- update |= vga_scanline_invalidated(s, y);
- if (update) {
- if (y_start < 0)
- y_start = y;
-- if (page0 < page_min)
-- page_min = page0;
-- if (page1 > page_max)
-- page_max = page1;
- if (!(is_buffer_shared(surface))) {
- vga_draw_line(s, d, s->vram_ptr + addr, width);
- if (s->cursor_draw_line)
-@@ -1687,13 +1691,7 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- dpy_gfx_update(s->con, 0, y_start,
- disp_width, y - y_start);
- }
-- /* reset modified pages */
-- if (page_max >= page_min) {
-- memory_region_reset_dirty(&s->vram,
-- page_min,
-- page_max - page_min,
-- DIRTY_MEMORY_VGA);
-- }
-+ g_free(snap);
- memset(s->invalidated_y_table, 0, sizeof(s->invalidated_y_table));
- }
-
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Gerd Hoffmann <kraxel@redhat.com>
-Date: Tue, 9 May 2017 12:48:39 +0200
-Subject: [PATCH] vga: fix display update region calculation
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-vga display update mis-calculated the region for the dirty bitmap
-snapshot in case the scanlines are padded. This can triggere an
-assert in cpu_physical_memory_snapshot_get_dirty().
-
-Fixes: fec5e8c92becad223df9d972770522f64aafdb72
-Reported-by: Kevin Wolf <kwolf@redhat.com>
-Reported-by: 李强 <liqiang6-s@360.cn>
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
-Message-id: 20170509104839.19415-1-kraxel@redhat.com
----
- hw/display/vga.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/hw/display/vga.c b/hw/display/vga.c
-index b2516c8d21..dcc95f88e2 100644
---- a/hw/display/vga.c
-+++ b/hw/display/vga.c
-@@ -1630,7 +1630,7 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- if (!full_update) {
- vga_sync_dirty_bitmap(s);
- snap = memory_region_snapshot_and_clear_dirty(&s->vram, addr1,
-- bwidth * height,
-+ line_offset * height,
- DIRTY_MEMORY_VGA);
- }
-
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Gerd Hoffmann <kraxel@redhat.com>
-Date: Fri, 1 Sep 2017 14:57:38 +0200
-Subject: [PATCH] vga: fix display update region calculation (split screen)
-
-vga display update mis-calculated the region for the dirty bitmap
-snapshot in case split screen mode is used. This can trigger an
-assert in cpu_physical_memory_snapshot_get_dirty().
-
-Impact: DoS for privileged guest users.
-
-Fixes: CVE-2017-13673
-Fixes: fec5e8c92becad223df9d972770522f64aafdb72
-Cc: P J P <ppandit@redhat.com>
-Reported-by: David Buchanan <d@vidbuchanan.co.uk>
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
-Message-id: 20170828123307.15392-1-kraxel@redhat.com
----
- hw/display/vga.c | 10 ++++++++--
- 1 file changed, 8 insertions(+), 2 deletions(-)
-
-diff --git a/hw/display/vga.c b/hw/display/vga.c
-index dcc95f88e2..533d8d7895 100644
---- a/hw/display/vga.c
-+++ b/hw/display/vga.c
-@@ -1628,9 +1628,15 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- y1 = 0;
-
- if (!full_update) {
-+ ram_addr_t region_start = addr1;
-+ ram_addr_t region_end = addr1 + line_offset * height;
- vga_sync_dirty_bitmap(s);
-- snap = memory_region_snapshot_and_clear_dirty(&s->vram, addr1,
-- line_offset * height,
-+ if (s->line_compare < height) {
-+ /* split screen mode */
-+ region_start = 0;
-+ }
-+ snap = memory_region_snapshot_and_clear_dirty(&s->vram, region_start,
-+ region_end - region_start,
- DIRTY_MEMORY_VGA);
- }
-
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Gerd Hoffmann <kraxel@redhat.com>
-Date: Fri, 1 Sep 2017 14:57:39 +0200
-Subject: [PATCH] vga: stop passing pointers to vga_draw_line* functions
-
-Instead pass around the address (aka offset into vga memory).
-Add vga_read_* helper functions which apply vbe_size_mask to
-the address, to make sure the address stays within the valid
-range, similar to the cirrus blitter fixes (commits ffaf857778
-and 026aeffcb4).
-
-Impact: DoS for privileged guest users. qemu crashes with
-a segfault, when hitting the guard page after vga memory
-allocation, while reading vga memory for display updates.
-
-Fixes: CVE-2017-13672
-Cc: P J P <ppandit@redhat.com>
-Reported-by: David Buchanan <d@vidbuchanan.co.uk>
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
-Message-id: 20170828122906.18993-1-kraxel@redhat.com
----
- hw/display/vga-helpers.h | 202 ++++++++++++++++++++++++++---------------------
- hw/display/vga.c | 5 +-
- hw/display/vga_int.h | 1 +
- 3 files changed, 114 insertions(+), 94 deletions(-)
-
-diff --git a/hw/display/vga-helpers.h b/hw/display/vga-helpers.h
-index 94f6de2046..5a752b3f9e 100644
---- a/hw/display/vga-helpers.h
-+++ b/hw/display/vga-helpers.h
-@@ -95,20 +95,46 @@ static void vga_draw_glyph9(uint8_t *d, int linesize,
- } while (--h);
- }
-
-+static inline uint8_t vga_read_byte(VGACommonState *vga, uint32_t addr)
-+{
-+ return vga->vram_ptr[addr & vga->vbe_size_mask];
-+}
-+
-+static inline uint16_t vga_read_word_le(VGACommonState *vga, uint32_t addr)
-+{
-+ uint32_t offset = addr & vga->vbe_size_mask & ~1;
-+ uint16_t *ptr = (uint16_t *)(vga->vram_ptr + offset);
-+ return lduw_le_p(ptr);
-+}
-+
-+static inline uint16_t vga_read_word_be(VGACommonState *vga, uint32_t addr)
-+{
-+ uint32_t offset = addr & vga->vbe_size_mask & ~1;
-+ uint16_t *ptr = (uint16_t *)(vga->vram_ptr + offset);
-+ return lduw_be_p(ptr);
-+}
-+
-+static inline uint32_t vga_read_dword_le(VGACommonState *vga, uint32_t addr)
-+{
-+ uint32_t offset = addr & vga->vbe_size_mask & ~3;
-+ uint32_t *ptr = (uint32_t *)(vga->vram_ptr + offset);
-+ return ldl_le_p(ptr);
-+}
-+
- /*
- * 4 color mode
- */
--static void vga_draw_line2(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line2(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
- uint32_t plane_mask, *palette, data, v;
- int x;
-
-- palette = s1->last_palette;
-- plane_mask = mask16[s1->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
-+ palette = vga->last_palette;
-+ plane_mask = mask16[vga->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
- width >>= 3;
- for(x = 0; x < width; x++) {
-- data = ((uint32_t *)s)[0];
-+ data = vga_read_dword_le(vga, addr);
- data &= plane_mask;
- v = expand2[GET_PLANE(data, 0)];
- v |= expand2[GET_PLANE(data, 2)] << 2;
-@@ -124,7 +150,7 @@ static void vga_draw_line2(VGACommonState *s1, uint8_t *d,
- ((uint32_t *)d)[6] = palette[(v >> 4) & 0xf];
- ((uint32_t *)d)[7] = palette[(v >> 0) & 0xf];
- d += 32;
-- s += 4;
-+ addr += 4;
- }
- }
-
-@@ -134,17 +160,17 @@ static void vga_draw_line2(VGACommonState *s1, uint8_t *d,
- /*
- * 4 color mode, dup2 horizontal
- */
--static void vga_draw_line2d2(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line2d2(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
- uint32_t plane_mask, *palette, data, v;
- int x;
-
-- palette = s1->last_palette;
-- plane_mask = mask16[s1->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
-+ palette = vga->last_palette;
-+ plane_mask = mask16[vga->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
- width >>= 3;
- for(x = 0; x < width; x++) {
-- data = ((uint32_t *)s)[0];
-+ data = vga_read_dword_le(vga, addr);
- data &= plane_mask;
- v = expand2[GET_PLANE(data, 0)];
- v |= expand2[GET_PLANE(data, 2)] << 2;
-@@ -160,24 +186,24 @@ static void vga_draw_line2d2(VGACommonState *s1, uint8_t *d,
- PUT_PIXEL2(d, 6, palette[(v >> 4) & 0xf]);
- PUT_PIXEL2(d, 7, palette[(v >> 0) & 0xf]);
- d += 64;
-- s += 4;
-+ addr += 4;
- }
- }
-
- /*
- * 16 color mode
- */
--static void vga_draw_line4(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line4(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
- uint32_t plane_mask, data, v, *palette;
- int x;
-
-- palette = s1->last_palette;
-- plane_mask = mask16[s1->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
-+ palette = vga->last_palette;
-+ plane_mask = mask16[vga->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
- width >>= 3;
- for(x = 0; x < width; x++) {
-- data = ((uint32_t *)s)[0];
-+ data = vga_read_dword_le(vga, addr);
- data &= plane_mask;
- v = expand4[GET_PLANE(data, 0)];
- v |= expand4[GET_PLANE(data, 1)] << 1;
-@@ -192,24 +218,24 @@ static void vga_draw_line4(VGACommonState *s1, uint8_t *d,
- ((uint32_t *)d)[6] = palette[(v >> 4) & 0xf];
- ((uint32_t *)d)[7] = palette[(v >> 0) & 0xf];
- d += 32;
-- s += 4;
-+ addr += 4;
- }
- }
-
- /*
- * 16 color mode, dup2 horizontal
- */
--static void vga_draw_line4d2(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line4d2(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
- uint32_t plane_mask, data, v, *palette;
- int x;
-
-- palette = s1->last_palette;
-- plane_mask = mask16[s1->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
-+ palette = vga->last_palette;
-+ plane_mask = mask16[vga->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
- width >>= 3;
- for(x = 0; x < width; x++) {
-- data = ((uint32_t *)s)[0];
-+ data = vga_read_dword_le(vga, addr);
- data &= plane_mask;
- v = expand4[GET_PLANE(data, 0)];
- v |= expand4[GET_PLANE(data, 1)] << 1;
-@@ -224,7 +250,7 @@ static void vga_draw_line4d2(VGACommonState *s1, uint8_t *d,
- PUT_PIXEL2(d, 6, palette[(v >> 4) & 0xf]);
- PUT_PIXEL2(d, 7, palette[(v >> 0) & 0xf]);
- d += 64;
-- s += 4;
-+ addr += 4;
- }
- }
-
-@@ -233,21 +259,21 @@ static void vga_draw_line4d2(VGACommonState *s1, uint8_t *d,
- *
- * XXX: add plane_mask support (never used in standard VGA modes)
- */
--static void vga_draw_line8d2(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line8d2(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
- uint32_t *palette;
- int x;
-
-- palette = s1->last_palette;
-+ palette = vga->last_palette;
- width >>= 3;
- for(x = 0; x < width; x++) {
-- PUT_PIXEL2(d, 0, palette[s[0]]);
-- PUT_PIXEL2(d, 1, palette[s[1]]);
-- PUT_PIXEL2(d, 2, palette[s[2]]);
-- PUT_PIXEL2(d, 3, palette[s[3]]);
-+ PUT_PIXEL2(d, 0, palette[vga_read_byte(vga, addr + 0)]);
-+ PUT_PIXEL2(d, 1, palette[vga_read_byte(vga, addr + 1)]);
-+ PUT_PIXEL2(d, 2, palette[vga_read_byte(vga, addr + 2)]);
-+ PUT_PIXEL2(d, 3, palette[vga_read_byte(vga, addr + 3)]);
- d += 32;
-- s += 4;
-+ addr += 4;
- }
- }
-
-@@ -256,63 +282,63 @@ static void vga_draw_line8d2(VGACommonState *s1, uint8_t *d,
- *
- * XXX: add plane_mask support (never used in standard VGA modes)
- */
--static void vga_draw_line8(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line8(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
- uint32_t *palette;
- int x;
-
-- palette = s1->last_palette;
-+ palette = vga->last_palette;
- width >>= 3;
- for(x = 0; x < width; x++) {
-- ((uint32_t *)d)[0] = palette[s[0]];
-- ((uint32_t *)d)[1] = palette[s[1]];
-- ((uint32_t *)d)[2] = palette[s[2]];
-- ((uint32_t *)d)[3] = palette[s[3]];
-- ((uint32_t *)d)[4] = palette[s[4]];
-- ((uint32_t *)d)[5] = palette[s[5]];
-- ((uint32_t *)d)[6] = palette[s[6]];
-- ((uint32_t *)d)[7] = palette[s[7]];
-+ ((uint32_t *)d)[0] = palette[vga_read_byte(vga, addr + 0)];
-+ ((uint32_t *)d)[1] = palette[vga_read_byte(vga, addr + 1)];
-+ ((uint32_t *)d)[2] = palette[vga_read_byte(vga, addr + 2)];
-+ ((uint32_t *)d)[3] = palette[vga_read_byte(vga, addr + 3)];
-+ ((uint32_t *)d)[4] = palette[vga_read_byte(vga, addr + 4)];
-+ ((uint32_t *)d)[5] = palette[vga_read_byte(vga, addr + 5)];
-+ ((uint32_t *)d)[6] = palette[vga_read_byte(vga, addr + 6)];
-+ ((uint32_t *)d)[7] = palette[vga_read_byte(vga, addr + 7)];
- d += 32;
-- s += 8;
-+ addr += 8;
- }
- }
-
- /*
- * 15 bit color
- */
--static void vga_draw_line15_le(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line15_le(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
- int w;
- uint32_t v, r, g, b;
-
- w = width;
- do {
-- v = lduw_le_p((void *)s);
-+ v = vga_read_word_le(vga, addr);
- r = (v >> 7) & 0xf8;
- g = (v >> 2) & 0xf8;
- b = (v << 3) & 0xf8;
- ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
-- s += 2;
-+ addr += 2;
- d += 4;
- } while (--w != 0);
- }
-
--static void vga_draw_line15_be(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line15_be(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
- int w;
- uint32_t v, r, g, b;
-
- w = width;
- do {
-- v = lduw_be_p((void *)s);
-+ v = vga_read_word_be(vga, addr);
- r = (v >> 7) & 0xf8;
- g = (v >> 2) & 0xf8;
- b = (v << 3) & 0xf8;
- ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
-- s += 2;
-+ addr += 2;
- d += 4;
- } while (--w != 0);
- }
-@@ -320,38 +346,38 @@ static void vga_draw_line15_be(VGACommonState *s1, uint8_t *d,
- /*
- * 16 bit color
- */
--static void vga_draw_line16_le(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line16_le(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
- int w;
- uint32_t v, r, g, b;
-
- w = width;
- do {
-- v = lduw_le_p((void *)s);
-+ v = vga_read_word_le(vga, addr);
- r = (v >> 8) & 0xf8;
- g = (v >> 3) & 0xfc;
- b = (v << 3) & 0xf8;
- ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
-- s += 2;
-+ addr += 2;
- d += 4;
- } while (--w != 0);
- }
-
--static void vga_draw_line16_be(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line16_be(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
- int w;
- uint32_t v, r, g, b;
-
- w = width;
- do {
-- v = lduw_be_p((void *)s);
-+ v = vga_read_word_be(vga, addr);
- r = (v >> 8) & 0xf8;
- g = (v >> 3) & 0xfc;
- b = (v << 3) & 0xf8;
- ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
-- s += 2;
-+ addr += 2;
- d += 4;
- } while (--w != 0);
- }
-@@ -359,36 +385,36 @@ static void vga_draw_line16_be(VGACommonState *s1, uint8_t *d,
- /*
- * 24 bit color
- */
--static void vga_draw_line24_le(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line24_le(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
- int w;
- uint32_t r, g, b;
-
- w = width;
- do {
-- b = s[0];
-- g = s[1];
-- r = s[2];
-+ b = vga_read_byte(vga, addr + 0);
-+ g = vga_read_byte(vga, addr + 1);
-+ r = vga_read_byte(vga, addr + 2);
- ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
-- s += 3;
-+ addr += 3;
- d += 4;
- } while (--w != 0);
- }
-
--static void vga_draw_line24_be(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line24_be(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
- int w;
- uint32_t r, g, b;
-
- w = width;
- do {
-- r = s[0];
-- g = s[1];
-- b = s[2];
-+ r = vga_read_byte(vga, addr + 0);
-+ g = vga_read_byte(vga, addr + 1);
-+ b = vga_read_byte(vga, addr + 2);
- ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
-- s += 3;
-+ addr += 3;
- d += 4;
- } while (--w != 0);
- }
-@@ -396,44 +422,36 @@ static void vga_draw_line24_be(VGACommonState *s1, uint8_t *d,
- /*
- * 32 bit color
- */
--static void vga_draw_line32_le(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line32_le(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
--#ifndef HOST_WORDS_BIGENDIAN
-- memcpy(d, s, width * 4);
--#else
- int w;
- uint32_t r, g, b;
-
- w = width;
- do {
-- b = s[0];
-- g = s[1];
-- r = s[2];
-+ b = vga_read_byte(vga, addr + 0);
-+ g = vga_read_byte(vga, addr + 1);
-+ r = vga_read_byte(vga, addr + 2);
- ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
-- s += 4;
-+ addr += 4;
- d += 4;
- } while (--w != 0);
--#endif
- }
-
--static void vga_draw_line32_be(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width)
-+static void vga_draw_line32_be(VGACommonState *vga, uint8_t *d,
-+ uint32_t addr, int width)
- {
--#ifdef HOST_WORDS_BIGENDIAN
-- memcpy(d, s, width * 4);
--#else
- int w;
- uint32_t r, g, b;
-
- w = width;
- do {
-- r = s[1];
-- g = s[2];
-- b = s[3];
-+ r = vga_read_byte(vga, addr + 1);
-+ g = vga_read_byte(vga, addr + 2);
-+ b = vga_read_byte(vga, addr + 3);
- ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
-- s += 4;
-+ addr += 4;
- d += 4;
- } while (--w != 0);
--#endif
- }
-diff --git a/hw/display/vga.c b/hw/display/vga.c
-index 533d8d7895..13e4a5d55d 100644
---- a/hw/display/vga.c
-+++ b/hw/display/vga.c
-@@ -1005,7 +1005,7 @@ void vga_mem_writeb(VGACommonState *s, hwaddr addr, uint32_t val)
- }
-
- typedef void vga_draw_line_func(VGACommonState *s1, uint8_t *d,
-- const uint8_t *s, int width);
-+ uint32_t srcaddr, int width);
-
- #include "vga-helpers.h"
-
-@@ -1666,7 +1666,7 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- if (y_start < 0)
- y_start = y;
- if (!(is_buffer_shared(surface))) {
-- vga_draw_line(s, d, s->vram_ptr + addr, width);
-+ vga_draw_line(s, d, addr, width);
- if (s->cursor_draw_line)
- s->cursor_draw_line(s, d, y);
- }
-@@ -2170,6 +2170,7 @@ void vga_common_init(VGACommonState *s, Object *obj, bool global_vmstate)
- if (!s->vbe_size) {
- s->vbe_size = s->vram_size;
- }
-+ s->vbe_size_mask = s->vbe_size - 1;
-
- s->is_vbe_vmstate = 1;
- memory_region_init_ram(&s->vram, obj, "vga.vram", s->vram_size,
-diff --git a/hw/display/vga_int.h b/hw/display/vga_int.h
-index dd6c958da3..ad34a1f048 100644
---- a/hw/display/vga_int.h
-+++ b/hw/display/vga_int.h
-@@ -94,6 +94,7 @@ typedef struct VGACommonState {
- uint32_t vram_size;
- uint32_t vram_size_mb; /* property */
- uint32_t vbe_size;
-+ uint32_t vbe_size_mask;
- uint32_t latch;
- bool has_chain4_alias;
- MemoryRegion chain4_alias;
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Prasad J Pandit <pjp@fedoraproject.org>
-Date: Thu, 7 Sep 2017 12:02:56 +0530
-Subject: [PATCH] multiboot: validate multiboot header address values
-
-While loading kernel via multiboot-v1 image, (flags & 0x00010000)
-indicates that multiboot header contains valid addresses to load
-the kernel image. These addresses are used to compute kernel
-size and kernel text offset in the OS image. Validate these
-address values to avoid an OOB access issue.
-
-This is CVE-2017-14167.
-
-Reported-by: Thomas Garnier <thgarnie@google.com>
-Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
----
- hw/i386/multiboot.c | 19 +++++++++++++++++++
- 1 file changed, 19 insertions(+)
-
-diff --git a/hw/i386/multiboot.c b/hw/i386/multiboot.c
-index f13e23139b..22688d376d 100644
---- a/hw/i386/multiboot.c
-+++ b/hw/i386/multiboot.c
-@@ -221,15 +221,34 @@ int load_multiboot(FWCfgState *fw_cfg,
- uint32_t mh_header_addr = ldl_p(header+i+12);
- uint32_t mh_load_end_addr = ldl_p(header+i+20);
- uint32_t mh_bss_end_addr = ldl_p(header+i+24);
-+
- mh_load_addr = ldl_p(header+i+16);
-+ if (mh_header_addr < mh_load_addr) {
-+ fprintf(stderr, "invalid mh_load_addr address\n");
-+ exit(1);
-+ }
-+
- uint32_t mb_kernel_text_offset = i - (mh_header_addr - mh_load_addr);
- uint32_t mb_load_size = 0;
- mh_entry_addr = ldl_p(header+i+28);
-
- if (mh_load_end_addr) {
-+ if (mh_bss_end_addr < mh_load_addr) {
-+ fprintf(stderr, "invalid mh_bss_end_addr address\n");
-+ exit(1);
-+ }
- mb_kernel_size = mh_bss_end_addr - mh_load_addr;
-+
-+ if (mh_load_end_addr < mh_load_addr) {
-+ fprintf(stderr, "invalid mh_load_end_addr address\n");
-+ exit(1);
-+ }
- mb_load_size = mh_load_end_addr - mh_load_addr;
- } else {
-+ if (kernel_file_size < mb_kernel_text_offset) {
-+ fprintf(stderr, "invalid kernel_file_size\n");
-+ exit(1);
-+ }
- mb_kernel_size = kernel_file_size - mb_kernel_text_offset;
- mb_load_size = mb_kernel_size;
- }
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Wed, 20 Sep 2017 08:09:33 +0200
-Subject: [PATCH] virtio: fix descriptor counting in virtqueue_pop
-
-While changing the s/g list allocation, commit 3b3b0628
-also changed the descriptor counting to count iovec entries
-as split by cpu_physical_memory_map(). Previously only the
-actual descriptor entries were counted and the split into
-the iovec happened afterwards in virtqueue_map().
-Count the entries again instead to avoid erroneous
-"Looped descriptor" errors.
-
-Reported-by: Hans Middelhoek <h.middelhoek@ospito.nl>
-Link: https://forum.proxmox.com/threads/vm-crash-with-memory-hotplug.35904/
-Fixes: 3b3b0628217e ("virtio: slim down allocation of VirtQueueElements")
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
-Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
----
- hw/virtio/virtio.c | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
-index 890b4d7eb7..33bb770177 100644
---- a/hw/virtio/virtio.c
-+++ b/hw/virtio/virtio.c
-@@ -834,7 +834,7 @@ void *virtqueue_pop(VirtQueue *vq, size_t sz)
- int64_t len;
- VirtIODevice *vdev = vq->vdev;
- VirtQueueElement *elem = NULL;
-- unsigned out_num, in_num;
-+ unsigned out_num, in_num, elem_entries;
- hwaddr addr[VIRTQUEUE_MAX_SIZE];
- struct iovec iov[VIRTQUEUE_MAX_SIZE];
- VRingDesc desc;
-@@ -852,7 +852,7 @@ void *virtqueue_pop(VirtQueue *vq, size_t sz)
- smp_rmb();
-
- /* When we start there are none of either input nor output. */
-- out_num = in_num = 0;
-+ out_num = in_num = elem_entries = 0;
-
- max = vq->vring.num;
-
-@@ -922,7 +922,7 @@ void *virtqueue_pop(VirtQueue *vq, size_t sz)
- }
-
- /* If we've got too many, that implies a descriptor loop. */
-- if ((in_num + out_num) > max) {
-+ if (++elem_entries > max) {
- virtio_error(vdev, "Looped descriptor");
- goto err_undo_map;
- }
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Wed, 29 Nov 2017 09:39:55 +0100
-Subject: [PATCH] nbd/server: CVE-2017-15119 Reject options larger than 32M
-
-Backported-from: fdad35ef6c58
----
- nbd/server.c | 6 ++++++
- 1 file changed, 6 insertions(+)
-
-diff --git a/nbd/server.c b/nbd/server.c
-index a98bb21a0a..4d6da8ac06 100644
---- a/nbd/server.c
-+++ b/nbd/server.c
-@@ -489,6 +489,12 @@ static int nbd_negotiate_options(NBDClient *client)
- }
- length = be32_to_cpu(length);
-
-+ if (length > NBD_MAX_BUFFER_SIZE) {
-+ LOG("len (%" PRIu32" ) is larger than max len (%u)",
-+ length, NBD_MAX_BUFFER_SIZE);
-+ return -EINVAL;
-+ }
-+
- TRACE("Checking option 0x%" PRIx32, clientflags);
- if (client->tlscreds &&
- client->ioc == (QIOChannel *)client->sioc) {
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: "Dr. David Alan Gilbert" <dgilbert@redhat.com>
-Date: Fri, 4 Aug 2017 12:33:29 +0100
-Subject: [PATCH] vga/migration: Update memory map in post_load
-
-After migration the chain4 alias mapping added by 80763888 (in 2011)
-might be missing, since there's no call to vga_update_memory_access
-in the post_load after the registers are updated. Add it back.
-
-Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
-Reviewed-by: Juan Quintela <quintela@redhat.com>
-Message-id: 20170804113329.13609-1-dgilbert@redhat.com
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
----
- hw/display/vga.c | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/hw/display/vga.c b/hw/display/vga.c
-index 13e4a5d55d..a99d831e04 100644
---- a/hw/display/vga.c
-+++ b/hw/display/vga.c
-@@ -2050,6 +2050,7 @@ static int vga_common_post_load(void *opaque, int version_id)
- /* force refresh */
- s->graphic_mode = -1;
- vbe_update_vgaregs(s);
-+ vga_update_memory_access(s);
- return 0;
- }
-
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Gerd Hoffmann <kraxel@redhat.com>
-Date: Tue, 10 Oct 2017 16:13:21 +0200
-Subject: [PATCH] vga: drop line_offset variable
-
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
----
- hw/display/vga.c | 7 +++----
- 1 file changed, 3 insertions(+), 4 deletions(-)
-
-diff --git a/hw/display/vga.c b/hw/display/vga.c
-index a99d831e04..77af807a51 100644
---- a/hw/display/vga.c
-+++ b/hw/display/vga.c
-@@ -1464,7 +1464,7 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- {
- DisplaySurface *surface = qemu_console_surface(s->con);
- int y1, y, update, linesize, y_start, double_scan, mask, depth;
-- int width, height, shift_control, line_offset, bwidth, bits;
-+ int width, height, shift_control, bwidth, bits;
- ram_addr_t page0, page1;
- DirtyBitmapSnapshot *snap = NULL;
- int disp_width, multi_scan, multi_run;
-@@ -1614,7 +1614,6 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- s->cursor_invalidate(s);
- }
-
-- line_offset = s->line_offset;
- #if 0
- printf("w=%d h=%d v=%d line_offset=%d cr[0x09]=0x%02x cr[0x17]=0x%02x linecmp=%d sr[0x01]=0x%02x\n",
- width, height, v, line_offset, s->cr[9], s->cr[VGA_CRTC_MODE],
-@@ -1629,7 +1628,7 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
-
- if (!full_update) {
- ram_addr_t region_start = addr1;
-- ram_addr_t region_end = addr1 + line_offset * height;
-+ ram_addr_t region_end = addr1 + s->line_offset * height;
- vga_sync_dirty_bitmap(s);
- if (s->line_compare < height) {
- /* split screen mode */
-@@ -1681,7 +1680,7 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- if (!multi_run) {
- mask = (s->cr[VGA_CRTC_MODE] & 3) ^ 3;
- if ((y1 & mask) == mask)
-- addr1 += line_offset;
-+ addr1 += s->line_offset;
- y1++;
- multi_run = multi_scan;
- } else {
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Gerd Hoffmann <kraxel@redhat.com>
-Date: Tue, 10 Oct 2017 16:13:22 +0200
-Subject: [PATCH] vga: handle cirrus vbe mode wraparounds.
-
-Commit "3d90c62548 vga: stop passing pointers to vga_draw_line*
-functions" is incomplete. It doesn't handle the case that the vga
-rendering code tries to create a shared surface, i.e. a pixman image
-backed by vga video memory. That can not work in case the guest display
-wraps from end of video memory to the start. So force shadowing in that
-case. Also adjust the snapshot region calculation.
-
-Can trigger with cirrus only, when programming vbe modes using the bochs
-api (stdvga, also qxl and virtio-vga in vga compat mode) wrap arounds
-can't happen.
-
-Fixes: CVE-2017-13672
-Fixes: 3d90c6254863693a6b13d918d2b8682e08bbc681
-Cc: P J P <ppandit@redhat.com>
-Reported-by: David Buchanan <d@vidbuchanan.co.uk>
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
-Message-id: 20171010141323.14049-3-kraxel@redhat.com
----
- hw/display/vga.c | 28 +++++++++++++++++++++-------
- 1 file changed, 21 insertions(+), 7 deletions(-)
-
-diff --git a/hw/display/vga.c b/hw/display/vga.c
-index 77af807a51..7bdbf7441e 100644
---- a/hw/display/vga.c
-+++ b/hw/display/vga.c
-@@ -1465,13 +1465,13 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- DisplaySurface *surface = qemu_console_surface(s->con);
- int y1, y, update, linesize, y_start, double_scan, mask, depth;
- int width, height, shift_control, bwidth, bits;
-- ram_addr_t page0, page1;
-+ ram_addr_t page0, page1, region_start, region_end;
- DirtyBitmapSnapshot *snap = NULL;
- int disp_width, multi_scan, multi_run;
- uint8_t *d;
- uint32_t v, addr1, addr;
- vga_draw_line_func *vga_draw_line = NULL;
-- bool share_surface;
-+ bool share_surface, force_shadow = false;
- pixman_format_code_t format;
- #ifdef HOST_WORDS_BIGENDIAN
- bool byteswap = !s->big_endian_fb;
-@@ -1484,6 +1484,15 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- s->get_resolution(s, &width, &height);
- disp_width = width;
-
-+ region_start = (s->start_addr * 4);
-+ region_end = region_start + s->line_offset * height;
-+ if (region_end > s->vbe_size) {
-+ /* wraps around (can happen with cirrus vbe modes) */
-+ region_start = 0;
-+ region_end = s->vbe_size;
-+ force_shadow = true;
-+ }
-+
- shift_control = (s->gr[VGA_GFX_MODE] >> 5) & 3;
- double_scan = (s->cr[VGA_CRTC_MAX_SCAN] >> 7);
- if (shift_control != 1) {
-@@ -1523,7 +1532,7 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- format = qemu_default_pixman_format(depth, !byteswap);
- if (format) {
- share_surface = dpy_gfx_check_format(s->con, format)
-- && !s->force_shadow;
-+ && !s->force_shadow && !force_shadow;
- } else {
- share_surface = false;
- }
-@@ -1627,8 +1636,6 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- y1 = 0;
-
- if (!full_update) {
-- ram_addr_t region_start = addr1;
-- ram_addr_t region_end = addr1 + s->line_offset * height;
- vga_sync_dirty_bitmap(s);
- if (s->line_compare < height) {
- /* split screen mode */
-@@ -1651,10 +1658,17 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- addr = (addr & ~0x8000) | ((y1 & 2) << 14);
- }
- update = full_update;
-- page0 = addr;
-- page1 = addr + bwidth - 1;
-+ page0 = addr & s->vbe_size_mask;
-+ page1 = (addr + bwidth - 1) & s->vbe_size_mask;
- if (full_update) {
- update = 1;
-+ } else if (page1 < page0) {
-+ /* scanline wraps from end of video memory to the start */
-+ assert(force_shadow);
-+ update = memory_region_snapshot_get_dirty(&s->vram, snap,
-+ page0, 0);
-+ update |= memory_region_snapshot_get_dirty(&s->vram, snap,
-+ page1, 0);
- } else {
- update = memory_region_snapshot_get_dirty(&s->vram, snap,
- page0, page1 - page0);
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Gerd Hoffmann <kraxel@redhat.com>
-Date: Tue, 10 Oct 2017 16:13:23 +0200
-Subject: [PATCH] vga: add ram_addr_t cast
-
-Reported by Coverity.
-
-Fixes: CID 1381409
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
-Message-id: 20171010141323.14049-4-kraxel@redhat.com
----
- hw/display/vga.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/hw/display/vga.c b/hw/display/vga.c
-index 7bdbf7441e..63ba404ef2 100644
---- a/hw/display/vga.c
-+++ b/hw/display/vga.c
-@@ -1485,7 +1485,7 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- disp_width = width;
-
- region_start = (s->start_addr * 4);
-- region_end = region_start + s->line_offset * height;
-+ region_end = region_start + (ram_addr_t)s->line_offset * height;
- if (region_end > s->vbe_size) {
- /* wraps around (can happen with cirrus vbe modes) */
- region_start = 0;
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Gerd Hoffmann <kraxel@redhat.com>
-Date: Mon, 30 Oct 2017 11:28:30 +0100
-Subject: [PATCH] vga: fix region checks in wraparound case
-
-Cc: "Dr. David Alan Gilbert" <dgilbert@redhat.com>
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
-Reviewed-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
-Message-id: 20171030102830.4469-1-kraxel@redhat.com
----
- hw/display/vga.c | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/hw/display/vga.c b/hw/display/vga.c
-index 63ba404ef2..a58d8bcd67 100644
---- a/hw/display/vga.c
-+++ b/hw/display/vga.c
-@@ -1666,9 +1666,9 @@ static void vga_draw_graphic(VGACommonState *s, int full_update)
- /* scanline wraps from end of video memory to the start */
- assert(force_shadow);
- update = memory_region_snapshot_get_dirty(&s->vram, snap,
-- page0, 0);
-+ page0, s->vbe_size - page0);
- update |= memory_region_snapshot_get_dirty(&s->vram, snap,
-- page1, 0);
-+ 0, page1);
- } else {
- update = memory_region_snapshot_get_dirty(&s->vram, snap,
- page0, page1 - page0);
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: "Daniel P. Berrange" <berrange@redhat.com>
-Date: Mon, 9 Oct 2017 14:43:42 +0100
-Subject: [PATCH] io: monitor encoutput buffer size from websocket GSource
-
-The websocket GSource is monitoring the size of the rawoutput
-buffer to determine if the channel can accepts more writes.
-The rawoutput buffer, however, is merely a temporary staging
-buffer before data is copied into the encoutput buffer. Thus
-its size will always be zero when the GSource runs.
-
-This flaw causes the encoutput buffer to grow without bound
-if the other end of the underlying data channel doesn't
-read data being sent. This can be seen with VNC if a client
-is on a slow WAN link and the guest OS is sending many screen
-updates. A malicious VNC client can act like it is on a slow
-link by playing a video in the guest and then reading data
-very slowly, causing QEMU host memory to expand arbitrarily.
-
-This issue is assigned CVE-2017-15268, publically reported in
-
- https://bugs.launchpad.net/qemu/+bug/1718964
-
-Reviewed-by: Eric Blake <eblake@redhat.com>
-Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
----
- io/channel-websock.c | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/io/channel-websock.c b/io/channel-websock.c
-index 8fabadea2f..882bbb4cbc 100644
---- a/io/channel-websock.c
-+++ b/io/channel-websock.c
-@@ -26,7 +26,7 @@
- #include "trace.h"
-
-
--/* Max amount to allow in rawinput/rawoutput buffers */
-+/* Max amount to allow in rawinput/encoutput buffers */
- #define QIO_CHANNEL_WEBSOCK_MAX_BUFFER 8192
-
- #define QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN 24
-@@ -1006,7 +1006,7 @@ qio_channel_websock_source_prepare(GSource *source,
- if (wsource->wioc->rawinput.offset) {
- cond |= G_IO_IN;
- }
-- if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
-+ if (wsource->wioc->encoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
- cond |= G_IO_OUT;
- }
-
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Prasad J Pandit <pjp@fedoraproject.org>
-Date: Mon, 16 Oct 2017 14:21:59 +0200
-Subject: [PATCH] 9pfs: use g_malloc0 to allocate space for xattr
-
-9p back-end first queries the size of an extended attribute,
-allocates space for it via g_malloc() and then retrieves its
-value into allocated buffer. Race between querying attribute
-size and retrieving its could lead to memory bytes disclosure.
-Use g_malloc0() to avoid it.
-
-Reported-by: Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
-Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
-Signed-off-by: Greg Kurz <groug@kaod.org>
----
- hw/9pfs/9p.c | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
-index c80ba67389..aaf9935ef4 100644
---- a/hw/9pfs/9p.c
-+++ b/hw/9pfs/9p.c
-@@ -3220,7 +3220,7 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque)
- xattr_fidp->fid_type = P9_FID_XATTR;
- xattr_fidp->fs.xattr.xattrwalk_fid = true;
- if (size) {
-- xattr_fidp->fs.xattr.value = g_malloc(size);
-+ xattr_fidp->fs.xattr.value = g_malloc0(size);
- err = v9fs_co_llistxattr(pdu, &xattr_fidp->path,
- xattr_fidp->fs.xattr.value,
- xattr_fidp->fs.xattr.len);
-@@ -3253,7 +3253,7 @@ static void coroutine_fn v9fs_xattrwalk(void *opaque)
- xattr_fidp->fid_type = P9_FID_XATTR;
- xattr_fidp->fs.xattr.xattrwalk_fid = true;
- if (size) {
-- xattr_fidp->fs.xattr.value = g_malloc(size);
-+ xattr_fidp->fs.xattr.value = g_malloc0(size);
- err = v9fs_co_lgetxattr(pdu, &xattr_fidp->path,
- &name, xattr_fidp->fs.xattr.value,
- xattr_fidp->fs.xattr.len);
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Gerd Hoffmann <kraxel@redhat.com>
-Date: Wed, 11 Oct 2017 10:43:14 +0200
-Subject: [PATCH] cirrus: fix oob access in mode4and5 write functions
-
-Move dst calculation into the loop, so we apply the mask on each
-interation and will not overflow vga memory.
-
-Cc: Prasad J Pandit <pjp@fedoraproject.org>
-Reported-by: Niu Guoxiang <niuguoxiang@huawei.com>
-Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
-Message-id: 20171011084314.21752-1-kraxel@redhat.com
----
- hw/display/cirrus_vga.c | 6 ++----
- 1 file changed, 2 insertions(+), 4 deletions(-)
-
-diff --git a/hw/display/cirrus_vga.c b/hw/display/cirrus_vga.c
-index afc290ab91..077a8cb74f 100644
---- a/hw/display/cirrus_vga.c
-+++ b/hw/display/cirrus_vga.c
-@@ -2038,15 +2038,14 @@ static void cirrus_mem_writeb_mode4and5_8bpp(CirrusVGAState * s,
- unsigned val = mem_value;
- uint8_t *dst;
-
-- dst = s->vga.vram_ptr + (offset &= s->cirrus_addr_mask);
- for (x = 0; x < 8; x++) {
-+ dst = s->vga.vram_ptr + ((offset + x) & s->cirrus_addr_mask);
- if (val & 0x80) {
- *dst = s->cirrus_shadow_gr1;
- } else if (mode == 5) {
- *dst = s->cirrus_shadow_gr0;
- }
- val <<= 1;
-- dst++;
- }
- memory_region_set_dirty(&s->vga.vram, offset, 8);
- }
-@@ -2060,8 +2059,8 @@ static void cirrus_mem_writeb_mode4and5_16bpp(CirrusVGAState * s,
- unsigned val = mem_value;
- uint8_t *dst;
-
-- dst = s->vga.vram_ptr + (offset &= s->cirrus_addr_mask);
- for (x = 0; x < 8; x++) {
-+ dst = s->vga.vram_ptr + ((offset + 2 * x) & s->cirrus_addr_mask & ~1);
- if (val & 0x80) {
- *dst = s->cirrus_shadow_gr1;
- *(dst + 1) = s->vga.gr[0x11];
-@@ -2070,7 +2069,6 @@ static void cirrus_mem_writeb_mode4and5_16bpp(CirrusVGAState * s,
- *(dst + 1) = s->vga.gr[0x10];
- }
- val <<= 1;
-- dst += 2;
- }
- memory_region_set_dirty(&s->vga.vram, offset, 16);
- }
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Prasad J Pandit <pjp@fedoraproject.org>
-Date: Wed, 29 Nov 2017 23:14:27 +0530
-Subject: [PATCH] virtio: check VirtQueue Vring object is set
-
-A guest could attempt to use an uninitialised VirtQueue object
-or unset Vring.align leading to a arithmetic exception. Add check
-to avoid it.
-
-Reported-by: Zhangboxian <zhangboxian@huawei.com>
-Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
-Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
-Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
-Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
-Reviewed-by: Cornelia Huck <cohuck@redhat.com>
----
- hw/virtio/virtio.c | 14 +++++++++++---
- 1 file changed, 11 insertions(+), 3 deletions(-)
-
-diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
-index 33bb770177..76b9a9907c 100644
---- a/hw/virtio/virtio.c
-+++ b/hw/virtio/virtio.c
-@@ -183,7 +183,7 @@ void virtio_queue_update_rings(VirtIODevice *vdev, int n)
- {
- VRing *vring = &vdev->vq[n].vring;
-
-- if (!vring->desc) {
-+ if (!vring->num || !vring->desc || !vring->align) {
- /* not yet setup -> nothing to do */
- return;
- }
-@@ -1416,6 +1416,9 @@ void virtio_config_modern_writel(VirtIODevice *vdev,
-
- void virtio_queue_set_addr(VirtIODevice *vdev, int n, hwaddr addr)
- {
-+ if (!vdev->vq[n].vring.num) {
-+ return;
-+ }
- vdev->vq[n].vring.desc = addr;
- virtio_queue_update_rings(vdev, n);
- }
-@@ -1428,6 +1431,9 @@ hwaddr virtio_queue_get_addr(VirtIODevice *vdev, int n)
- void virtio_queue_set_rings(VirtIODevice *vdev, int n, hwaddr desc,
- hwaddr avail, hwaddr used)
- {
-+ if (!vdev->vq[n].vring.num) {
-+ return;
-+ }
- vdev->vq[n].vring.desc = desc;
- vdev->vq[n].vring.avail = avail;
- vdev->vq[n].vring.used = used;
-@@ -1496,8 +1502,10 @@ void virtio_queue_set_align(VirtIODevice *vdev, int n, int align)
- */
- assert(k->has_variable_vring_alignment);
-
-- vdev->vq[n].vring.align = align;
-- virtio_queue_update_rings(vdev, n);
-+ if (align) {
-+ vdev->vq[n].vring.align = align;
-+ virtio_queue_update_rings(vdev, n);
-+ }
- }
-
- static bool virtio_queue_notify_aio_vq(VirtQueue *vq)
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jeff Cody <jcody@redhat.com>
-Date: Tue, 23 May 2017 13:27:50 -0400
-Subject: [PATCH] block/gluster: glfs_lseek() workaround
-
-On current released versions of glusterfs, glfs_lseek() will sometimes
-return invalid values for SEEK_DATA or SEEK_HOLE. For SEEK_DATA and
-SEEK_HOLE, the returned value should be >= the passed offset, or < 0 in
-the case of error:
-
-LSEEK(2):
-
- off_t lseek(int fd, off_t offset, int whence);
-
- [...]
-
- SEEK_HOLE
- Adjust the file offset to the next hole in the file greater
- than or equal to offset. If offset points into the middle of
- a hole, then the file offset is set to offset. If there is no
- hole past offset, then the file offset is adjusted to the end
- of the file (i.e., there is an implicit hole at the end of
- any file).
-
- [...]
-
- RETURN VALUE
- Upon successful completion, lseek() returns the resulting
- offset location as measured in bytes from the beginning of the
- file. On error, the value (off_t) -1 is returned and errno is
- set to indicate the error
-
-However, occasionally glfs_lseek() for SEEK_HOLE/DATA will return a
-value less than the passed offset, yet greater than zero.
-
-For instance, here are example values observed from this call:
-
- offs = glfs_lseek(s->fd, start, SEEK_HOLE);
- if (offs < 0) {
- return -errno; /* D1 and (H3 or H4) */
- }
-
-start == 7608336384
-offs == 7607877632
-
-This causes QEMU to abort on the assert test. When this value is
-returned, errno is also 0.
-
-This is a reported and known bug to glusterfs:
-https://bugzilla.redhat.com/show_bug.cgi?id=1425293
-
-Although this is being fixed in gluster, we still should work around it
-in QEMU, given that multiple released versions of gluster behave this
-way.
-
-This patch treats the return case of (offs < start) the same as if an
-error value other than ENXIO is returned; we will assume we learned
-nothing, and there are no holes in the file.
-
-Signed-off-by: Jeff Cody <jcody@redhat.com>
-Reviewed-by: Eric Blake <eblake@redhat.com>
-Reviewed-by: Niels de Vos <ndevos@redhat.com>
-Message-id: 87c0140e9407c08f6e74b04131b610f2e27c014c.1495560397.git.jcody@redhat.com
-Signed-off-by: Jeff Cody <jcody@redhat.com>
----
- block/gluster.c | 18 ++++++++++++++++--
- 1 file changed, 16 insertions(+), 2 deletions(-)
-
-diff --git a/block/gluster.c b/block/gluster.c
-index 4fdf68f1fc..06421ef79d 100644
---- a/block/gluster.c
-+++ b/block/gluster.c
-@@ -1287,7 +1287,14 @@ static int find_allocation(BlockDriverState *bs, off_t start,
- if (offs < 0) {
- return -errno; /* D3 or D4 */
- }
-- assert(offs >= start);
-+
-+ if (offs < start) {
-+ /* This is not a valid return by lseek(). We are safe to just return
-+ * -EIO in this case, and we'll treat it like D4. Unfortunately some
-+ * versions of gluster server will return offs < start, so an assert
-+ * here will unnecessarily abort QEMU. */
-+ return -EIO;
-+ }
-
- if (offs > start) {
- /* D2: in hole, next data at offs */
-@@ -1319,7 +1326,14 @@ static int find_allocation(BlockDriverState *bs, off_t start,
- if (offs < 0) {
- return -errno; /* D1 and (H3 or H4) */
- }
-- assert(offs >= start);
-+
-+ if (offs < start) {
-+ /* This is not a valid return by lseek(). We are safe to just return
-+ * -EIO in this case, and we'll treat it like H4. Unfortunately some
-+ * versions of gluster server will return offs < start, so an assert
-+ * here will unnecessarily abort QEMU. */
-+ return -EIO;
-+ }
-
- if (offs > start) {
- /*
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Niels de Vos <ndevos@redhat.com>
-Date: Sun, 28 May 2017 12:01:14 +0530
-Subject: [PATCH] gluster: add support for PREALLOC_MODE_FALLOC
-
-Add missing support for "preallocation=falloc" to the Gluster block
-driver. This change bases its logic on that of block/file-posix.c and
-removed the gluster_supports_zerofill() and qemu_gluster_zerofill()
-functions in favour of #ifdef checks in an easy to read
-switch-statement.
-
-Both glfs_zerofill() and glfs_fallocate() have been introduced with
-GlusterFS 3.5.0 (pkg-config glusterfs-api = 6). A #define for the
-availability of glfs_fallocate() has been added to ./configure.
-
-Reported-by: Satheesaran Sundaramoorthi <sasundar@redhat.com>
-Signed-off-by: Niels de Vos <ndevos@redhat.com>
-Message-id: 20170528063114.28691-1-ndevos@redhat.com
-URL: https://bugzilla.redhat.com/1450759
-Signed-off-by: Niels de Vos <ndevos@redhat.com>
-Signed-off-by: Jeff Cody <jcody@redhat.com>
----
- block/gluster.c | 76 ++++++++++++++++++++++++++++++---------------------------
- configure | 6 +++++
- 2 files changed, 46 insertions(+), 36 deletions(-)
-
-diff --git a/block/gluster.c b/block/gluster.c
-index 06421ef79d..8108c89c7f 100644
---- a/block/gluster.c
-+++ b/block/gluster.c
-@@ -975,29 +975,6 @@ static coroutine_fn int qemu_gluster_co_pwrite_zeroes(BlockDriverState *bs,
- qemu_coroutine_yield();
- return acb.ret;
- }
--
--static inline bool gluster_supports_zerofill(void)
--{
-- return 1;
--}
--
--static inline int qemu_gluster_zerofill(struct glfs_fd *fd, int64_t offset,
-- int64_t size)
--{
-- return glfs_zerofill(fd, offset, size);
--}
--
--#else
--static inline bool gluster_supports_zerofill(void)
--{
-- return 0;
--}
--
--static inline int qemu_gluster_zerofill(struct glfs_fd *fd, int64_t offset,
-- int64_t size)
--{
-- return 0;
--}
- #endif
-
- static int qemu_gluster_create(const char *filename,
-@@ -1007,9 +984,10 @@ static int qemu_gluster_create(const char *filename,
- struct glfs *glfs;
- struct glfs_fd *fd;
- int ret = 0;
-- int prealloc = 0;
-+ PreallocMode prealloc;
- int64_t total_size = 0;
- char *tmp = NULL;
-+ Error *local_err = NULL;
-
- gconf = g_new0(BlockdevOptionsGluster, 1);
- gconf->debug = qemu_opt_get_number_del(opts, GLUSTER_OPT_DEBUG,
-@@ -1037,13 +1015,12 @@ static int qemu_gluster_create(const char *filename,
- BDRV_SECTOR_SIZE);
-
- tmp = qemu_opt_get_del(opts, BLOCK_OPT_PREALLOC);
-- if (!tmp || !strcmp(tmp, "off")) {
-- prealloc = 0;
-- } else if (!strcmp(tmp, "full") && gluster_supports_zerofill()) {
-- prealloc = 1;
-- } else {
-- error_setg(errp, "Invalid preallocation mode: '%s'"
-- " or GlusterFS doesn't support zerofill API", tmp);
-+ prealloc = qapi_enum_parse(PreallocMode_lookup, tmp,
-+ PREALLOC_MODE__MAX, PREALLOC_MODE_OFF,
-+ &local_err);
-+ g_free(tmp);
-+ if (local_err) {
-+ error_propagate(errp, local_err);
- ret = -EINVAL;
- goto out;
- }
-@@ -1052,21 +1029,48 @@ static int qemu_gluster_create(const char *filename,
- O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR);
- if (!fd) {
- ret = -errno;
-- } else {
-+ goto out;
-+ }
-+
-+ switch (prealloc) {
-+#ifdef CONFIG_GLUSTERFS_FALLOCATE
-+ case PREALLOC_MODE_FALLOC:
-+ if (glfs_fallocate(fd, 0, 0, total_size)) {
-+ error_setg(errp, "Could not preallocate data for the new file");
-+ ret = -errno;
-+ }
-+ break;
-+#endif /* CONFIG_GLUSTERFS_FALLOCATE */
-+#ifdef CONFIG_GLUSTERFS_ZEROFILL
-+ case PREALLOC_MODE_FULL:
- if (!glfs_ftruncate(fd, total_size)) {
-- if (prealloc && qemu_gluster_zerofill(fd, 0, total_size)) {
-+ if (glfs_zerofill(fd, 0, total_size)) {
-+ error_setg(errp, "Could not zerofill the new file");
- ret = -errno;
- }
- } else {
-+ error_setg(errp, "Could not resize file");
- ret = -errno;
- }
--
-- if (glfs_close(fd) != 0) {
-+ break;
-+#endif /* CONFIG_GLUSTERFS_ZEROFILL */
-+ case PREALLOC_MODE_OFF:
-+ if (glfs_ftruncate(fd, total_size) != 0) {
- ret = -errno;
-+ error_setg(errp, "Could not resize file");
- }
-+ break;
-+ default:
-+ ret = -EINVAL;
-+ error_setg(errp, "Unsupported preallocation mode: %s",
-+ PreallocMode_lookup[prealloc]);
-+ break;
-+ }
-+
-+ if (glfs_close(fd) != 0) {
-+ ret = -errno;
- }
- out:
-- g_free(tmp);
- qapi_free_BlockdevOptionsGluster(gconf);
- glfs_clear_preopened(glfs);
- return ret;
-diff --git a/configure b/configure
-index 841f7a8fae..3667da6f07 100755
---- a/configure
-+++ b/configure
-@@ -300,6 +300,7 @@ seccomp=""
- glusterfs=""
- glusterfs_xlator_opt="no"
- glusterfs_discard="no"
-+glusterfs_fallocate="no"
- glusterfs_zerofill="no"
- gtk=""
- gtkabi=""
-@@ -3537,6 +3538,7 @@ if test "$glusterfs" != "no" ; then
- glusterfs_discard="yes"
- fi
- if $pkg_config --atleast-version=6 glusterfs-api; then
-+ glusterfs_fallocate="yes"
- glusterfs_zerofill="yes"
- fi
- else
-@@ -5717,6 +5719,10 @@ if test "$glusterfs_discard" = "yes" ; then
- echo "CONFIG_GLUSTERFS_DISCARD=y" >> $config_host_mak
- fi
-
-+if test "$glusterfs_fallocate" = "yes" ; then
-+ echo "CONFIG_GLUSTERFS_FALLOCATE=y" >> $config_host_mak
-+fi
-+
- if test "$glusterfs_zerofill" = "yes" ; then
- echo "CONFIG_GLUSTERFS_ZEROFILL=y" >> $config_host_mak
- fi
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eduardo Habkost <ehabkost@redhat.com>
-Date: Wed, 12 Jul 2017 13:20:56 -0300
-Subject: [PATCH] target/i386: Use host_vendor_fms() in max_x86_cpu_initfn()
-
-The existing code duplicated the logic in host_vendor_fms(), so
-reuse the helper function instead.
-
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
-Message-Id: <20170712162058.10538-3-ehabkost@redhat.com>
-Reviewed-by: Igor Mammedov <imammedo@redhat.com>
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
----
- target/i386/cpu.c | 9 ++-------
- 1 file changed, 2 insertions(+), 7 deletions(-)
-
-diff --git a/target/i386/cpu.c b/target/i386/cpu.c
-index 4b3bfb3802..1affd3bb5b 100644
---- a/target/i386/cpu.c
-+++ b/target/i386/cpu.c
-@@ -1592,13 +1592,8 @@ static void max_x86_cpu_initfn(Object *obj)
- X86CPUDefinition host_cpudef = { };
- uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0;
-
-- host_cpuid(0x0, 0, &eax, &ebx, &ecx, &edx);
-- x86_cpu_vendor_words2str(host_cpudef.vendor, ebx, edx, ecx);
--
-- host_cpuid(0x1, 0, &eax, &ebx, &ecx, &edx);
-- host_cpudef.family = ((eax >> 8) & 0x0F) + ((eax >> 20) & 0xFF);
-- host_cpudef.model = ((eax >> 4) & 0x0F) | ((eax & 0xF0000) >> 12);
-- host_cpudef.stepping = eax & 0x0F;
-+ host_vendor_fms(host_cpudef.vendor, &host_cpudef.family,
-+ &host_cpudef.model, &host_cpudef.stepping);
-
- cpu_x86_fill_model_id(host_cpudef.model_id);
-
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eduardo Habkost <ehabkost@redhat.com>
-Date: Wed, 12 Jul 2017 13:20:57 -0300
-Subject: [PATCH] target/i386: Define CPUID_MODEL_ID_SZ macro
-
-Document cpu_x86_fill_model_id() and define CPUID_MODEL_ID_SZ to
-help callers use the right buffer size.
-
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
-Message-Id: <20170712162058.10538-4-ehabkost@redhat.com>
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
----
- target/i386/cpu.c | 11 +++++++++++
- 1 file changed, 11 insertions(+)
-
-diff --git a/target/i386/cpu.c b/target/i386/cpu.c
-index 1affd3bb5b..54832dd591 100644
---- a/target/i386/cpu.c
-+++ b/target/i386/cpu.c
-@@ -1541,6 +1541,17 @@ static bool lmce_supported(void)
- return !!(mce_cap & MCG_LMCE_P);
- }
-
-+#define CPUID_MODEL_ID_SZ 48
-+
-+/**
-+ * cpu_x86_fill_model_id:
-+ * Get CPUID model ID string from host CPU.
-+ *
-+ * @str should have at least CPUID_MODEL_ID_SZ bytes
-+ *
-+ * The function does NOT add a null terminator to the string
-+ * automatically.
-+ */
- static int cpu_x86_fill_model_id(char *str)
- {
- uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0;
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eduardo Habkost <ehabkost@redhat.com>
-Date: Wed, 12 Jul 2017 13:20:58 -0300
-Subject: [PATCH] target/i386: Don't use x86_cpu_load_def() on "max" CPU model
-
-When commit 0bacd8b3046f ('i386: Don't set CPUClass::cpu_def on
-"max" model') removed the CPUClass::cpu_def field, we kept using
-the x86_cpu_load_def() helper directly in max_x86_cpu_initfn(),
-emulating the previous behavior when CPUClass::cpu_def was set.
-
-However, x86_cpu_load_def() is intended to help initialization of
-CPU models from the builtin_x86_defs table, and does lots of
-other steps that are not necessary for "max".
-
-One of the things x86_cpu_load_def() do is to set the properties
-listed at tcg_default_props/kvm_default_props. We must not do
-that on the "max" CPU model, otherwise under KVM we will
-incorrectly report all KVM features as always available, and the
-"svm" feature as always unavailable. The latter caused the bug
-reported at:
-
- https://bugzilla.redhat.com/show_bug.cgi?id=1467599
- ("Unable to start domain: the CPU is incompatible with host CPU:
- Host CPU does not provide required features: svm")
-
-Replace x86_cpu_load_def() with simple object_property_set*()
-calls. In addition to fixing the above bug, this makes the KVM
-branch in max_x86_cpu_initfn() very similar to the existing TCG
-branch.
-
-For reference, the full list of steps performed by
-x86_cpu_load_def() is:
-
-* Setting min-level and min-xlevel. Already done by
- max_x86_cpu_initfn().
-* Setting family/model/stepping/model-id. Done by the code added
- to max_x86_cpu_initfn() in this patch.
-* Copying def->features. Wrong because "-cpu max" features need to
- be calculated at realize time. This was not a problem in the
- current code because host_cpudef.features was all zeroes.
-* x86_cpu_apply_props() calls. This causes the bug above, and
- shouldn't be done.
-* Setting CPUID_EXT_HYPERVISOR. Not needed because it is already
- reported by x86_cpu_get_supported_feature_word(), and because
- "-cpu max" features need to be calculated at realize time.
-* Setting CPU vendor to host CPU vendor if on KVM mode.
- Redundant, because max_x86_cpu_initfn() already sets it to the
- host CPU vendor.
-
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
-Message-Id: <20170712162058.10538-5-ehabkost@redhat.com>
-Reviewed-by: Igor Mammedov <imammedo@redhat.com>
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
----
- target/i386/cpu.c | 18 ++++++++++++------
- 1 file changed, 12 insertions(+), 6 deletions(-)
-
-diff --git a/target/i386/cpu.c b/target/i386/cpu.c
-index 54832dd591..3d53cb4c86 100644
---- a/target/i386/cpu.c
-+++ b/target/i386/cpu.c
-@@ -1600,15 +1600,21 @@ static void max_x86_cpu_initfn(Object *obj)
- cpu->max_features = true;
-
- if (kvm_enabled()) {
-- X86CPUDefinition host_cpudef = { };
-- uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0;
-+ char vendor[CPUID_VENDOR_SZ + 1] = { 0 };
-+ char model_id[CPUID_MODEL_ID_SZ + 1] = { 0 };
-+ int family, model, stepping;
-
-- host_vendor_fms(host_cpudef.vendor, &host_cpudef.family,
-- &host_cpudef.model, &host_cpudef.stepping);
-+ host_vendor_fms(vendor, &family, &model, &stepping);
-
-- cpu_x86_fill_model_id(host_cpudef.model_id);
-+ cpu_x86_fill_model_id(model_id);
-
-- x86_cpu_load_def(cpu, &host_cpudef, &error_abort);
-+ object_property_set_str(OBJECT(cpu), vendor, "vendor", &error_abort);
-+ object_property_set_int(OBJECT(cpu), family, "family", &error_abort);
-+ object_property_set_int(OBJECT(cpu), model, "model", &error_abort);
-+ object_property_set_int(OBJECT(cpu), stepping, "stepping",
-+ &error_abort);
-+ object_property_set_str(OBJECT(cpu), model_id, "model-id",
-+ &error_abort);
-
- env->cpuid_min_level =
- kvm_arch_get_supported_cpuid(s, 0x0, 0, R_EAX);
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eduardo Habkost <ehabkost@redhat.com>
-Date: Tue, 9 Jan 2018 13:45:13 -0200
-Subject: [PATCH] i386: Change X86CPUDefinition::model_id to const char*
-
-It is valid to have a 48-character model ID on CPUID, however the
-definition of X86CPUDefinition::model_id is char[48], which can
-make the compiler drop the null terminator from the string.
-
-If a CPU model happens to have 48 bytes on model_id, "-cpu help"
-will print garbage and the object_property_set_str() call at
-x86_cpu_load_def() will read data outside the model_id array.
-
-We could increase the array size to 49, but this would mean the
-compiler would not issue a warning if a 49-char string is used by
-mistake for model_id.
-
-To make things simpler, simply change model_id to be const char*,
-and validate the string length using an assert() on
-x86_cpu_cpudef_class_init.
-
-Reported-by: "Dr. David Alan Gilbert" <dgilbert@redhat.com>
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
----
- target/i386/cpu.c | 9 ++++++++-
- 1 file changed, 8 insertions(+), 1 deletion(-)
-
-diff --git a/target/i386/cpu.c b/target/i386/cpu.c
-index 3d53cb4c86..c673521016 100644
---- a/target/i386/cpu.c
-+++ b/target/i386/cpu.c
-@@ -753,7 +753,7 @@ struct X86CPUDefinition {
- int model;
- int stepping;
- FeatureWordArray features;
-- char model_id[48];
-+ const char *model_id;
- };
-
- static X86CPUDefinition builtin_x86_defs[] = {
-@@ -922,6 +922,7 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .features[FEAT_1_EDX] =
- I486_FEATURES,
- .xlevel = 0,
-+ .model_id = "",
- },
- {
- .name = "pentium",
-@@ -933,6 +934,7 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .features[FEAT_1_EDX] =
- PENTIUM_FEATURES,
- .xlevel = 0,
-+ .model_id = "",
- },
- {
- .name = "pentium2",
-@@ -944,6 +946,7 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .features[FEAT_1_EDX] =
- PENTIUM2_FEATURES,
- .xlevel = 0,
-+ .model_id = "",
- },
- {
- .name = "pentium3",
-@@ -955,6 +958,7 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .features[FEAT_1_EDX] =
- PENTIUM3_FEATURES,
- .xlevel = 0,
-+ .model_id = "",
- },
- {
- .name = "athlon",
-@@ -2617,6 +2621,9 @@ static void x86_register_cpudef_type(X86CPUDefinition *def)
- * they shouldn't be set on the CPU model table.
- */
- assert(!(def->features[FEAT_8000_0001_EDX] & CPUID_EXT2_AMD_ALIASES));
-+ /* catch mistakes instead of silently truncating model_id when too long */
-+ assert(def->model_id && strlen(def->model_id) <= 48);
-+
-
- type_register(&ti);
- g_free(typename);
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Paolo Bonzini <pbonzini@redhat.com>
-Date: Tue, 9 Jan 2018 13:45:14 -0200
-Subject: [PATCH] i386: Add support for SPEC_CTRL MSR
-
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
----
- target/i386/cpu.h | 3 +++
- target/i386/kvm.c | 15 +++++++++++++++
- target/i386/machine.c | 20 ++++++++++++++++++++
- 3 files changed, 38 insertions(+)
-
-diff --git a/target/i386/cpu.h b/target/i386/cpu.h
-index c4602ca80d..cc322d6b39 100644
---- a/target/i386/cpu.h
-+++ b/target/i386/cpu.h
-@@ -333,6 +333,7 @@
- #define MSR_IA32_APICBASE_BASE (0xfffffU<<12)
- #define MSR_IA32_FEATURE_CONTROL 0x0000003a
- #define MSR_TSC_ADJUST 0x0000003b
-+#define MSR_IA32_SPEC_CTRL 0x48
- #define MSR_IA32_TSCDEADLINE 0x6e0
-
- #define FEATURE_CONTROL_LOCKED (1<<0)
-@@ -1080,6 +1081,8 @@ typedef struct CPUX86State {
-
- uint32_t pkru;
-
-+ uint64_t spec_ctrl;
-+
- /* End of state preserved by INIT (dummy marker). */
- struct {} end_init_save;
-
-diff --git a/target/i386/kvm.c b/target/i386/kvm.c
-index 55865dbee0..9f83c79338 100644
---- a/target/i386/kvm.c
-+++ b/target/i386/kvm.c
-@@ -89,6 +89,7 @@ static bool has_msr_hv_runtime;
- static bool has_msr_hv_synic;
- static bool has_msr_hv_stimer;
- static bool has_msr_xss;
-+static bool has_msr_spec_ctrl;
-
- static bool has_msr_architectural_pmu;
- static uint32_t num_architectural_pmu_counters;
-@@ -1140,6 +1141,10 @@ static int kvm_get_supported_msrs(KVMState *s)
- has_msr_hv_stimer = true;
- continue;
- }
-+ if (kvm_msr_list->indices[i] == MSR_IA32_SPEC_CTRL) {
-+ has_msr_spec_ctrl = true;
-+ continue;
-+ }
- }
- }
-
-@@ -1667,6 +1672,9 @@ static int kvm_put_msrs(X86CPU *cpu, int level)
- if (has_msr_xss) {
- kvm_msr_entry_add(cpu, MSR_IA32_XSS, env->xss);
- }
-+ if (has_msr_spec_ctrl) {
-+ kvm_msr_entry_add(cpu, MSR_IA32_SPEC_CTRL, env->spec_ctrl);
-+ }
- #ifdef TARGET_X86_64
- if (lm_capable_kernel) {
- kvm_msr_entry_add(cpu, MSR_CSTAR, env->cstar);
-@@ -1675,6 +1683,7 @@ static int kvm_put_msrs(X86CPU *cpu, int level)
- kvm_msr_entry_add(cpu, MSR_LSTAR, env->lstar);
- }
- #endif
-+
- /*
- * The following MSRs have side effects on the guest or are too heavy
- * for normal writeback. Limit them to reset or full state updates.
-@@ -2081,6 +2090,9 @@ static int kvm_get_msrs(X86CPU *cpu)
- if (has_msr_xss) {
- kvm_msr_entry_add(cpu, MSR_IA32_XSS, 0);
- }
-+ if (has_msr_spec_ctrl) {
-+ kvm_msr_entry_add(cpu, MSR_IA32_SPEC_CTRL, 0);
-+ }
-
-
- if (!env->tsc_valid) {
-@@ -2430,6 +2442,9 @@ static int kvm_get_msrs(X86CPU *cpu)
- env->mtrr_var[MSR_MTRRphysIndex(index)].base = msrs[i].data;
- }
- break;
-+ case MSR_IA32_SPEC_CTRL:
-+ env->spec_ctrl = msrs[i].data;
-+ break;
- }
- }
-
-diff --git a/target/i386/machine.c b/target/i386/machine.c
-index 78ae2f986b..8c0d5437fa 100644
---- a/target/i386/machine.c
-+++ b/target/i386/machine.c
-@@ -927,6 +927,25 @@ static const VMStateDescription vmstate_mcg_ext_ctl = {
- }
- };
-
-+static bool spec_ctrl_needed(void *opaque)
-+{
-+ X86CPU *cpu = opaque;
-+ CPUX86State *env = &cpu->env;
-+
-+ return env->spec_ctrl != 0;
-+}
-+
-+static const VMStateDescription vmstate_spec_ctrl = {
-+ .name = "cpu/spec_ctrl",
-+ .version_id = 1,
-+ .minimum_version_id = 1,
-+ .needed = spec_ctrl_needed,
-+ .fields = (VMStateField[]){
-+ VMSTATE_UINT64(env.spec_ctrl, X86CPU),
-+ VMSTATE_END_OF_LIST()
-+ }
-+};
-+
- VMStateDescription vmstate_x86_cpu = {
- .name = "cpu",
- .version_id = 12,
-@@ -1053,6 +1072,7 @@ VMStateDescription vmstate_x86_cpu = {
- #ifdef TARGET_X86_64
- &vmstate_pkru,
- #endif
-+ &vmstate_spec_ctrl,
- &vmstate_mcg_ext_ctl,
- NULL
- }
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eduardo Habkost <ehabkost@redhat.com>
-Date: Tue, 9 Jan 2018 13:45:15 -0200
-Subject: [PATCH] i386: Add spec-ctrl CPUID bit
-
-Add the feature name and a CPUID_7_0_EDX_SPEC_CTRL macro.
-
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
----
- target/i386/cpu.c | 2 +-
- target/i386/cpu.h | 1 +
- 2 files changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/target/i386/cpu.c b/target/i386/cpu.c
-index c673521016..faf1ff6dcc 100644
---- a/target/i386/cpu.c
-+++ b/target/i386/cpu.c
-@@ -460,7 +460,7 @@ static FeatureWordInfo feature_word_info[FEATURE_WORDS] = {
- NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
-- NULL, NULL, NULL, NULL,
-+ NULL, NULL, "spec-ctrl", NULL,
- NULL, NULL, NULL, NULL,
- },
- .cpuid_eax = 7,
-diff --git a/target/i386/cpu.h b/target/i386/cpu.h
-index cc322d6b39..71261f4819 100644
---- a/target/i386/cpu.h
-+++ b/target/i386/cpu.h
-@@ -640,6 +640,7 @@ typedef uint32_t FeatureWordArray[FEATURE_WORDS];
-
- #define CPUID_7_0_EDX_AVX512_4VNNIW (1U << 2) /* AVX512 Neural Network Instructions */
- #define CPUID_7_0_EDX_AVX512_4FMAPS (1U << 3) /* AVX512 Multiply Accumulation Single Precision */
-+#define CPUID_7_0_EDX_SPEC_CTRL (1U << 26) /* Speculation Control */
-
- #define CPUID_XSAVE_XSAVEOPT (1U << 0)
- #define CPUID_XSAVE_XSAVEC (1U << 1)
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eduardo Habkost <ehabkost@redhat.com>
-Date: Tue, 9 Jan 2018 13:45:16 -0200
-Subject: [PATCH] i386: Add FEAT_8000_0008_EBX CPUID feature word
-
-Add the new feature word and the "ibpb" feature flag.
-
-Based on a patch by Paolo Bonzini.
-
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
----
- target/i386/cpu.c | 19 ++++++++++++++++++-
- target/i386/cpu.h | 3 +++
- 2 files changed, 21 insertions(+), 1 deletion(-)
-
-diff --git a/target/i386/cpu.c b/target/i386/cpu.c
-index faf1ff6dcc..eee365b78d 100644
---- a/target/i386/cpu.c
-+++ b/target/i386/cpu.c
-@@ -484,6 +484,22 @@ static FeatureWordInfo feature_word_info[FEATURE_WORDS] = {
- .tcg_features = TCG_APM_FEATURES,
- .unmigratable_flags = CPUID_APM_INVTSC,
- },
-+ [FEAT_8000_0008_EBX] = {
-+ .feat_names = {
-+ NULL, NULL, NULL, NULL,
-+ NULL, NULL, NULL, NULL,
-+ NULL, NULL, NULL, NULL,
-+ "ibpb", NULL, NULL, NULL,
-+ NULL, NULL, NULL, NULL,
-+ NULL, NULL, NULL, NULL,
-+ NULL, NULL, NULL, NULL,
-+ NULL, NULL, NULL, NULL,
-+ },
-+ .cpuid_eax = 0x80000008,
-+ .cpuid_reg = R_EBX,
-+ .tcg_features = 0,
-+ .unmigratable_flags = 0,
-+ },
- [FEAT_XSAVE] = {
- .feat_names = {
- "xsaveopt", "xsavec", "xgetbv1", "xsaves",
-@@ -2984,7 +3000,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count,
- } else {
- *eax = cpu->phys_bits;
- }
-- *ebx = 0;
-+ *ebx = env->features[FEAT_8000_0008_EBX];
- *ecx = 0;
- *edx = 0;
- if (cs->nr_cores * cs->nr_threads > 1) {
-@@ -3440,6 +3456,7 @@ static void x86_cpu_expand_features(X86CPU *cpu, Error **errp)
- x86_cpu_adjust_feat_level(cpu, FEAT_8000_0001_EDX);
- x86_cpu_adjust_feat_level(cpu, FEAT_8000_0001_ECX);
- x86_cpu_adjust_feat_level(cpu, FEAT_8000_0007_EDX);
-+ x86_cpu_adjust_feat_level(cpu, FEAT_8000_0008_EBX);
- x86_cpu_adjust_feat_level(cpu, FEAT_C000_0001_EDX);
- x86_cpu_adjust_feat_level(cpu, FEAT_SVM);
- x86_cpu_adjust_feat_level(cpu, FEAT_XSAVE);
-diff --git a/target/i386/cpu.h b/target/i386/cpu.h
-index 71261f4819..1ebee91930 100644
---- a/target/i386/cpu.h
-+++ b/target/i386/cpu.h
-@@ -452,6 +452,7 @@ typedef enum FeatureWord {
- FEAT_8000_0001_EDX, /* CPUID[8000_0001].EDX */
- FEAT_8000_0001_ECX, /* CPUID[8000_0001].ECX */
- FEAT_8000_0007_EDX, /* CPUID[8000_0007].EDX */
-+ FEAT_8000_0008_EBX, /* CPUID[8000_0008].EBX */
- FEAT_C000_0001_EDX, /* CPUID[C000_0001].EDX */
- FEAT_KVM, /* CPUID[4000_0001].EAX (KVM_CPUID_FEATURES) */
- FEAT_HYPERV_EAX, /* CPUID[4000_0003].EAX */
-@@ -642,6 +643,8 @@ typedef uint32_t FeatureWordArray[FEATURE_WORDS];
- #define CPUID_7_0_EDX_AVX512_4FMAPS (1U << 3) /* AVX512 Multiply Accumulation Single Precision */
- #define CPUID_7_0_EDX_SPEC_CTRL (1U << 26) /* Speculation Control */
-
-+#define CPUID_8000_0008_EBX_IBPB (1U << 12) /* Indirect Branch Prediction Barrier */
-+
- #define CPUID_XSAVE_XSAVEOPT (1U << 0)
- #define CPUID_XSAVE_XSAVEC (1U << 1)
- #define CPUID_XSAVE_XGETBV1 (1U << 2)
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eduardo Habkost <ehabkost@redhat.com>
-Date: Tue, 9 Jan 2018 13:45:17 -0200
-Subject: [PATCH] i386: Add new -IBRS versions of Intel CPU models
-
-The new MSR IA32_SPEC_CTRL MSR was introduced by a recent Intel
-microcode updated and can be used by OSes to mitigate
-CVE-2017-5715. Unfortunately we can't change the existing CPU
-models without breaking existing setups, so users need to
-explicitly update their VM configuration to use the new *-IBRS
-CPU model if they want to expose IBRS to guests.
-
-The new CPU models are simple copies of the existing CPU models,
-with just CPUID_7_0_EDX_SPEC_CTRL added and model_id updated.
-
-Cc: Jiri Denemark <jdenemar@redhat.com>
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
----
- target/i386/cpu.c | 427 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
- 1 file changed, 426 insertions(+), 1 deletion(-)
-
-diff --git a/target/i386/cpu.c b/target/i386/cpu.c
-index eee365b78d..e4a2d5a012 100644
---- a/target/i386/cpu.c
-+++ b/target/i386/cpu.c
-@@ -1085,6 +1085,31 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .model_id = "Intel Core i7 9xx (Nehalem Class Core i7)",
- },
- {
-+ .name = "Nehalem-IBRS",
-+ .level = 11,
-+ .vendor = CPUID_VENDOR_INTEL,
-+ .family = 6,
-+ .model = 26,
-+ .stepping = 3,
-+ .features[FEAT_1_EDX] =
-+ CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX |
-+ CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA |
-+ CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 |
-+ CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE |
-+ CPUID_DE | CPUID_FP87,
-+ .features[FEAT_1_ECX] =
-+ CPUID_EXT_POPCNT | CPUID_EXT_SSE42 | CPUID_EXT_SSE41 |
-+ CPUID_EXT_CX16 | CPUID_EXT_SSSE3 | CPUID_EXT_SSE3,
-+ .features[FEAT_7_0_EDX] =
-+ CPUID_7_0_EDX_SPEC_CTRL,
-+ .features[FEAT_8000_0001_EDX] =
-+ CPUID_EXT2_LM | CPUID_EXT2_SYSCALL | CPUID_EXT2_NX,
-+ .features[FEAT_8000_0001_ECX] =
-+ CPUID_EXT3_LAHF_LM,
-+ .xlevel = 0x80000008,
-+ .model_id = "Intel Core i7 9xx (Nehalem Core i7, IBRS update)",
-+ },
-+ {
- .name = "Westmere",
- .level = 11,
- .vendor = CPUID_VENDOR_INTEL,
-@@ -1111,6 +1136,34 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .model_id = "Westmere E56xx/L56xx/X56xx (Nehalem-C)",
- },
- {
-+ .name = "Westmere-IBRS",
-+ .level = 11,
-+ .vendor = CPUID_VENDOR_INTEL,
-+ .family = 6,
-+ .model = 44,
-+ .stepping = 1,
-+ .features[FEAT_1_EDX] =
-+ CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX |
-+ CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA |
-+ CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 |
-+ CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE |
-+ CPUID_DE | CPUID_FP87,
-+ .features[FEAT_1_ECX] =
-+ CPUID_EXT_AES | CPUID_EXT_POPCNT | CPUID_EXT_SSE42 |
-+ CPUID_EXT_SSE41 | CPUID_EXT_CX16 | CPUID_EXT_SSSE3 |
-+ CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSE3,
-+ .features[FEAT_8000_0001_EDX] =
-+ CPUID_EXT2_LM | CPUID_EXT2_SYSCALL | CPUID_EXT2_NX,
-+ .features[FEAT_8000_0001_ECX] =
-+ CPUID_EXT3_LAHF_LM,
-+ .features[FEAT_7_0_EDX] =
-+ CPUID_7_0_EDX_SPEC_CTRL,
-+ .features[FEAT_6_EAX] =
-+ CPUID_6_EAX_ARAT,
-+ .xlevel = 0x80000008,
-+ .model_id = "Westmere E56xx/L56xx/X56xx (IBRS update)",
-+ },
-+ {
- .name = "SandyBridge",
- .level = 0xd,
- .vendor = CPUID_VENDOR_INTEL,
-@@ -1142,6 +1195,39 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .model_id = "Intel Xeon E312xx (Sandy Bridge)",
- },
- {
-+ .name = "SandyBridge-IBRS",
-+ .level = 0xd,
-+ .vendor = CPUID_VENDOR_INTEL,
-+ .family = 6,
-+ .model = 42,
-+ .stepping = 1,
-+ .features[FEAT_1_EDX] =
-+ CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX |
-+ CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA |
-+ CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 |
-+ CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE |
-+ CPUID_DE | CPUID_FP87,
-+ .features[FEAT_1_ECX] =
-+ CPUID_EXT_AVX | CPUID_EXT_XSAVE | CPUID_EXT_AES |
-+ CPUID_EXT_TSC_DEADLINE_TIMER | CPUID_EXT_POPCNT |
-+ CPUID_EXT_X2APIC | CPUID_EXT_SSE42 | CPUID_EXT_SSE41 |
-+ CPUID_EXT_CX16 | CPUID_EXT_SSSE3 | CPUID_EXT_PCLMULQDQ |
-+ CPUID_EXT_SSE3,
-+ .features[FEAT_8000_0001_EDX] =
-+ CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_NX |
-+ CPUID_EXT2_SYSCALL,
-+ .features[FEAT_8000_0001_ECX] =
-+ CPUID_EXT3_LAHF_LM,
-+ .features[FEAT_7_0_EDX] =
-+ CPUID_7_0_EDX_SPEC_CTRL,
-+ .features[FEAT_XSAVE] =
-+ CPUID_XSAVE_XSAVEOPT,
-+ .features[FEAT_6_EAX] =
-+ CPUID_6_EAX_ARAT,
-+ .xlevel = 0x80000008,
-+ .model_id = "Intel Xeon E312xx (Sandy Bridge, IBRS update)",
-+ },
-+ {
- .name = "IvyBridge",
- .level = 0xd,
- .vendor = CPUID_VENDOR_INTEL,
-@@ -1176,6 +1262,42 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .model_id = "Intel Xeon E3-12xx v2 (Ivy Bridge)",
- },
- {
-+ .name = "IvyBridge-IBRS",
-+ .level = 0xd,
-+ .vendor = CPUID_VENDOR_INTEL,
-+ .family = 6,
-+ .model = 58,
-+ .stepping = 9,
-+ .features[FEAT_1_EDX] =
-+ CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX |
-+ CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA |
-+ CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 |
-+ CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE |
-+ CPUID_DE | CPUID_FP87,
-+ .features[FEAT_1_ECX] =
-+ CPUID_EXT_AVX | CPUID_EXT_XSAVE | CPUID_EXT_AES |
-+ CPUID_EXT_TSC_DEADLINE_TIMER | CPUID_EXT_POPCNT |
-+ CPUID_EXT_X2APIC | CPUID_EXT_SSE42 | CPUID_EXT_SSE41 |
-+ CPUID_EXT_CX16 | CPUID_EXT_SSSE3 | CPUID_EXT_PCLMULQDQ |
-+ CPUID_EXT_SSE3 | CPUID_EXT_F16C | CPUID_EXT_RDRAND,
-+ .features[FEAT_7_0_EBX] =
-+ CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_SMEP |
-+ CPUID_7_0_EBX_ERMS,
-+ .features[FEAT_8000_0001_EDX] =
-+ CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_NX |
-+ CPUID_EXT2_SYSCALL,
-+ .features[FEAT_8000_0001_ECX] =
-+ CPUID_EXT3_LAHF_LM,
-+ .features[FEAT_7_0_EDX] =
-+ CPUID_7_0_EDX_SPEC_CTRL,
-+ .features[FEAT_XSAVE] =
-+ CPUID_XSAVE_XSAVEOPT,
-+ .features[FEAT_6_EAX] =
-+ CPUID_6_EAX_ARAT,
-+ .xlevel = 0x80000008,
-+ .model_id = "Intel Xeon E3-12xx v2 (Ivy Bridge, IBRS)",
-+ },
-+ {
- .name = "Haswell-noTSX",
- .level = 0xd,
- .vendor = CPUID_VENDOR_INTEL,
-@@ -1210,7 +1332,46 @@ static X86CPUDefinition builtin_x86_defs[] = {
- CPUID_6_EAX_ARAT,
- .xlevel = 0x80000008,
- .model_id = "Intel Core Processor (Haswell, no TSX)",
-- }, {
-+ },
-+ {
-+ .name = "Haswell-noTSX-IBRS",
-+ .level = 0xd,
-+ .vendor = CPUID_VENDOR_INTEL,
-+ .family = 6,
-+ .model = 60,
-+ .stepping = 1,
-+ .features[FEAT_1_EDX] =
-+ CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX |
-+ CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA |
-+ CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 |
-+ CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE |
-+ CPUID_DE | CPUID_FP87,
-+ .features[FEAT_1_ECX] =
-+ CPUID_EXT_AVX | CPUID_EXT_XSAVE | CPUID_EXT_AES |
-+ CPUID_EXT_POPCNT | CPUID_EXT_X2APIC | CPUID_EXT_SSE42 |
-+ CPUID_EXT_SSE41 | CPUID_EXT_CX16 | CPUID_EXT_SSSE3 |
-+ CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSE3 |
-+ CPUID_EXT_TSC_DEADLINE_TIMER | CPUID_EXT_FMA | CPUID_EXT_MOVBE |
-+ CPUID_EXT_PCID | CPUID_EXT_F16C | CPUID_EXT_RDRAND,
-+ .features[FEAT_8000_0001_EDX] =
-+ CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_NX |
-+ CPUID_EXT2_SYSCALL,
-+ .features[FEAT_8000_0001_ECX] =
-+ CPUID_EXT3_ABM | CPUID_EXT3_LAHF_LM,
-+ .features[FEAT_7_0_EDX] =
-+ CPUID_7_0_EDX_SPEC_CTRL,
-+ .features[FEAT_7_0_EBX] =
-+ CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_BMI1 |
-+ CPUID_7_0_EBX_AVX2 | CPUID_7_0_EBX_SMEP |
-+ CPUID_7_0_EBX_BMI2 | CPUID_7_0_EBX_ERMS | CPUID_7_0_EBX_INVPCID,
-+ .features[FEAT_XSAVE] =
-+ CPUID_XSAVE_XSAVEOPT,
-+ .features[FEAT_6_EAX] =
-+ CPUID_6_EAX_ARAT,
-+ .xlevel = 0x80000008,
-+ .model_id = "Intel Core Processor (Haswell, no TSX, IBRS)",
-+ },
-+ {
- .name = "Haswell",
- .level = 0xd,
- .vendor = CPUID_VENDOR_INTEL,
-@@ -1248,6 +1409,45 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .model_id = "Intel Core Processor (Haswell)",
- },
- {
-+ .name = "Haswell-IBRS",
-+ .level = 0xd,
-+ .vendor = CPUID_VENDOR_INTEL,
-+ .family = 6,
-+ .model = 60,
-+ .stepping = 4,
-+ .features[FEAT_1_EDX] =
-+ CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX |
-+ CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA |
-+ CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 |
-+ CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE |
-+ CPUID_DE | CPUID_FP87,
-+ .features[FEAT_1_ECX] =
-+ CPUID_EXT_AVX | CPUID_EXT_XSAVE | CPUID_EXT_AES |
-+ CPUID_EXT_POPCNT | CPUID_EXT_X2APIC | CPUID_EXT_SSE42 |
-+ CPUID_EXT_SSE41 | CPUID_EXT_CX16 | CPUID_EXT_SSSE3 |
-+ CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSE3 |
-+ CPUID_EXT_TSC_DEADLINE_TIMER | CPUID_EXT_FMA | CPUID_EXT_MOVBE |
-+ CPUID_EXT_PCID | CPUID_EXT_F16C | CPUID_EXT_RDRAND,
-+ .features[FEAT_8000_0001_EDX] =
-+ CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_NX |
-+ CPUID_EXT2_SYSCALL,
-+ .features[FEAT_8000_0001_ECX] =
-+ CPUID_EXT3_ABM | CPUID_EXT3_LAHF_LM,
-+ .features[FEAT_7_0_EDX] =
-+ CPUID_7_0_EDX_SPEC_CTRL,
-+ .features[FEAT_7_0_EBX] =
-+ CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_BMI1 |
-+ CPUID_7_0_EBX_HLE | CPUID_7_0_EBX_AVX2 | CPUID_7_0_EBX_SMEP |
-+ CPUID_7_0_EBX_BMI2 | CPUID_7_0_EBX_ERMS | CPUID_7_0_EBX_INVPCID |
-+ CPUID_7_0_EBX_RTM,
-+ .features[FEAT_XSAVE] =
-+ CPUID_XSAVE_XSAVEOPT,
-+ .features[FEAT_6_EAX] =
-+ CPUID_6_EAX_ARAT,
-+ .xlevel = 0x80000008,
-+ .model_id = "Intel Core Processor (Haswell, IBRS)",
-+ },
-+ {
- .name = "Broadwell-noTSX",
- .level = 0xd,
- .vendor = CPUID_VENDOR_INTEL,
-@@ -1286,6 +1486,46 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .model_id = "Intel Core Processor (Broadwell, no TSX)",
- },
- {
-+ .name = "Broadwell-noTSX-IBRS",
-+ .level = 0xd,
-+ .vendor = CPUID_VENDOR_INTEL,
-+ .family = 6,
-+ .model = 61,
-+ .stepping = 2,
-+ .features[FEAT_1_EDX] =
-+ CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX |
-+ CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA |
-+ CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 |
-+ CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE |
-+ CPUID_DE | CPUID_FP87,
-+ .features[FEAT_1_ECX] =
-+ CPUID_EXT_AVX | CPUID_EXT_XSAVE | CPUID_EXT_AES |
-+ CPUID_EXT_POPCNT | CPUID_EXT_X2APIC | CPUID_EXT_SSE42 |
-+ CPUID_EXT_SSE41 | CPUID_EXT_CX16 | CPUID_EXT_SSSE3 |
-+ CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSE3 |
-+ CPUID_EXT_TSC_DEADLINE_TIMER | CPUID_EXT_FMA | CPUID_EXT_MOVBE |
-+ CPUID_EXT_PCID | CPUID_EXT_F16C | CPUID_EXT_RDRAND,
-+ .features[FEAT_8000_0001_EDX] =
-+ CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_NX |
-+ CPUID_EXT2_SYSCALL,
-+ .features[FEAT_8000_0001_ECX] =
-+ CPUID_EXT3_ABM | CPUID_EXT3_LAHF_LM | CPUID_EXT3_3DNOWPREFETCH,
-+ .features[FEAT_7_0_EDX] =
-+ CPUID_7_0_EDX_SPEC_CTRL,
-+ .features[FEAT_7_0_EBX] =
-+ CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_BMI1 |
-+ CPUID_7_0_EBX_AVX2 | CPUID_7_0_EBX_SMEP |
-+ CPUID_7_0_EBX_BMI2 | CPUID_7_0_EBX_ERMS | CPUID_7_0_EBX_INVPCID |
-+ CPUID_7_0_EBX_RDSEED | CPUID_7_0_EBX_ADX |
-+ CPUID_7_0_EBX_SMAP,
-+ .features[FEAT_XSAVE] =
-+ CPUID_XSAVE_XSAVEOPT,
-+ .features[FEAT_6_EAX] =
-+ CPUID_6_EAX_ARAT,
-+ .xlevel = 0x80000008,
-+ .model_id = "Intel Core Processor (Broadwell, no TSX, IBRS)",
-+ },
-+ {
- .name = "Broadwell",
- .level = 0xd,
- .vendor = CPUID_VENDOR_INTEL,
-@@ -1324,6 +1564,46 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .model_id = "Intel Core Processor (Broadwell)",
- },
- {
-+ .name = "Broadwell-IBRS",
-+ .level = 0xd,
-+ .vendor = CPUID_VENDOR_INTEL,
-+ .family = 6,
-+ .model = 61,
-+ .stepping = 2,
-+ .features[FEAT_1_EDX] =
-+ CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX |
-+ CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA |
-+ CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 |
-+ CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE |
-+ CPUID_DE | CPUID_FP87,
-+ .features[FEAT_1_ECX] =
-+ CPUID_EXT_AVX | CPUID_EXT_XSAVE | CPUID_EXT_AES |
-+ CPUID_EXT_POPCNT | CPUID_EXT_X2APIC | CPUID_EXT_SSE42 |
-+ CPUID_EXT_SSE41 | CPUID_EXT_CX16 | CPUID_EXT_SSSE3 |
-+ CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSE3 |
-+ CPUID_EXT_TSC_DEADLINE_TIMER | CPUID_EXT_FMA | CPUID_EXT_MOVBE |
-+ CPUID_EXT_PCID | CPUID_EXT_F16C | CPUID_EXT_RDRAND,
-+ .features[FEAT_8000_0001_EDX] =
-+ CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_NX |
-+ CPUID_EXT2_SYSCALL,
-+ .features[FEAT_8000_0001_ECX] =
-+ CPUID_EXT3_ABM | CPUID_EXT3_LAHF_LM | CPUID_EXT3_3DNOWPREFETCH,
-+ .features[FEAT_7_0_EDX] =
-+ CPUID_7_0_EDX_SPEC_CTRL,
-+ .features[FEAT_7_0_EBX] =
-+ CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_BMI1 |
-+ CPUID_7_0_EBX_HLE | CPUID_7_0_EBX_AVX2 | CPUID_7_0_EBX_SMEP |
-+ CPUID_7_0_EBX_BMI2 | CPUID_7_0_EBX_ERMS | CPUID_7_0_EBX_INVPCID |
-+ CPUID_7_0_EBX_RTM | CPUID_7_0_EBX_RDSEED | CPUID_7_0_EBX_ADX |
-+ CPUID_7_0_EBX_SMAP,
-+ .features[FEAT_XSAVE] =
-+ CPUID_XSAVE_XSAVEOPT,
-+ .features[FEAT_6_EAX] =
-+ CPUID_6_EAX_ARAT,
-+ .xlevel = 0x80000008,
-+ .model_id = "Intel Core Processor (Broadwell, IBRS)",
-+ },
-+ {
- .name = "Skylake-Client",
- .level = 0xd,
- .vendor = CPUID_VENDOR_INTEL,
-@@ -1369,6 +1649,151 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .model_id = "Intel Core Processor (Skylake)",
- },
- {
-+ .name = "Skylake-Client-IBRS",
-+ .level = 0xd,
-+ .vendor = CPUID_VENDOR_INTEL,
-+ .family = 6,
-+ .model = 94,
-+ .stepping = 3,
-+ .features[FEAT_1_EDX] =
-+ CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX |
-+ CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA |
-+ CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 |
-+ CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE |
-+ CPUID_DE | CPUID_FP87,
-+ .features[FEAT_1_ECX] =
-+ CPUID_EXT_AVX | CPUID_EXT_XSAVE | CPUID_EXT_AES |
-+ CPUID_EXT_POPCNT | CPUID_EXT_X2APIC | CPUID_EXT_SSE42 |
-+ CPUID_EXT_SSE41 | CPUID_EXT_CX16 | CPUID_EXT_SSSE3 |
-+ CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSE3 |
-+ CPUID_EXT_TSC_DEADLINE_TIMER | CPUID_EXT_FMA | CPUID_EXT_MOVBE |
-+ CPUID_EXT_PCID | CPUID_EXT_F16C | CPUID_EXT_RDRAND,
-+ .features[FEAT_8000_0001_EDX] =
-+ CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_NX |
-+ CPUID_EXT2_SYSCALL,
-+ .features[FEAT_8000_0001_ECX] =
-+ CPUID_EXT3_ABM | CPUID_EXT3_LAHF_LM | CPUID_EXT3_3DNOWPREFETCH,
-+ .features[FEAT_7_0_EDX] =
-+ CPUID_7_0_EDX_SPEC_CTRL,
-+ .features[FEAT_7_0_EBX] =
-+ CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_BMI1 |
-+ CPUID_7_0_EBX_HLE | CPUID_7_0_EBX_AVX2 | CPUID_7_0_EBX_SMEP |
-+ CPUID_7_0_EBX_BMI2 | CPUID_7_0_EBX_ERMS | CPUID_7_0_EBX_INVPCID |
-+ CPUID_7_0_EBX_RTM | CPUID_7_0_EBX_RDSEED | CPUID_7_0_EBX_ADX |
-+ CPUID_7_0_EBX_SMAP | CPUID_7_0_EBX_MPX,
-+ /* Missing: XSAVES (not supported by some Linux versions,
-+ * including v4.1 to v4.12).
-+ * KVM doesn't yet expose any XSAVES state save component,
-+ * and the only one defined in Skylake (processor tracing)
-+ * probably will block migration anyway.
-+ */
-+ .features[FEAT_XSAVE] =
-+ CPUID_XSAVE_XSAVEOPT | CPUID_XSAVE_XSAVEC |
-+ CPUID_XSAVE_XGETBV1,
-+ .features[FEAT_6_EAX] =
-+ CPUID_6_EAX_ARAT,
-+ .xlevel = 0x80000008,
-+ .model_id = "Intel Core Processor (Skylake, IBRS)",
-+ },
-+ {
-+ .name = "Skylake-Server",
-+ .level = 0xd,
-+ .vendor = CPUID_VENDOR_INTEL,
-+ .family = 6,
-+ .model = 85,
-+ .stepping = 4,
-+ .features[FEAT_1_EDX] =
-+ CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX |
-+ CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA |
-+ CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 |
-+ CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE |
-+ CPUID_DE | CPUID_FP87,
-+ .features[FEAT_1_ECX] =
-+ CPUID_EXT_AVX | CPUID_EXT_XSAVE | CPUID_EXT_AES |
-+ CPUID_EXT_POPCNT | CPUID_EXT_X2APIC | CPUID_EXT_SSE42 |
-+ CPUID_EXT_SSE41 | CPUID_EXT_CX16 | CPUID_EXT_SSSE3 |
-+ CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSE3 |
-+ CPUID_EXT_TSC_DEADLINE_TIMER | CPUID_EXT_FMA | CPUID_EXT_MOVBE |
-+ CPUID_EXT_PCID | CPUID_EXT_F16C | CPUID_EXT_RDRAND,
-+ .features[FEAT_8000_0001_EDX] =
-+ CPUID_EXT2_LM | CPUID_EXT2_PDPE1GB | CPUID_EXT2_RDTSCP |
-+ CPUID_EXT2_NX | CPUID_EXT2_SYSCALL,
-+ .features[FEAT_8000_0001_ECX] =
-+ CPUID_EXT3_ABM | CPUID_EXT3_LAHF_LM | CPUID_EXT3_3DNOWPREFETCH,
-+ .features[FEAT_7_0_EBX] =
-+ CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_BMI1 |
-+ CPUID_7_0_EBX_HLE | CPUID_7_0_EBX_AVX2 | CPUID_7_0_EBX_SMEP |
-+ CPUID_7_0_EBX_BMI2 | CPUID_7_0_EBX_ERMS | CPUID_7_0_EBX_INVPCID |
-+ CPUID_7_0_EBX_RTM | CPUID_7_0_EBX_RDSEED | CPUID_7_0_EBX_ADX |
-+ CPUID_7_0_EBX_SMAP | CPUID_7_0_EBX_MPX | CPUID_7_0_EBX_CLWB |
-+ CPUID_7_0_EBX_AVX512F | CPUID_7_0_EBX_AVX512DQ |
-+ CPUID_7_0_EBX_AVX512BW | CPUID_7_0_EBX_AVX512CD |
-+ CPUID_7_0_EBX_AVX512VL,
-+ /* Missing: XSAVES (not supported by some Linux versions,
-+ * including v4.1 to v4.12).
-+ * KVM doesn't yet expose any XSAVES state save component,
-+ * and the only one defined in Skylake (processor tracing)
-+ * probably will block migration anyway.
-+ */
-+ .features[FEAT_XSAVE] =
-+ CPUID_XSAVE_XSAVEOPT | CPUID_XSAVE_XSAVEC |
-+ CPUID_XSAVE_XGETBV1,
-+ .features[FEAT_6_EAX] =
-+ CPUID_6_EAX_ARAT,
-+ .xlevel = 0x80000008,
-+ .model_id = "Intel Xeon Processor (Skylake)",
-+ },
-+ {
-+ .name = "Skylake-Server-IBRS",
-+ .level = 0xd,
-+ .vendor = CPUID_VENDOR_INTEL,
-+ .family = 6,
-+ .model = 85,
-+ .stepping = 4,
-+ .features[FEAT_1_EDX] =
-+ CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX |
-+ CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA |
-+ CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 |
-+ CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE |
-+ CPUID_DE | CPUID_FP87,
-+ .features[FEAT_1_ECX] =
-+ CPUID_EXT_AVX | CPUID_EXT_XSAVE | CPUID_EXT_AES |
-+ CPUID_EXT_POPCNT | CPUID_EXT_X2APIC | CPUID_EXT_SSE42 |
-+ CPUID_EXT_SSE41 | CPUID_EXT_CX16 | CPUID_EXT_SSSE3 |
-+ CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSE3 |
-+ CPUID_EXT_TSC_DEADLINE_TIMER | CPUID_EXT_FMA | CPUID_EXT_MOVBE |
-+ CPUID_EXT_PCID | CPUID_EXT_F16C | CPUID_EXT_RDRAND,
-+ .features[FEAT_8000_0001_EDX] =
-+ CPUID_EXT2_LM | CPUID_EXT2_PDPE1GB | CPUID_EXT2_RDTSCP |
-+ CPUID_EXT2_NX | CPUID_EXT2_SYSCALL,
-+ .features[FEAT_8000_0001_ECX] =
-+ CPUID_EXT3_ABM | CPUID_EXT3_LAHF_LM | CPUID_EXT3_3DNOWPREFETCH,
-+ .features[FEAT_7_0_EDX] =
-+ CPUID_7_0_EDX_SPEC_CTRL,
-+ .features[FEAT_7_0_EBX] =
-+ CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_BMI1 |
-+ CPUID_7_0_EBX_HLE | CPUID_7_0_EBX_AVX2 | CPUID_7_0_EBX_SMEP |
-+ CPUID_7_0_EBX_BMI2 | CPUID_7_0_EBX_ERMS | CPUID_7_0_EBX_INVPCID |
-+ CPUID_7_0_EBX_RTM | CPUID_7_0_EBX_RDSEED | CPUID_7_0_EBX_ADX |
-+ CPUID_7_0_EBX_SMAP | CPUID_7_0_EBX_MPX | CPUID_7_0_EBX_CLWB |
-+ CPUID_7_0_EBX_AVX512F | CPUID_7_0_EBX_AVX512DQ |
-+ CPUID_7_0_EBX_AVX512BW | CPUID_7_0_EBX_AVX512CD |
-+ CPUID_7_0_EBX_AVX512VL,
-+ /* Missing: XSAVES (not supported by some Linux versions,
-+ * including v4.1 to v4.12).
-+ * KVM doesn't yet expose any XSAVES state save component,
-+ * and the only one defined in Skylake (processor tracing)
-+ * probably will block migration anyway.
-+ */
-+ .features[FEAT_XSAVE] =
-+ CPUID_XSAVE_XSAVEOPT | CPUID_XSAVE_XSAVEC |
-+ CPUID_XSAVE_XGETBV1,
-+ .features[FEAT_6_EAX] =
-+ CPUID_6_EAX_ARAT,
-+ .xlevel = 0x80000008,
-+ .model_id = "Intel Xeon Processor (Skylake, IBRS)",
-+ },
-+ {
- .name = "Opteron_G1",
- .level = 5,
- .vendor = CPUID_VENDOR_AMD,
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Tue, 6 Feb 2018 11:34:34 +0100
-Subject: [PATCH] ratelimit: don't align wait time with slices
-
-It is possible for rate limited writes to keep overshooting a slice's
-quota by a tiny amount causing the slice-aligned waiting period to
-effectively halve the rate.
-
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
----
- include/qemu/ratelimit.h | 11 +++++------
- 1 file changed, 5 insertions(+), 6 deletions(-)
-
-diff --git a/include/qemu/ratelimit.h b/include/qemu/ratelimit.h
-index 8da1232574..324ff04fe2 100644
---- a/include/qemu/ratelimit.h
-+++ b/include/qemu/ratelimit.h
-@@ -35,7 +35,7 @@ typedef struct {
- static inline int64_t ratelimit_calculate_delay(RateLimit *limit, uint64_t n)
- {
- int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
-- uint64_t delay_slices;
-+ double delay_slices;
-
- assert(limit->slice_quota && limit->slice_ns);
-
-@@ -54,12 +54,11 @@ static inline int64_t ratelimit_calculate_delay(RateLimit *limit, uint64_t n)
- return 0;
- }
-
-- /* Quota exceeded. Calculate the next time slice we may start
-- * sending data again. */
-- delay_slices = (limit->dispatched + limit->slice_quota - 1) /
-- limit->slice_quota;
-+ /* Quota exceeded. Wait based on the excess amount and then start a new
-+ * slice. */
-+ delay_slices = (double)limit->dispatched / limit->slice_quota;
- limit->slice_end_time = limit->slice_start_time +
-- delay_slices * limit->slice_ns;
-+ (uint64_t)(delay_slices * limit->slice_ns);
- return limit->slice_end_time - now;
- }
-
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Paolo Bonzini <pbonzini@redhat.com>
-Date: Thu, 1 Jun 2017 12:44:56 +0200
-Subject: [PATCH] nbd: make it thread-safe, fix qcow2 over nbd
-
-NBD is not thread safe, because it accesses s->in_flight without
-a CoMutex. Fixing this will be required for multiqueue.
-CoQueue doesn't have spurious wakeups but, when another coroutine can
-run between qemu_co_queue_next's wakeup and qemu_co_queue_wait's
-re-locking of the mutex, the wait condition can become false and
-a loop is necessary.
-
-In fact, it turns out that the loop is necessary even without this
-multi-threaded scenario. A particular sequence of coroutine wakeups
-is happening ~80% of the time when starting a guest with qcow2 image
-served over NBD (i.e. qemu-nbd --format=raw, and QEMU's -drive option
-has -format=qcow2). This patch fixes that issue too.
-
-Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
----
- block/nbd-client.c | 30 +++++++++---------------------
- 1 file changed, 9 insertions(+), 21 deletions(-)
-
-diff --git a/block/nbd-client.c b/block/nbd-client.c
-index 56eb0e2e16..282679b4f8 100644
---- a/block/nbd-client.c
-+++ b/block/nbd-client.c
-@@ -114,6 +114,10 @@ static int nbd_co_send_request(BlockDriverState *bs,
- int rc, ret, i;
-
- qemu_co_mutex_lock(&s->send_mutex);
-+ while (s->in_flight == MAX_NBD_REQUESTS) {
-+ qemu_co_queue_wait(&s->free_sema, &s->send_mutex);
-+ }
-+ s->in_flight++;
-
- for (i = 0; i < MAX_NBD_REQUESTS; i++) {
- if (s->recv_coroutine[i] == NULL) {
-@@ -176,20 +180,6 @@ static void nbd_co_receive_reply(NBDClientSession *s,
- }
- }
-
--static void nbd_coroutine_start(NBDClientSession *s,
-- NBDRequest *request)
--{
-- /* Poor man semaphore. The free_sema is locked when no other request
-- * can be accepted, and unlocked after receiving one reply. */
-- if (s->in_flight == MAX_NBD_REQUESTS) {
-- qemu_co_queue_wait(&s->free_sema, NULL);
-- assert(s->in_flight < MAX_NBD_REQUESTS);
-- }
-- s->in_flight++;
--
-- /* s->recv_coroutine[i] is set as soon as we get the send_lock. */
--}
--
- static void nbd_coroutine_end(BlockDriverState *bs,
- NBDRequest *request)
- {
-@@ -197,13 +187,16 @@ static void nbd_coroutine_end(BlockDriverState *bs,
- int i = HANDLE_TO_INDEX(s, request->handle);
-
- s->recv_coroutine[i] = NULL;
-- s->in_flight--;
-- qemu_co_queue_next(&s->free_sema);
-
- /* Kick the read_reply_co to get the next reply. */
- if (s->read_reply_co) {
- aio_co_wake(s->read_reply_co);
- }
-+
-+ qemu_co_mutex_lock(&s->send_mutex);
-+ s->in_flight--;
-+ qemu_co_queue_next(&s->free_sema);
-+ qemu_co_mutex_unlock(&s->send_mutex);
- }
-
- int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset,
-@@ -221,7 +214,6 @@ int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset,
- assert(bytes <= NBD_MAX_BUFFER_SIZE);
- assert(!flags);
-
-- nbd_coroutine_start(client, &request);
- ret = nbd_co_send_request(bs, &request, NULL);
- if (ret < 0) {
- reply.error = -ret;
-@@ -251,7 +243,6 @@ int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset,
-
- assert(bytes <= NBD_MAX_BUFFER_SIZE);
-
-- nbd_coroutine_start(client, &request);
- ret = nbd_co_send_request(bs, &request, qiov);
- if (ret < 0) {
- reply.error = -ret;
-@@ -286,7 +277,6 @@ int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
- request.flags |= NBD_CMD_FLAG_NO_HOLE;
- }
-
-- nbd_coroutine_start(client, &request);
- ret = nbd_co_send_request(bs, &request, NULL);
- if (ret < 0) {
- reply.error = -ret;
-@@ -311,7 +301,6 @@ int nbd_client_co_flush(BlockDriverState *bs)
- request.from = 0;
- request.len = 0;
-
-- nbd_coroutine_start(client, &request);
- ret = nbd_co_send_request(bs, &request, NULL);
- if (ret < 0) {
- reply.error = -ret;
-@@ -337,7 +326,6 @@ int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int count)
- return 0;
- }
-
-- nbd_coroutine_start(client, &request);
- ret = nbd_co_send_request(bs, &request, NULL);
- if (ret < 0) {
- reply.error = -ret;
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
-Date: Tue, 16 May 2017 12:45:29 +0300
-Subject: [PATCH] nbd: strict nbd_wr_syncv
-
-nbd_wr_syncv is called either from coroutine or from client negotiation
-code, when socket is in blocking mode. So, -EAGAIN is impossible.
-
-Furthermore, EAGAIN is confusing, as, what to read/write again? With
-EAGAIN as a return code we don't know how much data is already
-read or written by the function, so in case of EAGAIN the whole
-communication is broken.
-
-Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
-Message-Id: <20170516094533.6160-2-vsementsov@virtuozzo.com>
-Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
----
- nbd/common.c | 11 ++++++-----
- 1 file changed, 6 insertions(+), 5 deletions(-)
-
-diff --git a/nbd/common.c b/nbd/common.c
-index dccbb8e9de..4db45b3ede 100644
---- a/nbd/common.c
-+++ b/nbd/common.c
-@@ -20,6 +20,10 @@
- #include "qapi/error.h"
- #include "nbd-internal.h"
-
-+/* nbd_wr_syncv
-+ * The function may be called from coroutine or from non-coroutine context.
-+ * When called from non-coroutine context @ioc must be in blocking mode.
-+ */
- ssize_t nbd_wr_syncv(QIOChannel *ioc,
- struct iovec *iov,
- size_t niov,
-@@ -42,11 +46,8 @@ ssize_t nbd_wr_syncv(QIOChannel *ioc,
- len = qio_channel_writev(ioc, local_iov, nlocal_iov, &local_err);
- }
- if (len == QIO_CHANNEL_ERR_BLOCK) {
-- if (qemu_in_coroutine()) {
-- qio_channel_yield(ioc, do_read ? G_IO_IN : G_IO_OUT);
-- } else {
-- return -EAGAIN;
-- }
-+ assert(qemu_in_coroutine());
-+ qio_channel_yield(ioc, do_read ? G_IO_IN : G_IO_OUT);
- continue;
- }
- if (len < 0) {
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
-Date: Tue, 16 May 2017 12:45:30 +0300
-Subject: [PATCH] nbd: read_sync and friends: return 0 on success
-
-functions read_sync, drop_sync, write_sync, and also
-nbd_negotiate_write, nbd_negotiate_read, nbd_negotiate_drop_sync
-returns number of processed bytes. But what this number can be,
-except requested number of bytes?
-
-Actually, underlying nbd_wr_syncv function returns a value >= 0 and
-!= requested_bytes only on eof on read operation. So, firstly, it is
-impossible on write (let's add an assert) and on read it actually
-means, that communication is broken (except nbd_receive_reply, see
-below).
-
-Most of callers operate like this:
- if (func(..., size) != size) {
- /* error path */
- }
-, i.e.:
- 1. They are not interested in partial success
- 2. Extra duplications in code (especially bad are duplications of
- magic numbers)
- 3. User doesn't see actual error message, as return code is lost.
- (this patch doesn't fix this point, but it makes fixing easier)
-
-Several callers handles ret >= 0 and != requested-size separately, by
-just returning EINVAL in this case. This patch makes read_sync and
-friends return EINVAL in this case, so final behavior is the same.
-
-And only one caller - nbd_receive_reply() does something not so
-obvious. It returns EINVAL for ret > 0 and != requested-size, like
-previous group, but for ret == 0 it returns 0. The only caller of
-nbd_receive_reply() - nbd_read_reply_entry() handles ret == 0 in the
-same way as ret < 0, so for now it doesn't matter. However, in
-following commits error path handling will be improved and we'll need
-to distinguish success from fail in this case too. So, this patch adds
-separate helper for this case - read_sync_eof.
-
-Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
-Message-Id: <20170516094533.6160-3-vsementsov@virtuozzo.com>
-Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
----
- nbd/client.c | 63 ++++++++++++++++------------------------
- nbd/nbd-internal.h | 34 +++++++++++++++++++---
- nbd/server.c | 84 +++++++++++++++++++++---------------------------------
- 3 files changed, 88 insertions(+), 93 deletions(-)
-
-diff --git a/nbd/client.c b/nbd/client.c
-index a58fb02cb4..6b74a628f1 100644
---- a/nbd/client.c
-+++ b/nbd/client.c
-@@ -86,9 +86,9 @@ static QTAILQ_HEAD(, NBDExport) exports = QTAILQ_HEAD_INITIALIZER(exports);
-
- */
-
--/* Discard length bytes from channel. Return -errno on failure, or
-- * the amount of bytes consumed. */
--static ssize_t drop_sync(QIOChannel *ioc, size_t size)
-+/* Discard length bytes from channel. Return -errno on failure and 0 on
-+ * success*/
-+static int drop_sync(QIOChannel *ioc, size_t size)
- {
- ssize_t ret = 0;
- char small[1024];
-@@ -96,14 +96,13 @@ static ssize_t drop_sync(QIOChannel *ioc, size_t size)
-
- buffer = sizeof(small) >= size ? small : g_malloc(MIN(65536, size));
- while (size > 0) {
-- ssize_t count = read_sync(ioc, buffer, MIN(65536, size));
-+ ssize_t count = MIN(65536, size);
-+ ret = read_sync(ioc, buffer, MIN(65536, size));
-
-- if (count <= 0) {
-+ if (ret < 0) {
- goto cleanup;
- }
-- assert(count <= size);
- size -= count;
-- ret += count;
- }
-
- cleanup:
-@@ -136,12 +135,12 @@ static int nbd_send_option_request(QIOChannel *ioc, uint32_t opt,
- stl_be_p(&req.option, opt);
- stl_be_p(&req.length, len);
-
-- if (write_sync(ioc, &req, sizeof(req)) != sizeof(req)) {
-+ if (write_sync(ioc, &req, sizeof(req)) < 0) {
- error_setg(errp, "Failed to send option request header");
- return -1;
- }
-
-- if (len && write_sync(ioc, (char *) data, len) != len) {
-+ if (len && write_sync(ioc, (char *) data, len) < 0) {
- error_setg(errp, "Failed to send option request data");
- return -1;
- }
-@@ -170,7 +169,7 @@ static int nbd_receive_option_reply(QIOChannel *ioc, uint32_t opt,
- nbd_opt_reply *reply, Error **errp)
- {
- QEMU_BUILD_BUG_ON(sizeof(*reply) != 20);
-- if (read_sync(ioc, reply, sizeof(*reply)) != sizeof(*reply)) {
-+ if (read_sync(ioc, reply, sizeof(*reply)) < 0) {
- error_setg(errp, "failed to read option reply");
- nbd_send_opt_abort(ioc);
- return -1;
-@@ -219,7 +218,7 @@ static int nbd_handle_reply_err(QIOChannel *ioc, nbd_opt_reply *reply,
- goto cleanup;
- }
- msg = g_malloc(reply->length + 1);
-- if (read_sync(ioc, msg, reply->length) != reply->length) {
-+ if (read_sync(ioc, msg, reply->length) < 0) {
- error_setg(errp, "failed to read option error message");
- goto cleanup;
- }
-@@ -321,7 +320,7 @@ static int nbd_receive_list(QIOChannel *ioc, const char *want, bool *match,
- nbd_send_opt_abort(ioc);
- return -1;
- }
-- if (read_sync(ioc, &namelen, sizeof(namelen)) != sizeof(namelen)) {
-+ if (read_sync(ioc, &namelen, sizeof(namelen)) < 0) {
- error_setg(errp, "failed to read option name length");
- nbd_send_opt_abort(ioc);
- return -1;
-@@ -334,7 +333,7 @@ static int nbd_receive_list(QIOChannel *ioc, const char *want, bool *match,
- return -1;
- }
- if (namelen != strlen(want)) {
-- if (drop_sync(ioc, len) != len) {
-+ if (drop_sync(ioc, len) < 0) {
- error_setg(errp, "failed to skip export name with wrong length");
- nbd_send_opt_abort(ioc);
- return -1;
-@@ -343,14 +342,14 @@ static int nbd_receive_list(QIOChannel *ioc, const char *want, bool *match,
- }
-
- assert(namelen < sizeof(name));
-- if (read_sync(ioc, name, namelen) != namelen) {
-+ if (read_sync(ioc, name, namelen) < 0) {
- error_setg(errp, "failed to read export name");
- nbd_send_opt_abort(ioc);
- return -1;
- }
- name[namelen] = '\0';
- len -= namelen;
-- if (drop_sync(ioc, len) != len) {
-+ if (drop_sync(ioc, len) < 0) {
- error_setg(errp, "failed to read export description");
- nbd_send_opt_abort(ioc);
- return -1;
-@@ -477,7 +476,7 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags,
- goto fail;
- }
-
-- if (read_sync(ioc, buf, 8) != 8) {
-+ if (read_sync(ioc, buf, 8) < 0) {
- error_setg(errp, "Failed to read data");
- goto fail;
- }
-@@ -503,7 +502,7 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags,
- goto fail;
- }
-
-- if (read_sync(ioc, &magic, sizeof(magic)) != sizeof(magic)) {
-+ if (read_sync(ioc, &magic, sizeof(magic)) < 0) {
- error_setg(errp, "Failed to read magic");
- goto fail;
- }
-@@ -515,8 +514,7 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags,
- uint16_t globalflags;
- bool fixedNewStyle = false;
-
-- if (read_sync(ioc, &globalflags, sizeof(globalflags)) !=
-- sizeof(globalflags)) {
-+ if (read_sync(ioc, &globalflags, sizeof(globalflags)) < 0) {
- error_setg(errp, "Failed to read server flags");
- goto fail;
- }
-@@ -534,8 +532,7 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags,
- }
- /* client requested flags */
- clientflags = cpu_to_be32(clientflags);
-- if (write_sync(ioc, &clientflags, sizeof(clientflags)) !=
-- sizeof(clientflags)) {
-+ if (write_sync(ioc, &clientflags, sizeof(clientflags)) < 0) {
- error_setg(errp, "Failed to send clientflags field");
- goto fail;
- }
-@@ -573,13 +570,13 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags,
- }
-
- /* Read the response */
-- if (read_sync(ioc, &s, sizeof(s)) != sizeof(s)) {
-+ if (read_sync(ioc, &s, sizeof(s)) < 0) {
- error_setg(errp, "Failed to read export length");
- goto fail;
- }
- *size = be64_to_cpu(s);
-
-- if (read_sync(ioc, flags, sizeof(*flags)) != sizeof(*flags)) {
-+ if (read_sync(ioc, flags, sizeof(*flags)) < 0) {
- error_setg(errp, "Failed to read export flags");
- goto fail;
- }
-@@ -596,14 +593,14 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags,
- goto fail;
- }
-
-- if (read_sync(ioc, &s, sizeof(s)) != sizeof(s)) {
-+ if (read_sync(ioc, &s, sizeof(s)) < 0) {
- error_setg(errp, "Failed to read export length");
- goto fail;
- }
- *size = be64_to_cpu(s);
- TRACE("Size is %" PRIu64, *size);
-
-- if (read_sync(ioc, &oldflags, sizeof(oldflags)) != sizeof(oldflags)) {
-+ if (read_sync(ioc, &oldflags, sizeof(oldflags)) < 0) {
- error_setg(errp, "Failed to read export flags");
- goto fail;
- }
-@@ -619,7 +616,7 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags,
- }
-
- TRACE("Size is %" PRIu64 ", export flags %" PRIx16, *size, *flags);
-- if (zeroes && drop_sync(ioc, 124) != 124) {
-+ if (zeroes && drop_sync(ioc, 124) < 0) {
- error_setg(errp, "Failed to read reserved block");
- goto fail;
- }
-@@ -744,7 +741,6 @@ int nbd_disconnect(int fd)
- ssize_t nbd_send_request(QIOChannel *ioc, NBDRequest *request)
- {
- uint8_t buf[NBD_REQUEST_SIZE];
-- ssize_t ret;
-
- TRACE("Sending request to server: "
- "{ .from = %" PRIu64", .len = %" PRIu32 ", .handle = %" PRIu64
-@@ -759,16 +755,7 @@ ssize_t nbd_send_request(QIOChannel *ioc, NBDRequest *request)
- stq_be_p(buf + 16, request->from);
- stl_be_p(buf + 24, request->len);
-
-- ret = write_sync(ioc, buf, sizeof(buf));
-- if (ret < 0) {
-- return ret;
-- }
--
-- if (ret != sizeof(buf)) {
-- LOG("writing to socket failed");
-- return -EINVAL;
-- }
-- return 0;
-+ return write_sync(ioc, buf, sizeof(buf));
- }
-
- ssize_t nbd_receive_reply(QIOChannel *ioc, NBDReply *reply)
-@@ -777,7 +764,7 @@ ssize_t nbd_receive_reply(QIOChannel *ioc, NBDReply *reply)
- uint32_t magic;
- ssize_t ret;
-
-- ret = read_sync(ioc, buf, sizeof(buf));
-+ ret = read_sync_eof(ioc, buf, sizeof(buf));
- if (ret <= 0) {
- return ret;
- }
-diff --git a/nbd/nbd-internal.h b/nbd/nbd-internal.h
-index f43d990a05..e6bbc7c4b4 100644
---- a/nbd/nbd-internal.h
-+++ b/nbd/nbd-internal.h
-@@ -94,7 +94,13 @@
- #define NBD_ENOSPC 28
- #define NBD_ESHUTDOWN 108
-
--static inline ssize_t read_sync(QIOChannel *ioc, void *buffer, size_t size)
-+/* read_sync_eof
-+ * Tries to read @size bytes from @ioc. Returns number of bytes actually read.
-+ * May return a value >= 0 and < size only on EOF, i.e. when iteratively called
-+ * qio_channel_readv() returns 0. So, there are no needs to call read_sync_eof
-+ * iteratively.
-+ */
-+static inline ssize_t read_sync_eof(QIOChannel *ioc, void *buffer, size_t size)
- {
- struct iovec iov = { .iov_base = buffer, .iov_len = size };
- /* Sockets are kept in blocking mode in the negotiation phase. After
-@@ -105,12 +111,32 @@ static inline ssize_t read_sync(QIOChannel *ioc, void *buffer, size_t size)
- return nbd_wr_syncv(ioc, &iov, 1, size, true);
- }
-
--static inline ssize_t write_sync(QIOChannel *ioc, const void *buffer,
-- size_t size)
-+/* read_sync
-+ * Reads @size bytes from @ioc. Returns 0 on success.
-+ */
-+static inline int read_sync(QIOChannel *ioc, void *buffer, size_t size)
-+{
-+ ssize_t ret = read_sync_eof(ioc, buffer, size);
-+
-+ if (ret >= 0 && ret != size) {
-+ ret = -EINVAL;
-+ }
-+
-+ return ret < 0 ? ret : 0;
-+}
-+
-+/* write_sync
-+ * Writes @size bytes to @ioc. Returns 0 on success.
-+ */
-+static inline int write_sync(QIOChannel *ioc, const void *buffer, size_t size)
- {
- struct iovec iov = { .iov_base = (void *) buffer, .iov_len = size };
-
-- return nbd_wr_syncv(ioc, &iov, 1, size, false);
-+ ssize_t ret = nbd_wr_syncv(ioc, &iov, 1, size, false);
-+
-+ assert(ret < 0 || ret == size);
-+
-+ return ret < 0 ? ret : 0;
- }
-
- struct NBDTLSHandshakeData {
-diff --git a/nbd/server.c b/nbd/server.c
-index 4d6da8ac06..c2a5909ad6 100644
---- a/nbd/server.c
-+++ b/nbd/server.c
-@@ -112,7 +112,7 @@ static gboolean nbd_negotiate_continue(QIOChannel *ioc,
- return TRUE;
- }
-
--static ssize_t nbd_negotiate_read(QIOChannel *ioc, void *buffer, size_t size)
-+static int nbd_negotiate_read(QIOChannel *ioc, void *buffer, size_t size)
- {
- ssize_t ret;
- guint watch;
-@@ -130,8 +130,7 @@ static ssize_t nbd_negotiate_read(QIOChannel *ioc, void *buffer, size_t size)
-
- }
-
--static ssize_t nbd_negotiate_write(QIOChannel *ioc, const void *buffer,
-- size_t size)
-+static int nbd_negotiate_write(QIOChannel *ioc, const void *buffer, size_t size)
- {
- ssize_t ret;
- guint watch;
-@@ -148,24 +147,24 @@ static ssize_t nbd_negotiate_write(QIOChannel *ioc, const void *buffer,
- return ret;
- }
-
--static ssize_t nbd_negotiate_drop_sync(QIOChannel *ioc, size_t size)
-+static int nbd_negotiate_drop_sync(QIOChannel *ioc, size_t size)
- {
-- ssize_t ret, dropped = size;
-+ ssize_t ret;
- uint8_t *buffer = g_malloc(MIN(65536, size));
-
- while (size > 0) {
-- ret = nbd_negotiate_read(ioc, buffer, MIN(65536, size));
-+ size_t count = MIN(65536, size);
-+ ret = nbd_negotiate_read(ioc, buffer, count);
- if (ret < 0) {
- g_free(buffer);
- return ret;
- }
-
-- assert(ret <= size);
-- size -= ret;
-+ size -= count;
- }
-
- g_free(buffer);
-- return dropped;
-+ return 0;
- }
-
- /* Basic flow for negotiation
-@@ -206,22 +205,22 @@ static int nbd_negotiate_send_rep_len(QIOChannel *ioc, uint32_t type,
- type, opt, len);
-
- magic = cpu_to_be64(NBD_REP_MAGIC);
-- if (nbd_negotiate_write(ioc, &magic, sizeof(magic)) != sizeof(magic)) {
-+ if (nbd_negotiate_write(ioc, &magic, sizeof(magic)) < 0) {
- LOG("write failed (rep magic)");
- return -EINVAL;
- }
- opt = cpu_to_be32(opt);
-- if (nbd_negotiate_write(ioc, &opt, sizeof(opt)) != sizeof(opt)) {
-+ if (nbd_negotiate_write(ioc, &opt, sizeof(opt)) < 0) {
- LOG("write failed (rep opt)");
- return -EINVAL;
- }
- type = cpu_to_be32(type);
-- if (nbd_negotiate_write(ioc, &type, sizeof(type)) != sizeof(type)) {
-+ if (nbd_negotiate_write(ioc, &type, sizeof(type)) < 0) {
- LOG("write failed (rep type)");
- return -EINVAL;
- }
- len = cpu_to_be32(len);
-- if (nbd_negotiate_write(ioc, &len, sizeof(len)) != sizeof(len)) {
-+ if (nbd_negotiate_write(ioc, &len, sizeof(len)) < 0) {
- LOG("write failed (rep data length)");
- return -EINVAL;
- }
-@@ -256,7 +255,7 @@ nbd_negotiate_send_rep_err(QIOChannel *ioc, uint32_t type,
- if (ret < 0) {
- goto out;
- }
-- if (nbd_negotiate_write(ioc, msg, len) != len) {
-+ if (nbd_negotiate_write(ioc, msg, len) < 0) {
- LOG("write failed (error message)");
- ret = -EIO;
- } else {
-@@ -287,15 +286,15 @@ static int nbd_negotiate_send_rep_list(QIOChannel *ioc, NBDExport *exp)
- }
-
- len = cpu_to_be32(name_len);
-- if (nbd_negotiate_write(ioc, &len, sizeof(len)) != sizeof(len)) {
-+ if (nbd_negotiate_write(ioc, &len, sizeof(len)) < 0) {
- LOG("write failed (name length)");
- return -EINVAL;
- }
-- if (nbd_negotiate_write(ioc, name, name_len) != name_len) {
-+ if (nbd_negotiate_write(ioc, name, name_len) < 0) {
- LOG("write failed (name buffer)");
- return -EINVAL;
- }
-- if (nbd_negotiate_write(ioc, desc, desc_len) != desc_len) {
-+ if (nbd_negotiate_write(ioc, desc, desc_len) < 0) {
- LOG("write failed (description buffer)");
- return -EINVAL;
- }
-@@ -309,7 +308,7 @@ static int nbd_negotiate_handle_list(NBDClient *client, uint32_t length)
- NBDExport *exp;
-
- if (length) {
-- if (nbd_negotiate_drop_sync(client->ioc, length) != length) {
-+ if (nbd_negotiate_drop_sync(client->ioc, length) < 0) {
- return -EIO;
- }
- return nbd_negotiate_send_rep_err(client->ioc,
-@@ -340,7 +339,7 @@ static int nbd_negotiate_handle_export_name(NBDClient *client, uint32_t length)
- LOG("Bad length received");
- goto fail;
- }
-- if (nbd_negotiate_read(client->ioc, name, length) != length) {
-+ if (nbd_negotiate_read(client->ioc, name, length) < 0) {
- LOG("read failed");
- goto fail;
- }
-@@ -373,7 +372,7 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
- TRACE("Setting up TLS");
- ioc = client->ioc;
- if (length) {
-- if (nbd_negotiate_drop_sync(ioc, length) != length) {
-+ if (nbd_negotiate_drop_sync(ioc, length) < 0) {
- return NULL;
- }
- nbd_negotiate_send_rep_err(ioc, NBD_REP_ERR_INVALID, NBD_OPT_STARTTLS,
-@@ -437,8 +436,7 @@ static int nbd_negotiate_options(NBDClient *client)
- ... Rest of request
- */
-
-- if (nbd_negotiate_read(client->ioc, &flags, sizeof(flags)) !=
-- sizeof(flags)) {
-+ if (nbd_negotiate_read(client->ioc, &flags, sizeof(flags)) < 0) {
- LOG("read failed");
- return -EIO;
- }
-@@ -464,8 +462,7 @@ static int nbd_negotiate_options(NBDClient *client)
- uint32_t clientflags, length;
- uint64_t magic;
-
-- if (nbd_negotiate_read(client->ioc, &magic, sizeof(magic)) !=
-- sizeof(magic)) {
-+ if (nbd_negotiate_read(client->ioc, &magic, sizeof(magic)) < 0) {
- LOG("read failed");
- return -EINVAL;
- }
-@@ -476,14 +473,14 @@ static int nbd_negotiate_options(NBDClient *client)
- }
-
- if (nbd_negotiate_read(client->ioc, &clientflags,
-- sizeof(clientflags)) != sizeof(clientflags)) {
-+ sizeof(clientflags)) < 0)
-+ {
- LOG("read failed");
- return -EINVAL;
- }
- clientflags = be32_to_cpu(clientflags);
-
-- if (nbd_negotiate_read(client->ioc, &length, sizeof(length)) !=
-- sizeof(length)) {
-+ if (nbd_negotiate_read(client->ioc, &length, sizeof(length)) < 0) {
- LOG("read failed");
- return -EINVAL;
- }
-@@ -519,7 +516,7 @@ static int nbd_negotiate_options(NBDClient *client)
- return -EINVAL;
-
- default:
-- if (nbd_negotiate_drop_sync(client->ioc, length) != length) {
-+ if (nbd_negotiate_drop_sync(client->ioc, length) < 0) {
- return -EIO;
- }
- ret = nbd_negotiate_send_rep_err(client->ioc,
-@@ -557,7 +554,7 @@ static int nbd_negotiate_options(NBDClient *client)
- return nbd_negotiate_handle_export_name(client, length);
-
- case NBD_OPT_STARTTLS:
-- if (nbd_negotiate_drop_sync(client->ioc, length) != length) {
-+ if (nbd_negotiate_drop_sync(client->ioc, length) < 0) {
- return -EIO;
- }
- if (client->tlscreds) {
-@@ -576,7 +573,7 @@ static int nbd_negotiate_options(NBDClient *client)
- }
- break;
- default:
-- if (nbd_negotiate_drop_sync(client->ioc, length) != length) {
-+ if (nbd_negotiate_drop_sync(client->ioc, length) < 0) {
- return -EIO;
- }
- ret = nbd_negotiate_send_rep_err(client->ioc,
-@@ -665,12 +662,12 @@ static coroutine_fn int nbd_negotiate(NBDClientNewData *data)
- TRACE("TLS cannot be enabled with oldstyle protocol");
- goto fail;
- }
-- if (nbd_negotiate_write(client->ioc, buf, sizeof(buf)) != sizeof(buf)) {
-+ if (nbd_negotiate_write(client->ioc, buf, sizeof(buf)) < 0) {
- LOG("write failed");
- goto fail;
- }
- } else {
-- if (nbd_negotiate_write(client->ioc, buf, 18) != 18) {
-+ if (nbd_negotiate_write(client->ioc, buf, 18) < 0) {
- LOG("write failed");
- goto fail;
- }
-@@ -685,7 +682,7 @@ static coroutine_fn int nbd_negotiate(NBDClientNewData *data)
- stq_be_p(buf + 18, client->exp->size);
- stw_be_p(buf + 26, client->exp->nbdflags | myflags);
- len = client->no_zeroes ? 10 : sizeof(buf) - 18;
-- if (nbd_negotiate_write(client->ioc, buf + 18, len) != len) {
-+ if (nbd_negotiate_write(client->ioc, buf + 18, len) < 0) {
- LOG("write failed");
- goto fail;
- }
-@@ -708,11 +705,6 @@ static ssize_t nbd_receive_request(QIOChannel *ioc, NBDRequest *request)
- return ret;
- }
-
-- if (ret != sizeof(buf)) {
-- LOG("read failed");
-- return -EINVAL;
-- }
--
- /* Request
- [ 0 .. 3] magic (NBD_REQUEST_MAGIC)
- [ 4 .. 5] flags (NBD_CMD_FLAG_FUA, ...)
-@@ -743,7 +735,6 @@ static ssize_t nbd_receive_request(QIOChannel *ioc, NBDRequest *request)
- static ssize_t nbd_send_reply(QIOChannel *ioc, NBDReply *reply)
- {
- uint8_t buf[NBD_REPLY_SIZE];
-- ssize_t ret;
-
- reply->error = system_errno_to_nbd_errno(reply->error);
-
-@@ -760,16 +751,7 @@ static ssize_t nbd_send_reply(QIOChannel *ioc, NBDReply *reply)
- stl_be_p(buf + 4, reply->error);
- stq_be_p(buf + 8, reply->handle);
-
-- ret = write_sync(ioc, buf, sizeof(buf));
-- if (ret < 0) {
-- return ret;
-- }
--
-- if (ret != sizeof(buf)) {
-- LOG("writing to socket failed");
-- return -EINVAL;
-- }
-- return 0;
-+ return write_sync(ioc, buf, sizeof(buf));
- }
-
- #define MAX_NBD_REQUESTS 16
-@@ -1073,7 +1055,7 @@ static ssize_t nbd_co_send_reply(NBDRequestData *req, NBDReply *reply,
- rc = nbd_send_reply(client->ioc, reply);
- if (rc >= 0) {
- ret = write_sync(client->ioc, req->data, len);
-- if (ret != len) {
-+ if (ret < 0) {
- rc = -EIO;
- }
- }
-@@ -1147,7 +1129,7 @@ static ssize_t nbd_co_receive_request(NBDRequestData *req,
- if (request->type == NBD_CMD_WRITE) {
- TRACE("Reading %" PRIu32 " byte(s)", request->len);
-
-- if (read_sync(client->ioc, req->data, request->len) != request->len) {
-+ if (read_sync(client->ioc, req->data, request->len) < 0) {
- LOG("reading from socket failed");
- rc = -EIO;
- goto out;
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eric Blake <eblake@redhat.com>
-Date: Wed, 19 Jul 2017 18:02:01 +0200
-Subject: [PATCH] nbd: make nbd_drop public
-
-RH-Author: Eric Blake <eblake@redhat.com>
-Message-id: <20170719180202.23329-4-eblake@redhat.com>
-Patchwork-id: 75814
-O-Subject: [RHEV-7.4.z qemu-kvm-rhev PATCH 3/4] nbd: make nbd_drop public
-Bugzilla: 1467509
-RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
-RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com>
-RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
-
-From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
-
-Following commit will reuse it for nbd server too.
-
-Reviewed-by: Eric Blake <eblake@redhat.com>
-Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
-Message-Id: <20170602150150.258222-3-vsementsov@virtuozzo.com>
-Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
-(cherry picked from commit 44298024d30ad36439707b89715a76333f58791b)
-
- Conflicts:
- nbd/client.c, nbd/nbd_internal.h, nbd/common.c - missing errp
- addition (e44ed99) and bulk rename (d1fdf25)
-
-Signed-off-by: Eric Blake <eblake@redhat.com>
-Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
----
- nbd/client.c | 32 +++-----------------------------
- nbd/common.c | 26 ++++++++++++++++++++++++++
- nbd/nbd-internal.h | 2 ++
- 3 files changed, 31 insertions(+), 29 deletions(-)
-
-diff --git a/nbd/client.c b/nbd/client.c
-index 6b74a628f1..1652f28e9f 100644
---- a/nbd/client.c
-+++ b/nbd/client.c
-@@ -86,32 +86,6 @@ static QTAILQ_HEAD(, NBDExport) exports = QTAILQ_HEAD_INITIALIZER(exports);
-
- */
-
--/* Discard length bytes from channel. Return -errno on failure and 0 on
-- * success*/
--static int drop_sync(QIOChannel *ioc, size_t size)
--{
-- ssize_t ret = 0;
-- char small[1024];
-- char *buffer;
--
-- buffer = sizeof(small) >= size ? small : g_malloc(MIN(65536, size));
-- while (size > 0) {
-- ssize_t count = MIN(65536, size);
-- ret = read_sync(ioc, buffer, MIN(65536, size));
--
-- if (ret < 0) {
-- goto cleanup;
-- }
-- size -= count;
-- }
--
-- cleanup:
-- if (buffer != small) {
-- g_free(buffer);
-- }
-- return ret;
--}
--
- /* Send an option request.
- *
- * The request is for option @opt, with @data containing @len bytes of
-@@ -333,7 +307,7 @@ static int nbd_receive_list(QIOChannel *ioc, const char *want, bool *match,
- return -1;
- }
- if (namelen != strlen(want)) {
-- if (drop_sync(ioc, len) < 0) {
-+ if (nbd_drop(ioc, len) < 0) {
- error_setg(errp, "failed to skip export name with wrong length");
- nbd_send_opt_abort(ioc);
- return -1;
-@@ -349,7 +323,7 @@ static int nbd_receive_list(QIOChannel *ioc, const char *want, bool *match,
- }
- name[namelen] = '\0';
- len -= namelen;
-- if (drop_sync(ioc, len) < 0) {
-+ if (nbd_drop(ioc, len) < 0) {
- error_setg(errp, "failed to read export description");
- nbd_send_opt_abort(ioc);
- return -1;
-@@ -616,7 +590,7 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags,
- }
-
- TRACE("Size is %" PRIu64 ", export flags %" PRIx16, *size, *flags);
-- if (zeroes && drop_sync(ioc, 124) < 0) {
-+ if (zeroes && nbd_drop(ioc, 124) < 0) {
- error_setg(errp, "Failed to read reserved block");
- goto fail;
- }
-diff --git a/nbd/common.c b/nbd/common.c
-index 4db45b3ede..9a54010c25 100644
---- a/nbd/common.c
-+++ b/nbd/common.c
-@@ -71,6 +71,32 @@ ssize_t nbd_wr_syncv(QIOChannel *ioc,
- return done;
- }
-
-+/* Discard length bytes from channel. Return -errno on failure and 0 on
-+ * success */
-+int nbd_drop(QIOChannel *ioc, size_t size)
-+{
-+ ssize_t ret = 0;
-+ char small[1024];
-+ char *buffer;
-+
-+ buffer = sizeof(small) >= size ? small : g_malloc(MIN(65536, size));
-+ while (size > 0) {
-+ ssize_t count = MIN(65536, size);
-+ ret = read_sync(ioc, buffer, MIN(65536, size));
-+
-+ if (ret < 0) {
-+ goto cleanup;
-+ }
-+ size -= count;
-+ }
-+
-+ cleanup:
-+ if (buffer != small) {
-+ g_free(buffer);
-+ }
-+ return ret;
-+}
-+
-
- void nbd_tls_handshake(QIOTask *task,
- void *opaque)
-diff --git a/nbd/nbd-internal.h b/nbd/nbd-internal.h
-index e6bbc7c4b4..c02378c553 100644
---- a/nbd/nbd-internal.h
-+++ b/nbd/nbd-internal.h
-@@ -149,4 +149,6 @@ struct NBDTLSHandshakeData {
- void nbd_tls_handshake(QIOTask *task,
- void *opaque);
-
-+int nbd_drop(QIOChannel *ioc, size_t size);
-+
- #endif
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eric Blake <eblake@redhat.com>
-Date: Wed, 19 Jul 2017 18:02:02 +0200
-Subject: [PATCH] nbd/server: get rid of nbd_negotiate_read and friends
-
-RH-Author: Eric Blake <eblake@redhat.com>
-Message-id: <20170719180202.23329-5-eblake@redhat.com>
-Patchwork-id: 75816
-O-Subject: [RHEV-7.4.z qemu-kvm-rhev PATCH 4/4] nbd/server: get rid of nbd_negotiate_read and friends
-Bugzilla: 1467509
-RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
-RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com>
-RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
-
-From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
-
-Functions nbd_negotiate_{read,write,drop_sync} were introduced in
-1a6245a5b, when nbd_rwv (was nbd_wr_sync) was working through
-qemu_co_sendv_recvv (the path is nbd_wr_sync -> qemu_co_{recv/send} ->
-qemu_co_send_recv -> qemu_co_sendv_recvv), which just yields, without
-setting any handlers. But starting from ff82911cd nbd_rwv (was
-nbd_wr_syncv) works through qio_channel_yield() which sets handlers, so
-watchers are redundant in nbd_negotiate_{read,write,drop_sync}, then,
-let's just use nbd_{read,write,drop} functions.
-
-Functions nbd_{read,write,drop} has errp parameter, which is unused in
-this patch. This will be fixed later.
-
-Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
-Reviewed-by: Eric Blake <eblake@redhat.com>
-Message-Id: <20170602150150.258222-4-vsementsov@virtuozzo.com>
-Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
-(cherry picked from commit 2b0bbc4f8809c972bad134bc1a2570dbb01dea0b)
-
-Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
-
-Conflicts:
- nbd/server.c - missing errp addition (e44ed99) and bulk
- rename (d1fdf25)
-Fixes CVE-2017-7539
-
-Signed-off-by: Eric Blake <eblake@redhat.com>
----
- nbd/server.c | 106 ++++++++++++-----------------------------------------------
- 1 file changed, 21 insertions(+), 85 deletions(-)
-
-diff --git a/nbd/server.c b/nbd/server.c
-index c2a5909ad6..ddf93d4717 100644
---- a/nbd/server.c
-+++ b/nbd/server.c
-@@ -104,69 +104,6 @@ struct NBDClient {
-
- static void nbd_client_receive_next_request(NBDClient *client);
-
--static gboolean nbd_negotiate_continue(QIOChannel *ioc,
-- GIOCondition condition,
-- void *opaque)
--{
-- qemu_coroutine_enter(opaque);
-- return TRUE;
--}
--
--static int nbd_negotiate_read(QIOChannel *ioc, void *buffer, size_t size)
--{
-- ssize_t ret;
-- guint watch;
--
-- assert(qemu_in_coroutine());
-- /* Negotiation are always in main loop. */
-- watch = qio_channel_add_watch(ioc,
-- G_IO_IN,
-- nbd_negotiate_continue,
-- qemu_coroutine_self(),
-- NULL);
-- ret = read_sync(ioc, buffer, size);
-- g_source_remove(watch);
-- return ret;
--
--}
--
--static int nbd_negotiate_write(QIOChannel *ioc, const void *buffer, size_t size)
--{
-- ssize_t ret;
-- guint watch;
--
-- assert(qemu_in_coroutine());
-- /* Negotiation are always in main loop. */
-- watch = qio_channel_add_watch(ioc,
-- G_IO_OUT,
-- nbd_negotiate_continue,
-- qemu_coroutine_self(),
-- NULL);
-- ret = write_sync(ioc, buffer, size);
-- g_source_remove(watch);
-- return ret;
--}
--
--static int nbd_negotiate_drop_sync(QIOChannel *ioc, size_t size)
--{
-- ssize_t ret;
-- uint8_t *buffer = g_malloc(MIN(65536, size));
--
-- while (size > 0) {
-- size_t count = MIN(65536, size);
-- ret = nbd_negotiate_read(ioc, buffer, count);
-- if (ret < 0) {
-- g_free(buffer);
-- return ret;
-- }
--
-- size -= count;
-- }
--
-- g_free(buffer);
-- return 0;
--}
--
- /* Basic flow for negotiation
-
- Server Client
-@@ -205,22 +142,22 @@ static int nbd_negotiate_send_rep_len(QIOChannel *ioc, uint32_t type,
- type, opt, len);
-
- magic = cpu_to_be64(NBD_REP_MAGIC);
-- if (nbd_negotiate_write(ioc, &magic, sizeof(magic)) < 0) {
-+ if (write_sync(ioc, &magic, sizeof(magic)) < 0) {
- LOG("write failed (rep magic)");
- return -EINVAL;
- }
- opt = cpu_to_be32(opt);
-- if (nbd_negotiate_write(ioc, &opt, sizeof(opt)) < 0) {
-+ if (write_sync(ioc, &opt, sizeof(opt)) < 0) {
- LOG("write failed (rep opt)");
- return -EINVAL;
- }
- type = cpu_to_be32(type);
-- if (nbd_negotiate_write(ioc, &type, sizeof(type)) < 0) {
-+ if (write_sync(ioc, &type, sizeof(type)) < 0) {
- LOG("write failed (rep type)");
- return -EINVAL;
- }
- len = cpu_to_be32(len);
-- if (nbd_negotiate_write(ioc, &len, sizeof(len)) < 0) {
-+ if (write_sync(ioc, &len, sizeof(len)) < 0) {
- LOG("write failed (rep data length)");
- return -EINVAL;
- }
-@@ -255,7 +192,7 @@ nbd_negotiate_send_rep_err(QIOChannel *ioc, uint32_t type,
- if (ret < 0) {
- goto out;
- }
-- if (nbd_negotiate_write(ioc, msg, len) < 0) {
-+ if (write_sync(ioc, msg, len) < 0) {
- LOG("write failed (error message)");
- ret = -EIO;
- } else {
-@@ -286,15 +223,15 @@ static int nbd_negotiate_send_rep_list(QIOChannel *ioc, NBDExport *exp)
- }
-
- len = cpu_to_be32(name_len);
-- if (nbd_negotiate_write(ioc, &len, sizeof(len)) < 0) {
-+ if (write_sync(ioc, &len, sizeof(len)) < 0) {
- LOG("write failed (name length)");
- return -EINVAL;
- }
-- if (nbd_negotiate_write(ioc, name, name_len) < 0) {
-+ if (write_sync(ioc, name, name_len) < 0) {
- LOG("write failed (name buffer)");
- return -EINVAL;
- }
-- if (nbd_negotiate_write(ioc, desc, desc_len) < 0) {
-+ if (write_sync(ioc, desc, desc_len) < 0) {
- LOG("write failed (description buffer)");
- return -EINVAL;
- }
-@@ -308,7 +245,7 @@ static int nbd_negotiate_handle_list(NBDClient *client, uint32_t length)
- NBDExport *exp;
-
- if (length) {
-- if (nbd_negotiate_drop_sync(client->ioc, length) < 0) {
-+ if (nbd_drop(client->ioc, length) < 0) {
- return -EIO;
- }
- return nbd_negotiate_send_rep_err(client->ioc,
-@@ -339,7 +276,7 @@ static int nbd_negotiate_handle_export_name(NBDClient *client, uint32_t length)
- LOG("Bad length received");
- goto fail;
- }
-- if (nbd_negotiate_read(client->ioc, name, length) < 0) {
-+ if (read_sync(client->ioc, name, length) < 0) {
- LOG("read failed");
- goto fail;
- }
-@@ -372,7 +309,7 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
- TRACE("Setting up TLS");
- ioc = client->ioc;
- if (length) {
-- if (nbd_negotiate_drop_sync(ioc, length) < 0) {
-+ if (nbd_drop(ioc, length) < 0) {
- return NULL;
- }
- nbd_negotiate_send_rep_err(ioc, NBD_REP_ERR_INVALID, NBD_OPT_STARTTLS,
-@@ -436,7 +373,7 @@ static int nbd_negotiate_options(NBDClient *client)
- ... Rest of request
- */
-
-- if (nbd_negotiate_read(client->ioc, &flags, sizeof(flags)) < 0) {
-+ if (read_sync(client->ioc, &flags, sizeof(flags)) < 0) {
- LOG("read failed");
- return -EIO;
- }
-@@ -462,7 +399,7 @@ static int nbd_negotiate_options(NBDClient *client)
- uint32_t clientflags, length;
- uint64_t magic;
-
-- if (nbd_negotiate_read(client->ioc, &magic, sizeof(magic)) < 0) {
-+ if (read_sync(client->ioc, &magic, sizeof(magic)) < 0) {
- LOG("read failed");
- return -EINVAL;
- }
-@@ -472,15 +409,14 @@ static int nbd_negotiate_options(NBDClient *client)
- return -EINVAL;
- }
-
-- if (nbd_negotiate_read(client->ioc, &clientflags,
-- sizeof(clientflags)) < 0)
-+ if (read_sync(client->ioc, &clientflags, sizeof(clientflags)) < 0)
- {
- LOG("read failed");
- return -EINVAL;
- }
- clientflags = be32_to_cpu(clientflags);
-
-- if (nbd_negotiate_read(client->ioc, &length, sizeof(length)) < 0) {
-+ if (read_sync(client->ioc, &length, sizeof(length)) < 0) {
- LOG("read failed");
- return -EINVAL;
- }
-@@ -516,7 +452,7 @@ static int nbd_negotiate_options(NBDClient *client)
- return -EINVAL;
-
- default:
-- if (nbd_negotiate_drop_sync(client->ioc, length) < 0) {
-+ if (nbd_drop(client->ioc, length) < 0) {
- return -EIO;
- }
- ret = nbd_negotiate_send_rep_err(client->ioc,
-@@ -554,7 +490,7 @@ static int nbd_negotiate_options(NBDClient *client)
- return nbd_negotiate_handle_export_name(client, length);
-
- case NBD_OPT_STARTTLS:
-- if (nbd_negotiate_drop_sync(client->ioc, length) < 0) {
-+ if (nbd_drop(client->ioc, length) < 0) {
- return -EIO;
- }
- if (client->tlscreds) {
-@@ -573,7 +509,7 @@ static int nbd_negotiate_options(NBDClient *client)
- }
- break;
- default:
-- if (nbd_negotiate_drop_sync(client->ioc, length) < 0) {
-+ if (nbd_drop(client->ioc, length) < 0) {
- return -EIO;
- }
- ret = nbd_negotiate_send_rep_err(client->ioc,
-@@ -662,12 +598,12 @@ static coroutine_fn int nbd_negotiate(NBDClientNewData *data)
- TRACE("TLS cannot be enabled with oldstyle protocol");
- goto fail;
- }
-- if (nbd_negotiate_write(client->ioc, buf, sizeof(buf)) < 0) {
-+ if (write_sync(client->ioc, buf, sizeof(buf)) < 0) {
- LOG("write failed");
- goto fail;
- }
- } else {
-- if (nbd_negotiate_write(client->ioc, buf, 18) < 0) {
-+ if (write_sync(client->ioc, buf, 18) < 0) {
- LOG("write failed");
- goto fail;
- }
-@@ -682,7 +618,7 @@ static coroutine_fn int nbd_negotiate(NBDClientNewData *data)
- stq_be_p(buf + 18, client->exp->size);
- stw_be_p(buf + 26, client->exp->nbdflags | myflags);
- len = client->no_zeroes ? 10 : sizeof(buf) - 18;
-- if (nbd_negotiate_write(client->ioc, buf + 18, len) < 0) {
-+ if (write_sync(client->ioc, buf + 18, len) < 0) {
- LOG("write failed");
- goto fail;
- }
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eric Blake <eblake@redhat.com>
-Date: Wed, 27 Sep 2017 17:57:19 +0200
-Subject: [PATCH] nbd-client: Fix regression when server sends garbage
-
-RH-Author: Eric Blake <eblake@redhat.com>
-Message-id: <20170927175725.20023-2-eblake@redhat.com>
-Patchwork-id: 76672
-O-Subject: [RHEV-7.4.z qemu-kvm-rhev PATCH 1/7] nbd-client: Fix regression when server sends garbage
-Bugzilla: 1495474
-RH-Acked-by: Max Reitz <mreitz@redhat.com>
-RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
-RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
-
-When we switched NBD to use coroutines for qemu 2.9 (in particular,
-commit a12a712a), we introduced a regression: if a server sends us
-garbage (such as a corrupted magic number), we quit the read loop
-but do not stop sending further queued commands, resulting in the
-client hanging when it never reads the response to those additional
-commands. In qemu 2.8, we properly detected that the server is no
-longer reliable, and cancelled all existing pending commands with
-EIO, then tore down the socket so that all further command attempts
-get EPIPE.
-
-Restore the proper behavior of quitting (almost) all communication
-with a broken server: Once we know we are out of sync or otherwise
-can't trust the server, we must assume that any further incoming
-data is unreliable and therefore end all pending commands with EIO,
-and quit trying to send any further commands. As an exception, we
-still (try to) send NBD_CMD_DISC to let the server know we are going
-away (in part, because it is easier to do that than to further
-refactor nbd_teardown_connection, and in part because it is the
-only command where we do not have to wait for a reply).
-
-Based on a patch by Vladimir Sementsov-Ogievskiy.
-
-A malicious server can be created with the following hack,
-followed by setting NBD_SERVER_DEBUG to a non-zero value in the
-environment when running qemu-nbd:
-
-| --- a/nbd/server.c
-| +++ b/nbd/server.c
-| @@ -919,6 +919,17 @@ static int nbd_send_reply(QIOChannel *ioc, NBDReply *reply, Error **errp)
-| stl_be_p(buf + 4, reply->error);
-| stq_be_p(buf + 8, reply->handle);
-|
-| + static int debug;
-| + static int count;
-| + if (!count++) {
-| + const char *str = getenv("NBD_SERVER_DEBUG");
-| + if (str) {
-| + debug = atoi(str);
-| + }
-| + }
-| + if (debug && !(count % debug)) {
-| + buf[0] = 0;
-| + }
-| return nbd_write(ioc, buf, sizeof(buf), errp);
-| }
-
-Reported-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
-Signed-off-by: Eric Blake <eblake@redhat.com>
-Message-Id: <20170814213426.24681-1-eblake@redhat.com>
-Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
-(cherry picked from commit 72b6ffc76653214b69a94a7b1643ff80df134486)
-Signed-off-by: Eric Blake <eblake@redhat.com>
-Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
-
-Conflicts:
- block/nbd-client.c
----
- block/nbd-client.c | 17 +++++++++++++----
- block/nbd-client.h | 1 +
- 2 files changed, 14 insertions(+), 4 deletions(-)
-
-diff --git a/block/nbd-client.c b/block/nbd-client.c
-index 282679b4f8..701b4ce2eb 100644
---- a/block/nbd-client.c
-+++ b/block/nbd-client.c
-@@ -71,7 +71,7 @@ static coroutine_fn void nbd_read_reply_entry(void *opaque)
- uint64_t i;
- int ret;
-
-- for (;;) {
-+ while (!s->quit) {
- assert(s->reply.handle == 0);
- ret = nbd_receive_reply(s->ioc, &s->reply);
- if (ret <= 0) {
-@@ -102,6 +102,9 @@ static coroutine_fn void nbd_read_reply_entry(void *opaque)
- qemu_coroutine_yield();
- }
-
-+ if (ret < 0) {
-+ s->quit = true;
-+ }
- nbd_recv_coroutines_enter_all(s);
- s->read_reply_co = NULL;
- }
-@@ -130,6 +133,10 @@ static int nbd_co_send_request(BlockDriverState *bs,
- assert(i < MAX_NBD_REQUESTS);
- request->handle = INDEX_TO_HANDLE(s, i);
-
-+ if (s->quit) {
-+ qemu_co_mutex_unlock(&s->send_mutex);
-+ return -EIO;
-+ }
- if (!s->ioc) {
- qemu_co_mutex_unlock(&s->send_mutex);
- return -EPIPE;
-@@ -138,7 +145,7 @@ static int nbd_co_send_request(BlockDriverState *bs,
- if (qiov) {
- qio_channel_set_cork(s->ioc, true);
- rc = nbd_send_request(s->ioc, request);
-- if (rc >= 0) {
-+ if (rc >= 0 && !s->quit) {
- ret = nbd_wr_syncv(s->ioc, qiov->iov, qiov->niov, request->len,
- false);
- if (ret != request->len) {
-@@ -149,6 +156,9 @@ static int nbd_co_send_request(BlockDriverState *bs,
- } else {
- rc = nbd_send_request(s->ioc, request);
- }
-+ if (rc < 0) {
-+ s->quit = true;
-+ }
- qemu_co_mutex_unlock(&s->send_mutex);
- return rc;
- }
-@@ -163,8 +173,7 @@ static void nbd_co_receive_reply(NBDClientSession *s,
- /* Wait until we're woken up by nbd_read_reply_entry. */
- qemu_coroutine_yield();
- *reply = s->reply;
-- if (reply->handle != request->handle ||
-- !s->ioc) {
-+ if (reply->handle != request->handle || !s->ioc || s->quit) {
- reply->error = EIO;
- } else {
- if (qiov && reply->error == 0) {
-diff --git a/block/nbd-client.h b/block/nbd-client.h
-index 891ba44a20..9774a8ebbb 100644
---- a/block/nbd-client.h
-+++ b/block/nbd-client.h
-@@ -30,6 +30,7 @@ typedef struct NBDClientSession {
-
- Coroutine *recv_coroutine[MAX_NBD_REQUESTS];
- NBDReply reply;
-+ bool quit;
- } NBDClientSession;
-
- NBDClientSession *nbd_get_client_session(BlockDriverState *bs);
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eric Blake <eblake@redhat.com>
-Date: Wed, 27 Sep 2017 17:57:20 +0200
-Subject: [PATCH] fix build failure in nbd_read_reply_entry()
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-RH-Author: Eric Blake <eblake@redhat.com>
-Message-id: <20170927175725.20023-3-eblake@redhat.com>
-Patchwork-id: 76668
-O-Subject: [RHEV-7.4.z qemu-kvm-rhev PATCH 2/7] fix build failure in nbd_read_reply_entry()
-Bugzilla: 1495474
-RH-Acked-by: Max Reitz <mreitz@redhat.com>
-RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
-RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
-
-From: Igor Mammedov <imammedo@redhat.com>
-
-travis builds fail at HEAD at rc3 master with
-
- block/nbd-client.c: In function ‘nbd_read_reply_entry’:
- block/nbd-client.c:110:8: error: ‘ret’ may be used uninitialized in this function [-Werror=uninitialized]
-
-fix it by initializing 'ret' to 0
-
-Signed-off-by: Igor Mammedov <imammedo@redhat.com>
-Reviewed-by: Eric Blake <eblake@redhat.com>
-Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-(cherry picked from commit d0a180131c6655487b47ea72e7da0a909a479a3c)
-Signed-off-by: Eric Blake <eblake@redhat.com>
-Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
-
-Conflicts:
- block/nbd-client.c - context
----
- block/nbd-client.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/block/nbd-client.c b/block/nbd-client.c
-index 701b4ce2eb..256dabeeef 100644
---- a/block/nbd-client.c
-+++ b/block/nbd-client.c
-@@ -69,7 +69,7 @@ static coroutine_fn void nbd_read_reply_entry(void *opaque)
- {
- NBDClientSession *s = opaque;
- uint64_t i;
-- int ret;
-+ int ret = 0;
-
- while (!s->quit) {
- assert(s->reply.handle == 0);
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eric Blake <eblake@redhat.com>
-Date: Wed, 27 Sep 2017 17:57:21 +0200
-Subject: [PATCH] nbd-client: avoid spurious qio_channel_yield() re-entry
-
-RH-Author: Eric Blake <eblake@redhat.com>
-Message-id: <20170927175725.20023-4-eblake@redhat.com>
-Patchwork-id: 76671
-O-Subject: [RHEV-7.4.z qemu-kvm-rhev PATCH 3/7] nbd-client: avoid spurious qio_channel_yield() re-entry
-Bugzilla: 1495474
-RH-Acked-by: Max Reitz <mreitz@redhat.com>
-RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
-RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
-
-From: Stefan Hajnoczi <stefanha@redhat.com>
-
-The following scenario leads to an assertion failure in
-qio_channel_yield():
-
-1. Request coroutine calls qio_channel_yield() successfully when sending
- would block on the socket. It is now yielded.
-2. nbd_read_reply_entry() calls nbd_recv_coroutines_enter_all() because
- nbd_receive_reply() failed.
-3. Request coroutine is entered and returns from qio_channel_yield().
- Note that the socket fd handler has not fired yet so
- ioc->write_coroutine is still set.
-4. Request coroutine attempts to send the request body with nbd_rwv()
- but the socket would still block. qio_channel_yield() is called
- again and assert(!ioc->write_coroutine) is hit.
-
-The problem is that nbd_read_reply_entry() does not distinguish between
-request coroutines that are waiting to receive a reply and those that
-are not.
-
-This patch adds a per-request bool receiving flag so
-nbd_read_reply_entry() can avoid spurious aio_wake() calls.
-
-Reported-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
-Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
-Message-Id: <20170822125113.5025-1-stefanha@redhat.com>
-Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
-Tested-by: Eric Blake <eblake@redhat.com>
-Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
-Signed-off-by: Eric Blake <eblake@redhat.com>
-(cherry picked from commit 40f4a21895b5a7eae4011593837069f63460d983)
-Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
----
- block/nbd-client.c | 35 ++++++++++++++++++++++-------------
- block/nbd-client.h | 7 ++++++-
- 2 files changed, 28 insertions(+), 14 deletions(-)
-
-diff --git a/block/nbd-client.c b/block/nbd-client.c
-index 256dabeeef..f7bca3f996 100644
---- a/block/nbd-client.c
-+++ b/block/nbd-client.c
-@@ -38,8 +38,10 @@ static void nbd_recv_coroutines_enter_all(NBDClientSession *s)
- int i;
-
- for (i = 0; i < MAX_NBD_REQUESTS; i++) {
-- if (s->recv_coroutine[i]) {
-- aio_co_wake(s->recv_coroutine[i]);
-+ NBDClientRequest *req = &s->requests[i];
-+
-+ if (req->coroutine && req->receiving) {
-+ aio_co_wake(req->coroutine);
- }
- }
- }
-@@ -83,28 +85,28 @@ static coroutine_fn void nbd_read_reply_entry(void *opaque)
- * one coroutine is called until the reply finishes.
- */
- i = HANDLE_TO_INDEX(s, s->reply.handle);
-- if (i >= MAX_NBD_REQUESTS || !s->recv_coroutine[i]) {
-+ if (i >= MAX_NBD_REQUESTS ||
-+ !s->requests[i].coroutine ||
-+ !s->requests[i].receiving) {
- break;
- }
-
-- /* We're woken up by the recv_coroutine itself. Note that there
-+ /* We're woken up again by the request itself. Note that there
- * is no race between yielding and reentering read_reply_co. This
- * is because:
- *
-- * - if recv_coroutine[i] runs on the same AioContext, it is only
-+ * - if the request runs on the same AioContext, it is only
- * entered after we yield
- *
-- * - if recv_coroutine[i] runs on a different AioContext, reentering
-+ * - if the request runs on a different AioContext, reentering
- * read_reply_co happens through a bottom half, which can only
- * run after we yield.
- */
-- aio_co_wake(s->recv_coroutine[i]);
-+ aio_co_wake(s->requests[i].coroutine);
- qemu_coroutine_yield();
- }
-
-- if (ret < 0) {
-- s->quit = true;
-- }
-+ s->quit = true;
- nbd_recv_coroutines_enter_all(s);
- s->read_reply_co = NULL;
- }
-@@ -123,14 +125,17 @@ static int nbd_co_send_request(BlockDriverState *bs,
- s->in_flight++;
-
- for (i = 0; i < MAX_NBD_REQUESTS; i++) {
-- if (s->recv_coroutine[i] == NULL) {
-- s->recv_coroutine[i] = qemu_coroutine_self();
-+ if (s->requests[i].coroutine == NULL) {
- break;
- }
- }
-
- g_assert(qemu_in_coroutine());
- assert(i < MAX_NBD_REQUESTS);
-+
-+ s->requests[i].coroutine = qemu_coroutine_self();
-+ s->requests[i].receiving = false;
-+
- request->handle = INDEX_TO_HANDLE(s, i);
-
- if (s->quit) {
-@@ -168,10 +173,13 @@ static void nbd_co_receive_reply(NBDClientSession *s,
- NBDReply *reply,
- QEMUIOVector *qiov)
- {
-+ int i = HANDLE_TO_INDEX(s, request->handle);
- int ret;
-
- /* Wait until we're woken up by nbd_read_reply_entry. */
-+ s->requests[i].receiving = true;
- qemu_coroutine_yield();
-+ s->requests[i].receiving = false;
- *reply = s->reply;
- if (reply->handle != request->handle || !s->ioc || s->quit) {
- reply->error = EIO;
-@@ -181,6 +189,7 @@ static void nbd_co_receive_reply(NBDClientSession *s,
- true);
- if (ret != request->len) {
- reply->error = EIO;
-+ s->quit = true;
- }
- }
-
-@@ -195,7 +204,7 @@ static void nbd_coroutine_end(BlockDriverState *bs,
- NBDClientSession *s = nbd_get_client_session(bs);
- int i = HANDLE_TO_INDEX(s, request->handle);
-
-- s->recv_coroutine[i] = NULL;
-+ s->requests[i].coroutine = NULL;
-
- /* Kick the read_reply_co to get the next reply. */
- if (s->read_reply_co) {
-diff --git a/block/nbd-client.h b/block/nbd-client.h
-index 9774a8ebbb..f97792ff49 100644
---- a/block/nbd-client.h
-+++ b/block/nbd-client.h
-@@ -17,6 +17,11 @@
-
- #define MAX_NBD_REQUESTS 16
-
-+typedef struct {
-+ Coroutine *coroutine;
-+ bool receiving; /* waiting for read_reply_co? */
-+} NBDClientRequest;
-+
- typedef struct NBDClientSession {
- QIOChannelSocket *sioc; /* The master data channel */
- QIOChannel *ioc; /* The current I/O channel which may differ (eg TLS) */
-@@ -28,7 +33,7 @@ typedef struct NBDClientSession {
- Coroutine *read_reply_co;
- int in_flight;
-
-- Coroutine *recv_coroutine[MAX_NBD_REQUESTS];
-+ NBDClientRequest requests[MAX_NBD_REQUESTS];
- NBDReply reply;
- bool quit;
- } NBDClientSession;
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eric Blake <eblake@redhat.com>
-Date: Wed, 27 Sep 2017 17:57:22 +0200
-Subject: [PATCH] nbd-client: avoid read_reply_co entry if send failed
-
-RH-Author: Eric Blake <eblake@redhat.com>
-Message-id: <20170927175725.20023-5-eblake@redhat.com>
-Patchwork-id: 76674
-O-Subject: [RHEV-7.4.z qemu-kvm-rhev PATCH 4/7] nbd-client: avoid read_reply_co entry if send failed
-Bugzilla: 1495474
-RH-Acked-by: Max Reitz <mreitz@redhat.com>
-RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
-RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
-
-From: Stefan Hajnoczi <stefanha@redhat.com>
-
-The following segfault is encountered if the NBD server closes the UNIX
-domain socket immediately after negotiation:
-
- Program terminated with signal SIGSEGV, Segmentation fault.
- #0 aio_co_schedule (ctx=0x0, co=0xd3c0ff2ef0) at util/async.c:441
- 441 QSLIST_INSERT_HEAD_ATOMIC(&ctx->scheduled_coroutines,
- (gdb) bt
- #0 0x000000d3c01a50f8 in aio_co_schedule (ctx=0x0, co=0xd3c0ff2ef0) at util/async.c:441
- #1 0x000000d3c012fa90 in nbd_coroutine_end (bs=bs@entry=0xd3c0fec650, request=<optimized out>) at block/nbd-client.c:207
- #2 0x000000d3c012fb58 in nbd_client_co_preadv (bs=0xd3c0fec650, offset=0, bytes=<optimized out>, qiov=0x7ffc10a91b20, flags=0) at block/nbd-client.c:237
- #3 0x000000d3c0128e63 in bdrv_driver_preadv (bs=bs@entry=0xd3c0fec650, offset=offset@entry=0, bytes=bytes@entry=512, qiov=qiov@entry=0x7ffc10a91b20, flags=0) at block/io.c:836
- #4 0x000000d3c012c3e0 in bdrv_aligned_preadv (child=child@entry=0xd3c0ff51d0, req=req@entry=0x7f31885d6e90, offset=offset@entry=0, bytes=bytes@entry=512, align=align@entry=1, qiov=qiov@entry=0x7ffc10a91b20, f
-+lags=0) at block/io.c:1086
- #5 0x000000d3c012c6b8 in bdrv_co_preadv (child=0xd3c0ff51d0, offset=offset@entry=0, bytes=bytes@entry=512, qiov=qiov@entry=0x7ffc10a91b20, flags=flags@entry=0) at block/io.c:1182
- #6 0x000000d3c011cc17 in blk_co_preadv (blk=0xd3c0ff4f80, offset=0, bytes=512, qiov=0x7ffc10a91b20, flags=0) at block/block-backend.c:1032
- #7 0x000000d3c011ccec in blk_read_entry (opaque=0x7ffc10a91b40) at block/block-backend.c:1079
- #8 0x000000d3c01bbb96 in coroutine_trampoline (i0=<optimized out>, i1=<optimized out>) at util/coroutine-ucontext.c:79
- #9 0x00007f3196cb8600 in __start_context () at /lib64/libc.so.6
-
-The problem is that nbd_client_init() uses
-nbd_client_attach_aio_context() -> aio_co_schedule(new_context,
-client->read_reply_co). Execution of read_reply_co is deferred to a BH
-which doesn't run until later.
-
-In the mean time blk_co_preadv() can be called and nbd_coroutine_end()
-calls aio_wake() on read_reply_co. At this point in time
-read_reply_co's ctx isn't set because it has never been entered yet.
-
-This patch simplifies the nbd_co_send_request() ->
-nbd_co_receive_reply() -> nbd_coroutine_end() lifecycle to just
-nbd_co_send_request() -> nbd_co_receive_reply(). The request is "ended"
-if an error occurs at any point. Callers no longer have to invoke
-nbd_coroutine_end().
-
-This cleanup also eliminates the segfault because we don't call
-aio_co_schedule() to wake up s->read_reply_co if sending the request
-failed. It is only necessary to wake up s->read_reply_co if a reply was
-received.
-
-Note this only happens with UNIX domain sockets on Linux. It doesn't
-seem possible to reproduce this with TCP sockets.
-
-Suggested-by: Paolo Bonzini <pbonzini@redhat.com>
-Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
-Message-Id: <20170829122745.14309-2-stefanha@redhat.com>
-Signed-off-by: Eric Blake <eblake@redhat.com>
-(cherry picked from commit 3c2d5183f9fa4eac3d17d841e26da65a0181ae7b)
-Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
----
- block/nbd-client.c | 25 +++++++++----------------
- 1 file changed, 9 insertions(+), 16 deletions(-)
-
-diff --git a/block/nbd-client.c b/block/nbd-client.c
-index f7bca3f996..434acf647f 100644
---- a/block/nbd-client.c
-+++ b/block/nbd-client.c
-@@ -139,12 +139,12 @@ static int nbd_co_send_request(BlockDriverState *bs,
- request->handle = INDEX_TO_HANDLE(s, i);
-
- if (s->quit) {
-- qemu_co_mutex_unlock(&s->send_mutex);
-- return -EIO;
-+ rc = -EIO;
-+ goto err;
- }
- if (!s->ioc) {
-- qemu_co_mutex_unlock(&s->send_mutex);
-- return -EPIPE;
-+ rc = -EPIPE;
-+ goto err;
- }
-
- if (qiov) {
-@@ -161,8 +161,13 @@ static int nbd_co_send_request(BlockDriverState *bs,
- } else {
- rc = nbd_send_request(s->ioc, request);
- }
-+
-+err:
- if (rc < 0) {
- s->quit = true;
-+ s->requests[i].coroutine = NULL;
-+ s->in_flight--;
-+ qemu_co_queue_next(&s->free_sema);
- }
- qemu_co_mutex_unlock(&s->send_mutex);
- return rc;
-@@ -196,13 +201,6 @@ static void nbd_co_receive_reply(NBDClientSession *s,
- /* Tell the read handler to read another header. */
- s->reply.handle = 0;
- }
--}
--
--static void nbd_coroutine_end(BlockDriverState *bs,
-- NBDRequest *request)
--{
-- NBDClientSession *s = nbd_get_client_session(bs);
-- int i = HANDLE_TO_INDEX(s, request->handle);
-
- s->requests[i].coroutine = NULL;
-
-@@ -238,7 +236,6 @@ int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset,
- } else {
- nbd_co_receive_reply(client, &request, &reply, qiov);
- }
-- nbd_coroutine_end(bs, &request);
- return -reply.error;
- }
-
-@@ -267,7 +264,6 @@ int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset,
- } else {
- nbd_co_receive_reply(client, &request, &reply, NULL);
- }
-- nbd_coroutine_end(bs, &request);
- return -reply.error;
- }
-
-@@ -301,7 +297,6 @@ int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
- } else {
- nbd_co_receive_reply(client, &request, &reply, NULL);
- }
-- nbd_coroutine_end(bs, &request);
- return -reply.error;
- }
-
-@@ -325,7 +320,6 @@ int nbd_client_co_flush(BlockDriverState *bs)
- } else {
- nbd_co_receive_reply(client, &request, &reply, NULL);
- }
-- nbd_coroutine_end(bs, &request);
- return -reply.error;
- }
-
-@@ -350,7 +344,6 @@ int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int count)
- } else {
- nbd_co_receive_reply(client, &request, &reply, NULL);
- }
-- nbd_coroutine_end(bs, &request);
- return -reply.error;
-
- }
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eric Blake <eblake@redhat.com>
-Date: Wed, 27 Sep 2017 17:57:23 +0200
-Subject: [PATCH] qemu-iotests: improve nbd-fault-injector.py startup protocol
-
-RH-Author: Eric Blake <eblake@redhat.com>
-Message-id: <20170927175725.20023-6-eblake@redhat.com>
-Patchwork-id: 76675
-O-Subject: [RHEV-7.4.z qemu-kvm-rhev PATCH 5/7] qemu-iotests: improve nbd-fault-injector.py startup protocol
-Bugzilla: 1495474
-RH-Acked-by: Max Reitz <mreitz@redhat.com>
-RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
-RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
-
-From: Stefan Hajnoczi <stefanha@redhat.com>
-
-Currently 083 waits for the nbd-fault-injector.py server to start up by
-looping until netstat shows the TCP listen socket.
-
-The startup protocol can be simplified by passing a 0 port number to
-nbd-fault-injector.py. The kernel will allocate a port in bind(2) and
-the final port number can be printed by nbd-fault-injector.py.
-
-This should make it slightly nicer and less TCP-specific to wait for
-server startup. This patch changes nbd-fault-injector.py, the next one
-will rewrite server startup in 083.
-
-Reviewed-by: Eric Blake <eblake@redhat.com>
-Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
-Message-Id: <20170829122745.14309-3-stefanha@redhat.com>
-Signed-off-by: Eric Blake <eblake@redhat.com>
-(cherry picked from commit 6e592fc92234a58c7156c385840633c17dedd24f)
-Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
----
- tests/qemu-iotests/nbd-fault-injector.py | 4 ++++
- 1 file changed, 4 insertions(+)
-
-diff --git a/tests/qemu-iotests/nbd-fault-injector.py b/tests/qemu-iotests/nbd-fault-injector.py
-index 6c07191a5a..1c10dcb51c 100755
---- a/tests/qemu-iotests/nbd-fault-injector.py
-+++ b/tests/qemu-iotests/nbd-fault-injector.py
-@@ -235,11 +235,15 @@ def open_socket(path):
- sock = socket.socket()
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- sock.bind((host, int(port)))
-+
-+ # If given port was 0 the final port number is now available
-+ path = '%s:%d' % sock.getsockname()
- else:
- sock = socket.socket(socket.AF_UNIX)
- sock.bind(path)
- sock.listen(0)
- print 'Listening on %s' % path
-+ sys.stdout.flush() # another process may be waiting, show message now
- return sock
-
- def usage(args):
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eric Blake <eblake@redhat.com>
-Date: Wed, 27 Sep 2017 17:57:24 +0200
-Subject: [PATCH] qemu-iotests: test NBD over UNIX domain sockets in 083
-
-RH-Author: Eric Blake <eblake@redhat.com>
-Message-id: <20170927175725.20023-7-eblake@redhat.com>
-Patchwork-id: 76670
-O-Subject: [RHEV-7.4.z qemu-kvm-rhev PATCH 6/7] qemu-iotests: test NBD over UNIX domain sockets in 083
-Bugzilla: 1495474
-RH-Acked-by: Max Reitz <mreitz@redhat.com>
-RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
-RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
-
-From: Stefan Hajnoczi <stefanha@redhat.com>
-
-083 only tests TCP. Some failures might be specific to UNIX domain
-sockets.
-
-A few adjustments are necessary:
-
-1. Generating a port number and waiting for server startup is
- TCP-specific. Use the new nbd-fault-injector.py startup protocol to
- fetch the address. This is a little more elegant because we don't
- need netstat anymore.
-
-2. The NBD filter does not work for the UNIX domain sockets URIs we
- generate and must be extended.
-
-3. Run all tests twice: once for TCP and once for UNIX domain sockets.
-
-Reviewed-by: Eric Blake <eblake@redhat.com>
-Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
-Message-Id: <20170829122745.14309-4-stefanha@redhat.com>
-Signed-off-by: Eric Blake <eblake@redhat.com>
-(cherry picked from commit 02d2d860d25e439f0e88658c701668ab684568fb)
-Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
-
-Conflicts:
- tests/qemu-iotests/083.out - error message improvements not backported
-
-Signed-off-by: Eric Blake <eblake@redhat.com>
----
- tests/qemu-iotests/083 | 136 +++++++++++++++++++++++--------------
- tests/qemu-iotests/083.out | 143 ++++++++++++++++++++++++++++++++++-----
- tests/qemu-iotests/common.filter | 4 +-
- 3 files changed, 212 insertions(+), 71 deletions(-)
-
-diff --git a/tests/qemu-iotests/083 b/tests/qemu-iotests/083
-index bff9360048..0306f112da 100755
---- a/tests/qemu-iotests/083
-+++ b/tests/qemu-iotests/083
-@@ -27,6 +27,14 @@ echo "QA output created by $seq"
- here=`pwd`
- status=1 # failure is the default!
-
-+_cleanup()
-+{
-+ rm -f nbd.sock
-+ rm -f nbd-fault-injector.out
-+ rm -f nbd-fault-injector.conf
-+}
-+trap "_cleanup; exit \$status" 0 1 2 3 15
-+
- # get standard environment, filters and checks
- . ./common.rc
- . ./common.filter
-@@ -35,81 +43,105 @@ _supported_fmt generic
- _supported_proto nbd
- _supported_os Linux
-
--# Pick a TCP port based on our pid. This way multiple instances of this test
--# can run in parallel without conflicting.
--choose_tcp_port() {
-- echo $((($$ % 31744) + 1024)) # 1024 <= port < 32768
--}
--
--wait_for_tcp_port() {
-- while ! (netstat --tcp --listening --numeric | \
-- grep "$1.*0\\.0\\.0\\.0:\\*.*LISTEN") >/dev/null 2>&1; do
-- sleep 0.1
-+check_disconnect() {
-+ local event export_name=foo extra_args nbd_addr nbd_url proto when
-+
-+ while true; do
-+ case $1 in
-+ --classic-negotiation)
-+ shift
-+ extra_args=--classic-negotiation
-+ export_name=
-+ ;;
-+ --tcp)
-+ shift
-+ proto=tcp
-+ ;;
-+ --unix)
-+ shift
-+ proto=unix
-+ ;;
-+ *)
-+ break
-+ ;;
-+ esac
- done
--}
-
--check_disconnect() {
- event=$1
- when=$2
-- negotiation=$3
- echo "=== Check disconnect $when $event ==="
- echo
-
-- port=$(choose_tcp_port)
--
- cat > "$TEST_DIR/nbd-fault-injector.conf" <<EOF
- [inject-error]
- event=$event
- when=$when
- EOF
-
-- if [ "$negotiation" = "--classic-negotiation" ]; then
-- extra_args=--classic-negotiation
-- nbd_url="nbd:127.0.0.1:$port"
-+ if [ "$proto" = "tcp" ]; then
-+ nbd_addr="127.0.0.1:0"
- else
-- nbd_url="nbd:127.0.0.1:$port:exportname=foo"
-+ nbd_addr="$TEST_DIR/nbd.sock"
-+ fi
-+
-+ rm -f "$TEST_DIR/nbd.sock"
-+
-+ $PYTHON nbd-fault-injector.py $extra_args "$nbd_addr" "$TEST_DIR/nbd-fault-injector.conf" >"$TEST_DIR/nbd-fault-injector.out" 2>&1 &
-+
-+ # Wait for server to be ready
-+ while ! grep -q 'Listening on ' "$TEST_DIR/nbd-fault-injector.out"; do
-+ sleep 0.1
-+ done
-+
-+ # Extract the final address (port number has now been assigned in tcp case)
-+ nbd_addr=$(sed 's/Listening on \(.*\)$/\1/' "$TEST_DIR/nbd-fault-injector.out")
-+
-+ if [ "$proto" = "tcp" ]; then
-+ nbd_url="nbd+tcp://$nbd_addr/$export_name"
-+ else
-+ nbd_url="nbd+unix:///$export_name?socket=$nbd_addr"
- fi
-
-- $PYTHON nbd-fault-injector.py $extra_args "127.0.0.1:$port" "$TEST_DIR/nbd-fault-injector.conf" >/dev/null 2>&1 &
-- wait_for_tcp_port "127\\.0\\.0\\.1:$port"
- $QEMU_IO -c "read 0 512" "$nbd_url" 2>&1 | _filter_qemu_io | _filter_nbd
-
- echo
- }
-
--for event in neg1 "export" neg2 request reply data; do
-- for when in before after; do
-- check_disconnect "$event" "$when"
-- done
--
-- # Also inject short replies from the NBD server
-- case "$event" in
-- neg1)
-- for when in 8 16; do
-- check_disconnect "$event" "$when"
-- done
-- ;;
-- "export")
-- for when in 4 12 16; do
-- check_disconnect "$event" "$when"
-+for proto in tcp unix; do
-+ for event in neg1 "export" neg2 request reply data; do
-+ for when in before after; do
-+ check_disconnect "--$proto" "$event" "$when"
- done
-- ;;
-- neg2)
-- for when in 8 10; do
-- check_disconnect "$event" "$when"
-- done
-- ;;
-- reply)
-- for when in 4 8; do
-- check_disconnect "$event" "$when"
-- done
-- ;;
-- esac
--done
-
--# Also check classic negotiation without export information
--for when in before 8 16 24 28 after; do
-- check_disconnect "neg-classic" "$when" --classic-negotiation
-+ # Also inject short replies from the NBD server
-+ case "$event" in
-+ neg1)
-+ for when in 8 16; do
-+ check_disconnect "--$proto" "$event" "$when"
-+ done
-+ ;;
-+ "export")
-+ for when in 4 12 16; do
-+ check_disconnect "--$proto" "$event" "$when"
-+ done
-+ ;;
-+ neg2)
-+ for when in 8 10; do
-+ check_disconnect "--$proto" "$event" "$when"
-+ done
-+ ;;
-+ reply)
-+ for when in 4 8; do
-+ check_disconnect "--$proto" "$event" "$when"
-+ done
-+ ;;
-+ esac
-+ done
-+
-+ # Also check classic negotiation without export information
-+ for when in before 8 16 24 28 after; do
-+ check_disconnect "--$proto" --classic-negotiation "neg-classic" "$when"
-+ done
- done
-
- # success, all done
-diff --git a/tests/qemu-iotests/083.out b/tests/qemu-iotests/083.out
-index 0c13888ba1..7419722cd7 100644
---- a/tests/qemu-iotests/083.out
-+++ b/tests/qemu-iotests/083.out
-@@ -1,43 +1,43 @@
- QA output created by 083
- === Check disconnect before neg1 ===
-
--can't open device nbd:127.0.0.1:PORT:exportname=foo
-+can't open device nbd+tcp://127.0.0.1:PORT/foo
-
- === Check disconnect after neg1 ===
-
--can't open device nbd:127.0.0.1:PORT:exportname=foo
-+can't open device nbd+tcp://127.0.0.1:PORT/foo
-
- === Check disconnect 8 neg1 ===
-
--can't open device nbd:127.0.0.1:PORT:exportname=foo
-+can't open device nbd+tcp://127.0.0.1:PORT/foo
-
- === Check disconnect 16 neg1 ===
-
--can't open device nbd:127.0.0.1:PORT:exportname=foo
-+can't open device nbd+tcp://127.0.0.1:PORT/foo
-
- === Check disconnect before export ===
-
--can't open device nbd:127.0.0.1:PORT:exportname=foo
-+can't open device nbd+tcp://127.0.0.1:PORT/foo
-
- === Check disconnect after export ===
-
--can't open device nbd:127.0.0.1:PORT:exportname=foo
-+can't open device nbd+tcp://127.0.0.1:PORT/foo
-
- === Check disconnect 4 export ===
-
--can't open device nbd:127.0.0.1:PORT:exportname=foo
-+can't open device nbd+tcp://127.0.0.1:PORT/foo
-
- === Check disconnect 12 export ===
-
--can't open device nbd:127.0.0.1:PORT:exportname=foo
-+can't open device nbd+tcp://127.0.0.1:PORT/foo
-
- === Check disconnect 16 export ===
-
--can't open device nbd:127.0.0.1:PORT:exportname=foo
-+can't open device nbd+tcp://127.0.0.1:PORT/foo
-
- === Check disconnect before neg2 ===
-
--can't open device nbd:127.0.0.1:PORT:exportname=foo
-+can't open device nbd+tcp://127.0.0.1:PORT/foo
-
- === Check disconnect after neg2 ===
-
-@@ -45,11 +45,11 @@ read failed: Input/output error
-
- === Check disconnect 8 neg2 ===
-
--can't open device nbd:127.0.0.1:PORT:exportname=foo
-+can't open device nbd+tcp://127.0.0.1:PORT/foo
-
- === Check disconnect 10 neg2 ===
-
--can't open device nbd:127.0.0.1:PORT:exportname=foo
-+can't open device nbd+tcp://127.0.0.1:PORT/foo
-
- === Check disconnect before request ===
-
-@@ -86,23 +86,132 @@ read 512/512 bytes at offset 0
-
- === Check disconnect before neg-classic ===
-
--can't open device nbd:127.0.0.1:PORT
-+can't open device nbd+tcp://127.0.0.1:PORT/
-
- === Check disconnect 8 neg-classic ===
-
--can't open device nbd:127.0.0.1:PORT
-+can't open device nbd+tcp://127.0.0.1:PORT/
-
- === Check disconnect 16 neg-classic ===
-
--can't open device nbd:127.0.0.1:PORT
-+can't open device nbd+tcp://127.0.0.1:PORT/
-
- === Check disconnect 24 neg-classic ===
-
--can't open device nbd:127.0.0.1:PORT
-+can't open device nbd+tcp://127.0.0.1:PORT/
-
- === Check disconnect 28 neg-classic ===
-
--can't open device nbd:127.0.0.1:PORT
-+can't open device nbd+tcp://127.0.0.1:PORT/
-+
-+=== Check disconnect after neg-classic ===
-+
-+read failed: Input/output error
-+
-+=== Check disconnect before neg1 ===
-+
-+can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect after neg1 ===
-+
-+can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect 8 neg1 ===
-+
-+can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect 16 neg1 ===
-+
-+can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect before export ===
-+
-+can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect after export ===
-+
-+can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect 4 export ===
-+
-+can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect 12 export ===
-+
-+can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect 16 export ===
-+
-+can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect before neg2 ===
-+
-+can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect after neg2 ===
-+
-+read failed: Input/output error
-+
-+=== Check disconnect 8 neg2 ===
-+
-+can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect 10 neg2 ===
-+
-+can't open device nbd+unix:///foo?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect before request ===
-+
-+read failed: Input/output error
-+
-+=== Check disconnect after request ===
-+
-+read failed: Input/output error
-+
-+=== Check disconnect before reply ===
-+
-+read failed: Input/output error
-+
-+=== Check disconnect after reply ===
-+
-+read failed: Input/output error
-+
-+=== Check disconnect 4 reply ===
-+
-+read failed: Input/output error
-+
-+=== Check disconnect 8 reply ===
-+
-+read failed: Input/output error
-+
-+=== Check disconnect before data ===
-+
-+read failed: Input/output error
-+
-+=== Check disconnect after data ===
-+
-+read 512/512 bytes at offset 0
-+512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-+
-+=== Check disconnect before neg-classic ===
-+
-+can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect 8 neg-classic ===
-+
-+can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect 16 neg-classic ===
-+
-+can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect 24 neg-classic ===
-+
-+can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock
-+
-+=== Check disconnect 28 neg-classic ===
-+
-+can't open device nbd+unix:///?socket=TEST_DIR/nbd.sock
-
- === Check disconnect after neg-classic ===
-
-diff --git a/tests/qemu-iotests/common.filter b/tests/qemu-iotests/common.filter
-index 104001358b..65ec315f25 100644
---- a/tests/qemu-iotests/common.filter
-+++ b/tests/qemu-iotests/common.filter
-@@ -153,9 +153,9 @@ _filter_nbd()
- #
- # Filter out the TCP port number since this changes between runs.
- sed -e '/nbd\/.*\.c:/d' \
-- -e 's#nbd:\(//\)\?127\.0\.0\.1:[0-9]*#nbd:\1127.0.0.1:PORT#g' \
-+ -e 's#127\.0\.0\.1:[0-9]*#127.0.0.1:PORT#g' \
- -e "s#?socket=$TEST_DIR#?socket=TEST_DIR#g" \
-- -e 's#\(exportname=foo\|PORT\): Failed to .*$#\1#'
-+ -e 's#\(foo\|PORT/\?\|.sock\): Failed to .*$#\1#'
- }
-
- # make sure this script returns success
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eric Blake <eblake@redhat.com>
-Date: Wed, 27 Sep 2017 17:57:25 +0200
-Subject: [PATCH] block/nbd-client: nbd_co_send_request: fix return code
-
-RH-Author: Eric Blake <eblake@redhat.com>
-Message-id: <20170927175725.20023-8-eblake@redhat.com>
-Patchwork-id: 76673
-O-Subject: [RHEV-7.4.z qemu-kvm-rhev PATCH 7/7] block/nbd-client: nbd_co_send_request: fix return code
-Bugzilla: 1495474
-RH-Acked-by: Max Reitz <mreitz@redhat.com>
-RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
-RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
-
-From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
-
-It's incorrect to return success rc >= 0 if we skip qio_channel_writev_all()
-call due to s->quit.
-
-Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
-Reviewed-by: Eric Blake <eblake@redhat.com>
-Message-Id: <20170920124507.18841-4-vsementsov@virtuozzo.com>
-Signed-off-by: Eric Blake <eblake@redhat.com>
-(cherry picked from commit a693437037328a95d815ad5aec37ac2f8e130e58)
-Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
----
- block/nbd-client.c | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/block/nbd-client.c b/block/nbd-client.c
-index 434acf647f..76789c1829 100644
---- a/block/nbd-client.c
-+++ b/block/nbd-client.c
-@@ -156,6 +156,8 @@ static int nbd_co_send_request(BlockDriverState *bs,
- if (ret != request->len) {
- rc = -EIO;
- }
-+ } else if (rc >= 0) {
-+ rc = -EIO;
- }
- qio_channel_set_cork(s->ioc, false);
- } else {
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Brijesh Singh <brijesh.singh@amd.com>
-Date: Tue, 15 Aug 2017 12:00:51 -0500
-Subject: [PATCH] target-i386/cpu: Add new EPYC CPU model
-
-Add a new base CPU model called 'EPYC' to model processors from AMD EPYC
-family (which includes EPYC 76xx,75xx,74xx, 73xx and 72xx).
-
-The following features bits have been added/removed compare to Opteron_G5
-
-Added: monitor, movbe, rdrand, mmxext, ffxsr, rdtscp, cr8legacy, osvw,
- fsgsbase, bmi1, avx2, smep, bmi2, rdseed, adx, smap, clfshopt, sha
- xsaveopt, xsavec, xgetbv1, arat
-
-Removed: xop, fma4, tbm
-
-Cc: Paolo Bonzini <pbonzini@redhat.com>
-Cc: Richard Henderson <rth@twiddle.net>
-Cc: Eduardo Habkost <ehabkost@redhat.com>
-Cc: Tom Lendacky <Thomas.Lendacky@amd.com>
-Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
-Message-Id: <20170815170051.127257-1-brijesh.singh@amd.com>
-Reviewed-by: Eduardo Habkost <ehabkost@redhat.com>
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
----
- target/i386/cpu.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
- 1 file changed, 44 insertions(+)
-
-diff --git a/target/i386/cpu.c b/target/i386/cpu.c
-index e4a2d5a012..e977c6c616 100644
---- a/target/i386/cpu.c
-+++ b/target/i386/cpu.c
-@@ -1923,6 +1923,50 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .xlevel = 0x8000001A,
- .model_id = "AMD Opteron 63xx class CPU",
- },
-+ {
-+ .name = "EPYC",
-+ .level = 0xd,
-+ .vendor = CPUID_VENDOR_AMD,
-+ .family = 23,
-+ .model = 1,
-+ .stepping = 2,
-+ .features[FEAT_1_EDX] =
-+ CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX | CPUID_CLFLUSH |
-+ CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA | CPUID_PGE |
-+ CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 | CPUID_MCE |
-+ CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE | CPUID_DE |
-+ CPUID_VME | CPUID_FP87,
-+ .features[FEAT_1_ECX] =
-+ CPUID_EXT_RDRAND | CPUID_EXT_F16C | CPUID_EXT_AVX |
-+ CPUID_EXT_XSAVE | CPUID_EXT_AES | CPUID_EXT_POPCNT |
-+ CPUID_EXT_MOVBE | CPUID_EXT_SSE42 | CPUID_EXT_SSE41 |
-+ CPUID_EXT_CX16 | CPUID_EXT_FMA | CPUID_EXT_SSSE3 |
-+ CPUID_EXT_MONITOR | CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSE3,
-+ .features[FEAT_8000_0001_EDX] =
-+ CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_PDPE1GB |
-+ CPUID_EXT2_FFXSR | CPUID_EXT2_MMXEXT | CPUID_EXT2_NX |
-+ CPUID_EXT2_SYSCALL,
-+ .features[FEAT_8000_0001_ECX] =
-+ CPUID_EXT3_OSVW | CPUID_EXT3_3DNOWPREFETCH |
-+ CPUID_EXT3_MISALIGNSSE | CPUID_EXT3_SSE4A | CPUID_EXT3_ABM |
-+ CPUID_EXT3_CR8LEG | CPUID_EXT3_SVM | CPUID_EXT3_LAHF_LM,
-+ .features[FEAT_7_0_EBX] =
-+ CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_BMI1 | CPUID_7_0_EBX_AVX2 |
-+ CPUID_7_0_EBX_SMEP | CPUID_7_0_EBX_BMI2 | CPUID_7_0_EBX_RDSEED |
-+ CPUID_7_0_EBX_ADX | CPUID_7_0_EBX_SMAP | CPUID_7_0_EBX_CLFLUSHOPT |
-+ CPUID_7_0_EBX_SHA_NI,
-+ /* Missing: XSAVES (not supported by some Linux versions,
-+ * including v4.1 to v4.12).
-+ * KVM doesn't yet expose any XSAVES state save component.
-+ */
-+ .features[FEAT_XSAVE] =
-+ CPUID_XSAVE_XSAVEOPT | CPUID_XSAVE_XSAVEC |
-+ CPUID_XSAVE_XGETBV1,
-+ .features[FEAT_6_EAX] =
-+ CPUID_6_EAX_ARAT,
-+ .xlevel = 0x8000000A,
-+ .model_id = "AMD EPYC Processor",
-+ },
- };
-
- typedef struct PropValue {
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Eduardo Habkost <ehabkost@redhat.com>
-Date: Tue, 9 Jan 2018 13:45:18 -0200
-Subject: [PATCH] i386: Add EPYC-IBPB CPU model
-
-EPYC-IBPB is a copy of the EPYC CPU model with
-just CPUID_8000_0008_EBX_IBPB added.
-
-Cc: Jiri Denemark <jdenemar@redhat.com>
-Cc: Tom Lendacky <thomas.lendacky@amd.com>
-Cc: Brijesh Singh <brijesh.singh@amd.com>
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
-Message-Id: <20180109154519.25634-7-ehabkost@redhat.com>
-Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
----
- target/i386/cpu.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
- 1 file changed, 46 insertions(+)
-
-diff --git a/target/i386/cpu.c b/target/i386/cpu.c
-index e977c6c616..95e6a1f122 100644
---- a/target/i386/cpu.c
-+++ b/target/i386/cpu.c
-@@ -1967,6 +1967,52 @@ static X86CPUDefinition builtin_x86_defs[] = {
- .xlevel = 0x8000000A,
- .model_id = "AMD EPYC Processor",
- },
-+ {
-+ .name = "EPYC-IBPB",
-+ .level = 0xd,
-+ .vendor = CPUID_VENDOR_AMD,
-+ .family = 23,
-+ .model = 1,
-+ .stepping = 2,
-+ .features[FEAT_1_EDX] =
-+ CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX | CPUID_CLFLUSH |
-+ CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA | CPUID_PGE |
-+ CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 | CPUID_MCE |
-+ CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE | CPUID_DE |
-+ CPUID_VME | CPUID_FP87,
-+ .features[FEAT_1_ECX] =
-+ CPUID_EXT_RDRAND | CPUID_EXT_F16C | CPUID_EXT_AVX |
-+ CPUID_EXT_XSAVE | CPUID_EXT_AES | CPUID_EXT_POPCNT |
-+ CPUID_EXT_MOVBE | CPUID_EXT_SSE42 | CPUID_EXT_SSE41 |
-+ CPUID_EXT_CX16 | CPUID_EXT_FMA | CPUID_EXT_SSSE3 |
-+ CPUID_EXT_MONITOR | CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSE3,
-+ .features[FEAT_8000_0001_EDX] =
-+ CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_PDPE1GB |
-+ CPUID_EXT2_FFXSR | CPUID_EXT2_MMXEXT | CPUID_EXT2_NX |
-+ CPUID_EXT2_SYSCALL,
-+ .features[FEAT_8000_0001_ECX] =
-+ CPUID_EXT3_OSVW | CPUID_EXT3_3DNOWPREFETCH |
-+ CPUID_EXT3_MISALIGNSSE | CPUID_EXT3_SSE4A | CPUID_EXT3_ABM |
-+ CPUID_EXT3_CR8LEG | CPUID_EXT3_SVM | CPUID_EXT3_LAHF_LM,
-+ .features[FEAT_8000_0008_EBX] =
-+ CPUID_8000_0008_EBX_IBPB,
-+ .features[FEAT_7_0_EBX] =
-+ CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_BMI1 | CPUID_7_0_EBX_AVX2 |
-+ CPUID_7_0_EBX_SMEP | CPUID_7_0_EBX_BMI2 | CPUID_7_0_EBX_RDSEED |
-+ CPUID_7_0_EBX_ADX | CPUID_7_0_EBX_SMAP | CPUID_7_0_EBX_CLFLUSHOPT |
-+ CPUID_7_0_EBX_SHA_NI,
-+ /* Missing: XSAVES (not supported by some Linux versions,
-+ * including v4.1 to v4.12).
-+ * KVM doesn't yet expose any XSAVES state save component.
-+ */
-+ .features[FEAT_XSAVE] =
-+ CPUID_XSAVE_XSAVEOPT | CPUID_XSAVE_XSAVEC |
-+ CPUID_XSAVE_XGETBV1,
-+ .features[FEAT_6_EAX] =
-+ CPUID_6_EAX_ARAT,
-+ .xlevel = 0x8000000A,
-+ .model_id = "AMD EPYC Processor (with IBPB)",
-+ },
- };
-
- typedef struct PropValue {
---
-2.11.0
-
--- /dev/null
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Wed, 29 Nov 2017 11:11:46 +0100
+Subject: [PATCH] block/file: change locking default to off
+
+'auto' only checks whether the system generally supports OFD
+locks but not whether the storage the file resides on
+supports any locking, causing issues with NFS.
+
+Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
+---
+ block/file-posix.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/block/file-posix.c b/block/file-posix.c
+index 36ee89e940..4bff572d07 100644
+--- a/block/file-posix.c
++++ b/block/file-posix.c
+@@ -405,7 +405,7 @@ static QemuOptsList raw_runtime_opts = {
+ {
+ .name = "locking",
+ .type = QEMU_OPT_STRING,
+- .help = "file locking mode (on/off/auto, default: auto)",
++ .help = "file locking mode (on/off/auto, default: off)",
+ },
+ {
+ .name = "pr-manager",
+@@ -481,7 +481,7 @@ static int raw_open_common(BlockDriverState *bs, QDict *options,
+ s->use_lock = false;
+ break;
+ case ON_OFF_AUTO_AUTO:
+- s->use_lock = qemu_has_ofd_lock();
++ s->use_lock = false;
+ break;
+ default:
+ abort();
+--
+2.11.0
+
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Wed, 9 Dec 2015 14:15:49 +0100
-Subject: [PATCH] fr-ca keymap corrections
-
----
- pc-bios/keymaps/fr-ca | 9 +++++++++
- 1 file changed, 9 insertions(+)
-
-diff --git a/pc-bios/keymaps/fr-ca b/pc-bios/keymaps/fr-ca
-index b645208e42..92912409e1 100644
---- a/pc-bios/keymaps/fr-ca
-+++ b/pc-bios/keymaps/fr-ca
-@@ -14,22 +14,31 @@ bar 0x29 shift
- twosuperior 0x9 altgr
- threesuperior 0xa altgr
- onequarter 0xb altgr
-+minus 0x0c
- onehalf 0xc altgr
-+equal 0xd
- threequarters 0xd altgr
- section 0x18 altgr
- paragraph 0x19 altgr
- bracketleft 0x1a altgr
- bracketright 0x1b altgr
-+semicolon 0x27
-+colon 0x27 shift
- asciitilde 0x27 altgr
- braceleft 0x28 altgr
-+numbersign 0x29
- braceright 0x2b altgr
- less 0x2b
- greater 0x2b shift
- guillemotleft 0x56
- guillemotright 0x56 shift
- degree 0x56 altgr
-+comma 0x33
- mu 0x32 altgr
-+apostrophe 0x33 shift
-+period 0x34 shift
- eacute 0x35
-+Eacute 0x35 shift
- dead_acute 0x35 altgr
- dead_grave 0x28
- dead_circumflex 0x1a
---
-2.11.0
-
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/include/net/net.h b/include/net/net.h
-index 99b28d5b38..40c39f00e6 100644
+index 1c55a93588..13ecb9cc8c 100644
--- a/include/net/net.h
+++ b/include/net/net.h
-@@ -214,8 +214,9 @@ void qmp_netdev_add(QDict *qdict, QObject **ret, Error **errp);
+@@ -220,8 +220,9 @@ void qmp_netdev_add(QDict *qdict, QObject **ret, Error **errp);
int net_hub_id_for_client(NetClientState *nc, int *id);
NetClientState *net_hub_port_find(int hub_id);
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/qemu-img.c b/qemu-img.c
-index e4a2686f56..c7804d63ee 100644
+index 68b375f998..f8a39dd195 100644
--- a/qemu-img.c
+++ b/qemu-img.c
-@@ -2596,7 +2596,8 @@ static int img_info(int argc, char **argv)
-
- list = collect_image_info_list(image_opts, filename, fmt, chain);
+@@ -2594,7 +2594,8 @@ static int img_info(int argc, char **argv)
+ list = collect_image_info_list(image_opts, filename, fmt, chain,
+ force_share);
if (!list) {
- return 1;
+ // return success if snapshot does not exists
Subject: [PATCH] use kvm by default
---
- accel.c | 4 ++--
+ accel/accel.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
-diff --git a/accel.c b/accel.c
-index 664bb88422..ddb23a3f1d 100644
---- a/accel.c
-+++ b/accel.c
-@@ -87,8 +87,8 @@ void configure_accelerator(MachineState *ms)
+diff --git a/accel/accel.c b/accel/accel.c
+index 8ae40e1e13..df21981026 100644
+--- a/accel/accel.c
++++ b/accel/accel.c
+@@ -79,8 +79,8 @@ void configure_accelerator(MachineState *ms)
- p = qemu_opt_get(qemu_get_machine_opts(), "accel");
- if (p == NULL) {
+ accel = qemu_opt_get(qemu_get_machine_opts(), "accel");
+ if (accel == NULL) {
- /* Use the default "accelerator", tcg */
-- p = "tcg";
+- accel = "tcg";
+ /* Use the default "accelerator", kvm */
-+ p = "kvm";
++ accel = "kvm";
}
- while (!accel_initialised && *p != '\0') {
+ p = accel;
--
2.11.0
---
hmp.c | 30 +++++++++++++++++++++++++++++-
hw/virtio/virtio-balloon.c | 33 +++++++++++++++++++++++++++++++--
- qapi-schema.json | 23 +++++++++++++++++++++--
- 3 files changed, 81 insertions(+), 5 deletions(-)
+ qapi-schema.json | 22 +++++++++++++++++++++-
+ 3 files changed, 81 insertions(+), 4 deletions(-)
diff --git a/hmp.c b/hmp.c
-index edb8970461..904542d026 100644
+index 35a7041824..4e1d571003 100644
--- a/hmp.c
+++ b/hmp.c
-@@ -723,7 +723,35 @@ void hmp_info_balloon(Monitor *mon, const QDict *qdict)
+@@ -789,7 +789,35 @@ void hmp_info_balloon(Monitor *mon, const QDict *qdict)
return;
}
qapi_free_BalloonInfo(info);
}
diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
-index a705e0ec55..158e13e795 100644
+index 37cde38982..1feaf77223 100644
--- a/hw/virtio/virtio-balloon.c
+++ b/hw/virtio/virtio-balloon.c
-@@ -379,8 +379,37 @@ static uint64_t virtio_balloon_get_features(VirtIODevice *vdev, uint64_t f,
+@@ -380,8 +380,37 @@ static uint64_t virtio_balloon_get_features(VirtIODevice *vdev, uint64_t f,
static void virtio_balloon_stat(void *opaque, BalloonInfo *info)
{
VirtIOBalloon *dev = opaque;
static void virtio_balloon_to_target(void *opaque, ram_addr_t target)
diff --git a/qapi-schema.json b/qapi-schema.json
-index 250e4dc49b..f38b85bf6a 100644
+index 18457954a8..ebc22fe5a6 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
-@@ -1900,10 +1900,29 @@
+@@ -623,10 +623,30 @@
#
# @actual: the number of bytes the balloon currently contains
#
--# Since: 0.14.0
+# @last_update: time when stats got updated from guest
+#
+# @mem_swapped_in: number of pages swapped in within the guest
+# @mem_swapped_out: number of pages swapped out within the guest
+#
+# @major_page_faults: number of major page faults within the guest
- #
++#
+# @minor_page_faults: number of minor page faults within the guest
+#
+# @free_mem: amount of memory (in bytes) free in the guest
+# @total_mem: amount of memory (in bytes) visible to the guest
+#
+# @max_mem: amount of memory (in bytes) assigned to the guest
-+#
-+# Since: 0.14.0
++#
+ # Since: 0.14.0
+ #
##
-{ 'struct': 'BalloonInfo', 'data': {'actual': 'int' } }
+{ 'struct': 'BalloonInfo',
Subject: [PATCH] set the CPU model to kvm64/32 instead of qemu64/32
---
- hw/i386/pc.c | 4 ++--
+ target/i386/cpu.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
-diff --git a/hw/i386/pc.c b/hw/i386/pc.c
-index 610050eb4f..e96901435a 100644
---- a/hw/i386/pc.c
-+++ b/hw/i386/pc.c
-@@ -1151,9 +1151,9 @@ void pc_cpus_init(PCMachineState *pcms)
- /* init CPUs */
- if (machine->cpu_model == NULL) {
+diff --git a/target/i386/cpu.h b/target/i386/cpu.h
+index f3d0ebb673..660e42977b 100644
+--- a/target/i386/cpu.h
++++ b/target/i386/cpu.h
+@@ -1521,9 +1521,9 @@ uint64_t cpu_get_tsc(CPUX86State *env);
+ #define X86_CPU_TYPE_NAME(name) (name X86_CPU_TYPE_SUFFIX)
+
#ifdef TARGET_X86_64
-- machine->cpu_model = "qemu64";
-+ machine->cpu_model = "kvm64";
+-#define TARGET_DEFAULT_CPU_TYPE X86_CPU_TYPE_NAME("qemu64")
++#define TARGET_DEFAULT_CPU_TYPE X86_CPU_TYPE_NAME("kvm64")
#else
-- machine->cpu_model = "qemu32";
-+ machine->cpu_model = "kvm32";
+-#define TARGET_DEFAULT_CPU_TYPE X86_CPU_TYPE_NAME("qemu32")
++#define TARGET_DEFAULT_CPU_TYPE X86_CPU_TYPE_NAME("kvm32")
#endif
- }
+ #define cpu_signal_handler cpu_x86_signal_handler
--
2.11.0
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/qapi-schema.json b/qapi-schema.json
-index f38b85bf6a..51e150c6c1 100644
+index ebc22fe5a6..8f436ba1f3 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
-@@ -4242,6 +4242,8 @@
+@@ -1914,6 +1914,8 @@
#
# @is-default: whether the machine is default
#
# @cpu-max: maximum number of CPUs supported by the machine type
# (since 1.5.0)
#
-@@ -4251,7 +4253,7 @@
+@@ -1923,7 +1925,7 @@
##
{ 'struct': 'MachineInfo',
'data': { 'name': 'str', '*alias': 'str',
##
diff --git a/vl.c b/vl.c
-index b719cc432e..46de1b9087 100644
+index 1ad1c04637..2e0fe15978 100644
--- a/vl.c
+++ b/vl.c
-@@ -1518,6 +1518,11 @@ MachineInfoList *qmp_query_machines(Error **errp)
+@@ -1604,6 +1604,11 @@ MachineInfoList *qmp_query_machines(Error **errp)
info->cpu_max = !mc->max_cpus ? 1 : mc->max_cpus;
info->hotpluggable_cpus = mc->has_hotpluggable_cpus;
Provide the last ticket in the SpiceInfo struct optionally.
---
- qapi-schema.json | 3 +++
- ui/spice-core.c | 5 +++++
+ qapi/ui.json | 3 +++
+ ui/spice-core.c | 5 +++++
2 files changed, 8 insertions(+)
-diff --git a/qapi-schema.json b/qapi-schema.json
-index 51e150c6c1..1b14ff2476 100644
---- a/qapi-schema.json
-+++ b/qapi-schema.json
-@@ -1841,11 +1841,14 @@
+diff --git a/qapi/ui.json b/qapi/ui.json
+index 07b468f625..78c906ddcf 100644
+--- a/qapi/ui.json
++++ b/qapi/ui.json
+@@ -199,11 +199,14 @@
#
# @channels: a list of @SpiceChannel for each active spice channel
#
##
diff --git a/ui/spice-core.c b/ui/spice-core.c
-index 804abc5c0f..4a417310d3 100644
+index ea04dc69b5..05f5958b14 100644
--- a/ui/spice-core.c
+++ b/ui/spice-core.c
-@@ -552,6 +552,11 @@ SpiceInfo *qmp_query_spice(Error **errp)
+@@ -551,6 +551,11 @@ SpiceInfo *qmp_query_spice(Error **errp)
micro = SPICE_SERVER_VERSION & 0xff;
info->compiled_version = g_strdup_printf("%d.%d.%d", major, minor, micro);
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/ui/spice-core.c b/ui/spice-core.c
-index 4a417310d3..af1dc8c653 100644
+index 05f5958b14..6e1a46d1e5 100644
--- a/ui/spice-core.c
+++ b/ui/spice-core.c
-@@ -685,32 +685,35 @@ void qemu_spice_init(void)
+@@ -684,32 +684,35 @@ void qemu_spice_init(void)
if (tls_port) {
x509_dir = qemu_opt_get(opts, "x509-dir");
Subject: [PATCH] internal snapshot async
---
- Makefile.objs | 1 +
- hmp-commands-info.hx | 13 ++
- hmp-commands.hx | 32 +++
- hmp.c | 57 ++++++
- hmp.h | 5 +
- include/sysemu/sysemu.h | 5 +-
- migration/savevm.c | 12 +-
- qapi-schema.json | 68 +++++++
- qemu-options.hx | 13 ++
- savevm-async.c | 523 ++++++++++++++++++++++++++++++++++++++++++++++++
- vl.c | 8 +
- 11 files changed, 730 insertions(+), 7 deletions(-)
+ Makefile.objs | 2 +-
+ hmp-commands-info.hx | 13 ++
+ hmp-commands.hx | 32 +++
+ hmp.c | 57 +++++
+ hmp.h | 5 +
+ include/migration/snapshot.h | 1 +
+ qapi-schema.json | 32 +++
+ qapi/migration.json | 34 +++
+ qemu-options.hx | 13 ++
+ savevm-async.c | 524 +++++++++++++++++++++++++++++++++++++++++++
+ vl.c | 10 +
+ 11 files changed, 722 insertions(+), 1 deletion(-)
create mode 100644 savevm-async.c
diff --git a/Makefile.objs b/Makefile.objs
-index 6167e7b17d..fbfbbb7f70 100644
+index 285c6f3c15..686247b556 100644
--- a/Makefile.objs
+++ b/Makefile.objs
-@@ -50,6 +50,7 @@ common-obj-$(CONFIG_LINUX) += fsdev/
-
- common-obj-y += migration/
- common-obj-y += page_cache.o #aio.o
+@@ -41,6 +41,7 @@ io-obj-y = io/
+ ifeq ($(CONFIG_SOFTMMU),y)
+ common-obj-y = blockdev.o blockdev-nbd.o block/
+ common-obj-y += bootdevice.o iothread.o
+common-obj-y += savevm-async.o
+ common-obj-y += net/
+ common-obj-y += qdev-monitor.o device-hotplug.o
+ common-obj-$(CONFIG_WIN32) += os-win32.o
+@@ -49,7 +50,6 @@ common-obj-$(CONFIG_POSIX) += os-posix.o
+ common-obj-$(CONFIG_LINUX) += fsdev/
- common-obj-$(CONFIG_SPICE) += spice-qemu-char.o
+ common-obj-y += migration/
+-
+ common-obj-y += audio/
+ common-obj-y += hw/
diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
-index a53f105c52..5fc57a2210 100644
+index 54c3e5eac6..3bf69a193c 100644
--- a/hmp-commands-info.hx
+++ b/hmp-commands-info.hx
-@@ -560,6 +560,19 @@ Show current migration xbzrle cache size.
+@@ -566,6 +566,19 @@ Show current migration xbzrle cache size.
ETEXI
{
.args_type = "",
.params = "",
diff --git a/hmp-commands.hx b/hmp-commands.hx
-index 88192817b2..58940a762b 100644
+index 4afd57cf5f..b35bc6ab6c 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
-@@ -1777,3 +1777,35 @@ ETEXI
+@@ -1873,3 +1873,35 @@ ETEXI
STEXI
@end table
ETEXI
+ .cmd = hmp_savevm_end,
+ },
diff --git a/hmp.c b/hmp.c
-index 904542d026..f725d061e6 100644
+index 4e1d571003..b9ade681f0 100644
--- a/hmp.c
+++ b/hmp.c
-@@ -2207,6 +2207,63 @@ void hmp_info_memory_devices(Monitor *mon, const QDict *qdict)
- qapi_free_MemoryDeviceInfoList(info_list);
+@@ -2486,6 +2486,63 @@ void hmp_info_memory_devices(Monitor *mon, const QDict *qdict)
+ hmp_handle_error(mon, &err);
}
+void hmp_savevm_start(Monitor *mon, const QDict *qdict)
{
IOThreadInfoList *info_list = qmp_query_iothreads(NULL);
diff --git a/hmp.h b/hmp.h
-index 799fd371fa..0497afbf65 100644
+index a6f56b1f29..45ada581b6 100644
--- a/hmp.h
+++ b/hmp.h
@@ -26,6 +26,7 @@ void hmp_info_status(Monitor *mon, const QDict *qdict);
void hmp_info_migrate(Monitor *mon, const QDict *qdict);
void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict);
void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict);
-@@ -92,6 +93,10 @@ void hmp_netdev_add(Monitor *mon, const QDict *qdict);
+@@ -97,6 +98,10 @@ void hmp_netdev_add(Monitor *mon, const QDict *qdict);
void hmp_netdev_del(Monitor *mon, const QDict *qdict);
void hmp_getfd(Monitor *mon, const QDict *qdict);
void hmp_closefd(Monitor *mon, const QDict *qdict);
void hmp_sendkey(Monitor *mon, const QDict *qdict);
void hmp_screendump(Monitor *mon, const QDict *qdict);
void hmp_nbd_server_start(Monitor *mon, const QDict *qdict);
-diff --git a/include/sysemu/sysemu.h b/include/sysemu/sysemu.h
-index 576c7ce640..74623de16c 100644
---- a/include/sysemu/sysemu.h
-+++ b/include/sysemu/sysemu.h
-@@ -78,6 +78,7 @@ void qemu_remove_machine_init_done_notifier(Notifier *notify);
- void hmp_savevm(Monitor *mon, const QDict *qdict);
- int save_vmstate(Monitor *mon, const char *name);
- int load_vmstate(const char *name);
-+int load_state_from_blockdev(const char *filename);
- void hmp_delvm(Monitor *mon, const QDict *qdict);
- void hmp_info_snapshots(Monitor *mon, const QDict *qdict);
-
-@@ -105,13 +106,13 @@ enum qemu_vm_cmd {
- #define MAX_VM_CMD_PACKAGED_SIZE (1ul << 24)
-
- bool qemu_savevm_state_blocked(Error **errp);
--void qemu_savevm_state_begin(QEMUFile *f,
-+int qemu_savevm_state_begin(QEMUFile *f,
- const MigrationParams *params);
- void qemu_savevm_state_header(QEMUFile *f);
- int qemu_savevm_state_iterate(QEMUFile *f, bool postcopy);
- void qemu_savevm_state_cleanup(void);
- void qemu_savevm_state_complete_postcopy(QEMUFile *f);
--void qemu_savevm_state_complete_precopy(QEMUFile *f, bool iterable_only);
-+int qemu_savevm_state_complete_precopy(QEMUFile *f, bool iterable_only);
- void qemu_savevm_state_pending(QEMUFile *f, uint64_t max_size,
- uint64_t *res_non_postcopiable,
- uint64_t *res_postcopiable);
-diff --git a/migration/savevm.c b/migration/savevm.c
-index 3b19a4a274..feb0dc6834 100644
---- a/migration/savevm.c
-+++ b/migration/savevm.c
-@@ -970,11 +970,11 @@ void qemu_savevm_state_header(QEMUFile *f)
-
- }
-
--void qemu_savevm_state_begin(QEMUFile *f,
-+int qemu_savevm_state_begin(QEMUFile *f,
- const MigrationParams *params)
- {
- SaveStateEntry *se;
-- int ret;
-+ int ret = 0;
-
- trace_savevm_state_begin();
- QTAILQ_FOREACH(se, &savevm_state.handlers, entry) {
-@@ -1002,6 +1002,7 @@ void qemu_savevm_state_begin(QEMUFile *f,
- break;
- }
- }
-+ return ret;
- }
-
- /*
-@@ -1105,7 +1106,7 @@ void qemu_savevm_state_complete_postcopy(QEMUFile *f)
- qemu_fflush(f);
- }
-
--void qemu_savevm_state_complete_precopy(QEMUFile *f, bool iterable_only)
-+int qemu_savevm_state_complete_precopy(QEMUFile *f, bool iterable_only)
- {
- QJSON *vmdesc;
- int vmdesc_len;
-@@ -1139,12 +1140,12 @@ void qemu_savevm_state_complete_precopy(QEMUFile *f, bool iterable_only)
- save_section_footer(f, se);
- if (ret < 0) {
- qemu_file_set_error(f, ret);
-- return;
-+ return ret;
- }
- }
+diff --git a/include/migration/snapshot.h b/include/migration/snapshot.h
+index c85b6ec75b..4411b7121d 100644
+--- a/include/migration/snapshot.h
++++ b/include/migration/snapshot.h
+@@ -17,5 +17,6 @@
- if (iterable_only) {
-- return;
-+ return ret;
- }
+ int save_snapshot(const char *name, Error **errp);
+ int load_snapshot(const char *name, Error **errp);
++int load_snapshot_from_blockdev(const char *filename, Error **errp);
- vmdesc = qjson_new();
-@@ -1191,6 +1192,7 @@ void qemu_savevm_state_complete_precopy(QEMUFile *f, bool iterable_only)
- qjson_destroy(vmdesc);
-
- qemu_fflush(f);
-+ return qemu_file_get_error(f);
- }
-
- /* Give an estimate of the amount left to be transferred,
+ #endif
diff --git a/qapi-schema.json b/qapi-schema.json
-index 1b14ff2476..361700d37c 100644
+index 8f436ba1f3..348b527681 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
-@@ -723,6 +723,40 @@
+@@ -2439,6 +2439,38 @@
+ { 'command': 'query-target', 'returns': 'TargetInfo' }
+
+ ##
++# @savevm-start:
++#
++# Prepare for snapshot and halt VM. Save VM state to statefile.
++#
++##
++{ 'command': 'savevm-start', 'data': { '*statefile': 'str' } }
++
++##
++# @snapshot-drive:
++#
++# Create an internal drive snapshot.
++#
++##
++{ 'command': 'snapshot-drive', 'data': { 'device': 'str', 'name': 'str' } }
++
++##
++# @delete-drive-snapshot:
++#
++# Delete a drive snapshot.
++#
++##
++{ 'command': 'delete-drive-snapshot', 'data': { 'device': 'str', 'name': 'str' } }
++
++##
++# @savevm-end:
++#
++# Resume VM after a snapshot.
++#
++##
++{ 'command': 'savevm-end' }
++
++##
+ # @AcpiTableOptions:
+ #
+ # Specify an ACPI table on the command line to load.
+diff --git a/qapi/migration.json b/qapi/migration.json
+index 03f57c9616..9ae55b81a2 100644
+--- a/qapi/migration.json
++++ b/qapi/migration.json
+@@ -170,6 +170,40 @@
'*error-desc': 'str'} }
##
# @query-migrate:
#
# Returns information about current migration process. If migration
-@@ -4735,9 +4769,43 @@
- #
- # Since: 1.2.0
- ##
-+
- { 'command': 'query-target', 'returns': 'TargetInfo' }
-
- ##
-+# @savevm-start:
-+#
-+# Prepare for snapshot and halt VM. Save VM state to statefile.
-+#
-+##
-+{ 'command': 'savevm-start', 'data': { '*statefile': 'str' } }
-+
-+##
-+# @snapshot-drive:
-+#
-+# Create an internal drive snapshot.
-+#
-+##
-+{ 'command': 'snapshot-drive', 'data': { 'device': 'str', 'name': 'str' } }
-+
-+##
-+# @delete-drive-snapshot:
-+#
-+# Delete a drive snapshot.
-+#
-+##
-+{ 'command': 'delete-drive-snapshot', 'data': { 'device': 'str', 'name': 'str' } }
-+
-+##
-+# @savevm-end:
-+#
-+# Resume VM after a snapshot.
-+#
-+##
-+{ 'command': 'savevm-end' }
-+
-+
-+##
- # @QKeyCode:
- #
- # An enumeration of key name.
diff --git a/qemu-options.hx b/qemu-options.hx
-index 48dfffd86a..cbcb27da9a 100644
+index 57f2c6a75f..7c054af8f9 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
-@@ -3396,6 +3396,19 @@ STEXI
+@@ -3698,6 +3698,19 @@ STEXI
Start right away with a saved state (@code{loadvm} in monitor)
ETEXI
"-daemonize daemonize QEMU after initializing\n", QEMU_ARCH_ALL)
diff --git a/savevm-async.c b/savevm-async.c
new file mode 100644
-index 0000000000..5fcb56d373
+index 0000000000..897134ab5a
--- /dev/null
+++ b/savevm-async.c
-@@ -0,0 +1,523 @@
+@@ -0,0 +1,524 @@
+#include "qemu/osdep.h"
-+#include "qemu-common.h"
++#include "migration/migration.h"
++#include "migration/savevm.h"
++#include "migration/snapshot.h"
++#include "migration/global_state.h"
++#include "migration/ram.h"
++#include "migration/qemu-file.h"
+#include "qapi/qmp/qerror.h"
-+#include "qemu/error-report.h"
+#include "sysemu/sysemu.h"
+#include "qmp-commands.h"
-+#include "qemu-options.h"
-+#include "migration/qemu-file.h"
-+#include "qom/qom-qobject.h"
-+#include "migration/migration.h"
-+#include "block/snapshot.h"
-+#include "block/qapi.h"
+#include "block/block.h"
-+#include "qemu/timer.h"
+#include "sysemu/block-backend.h"
+#include "qapi/qmp/qstring.h"
-+#include "qemu/rcu.h"
-+#include "qemu/thread.h"
+#include "qemu/cutils.h"
+
+/* #define DEBUG_SAVEVM_STATE */
+ * note: bdrv_read() need whole blocks, so we round up
+ */
+ size_t size = (snap_state.bs_pos + BDRV_SECTOR_SIZE) & BDRV_SECTOR_MASK;
-+ blk_truncate(snap_state.target, size, NULL);
++ blk_truncate(snap_state.target, size, PREALLOC_MODE_OFF, NULL);
+ blk_op_unblock_all(snap_state.target, snap_state.blocker);
+ error_free(snap_state.blocker);
+ snap_state.blocker = NULL;
+{
+ int ret;
+ int64_t maxlen;
-+ MigrationParams params = {
-+ .blk = 0,
-+ .shared = 0
-+ };
+
+ snap_state.state = SAVE_STATE_ACTIVE;
+
+ qemu_mutex_unlock_iothread();
+ qemu_savevm_state_header(snap_state.file);
-+ ret = qemu_savevm_state_begin(snap_state.file, ¶ms);
++ qemu_savevm_state_setup(snap_state.file);
++ ret = qemu_file_get_error(snap_state.file);
+ qemu_mutex_lock_iothread();
+
+ if (ret < 0) {
-+ save_snapshot_error("qemu_savevm_state_begin failed");
++ save_snapshot_error("qemu_savevm_state_setup failed");
+ return;
+ }
+
+ if (store_and_stop())
+ break;
+ DPRINTF("savevm inerate finished\n");
-+ qemu_savevm_state_complete_precopy(snap_state.file, false);
++ /* upstream made the return value here inconsistent
++ * (-1 instead of 'ret' in one case and 0 after flush which can
++ * still set a file error...)
++ */
++ (void)qemu_savevm_state_complete_precopy(snap_state.file, false, false);
++ ret = qemu_file_get_error(snap_state.file);
++ if (ret < 0) {
++ save_snapshot_error("qemu_savevm_state_iterate error %d", ret);
++ break;
++ }
+ DPRINTF("save complete\n");
+ save_snapshot_completed();
+ break;
+ .get_buffer = loadstate_get_buffer,
+};
+
-+int load_state_from_blockdev(const char *filename)
++int load_snapshot_from_blockdev(const char *filename, Error **errp)
+{
+ BlockBackend *be;
+ Error *local_err = NULL;
+ be = blk_new_open(filename, NULL, NULL, 0, &local_err);
+
+ if (!be) {
-+ error_report("Could not open VM state file");
++ error_setg(errp, "Could not open VM state file");
+ goto the_end;
+ }
+
+ /* restore the VM state */
+ f = qemu_fopen_ops(be, &loadstate_file_ops);
+ if (!f) {
-+ error_report("Could not open VM state file");
++ error_setg(errp, "Could not open VM state file");
+ goto the_end;
+ }
+
-+ qemu_system_reset(VMRESET_SILENT);
++ qemu_system_reset(SHUTDOWN_CAUSE_NONE);
+ ret = qemu_loadvm_state(f);
+
+ qemu_fclose(f);
+ migration_incoming_state_destroy();
+ if (ret < 0) {
-+ error_report("Error %d while loading VM state", ret);
++ error_setg_errno(errp, -ret, "Error while loading VM state");
+ goto the_end;
+ }
+
+ return ret;
+}
diff --git a/vl.c b/vl.c
-index 46de1b9087..2132a77129 100644
+index 2e0fe15978..1bfbe95b22 100644
--- a/vl.c
+++ b/vl.c
-@@ -2960,6 +2960,7 @@ int main(int argc, char **argv, char **envp)
+@@ -3109,6 +3109,7 @@ int main(int argc, char **argv, char **envp)
int optind;
const char *optarg;
const char *loadvm = NULL;
MachineClass *machine_class;
const char *cpu_model;
const char *vga_model = NULL;
-@@ -3635,6 +3636,9 @@ int main(int argc, char **argv, char **envp)
+@@ -3785,6 +3786,9 @@ int main(int argc, char **argv, char **envp)
case QEMU_OPTION_loadvm:
loadvm = optarg;
break;
case QEMU_OPTION_full_screen:
full_screen = 1;
break;
-@@ -4693,6 +4697,10 @@ int main(int argc, char **argv, char **envp)
- if (load_vmstate(loadvm) < 0) {
+@@ -4891,6 +4895,12 @@ int main(int argc, char **argv, char **envp)
+ error_report_err(local_err);
autostart = 0;
}
+ } else if (loadstate) {
-+ if (load_state_from_blockdev(loadstate) < 0) {
++ Error *local_err = NULL;
++ if (load_snapshot_from_blockdev(loadstate, &local_err) < 0) {
++ error_report_err(local_err);
+ autostart = 0;
+ }
}
Subject: [PATCH] convert savevm-async to threads
---
- savevm-async.c | 144 +++++++++++++++++++++++++++++++++++----------------------
- 1 file changed, 88 insertions(+), 56 deletions(-)
+ savevm-async.c | 143 +++++++++++++++++++++++++++++++++++----------------------
+ 1 file changed, 87 insertions(+), 56 deletions(-)
diff --git a/savevm-async.c b/savevm-async.c
-index 5fcb56d373..97d51d3edd 100644
+index 897134ab5a..96523c88ae 100644
--- a/savevm-async.c
+++ b/savevm-async.c
-@@ -48,6 +48,8 @@ static struct SnapshotState {
+@@ -43,6 +43,8 @@ static struct SnapshotState {
int saved_vm_running;
QEMUFile *file;
int64_t total_time;
} snap_state;
SaveVMInfo *qmp_query_savevm(Error **errp)
-@@ -135,19 +137,6 @@ static void save_snapshot_error(const char *fmt, ...)
+@@ -130,19 +132,6 @@ static void save_snapshot_error(const char *fmt, ...)
g_free (msg);
snap_state.state = SAVE_STATE_ERROR;
}
static int block_state_close(void *opaque)
-@@ -156,51 +145,90 @@ static int block_state_close(void *opaque)
+@@ -151,48 +140,86 @@ static int block_state_close(void *opaque)
return blk_flush(snap_state.target);
}
{
int ret;
int64_t maxlen;
-+
- MigrationParams params = {
- .blk = 0,
- .shared = 0
- };
- snap_state.state = SAVE_STATE_ACTIVE;
+ rcu_register_thread();
- qemu_mutex_unlock_iothread();
qemu_savevm_state_header(snap_state.file);
- ret = qemu_savevm_state_begin(snap_state.file, ¶ms);
+ qemu_savevm_state_setup(snap_state.file);
+ ret = qemu_file_get_error(snap_state.file);
- qemu_mutex_lock_iothread();
if (ret < 0) {
- save_snapshot_error("qemu_savevm_state_begin failed");
+ save_snapshot_error("qemu_savevm_state_setup failed");
- return;
+ rcu_unregister_thread();
+ return NULL;
}
while (snap_state.state == SAVE_STATE_ACTIVE) {
-@@ -209,41 +237,43 @@ static void process_savevm_co(void *opaque)
+@@ -201,17 +228,30 @@ static void process_savevm_co(void *opaque)
qemu_savevm_state_pending(snap_state.file, 0, &pend_nonpost, &pend_post);
pending_size = pend_post + pend_nonpost;
+ break;
+ }
DPRINTF("savevm inerate finished\n");
- qemu_savevm_state_complete_precopy(snap_state.file, false);
+ /* upstream made the return value here inconsistent
+ * (-1 instead of 'ret' in one case and 0 after flush which can
+@@ -223,28 +263,17 @@ static void process_savevm_co(void *opaque)
+ save_snapshot_error("qemu_savevm_state_iterate error %d", ret);
+ break;
+ }
+ qemu_savevm_state_cleanup();
DPRINTF("save complete\n");
- save_snapshot_completed();
}
static const QEMUFileOps block_file_ops = {
-@@ -306,8 +336,10 @@ void qmp_savevm_start(bool has_statefile, const char *statefile, Error **errp)
+@@ -307,8 +336,10 @@ void qmp_savevm_start(bool has_statefile, const char *statefile, Error **errp)
error_setg(&snap_state.blocker, "block device is in use by savevm");
blk_op_block_all(snap_state.target, snap_state.blocker);
Subject: [PATCH] qmp: add get_link_status
---
- net/net.c | 27 +++++++++++++++++++++++++++
- qapi-schema.json | 16 ++++++++++++++++
- 2 files changed, 43 insertions(+)
+ net/net.c | 27 +++++++++++++++++++++++++++
+ qapi/net.json | 15 +++++++++++++++
+ 2 files changed, 42 insertions(+)
diff --git a/net/net.c b/net/net.c
-index 0ac3b9e80c..7410c1e5f3 100644
+index 39ef546708..3681aa2173 100644
--- a/net/net.c
+++ b/net/net.c
-@@ -1373,6 +1373,33 @@ void hmp_info_network(Monitor *mon, const QDict *qdict)
+@@ -1399,6 +1399,33 @@ void hmp_info_network(Monitor *mon, const QDict *qdict)
}
}
void qmp_set_link(const char *name, bool up, Error **errp)
{
NetClientState *ncs[MAX_QUEUE_NUM];
-diff --git a/qapi-schema.json b/qapi-schema.json
-index 361700d37c..5e82933ca1 100644
---- a/qapi-schema.json
-+++ b/qapi-schema.json
-@@ -56,6 +56,7 @@
- { 'pragma': {
- # Commands allowed to return a non-dictionary:
- 'returns-whitelist': [
-+ 'get_link_status',
- 'human-monitor-command',
- 'qom-get',
- 'query-migrate-cache-size',
-@@ -2537,6 +2538,21 @@
+diff --git a/qapi/net.json b/qapi/net.json
+index 4beff5d582..73334c8f3c 100644
+--- a/qapi/net.json
++++ b/qapi/net.json
+@@ -35,6 +35,21 @@
{ 'command': 'set_link', 'data': {'name': 'str', 'up': 'bool'} }
##
+{ 'command': 'get_link_status', 'data': {'name': 'str'}, 'returns': 'int'}
+
+##
- # @balloon:
+ # @netdev_add:
#
- # Request the balloon driver to change its balloon size.
+ # Add a network backend.
--
2.11.0
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hw/i386/pc.c b/hw/i386/pc.c
-index e96901435a..f049bbca9a 100644
+index 186545d2a4..603a7ce6bf 100644
--- a/hw/i386/pc.c
+++ b/hw/i386/pc.c
-@@ -2123,7 +2123,7 @@ bool pc_machine_is_smm_enabled(PCMachineState *pcms)
+@@ -2135,7 +2135,7 @@ bool pc_machine_is_smm_enabled(PCMachineState *pcms)
if (tcg_enabled() || qtest_enabled()) {
smm_available = true;
} else if (kvm_enabled()) {
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/vl.c b/vl.c
-index 2132a77129..5d888cd179 100644
+index 1bfbe95b22..75fde82180 100644
--- a/vl.c
+++ b/vl.c
-@@ -1909,7 +1909,7 @@ static void main_loop(void)
+@@ -2006,7 +2006,7 @@ static void main_loop(void)
static void version(void)
{
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/ui/vnc.c b/ui/vnc.c
-index b0314441c4..f30687884b 100644
+index 06abe7360e..03f8f61b2e 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
-@@ -1631,6 +1631,10 @@ static void kbd_leds(void *opaque, int ledstate)
+@@ -1775,6 +1775,10 @@ static void kbd_leds(void *opaque, int ledstate)
static void do_key_event(VncState *vs, int down, int keycode, int sym)
{
/* QEMU console switch */
switch(keycode) {
case 0x2a: /* Left Shift */
-@@ -1711,8 +1715,27 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym)
+@@ -1855,8 +1859,27 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym)
}
if (qemu_console_is_graphic(NULL)) {
} else {
bool numlock = vs->modifiers_state[0x45];
bool control = (vs->modifiers_state[0x1d] ||
-@@ -1852,7 +1875,8 @@ static void key_event(VncState *vs, int down, uint32_t sym)
+@@ -1996,7 +2019,8 @@ static void key_event(VncState *vs, int down, uint32_t sym)
lsym = lsym - 'A' + 'a';
}
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/ui/vnc.c b/ui/vnc.c
-index f30687884b..a345bf0d78 100644
+index 03f8f61b2e..4494cb1dd4 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
-@@ -3881,9 +3881,8 @@ void vnc_display_open(const char *id, Error **errp)
+@@ -4034,9 +4034,8 @@ void vnc_display_open(const char *id, Error **errp)
const char *path;
bool tls = false, x509 = false, x509verify = false;
tls = qemu_opt_get_bool(opts, "tls", false);
Subject: [PATCH] vnc: PVE VNC authentication
---
- crypto/tlscreds.c | 47 +++++++++++
+ crypto/tlscreds.c | 47 ++++++++++++
crypto/tlscredspriv.h | 2 +
- crypto/tlscredsx509.c | 13 +--
+ crypto/tlscredsx509.c | 13 ++--
crypto/tlssession.c | 1 +
include/crypto/tlscreds.h | 1 +
include/ui/console.h | 1 +
+ qapi-schema.json | 1 +
qemu-options.hx | 3 +
- ui/vnc-auth-vencrypt.c | 197 ++++++++++++++++++++++++++++++++++++++--------
- ui/vnc.c | 140 +++++++++++++++++++++++++++++++-
+ ui/vnc-auth-vencrypt.c | 182 ++++++++++++++++++++++++++++++++++++++--------
+ ui/vnc.c | 140 ++++++++++++++++++++++++++++++++++-
ui/vnc.h | 4 +
vl.c | 9 +++
- 11 files changed, 377 insertions(+), 41 deletions(-)
+ 12 files changed, 364 insertions(+), 40 deletions(-)
diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c
-index a8965531b6..e9ae13ce47 100644
+index 3cd41035bb..e982da3451 100644
--- a/crypto/tlscreds.c
+++ b/crypto/tlscreds.c
@@ -158,6 +158,33 @@ qcrypto_tls_creds_prop_get_verify(Object *obj,
+ NULL);
+ object_property_add_enum(obj, "endpoint",
+ "QCryptoTLSCredsEndpoint",
-+ QCryptoTLSCredsEndpoint_lookup,
++ &QCryptoTLSCredsEndpoint_lookup,
+ qcrypto_tls_creds_prop_get_endpoint,
+ qcrypto_tls_creds_prop_set_endpoint,
+ NULL);
diff --git a/include/ui/console.h b/include/ui/console.h
-index d759338816..69f010e1db 100644
+index 580dfc57ee..383e5c88bd 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
-@@ -462,6 +462,7 @@ static inline void cocoa_display_init(DisplayState *ds, int full_screen)
+@@ -466,6 +466,7 @@ static inline void cocoa_display_init(DisplayState *ds, int full_screen)
#endif
/* vnc.c */
void vnc_display_init(const char *id);
void vnc_display_open(const char *id, Error **errp);
void vnc_display_add_client(const char *id, int csock, bool skipauth);
+diff --git a/qapi-schema.json b/qapi-schema.json
+index 348b527681..d2155cb00f 100644
+--- a/qapi-schema.json
++++ b/qapi-schema.json
+@@ -56,6 +56,7 @@
+ { 'pragma': {
+ # Commands allowed to return a non-dictionary:
+ 'returns-whitelist': [
++ 'get_link_status',
+ 'human-monitor-command',
+ 'qom-get',
+ 'query-migrate-cache-size',
diff --git a/qemu-options.hx b/qemu-options.hx
-index cbcb27da9a..0b1957c034 100644
+index 7c054af8f9..07129d55bc 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
-@@ -513,6 +513,9 @@ STEXI
+@@ -583,6 +583,9 @@ STEXI
@table @option
ETEXI
"-fda/-fdb file use 'file' as floppy disk 0/1 image\n", QEMU_ARCH_ALL)
DEF("fdb", HAS_ARG, QEMU_OPTION_fdb, "", QEMU_ARCH_ALL)
diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c
-index ffaab57550..594ca737a9 100644
+index 7833631275..c42acd3714 100644
--- a/ui/vnc-auth-vencrypt.c
+++ b/ui/vnc-auth-vencrypt.c
-@@ -28,6 +28,108 @@
- #include "vnc.h"
+@@ -29,6 +29,108 @@
#include "qapi/error.h"
#include "qemu/main-loop.h"
+ #include "trace.h"
+#include "io/channel-socket.h"
+
+static int protocol_client_auth_plain(VncState *vs, uint8_t *data, size_t len)
+
+ VNC_DEBUG("AUTH PLAIN username: %s pw: %s\n", username, passwd);
+
-+ if (pve_auth_verify(clientip->u.inet.data->host, username, passwd) == 0) {
++ if (pve_auth_verify(clientip->u.inet.host, username, passwd) == 0) {
+ vnc_write_u32(vs, 0); /* Accept auth completion */
+ start_client_init(vs);
+ qapi_free_SocketAddress(clientip);
+
case VNC_AUTH_VENCRYPT_TLSVNC:
case VNC_AUTH_VENCRYPT_X509VNC:
- VNC_DEBUG("Start TLS auth VNC\n");
-@@ -88,45 +201,64 @@ static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len
- {
+ start_auth_vnc(vs);
+@@ -90,45 +203,51 @@ static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len
int auth = read_u32(data, 0);
+ trace_vnc_auth_vencrypt_subauth(vs, auth);
- if (auth != vs->subauth) {
+ if (auth != vs->subauth && auth != VNC_AUTH_VENCRYPT_PLAIN) {
- VNC_DEBUG("Rejecting auth %d\n", auth);
+ trace_vnc_auth_fail(vs, vs->auth, "Unsupported sub-auth version", "");
vnc_write_u8(vs, 0); /* Reject auth */
vnc_flush(vs);
vnc_client_error(vs);
} else {
- Error *err = NULL;
- QIOChannelTLS *tls;
-- VNC_DEBUG("Accepting auth %d, setting up TLS for handshake\n", auth);
- vnc_write_u8(vs, 1); /* Accept auth */
- vnc_flush(vs);
-
+ {
+ Error *err = NULL;
+ QIOChannelTLS *tls;
-+ VNC_DEBUG("Accepting auth %d, setting up TLS for handshake\n", auth);
+ vnc_write_u8(vs, 1); /* Accept auth */
+ vnc_flush(vs);
- vs->vd->tlsaclname,
- &err);
- if (!tls) {
-- VNC_DEBUG("Failed to setup TLS %s\n", error_get_pretty(err));
+- trace_vnc_auth_fail(vs, vs->auth, "TLS setup failed",
+- error_get_pretty(err));
- error_free(err);
- vnc_client_error(vs);
- return 0;
+ g_source_remove(vs->ioc_tag);
+ vs->ioc_tag = 0;
+ }
-
-- qio_channel_set_name(QIO_CHANNEL(tls), "vnc-server-tls");
-- VNC_DEBUG("Start TLS VeNCrypt handshake process\n");
-- object_unref(OBJECT(vs->ioc));
-- vs->ioc = QIO_CHANNEL(tls);
-- vs->tls = qio_channel_tls_get_session(tls);
+ tls = qio_channel_tls_new_server(
+ vs->ioc,
+ vs->vd->tlscreds,
+ vs->vd->tlsaclname,
+ &err);
+ if (!tls) {
-+ VNC_DEBUG("Failed to setup TLS %s\n", error_get_pretty(err));
++ trace_vnc_auth_fail(vs, vs->auth, "TLS setup failed",
++ error_get_pretty(err));
+ error_free(err);
+ vnc_client_error(vs);
+ return 0;
-+ vs->tls = qcrypto_tls_session_new(vs->vd->tlscreds,
-+ NULL,
-+ vs->vd->tlsaclname,
-+ QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
-+ &err);
-+ if (!vs->tls) {
-+ VNC_DEBUG("Failed to setup TLS %s\n",
-+ error_get_pretty(err));
-+ error_free(err);
-+ vnc_client_error(vs);
-+ return 0;
-+ }
+ }
+
+- qio_channel_set_name(QIO_CHANNEL(tls), "vnc-server-tls");
+- object_unref(OBJECT(vs->ioc));
+- vs->ioc = QIO_CHANNEL(tls);
+- trace_vnc_client_io_wrap(vs, vs->ioc, "tls");
+- vs->tls = qio_channel_tls_get_session(tls);
+ qio_channel_set_name(QIO_CHANNEL(tls), "vnc-server-tls");
++ object_unref(OBJECT(vs->ioc));
++ vs->ioc = QIO_CHANNEL(tls);
++ trace_vnc_client_io_wrap(vs, vs->ioc, "tls");
++ vs->tls = qio_channel_tls_get_session(tls);
- qio_channel_tls_handshake(tls,
- vnc_tls_handshake_done,
- vs,
- NULL);
-+ VNC_DEBUG("Start TLS VeNCrypt handshake process\n");
-+ object_unref(OBJECT(vs->ioc));
-+ vs->ioc = QIO_CHANNEL(tls);
-+ vs->tls = qio_channel_tls_get_session(tls);
-+
+ qio_channel_tls_handshake(tls,
+ vnc_tls_handshake_done,
+ vs,
}
return 0;
}
-@@ -140,10 +272,11 @@ static int protocol_client_vencrypt_init(VncState *vs, uint8_t *data, size_t len
- vnc_flush(vs);
+@@ -144,8 +263,9 @@ static int protocol_client_vencrypt_init(VncState *vs, uint8_t *data, size_t len
vnc_client_error(vs);
} else {
-- VNC_DEBUG("Sending allowed auth %d\n", vs->subauth);
-+ VNC_DEBUG("Sending allowed auths %d %d\n", vs->subauth, VNC_AUTH_VENCRYPT_PLAIN);
vnc_write_u8(vs, 0); /* Accept version */
- vnc_write_u8(vs, 1); /* Number of sub-auths */
+ vnc_write_u8(vs, 2); /* Number of sub-auths */
vnc_read_when(vs, protocol_client_vencrypt_auth, 4);
}
diff --git a/ui/vnc.c b/ui/vnc.c
-index a345bf0d78..42db7e386b 100644
+index 4494cb1dd4..1589cbe1b3 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
-@@ -56,6 +56,125 @@ static const struct timeval VNC_REFRESH_LOSSY = { 2, 0 };
+@@ -55,6 +55,125 @@ static const struct timeval VNC_REFRESH_LOSSY = { 2, 0 };
#include "vnc_keysym.h"
#include "crypto/cipher.h"
static QTAILQ_HEAD(, VncDisplay) vnc_displays =
QTAILQ_HEAD_INITIALIZER(vnc_displays);
-@@ -3356,10 +3475,16 @@ vnc_display_setup_auth(int *auth,
+@@ -3507,10 +3626,16 @@ vnc_display_setup_auth(int *auth,
if (password) {
if (is_x509) {
VNC_DEBUG("Initializing VNC server with x509 password auth\n");
}
} else if (sasl) {
-@@ -3393,6 +3518,7 @@ vnc_display_create_creds(bool x509,
+@@ -3544,6 +3669,7 @@ vnc_display_create_creds(bool x509,
bool x509verify,
const char *dir,
const char *id,
Error **errp)
{
gchar *credsid = g_strdup_printf("tlsvnc%s", id);
-@@ -3408,6 +3534,7 @@ vnc_display_create_creds(bool x509,
+@@ -3559,6 +3685,7 @@ vnc_display_create_creds(bool x509,
"endpoint", "server",
"dir", dir,
"verify-peer", x509verify ? "yes" : "no",
NULL);
} else {
creds = object_new_with_props(TYPE_QCRYPTO_TLS_CREDS_ANON,
-@@ -3415,6 +3542,7 @@ vnc_display_create_creds(bool x509,
+@@ -3566,6 +3693,7 @@ vnc_display_create_creds(bool x509,
credsid,
&err,
"endpoint", "server",
NULL);
}
-@@ -3879,12 +4007,17 @@ void vnc_display_open(const char *id, Error **errp)
+@@ -4032,12 +4160,17 @@ void vnc_display_open(const char *id, Error **errp)
}
} else {
const char *path;
} else {
path = qemu_opt_get(opts, "x509verify");
if (path) {
-@@ -3896,6 +4029,7 @@ void vnc_display_open(const char *id, Error **errp)
+@@ -4049,6 +4182,7 @@ void vnc_display_open(const char *id, Error **errp)
x509verify,
path,
vd->id,
if (!vd->tlscreds) {
goto fail;
diff --git a/ui/vnc.h b/ui/vnc.h
-index 694cf32ca9..78d622ab84 100644
+index bbda0540a7..8cc6367ed3 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
-@@ -284,6 +284,8 @@ struct VncState
+@@ -290,6 +290,8 @@ struct VncState
int auth;
int subauth; /* Used by VeNCrypt */
char challenge[VNC_AUTH_CHALLENGE_SIZE];
QCryptoTLSSession *tls; /* Borrowed pointer from channel, don't free */
#ifdef CONFIG_VNC_SASL
VncStateSASL sasl;
-@@ -577,4 +579,6 @@ int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
+@@ -595,4 +597,6 @@ int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
void vnc_zrle_clear(VncState *vs);
+
#endif /* QEMU_VNC_H */
diff --git a/vl.c b/vl.c
-index 5d888cd179..1000a4a259 100644
+index 75fde82180..255d989009 100644
--- a/vl.c
+++ b/vl.c
-@@ -2947,6 +2947,7 @@ static int qemu_read_default_config_file(void)
+@@ -3096,6 +3096,7 @@ static void register_global_properties(MachineState *ms)
int main(int argc, char **argv, char **envp)
{
int i;
int snapshot, linux_boot;
const char *initrd_filename;
const char *kernel_filename, *kernel_cmdline;
-@@ -3778,6 +3779,14 @@ int main(int argc, char **argv, char **envp)
+@@ -3922,6 +3923,14 @@ int main(int argc, char **argv, char **envp)
exit(1);
}
break;
--- /dev/null
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexandre Derumier <aderumier@odiso.com>
+Date: Tue, 26 Jul 2016 16:51:00 +0200
+Subject: [PATCH] block: rbd: disable rbd_cache_writethrough_until_flush with
+ cache=unsafe
+
+Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
+---
+ block/rbd.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/block/rbd.c b/block/rbd.c
+index a76a5e8755..a33738a254 100644
+--- a/block/rbd.c
++++ b/block/rbd.c
+@@ -642,6 +642,10 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
+ rados_conf_set(s->cluster, "rbd_cache", "true");
+ }
+
++ if (flags & BDRV_O_NO_FLUSH) {
++ rados_conf_set(s->cluster, "rbd_cache_writethrough_until_flush", "false");
++ }
++
+ r = rados_connect(s->cluster);
+ if (r < 0) {
+ error_setg_errno(errp, -r, "error connecting");
+--
+2.11.0
+
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Thomas Lamprecht <t.lamprecht@proxmox.com>
-Date: Wed, 6 Apr 2016 16:45:15 +0200
-Subject: [PATCH] migrate: fix possible unitialised return value
-
----
- migration/savevm.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/migration/savevm.c b/migration/savevm.c
-index feb0dc6834..d2615f4620 100644
---- a/migration/savevm.c
-+++ b/migration/savevm.c
-@@ -1111,7 +1111,7 @@ int qemu_savevm_state_complete_precopy(QEMUFile *f, bool iterable_only)
- QJSON *vmdesc;
- int vmdesc_len;
- SaveStateEntry *se;
-- int ret;
-+ int ret = -1;
- bool in_postcopy = migration_in_postcopy(migrate_get_current());
-
- trace_savevm_state_complete_precopy();
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Alexandre Derumier <aderumier@odiso.com>
-Date: Tue, 26 Jul 2016 16:51:00 +0200
-Subject: [PATCH] block: rbd: disable rbd_cache_writethrough_until_flush with
- cache=unsafe
-
-Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
----
- block/rbd.c | 4 ++++
- 1 file changed, 4 insertions(+)
-
-diff --git a/block/rbd.c b/block/rbd.c
-index 2354ffcc64..b7700648ff 100644
---- a/block/rbd.c
-+++ b/block/rbd.c
-@@ -623,6 +623,10 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
- rados_conf_set(s->cluster, "rbd_cache", "true");
- }
-
-+ if (flags & BDRV_O_NO_FLUSH) {
-+ rados_conf_set(s->cluster, "rbd_cache_writethrough_until_flush", "false");
-+ }
-+
- r = rados_connect(s->cluster);
- if (r < 0) {
- error_setg_errno(errp, -r, "error connecting");
---
-2.11.0
-
--- /dev/null
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexandre Derumier <aderumier@odiso.com>
+Date: Tue, 13 Sep 2016 01:57:56 +0200
+Subject: [PATCH] block: snapshot: qmp_snapshot_drive: add aiocontext
+
+Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
+---
+ savevm-async.c | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+diff --git a/savevm-async.c b/savevm-async.c
+index 96523c88ae..06dcd29fc7 100644
+--- a/savevm-async.c
++++ b/savevm-async.c
+@@ -377,6 +377,7 @@ void qmp_snapshot_drive(const char *device, const char *name, Error **errp)
+ BlockBackend *blk;
+ BlockDriverState *bs;
+ QEMUSnapshotInfo sn1, *sn = &sn1;
++ AioContext *aio_context;
+ int ret;
+ #ifdef _WIN32
+ struct _timeb tb;
+@@ -403,20 +404,23 @@ void qmp_snapshot_drive(const char *device, const char *name, Error **errp)
+ return;
+ }
+
++ aio_context = bdrv_get_aio_context(bs);
++ aio_context_acquire(aio_context);
++
+ if (bdrv_is_read_only(bs)) {
+ error_setg(errp, "Node '%s' is read only", device);
+- return;
++ goto out;
+ }
+
+ if (!bdrv_can_snapshot(bs)) {
+ error_setg(errp, QERR_UNSUPPORTED);
+- return;
++ goto out;
+ }
+
+ if (bdrv_snapshot_find(bs, sn, name) >= 0) {
+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+ "snapshot '%s' already exists", name);
+- return;
++ goto out;
+ }
+
+ sn = &sn1;
+@@ -441,8 +445,11 @@ void qmp_snapshot_drive(const char *device, const char *name, Error **errp)
+ if (ret < 0) {
+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+ "Error while creating snapshot on '%s'\n", device);
+- return;
++ goto out;
+ }
++
++out:
++ aio_context_release(aio_context);
+ }
+
+ void qmp_delete_drive_snapshot(const char *device, const char *name,
+--
+2.11.0
+
--- /dev/null
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alexandre Derumier <aderumier@odiso.com>
+Date: Mon, 7 Nov 2016 11:47:50 +0100
+Subject: [PATCH] block: snapshot: qmp_delete_drive_snapshot : add aiocontext
+
+this fix snapshot delete of qcow2 with iothread enabled
+
+Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
+---
+ savevm-async.c | 13 ++++++++++---
+ 1 file changed, 10 insertions(+), 3 deletions(-)
+
+diff --git a/savevm-async.c b/savevm-async.c
+index 06dcd29fc7..e6f86ef865 100644
+--- a/savevm-async.c
++++ b/savevm-async.c
+@@ -459,6 +459,7 @@ void qmp_delete_drive_snapshot(const char *device, const char *name,
+ BlockDriverState *bs;
+ QEMUSnapshotInfo sn1, *sn = &sn1;
+ Error *local_err = NULL;
++ AioContext *aio_context;
+
+ int ret;
+
+@@ -475,22 +476,28 @@ void qmp_delete_drive_snapshot(const char *device, const char *name,
+ return;
+ }
+
++ aio_context = bdrv_get_aio_context(bs);
++ aio_context_acquire(aio_context);
++
+ if (!bdrv_can_snapshot(bs)) {
+ error_setg(errp, QERR_UNSUPPORTED);
+- return;
++ goto out;
+ }
+
+ if (bdrv_snapshot_find(bs, sn, name) < 0) {
+ /* return success if snapshot does not exists */
+- return;
++ goto out;
+ }
+
+ ret = bdrv_snapshot_delete(bs, NULL, name, &local_err);
+ if (ret < 0) {
+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+ "Error while deleting snapshot on '%s'\n", device);
+- return;
++ goto out;
+ }
++
++out:
++ aio_context_release(aio_context);
+ }
+
+ static ssize_t loadstate_get_buffer(void *opaque, uint8_t *buf, int64_t pos,
+--
+2.11.0
+
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Alexandre Derumier <aderumier@odiso.com>
-Date: Tue, 13 Sep 2016 01:57:56 +0200
-Subject: [PATCH] block: snapshot: qmp_snapshot_drive: add aiocontext
-
-Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
----
- savevm-async.c | 15 +++++++++++----
- 1 file changed, 11 insertions(+), 4 deletions(-)
-
-diff --git a/savevm-async.c b/savevm-async.c
-index 97d51d3edd..ece193a065 100644
---- a/savevm-async.c
-+++ b/savevm-async.c
-@@ -377,6 +377,7 @@ void qmp_snapshot_drive(const char *device, const char *name, Error **errp)
- BlockBackend *blk;
- BlockDriverState *bs;
- QEMUSnapshotInfo sn1, *sn = &sn1;
-+ AioContext *aio_context;
- int ret;
- #ifdef _WIN32
- struct _timeb tb;
-@@ -403,20 +404,23 @@ void qmp_snapshot_drive(const char *device, const char *name, Error **errp)
- return;
- }
-
-+ aio_context = bdrv_get_aio_context(bs);
-+ aio_context_acquire(aio_context);
-+
- if (bdrv_is_read_only(bs)) {
- error_setg(errp, "Node '%s' is read only", device);
-- return;
-+ goto out;
- }
-
- if (!bdrv_can_snapshot(bs)) {
- error_setg(errp, QERR_UNSUPPORTED);
-- return;
-+ goto out;
- }
-
- if (bdrv_snapshot_find(bs, sn, name) >= 0) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR,
- "snapshot '%s' already exists", name);
-- return;
-+ goto out;
- }
-
- sn = &sn1;
-@@ -441,8 +445,11 @@ void qmp_snapshot_drive(const char *device, const char *name, Error **errp)
- if (ret < 0) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR,
- "Error while creating snapshot on '%s'\n", device);
-- return;
-+ goto out;
- }
-+
-+out:
-+ aio_context_release(aio_context);
- }
-
- void qmp_delete_drive_snapshot(const char *device, const char *name,
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Alexandre Derumier <aderumier@odiso.com>
-Date: Mon, 7 Nov 2016 11:47:50 +0100
-Subject: [PATCH] block: snapshot: qmp_delete_drive_snapshot : add aiocontext
-
-this fix snapshot delete of qcow2 with iothread enabled
-
-Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
----
- savevm-async.c | 13 ++++++++++---
- 1 file changed, 10 insertions(+), 3 deletions(-)
-
-diff --git a/savevm-async.c b/savevm-async.c
-index ece193a065..716dd2d7a1 100644
---- a/savevm-async.c
-+++ b/savevm-async.c
-@@ -459,6 +459,7 @@ void qmp_delete_drive_snapshot(const char *device, const char *name,
- BlockDriverState *bs;
- QEMUSnapshotInfo sn1, *sn = &sn1;
- Error *local_err = NULL;
-+ AioContext *aio_context;
-
- int ret;
-
-@@ -475,22 +476,28 @@ void qmp_delete_drive_snapshot(const char *device, const char *name,
- return;
- }
-
-+ aio_context = bdrv_get_aio_context(bs);
-+ aio_context_acquire(aio_context);
-+
- if (!bdrv_can_snapshot(bs)) {
- error_setg(errp, QERR_UNSUPPORTED);
-- return;
-+ goto out;
- }
-
- if (bdrv_snapshot_find(bs, sn, name) < 0) {
- /* return success if snapshot does not exists */
-- return;
-+ goto out;
- }
-
- ret = bdrv_snapshot_delete(bs, NULL, name, &local_err);
- if (ret < 0) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR,
- "Error while deleting snapshot on '%s'\n", device);
-- return;
-+ goto out;
- }
-+
-+out:
-+ aio_context_release(aio_context);
- }
-
- static ssize_t loadstate_get_buffer(void *opaque, uint8_t *buf, int64_t pos,
---
-2.11.0
-
--- /dev/null
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Mon, 24 Oct 2016 09:32:36 +0200
+Subject: [PATCH] glusterfs: no default logfile if daemonized
+
+---
+ block/gluster.c | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+diff --git a/block/gluster.c b/block/gluster.c
+index 0f4265a3a4..8fab26481c 100644
+--- a/block/gluster.c
++++ b/block/gluster.c
+@@ -32,7 +32,7 @@
+ #define GLUSTER_DEBUG_DEFAULT 4
+ #define GLUSTER_DEBUG_MAX 9
+ #define GLUSTER_OPT_LOGFILE "logfile"
+-#define GLUSTER_LOGFILE_DEFAULT "-" /* handled in libgfapi as /dev/stderr */
++#define GLUSTER_LOGFILE_DEFAULT NULL
+
+ #define GERR_INDEX_HINT "hint: check in 'server' array index '%d'\n"
+
+@@ -396,6 +396,7 @@ static struct glfs *qemu_gluster_glfs_init(BlockdevOptionsGluster *gconf,
+ int old_errno;
+ SocketAddressList *server;
+ unsigned long long port;
++ const char *logfile;
+
+ glfs = glfs_find_preopened(gconf->volume);
+ if (glfs) {
+@@ -438,9 +439,15 @@ static struct glfs *qemu_gluster_glfs_init(BlockdevOptionsGluster *gconf,
+ }
+ }
+
+- ret = glfs_set_logging(glfs, gconf->logfile, gconf->debug);
+- if (ret < 0) {
+- goto out;
++ logfile = gconf->logfile;
++ if (!logfile && !is_daemonized()) {
++ logfile = "-";
++ }
++ if (logfile) {
++ ret = glfs_set_logging(glfs, logfile, gconf->debug);
++ if (ret < 0) {
++ goto out;
++ }
+ }
+
+ ret = glfs_init(glfs);
+--
+2.11.0
+
--- /dev/null
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Wed, 30 Nov 2016 10:27:47 +0100
+Subject: [PATCH] glusterfs: allow partial reads
+
+This should deal with qemu bug #1644754 until upstream
+decides which way to go. The general direction seems to be
+away from sector based block APIs and with that in mind, and
+when comparing to other network block backends (eg. nfs)
+treating partial reads as errors doesn't seem to make much
+sense.
+---
+ block/gluster.c | 10 +++++++++-
+ 1 file changed, 9 insertions(+), 1 deletion(-)
+
+diff --git a/block/gluster.c b/block/gluster.c
+index 8fab26481c..24296a39b3 100644
+--- a/block/gluster.c
++++ b/block/gluster.c
+@@ -41,6 +41,7 @@ typedef struct GlusterAIOCB {
+ int ret;
+ Coroutine *coroutine;
+ AioContext *aio_context;
++ bool is_write;
+ } GlusterAIOCB;
+
+ typedef struct BDRVGlusterState {
+@@ -709,8 +710,10 @@ static void gluster_finish_aiocb(struct glfs_fd *fd, ssize_t ret, void *arg)
+ acb->ret = 0; /* Success */
+ } else if (ret < 0) {
+ acb->ret = -errno; /* Read/Write failed */
++ } else if (acb->is_write) {
++ acb->ret = -EIO; /* Partial write - fail it */
+ } else {
+- acb->ret = -EIO; /* Partial read/write - fail it */
++ acb->ret = 0; /* Success */
+ }
+
+ aio_co_schedule(acb->aio_context, acb->coroutine);
+@@ -958,6 +961,7 @@ static coroutine_fn int qemu_gluster_co_pwrite_zeroes(BlockDriverState *bs,
+ acb.ret = 0;
+ acb.coroutine = qemu_coroutine_self();
+ acb.aio_context = bdrv_get_aio_context(bs);
++ acb.is_write = true;
+
+ ret = glfs_zerofill_async(s->fd, offset, size, gluster_finish_aiocb, &acb);
+ if (ret < 0) {
+@@ -1083,9 +1087,11 @@ static coroutine_fn int qemu_gluster_co_rw(BlockDriverState *bs,
+ acb.aio_context = bdrv_get_aio_context(bs);
+
+ if (write) {
++ acb.is_write = true;
+ ret = glfs_pwritev_async(s->fd, qiov->iov, qiov->niov, offset, 0,
+ gluster_finish_aiocb, &acb);
+ } else {
++ acb.is_write = false;
+ ret = glfs_preadv_async(s->fd, qiov->iov, qiov->niov, offset, 0,
+ gluster_finish_aiocb, &acb);
+ }
+@@ -1158,6 +1164,7 @@ static coroutine_fn int qemu_gluster_co_flush_to_disk(BlockDriverState *bs)
+ acb.ret = 0;
+ acb.coroutine = qemu_coroutine_self();
+ acb.aio_context = bdrv_get_aio_context(bs);
++ acb.is_write = true;
+
+ ret = glfs_fsync_async(s->fd, gluster_finish_aiocb, &acb);
+ if (ret < 0) {
+@@ -1204,6 +1211,7 @@ static coroutine_fn int qemu_gluster_co_pdiscard(BlockDriverState *bs,
+ acb.ret = 0;
+ acb.coroutine = qemu_coroutine_self();
+ acb.aio_context = bdrv_get_aio_context(bs);
++ acb.is_write = true;
+
+ ret = glfs_discard_async(s->fd, offset, size, gluster_finish_aiocb, &acb);
+ if (ret < 0) {
+--
+2.11.0
+
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Mon, 24 Oct 2016 09:32:36 +0200
-Subject: [PATCH] glusterfs: no default logfile if daemonized
-
----
- block/gluster.c | 15 +++++++++++----
- 1 file changed, 11 insertions(+), 4 deletions(-)
-
-diff --git a/block/gluster.c b/block/gluster.c
-index cf29b5f9a4..bc44c50db0 100644
---- a/block/gluster.c
-+++ b/block/gluster.c
-@@ -33,7 +33,7 @@
- #define GLUSTER_DEBUG_DEFAULT 4
- #define GLUSTER_DEBUG_MAX 9
- #define GLUSTER_OPT_LOGFILE "logfile"
--#define GLUSTER_LOGFILE_DEFAULT "-" /* handled in libgfapi as /dev/stderr */
-+#define GLUSTER_LOGFILE_DEFAULT NULL
-
- #define GERR_INDEX_HINT "hint: check in 'server' array index '%d'\n"
-
-@@ -398,6 +398,7 @@ static struct glfs *qemu_gluster_glfs_init(BlockdevOptionsGluster *gconf,
- int old_errno;
- SocketAddressFlatList *server;
- unsigned long long port;
-+ const char *logfile;
-
- glfs = glfs_find_preopened(gconf->volume);
- if (glfs) {
-@@ -440,9 +441,15 @@ static struct glfs *qemu_gluster_glfs_init(BlockdevOptionsGluster *gconf,
- }
- }
-
-- ret = glfs_set_logging(glfs, gconf->logfile, gconf->debug);
-- if (ret < 0) {
-- goto out;
-+ logfile = gconf->logfile;
-+ if (!logfile && !is_daemonized()) {
-+ logfile = "-";
-+ }
-+ if (logfile) {
-+ ret = glfs_set_logging(glfs, logfile, gconf->debug);
-+ if (ret < 0) {
-+ goto out;
-+ }
- }
-
- ret = glfs_init(glfs);
---
-2.11.0
-
--- /dev/null
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Thu, 17 Mar 2016 11:33:37 +0100
+Subject: [PATCH] block: add the zeroinit block driver filter
+
+---
+ block/Makefile.objs | 1 +
+ block/zeroinit.c | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 203 insertions(+)
+ create mode 100644 block/zeroinit.c
+
+diff --git a/block/Makefile.objs b/block/Makefile.objs
+index 6eaf78a046..823e60cda6 100644
+--- a/block/Makefile.objs
++++ b/block/Makefile.objs
+@@ -4,6 +4,7 @@ block-obj-y += qed.o qed-l2-cache.o qed-table.o qed-cluster.o
+ block-obj-y += qed-check.o
+ block-obj-y += vhdx.o vhdx-endian.o vhdx-log.o
+ block-obj-y += quorum.o
++block-obj-y += zeroinit.o
+ block-obj-y += parallels.o blkdebug.o blkverify.o blkreplay.o
+ block-obj-y += block-backend.o snapshot.o qapi.o
+ block-obj-$(CONFIG_WIN32) += file-win32.o win32-aio.o
+diff --git a/block/zeroinit.c b/block/zeroinit.c
+new file mode 100644
+index 0000000000..37f588f75c
+--- /dev/null
++++ b/block/zeroinit.c
+@@ -0,0 +1,202 @@
++/*
++ * Filter to fake a zero-initialized block device.
++ *
++ * Copyright (c) 2016 Wolfgang Bumiller <w.bumiller@proxmox.com>
++ * Copyright (c) 2016 Proxmox Server Solutions GmbH
++ *
++ * This work is licensed under the terms of the GNU GPL, version 2 or later.
++ * See the COPYING file in the top-level directory.
++ */
++
++#include "qemu/osdep.h"
++#include "qapi/error.h"
++#include "block/block_int.h"
++#include "qapi/qmp/qdict.h"
++#include "qapi/qmp/qstring.h"
++#include "qemu/cutils.h"
++
++typedef struct {
++ bool has_zero_init;
++ int64_t extents;
++} BDRVZeroinitState;
++
++/* Valid blkverify filenames look like blkverify:path/to/raw_image:path/to/image */
++static void zeroinit_parse_filename(const char *filename, QDict *options,
++ Error **errp)
++{
++ QString *raw_path;
++
++ /* Parse the blkverify: prefix */
++ if (!strstart(filename, "zeroinit:", &filename)) {
++ /* There was no prefix; therefore, all options have to be already
++ present in the QDict (except for the filename) */
++ return;
++ }
++
++ raw_path = qstring_from_str(filename);
++ qdict_put(options, "x-next", raw_path);
++}
++
++static QemuOptsList runtime_opts = {
++ .name = "zeroinit",
++ .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
++ .desc = {
++ {
++ .name = "x-next",
++ .type = QEMU_OPT_STRING,
++ .help = "[internal use only, will be removed]",
++ },
++ {
++ .name = "x-zeroinit",
++ .type = QEMU_OPT_BOOL,
++ .help = "set has_initialized_zero flag",
++ },
++ { /* end of list */ }
++ },
++};
++
++static int zeroinit_open(BlockDriverState *bs, QDict *options, int flags,
++ Error **errp)
++{
++ BDRVZeroinitState *s = bs->opaque;
++ QemuOpts *opts;
++ Error *local_err = NULL;
++ int ret;
++
++ s->extents = 0;
++
++ opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
++ qemu_opts_absorb_qdict(opts, options, &local_err);
++ if (local_err) {
++ error_propagate(errp, local_err);
++ ret = -EINVAL;
++ goto fail;
++ }
++
++ /* Open the raw file */
++ bs->file = bdrv_open_child(qemu_opt_get(opts, "x-next"), options, "next",
++ bs, &child_file, false, &local_err);
++ if (local_err) {
++ ret = -EINVAL;
++ error_propagate(errp, local_err);
++ goto fail;
++ }
++
++ /* set the options */
++ s->has_zero_init = qemu_opt_get_bool(opts, "x-zeroinit", true);
++
++ ret = 0;
++fail:
++ if (ret < 0) {
++ bdrv_unref_child(bs, bs->file);
++ }
++ qemu_opts_del(opts);
++ return ret;
++}
++
++static void zeroinit_close(BlockDriverState *bs)
++{
++ BDRVZeroinitState *s = bs->opaque;
++ (void)s;
++}
++
++static int64_t zeroinit_getlength(BlockDriverState *bs)
++{
++ return bdrv_getlength(bs->file->bs);
++}
++
++static int coroutine_fn zeroinit_co_preadv(BlockDriverState *bs,
++ uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags)
++{
++ return bdrv_co_preadv(bs->file, offset, bytes, qiov, flags);
++}
++
++static int coroutine_fn zeroinit_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
++ int count, BdrvRequestFlags flags)
++{
++ BDRVZeroinitState *s = bs->opaque;
++ if (offset >= s->extents)
++ return 0;
++ return bdrv_pwrite_zeroes(bs->file, offset, count, flags);
++}
++
++static int coroutine_fn zeroinit_co_pwritev(BlockDriverState *bs,
++ uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags)
++{
++ BDRVZeroinitState *s = bs->opaque;
++ int64_t extents = offset + bytes;
++ if (extents > s->extents)
++ s->extents = extents;
++ return bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags);
++}
++
++static bool zeroinit_recurse_is_first_non_filter(BlockDriverState *bs,
++ BlockDriverState *candidate)
++{
++ return bdrv_recurse_is_first_non_filter(bs->file->bs, candidate);
++}
++
++static coroutine_fn int zeroinit_co_flush(BlockDriverState *bs)
++{
++ return bdrv_co_flush(bs->file->bs);
++}
++
++static int zeroinit_has_zero_init(BlockDriverState *bs)
++{
++ BDRVZeroinitState *s = bs->opaque;
++ return s->has_zero_init;
++}
++
++static int coroutine_fn zeroinit_co_pdiscard(BlockDriverState *bs,
++ int64_t offset, int count)
++{
++ return bdrv_co_pdiscard(bs->file->bs, offset, count);
++}
++
++static int zeroinit_truncate(BlockDriverState *bs, int64_t offset,
++ PreallocMode prealloc, Error **errp)
++{
++ return bdrv_truncate(bs->file, offset, prealloc, errp);
++}
++
++static int zeroinit_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
++{
++ return bdrv_get_info(bs->file->bs, bdi);
++}
++
++static BlockDriver bdrv_zeroinit = {
++ .format_name = "zeroinit",
++ .protocol_name = "zeroinit",
++ .instance_size = sizeof(BDRVZeroinitState),
++
++ .bdrv_parse_filename = zeroinit_parse_filename,
++ .bdrv_file_open = zeroinit_open,
++ .bdrv_close = zeroinit_close,
++ .bdrv_getlength = zeroinit_getlength,
++ .bdrv_child_perm = bdrv_filter_default_perms,
++ .bdrv_co_flush_to_disk = zeroinit_co_flush,
++
++ .bdrv_co_pwrite_zeroes = zeroinit_co_pwrite_zeroes,
++ .bdrv_co_pwritev = zeroinit_co_pwritev,
++ .bdrv_co_preadv = zeroinit_co_preadv,
++ .bdrv_co_flush = zeroinit_co_flush,
++
++ .is_filter = true,
++ .bdrv_recurse_is_first_non_filter = zeroinit_recurse_is_first_non_filter,
++
++ .bdrv_has_zero_init = zeroinit_has_zero_init,
++
++ .bdrv_co_get_block_status = bdrv_co_get_block_status_from_file,
++
++ .bdrv_co_pdiscard = zeroinit_co_pdiscard,
++
++ .bdrv_truncate = zeroinit_truncate,
++ .bdrv_get_info = zeroinit_get_info,
++};
++
++static void bdrv_zeroinit_init(void)
++{
++ bdrv_register(&bdrv_zeroinit);
++}
++
++block_init(bdrv_zeroinit_init);
+--
+2.11.0
+
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Wed, 30 Nov 2016 10:27:47 +0100
-Subject: [PATCH] glusterfs: allow partial reads
-
-This should deal with qemu bug #1644754 until upstream
-decides which way to go. The general direction seems to be
-away from sector based block APIs and with that in mind, and
-when comparing to other network block backends (eg. nfs)
-treating partial reads as errors doesn't seem to make much
-sense.
----
- block/gluster.c | 10 +++++++++-
- 1 file changed, 9 insertions(+), 1 deletion(-)
-
-diff --git a/block/gluster.c b/block/gluster.c
-index bc44c50db0..4fdf68f1fc 100644
---- a/block/gluster.c
-+++ b/block/gluster.c
-@@ -42,6 +42,7 @@ typedef struct GlusterAIOCB {
- int ret;
- Coroutine *coroutine;
- AioContext *aio_context;
-+ bool is_write;
- } GlusterAIOCB;
-
- typedef struct BDRVGlusterState {
-@@ -713,8 +714,10 @@ static void gluster_finish_aiocb(struct glfs_fd *fd, ssize_t ret, void *arg)
- acb->ret = 0; /* Success */
- } else if (ret < 0) {
- acb->ret = -errno; /* Read/Write failed */
-+ } else if (acb->is_write) {
-+ acb->ret = -EIO; /* Partial write - fail it */
- } else {
-- acb->ret = -EIO; /* Partial read/write - fail it */
-+ acb->ret = 0; /* Success */
- }
-
- aio_co_schedule(acb->aio_context, acb->coroutine);
-@@ -962,6 +965,7 @@ static coroutine_fn int qemu_gluster_co_pwrite_zeroes(BlockDriverState *bs,
- acb.ret = 0;
- acb.coroutine = qemu_coroutine_self();
- acb.aio_context = bdrv_get_aio_context(bs);
-+ acb.is_write = true;
-
- ret = glfs_zerofill_async(s->fd, offset, size, gluster_finish_aiocb, &acb);
- if (ret < 0) {
-@@ -1084,9 +1088,11 @@ static coroutine_fn int qemu_gluster_co_rw(BlockDriverState *bs,
- acb.aio_context = bdrv_get_aio_context(bs);
-
- if (write) {
-+ acb.is_write = true;
- ret = glfs_pwritev_async(s->fd, qiov->iov, qiov->niov, offset, 0,
- gluster_finish_aiocb, &acb);
- } else {
-+ acb.is_write = false;
- ret = glfs_preadv_async(s->fd, qiov->iov, qiov->niov, offset, 0,
- gluster_finish_aiocb, &acb);
- }
-@@ -1150,6 +1156,7 @@ static coroutine_fn int qemu_gluster_co_flush_to_disk(BlockDriverState *bs)
- acb.ret = 0;
- acb.coroutine = qemu_coroutine_self();
- acb.aio_context = bdrv_get_aio_context(bs);
-+ acb.is_write = true;
-
- ret = glfs_fsync_async(s->fd, gluster_finish_aiocb, &acb);
- if (ret < 0) {
-@@ -1196,6 +1203,7 @@ static coroutine_fn int qemu_gluster_co_pdiscard(BlockDriverState *bs,
- acb.ret = 0;
- acb.coroutine = qemu_coroutine_self();
- acb.aio_context = bdrv_get_aio_context(bs);
-+ acb.is_write = true;
-
- ret = glfs_discard_async(s->fd, offset, size, gluster_finish_aiocb, &acb);
- if (ret < 0) {
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Thu, 17 Mar 2016 11:33:37 +0100
-Subject: [PATCH] block: add the zeroinit block driver filter
-
----
- block/Makefile.objs | 1 +
- block/zeroinit.c | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++
- 2 files changed, 221 insertions(+)
- create mode 100644 block/zeroinit.c
-
-diff --git a/block/Makefile.objs b/block/Makefile.objs
-index de96f8ee80..8cdac08db5 100644
---- a/block/Makefile.objs
-+++ b/block/Makefile.objs
-@@ -4,6 +4,7 @@ block-obj-y += qed.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o
- block-obj-y += qed-check.o
- block-obj-y += vhdx.o vhdx-endian.o vhdx-log.o
- block-obj-y += quorum.o
-+block-obj-y += zeroinit.o
- block-obj-y += parallels.o blkdebug.o blkverify.o blkreplay.o
- block-obj-y += block-backend.o snapshot.o qapi.o
- block-obj-$(CONFIG_WIN32) += file-win32.o win32-aio.o
-diff --git a/block/zeroinit.c b/block/zeroinit.c
-new file mode 100644
-index 0000000000..305185512e
---- /dev/null
-+++ b/block/zeroinit.c
-@@ -0,0 +1,220 @@
-+/*
-+ * Filter to fake a zero-initialized block device.
-+ *
-+ * Copyright (c) 2016 Wolfgang Bumiller <w.bumiller@proxmox.com>
-+ * Copyright (c) 2016 Proxmox Server Solutions GmbH
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ */
-+
-+#include "qemu/osdep.h"
-+#include "qapi/error.h"
-+#include "block/block_int.h"
-+#include "qapi/qmp/qdict.h"
-+#include "qapi/qmp/qstring.h"
-+#include "qemu/cutils.h"
-+
-+typedef struct {
-+ bool has_zero_init;
-+ int64_t extents;
-+} BDRVZeroinitState;
-+
-+/* Valid blkverify filenames look like blkverify:path/to/raw_image:path/to/image */
-+static void zeroinit_parse_filename(const char *filename, QDict *options,
-+ Error **errp)
-+{
-+ QString *raw_path;
-+
-+ /* Parse the blkverify: prefix */
-+ if (!strstart(filename, "zeroinit:", &filename)) {
-+ /* There was no prefix; therefore, all options have to be already
-+ present in the QDict (except for the filename) */
-+ return;
-+ }
-+
-+ raw_path = qstring_from_str(filename);
-+ qdict_put(options, "x-next", raw_path);
-+}
-+
-+static QemuOptsList runtime_opts = {
-+ .name = "zeroinit",
-+ .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
-+ .desc = {
-+ {
-+ .name = "x-next",
-+ .type = QEMU_OPT_STRING,
-+ .help = "[internal use only, will be removed]",
-+ },
-+ {
-+ .name = "x-zeroinit",
-+ .type = QEMU_OPT_BOOL,
-+ .help = "set has_initialized_zero flag",
-+ },
-+ { /* end of list */ }
-+ },
-+};
-+
-+static int zeroinit_open(BlockDriverState *bs, QDict *options, int flags,
-+ Error **errp)
-+{
-+ BDRVZeroinitState *s = bs->opaque;
-+ QemuOpts *opts;
-+ Error *local_err = NULL;
-+ int ret;
-+
-+ s->extents = 0;
-+
-+ opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
-+ qemu_opts_absorb_qdict(opts, options, &local_err);
-+ if (local_err) {
-+ error_propagate(errp, local_err);
-+ ret = -EINVAL;
-+ goto fail;
-+ }
-+
-+ /* Open the raw file */
-+ bs->file = bdrv_open_child(qemu_opt_get(opts, "x-next"), options, "next",
-+ bs, &child_file, false, &local_err);
-+ if (local_err) {
-+ ret = -EINVAL;
-+ error_propagate(errp, local_err);
-+ goto fail;
-+ }
-+
-+ /* set the options */
-+ s->has_zero_init = qemu_opt_get_bool(opts, "x-zeroinit", true);
-+
-+ ret = 0;
-+fail:
-+ if (ret < 0) {
-+ bdrv_unref_child(bs, bs->file);
-+ }
-+ qemu_opts_del(opts);
-+ return ret;
-+}
-+
-+static void zeroinit_close(BlockDriverState *bs)
-+{
-+ BDRVZeroinitState *s = bs->opaque;
-+ (void)s;
-+}
-+
-+static int64_t zeroinit_getlength(BlockDriverState *bs)
-+{
-+ return bdrv_getlength(bs->file->bs);
-+}
-+
-+static BlockAIOCB *zeroinit_aio_readv(BlockDriverState *bs,
-+ int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
-+ BlockCompletionFunc *cb, void *opaque)
-+{
-+ return bdrv_aio_readv(bs->file, sector_num, qiov, nb_sectors,
-+ cb, opaque);
-+}
-+
-+static int coroutine_fn zeroinit_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
-+ int count, BdrvRequestFlags flags)
-+{
-+ BDRVZeroinitState *s = bs->opaque;
-+ if (offset >= s->extents)
-+ return 0;
-+ return bdrv_pwrite_zeroes(bs->file, offset, count, flags);
-+}
-+
-+static BlockAIOCB *zeroinit_aio_writev(BlockDriverState *bs,
-+ int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
-+ BlockCompletionFunc *cb, void *opaque)
-+{
-+ BDRVZeroinitState *s = bs->opaque;
-+ int64_t extents = (sector_num << BDRV_SECTOR_BITS) + ((nb_sectors + 1) << BDRV_SECTOR_BITS);
-+ if (extents > s->extents)
-+ s->extents = extents;
-+ return bdrv_aio_writev(bs->file, sector_num, qiov, nb_sectors,
-+ cb, opaque);
-+}
-+
-+static BlockAIOCB *zeroinit_aio_flush(BlockDriverState *bs,
-+ BlockCompletionFunc *cb,
-+ void *opaque)
-+{
-+ return bdrv_aio_flush(bs->file->bs, cb, opaque);
-+}
-+
-+static bool zeroinit_recurse_is_first_non_filter(BlockDriverState *bs,
-+ BlockDriverState *candidate)
-+{
-+ return bdrv_recurse_is_first_non_filter(bs->file->bs, candidate);
-+}
-+
-+static coroutine_fn int zeroinit_co_flush(BlockDriverState *bs)
-+{
-+ return bdrv_co_flush(bs->file->bs);
-+}
-+
-+static int zeroinit_has_zero_init(BlockDriverState *bs)
-+{
-+ BDRVZeroinitState *s = bs->opaque;
-+ return s->has_zero_init;
-+}
-+
-+static int64_t coroutine_fn zeroinit_co_get_block_status(BlockDriverState *bs,
-+ int64_t sector_num,
-+ int nb_sectors, int *pnum,
-+ BlockDriverState **file)
-+{
-+ return bdrv_get_block_status(bs->file->bs, sector_num, nb_sectors, pnum, file);
-+}
-+
-+static int coroutine_fn zeroinit_co_pdiscard(BlockDriverState *bs,
-+ int64_t offset, int count)
-+{
-+ return bdrv_co_pdiscard(bs->file->bs, offset, count);
-+}
-+
-+static int zeroinit_truncate(BlockDriverState *bs, int64_t offset)
-+{
-+ return bdrv_truncate(bs->file, offset, NULL);
-+}
-+
-+static int zeroinit_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
-+{
-+ return bdrv_get_info(bs->file->bs, bdi);
-+}
-+
-+static BlockDriver bdrv_zeroinit = {
-+ .format_name = "zeroinit",
-+ .protocol_name = "zeroinit",
-+ .instance_size = sizeof(BDRVZeroinitState),
-+
-+ .bdrv_parse_filename = zeroinit_parse_filename,
-+ .bdrv_file_open = zeroinit_open,
-+ .bdrv_close = zeroinit_close,
-+ .bdrv_getlength = zeroinit_getlength,
-+ .bdrv_child_perm = bdrv_filter_default_perms,
-+ .bdrv_co_flush_to_disk = zeroinit_co_flush,
-+
-+ .bdrv_co_pwrite_zeroes = zeroinit_co_pwrite_zeroes,
-+ .bdrv_aio_writev = zeroinit_aio_writev,
-+ .bdrv_aio_readv = zeroinit_aio_readv,
-+ .bdrv_aio_flush = zeroinit_aio_flush,
-+
-+ .is_filter = true,
-+ .bdrv_recurse_is_first_non_filter = zeroinit_recurse_is_first_non_filter,
-+
-+ .bdrv_has_zero_init = zeroinit_has_zero_init,
-+
-+ .bdrv_co_get_block_status = zeroinit_co_get_block_status,
-+
-+ .bdrv_co_pdiscard = zeroinit_co_pdiscard,
-+
-+ .bdrv_truncate = zeroinit_truncate,
-+ .bdrv_get_info = zeroinit_get_info,
-+};
-+
-+static void bdrv_zeroinit_init(void)
-+{
-+ bdrv_register(&bdrv_zeroinit);
-+}
-+
-+block_init(bdrv_zeroinit_init);
---
-2.11.0
-
--- /dev/null
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Fri, 23 Jun 2017 12:01:43 +0200
+Subject: [PATCH] qemu-img dd: add osize and read from/to stdin/stdout
+
+Neither convert nor dd were previously able to write to or
+read from a pipe. Particularly serializing an image file
+into a raw stream or vice versa can be useful, but using
+`qemu-img convert -f qcow2 -O raw foo.qcow2 /dev/stdout` in
+a pipe will fail trying to seek.
+
+While dd and convert have overlapping use cases, `dd` is a
+simple read/write loop while convert is much more
+sophisticated and has ways to dealing with holes and blocks
+of zeroes.
+Since these typically can't be detected in pipes via
+SEEK_DATA/HOLE or skipped while writing, dd seems to be the
+better choice for implementing stdin/stdout streams.
+
+This patch causes "if" and "of" to default to stdin and
+stdout respectively, allowing only the "raw" format to be
+used in these cases.
+Since the input can now be a pipe we have no way of
+detecting the size of the output image to create. Since we
+also want to support images with a size not matching the
+dd command's "bs" parameter (which, together with "count"
+could be used to calculate the desired size, and is already
+used to limit it), the "osize" option is added to explicitly
+override the output file's size.
+
+Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
+---
+ qemu-img-cmds.hx | 4 +-
+ qemu-img.c | 192 ++++++++++++++++++++++++++++++++++---------------------
+ 2 files changed, 122 insertions(+), 74 deletions(-)
+
+diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx
+index 2fe31893cf..52042f2773 100644
+--- a/qemu-img-cmds.hx
++++ b/qemu-img-cmds.hx
+@@ -53,9 +53,9 @@ STEXI
+ ETEXI
+
+ DEF("dd", img_dd,
+- "dd [--image-opts] [-U] [-f fmt] [-O output_fmt] [bs=block_size] [count=blocks] [skip=blocks] if=input of=output")
++ "dd [--image-opts] [-U] [-f fmt] [-O output_fmt] [bs=block_size] [count=blocks] [skip=blocks] [osize=output_size] if=input of=output")
+ STEXI
+-@item dd [--image-opts] [-U] [-f @var{fmt}] [-O @var{output_fmt}] [bs=@var{block_size}] [count=@var{blocks}] [skip=@var{blocks}] if=@var{input} of=@var{output}
++@item dd [--image-opts] [-U] [-f @var{fmt}] [-O @var{output_fmt}] [bs=@var{block_size}] [count=@var{blocks}] [skip=@var{blocks}] [osize=output_size] if=@var{input} of=@var{output}
+ ETEXI
+
+ DEF("info", img_info,
+diff --git a/qemu-img.c b/qemu-img.c
+index f8a39dd195..876a3623a7 100644
+--- a/qemu-img.c
++++ b/qemu-img.c
+@@ -4088,10 +4088,12 @@ out:
+ #define C_IF 04
+ #define C_OF 010
+ #define C_SKIP 020
++#define C_OSIZE 040
+
+ struct DdInfo {
+ unsigned int flags;
+ int64_t count;
++ int64_t osize;
+ };
+
+ struct DdIo {
+@@ -4170,6 +4172,20 @@ static int img_dd_skip(const char *arg,
+ return 0;
+ }
+
++static int img_dd_osize(const char *arg,
++ struct DdIo *in, struct DdIo *out,
++ struct DdInfo *dd)
++{
++ dd->osize = cvtnum(arg);
++
++ if (dd->osize < 0) {
++ error_report("invalid number: '%s'", arg);
++ return 1;
++ }
++
++ return 0;
++}
++
+ static int img_dd(int argc, char **argv)
+ {
+ int ret = 0;
+@@ -4210,6 +4226,7 @@ static int img_dd(int argc, char **argv)
+ { "if", img_dd_if, C_IF },
+ { "of", img_dd_of, C_OF },
+ { "skip", img_dd_skip, C_SKIP },
++ { "osize", img_dd_osize, C_OSIZE },
+ { NULL, NULL, 0 }
+ };
+ const struct option long_options[] = {
+@@ -4288,8 +4305,13 @@ static int img_dd(int argc, char **argv)
+ arg = NULL;
+ }
+
+- if (!(dd.flags & C_IF && dd.flags & C_OF)) {
+- error_report("Must specify both input and output files");
++ if (!(dd.flags & C_IF) && (!fmt || strcmp(fmt, "raw") != 0)) {
++ error_report("Input format must be raw when readin from stdin");
++ ret = -1;
++ goto out;
++ }
++ if (!(dd.flags & C_OF) && strcmp(out_fmt, "raw") != 0) {
++ error_report("Output format must be raw when writing to stdout");
+ ret = -1;
+ goto out;
+ }
+@@ -4301,85 +4323,101 @@ static int img_dd(int argc, char **argv)
+ goto out;
+ }
+
+- blk1 = img_open(image_opts, in.filename, fmt, 0, false, false,
+- force_share);
++ 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 (!blk1) {
++ ret = -1;
++ goto out;
++ }
+ }
+
+- drv = bdrv_find_format(out_fmt);
+- if (!drv) {
+- error_report("Unknown file format");
++ if (dd.flags & C_OSIZE) {
++ size = dd.osize;
++ } else if (dd.flags & C_IF) {
++ size = blk_getlength(blk1);
++ if (size < 0) {
++ error_report("Failed to get size for '%s'", in.filename);
++ ret = -1;
++ goto out;
++ }
++ } else if (dd.flags & C_COUNT) {
++ size = dd.count * in.bsz;
++ } else {
++ error_report("Output size must be known when reading from stdin");
+ ret = -1;
+ goto out;
+ }
+- proto_drv = bdrv_find_protocol(out.filename, true, &local_err);
+
+- if (!proto_drv) {
+- error_report_err(local_err);
+- ret = -1;
+- goto out;
+- }
+- if (!drv->create_opts) {
+- error_report("Format driver '%s' does not support image creation",
+- drv->format_name);
+- ret = -1;
+- goto out;
+- }
+- if (!proto_drv->create_opts) {
+- error_report("Protocol driver '%s' does not support image creation",
+- proto_drv->format_name);
+- ret = -1;
+- goto out;
++ if (!(dd.flags & C_OSIZE) && dd.flags & C_COUNT && dd.count <= INT64_MAX / in.bsz &&
++ dd.count * in.bsz < size) {
++ size = dd.count * in.bsz;
+ }
+- create_opts = qemu_opts_append(create_opts, drv->create_opts);
+- create_opts = qemu_opts_append(create_opts, proto_drv->create_opts);
+
+- opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
++ if (dd.flags & C_OF) {
++ drv = bdrv_find_format(out_fmt);
++ if (!drv) {
++ error_report("Unknown file format");
++ ret = -1;
++ goto out;
++ }
++ proto_drv = bdrv_find_protocol(out.filename, true, &local_err);
+
+- size = blk_getlength(blk1);
+- if (size < 0) {
+- error_report("Failed to get size for '%s'", in.filename);
+- ret = -1;
+- goto out;
+- }
++ if (!proto_drv) {
++ error_report_err(local_err);
++ ret = -1;
++ goto out;
++ }
++ if (!drv->create_opts) {
++ error_report("Format driver '%s' does not support image creation",
++ drv->format_name);
++ ret = -1;
++ goto out;
++ }
++ if (!proto_drv->create_opts) {
++ error_report("Protocol driver '%s' does not support image creation",
++ proto_drv->format_name);
++ ret = -1;
++ goto out;
++ }
++ create_opts = qemu_opts_append(create_opts, drv->create_opts);
++ create_opts = qemu_opts_append(create_opts, proto_drv->create_opts);
+
+- if (dd.flags & C_COUNT && dd.count <= INT64_MAX / in.bsz &&
+- dd.count * in.bsz < size) {
+- size = dd.count * in.bsz;
+- }
++ opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
+
+- /* Overflow means the specified offset is beyond input image's size */
+- if (dd.flags & C_SKIP && (in.offset > INT64_MAX / in.bsz ||
+- size < in.bsz * in.offset)) {
+- qemu_opt_set_number(opts, BLOCK_OPT_SIZE, 0, &error_abort);
+- } else {
+- qemu_opt_set_number(opts, BLOCK_OPT_SIZE,
+- size - in.bsz * in.offset, &error_abort);
+- }
++ /* Overflow means the specified offset is beyond input image's size */
++ if (dd.flags & C_OSIZE) {
++ qemu_opt_set_number(opts, BLOCK_OPT_SIZE, size, &error_abort);
++ } else if (dd.flags & C_SKIP && (in.offset > INT64_MAX / in.bsz ||
++ size < in.bsz * in.offset)) {
++ qemu_opt_set_number(opts, BLOCK_OPT_SIZE, 0, &error_abort);
++ } else {
++ qemu_opt_set_number(opts, BLOCK_OPT_SIZE,
++ size - in.bsz * in.offset, &error_abort);
++ }
+
+- ret = bdrv_create(drv, out.filename, opts, &local_err);
+- if (ret < 0) {
+- error_reportf_err(local_err,
+- "%s: error while creating output image: ",
+- out.filename);
+- ret = -1;
+- goto out;
+- }
++ ret = bdrv_create(drv, out.filename, opts, &local_err);
++ if (ret < 0) {
++ error_reportf_err(local_err,
++ "%s: error while creating output image: ",
++ out.filename);
++ ret = -1;
++ goto out;
++ }
+
+- /* TODO, we can't honour --image-opts for the target,
+- * since it needs to be given in a format compatible
+- * with the bdrv_create() call above which does not
+- * support image-opts style.
+- */
+- blk2 = img_open_file(out.filename, NULL, out_fmt, BDRV_O_RDWR,
+- false, false, false);
++ /* TODO, we can't honour --image-opts for the target,
++ * since it needs to be given in a format compatible
++ * with the bdrv_create() call above which does not
++ * support image-opts style.
++ */
++ blk2 = img_open_file(out.filename, NULL, out_fmt, BDRV_O_RDWR,
++ false, false, false);
+
+- if (!blk2) {
+- ret = -1;
+- goto out;
++ if (!blk2) {
++ ret = -1;
++ goto out;
++ }
+ }
+
+ if (dd.flags & C_SKIP && (in.offset > INT64_MAX / in.bsz ||
+@@ -4397,11 +4435,17 @@ static int img_dd(int argc, char **argv)
+
+ for (out_pos = 0; in_pos < size; block_count++) {
+ int in_ret, out_ret;
+-
+- if (in_pos + in.bsz > size) {
+- in_ret = blk_pread(blk1, in_pos, in.buf, size - in_pos);
++ size_t in_bsz = in_pos + in.bsz > size ? size - in_pos : in.bsz;
++ if (blk1) {
++ in_ret = blk_pread(blk1, in_pos, in.buf, in_bsz);
+ } else {
+- in_ret = blk_pread(blk1, in_pos, in.buf, in.bsz);
++ in_ret = read(STDIN_FILENO, in.buf, in_bsz);
++ if (in_ret == 0) {
++ /* early EOF is considered an error */
++ error_report("Input ended unexpectedly");
++ ret = -1;
++ goto out;
++ }
+ }
+ if (in_ret < 0) {
+ error_report("error while reading from input image file: %s",
+@@ -4411,9 +4455,13 @@ static int img_dd(int argc, char **argv)
+ }
+ in_pos += in_ret;
+
+- out_ret = blk_pwrite(blk2, out_pos, in.buf, in_ret, 0);
++ if (blk2) {
++ out_ret = blk_pwrite(blk2, out_pos, in.buf, in_ret, 0);
++ } else {
++ out_ret = write(STDOUT_FILENO, in.buf, in_ret);
++ }
+
+- if (out_ret < 0) {
++ if (out_ret != in_ret) {
+ error_report("error while writing to output image file: %s",
+ strerror(-out_ret));
+ ret = -1;
+--
+2.11.0
+
--- /dev/null
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Wed, 9 Dec 2015 15:04:57 +0100
+Subject: [PATCH] backup: modify job api
+
+Introduce a pause_count parameter to start a backup in
+paused mode. This way backups of multiple drives can be
+started up sequentially via the completion callback while
+having been started at the same point in time.
+---
+ block/backup.c | 2 ++
+ block/replication.c | 2 +-
+ blockdev.c | 4 ++--
+ blockjob.c | 2 +-
+ include/block/block_int.h | 1 +
+ 5 files changed, 7 insertions(+), 4 deletions(-)
+
+diff --git a/block/backup.c b/block/backup.c
+index 99e6bcc748..8c2967a8cb 100644
+--- a/block/backup.c
++++ b/block/backup.c
+@@ -539,6 +539,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
+ BlockdevOnError on_target_error,
+ int creation_flags,
+ BlockCompletionFunc *cb, void *opaque,
++ int pause_count,
+ BlockJobTxn *txn, Error **errp)
+ {
+ int64_t len;
+@@ -663,6 +664,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
+ block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
+ &error_abort);
+ job->common.len = len;
++ job->common.pause_count = pause_count;
+ block_job_txn_add_job(txn, &job->common);
+
+ return &job->common;
+diff --git a/block/replication.c b/block/replication.c
+index e41e293d2b..1b08b242eb 100644
+--- a/block/replication.c
++++ b/block/replication.c
+@@ -561,7 +561,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
+ 0, MIRROR_SYNC_MODE_NONE, NULL, false,
+ BLOCKDEV_ON_ERROR_REPORT,
+ BLOCKDEV_ON_ERROR_REPORT, BLOCK_JOB_INTERNAL,
+- backup_job_completed, bs, NULL, &local_err);
++ backup_job_completed, bs, 0, NULL, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ backup_job_cleanup(bs);
+diff --git a/blockdev.c b/blockdev.c
+index 56a6b24a0b..a9ed9034b5 100644
+--- a/blockdev.c
++++ b/blockdev.c
+@@ -3293,7 +3293,7 @@ static BlockJob *do_drive_backup(DriveBackup *backup, BlockJobTxn *txn,
+ job = backup_job_create(backup->job_id, bs, target_bs, backup->speed,
+ backup->sync, bmap, backup->compress,
+ backup->on_source_error, backup->on_target_error,
+- BLOCK_JOB_DEFAULT, NULL, NULL, txn, &local_err);
++ BLOCK_JOB_DEFAULT, NULL, NULL, 0, txn, &local_err);
+ bdrv_unref(target_bs);
+ if (local_err != NULL) {
+ error_propagate(errp, local_err);
+@@ -3372,7 +3372,7 @@ BlockJob *do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn,
+ job = backup_job_create(backup->job_id, bs, target_bs, backup->speed,
+ backup->sync, NULL, backup->compress,
+ backup->on_source_error, backup->on_target_error,
+- BLOCK_JOB_DEFAULT, NULL, NULL, txn, &local_err);
++ BLOCK_JOB_DEFAULT, NULL, NULL, 0, txn, &local_err);
+ if (local_err != NULL) {
+ error_propagate(errp, local_err);
+ }
+diff --git a/blockjob.c b/blockjob.c
+index 715c2c2680..c1b6b6a810 100644
+--- a/blockjob.c
++++ b/blockjob.c
+@@ -322,7 +322,7 @@ void block_job_start(BlockJob *job)
+ job->co = qemu_coroutine_create(block_job_co_entry, job);
+ job->pause_count--;
+ job->busy = true;
+- job->paused = false;
++ job->paused = job->pause_count > 0;
+ bdrv_coroutine_enter(blk_bs(job->blk), job->co);
+ }
+
+diff --git a/include/block/block_int.h b/include/block/block_int.h
+index a5482775ec..1dbbdafd31 100644
+--- a/include/block/block_int.h
++++ b/include/block/block_int.h
+@@ -985,6 +985,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
+ BlockdevOnError on_target_error,
+ int creation_flags,
+ BlockCompletionFunc *cb, void *opaque,
++ int pause_count,
+ BlockJobTxn *txn, Error **errp);
+
+ void hmp_drive_add_node(Monitor *mon, const char *optstr);
+--
+2.11.0
+
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Fri, 23 Jun 2017 12:01:43 +0200
-Subject: [PATCH] qemu-img dd: add osize and read from/to stdin/stdout
-
-Neither convert nor dd were previously able to write to or
-read from a pipe. Particularly serializing an image file
-into a raw stream or vice versa can be useful, but using
-`qemu-img convert -f qcow2 -O raw foo.qcow2 /dev/stdout` in
-a pipe will fail trying to seek.
-
-While dd and convert have overlapping use cases, `dd` is a
-simple read/write loop while convert is much more
-sophisticated and has ways to dealing with holes and blocks
-of zeroes.
-Since these typically can't be detected in pipes via
-SEEK_DATA/HOLE or skipped while writing, dd seems to be the
-better choice for implementing stdin/stdout streams.
-
-This patch causes "if" and "of" to default to stdin and
-stdout respectively, allowing only the "raw" format to be
-used in these cases.
-Since the input can now be a pipe we have no way of
-detecting the size of the output image to create. Since we
-also want to support images with a size not matching the
-dd command's "bs" parameter (which, together with "count"
-could be used to calculate the desired size, and is already
-used to limit it), the "osize" option is added to explicitly
-override the output file's size.
-
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
----
- qemu-img-cmds.hx | 4 +-
- qemu-img.c | 176 +++++++++++++++++++++++++++++++++++--------------------
- 2 files changed, 115 insertions(+), 65 deletions(-)
-
-diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx
-index 8ac78222af..16bee83987 100644
---- a/qemu-img-cmds.hx
-+++ b/qemu-img-cmds.hx
-@@ -46,9 +46,9 @@ STEXI
- ETEXI
-
- DEF("dd", img_dd,
-- "dd [--image-opts] [-f fmt] [-O output_fmt] [bs=block_size] [count=blocks] [skip=blocks] if=input of=output")
-+ "dd [--image-opts] [-f fmt] [-O output_fmt] [bs=block_size] [count=blocks] [skip=blocks] [osize=output_size] [if=input] [of=output]")
- STEXI
--@item dd [--image-opts] [-f @var{fmt}] [-O @var{output_fmt}] [bs=@var{block_size}] [count=@var{blocks}] [skip=@var{blocks}] if=@var{input} of=@var{output}
-+@item dd [--image-opts] [-f @var{fmt}] [-O @var{output_fmt}] [bs=@var{block_size}] [count=@var{blocks}] [skip=@var{blocks}] [osize=output_size] [if=@var{input}] [of=@var{output}]
- ETEXI
-
- DEF("info", img_info,
-diff --git a/qemu-img.c b/qemu-img.c
-index c7804d63ee..ee7816e727 100644
---- a/qemu-img.c
-+++ b/qemu-img.c
-@@ -4026,10 +4026,12 @@ out:
- #define C_IF 04
- #define C_OF 010
- #define C_SKIP 020
-+#define C_OSIZE 040
-
- struct DdInfo {
- unsigned int flags;
- int64_t count;
-+ int64_t osize;
- };
-
- struct DdIo {
-@@ -4108,6 +4110,20 @@ static int img_dd_skip(const char *arg,
- return 0;
- }
-
-+static int img_dd_osize(const char *arg,
-+ struct DdIo *in, struct DdIo *out,
-+ struct DdInfo *dd)
-+{
-+ dd->osize = cvtnum(arg);
-+
-+ if (dd->osize < 0) {
-+ error_report("invalid number: '%s'", arg);
-+ return 1;
-+ }
-+
-+ return 0;
-+}
-+
- static int img_dd(int argc, char **argv)
- {
- int ret = 0;
-@@ -4147,6 +4163,7 @@ static int img_dd(int argc, char **argv)
- { "if", img_dd_if, C_IF },
- { "of", img_dd_of, C_OF },
- { "skip", img_dd_skip, C_SKIP },
-+ { "osize", img_dd_osize, C_OSIZE },
- { NULL, NULL, 0 }
- };
- const struct option long_options[] = {
-@@ -4214,84 +4231,106 @@ static int img_dd(int argc, char **argv)
- arg = NULL;
- }
-
-- if (!(dd.flags & C_IF && dd.flags & C_OF)) {
-- error_report("Must specify both input and output files");
-+ if (!(dd.flags & C_IF) && (!fmt || strcmp(fmt, "raw") != 0)) {
-+ error_report("Input format must be raw when readin from stdin");
- ret = -1;
- goto out;
- }
-- blk1 = img_open(image_opts, in.filename, fmt, 0, false, false);
--
-- if (!blk1) {
-+ if (!(dd.flags & C_OF) && strcmp(out_fmt, "raw") != 0) {
-+ error_report("Output format must be raw when writing to stdout");
- ret = -1;
- goto out;
- }
-+ if (dd.flags & C_IF) {
-+ blk1 = img_open(image_opts, in.filename, fmt, 0, false, false);
-
-- drv = bdrv_find_format(out_fmt);
-- if (!drv) {
-- error_report("Unknown file format");
-- ret = -1;
-- goto out;
-- }
-- proto_drv = bdrv_find_protocol(out.filename, true, &local_err);
--
-- if (!proto_drv) {
-- error_report_err(local_err);
-- ret = -1;
-- goto out;
-- }
-- if (!drv->create_opts) {
-- error_report("Format driver '%s' does not support image creation",
-- drv->format_name);
-- ret = -1;
-- goto out;
-- }
-- if (!proto_drv->create_opts) {
-- error_report("Protocol driver '%s' does not support image creation",
-- proto_drv->format_name);
-- ret = -1;
-- goto out;
-+ if (!blk1) {
-+ ret = -1;
-+ goto out;
-+ }
- }
-- create_opts = qemu_opts_append(create_opts, drv->create_opts);
-- create_opts = qemu_opts_append(create_opts, proto_drv->create_opts);
--
-- opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
-
-- size = blk_getlength(blk1);
-- if (size < 0) {
-- error_report("Failed to get size for '%s'", in.filename);
-+ if (dd.flags & C_OSIZE) {
-+ size = dd.osize;
-+ } else if (dd.flags & C_IF) {
-+ size = blk_getlength(blk1);
-+ if (size < 0) {
-+ error_report("Failed to get size for '%s'", in.filename);
-+ ret = -1;
-+ goto out;
-+ }
-+ } else if (dd.flags & C_COUNT) {
-+ size = dd.count * in.bsz;
-+ } else {
-+ error_report("Output size must be known when reading from stdin");
- ret = -1;
- goto out;
- }
-
-- if (dd.flags & C_COUNT && dd.count <= INT64_MAX / in.bsz &&
-+ if (!(dd.flags & C_OSIZE) && dd.flags & C_COUNT && dd.count <= INT64_MAX / in.bsz &&
- dd.count * in.bsz < size) {
- size = dd.count * in.bsz;
- }
-
-- /* Overflow means the specified offset is beyond input image's size */
-- if (dd.flags & C_SKIP && (in.offset > INT64_MAX / in.bsz ||
-- size < in.bsz * in.offset)) {
-- qemu_opt_set_number(opts, BLOCK_OPT_SIZE, 0, &error_abort);
-- } else {
-- qemu_opt_set_number(opts, BLOCK_OPT_SIZE,
-- size - in.bsz * in.offset, &error_abort);
-- }
-
-- ret = bdrv_create(drv, out.filename, opts, &local_err);
-- if (ret < 0) {
-- error_reportf_err(local_err,
-- "%s: error while creating output image: ",
-- out.filename);
-- ret = -1;
-- goto out;
-- }
-+ if (dd.flags & C_OF) {
-+ drv = bdrv_find_format(out_fmt);
-+ if (!drv) {
-+ error_report("Unknown file format");
-+ ret = -1;
-+ goto out;
-+ }
-+ proto_drv = bdrv_find_protocol(out.filename, true, &local_err);
-+
-+ if (!proto_drv) {
-+ error_report_err(local_err);
-+ ret = -1;
-+ goto out;
-+ }
-+ if (!drv->create_opts) {
-+ error_report("Format driver '%s' does not support image creation",
-+ drv->format_name);
-+ ret = -1;
-+ goto out;
-+ }
-+ if (!proto_drv->create_opts) {
-+ error_report("Protocol driver '%s' does not support image creation",
-+ proto_drv->format_name);
-+ ret = -1;
-+ goto out;
-+ }
-+ create_opts = qemu_opts_append(create_opts, drv->create_opts);
-+ create_opts = qemu_opts_append(create_opts, proto_drv->create_opts);
-
-- blk2 = img_open(image_opts, out.filename, out_fmt, BDRV_O_RDWR,
-- false, false);
-+ opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
-
-- if (!blk2) {
-- ret = -1;
-- goto out;
-+ /* Overflow means the specified offset is beyond input image's size */
-+ if (dd.flags & C_OSIZE) {
-+ qemu_opt_set_number(opts, BLOCK_OPT_SIZE, size, &error_abort);
-+ } else if (dd.flags & C_SKIP && (in.offset > INT64_MAX / in.bsz ||
-+ size < in.bsz * in.offset)) {
-+ qemu_opt_set_number(opts, BLOCK_OPT_SIZE, 0, &error_abort);
-+ } else {
-+ qemu_opt_set_number(opts, BLOCK_OPT_SIZE,
-+ size - in.bsz * in.offset, &error_abort);
-+ }
-+
-+ ret = bdrv_create(drv, out.filename, opts, &local_err);
-+ if (ret < 0) {
-+ error_reportf_err(local_err,
-+ "%s: error while creating output image: ",
-+ out.filename);
-+ ret = -1;
-+ goto out;
-+ }
-+
-+ blk2 = img_open(image_opts, out.filename, out_fmt, BDRV_O_RDWR,
-+ false, false);
-+
-+ if (!blk2) {
-+ ret = -1;
-+ goto out;
-+ }
- }
-
- if (dd.flags & C_SKIP && (in.offset > INT64_MAX / in.bsz ||
-@@ -4309,11 +4348,18 @@ static int img_dd(int argc, char **argv)
-
- for (out_pos = 0; in_pos < size; block_count++) {
- int in_ret, out_ret;
-+ size_t in_bsz = in_pos + in.bsz > size ? size - in_pos : in.bsz;
-
-- if (in_pos + in.bsz > size) {
-- in_ret = blk_pread(blk1, in_pos, in.buf, size - in_pos);
-+ if (blk1) {
-+ in_ret = blk_pread(blk1, in_pos, in.buf, in_bsz);
- } else {
-- in_ret = blk_pread(blk1, in_pos, in.buf, in.bsz);
-+ in_ret = read(STDIN_FILENO, in.buf, in_bsz);
-+ if (in_ret == 0) {
-+ /* early EOF is considered an error */
-+ error_report("Input ended unexpectedly");
-+ ret = -1;
-+ goto out;
-+ }
- }
- if (in_ret < 0) {
- error_report("error while reading from input image file: %s",
-@@ -4323,9 +4369,13 @@ static int img_dd(int argc, char **argv)
- }
- in_pos += in_ret;
-
-- out_ret = blk_pwrite(blk2, out_pos, in.buf, in_ret, 0);
-+ if (blk2) {
-+ out_ret = blk_pwrite(blk2, out_pos, in.buf, in_ret, 0);
-+ } else {
-+ out_ret = write(STDOUT_FILENO, in.buf, in_ret);
-+ }
-
-- if (out_ret < 0) {
-+ if (out_ret != in_ret) {
- error_report("error while writing to output image file: %s",
- strerror(-out_ret));
- ret = -1;
---
-2.11.0
-
--- /dev/null
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Wed, 2 Aug 2017 13:51:02 +0200
+Subject: [PATCH] backup: introduce vma archive format
+
+---
+ MAINTAINERS | 6 +
+ block/Makefile.objs | 3 +
+ block/vma.c | 424 ++++++++++++++++++++++++++++++++++++++++
+ blockdev.c | 536 +++++++++++++++++++++++++++++++++++++++++++++++++++
+ blockjob.c | 3 +-
+ configure | 30 +++
+ hmp-commands-info.hx | 13 ++
+ hmp-commands.hx | 31 +++
+ hmp.c | 63 ++++++
+ hmp.h | 3 +
+ qapi/block-core.json | 109 ++++++++++-
+ 11 files changed, 1219 insertions(+), 2 deletions(-)
+ create mode 100644 block/vma.c
+
+diff --git a/MAINTAINERS b/MAINTAINERS
+index 0255113470..581d80d144 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -1950,6 +1950,12 @@ L: qemu-block@nongnu.org
+ S: Supported
+ F: block/vvfat.c
+
++VMA
++M: Wolfgang Bumiller <w.bumiller@proxmox.com>.
++L: pve-devel@proxmox.com
++S: Supported
++F: block/vma.c
++
+ Image format fuzzer
+ M: Stefan Hajnoczi <stefanha@redhat.com>
+ L: qemu-block@nongnu.org
+diff --git a/block/Makefile.objs b/block/Makefile.objs
+index 823e60cda6..d74140e413 100644
+--- a/block/Makefile.objs
++++ b/block/Makefile.objs
+@@ -22,6 +22,7 @@ block-obj-$(CONFIG_RBD) += rbd.o
+ block-obj-$(CONFIG_GLUSTERFS) += gluster.o
+ block-obj-$(CONFIG_VXHS) += vxhs.o
+ block-obj-$(CONFIG_LIBSSH2) += ssh.o
++block-obj-$(CONFIG_VMA) += vma.o
+ block-obj-y += accounting.o dirty-bitmap.o
+ block-obj-y += write-threshold.o
+ block-obj-y += backup.o
+@@ -48,3 +49,5 @@ block-obj-$(if $(CONFIG_BZIP2),m,n) += dmg-bz2.o
+ dmg-bz2.o-libs := $(BZIP2_LIBS)
+ qcow.o-libs := -lz
+ linux-aio.o-libs := -laio
++vma.o-cflags := $(VMA_CFLAGS)
++vma.o-libs := $(VMA_LIBS)
+diff --git a/block/vma.c b/block/vma.c
+new file mode 100644
+index 0000000000..7151514f94
+--- /dev/null
++++ b/block/vma.c
+@@ -0,0 +1,424 @@
++/*
++ * VMA archive backend for QEMU, container object
++ *
++ * Copyright (C) 2017 Proxmox Server Solutions GmbH
++ *
++ * This work is licensed under the terms of the GNU GPL, version 2 or later.
++ * See the COPYING file in the top-level directory.
++ *
++ */
++#include <vma/vma.h>
++
++#include "qemu/osdep.h"
++#include "qemu/uuid.h"
++#include "qemu-common.h"
++#include "qapi/error.h"
++#include "qapi/qmp/qerror.h"
++#include "qapi/qmp/qstring.h"
++#include "qom/object.h"
++#include "qom/object_interfaces.h"
++#include "block/block_int.h"
++
++/* exported interface */
++void vma_object_add_config_file(Object *obj, const char *name,
++ const char *contents, size_t len,
++ Error **errp);
++
++#define TYPE_VMA_OBJECT "vma"
++#define VMA_OBJECT(obj) \
++ OBJECT_CHECK(VMAObjectState, (obj), TYPE_VMA_OBJECT)
++#define VMA_OBJECT_GET_CLASS(obj) \
++ OBJECT_GET_CLASS(VMAObjectClass, (obj), TYPE_VMA_OBJECT)
++
++typedef struct VMAObjectClass {
++ ObjectClass parent_class;
++} VMAObjectClass;
++
++typedef struct VMAObjectState {
++ Object parent;
++
++ char *filename;
++
++ QemuUUID uuid;
++ bool blocked;
++ VMAWriter *vma;
++ QemuMutex mutex;
++} VMAObjectState;
++
++static VMAObjectState *vma_by_id(const char *name)
++{
++ Object *container;
++ Object *obj;
++
++ container = object_get_objects_root();
++ obj = object_resolve_path_component(container, name);
++
++ return VMA_OBJECT(obj);
++}
++
++static void vma_object_class_complete(UserCreatable *uc, Error **errp)
++{
++ int rc;
++ VMAObjectState *vo = VMA_OBJECT(uc);
++ VMAObjectClass *voc = VMA_OBJECT_GET_CLASS(uc);
++ (void)!vo;
++ (void)!voc;
++
++ if (!vo->filename) {
++ error_setg(errp, "Parameter 'filename' is required");
++ return;
++ }
++
++ rc = VMAWriter_fopen(vo->filename, &vo->vma);
++ if (rc < 0) {
++ error_setg_errno(errp, -rc, "failed to create VMA archive");
++ return;
++ }
++
++ rc = VMAWriter_set_uuid(vo->vma, vo->uuid.data, sizeof(vo->uuid.data));
++ if (rc < 0) {
++ error_setg_errno(errp, -rc, "failed to set UUID of VMA archive");
++ return;
++ }
++
++ qemu_mutex_init(&vo->mutex);
++}
++
++static bool vma_object_can_be_deleted(UserCreatable *uc, Error **errp)
++{
++ //VMAObjectState *vo = VMA_OBJECT(uc);
++ //if (!vo->vma) {
++ // return true;
++ //}
++ //return false;
++ return true;
++}
++
++static void vma_object_class_init(ObjectClass *oc, void *data)
++{
++ UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
++
++ ucc->can_be_deleted = vma_object_can_be_deleted;
++ ucc->complete = vma_object_class_complete;
++}
++
++static char *vma_object_get_filename(Object *obj, Error **errp)
++{
++ VMAObjectState *vo = VMA_OBJECT(obj);
++
++ return g_strdup(vo->filename);
++}
++
++static void vma_object_set_filename(Object *obj, const char *str, Error **errp)
++{
++ VMAObjectState *vo = VMA_OBJECT(obj);
++
++ if (vo->vma) {
++ error_setg(errp, "filename cannot be changed after creation");
++ return;
++ }
++
++ g_free(vo->filename);
++ vo->filename = g_strdup(str);
++}
++
++static char *vma_object_get_uuid(Object *obj, Error **errp)
++{
++ VMAObjectState *vo = VMA_OBJECT(obj);
++
++ return qemu_uuid_unparse_strdup(&vo->uuid);
++}
++
++static void vma_object_set_uuid(Object *obj, const char *str, Error **errp)
++{
++ VMAObjectState *vo = VMA_OBJECT(obj);
++
++ if (vo->vma) {
++ error_setg(errp, "uuid cannot be changed after creation");
++ return;
++ }
++
++ qemu_uuid_parse(str, &vo->uuid);
++}
++
++static bool vma_object_get_blocked(Object *obj, Error **errp)
++{
++ VMAObjectState *vo = VMA_OBJECT(obj);
++
++ return vo->blocked;
++}
++
++static void vma_object_set_blocked(Object *obj, bool blocked, Error **errp)
++{
++ VMAObjectState *vo = VMA_OBJECT(obj);
++
++ (void)errp;
++
++ vo->blocked = blocked;
++}
++
++void vma_object_add_config_file(Object *obj, const char *name,
++ const char *contents, size_t len,
++ Error **errp)
++{
++ int rc;
++ VMAObjectState *vo = VMA_OBJECT(obj);
++
++ if (!vo || !vo->vma) {
++ error_setg(errp, "not a valid vma object to add config files to");
++ return;
++ }
++
++ rc = VMAWriter_addConfigFile(vo->vma, name, contents, len);
++ if (rc < 0) {
++ error_setg_errno(errp, -rc, "failed to add config file to VMA");
++ return;
++ }
++}
++
++static void vma_object_init(Object *obj)
++{
++ VMAObjectState *vo = VMA_OBJECT(obj);
++ (void)!vo;
++
++ object_property_add_str(obj, "filename",
++ vma_object_get_filename, vma_object_set_filename,
++ NULL);
++ object_property_add_str(obj, "uuid",
++ vma_object_get_uuid, vma_object_set_uuid,
++ NULL);
++ object_property_add_bool(obj, "blocked",
++ vma_object_get_blocked, vma_object_set_blocked,
++ NULL);
++}
++
++static void vma_object_finalize(Object *obj)
++{
++ VMAObjectState *vo = VMA_OBJECT(obj);
++ VMAObjectClass *voc = VMA_OBJECT_GET_CLASS(obj);
++ (void)!voc;
++
++ qemu_mutex_destroy(&vo->mutex);
++
++ VMAWriter_destroy(vo->vma, true);
++ g_free(vo->filename);
++}
++
++static const TypeInfo vma_object_info = {
++ .name = TYPE_VMA_OBJECT,
++ .parent = TYPE_OBJECT,
++ .class_size = sizeof(VMAObjectClass),
++ .class_init = vma_object_class_init,
++ .instance_size = sizeof(VMAObjectState),
++ .instance_init = vma_object_init,
++ .instance_finalize = vma_object_finalize,
++ .interfaces = (InterfaceInfo[]) {
++ { TYPE_USER_CREATABLE },
++ { }
++ }
++};
++
++static void register_types(void)
++{
++ type_register_static(&vma_object_info);
++}
++
++type_init(register_types);
++
++typedef struct {
++ VMAObjectState *vma_obj;
++ char *name;
++ size_t device_id;
++ uint64_t byte_size;
++} BDRVVMAState;
++
++static void qemu_vma_parse_filename(const char *filename, QDict *options,
++ Error **errp)
++{
++ char *sep;
++
++ sep = strchr(filename, '/');
++ if (!sep || sep == filename) {
++ error_setg(errp, "VMA filename should be <vma-object>/<device-name>");
++ return;
++ }
++
++ qdict_put(options, "vma", qstring_from_substr(filename, 0, sep-filename-1));
++
++ while (*sep && *sep == '/')
++ ++sep;
++ if (!*sep) {
++ error_setg(errp, "missing device name\n");
++ return;
++ }
++
++ qdict_put(options, "name", qstring_from_str(sep));
++}
++
++static QemuOptsList runtime_opts = {
++ .name = "vma-drive",
++ .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
++ .desc = {
++ {
++ .name = "vma",
++ .type = QEMU_OPT_STRING,
++ .help = "VMA Object name",
++ },
++ {
++ .name = "name",
++ .type = QEMU_OPT_STRING,
++ .help = "VMA device name",
++ },
++ {
++ .name = BLOCK_OPT_SIZE,
++ .type = QEMU_OPT_SIZE,
++ .help = "Virtual disk size"
++ },
++ { /* end of list */ }
++ },
++};
++static int qemu_vma_open(BlockDriverState *bs, QDict *options, int flags,
++ Error **errp)
++{
++ Error *local_err = NULL;
++ BDRVVMAState *s = bs->opaque;
++ QemuOpts *opts;
++ const char *vma_id, *device_name;
++ ssize_t dev_id;
++ int64_t bytes = 0;
++ int ret;
++
++ opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
++ qemu_opts_absorb_qdict(opts, options, &local_err);
++ if (local_err) {
++ error_propagate(errp, local_err);
++ ret = -EINVAL;
++ goto failed_opts;
++ }
++
++ bytes = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
++ BDRV_SECTOR_SIZE);
++
++ vma_id = qemu_opt_get(opts, "vma");
++ device_name = qemu_opt_get(opts, "name");
++
++ VMAObjectState *vma = vma_by_id(vma_id);
++ if (!vma) {
++ ret = -EINVAL;
++ error_setg(errp, "no such VMA object: %s", vma_id);
++ goto failed_opts;
++ }
++
++ dev_id = VMAWriter_findDevice(vma->vma, device_name);
++ if (dev_id >= 0) {
++ error_setg(errp, "drive already exists in VMA object");
++ ret = -EIO;
++ goto failed_opts;
++ }
++
++ dev_id = VMAWriter_addDevice(vma->vma, device_name, (uint64_t)bytes);
++ if (dev_id < 0) {
++ error_setg_errno(errp, -dev_id, "failed to add VMA device");
++ ret = -EIO;
++ goto failed_opts;
++ }
++
++ object_ref(OBJECT(vma));
++ s->vma_obj = vma;
++ s->name = g_strdup(device_name);
++ s->device_id = (size_t)dev_id;
++ s->byte_size = bytes;
++
++ ret = 0;
++
++failed_opts:
++ qemu_opts_del(opts);
++ return ret;
++}
++
++static void qemu_vma_close(BlockDriverState *bs)
++{
++ BDRVVMAState *s = bs->opaque;
++
++ (void)VMAWriter_finishDevice(s->vma_obj->vma, s->device_id);
++ object_unref(OBJECT(s->vma_obj));
++
++ g_free(s->name);
++}
++
++static int64_t qemu_vma_getlength(BlockDriverState *bs)
++{
++ BDRVVMAState *s = bs->opaque;
++
++ return s->byte_size;
++}
++
++static coroutine_fn int qemu_vma_co_writev(BlockDriverState *bs,
++ int64_t sector_num,
++ int nb_sectors,
++ QEMUIOVector *qiov)
++{
++ size_t i;
++ ssize_t rc;
++ BDRVVMAState *s = bs->opaque;
++ VMAObjectState *vo = s->vma_obj;
++ off_t offset = sector_num * BDRV_SECTOR_SIZE;
++
++ qemu_mutex_lock(&vo->mutex);
++ if (vo->blocked) {
++ return -EPERM;
++ }
++ for (i = 0; i != qiov->niov; ++i) {
++ const struct iovec *v = &qiov->iov[i];
++ size_t blocks = v->iov_len / VMA_BLOCK_SIZE;
++ if (blocks * VMA_BLOCK_SIZE != v->iov_len) {
++ return -EIO;
++ }
++ rc = VMAWriter_writeBlocks(vo->vma, s->device_id,
++ v->iov_base, blocks, offset);
++ if (errno) {
++ return -errno;
++ }
++ if (rc != blocks) {
++ return -EIO;
++ }
++ offset += v->iov_len;
++ }
++ qemu_mutex_unlock(&vo->mutex);
++ return 0;
++}
++
++static int qemu_vma_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
++{
++ bdi->cluster_size = VMA_CLUSTER_SIZE;
++ bdi->unallocated_blocks_are_zero = true;
++ bdi->can_write_zeroes_with_unmap = false;
++ return 0;
++}
++
++static BlockDriver bdrv_vma_drive = {
++ .format_name = "vma-drive",
++ .instance_size = sizeof(BDRVVMAState),
++
++#if 0
++ .bdrv_create = qemu_vma_create,
++ .create_opts = &qemu_vma_create_opts,
++#endif
++
++ .bdrv_parse_filename = qemu_vma_parse_filename,
++ .bdrv_file_open = qemu_vma_open,
++
++ .bdrv_close = qemu_vma_close,
++ .bdrv_has_zero_init = bdrv_has_zero_init_1,
++ .bdrv_getlength = qemu_vma_getlength,
++ .bdrv_get_info = qemu_vma_get_info,
++
++ .bdrv_co_writev = qemu_vma_co_writev,
++};
++
++static void bdrv_vma_init(void)
++{
++ bdrv_register(&bdrv_vma_drive);
++}
++
++block_init(bdrv_vma_init);
+diff --git a/blockdev.c b/blockdev.c
+index a9ed9034b5..3ffd064c48 100644
+--- a/blockdev.c
++++ b/blockdev.c
+@@ -31,10 +31,12 @@
+ */
+
+ #include "qemu/osdep.h"
++#include "qemu/uuid.h"
+ #include "sysemu/block-backend.h"
+ #include "sysemu/blockdev.h"
+ #include "hw/block/block.h"
+ #include "block/blockjob.h"
++#include "block/blockjob_int.h"
+ #include "block/throttle-groups.h"
+ #include "monitor/monitor.h"
+ #include "qemu/error-report.h"
+@@ -2963,6 +2965,540 @@ out:
+ aio_context_release(aio_context);
+ }
+
++/* PVE backup related function */
++
++static struct PVEBackupState {
++ Error *error;
++ bool cancel;
++ QemuUUID uuid;
++ char uuid_str[37];
++ int64_t speed;
++ time_t start_time;
++ time_t end_time;
++ char *backup_file;
++ Object *vmaobj;
++ GList *di_list;
++ size_t next_job;
++ size_t total;
++ size_t transferred;
++ size_t zero_bytes;
++ QemuMutex backup_mutex;
++ bool backup_mutex_initialized;
++} backup_state;
++
++typedef struct PVEBackupDevInfo {
++ BlockDriverState *bs;
++ size_t size;
++ uint8_t dev_id;
++ bool completed;
++ char targetfile[PATH_MAX];
++ BlockDriverState *target;
++} PVEBackupDevInfo;
++
++static void pvebackup_run_next_job(void);
++
++static void pvebackup_cleanup(void)
++{
++ qemu_mutex_lock(&backup_state.backup_mutex);
++ // Avoid race between block jobs and backup-cancel command:
++ if (!backup_state.vmaw) {
++ qemu_mutex_unlock(&backup_state.backup_mutex);
++ return;
++ }
++
++ backup_state.end_time = time(NULL);
++
++ if (backup_state.vmaobj) {
++ object_unparent(backup_state.vmaobj);
++ backup_state.vmaobj = NULL;
++ }
++
++ g_list_free(backup_state.di_list);
++ backup_state.di_list = NULL;
++ qemu_mutex_unlock(&backup_state.backup_mutex);
++}
++
++static void pvebackup_complete_cb(void *opaque, int ret)
++{
++ // This always runs in the main loop
++
++ PVEBackupDevInfo *di = opaque;
++
++ di->completed = true;
++
++ if (ret < 0 && !backup_state.error) {
++ error_setg(&backup_state.error, "job failed with err %d - %s",
++ ret, strerror(-ret));
++ }
++
++ di->bs = NULL;
++ di->target = NULL;
++
++ if (backup_state.vmaobj) {
++ object_unparent(backup_state.vmaobj);
++ backup_state.vmaobj = NULL;
++ }
++
++ // remove self from job queue
++ qemu_mutex_lock(&backup_state.backup_mutex);
++ backup_state.di_list = g_list_remove(backup_state.di_list, di);
++ g_free(di);
++ qemu_mutex_unlock(&backup_state.backup_mutex);
++
++ if (!backup_state.cancel) {
++ pvebackup_run_next_job();
++ }
++}
++
++static void pvebackup_cancel(void *opaque)
++{
++ backup_state.cancel = true;
++ qemu_mutex_lock(&backup_state.backup_mutex);
++ // Avoid race between block jobs and backup-cancel command:
++ if (!backup_state.vmaw) {
++ qemu_mutex_unlock(&backup_state.backup_mutex);
++ return;
++ }
++
++ if (!backup_state.error) {
++ error_setg(&backup_state.error, "backup cancelled");
++ }
++
++ if (backup_state.vmaobj) {
++ Error *err;
++ /* make sure vma writer does not block anymore */
++ if (!object_set_props(backup_state.vmaobj, &err, "blocked", "yes", NULL)) {
++ if (err) {
++ error_report_err(err);
++ }
++ }
++ }
++
++ GList *l = backup_state.di_list;
++ while (l) {
++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++ l = g_list_next(l);
++ if (!di->completed && di->bs) {
++ BlockJob *job = di->bs->job;
++ if (job) {
++ AioContext *aio_context = blk_get_aio_context(job->blk);
++ aio_context_acquire(aio_context);
++ if (!di->completed) {
++ block_job_cancel(job);
++ }
++ aio_context_release(aio_context);
++ }
++ }
++ }
++
++ qemu_mutex_unlock(&backup_state.backup_mutex);
++ pvebackup_cleanup();
++}
++
++void qmp_backup_cancel(Error **errp)
++{
++ if (!backup_state.backup_mutex_initialized)
++ return;
++ Coroutine *co = qemu_coroutine_create(pvebackup_cancel, NULL);
++ qemu_coroutine_enter(co);
++
++ while (backup_state.vmaobj) {
++ /* FIXME: Find something better for this */
++ aio_poll(qemu_get_aio_context(), true);
++ }
++}
++
++void vma_object_add_config_file(Object *obj, const char *name,
++ const char *contents, size_t len,
++ Error **errp);
++static int config_to_vma(const char *file, BackupFormat format,
++ Object *vmaobj,
++ const char *backup_dir,
++ Error **errp)
++{
++ char *cdata = NULL;
++ gsize clen = 0;
++ GError *err = NULL;
++ if (!g_file_get_contents(file, &cdata, &clen, &err)) {
++ error_setg(errp, "unable to read file '%s'", file);
++ return 1;
++ }
++
++ char *basename = g_path_get_basename(file);
++
++ if (format == BACKUP_FORMAT_VMA) {
++ vma_object_add_config_file(vmaobj, basename, cdata, clen, errp);
++ } else if (format == BACKUP_FORMAT_DIR) {
++ char config_path[PATH_MAX];
++ snprintf(config_path, PATH_MAX, "%s/%s", backup_dir, basename);
++ if (!g_file_set_contents(config_path, cdata, clen, &err)) {
++ error_setg(errp, "unable to write config file '%s'", config_path);
++ g_free(cdata);
++ g_free(basename);
++ return 1;
++ }
++ }
++
++ g_free(basename);
++ g_free(cdata);
++ return 0;
++}
++
++void block_job_resume(BlockJob *job);
++static void pvebackup_run_next_job(void)
++{
++ qemu_mutex_lock(&backup_state.backup_mutex);
++
++ GList *next = g_list_nth(backup_state.di_list, backup_state.next_job);
++ while (next) {
++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)next->data;
++ backup_state.next_job++;
++ if (!di->completed && di->bs && di->bs->job) {
++ BlockJob *job = di->bs->job;
++ AioContext *aio_context = blk_get_aio_context(job->blk);
++ aio_context_acquire(aio_context);
++ qemu_mutex_unlock(&backup_state.backup_mutex);
++ if (backup_state.error || backup_state.cancel) {
++ block_job_cancel_sync(job);
++ } else {
++ block_job_resume(job);
++ }
++ aio_context_release(aio_context);
++ return;
++ }
++ next = g_list_next(next);
++ }
++ qemu_mutex_unlock(&backup_state.backup_mutex);
++
++ // no more jobs, run the cleanup
++ pvebackup_cleanup();
++}
++
++UuidInfo *qmp_backup(const char *backup_file, bool has_format,
++ BackupFormat format,
++ bool has_config_file, const char *config_file,
++ bool has_firewall_file, const char *firewall_file,
++ bool has_devlist, const char *devlist,
++ bool has_speed, int64_t speed, Error **errp)
++{
++ BlockBackend *blk;
++ BlockDriverState *bs = NULL;
++ const char *backup_dir = NULL;
++ Error *local_err = NULL;
++ QemuUUID uuid;
++ gchar **devs = NULL;
++ GList *di_list = NULL;
++ GList *l;
++ UuidInfo *uuid_info;
++ BlockJob *job;
++
++ if (!backup_state.backup_mutex_initialized) {
++ qemu_mutex_init(&backup_state.backup_mutex);
++ backup_state.backup_mutex_initialized = true;
++ }
++
++ if (backup_state.di_list || backup_state.vmaobj) {
++ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
++ "previous backup not finished");
++ return NULL;
++ }
++
++ /* Todo: try to auto-detect format based on file name */
++ format = has_format ? format : BACKUP_FORMAT_VMA;
++
++ if (has_devlist) {
++ devs = g_strsplit_set(devlist, ",;:", -1);
++
++ gchar **d = devs;
++ while (d && *d) {
++ blk = blk_by_name(*d);
++ if (blk) {
++ bs = blk_bs(blk);
++ if (bdrv_is_read_only(bs)) {
++ error_setg(errp, "Node '%s' is read only", *d);
++ goto err;
++ }
++ if (!bdrv_is_inserted(bs)) {
++ error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
++ goto err;
++ }
++ PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
++ di->bs = bs;
++ di_list = g_list_append(di_list, di);
++ } else {
++ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
++ "Device '%s' not found", *d);
++ goto err;
++ }
++ d++;
++ }
++
++ } else {
++ BdrvNextIterator it;
++
++ bs = NULL;
++ for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
++ if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) {
++ continue;
++ }
++
++ PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
++ di->bs = bs;
++ di_list = g_list_append(di_list, di);
++ }
++ }
++
++ if (!di_list) {
++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
++ goto err;
++ }
++
++ size_t total = 0;
++
++ l = di_list;
++ while (l) {
++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++ l = g_list_next(l);
++ if (bdrv_op_is_blocked(di->bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) {
++ goto err;
++ }
++
++ ssize_t size = bdrv_getlength(di->bs);
++ if (size < 0) {
++ error_setg_errno(errp, -di->size, "bdrv_getlength failed");
++ goto err;
++ }
++ di->size = size;
++ total += size;
++ }
++
++ qemu_uuid_generate(&uuid);
++
++ if (format == BACKUP_FORMAT_VMA) {
++ char uuidstr[UUID_FMT_LEN+1];
++ qemu_uuid_unparse(&uuid, uuidstr);
++ uuidstr[UUID_FMT_LEN] = 0;
++ backup_state.vmaobj =
++ object_new_with_props("vma", object_get_objects_root(),
++ "vma-backup-obj", &local_err,
++ "filename", backup_file,
++ "uuid", uuidstr,
++ NULL);
++ if (!backup_state.vmaobj) {
++ if (local_err) {
++ error_propagate(errp, local_err);
++ }
++ goto err;
++ }
++
++ l = di_list;
++ while (l) {
++ QDict *options = qdict_new();
++
++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++ l = g_list_next(l);
++
++ const char *devname = bdrv_get_device_name(di->bs);
++ snprintf(di->targetfile, PATH_MAX, "vma-backup-obj/%s.raw", devname);
++
++ qdict_put(options, "driver", qstring_from_str("vma-drive"));
++ qdict_put(options, "size", qint_from_int(di->size));
++ di->target = bdrv_open(di->targetfile, NULL, options, BDRV_O_RDWR, &local_err);
++ if (!di->target) {
++ error_propagate(errp, local_err);
++ goto err;
++ }
++ }
++ } else if (format == BACKUP_FORMAT_DIR) {
++ if (mkdir(backup_file, 0640) != 0) {
++ error_setg_errno(errp, errno, "can't create directory '%s'\n",
++ backup_file);
++ goto err;
++ }
++ backup_dir = backup_file;
++
++ l = di_list;
++ while (l) {
++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++ l = g_list_next(l);
++
++ const char *devname = bdrv_get_device_name(di->bs);
++ snprintf(di->targetfile, PATH_MAX, "%s/%s.raw", backup_dir, devname);
++
++ int flags = BDRV_O_RDWR;
++ bdrv_img_create(di->targetfile, "raw", NULL, NULL, NULL,
++ di->size, flags, false, &local_err);
++ if (local_err) {
++ error_propagate(errp, local_err);
++ goto err;
++ }
++
++ di->target = bdrv_open(di->targetfile, NULL, NULL, flags, &local_err);
++ if (!di->target) {
++ error_propagate(errp, local_err);
++ goto err;
++ }
++ }
++ } else {
++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
++ goto err;
++ }
++
++ /* add configuration file to archive */
++ if (has_config_file) {
++ if(config_to_vma(config_file, format, backup_state.vmaobj, backup_dir, errp) != 0) {
++ goto err;
++ }
++ }
++
++ /* add firewall file to archive */
++ if (has_firewall_file) {
++ if(config_to_vma(firewall_file, format, backup_state.vmaobj, backup_dir, errp) != 0) {
++ goto err;
++ }
++ }
++ /* initialize global backup_state now */
++
++ backup_state.cancel = false;
++
++ if (backup_state.error) {
++ error_free(backup_state.error);
++ backup_state.error = NULL;
++ }
++
++ backup_state.speed = (has_speed && speed > 0) ? speed : 0;
++
++ backup_state.start_time = time(NULL);
++ backup_state.end_time = 0;
++
++ if (backup_state.backup_file) {
++ g_free(backup_state.backup_file);
++ }
++ backup_state.backup_file = g_strdup(backup_file);
++
++ memcpy(&backup_state.uuid, &uuid, sizeof(uuid));
++ qemu_uuid_unparse(&uuid, backup_state.uuid_str);
++
++ qemu_mutex_lock(&backup_state.backup_mutex);
++ backup_state.di_list = di_list;
++ backup_state.next_job = 0;
++
++ backup_state.total = total;
++ backup_state.transferred = 0;
++ backup_state.zero_bytes = 0;
++
++ /* start all jobs (paused state) */
++ l = di_list;
++ while (l) {
++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++ l = g_list_next(l);
++
++ job = backup_job_create(NULL, di->bs, di->target, speed, MIRROR_SYNC_MODE_FULL, NULL,
++ false, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
++ BLOCK_JOB_DEFAULT,
++ pvebackup_complete_cb, di, 2, NULL, &local_err);
++ if (di->target) {
++ bdrv_unref(di->target);
++ di->target = NULL;
++ }
++ if (!job || local_err != NULL) {
++ error_setg(&backup_state.error, "backup_job_create failed");
++ pvebackup_cancel(NULL);
++ } else {
++ block_job_start(job);
++ }
++ }
++
++ qemu_mutex_unlock(&backup_state.backup_mutex);
++
++ if (!backup_state.error) {
++ pvebackup_run_next_job(); // run one job
++ }
++
++ uuid_info = g_malloc0(sizeof(*uuid_info));
++ uuid_info->UUID = g_strdup(backup_state.uuid_str);
++
++ return uuid_info;
++
++err:
++
++ l = di_list;
++ while (l) {
++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++ l = g_list_next(l);
++
++ if (di->target) {
++ bdrv_unref(di->target);
++ }
++
++ if (di->targetfile[0]) {
++ unlink(di->targetfile);
++ }
++ g_free(di);
++ }
++ g_list_free(di_list);
++
++ if (devs) {
++ g_strfreev(devs);
++ }
++
++ if (backup_state.vmaobj) {
++ object_unparent(backup_state.vmaobj);
++ backup_state.vmaobj = NULL;
++ }
++
++ if (backup_dir) {
++ rmdir(backup_dir);
++ }
++
++ return NULL;
++}
++
++BackupStatus *qmp_query_backup(Error **errp)
++{
++ BackupStatus *info = g_malloc0(sizeof(*info));
++
++ if (!backup_state.start_time) {
++ /* not started, return {} */
++ return info;
++ }
++
++ info->has_status = true;
++ info->has_start_time = true;
++ info->start_time = backup_state.start_time;
++
++ if (backup_state.backup_file) {
++ info->has_backup_file = true;
++ info->backup_file = g_strdup(backup_state.backup_file);
++ }
++
++ info->has_uuid = true;
++ info->uuid = g_strdup(backup_state.uuid_str);
++
++ if (backup_state.end_time) {
++ if (backup_state.error) {
++ info->status = g_strdup("error");
++ info->has_errmsg = true;
++ info->errmsg = g_strdup(error_get_pretty(backup_state.error));
++ } else {
++ info->status = g_strdup("done");
++ }
++ info->has_end_time = true;
++ info->end_time = backup_state.end_time;
++ } else {
++ info->status = g_strdup("active");
++ }
++
++ info->has_total = true;
++ info->total = backup_state.total;
++ info->has_zero_bytes = true;
++ info->zero_bytes = backup_state.zero_bytes;
++ info->has_transferred = true;
++ info->transferred = backup_state.transferred;
++
++ return info;
++}
++
+ void qmp_block_stream(bool has_job_id, const char *job_id, const char *device,
+ bool has_base, const char *base,
+ bool has_base_node, const char *base_node,
+diff --git a/blockjob.c b/blockjob.c
+index c1b6b6a810..2de9f8f4dd 100644
+--- a/blockjob.c
++++ b/blockjob.c
+@@ -149,7 +149,8 @@ static void block_job_pause(BlockJob *job)
+ job->pause_count++;
+ }
+
+-static void block_job_resume(BlockJob *job)
++void block_job_resume(BlockJob *job);
++void block_job_resume(BlockJob *job)
+ {
+ assert(job->pause_count > 0);
+ job->pause_count--;
+diff --git a/configure b/configure
+index 0c6e7572db..3a28a0a092 100755
+--- a/configure
++++ b/configure
+@@ -422,6 +422,7 @@ tcmalloc="no"
+ jemalloc="no"
+ replication="yes"
+ vxhs=""
++vma=""
+
+ supported_cpu="no"
+ supported_os="no"
+@@ -1313,6 +1314,10 @@ for opt do
+ ;;
+ --disable-git-update) git_update=no
+ ;;
++ --disable-vma) vma="no"
++ ;;
++ --enable-vma) vma="yes"
++ ;;
+ *)
+ echo "ERROR: unknown option $opt"
+ echo "Try '$0 --help' for more information"
+@@ -1561,6 +1566,7 @@ disabled with --disable-FEATURE, default is enabled if available:
+ crypto-afalg Linux AF_ALG crypto backend driver
+ vhost-user vhost-user support
+ capstone capstone disassembler support
++ vma VMA archive backend
+
+ NOTE: The object files are built at the place where configure is launched
+ EOF
+@@ -3890,6 +3896,23 @@ EOF
+ fi
+
+ ##########################################
++# vma probe
++if test "$vma" != "no" ; then
++ if $pkg_config --exact-version=0.1.0 vma; then
++ vma="yes"
++ vma_cflags=$($pkg_config --cflags vma)
++ vma_libs=$($pkg_config --libs vma)
++ else
++ if test "$vma" = "yes" ; then
++ feature_not_found "VMA Archive backend support" \
++ "Install libvma devel"
++ fi
++ vma="no"
++ fi
++fi
++
++
++##########################################
+ # signalfd probe
+ signalfd="no"
+ cat > $TMPC << EOF
+@@ -5555,6 +5578,7 @@ echo "avx2 optimization $avx2_opt"
+ echo "replication support $replication"
+ echo "VxHS block device $vxhs"
+ echo "capstone $capstone"
++echo "VMA support $vma"
+
+ if test "$sdl_too_old" = "yes"; then
+ echo "-> Your SDL version is too old - please upgrade to have SDL support"
+@@ -5998,6 +6022,12 @@ if test "$libusb" = "yes" ; then
+ echo "LIBUSB_LIBS=$libusb_libs" >> $config_host_mak
+ fi
+
++if test "$vma" = "yes" ; then
++ echo "CONFIG_VMA=y" >> $config_host_mak
++ echo "VMA_CFLAGS=$vma_cflags" >> $config_host_mak
++ echo "VMA_LIBS=$vma_libs" >> $config_host_mak
++fi
++
+ if test "$usb_redir" = "yes" ; then
+ echo "CONFIG_USB_REDIR=y" >> $config_host_mak
+ echo "USB_REDIR_CFLAGS=$usb_redir_cflags" >> $config_host_mak
+diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
+index 3bf69a193c..7beeb29e99 100644
+--- a/hmp-commands-info.hx
++++ b/hmp-commands-info.hx
+@@ -493,6 +493,19 @@ STEXI
+ Show CPU statistics.
+ ETEXI
+
++ {
++ .name = "backup",
++ .args_type = "",
++ .params = "",
++ .help = "show backup status",
++ .cmd = hmp_info_backup,
++ },
++
++STEXI
++@item info backup
++show backup status
++ETEXI
++
+ #if defined(CONFIG_SLIRP)
+ {
+ .name = "usernet",
+diff --git a/hmp-commands.hx b/hmp-commands.hx
+index b35bc6ab6c..9e50947845 100644
+--- a/hmp-commands.hx
++++ b/hmp-commands.hx
+@@ -87,6 +87,37 @@ STEXI
+ Copy data from a backing file into a block device.
+ ETEXI
+
++ {
++ .name = "backup",
++ .args_type = "directory:-d,backupfile:s,speed:o?,devlist:s?",
++ .params = "[-d] backupfile [speed [devlist]]",
++ .help = "create a VM Backup."
++ "\n\t\t\t Use -d to dump data into a directory instead"
++ "\n\t\t\t of using VMA format.",
++ .cmd = hmp_backup,
++ },
++
++STEXI
++@item backup
++@findex backup
++Create a VM backup.
++ETEXI
++
++ {
++ .name = "backup_cancel",
++ .args_type = "",
++ .params = "",
++ .help = "cancel the current VM backup",
++ .cmd = hmp_backup_cancel,
++ },
++
++STEXI
++@item backup_cancel
++@findex backup_cancel
++Cancel the current VM backup.
++
++ETEXI
++
+ {
+ .name = "block_job_set_speed",
+ .args_type = "device:B,speed:o",
+diff --git a/hmp.c b/hmp.c
+index b9ade681f0..241c2543ec 100644
+--- a/hmp.c
++++ b/hmp.c
+@@ -156,6 +156,44 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
+ qapi_free_MouseInfoList(mice_list);
+ }
+
++void hmp_info_backup(Monitor *mon, const QDict *qdict)
++{
++ BackupStatus *info;
++
++ info = qmp_query_backup(NULL);
++ if (info->has_status) {
++ if (info->has_errmsg) {
++ monitor_printf(mon, "Backup status: %s - %s\n",
++ info->status, info->errmsg);
++ } else {
++ monitor_printf(mon, "Backup status: %s\n", info->status);
++ }
++ }
++
++ if (info->has_backup_file) {
++ monitor_printf(mon, "Start time: %s", ctime(&info->start_time));
++ if (info->end_time) {
++ monitor_printf(mon, "End time: %s", ctime(&info->end_time));
++ }
++
++ int per = (info->has_total && info->total &&
++ info->has_transferred && info->transferred) ?
++ (info->transferred * 100)/info->total : 0;
++ int zero_per = (info->has_total && info->total &&
++ info->has_zero_bytes && info->zero_bytes) ?
++ (info->zero_bytes * 100)/info->total : 0;
++ monitor_printf(mon, "Backup file: %s\n", info->backup_file);
++ monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
++ monitor_printf(mon, "Total size: %zd\n", info->total);
++ monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
++ info->transferred, per);
++ monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
++ info->zero_bytes, zero_per);
++ }
++
++ qapi_free_BackupStatus(info);
++}
++
+ void hmp_info_migrate(Monitor *mon, const QDict *qdict)
+ {
+ MigrationInfo *info;
+@@ -1848,6 +1886,31 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict)
+ hmp_handle_error(mon, &error);
+ }
+
++void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
++{
++ Error *error = NULL;
++
++ qmp_backup_cancel(&error);
++
++ hmp_handle_error(mon, &error);
++}
++
++void hmp_backup(Monitor *mon, const QDict *qdict)
++{
++ Error *error = NULL;
++
++ int dir = qdict_get_try_bool(qdict, "directory", 0);
++ const char *backup_file = qdict_get_str(qdict, "backupfile");
++ const char *devlist = qdict_get_try_str(qdict, "devlist");
++ int64_t speed = qdict_get_try_int(qdict, "speed", 0);
++
++ qmp_backup(backup_file, true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
++ false, NULL, false, NULL, !!devlist,
++ devlist, qdict_haskey(qdict, "speed"), speed, &error);
++
++ hmp_handle_error(mon, &error);
++}
++
+ void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict)
+ {
+ Error *error = NULL;
+diff --git a/hmp.h b/hmp.h
+index 45ada581b6..635b9b4218 100644
+--- a/hmp.h
++++ b/hmp.h
+@@ -31,6 +31,7 @@ void hmp_info_migrate(Monitor *mon, const QDict *qdict);
+ void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict);
+ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict);
+ void hmp_info_migrate_cache_size(Monitor *mon, const QDict *qdict);
++void hmp_info_backup(Monitor *mon, const QDict *qdict);
+ void hmp_info_cpus(Monitor *mon, const QDict *qdict);
+ void hmp_info_block(Monitor *mon, const QDict *qdict);
+ void hmp_info_blockstats(Monitor *mon, const QDict *qdict);
+@@ -85,6 +86,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict);
+ void hmp_change(Monitor *mon, const QDict *qdict);
+ void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict);
+ void hmp_block_stream(Monitor *mon, const QDict *qdict);
++void hmp_backup(Monitor *mon, const QDict *qdict);
++void hmp_backup_cancel(Monitor *mon, const QDict *qdict);
+ void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict);
+ void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
+ void hmp_block_job_pause(Monitor *mon, const QDict *qdict);
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index dd763dcf87..461fca9a3d 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -615,6 +615,97 @@
+
+
+ ##
++# @BackupStatus:
++#
++# Detailed backup status.
++#
++# @status: string describing the current backup status.
++# This can be 'active', 'done', 'error'. If this field is not
++# returned, no backup process has been initiated
++#
++# @errmsg: error message (only returned if status is 'error')
++#
++# @total: total amount of bytes involved in the backup process
++#
++# @transferred: amount of bytes already backed up.
++#
++# @zero-bytes: amount of 'zero' bytes detected.
++#
++# @start-time: time (epoch) when backup job started.
++#
++# @end-time: time (epoch) when backup job finished.
++#
++# @backup-file: backup file name
++#
++# @uuid: uuid for this backup job
++#
++##
++{ 'struct': 'BackupStatus',
++ 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
++ '*transferred': 'int', '*zero-bytes': 'int',
++ '*start-time': 'int', '*end-time': 'int',
++ '*backup-file': 'str', '*uuid': 'str' } }
++
++##
++# @BackupFormat:
++#
++# An enumeration of supported backup formats.
++#
++# @vma: Proxmox vma backup format
++##
++{ 'enum': 'BackupFormat',
++ 'data': [ 'vma', 'dir' ] }
++
++##
++# @backup:
++#
++# Starts a VM backup.
++#
++# @backup-file: the backup file name
++#
++# @format: format of the backup file
++#
++# @config-file: a configuration file to include into
++# the backup archive.
++#
++# @speed: the maximum speed, in bytes per second
++#
++# @devlist: list of block device names (separated by ',', ';'
++# or ':'). By default the backup includes all writable block devices.
++#
++# Returns: the uuid of the backup job
++#
++##
++{ 'command': 'backup', 'data': { 'backup-file': 'str',
++ '*format': 'BackupFormat',
++ '*config-file': 'str',
++ '*firewall-file': 'str',
++ '*devlist': 'str', '*speed': 'int' },
++ 'returns': 'UuidInfo' }
++
++##
++# @query-backup:
++#
++# Returns information about current/last backup task.
++#
++# Returns: @BackupStatus
++#
++##
++{ 'command': 'query-backup', 'returns': 'BackupStatus' }
++
++##
++# @backup-cancel:
++#
++# Cancel the current executing backup process.
++#
++# Returns: nothing on success
++#
++# Notes: This command succeeds even if there is no backup process running.
++#
++##
++{ 'command': 'backup-cancel' }
++
++##
+ # @BlockDeviceTimedStats:
+ #
+ # Statistics of a block device during a given interval of time.
+@@ -2239,7 +2330,7 @@
+ 'host_device', 'http', 'https', 'iscsi', 'luks', 'nbd', 'nfs',
+ 'null-aio', 'null-co', 'parallels', 'qcow', 'qcow2', 'qed',
+ 'quorum', 'raw', 'rbd', 'replication', 'sheepdog', 'ssh',
+- 'throttle', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat', 'vxhs' ] }
++ 'throttle', 'vdi', 'vhdx', 'vma-drive', 'vmdk', 'vpc', 'vvfat', 'vxhs' ] }
+
+ ##
+ # @BlockdevOptionsFile:
+@@ -3116,6 +3207,21 @@
+ '*tls-creds': 'str' } }
+
+ ##
++# @BlockdevOptionsVMADrive:
++#
++# Driver specific block device options for VMA Drives
++#
++# @filename: vma-drive path
++#
++# @size: drive size in bytes
++#
++# Since: 2.9
++##
++{ 'struct': 'BlockdevOptionsVMADrive',
++ 'data': { 'filename': 'str',
++ 'size': 'int' } }
++
++##
+ # @BlockdevOptionsThrottle:
+ #
+ # Driver specific block device options for the throttle driver
+@@ -3196,6 +3302,7 @@
+ 'throttle': 'BlockdevOptionsThrottle',
+ 'vdi': 'BlockdevOptionsGenericFormat',
+ 'vhdx': 'BlockdevOptionsGenericFormat',
++ 'vma-drive': 'BlockdevOptionsVMADrive',
+ 'vmdk': 'BlockdevOptionsGenericCOWFormat',
+ 'vpc': 'BlockdevOptionsGenericFormat',
+ 'vvfat': 'BlockdevOptionsVVFAT',
+--
+2.11.0
+
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Wed, 9 Dec 2015 15:04:57 +0100
-Subject: [PATCH] backup: modify job api
-
-Introduce a pause_count parameter to start a backup in
-paused mode. This way backups of multiple drives can be
-started up sequentially via the completion callback while
-having been started at the same point in time.
----
- block/backup.c | 2 ++
- block/replication.c | 2 +-
- blockdev.c | 4 ++--
- blockjob.c | 2 +-
- include/block/block_int.h | 1 +
- 5 files changed, 7 insertions(+), 4 deletions(-)
-
-diff --git a/block/backup.c b/block/backup.c
-index a4fb2884f9..1ede70c061 100644
---- a/block/backup.c
-+++ b/block/backup.c
-@@ -558,6 +558,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
- BlockdevOnError on_target_error,
- int creation_flags,
- BlockCompletionFunc *cb, void *opaque,
-+ int pause_count,
- BlockJobTxn *txn, Error **errp)
- {
- int64_t len;
-@@ -682,6 +683,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
- block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
- &error_abort);
- job->common.len = len;
-+ job->common.pause_count = pause_count;
- block_job_txn_add_job(txn, &job->common);
-
- return &job->common;
-diff --git a/block/replication.c b/block/replication.c
-index bf3c395eb4..1c41d9e6bf 100644
---- a/block/replication.c
-+++ b/block/replication.c
-@@ -531,7 +531,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
- 0, MIRROR_SYNC_MODE_NONE, NULL, false,
- BLOCKDEV_ON_ERROR_REPORT,
- BLOCKDEV_ON_ERROR_REPORT, BLOCK_JOB_INTERNAL,
-- backup_job_completed, bs, NULL, &local_err);
-+ backup_job_completed, bs, 0, NULL, &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
- backup_job_cleanup(bs);
-diff --git a/blockdev.c b/blockdev.c
-index e8a9a65167..9b6cfafd33 100644
---- a/blockdev.c
-+++ b/blockdev.c
-@@ -3262,7 +3262,7 @@ static BlockJob *do_drive_backup(DriveBackup *backup, BlockJobTxn *txn,
- job = backup_job_create(backup->job_id, bs, target_bs, backup->speed,
- backup->sync, bmap, backup->compress,
- backup->on_source_error, backup->on_target_error,
-- BLOCK_JOB_DEFAULT, NULL, NULL, txn, &local_err);
-+ BLOCK_JOB_DEFAULT, NULL, NULL, 0, txn, &local_err);
- bdrv_unref(target_bs);
- if (local_err != NULL) {
- error_propagate(errp, local_err);
-@@ -3341,7 +3341,7 @@ BlockJob *do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn,
- job = backup_job_create(backup->job_id, bs, target_bs, backup->speed,
- backup->sync, NULL, backup->compress,
- backup->on_source_error, backup->on_target_error,
-- BLOCK_JOB_DEFAULT, NULL, NULL, txn, &local_err);
-+ BLOCK_JOB_DEFAULT, NULL, NULL, 0, txn, &local_err);
- if (local_err != NULL) {
- error_propagate(errp, local_err);
- }
-diff --git a/blockjob.c b/blockjob.c
-index 6e489327ff..764d41863e 100644
---- a/blockjob.c
-+++ b/blockjob.c
-@@ -289,7 +289,7 @@ void block_job_start(BlockJob *job)
- job->co = qemu_coroutine_create(block_job_co_entry, job);
- job->pause_count--;
- job->busy = true;
-- job->paused = false;
-+ job->paused = job->pause_count > 0;
- bdrv_coroutine_enter(blk_bs(job->blk), job->co);
- }
-
-diff --git a/include/block/block_int.h b/include/block/block_int.h
-index 89d7b458e7..19b84b027f 100644
---- a/include/block/block_int.h
-+++ b/include/block/block_int.h
-@@ -879,6 +879,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
- BlockdevOnError on_target_error,
- int creation_flags,
- BlockCompletionFunc *cb, void *opaque,
-+ int pause_count,
- BlockJobTxn *txn, Error **errp);
-
- void hmp_drive_add_node(Monitor *mon, const char *optstr);
---
-2.11.0
-
--- /dev/null
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bumiller <w.bumiller@proxmox.com>
+Date: Mon, 7 Aug 2017 08:51:16 +0200
+Subject: [PATCH] adding old vma files
+
+---
+ Makefile | 3 +-
+ Makefile.objs | 1 +
+ block/backup.c | 130 ++++---
+ block/replication.c | 1 +
+ blockdev.c | 207 +++++++----
+ blockjob.c | 3 +-
+ include/block/block_int.h | 4 +
+ vma-reader.c | 857 ++++++++++++++++++++++++++++++++++++++++++++++
+ vma-writer.c | 771 +++++++++++++++++++++++++++++++++++++++++
+ vma.c | 756 ++++++++++++++++++++++++++++++++++++++++
+ vma.h | 150 ++++++++
+ 11 files changed, 2760 insertions(+), 123 deletions(-)
+ create mode 100644 vma-reader.c
+ create mode 100644 vma-writer.c
+ create mode 100644 vma.c
+ create mode 100644 vma.h
+
+diff --git a/Makefile b/Makefile
+index ab0354c153..ad28227b6c 100644
+--- a/Makefile
++++ b/Makefile
+@@ -340,7 +340,7 @@ dummy := $(call unnest-vars,, \
+
+ include $(SRC_PATH)/tests/Makefile.include
+
+-all: $(DOCS) $(TOOLS) $(HELPERS-y) recurse-all modules
++all: $(DOCS) $(TOOLS) vma$(EXESUF) $(HELPERS-y) recurse-all modules
+
+ qemu-version.h: FORCE
+ $(call quiet-command, \
+@@ -439,6 +439,7 @@ qemu-img.o: qemu-img-cmds.h
+ qemu-img$(EXESUF): qemu-img.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
+ qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
+ qemu-io$(EXESUF): qemu-io.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
++vma$(EXESUF): vma.o vma-reader.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
+
+ qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o $(COMMON_LDADDS)
+
+diff --git a/Makefile.objs b/Makefile.objs
+index 686247b556..34e62547d8 100644
+--- a/Makefile.objs
++++ b/Makefile.objs
+@@ -14,6 +14,7 @@ block-obj-y += block.o blockjob.o
+ block-obj-y += block/ scsi/
+ block-obj-y += qemu-io-cmds.o
+ block-obj-$(CONFIG_REPLICATION) += replication.o
++block-obj-y += vma-writer.o
+
+ block-obj-m = block/
+
+diff --git a/block/backup.c b/block/backup.c
+index 8c2967a8cb..0870acdae7 100644
+--- a/block/backup.c
++++ b/block/backup.c
+@@ -36,6 +36,7 @@ typedef struct BackupBlockJob {
+ BdrvDirtyBitmap *sync_bitmap;
+ MirrorSyncMode sync_mode;
+ RateLimit limit;
++ BackupDumpFunc *dump_cb;
+ BlockdevOnError on_source_error;
+ BlockdevOnError on_target_error;
+ CoRwlock flush_rwlock;
+@@ -135,13 +136,24 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
+ goto out;
+ }
+
++
+ if (buffer_is_zero(iov.iov_base, iov.iov_len)) {
+- ret = blk_co_pwrite_zeroes(job->target, start,
+- bounce_qiov.size, BDRV_REQ_MAY_UNMAP);
++ if (job->dump_cb) {
++ ret = job->dump_cb(job->common.opaque, job->target, start, bounce_qiov.size, NULL);
++ }
++ if (job->target) {
++ ret = blk_co_pwrite_zeroes(job->target, start,
++ bounce_qiov.size, BDRV_REQ_MAY_UNMAP);
++ }
+ } else {
+- ret = blk_co_pwritev(job->target, start,
+- bounce_qiov.size, &bounce_qiov,
+- job->compress ? BDRV_REQ_WRITE_COMPRESSED : 0);
++ if (job->dump_cb) {
++ ret = job->dump_cb(job->common.opaque, job->target, start, bounce_qiov.size, bounce_buffer);
++ }
++ if (job->target) {
++ ret = blk_co_pwritev(job->target, start,
++ bounce_qiov.size, &bounce_qiov,
++ job->compress ? BDRV_REQ_WRITE_COMPRESSED : 0);
++ }
+ }
+ if (ret < 0) {
+ trace_backup_do_cow_write_fail(job, start, ret);
+@@ -234,7 +246,9 @@ static void backup_abort(BlockJob *job)
+ static void backup_clean(BlockJob *job)
+ {
+ BackupBlockJob *s = container_of(job, BackupBlockJob, common);
+- assert(s->target);
++ if (!s->target) {
++ return;
++ }
+ blk_unref(s->target);
+ s->target = NULL;
+ }
+@@ -243,7 +257,9 @@ static void backup_attached_aio_context(BlockJob *job, AioContext *aio_context)
+ {
+ BackupBlockJob *s = container_of(job, BackupBlockJob, common);
+
+- blk_set_aio_context(s->target, aio_context);
++ if (s->target) {
++ blk_set_aio_context(s->target, aio_context);
++ }
+ }
+
+ void backup_do_checkpoint(BlockJob *job, Error **errp)
+@@ -315,9 +331,11 @@ static BlockErrorAction backup_error_action(BackupBlockJob *job,
+ if (read) {
+ return block_job_error_action(&job->common, job->on_source_error,
+ true, error);
+- } else {
++ } else if (job->target) {
+ return block_job_error_action(&job->common, job->on_target_error,
+ false, error);
++ } else {
++ return BLOCK_ERROR_ACTION_REPORT;
+ }
+ }
+
+@@ -538,6 +556,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
+ BlockdevOnError on_source_error,
+ BlockdevOnError on_target_error,
+ int creation_flags,
++ BackupDumpFunc *dump_cb,
+ BlockCompletionFunc *cb, void *opaque,
+ int pause_count,
+ BlockJobTxn *txn, Error **errp)
+@@ -548,7 +567,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
+ int ret;
+
+ assert(bs);
+- assert(target);
++ assert(target || dump_cb);
+
+ if (bs == target) {
+ error_setg(errp, "Source and target cannot be the same");
+@@ -561,13 +580,13 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
+ return NULL;
+ }
+
+- if (!bdrv_is_inserted(target)) {
++ if (target && !bdrv_is_inserted(target)) {
+ error_setg(errp, "Device is not inserted: %s",
+ bdrv_get_device_name(target));
+ return NULL;
+ }
+
+- if (compress && target->drv->bdrv_co_pwritev_compressed == NULL) {
++ if (target && compress && target->drv->bdrv_co_pwritev_compressed == NULL) {
+ error_setg(errp, "Compression is not supported for this drive %s",
+ bdrv_get_device_name(target));
+ return NULL;
+@@ -577,7 +596,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
+ return NULL;
+ }
+
+- if (bdrv_op_is_blocked(target, BLOCK_OP_TYPE_BACKUP_TARGET, errp)) {
++ if (target && bdrv_op_is_blocked(target, BLOCK_OP_TYPE_BACKUP_TARGET, errp)) {
+ return NULL;
+ }
+
+@@ -617,15 +636,18 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
+ goto error;
+ }
+
+- /* The target must match the source in size, so no resize here either */
+- job->target = blk_new(BLK_PERM_WRITE,
+- BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE |
+- BLK_PERM_WRITE_UNCHANGED | BLK_PERM_GRAPH_MOD);
+- ret = blk_insert_bs(job->target, target, errp);
+- if (ret < 0) {
+- goto error;
++ if (target) {
++ /* The target must match the source in size, so no resize here either */
++ job->target = blk_new(BLK_PERM_WRITE,
++ BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE |
++ BLK_PERM_WRITE_UNCHANGED | BLK_PERM_GRAPH_MOD);
++ ret = blk_insert_bs(job->target, target, errp);
++ if (ret < 0) {
++ goto error;
++ }
+ }
+
++ job->dump_cb = dump_cb;
+ job->on_source_error = on_source_error;
+ job->on_target_error = on_target_error;
+ job->sync_mode = sync_mode;
+@@ -633,36 +655,52 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
+ sync_bitmap : NULL;
+ job->compress = compress;
+
+- /* If there is no backing file on the target, we cannot rely on COW if our
+- * backup cluster size is smaller than the target cluster size. Even for
+- * targets with a backing file, try to avoid COW if possible. */
+- ret = bdrv_get_info(target, &bdi);
+- if (ret == -ENOTSUP && !target->backing) {
+- /* Cluster size is not defined */
+- warn_report("The target block device doesn't provide "
+- "information about the block size and it doesn't have a "
+- "backing file. The default block size of %u bytes is "
+- "used. If the actual block size of the target exceeds "
+- "this default, the backup may be unusable",
+- BACKUP_CLUSTER_SIZE_DEFAULT);
+- job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
+- } else if (ret < 0 && !target->backing) {
+- error_setg_errno(errp, -ret,
+- "Couldn't determine the cluster size of the target image, "
+- "which has no backing file");
+- error_append_hint(errp,
+- "Aborting, since this may create an unusable destination image\n");
+- goto error;
+- } else if (ret < 0 && target->backing) {
+- /* Not fatal; just trudge on ahead. */
+- job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
++ if (target) {
++ /* If there is no backing file on the target, we cannot rely on COW if our
++ * backup cluster size is smaller than the target cluster size. Even for
++ * targets with a backing file, try to avoid COW if possible. */
++ ret = bdrv_get_info(target, &bdi);
++ if (ret == -ENOTSUP && !target->backing) {
++ /* Cluster size is not defined */
++ warn_report("The target block device doesn't provide "
++ "information about the block size and it doesn't have a "
++ "backing file. The default block size of %u bytes is "
++ "used. If the actual block size of the target exceeds "
++ "this default, the backup may be unusable",
++ BACKUP_CLUSTER_SIZE_DEFAULT);
++ job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
++ } else if (ret < 0 && !target->backing) {
++ error_setg_errno(errp, -ret,
++ "Couldn't determine the cluster size of the target image, "
++ "which has no backing file");
++ error_append_hint(errp,
++ "Aborting, since this may create an unusable destination image\n");
++ goto error;
++ } else if (ret < 0 && target->backing) {
++ /* Not fatal; just trudge on ahead. */
++ job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
++ } else {
++ job->cluster_size = MAX(BACKUP_CLUSTER_SIZE_DEFAULT, bdi.cluster_size);
++ }
+ } else {
+- job->cluster_size = MAX(BACKUP_CLUSTER_SIZE_DEFAULT, bdi.cluster_size);
++ ret = bdrv_get_info(bs, &bdi);
++ if (ret < 0) {
++ job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
++ } else {
++ /* round down to nearest BACKUP_CLUSTER_SIZE_DEFAULT */
++ job->cluster_size = (bdi.cluster_size / BACKUP_CLUSTER_SIZE_DEFAULT) * BACKUP_CLUSTER_SIZE_DEFAULT;
++ if (job->cluster_size == 0) {
++ /* but we can't go below it */
++ job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
++ }
++ }
+ }
+
+- /* Required permissions are already taken with target's blk_new() */
+- block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
+- &error_abort);
++ if (target) {
++ /* Required permissions are already taken with target's blk_new() */
++ block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
++ &error_abort);
++ }
+ job->common.len = len;
+ job->common.pause_count = pause_count;
+ block_job_txn_add_job(txn, &job->common);
+diff --git a/block/replication.c b/block/replication.c
+index 1b08b242eb..3d101ce6e6 100644
+--- a/block/replication.c
++++ b/block/replication.c
+@@ -561,6 +561,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
+ 0, MIRROR_SYNC_MODE_NONE, NULL, false,
+ BLOCKDEV_ON_ERROR_REPORT,
+ BLOCKDEV_ON_ERROR_REPORT, BLOCK_JOB_INTERNAL,
++ NULL,
+ backup_job_completed, bs, 0, NULL, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+diff --git a/blockdev.c b/blockdev.c
+index 3ffd064c48..4b6091afc6 100644
+--- a/blockdev.c
++++ b/blockdev.c
+@@ -31,7 +31,6 @@
+ */
+
+ #include "qemu/osdep.h"
+-#include "qemu/uuid.h"
+ #include "sysemu/block-backend.h"
+ #include "sysemu/blockdev.h"
+ #include "hw/block/block.h"
+@@ -55,6 +54,7 @@
+ #include "qemu/cutils.h"
+ #include "qemu/help_option.h"
+ #include "qemu/throttle-options.h"
++#include "vma.h"
+
+ static QTAILQ_HEAD(, BlockDriverState) monitor_bdrv_states =
+ QTAILQ_HEAD_INITIALIZER(monitor_bdrv_states);
+@@ -2970,15 +2970,14 @@ out:
+ static struct PVEBackupState {
+ Error *error;
+ bool cancel;
+- QemuUUID uuid;
++ uuid_t uuid;
+ char uuid_str[37];
+ int64_t speed;
+ time_t start_time;
+ time_t end_time;
+ char *backup_file;
+- Object *vmaobj;
++ VmaWriter *vmaw;
+ GList *di_list;
+- size_t next_job;
+ size_t total;
+ size_t transferred;
+ size_t zero_bytes;
+@@ -2997,6 +2996,71 @@ typedef struct PVEBackupDevInfo {
+
+ static void pvebackup_run_next_job(void);
+
++static int pvebackup_dump_cb(void *opaque, BlockBackend *target,
++ uint64_t start, uint64_t bytes,
++ const void *pbuf)
++{
++ const uint64_t size = bytes;
++ const unsigned char *buf = pbuf;
++ PVEBackupDevInfo *di = opaque;
++
++ if (backup_state.cancel) {
++ return size; // return success
++ }
++
++ uint64_t cluster_num = start / VMA_CLUSTER_SIZE;
++ if ((cluster_num * VMA_CLUSTER_SIZE) != start) {
++ if (!backup_state.error) {
++ error_setg(&backup_state.error,
++ "got unaligned write inside backup dump "
++ "callback (sector %ld)", start);
++ }
++ return -1; // not aligned to cluster size
++ }
++
++ int ret = -1;
++
++ if (backup_state.vmaw) {
++ size_t zero_bytes = 0;
++ uint64_t remaining = size;
++ while (remaining > 0) {
++ ret = vma_writer_write(backup_state.vmaw, di->dev_id, cluster_num,
++ buf, &zero_bytes);
++ ++cluster_num;
++ if (buf) {
++ buf += VMA_CLUSTER_SIZE;
++ }
++ if (ret < 0) {
++ if (!backup_state.error) {
++ vma_writer_error_propagate(backup_state.vmaw, &backup_state.error);
++ }
++ if (di->bs && di->bs->job) {
++ block_job_cancel(di->bs->job);
++ }
++ break;
++ } else {
++ backup_state.zero_bytes += zero_bytes;
++ if (remaining >= VMA_CLUSTER_SIZE) {
++ backup_state.transferred += VMA_CLUSTER_SIZE;
++ remaining -= VMA_CLUSTER_SIZE;
++ } else {
++ backup_state.transferred += remaining;
++ remaining = 0;
++ }
++ }
++ }
++ } else {
++ if (!buf) {
++ backup_state.zero_bytes += size;
++ }
++ backup_state.transferred += size;
++ }
++
++ // Note: always return success, because we want that writes succeed anyways.
++
++ return size;
++}
++
+ static void pvebackup_cleanup(void)
+ {
+ qemu_mutex_lock(&backup_state.backup_mutex);
+@@ -3008,9 +3072,11 @@ static void pvebackup_cleanup(void)
+
+ backup_state.end_time = time(NULL);
+
+- if (backup_state.vmaobj) {
+- object_unparent(backup_state.vmaobj);
+- backup_state.vmaobj = NULL;
++ if (backup_state.vmaw) {
++ Error *local_err = NULL;
++ vma_writer_close(backup_state.vmaw, &local_err);
++ error_propagate(&backup_state.error, local_err);
++ backup_state.vmaw = NULL;
+ }
+
+ g_list_free(backup_state.di_list);
+@@ -3018,6 +3084,13 @@ static void pvebackup_cleanup(void)
+ qemu_mutex_unlock(&backup_state.backup_mutex);
+ }
+
++static void coroutine_fn backup_close_vma_stream(void *opaque)
++{
++ PVEBackupDevInfo *di = opaque;
++
++ vma_writer_close_stream(backup_state.vmaw, di->dev_id);
++}
++
+ static void pvebackup_complete_cb(void *opaque, int ret)
+ {
+ // This always runs in the main loop
+@@ -3034,9 +3107,9 @@ static void pvebackup_complete_cb(void *opaque, int ret)
+ di->bs = NULL;
+ di->target = NULL;
+
+- if (backup_state.vmaobj) {
+- object_unparent(backup_state.vmaobj);
+- backup_state.vmaobj = NULL;
++ if (backup_state.vmaw) {
++ Coroutine *co = qemu_coroutine_create(backup_close_vma_stream, di);
++ qemu_coroutine_enter(co);
+ }
+
+ // remove self from job queue
+@@ -3064,14 +3137,9 @@ static void pvebackup_cancel(void *opaque)
+ error_setg(&backup_state.error, "backup cancelled");
+ }
+
+- if (backup_state.vmaobj) {
+- Error *err;
++ if (backup_state.vmaw) {
+ /* make sure vma writer does not block anymore */
+- if (!object_set_props(backup_state.vmaobj, &err, "blocked", "yes", NULL)) {
+- if (err) {
+- error_report_err(err);
+- }
+- }
++ vma_writer_set_error(backup_state.vmaw, "backup cancelled");
+ }
+
+ GList *l = backup_state.di_list;
+@@ -3102,18 +3170,14 @@ void qmp_backup_cancel(Error **errp)
+ Coroutine *co = qemu_coroutine_create(pvebackup_cancel, NULL);
+ qemu_coroutine_enter(co);
+
+- while (backup_state.vmaobj) {
+- /* FIXME: Find something better for this */
++ while (backup_state.vmaw) {
++ /* vma writer use main aio context */
+ aio_poll(qemu_get_aio_context(), true);
+ }
+ }
+
+-void vma_object_add_config_file(Object *obj, const char *name,
+- const char *contents, size_t len,
+- Error **errp);
+ static int config_to_vma(const char *file, BackupFormat format,
+- Object *vmaobj,
+- const char *backup_dir,
++ const char *backup_dir, VmaWriter *vmaw,
+ Error **errp)
+ {
+ char *cdata = NULL;
+@@ -3127,7 +3191,12 @@ static int config_to_vma(const char *file, BackupFormat format,
+ char *basename = g_path_get_basename(file);
+
+ if (format == BACKUP_FORMAT_VMA) {
+- vma_object_add_config_file(vmaobj, basename, cdata, clen, errp);
++ if (vma_writer_add_config(vmaw, basename, cdata, clen) != 0) {
++ error_setg(errp, "unable to add %s config data to vma archive", file);
++ g_free(cdata);
++ g_free(basename);
++ return 1;
++ }
+ } else if (format == BACKUP_FORMAT_DIR) {
+ char config_path[PATH_MAX];
+ snprintf(config_path, PATH_MAX, "%s/%s", backup_dir, basename);
+@@ -3145,28 +3214,30 @@ static int config_to_vma(const char *file, BackupFormat format,
+ }
+
+ void block_job_resume(BlockJob *job);
++bool block_job_should_pause(BlockJob *job);
+ static void pvebackup_run_next_job(void)
+ {
+ qemu_mutex_lock(&backup_state.backup_mutex);
+
+- GList *next = g_list_nth(backup_state.di_list, backup_state.next_job);
+- while (next) {
+- PVEBackupDevInfo *di = (PVEBackupDevInfo *)next->data;
+- backup_state.next_job++;
++ GList *l = backup_state.di_list;
++ while (l) {
++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
++ l = g_list_next(l);
+ if (!di->completed && di->bs && di->bs->job) {
+ BlockJob *job = di->bs->job;
+ AioContext *aio_context = blk_get_aio_context(job->blk);
+ aio_context_acquire(aio_context);
+ qemu_mutex_unlock(&backup_state.backup_mutex);
+- if (backup_state.error || backup_state.cancel) {
+- block_job_cancel_sync(job);
+- } else {
+- block_job_resume(job);
++ if (block_job_should_pause(job)) {
++ if (backup_state.error || backup_state.cancel) {
++ block_job_cancel_sync(job);
++ } else {
++ block_job_resume(job);
++ }
+ }
+ aio_context_release(aio_context);
+ return;
+ }
+- next = g_list_next(next);
+ }
+ qemu_mutex_unlock(&backup_state.backup_mutex);
+
+@@ -3177,7 +3248,7 @@ static void pvebackup_run_next_job(void)
+ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
+ BackupFormat format,
+ bool has_config_file, const char *config_file,
+- bool has_firewall_file, const char *firewall_file,
++ bool has_firewall_file, const char *firewall_file,
+ bool has_devlist, const char *devlist,
+ bool has_speed, int64_t speed, Error **errp)
+ {
+@@ -3185,7 +3256,8 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
+ BlockDriverState *bs = NULL;
+ const char *backup_dir = NULL;
+ Error *local_err = NULL;
+- QemuUUID uuid;
++ uuid_t uuid;
++ VmaWriter *vmaw = NULL;
+ gchar **devs = NULL;
+ GList *di_list = NULL;
+ GList *l;
+@@ -3197,7 +3269,7 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
+ backup_state.backup_mutex_initialized = true;
+ }
+
+- if (backup_state.di_list || backup_state.vmaobj) {
++ if (backup_state.di_list) {
+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+ "previous backup not finished");
+ return NULL;
+@@ -3272,40 +3344,28 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
+ total += size;
+ }
+
+- qemu_uuid_generate(&uuid);
++ uuid_generate(uuid);
+
+ if (format == BACKUP_FORMAT_VMA) {
+- char uuidstr[UUID_FMT_LEN+1];
+- qemu_uuid_unparse(&uuid, uuidstr);
+- uuidstr[UUID_FMT_LEN] = 0;
+- backup_state.vmaobj =
+- object_new_with_props("vma", object_get_objects_root(),
+- "vma-backup-obj", &local_err,
+- "filename", backup_file,
+- "uuid", uuidstr,
+- NULL);
+- if (!backup_state.vmaobj) {
++ vmaw = vma_writer_create(backup_file, uuid, &local_err);
++ if (!vmaw) {
+ if (local_err) {
+ error_propagate(errp, local_err);
+ }
+ goto err;
+ }
+
++ /* register all devices for vma writer */
+ l = di_list;
+ while (l) {
+- QDict *options = qdict_new();
+-
+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
+ l = g_list_next(l);
+
+ const char *devname = bdrv_get_device_name(di->bs);
+- snprintf(di->targetfile, PATH_MAX, "vma-backup-obj/%s.raw", devname);
+-
+- qdict_put(options, "driver", qstring_from_str("vma-drive"));
+- qdict_put(options, "size", qint_from_int(di->size));
+- di->target = bdrv_open(di->targetfile, NULL, options, BDRV_O_RDWR, &local_err);
+- if (!di->target) {
+- error_propagate(errp, local_err);
++ di->dev_id = vma_writer_register_stream(vmaw, devname, di->size);
++ if (di->dev_id <= 0) {
++ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
++ "register_stream failed");
+ goto err;
+ }
+ }
+@@ -3346,14 +3406,14 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
+
+ /* add configuration file to archive */
+ if (has_config_file) {
+- if(config_to_vma(config_file, format, backup_state.vmaobj, backup_dir, errp) != 0) {
++ if (config_to_vma(config_file, format, backup_dir, vmaw, errp) != 0) {
+ goto err;
+ }
+ }
+
+ /* add firewall file to archive */
+ if (has_firewall_file) {
+- if(config_to_vma(firewall_file, format, backup_state.vmaobj, backup_dir, errp) != 0) {
++ if (config_to_vma(firewall_file, format, backup_dir, vmaw, errp) != 0) {
+ goto err;
+ }
+ }
+@@ -3376,12 +3436,13 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
+ }
+ backup_state.backup_file = g_strdup(backup_file);
+
+- memcpy(&backup_state.uuid, &uuid, sizeof(uuid));
+- qemu_uuid_unparse(&uuid, backup_state.uuid_str);
++ backup_state.vmaw = vmaw;
++
++ uuid_copy(backup_state.uuid, uuid);
++ uuid_unparse_lower(uuid, backup_state.uuid_str);
+
+ qemu_mutex_lock(&backup_state.backup_mutex);
+ backup_state.di_list = di_list;
+- backup_state.next_job = 0;
+
+ backup_state.total = total;
+ backup_state.transferred = 0;
+@@ -3392,21 +3453,16 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
+ while (l) {
+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
+ l = g_list_next(l);
+-
+ job = backup_job_create(NULL, di->bs, di->target, speed, MIRROR_SYNC_MODE_FULL, NULL,
+ false, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
+ BLOCK_JOB_DEFAULT,
+- pvebackup_complete_cb, di, 2, NULL, &local_err);
+- if (di->target) {
+- bdrv_unref(di->target);
+- di->target = NULL;
+- }
++ pvebackup_dump_cb, pvebackup_complete_cb, di,
++ 2, NULL, &local_err);
+ if (!job || local_err != NULL) {
+ error_setg(&backup_state.error, "backup_job_create failed");
+ pvebackup_cancel(NULL);
+- } else {
+- block_job_start(job);
+ }
++ block_job_start(job);
+ }
+
+ qemu_mutex_unlock(&backup_state.backup_mutex);
+@@ -3442,9 +3498,10 @@ err:
+ g_strfreev(devs);
+ }
+
+- if (backup_state.vmaobj) {
+- object_unparent(backup_state.vmaobj);
+- backup_state.vmaobj = NULL;
++ if (vmaw) {
++ Error *err = NULL;
++ vma_writer_close(vmaw, &err);
++ unlink(backup_file);
+ }
+
+ if (backup_dir) {
+@@ -3829,7 +3886,7 @@ static BlockJob *do_drive_backup(DriveBackup *backup, BlockJobTxn *txn,
+ job = backup_job_create(backup->job_id, bs, target_bs, backup->speed,
+ backup->sync, bmap, backup->compress,
+ backup->on_source_error, backup->on_target_error,
+- BLOCK_JOB_DEFAULT, NULL, NULL, 0, txn, &local_err);
++ BLOCK_JOB_DEFAULT, NULL, NULL, NULL, 0, txn, &local_err);
+ bdrv_unref(target_bs);
+ if (local_err != NULL) {
+ error_propagate(errp, local_err);
+@@ -3908,7 +3965,7 @@ BlockJob *do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn,
+ job = backup_job_create(backup->job_id, bs, target_bs, backup->speed,
+ backup->sync, NULL, backup->compress,
+ backup->on_source_error, backup->on_target_error,
+- BLOCK_JOB_DEFAULT, NULL, NULL, 0, txn, &local_err);
++ BLOCK_JOB_DEFAULT, NULL, NULL, NULL, 0, txn, &local_err);
+ if (local_err != NULL) {
+ error_propagate(errp, local_err);
+ }
+diff --git a/blockjob.c b/blockjob.c
+index 2de9f8f4dd..1df33bd194 100644
+--- a/blockjob.c
++++ b/blockjob.c
+@@ -757,7 +757,8 @@ void block_job_completed(BlockJob *job, int ret)
+ }
+ }
+
+-static bool block_job_should_pause(BlockJob *job)
++bool block_job_should_pause(BlockJob *job);
++bool block_job_should_pause(BlockJob *job)
+ {
+ return job->pause_count > 0;
+ }
+diff --git a/include/block/block_int.h b/include/block/block_int.h
+index 1dbbdafd31..2ed3e41437 100644
+--- a/include/block/block_int.h
++++ b/include/block/block_int.h
+@@ -60,6 +60,9 @@
+
+ #define BLOCK_PROBE_BUF_SIZE 512
+
++typedef int BackupDumpFunc(void *opaque, BlockBackend *be,
++ uint64_t offset, uint64_t bytes, const void *buf);
++
+ enum BdrvTrackedRequestType {
+ BDRV_TRACKED_READ,
+ BDRV_TRACKED_WRITE,
+@@ -984,6 +987,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
+ BlockdevOnError on_source_error,
+ BlockdevOnError on_target_error,
+ int creation_flags,
++ BackupDumpFunc *dump_cb,
+ BlockCompletionFunc *cb, void *opaque,
+ int pause_count,
+ BlockJobTxn *txn, Error **errp);
+diff --git a/vma-reader.c b/vma-reader.c
+new file mode 100644
+index 0000000000..2000889bd3
+--- /dev/null
++++ b/vma-reader.c
+@@ -0,0 +1,857 @@
++/*
++ * VMA: Virtual Machine Archive
++ *
++ * Copyright (C) 2012 Proxmox Server Solutions
++ *
++ * Authors:
++ * Dietmar Maurer (dietmar@proxmox.com)
++ *
++ * This work is licensed under the terms of the GNU GPL, version 2 or later.
++ * See the COPYING file in the top-level directory.
++ *
++ */
++
++#include "qemu/osdep.h"
++#include <glib.h>
++#include <uuid/uuid.h>
++
++#include "qemu-common.h"
++#include "qemu/timer.h"
++#include "qemu/ratelimit.h"
++#include "vma.h"
++#include "block/block.h"
++#include "sysemu/block-backend.h"
++
++static unsigned char zero_vma_block[VMA_BLOCK_SIZE];
++
++typedef struct VmaRestoreState {
++ BlockBackend *target;
++ bool write_zeroes;
++ unsigned long *bitmap;
++ int bitmap_size;
++} VmaRestoreState;
++
++struct VmaReader {
++ int fd;
++ GChecksum *md5csum;
++ GHashTable *blob_hash;
++ unsigned char *head_data;
++ VmaDeviceInfo devinfo[256];
++ VmaRestoreState rstate[256];
++ GList *cdata_list;
++ guint8 vmstate_stream;
++ uint32_t vmstate_clusters;
++ /* to show restore percentage if run with -v */
++ time_t start_time;
++ int64_t cluster_count;
++ int64_t clusters_read;
++ int64_t zero_cluster_data;
++ int64_t partial_zero_cluster_data;
++ int clusters_read_per;
++};
++
++static guint
++g_int32_hash(gconstpointer v)
++{
++ return *(const uint32_t *)v;
++}
++
++static gboolean
++g_int32_equal(gconstpointer v1, gconstpointer v2)
++{
++ return *((const uint32_t *)v1) == *((const uint32_t *)v2);
++}
++
++static int vma_reader_get_bitmap(VmaRestoreState *rstate, int64_t cluster_num)
++{
++ assert(rstate);
++ assert(rstate->bitmap);
++
++ unsigned long val, idx, bit;
++
++ idx = cluster_num / BITS_PER_LONG;
++
++ assert(rstate->bitmap_size > idx);
++
++ bit = cluster_num % BITS_PER_LONG;
++ val = rstate->bitmap[idx];
++
++ return !!(val & (1UL << bit));
++}
++
++static void vma_reader_set_bitmap(VmaRestoreState *rstate, int64_t cluster_num,
++ int dirty)
++{
++ assert(rstate);
++ assert(rstate->bitmap);
++
++ unsigned long val, idx, bit;
++
++ idx = cluster_num / BITS_PER_LONG;
++
++ assert(rstate->bitmap_size > idx);
++
++ bit = cluster_num % BITS_PER_LONG;
++ val = rstate->bitmap[idx];
++ if (dirty) {
++ if (!(val & (1UL << bit))) {
++ val |= 1UL << bit;
++ }
++ } else {
++ if (val & (1UL << bit)) {
++ val &= ~(1UL << bit);
++ }
++ }
++ rstate->bitmap[idx] = val;
++}
++
++typedef struct VmaBlob {
++ uint32_t start;
++ uint32_t len;
++ void *data;
++} VmaBlob;
++
++static const VmaBlob *get_header_blob(VmaReader *vmar, uint32_t pos)
++{
++ assert(vmar);
++ assert(vmar->blob_hash);
++
++ return g_hash_table_lookup(vmar->blob_hash, &pos);
++}
++
++static const char *get_header_str(VmaReader *vmar, uint32_t pos)
++{
++ const VmaBlob *blob = get_header_blob(vmar, pos);
++ if (!blob) {
++ return NULL;
++ }
++ const char *res = (char *)blob->data;
++ if (res[blob->len-1] != '\0') {
++ return NULL;
++ }
++ return res;
++}
++
++static ssize_t
++safe_read(int fd, unsigned char *buf, size_t count)
++{
++ ssize_t n;
++
++ do {
++ n = read(fd, buf, count);
++ } while (n < 0 && errno == EINTR);
++
++ return n;
++}
++
++static ssize_t
++full_read(int fd, unsigned char *buf, size_t len)
++{
++ ssize_t n;
++ size_t total;
++
++ total = 0;
++
++ while (len > 0) {
++ n = safe_read(fd, buf, len);
++
++ if (n == 0) {
++ return total;
++ }
++
++ if (n <= 0) {
++ break;
++ }
++
++ buf += n;
++ total += n;
++ len -= n;
++ }
++
++ if (len) {
++ return -1;
++ }
++
++ return total;
++}
++
++void vma_reader_destroy(VmaReader *vmar)
++{
++ assert(vmar);
++
++ if (vmar->fd >= 0) {
++ close(vmar->fd);
++ }
++
++ if (vmar->cdata_list) {
++ g_list_free(vmar->cdata_list);
++ }
++
++ int i;
++ for (i = 1; i < 256; i++) {
++ if (vmar->rstate[i].bitmap) {
++ g_free(vmar->rstate[i].bitmap);
++ }
++ }
++
++ if (vmar->md5csum) {
++ g_checksum_free(vmar->md5csum);
++ }
++
++ if (vmar->blob_hash) {
++ g_hash_table_destroy(vmar->blob_hash);
++ }
++
++ if (vmar->head_data) {
++ g_free(vmar->head_data);
++ }
++
++ g_free(vmar);
++
++};
++
++static int vma_reader_read_head(VmaReader *vmar, Error **errp)
++{
++ assert(vmar);
++ assert(errp);
++ assert(*errp == NULL);
++
++ unsigned char md5sum[16];
++ int i;
++ int ret = 0;
++
++ vmar->head_data = g_malloc(sizeof(VmaHeader));
++
++ if (full_read(vmar->fd, vmar->head_data, sizeof(VmaHeader)) !=
++ sizeof(VmaHeader)) {
++ error_setg(errp, "can't read vma header - %s",
++ errno ? g_strerror(errno) : "got EOF");
++ return -1;
++ }
++
++ VmaHeader *h = (VmaHeader *)vmar->head_data;
++
++ if (h->magic != VMA_MAGIC) {
++ error_setg(errp, "not a vma file - wrong magic number");
++ return -1;
++ }
++
++ uint32_t header_size = GUINT32_FROM_BE(h->header_size);
++ int need = header_size - sizeof(VmaHeader);
++ if (need <= 0) {
++ error_setg(errp, "wrong vma header size %d", header_size);
++ return -1;
++ }
++
++ vmar->head_data = g_realloc(vmar->head_data, header_size);
++ h = (VmaHeader *)vmar->head_data;
++
++ if (full_read(vmar->fd, vmar->head_data + sizeof(VmaHeader), need) !=
++ need) {
++ error_setg(errp, "can't read vma header data - %s",
++ errno ? g_strerror(errno) : "got EOF");
++ return -1;
++ }
++
++ memcpy(md5sum, h->md5sum, 16);
++ memset(h->md5sum, 0, 16);
++
++ g_checksum_reset(vmar->md5csum);
++ g_checksum_update(vmar->md5csum, vmar->head_data, header_size);
++ gsize csize = 16;
++ g_checksum_get_digest(vmar->md5csum, (guint8 *)(h->md5sum), &csize);
++
++ if (memcmp(md5sum, h->md5sum, 16) != 0) {
++ error_setg(errp, "wrong vma header chechsum");
++ return -1;
++ }
++
++ /* we can modify header data after checksum verify */
++ h->header_size = header_size;
++
++ h->version = GUINT32_FROM_BE(h->version);
++ if (h->version != 1) {
++ error_setg(errp, "wrong vma version %d", h->version);
++ return -1;
++ }
++
++ h->ctime = GUINT64_FROM_BE(h->ctime);
++ h->blob_buffer_offset = GUINT32_FROM_BE(h->blob_buffer_offset);
++ h->blob_buffer_size = GUINT32_FROM_BE(h->blob_buffer_size);
++
++ uint32_t bstart = h->blob_buffer_offset + 1;
++ uint32_t bend = h->blob_buffer_offset + h->blob_buffer_size;
++
++ if (bstart <= sizeof(VmaHeader)) {
++ error_setg(errp, "wrong vma blob buffer offset %d",
++ h->blob_buffer_offset);
++ return -1;
++ }
++
++ if (bend > header_size) {
++ error_setg(errp, "wrong vma blob buffer size %d/%d",
++ h->blob_buffer_offset, h->blob_buffer_size);
++ return -1;
++ }
++
++ while ((bstart + 2) <= bend) {
++ uint32_t size = vmar->head_data[bstart] +
++ (vmar->head_data[bstart+1] << 8);
++ if ((bstart + size + 2) <= bend) {
++ VmaBlob *blob = g_new0(VmaBlob, 1);
++ blob->start = bstart - h->blob_buffer_offset;
++ blob->len = size;
++ blob->data = vmar->head_data + bstart + 2;
++ g_hash_table_insert(vmar->blob_hash, &blob->start, blob);
++ }
++ bstart += size + 2;
++ }
++
++
++ int count = 0;
++ for (i = 1; i < 256; i++) {
++ VmaDeviceInfoHeader *dih = &h->dev_info[i];
++ uint32_t devname_ptr = GUINT32_FROM_BE(dih->devname_ptr);
++ uint64_t size = GUINT64_FROM_BE(dih->size);
++ const char *devname = get_header_str(vmar, devname_ptr);
++
++ if (size && devname) {
++ count++;
++ vmar->devinfo[i].size = size;
++ vmar->devinfo[i].devname = devname;
++
++ if (strcmp(devname, "vmstate") == 0) {
++ vmar->vmstate_stream = i;
++ }
++ }
++ }
++
++ for (i = 0; i < VMA_MAX_CONFIGS; i++) {
++ uint32_t name_ptr = GUINT32_FROM_BE(h->config_names[i]);
++ uint32_t data_ptr = GUINT32_FROM_BE(h->config_data[i]);
++
++ if (!(name_ptr && data_ptr)) {
++ continue;
++ }
++ const char *name = get_header_str(vmar, name_ptr);
++ const VmaBlob *blob = get_header_blob(vmar, data_ptr);
++
++ if (!(name && blob)) {
++ error_setg(errp, "vma contains invalid data pointers");
++ return -1;
++ }
++
++ VmaConfigData *cdata = g_new0(VmaConfigData, 1);
++ cdata->name = name;
++ cdata->data = blob->data;
++ cdata->len = blob->len;
++
++ vmar->cdata_list = g_list_append(vmar->cdata_list, cdata);
++ }
++
++ return ret;
++};
++
++VmaReader *vma_reader_create(const char *filename, Error **errp)
++{
++ assert(filename);
++ assert(errp);
++
++ VmaReader *vmar = g_new0(VmaReader, 1);
++
++ if (strcmp(filename, "-") == 0) {
++ vmar->fd = dup(0);
++ } else {
++ vmar->fd = open(filename, O_RDONLY);
++ }
++
++ if (vmar->fd < 0) {
++ error_setg(errp, "can't open file %s - %s\n", filename,
++ g_strerror(errno));
++ goto err;
++ }
++
++ vmar->md5csum = g_checksum_new(G_CHECKSUM_MD5);
++ if (!vmar->md5csum) {
++ error_setg(errp, "can't allocate cmsum\n");
++ goto err;
++ }
++
++ vmar->blob_hash = g_hash_table_new_full(g_int32_hash, g_int32_equal,
++ NULL, g_free);
++
++ if (vma_reader_read_head(vmar, errp) < 0) {
++ goto err;
++ }
++
++ return vmar;
++
++err:
++ if (vmar) {
++ vma_reader_destroy(vmar);
++ }
++
++ return NULL;
++}
++
++VmaHeader *vma_reader_get_header(VmaReader *vmar)
++{
++ assert(vmar);
++ assert(vmar->head_data);
++
++ return (VmaHeader *)(vmar->head_data);
++}
++
++GList *vma_reader_get_config_data(VmaReader *vmar)
++{
++ assert(vmar);
++ assert(vmar->head_data);
++
++ return vmar->cdata_list;
++}
++
++VmaDeviceInfo *vma_reader_get_device_info(VmaReader *vmar, guint8 dev_id)
++{
++ assert(vmar);
++ assert(dev_id);
++
++ if (vmar->devinfo[dev_id].size && vmar->devinfo[dev_id].devname) {
++ return &vmar->devinfo[dev_id];
++ }
++
++ return NULL;
++}
++
++static void allocate_rstate(VmaReader *vmar, guint8 dev_id,
++ BlockBackend *target, bool write_zeroes)
++{
++ assert(vmar);
++ assert(dev_id);
++
++ vmar->rstate[dev_id].target = target;
++ vmar->rstate[dev_id].write_zeroes = write_zeroes;
++
++ int64_t size = vmar->devinfo[dev_id].size;
++
++ int64_t bitmap_size = (size/BDRV_SECTOR_SIZE) +
++ (VMA_CLUSTER_SIZE/BDRV_SECTOR_SIZE) * BITS_PER_LONG - 1;
++ bitmap_size /= (VMA_CLUSTER_SIZE/BDRV_SECTOR_SIZE) * BITS_PER_LONG;
++
++ vmar->rstate[dev_id].bitmap_size = bitmap_size;
++ vmar->rstate[dev_id].bitmap = g_new0(unsigned long, bitmap_size);
++
++ vmar->cluster_count += size/VMA_CLUSTER_SIZE;
++}
++
++int vma_reader_register_bs(VmaReader *vmar, guint8 dev_id, BlockBackend *target,
++ bool write_zeroes, Error **errp)
++{
++ assert(vmar);
++ assert(target != NULL);
++ assert(dev_id);
++ assert(vmar->rstate[dev_id].target == NULL);
++
++ int64_t size = blk_getlength(target);
++ int64_t size_diff = size - vmar->devinfo[dev_id].size;
++
++ /* storage types can have different size restrictions, so it
++ * is not always possible to create an image with exact size.
++ * So we tolerate a size difference up to 4MB.
++ */
++ if ((size_diff < 0) || (size_diff > 4*1024*1024)) {
++ error_setg(errp, "vma_reader_register_bs for stream %s failed - "
++ "unexpected size %zd != %zd", vmar->devinfo[dev_id].devname,
++ size, vmar->devinfo[dev_id].size);
++ return -1;
++ }
++
++ allocate_rstate(vmar, dev_id, target, write_zeroes);
++
++ return 0;
++}
++
++static ssize_t safe_write(int fd, void *buf, size_t count)
++{
++ ssize_t n;
++
++ do {
++ n = write(fd, buf, count);
++ } while (n < 0 && errno == EINTR);
++
++ return n;
++}
++
++static size_t full_write(int fd, void *buf, size_t len)
++{
++ ssize_t n;
++ size_t total;
++
++ total = 0;
++
++ while (len > 0) {
++ n = safe_write(fd, buf, len);
++ if (n < 0) {
++ return n;
++ }
++ buf += n;
++ total += n;
++ len -= n;
++ }
++
++ if (len) {
++ /* incomplete write ? */
++ return -1;
++ }
++
++ return total;
++}
++
++static int restore_write_data(VmaReader *vmar, guint8 dev_id,
++ BlockBackend *target, int vmstate_fd,
++ unsigned char *buf, int64_t sector_num,
++ int nb_sectors, Error **errp)
++{
++ assert(vmar);
++
++ if (dev_id == vmar->vmstate_stream) {
++ if (vmstate_fd >= 0) {
++ int len = nb_sectors * BDRV_SECTOR_SIZE;
++ int res = full_write(vmstate_fd, buf, len);
++ if (res < 0) {
++ error_setg(errp, "write vmstate failed %d", res);
++ return -1;
++ }
++ }
++ } else {
++ int res = blk_pwrite(target, sector_num * BDRV_SECTOR_SIZE, buf, nb_sectors * BDRV_SECTOR_SIZE, 0);
++ if (res < 0) {
++ error_setg(errp, "blk_pwrite to %s failed (%d)",
++ bdrv_get_device_name(blk_bs(target)), res);
++ return -1;
++ }
++ }
++ return 0;
++}
++
++static int restore_extent(VmaReader *vmar, unsigned char *buf,
++ int extent_size, int vmstate_fd,
++ bool verbose, bool verify, Error **errp)
++{
++ assert(vmar);
++ assert(buf);
++
++ VmaExtentHeader *ehead = (VmaExtentHeader *)buf;
++ int start = VMA_EXTENT_HEADER_SIZE;
++ int i;
++
++ for (i = 0; i < VMA_BLOCKS_PER_EXTENT; i++) {
++ uint64_t block_info = GUINT64_FROM_BE(ehead->blockinfo[i]);
++ uint64_t cluster_num = block_info & 0xffffffff;
++ uint8_t dev_id = (block_info >> 32) & 0xff;
++ uint16_t mask = block_info >> (32+16);
++ int64_t max_sector;
++
++ if (!dev_id) {
++ continue;
++ }
++
++ VmaRestoreState *rstate = &vmar->rstate[dev_id];
++ BlockBackend *target = NULL;
++
++ if (dev_id != vmar->vmstate_stream) {
++ target = rstate->target;
++ if (!verify && !target) {
++ error_setg(errp, "got wrong dev id %d", dev_id);
++ return -1;
++ }
++
++ if (vma_reader_get_bitmap(rstate, cluster_num)) {
++ error_setg(errp, "found duplicated cluster %zd for stream %s",
++ cluster_num, vmar->devinfo[dev_id].devname);
++ return -1;
++ }
++ vma_reader_set_bitmap(rstate, cluster_num, 1);
++
++ max_sector = vmar->devinfo[dev_id].size/BDRV_SECTOR_SIZE;
++ } else {
++ max_sector = G_MAXINT64;
++ if (cluster_num != vmar->vmstate_clusters) {
++ error_setg(errp, "found out of order vmstate data");
++ return -1;
++ }
++ vmar->vmstate_clusters++;
++ }
++
++ vmar->clusters_read++;
++
++ if (verbose) {
++ time_t duration = time(NULL) - vmar->start_time;
++ int percent = (vmar->clusters_read*100)/vmar->cluster_count;
++ if (percent != vmar->clusters_read_per) {
++ printf("progress %d%% (read %zd bytes, duration %zd sec)\n",
++ percent, vmar->clusters_read*VMA_CLUSTER_SIZE,
++ duration);
++ fflush(stdout);
++ vmar->clusters_read_per = percent;
++ }
++ }
++
++ /* try to write whole clusters to speedup restore */
++ if (mask == 0xffff) {
++ if ((start + VMA_CLUSTER_SIZE) > extent_size) {
++ error_setg(errp, "short vma extent - too many blocks");
++ return -1;
++ }
++ int64_t sector_num = (cluster_num * VMA_CLUSTER_SIZE) /
++ BDRV_SECTOR_SIZE;
++ int64_t end_sector = sector_num +
++ VMA_CLUSTER_SIZE/BDRV_SECTOR_SIZE;
++
++ if (end_sector > max_sector) {
++ end_sector = max_sector;
++ }
++
++ if (end_sector <= sector_num) {
++ error_setg(errp, "got wrong block address - write bejond end");
++ return -1;
++ }
++
++ if (!verify) {
++ int nb_sectors = end_sector - sector_num;
++ if (restore_write_data(vmar, dev_id, target, vmstate_fd,
++ buf + start, sector_num, nb_sectors,
++ errp) < 0) {
++ return -1;
++ }
++ }
++
++ start += VMA_CLUSTER_SIZE;
++ } else {
++ int j;
++ int bit = 1;
++
++ for (j = 0; j < 16; j++) {
++ int64_t sector_num = (cluster_num*VMA_CLUSTER_SIZE +
++ j*VMA_BLOCK_SIZE)/BDRV_SECTOR_SIZE;
++
++ int64_t end_sector = sector_num +
++ VMA_BLOCK_SIZE/BDRV_SECTOR_SIZE;
++ if (end_sector > max_sector) {
++ end_sector = max_sector;
++ }
++
++ if (mask & bit) {
++ if ((start + VMA_BLOCK_SIZE) > extent_size) {
++ error_setg(errp, "short vma extent - too many blocks");
++ return -1;
++ }
++
++ if (end_sector <= sector_num) {
++ error_setg(errp, "got wrong block address - "
++ "write bejond end");
++ return -1;
++ }
++
++ if (!verify) {
++ int nb_sectors = end_sector - sector_num;
++ if (restore_write_data(vmar, dev_id, target, vmstate_fd,
++ buf + start, sector_num,
++ nb_sectors, errp) < 0) {
++ return -1;
++ }
++ }
++
++ start += VMA_BLOCK_SIZE;
++
++ } else {
++
++
++ if (end_sector > sector_num) {
++ /* Todo: use bdrv_co_write_zeroes (but that need to
++ * be run inside coroutine?)
++ */
++ int nb_sectors = end_sector - sector_num;
++ int zero_size = BDRV_SECTOR_SIZE*nb_sectors;
++ vmar->zero_cluster_data += zero_size;
++ if (mask != 0) {
++ vmar->partial_zero_cluster_data += zero_size;
++ }
++
++ if (rstate->write_zeroes && !verify) {
++ if (restore_write_data(vmar, dev_id, target, vmstate_fd,
++ zero_vma_block, sector_num,
++ nb_sectors, errp) < 0) {
++ return -1;
++ }
++ }
++ }
++ }
++
++ bit = bit << 1;
++ }
++ }
++ }
++
++ if (start != extent_size) {
++ error_setg(errp, "vma extent error - missing blocks");
++ return -1;
++ }
++
++ return 0;
++}
++
++static int vma_reader_restore_full(VmaReader *vmar, int vmstate_fd,
++ bool verbose, bool verify,
++ Error **errp)
++{
++ assert(vmar);
++ assert(vmar->head_data);
++
++ int ret = 0;
++ unsigned char buf[VMA_MAX_EXTENT_SIZE];
++ int buf_pos = 0;
++ unsigned char md5sum[16];
++ VmaHeader *h = (VmaHeader *)vmar->head_data;
++
++ vmar->start_time = time(NULL);
++
++ while (1) {
++ int bytes = full_read(vmar->fd, buf + buf_pos, sizeof(buf) - buf_pos);
++ if (bytes < 0) {
++ error_setg(errp, "read failed - %s", g_strerror(errno));
++ return -1;
++ }
++
++ buf_pos += bytes;
++
++ if (!buf_pos) {
++ break; /* EOF */
++ }
++
++ if (buf_pos < VMA_EXTENT_HEADER_SIZE) {
++ error_setg(errp, "read short extent (%d bytes)", buf_pos);
++ return -1;
++ }
++
++ VmaExtentHeader *ehead = (VmaExtentHeader *)buf;
++
++ /* extract md5sum */
++ memcpy(md5sum, ehead->md5sum, sizeof(ehead->md5sum));
++ memset(ehead->md5sum, 0, sizeof(ehead->md5sum));
++
++ g_checksum_reset(vmar->md5csum);
++ g_checksum_update(vmar->md5csum, buf, VMA_EXTENT_HEADER_SIZE);
++ gsize csize = 16;
++ g_checksum_get_digest(vmar->md5csum, ehead->md5sum, &csize);
++
++ if (memcmp(md5sum, ehead->md5sum, 16) != 0) {
++ error_setg(errp, "wrong vma extent header chechsum");
++ return -1;
++ }
++
++ if (memcmp(h->uuid, ehead->uuid, sizeof(ehead->uuid)) != 0) {
++ error_setg(errp, "wrong vma extent uuid");
++ return -1;
++ }
++
++ if (ehead->magic != VMA_EXTENT_MAGIC || ehead->reserved1 != 0) {
++ error_setg(errp, "wrong vma extent header magic");
++ return -1;
++ }
++
++ int block_count = GUINT16_FROM_BE(ehead->block_count);
++ int extent_size = VMA_EXTENT_HEADER_SIZE + block_count*VMA_BLOCK_SIZE;
++
++ if (buf_pos < extent_size) {
++ error_setg(errp, "short vma extent (%d < %d)", buf_pos,
++ extent_size);
++ return -1;
++ }
++
++ if (restore_extent(vmar, buf, extent_size, vmstate_fd, verbose,
++ verify, errp) < 0) {
++ return -1;
++ }
++
++ if (buf_pos > extent_size) {
++ memmove(buf, buf + extent_size, buf_pos - extent_size);
++ buf_pos = buf_pos - extent_size;
++ } else {
++ buf_pos = 0;
++ }
++ }
++
++ bdrv_drain_all();
++
++ int i;
++ for (i = 1; i < 256; i++) {
++ VmaRestoreState *rstate = &vmar->rstate[i];
++ if (!rstate->target) {
++ continue;
++ }
++
++ if (blk_flush(rstate->target) < 0) {
++ error_setg(errp, "vma blk_flush %s failed",
++ vmar->devinfo[i].devname);
++ return -1;
++ }
++
++ if (vmar->devinfo[i].size &&
++ (strcmp(vmar->devinfo[i].devname, "vmstate") != 0)) {
++ assert(rstate->bitmap);
++
++ int64_t cluster_num, end;
++
++ end = (vmar->devinfo[i].size + VMA_CLUSTER_SIZE - 1) /
++ VMA_CLUSTER_SIZE;
++
++ for (cluster_num = 0; cluster_num < end; cluster_num++) {
++ if (!vma_reader_get_bitmap(rstate, cluster_num)) {
++ error_setg(errp, "detected missing cluster %zd "
++ "for stream %s", cluster_num,
++ vmar->devinfo[i].devname);
++ return -1;
++ }
++ }
++ }
++ }
++
++ if (verbose) {
++ if (vmar->clusters_read) {
++ printf("total bytes read %zd, sparse bytes %zd (%.3g%%)\n",
++ vmar->clusters_read*VMA_CLUSTER_SIZE,
++ vmar->zero_cluster_data,
++ (double)(100.0*vmar->zero_cluster_data)/
++ (vmar->clusters_read*VMA_CLUSTER_SIZE));
++
++ int64_t datasize = vmar->clusters_read*VMA_CLUSTER_SIZE-vmar->zero_cluster_data;
++ if (datasize) { // this does not make sense for empty files
++ printf("space reduction due to 4K zero blocks %.3g%%\n",
++ (double)(100.0*vmar->partial_zero_cluster_data) / datasize);
++ }
++ } else {
++ printf("vma archive contains no image data\n");
++ }
++ }
++ return ret;
++}
++
++int vma_reader_restore(VmaReader *vmar, int vmstate_fd, bool verbose,
++ Error **errp)
++{
++ return vma_reader_restore_full(vmar, vmstate_fd, verbose, false, errp);
++}
++
++int vma_reader_verify(VmaReader *vmar, bool verbose, Error **errp)
++{
++ guint8 dev_id;
++
++ for (dev_id = 1; dev_id < 255; dev_id++) {
++ if (vma_reader_get_device_info(vmar, dev_id)) {
++ allocate_rstate(vmar, dev_id, NULL, false);
++ }
++ }
++
++ return vma_reader_restore_full(vmar, -1, verbose, true, errp);
++}
++
+diff --git a/vma-writer.c b/vma-writer.c
+new file mode 100644
+index 0000000000..fd9567634d
+--- /dev/null
++++ b/vma-writer.c
+@@ -0,0 +1,771 @@
++/*
++ * VMA: Virtual Machine Archive
++ *
++ * Copyright (C) 2012 Proxmox Server Solutions
++ *
++ * Authors:
++ * Dietmar Maurer (dietmar@proxmox.com)
++ *
++ * This work is licensed under the terms of the GNU GPL, version 2 or later.
++ * See the COPYING file in the top-level directory.
++ *
++ */
++
++#include "qemu/osdep.h"
++#include <glib.h>
++#include <uuid/uuid.h>
++
++#include "vma.h"
++#include "block/block.h"
++#include "monitor/monitor.h"
++#include "qemu/main-loop.h"
++#include "qemu/coroutine.h"
++#include "qemu/cutils.h"
++
++#define DEBUG_VMA 0
++
++#define DPRINTF(fmt, ...)\
++ do { if (DEBUG_VMA) { printf("vma: " fmt, ## __VA_ARGS__); } } while (0)
++
++#define WRITE_BUFFERS 5
++#define HEADER_CLUSTERS 8
++#define HEADERBUF_SIZE (VMA_CLUSTER_SIZE*HEADER_CLUSTERS)
++
++struct VmaWriter {
++ int fd;
++ FILE *cmd;
++ int status;
++ char errmsg[8192];
++ uuid_t uuid;
++ bool header_written;
++ bool closed;
++
++ /* we always write extents */
++ unsigned char *outbuf;
++ int outbuf_pos; /* in bytes */
++ int outbuf_count; /* in VMA_BLOCKS */
++ uint64_t outbuf_block_info[VMA_BLOCKS_PER_EXTENT];
++
++ unsigned char *headerbuf;
++
++ GChecksum *md5csum;
++ CoMutex flush_lock;
++ Coroutine *co_writer;
++
++ /* drive informations */
++ VmaStreamInfo stream_info[256];
++ guint stream_count;
++
++ guint8 vmstate_stream;
++ uint32_t vmstate_clusters;
++
++ /* header blob table */
++ char *header_blob_table;
++ uint32_t header_blob_table_size;
++ uint32_t header_blob_table_pos;
++
++ /* store for config blobs */
++ uint32_t config_names[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
++ uint32_t config_data[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
++ uint32_t config_count;
++};
++
++void vma_writer_set_error(VmaWriter *vmaw, const char *fmt, ...)
++{
++ va_list ap;
++
++ if (vmaw->status < 0) {
++ return;
++ }
++
++ vmaw->status = -1;
++
++ va_start(ap, fmt);
++ g_vsnprintf(vmaw->errmsg, sizeof(vmaw->errmsg), fmt, ap);
++ va_end(ap);
++
++ DPRINTF("vma_writer_set_error: %s\n", vmaw->errmsg);
++}
++
++static uint32_t allocate_header_blob(VmaWriter *vmaw, const char *data,
++ size_t len)
++{
++ if (len > 65535) {
++ return 0;
++ }
++
++ if (!vmaw->header_blob_table ||
++ (vmaw->header_blob_table_size <
++ (vmaw->header_blob_table_pos + len + 2))) {
++ int newsize = vmaw->header_blob_table_size + ((len + 2 + 511)/512)*512;
++
++ vmaw->header_blob_table = g_realloc(vmaw->header_blob_table, newsize);
++ memset(vmaw->header_blob_table + vmaw->header_blob_table_size,
++ 0, newsize - vmaw->header_blob_table_size);
++ vmaw->header_blob_table_size = newsize;
++ }
++
++ uint32_t cpos = vmaw->header_blob_table_pos;
++ vmaw->header_blob_table[cpos] = len & 255;
++ vmaw->header_blob_table[cpos+1] = (len >> 8) & 255;
++ memcpy(vmaw->header_blob_table + cpos + 2, data, len);
++ vmaw->header_blob_table_pos += len + 2;
++ return cpos;
++}
++
++static uint32_t allocate_header_string(VmaWriter *vmaw, const char *str)
++{
++ assert(vmaw);
++
++ size_t len = strlen(str) + 1;
++
++ return allocate_header_blob(vmaw, str, len);
++}
++
++int vma_writer_add_config(VmaWriter *vmaw, const char *name, gpointer data,
++ gsize len)
++{
++ assert(vmaw);
++ assert(!vmaw->header_written);
++ assert(vmaw->config_count < VMA_MAX_CONFIGS);
++ assert(name);
++ assert(data);
++
++ gchar *basename = g_path_get_basename(name);
++ uint32_t name_ptr = allocate_header_string(vmaw, basename);
++ g_free(basename);
++
++ if (!name_ptr) {
++ return -1;
++ }
++
++ uint32_t data_ptr = allocate_header_blob(vmaw, data, len);
++ if (!data_ptr) {
++ return -1;
++ }
++
++ vmaw->config_names[vmaw->config_count] = name_ptr;
++ vmaw->config_data[vmaw->config_count] = data_ptr;
++
++ vmaw->config_count++;
++
++ return 0;
++}
++
++int vma_writer_register_stream(VmaWriter *vmaw, const char *devname,
++ size_t size)
++{
++ assert(vmaw);
++ assert(devname);
++ assert(!vmaw->status);
++
++ if (vmaw->header_written) {
++ vma_writer_set_error(vmaw, "vma_writer_register_stream: header "
++ "already written");
++ return -1;
++ }
++
++ guint n = vmaw->stream_count + 1;
++
++ /* we can have dev_ids form 1 to 255 (0 reserved)
++ * 255(-1) reseverd for safety
++ */
++ if (n > 254) {
++ vma_writer_set_error(vmaw, "vma_writer_register_stream: "
++ "too many drives");
++ return -1;
++ }
++
++ if (size <= 0) {
++ vma_writer_set_error(vmaw, "vma_writer_register_stream: "
++ "got strange size %zd", size);
++ return -1;
++ }
++
++ DPRINTF("vma_writer_register_stream %s %zu %d\n", devname, size, n);
++
++ vmaw->stream_info[n].devname = g_strdup(devname);
++ vmaw->stream_info[n].size = size;
++
++ vmaw->stream_info[n].cluster_count = (size + VMA_CLUSTER_SIZE - 1) /
++ VMA_CLUSTER_SIZE;
++
++ vmaw->stream_count = n;
++
++ if (strcmp(devname, "vmstate") == 0) {
++ vmaw->vmstate_stream = n;
++ }
++
++ return n;
++}
++
++static void vma_co_continue_write(void *opaque)
++{
++ VmaWriter *vmaw = opaque;
++
++ DPRINTF("vma_co_continue_write\n");
++ qemu_coroutine_enter(vmaw->co_writer);
++}
++
++static ssize_t coroutine_fn
++vma_queue_write(VmaWriter *vmaw, const void *buf, size_t bytes)
++{
++ DPRINTF("vma_queue_write enter %zd\n", bytes);
++
++ assert(vmaw);
++ assert(buf);
++ assert(bytes <= VMA_MAX_EXTENT_SIZE);
++
++ size_t done = 0;
++ ssize_t ret;
++
++ assert(vmaw->co_writer == NULL);
++
++ vmaw->co_writer = qemu_coroutine_self();
++
++ while (done < bytes) {
++ aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, false, NULL, vma_co_continue_write, NULL, vmaw);
++ qemu_coroutine_yield();
++ aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, false, NULL, NULL, NULL, NULL);
++ if (vmaw->status < 0) {
++ DPRINTF("vma_queue_write detected canceled backup\n");
++ done = -1;
++ break;
++ }
++ ret = write(vmaw->fd, buf + done, bytes - done);
++ if (ret > 0) {
++ done += ret;
++ DPRINTF("vma_queue_write written %zd %zd\n", done, ret);
++ } else if (ret < 0) {
++ if (errno == EAGAIN || errno == EWOULDBLOCK) {
++ /* try again */
++ } else {
++ vma_writer_set_error(vmaw, "vma_queue_write: write error - %s",
++ g_strerror(errno));
++ done = -1; /* always return failure for partial writes */
++ break;
++ }
++ } else if (ret == 0) {
++ /* should not happen - simply try again */
++ }
++ }
++
++ vmaw->co_writer = NULL;
++
++ return (done == bytes) ? bytes : -1;
++}
++
++VmaWriter *vma_writer_create(const char *filename, uuid_t uuid, Error **errp)
++{
++ const char *p;
++
++ assert(sizeof(VmaHeader) == (4096 + 8192));
++ assert(G_STRUCT_OFFSET(VmaHeader, config_names) == 2044);
++ assert(G_STRUCT_OFFSET(VmaHeader, config_data) == 3068);
++ assert(G_STRUCT_OFFSET(VmaHeader, dev_info) == 4096);
++ assert(sizeof(VmaExtentHeader) == 512);
++
++ VmaWriter *vmaw = g_new0(VmaWriter, 1);
++ vmaw->fd = -1;
++
++ vmaw->md5csum = g_checksum_new(G_CHECKSUM_MD5);
++ if (!vmaw->md5csum) {
++ error_setg(errp, "can't allocate cmsum\n");
++ goto err;
++ }
++
++ if (strstart(filename, "exec:", &p)) {
++ vmaw->cmd = popen(p, "w");
++ if (vmaw->cmd == NULL) {
++ error_setg(errp, "can't popen command '%s' - %s\n", p,
++ g_strerror(errno));
++ goto err;
++ }
++ vmaw->fd = fileno(vmaw->cmd);
++
++ /* try to use O_NONBLOCK */
++ fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_NONBLOCK);
++
++ } else {
++ struct stat st;
++ int oflags;
++ const char *tmp_id_str;
++
++ if ((stat(filename, &st) == 0) && S_ISFIFO(st.st_mode)) {
++ oflags = O_NONBLOCK|O_WRONLY;
++ vmaw->fd = qemu_open(filename, oflags, 0644);
++ } else if (strstart(filename, "/dev/fdset/", &tmp_id_str)) {
++ oflags = O_NONBLOCK|O_WRONLY;
++ vmaw->fd = qemu_open(filename, oflags, 0644);
++ } else if (strstart(filename, "/dev/fdname/", &tmp_id_str)) {
++ vmaw->fd = monitor_get_fd(cur_mon, tmp_id_str, errp);
++ if (vmaw->fd < 0) {
++ goto err;
++ }
++ /* try to use O_NONBLOCK */
++ fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_NONBLOCK);
++ } else {
++ oflags = O_NONBLOCK|O_DIRECT|O_WRONLY|O_CREAT|O_EXCL;
++ vmaw->fd = qemu_open(filename, oflags, 0644);
++ }
++
++ if (vmaw->fd < 0) {
++ error_setg(errp, "can't open file %s - %s\n", filename,
++ g_strerror(errno));
++ goto err;
++ }
++ }
++
++ /* we use O_DIRECT, so we need to align IO buffers */
++
++ vmaw->outbuf = qemu_memalign(512, VMA_MAX_EXTENT_SIZE);
++ vmaw->headerbuf = qemu_memalign(512, HEADERBUF_SIZE);
++
++ vmaw->outbuf_count = 0;
++ vmaw->outbuf_pos = VMA_EXTENT_HEADER_SIZE;
++
++ vmaw->header_blob_table_pos = 1; /* start at pos 1 */
++
++ qemu_co_mutex_init(&vmaw->flush_lock);
++
++ uuid_copy(vmaw->uuid, uuid);
++
++ return vmaw;
++
++err:
++ if (vmaw) {
++ if (vmaw->cmd) {
++ pclose(vmaw->cmd);
++ } else if (vmaw->fd >= 0) {
++ close(vmaw->fd);
++ }
++
++ if (vmaw->md5csum) {
++ g_checksum_free(vmaw->md5csum);
++ }
++
++ g_free(vmaw);
++ }
++
++ return NULL;
++}
++
++static int coroutine_fn vma_write_header(VmaWriter *vmaw)
++{
++ assert(vmaw);
++ unsigned char *buf = vmaw->headerbuf;
++ VmaHeader *head = (VmaHeader *)buf;
++
++ int i;
++
++ DPRINTF("VMA WRITE HEADER\n");
++
++ if (vmaw->status < 0) {
++ return vmaw->status;
++ }
++
++ memset(buf, 0, HEADERBUF_SIZE);
++
++ head->magic = VMA_MAGIC;
++ head->version = GUINT32_TO_BE(1); /* v1 */
++ memcpy(head->uuid, vmaw->uuid, 16);
++
++ time_t ctime = time(NULL);
++ head->ctime = GUINT64_TO_BE(ctime);
++
++ for (i = 0; i < VMA_MAX_CONFIGS; i++) {
++ head->config_names[i] = GUINT32_TO_BE(vmaw->config_names[i]);
++ head->config_data[i] = GUINT32_TO_BE(vmaw->config_data[i]);
++ }
++
++ /* 32 bytes per device (12 used currently) = 8192 bytes max */
++ for (i = 1; i <= 254; i++) {
++ VmaStreamInfo *si = &vmaw->stream_info[i];
++ if (si->size) {
++ assert(si->devname);
++ uint32_t devname_ptr = allocate_header_string(vmaw, si->devname);
++ if (!devname_ptr) {
++ return -1;
++ }
++ head->dev_info[i].devname_ptr = GUINT32_TO_BE(devname_ptr);
++ head->dev_info[i].size = GUINT64_TO_BE(si->size);
++ }
++ }
++
++ uint32_t header_size = sizeof(VmaHeader) + vmaw->header_blob_table_size;
++ head->header_size = GUINT32_TO_BE(header_size);
++
++ if (header_size > HEADERBUF_SIZE) {
++ return -1; /* just to be sure */
++ }
++
++ uint32_t blob_buffer_offset = sizeof(VmaHeader);
++ memcpy(buf + blob_buffer_offset, vmaw->header_blob_table,
++ vmaw->header_blob_table_size);
++ head->blob_buffer_offset = GUINT32_TO_BE(blob_buffer_offset);
++ head->blob_buffer_size = GUINT32_TO_BE(vmaw->header_blob_table_pos);
++
++ g_checksum_reset(vmaw->md5csum);
++ g_checksum_update(vmaw->md5csum, (const guchar *)buf, header_size);
++ gsize csize = 16;
++ g_checksum_get_digest(vmaw->md5csum, (guint8 *)(head->md5sum), &csize);
++
++ return vma_queue_write(vmaw, buf, header_size);
++}
++
++static int coroutine_fn vma_writer_flush(VmaWriter *vmaw)
++{
++ assert(vmaw);
++
++ int ret;
++ int i;
++
++ if (vmaw->status < 0) {
++ return vmaw->status;
++ }
++
++ if (!vmaw->header_written) {
++ vmaw->header_written = true;
++ ret = vma_write_header(vmaw);
++ if (ret < 0) {
++ vma_writer_set_error(vmaw, "vma_writer_flush: write header failed");
++ return ret;
++ }
++ }
++
++ DPRINTF("VMA WRITE FLUSH %d %d\n", vmaw->outbuf_count, vmaw->outbuf_pos);
++
++
++ VmaExtentHeader *ehead = (VmaExtentHeader *)vmaw->outbuf;
++
++ ehead->magic = VMA_EXTENT_MAGIC;
++ ehead->reserved1 = 0;
++
++ for (i = 0; i < VMA_BLOCKS_PER_EXTENT; i++) {
++ ehead->blockinfo[i] = GUINT64_TO_BE(vmaw->outbuf_block_info[i]);
++ }
++
++ guint16 block_count = (vmaw->outbuf_pos - VMA_EXTENT_HEADER_SIZE) /
++ VMA_BLOCK_SIZE;
++
++ ehead->block_count = GUINT16_TO_BE(block_count);
++
++ memcpy(ehead->uuid, vmaw->uuid, sizeof(ehead->uuid));
++ memset(ehead->md5sum, 0, sizeof(ehead->md5sum));
++
++ g_checksum_reset(vmaw->md5csum);
++ g_checksum_update(vmaw->md5csum, vmaw->outbuf, VMA_EXTENT_HEADER_SIZE);
++ gsize csize = 16;
++ g_checksum_get_digest(vmaw->md5csum, ehead->md5sum, &csize);
++
++ int bytes = vmaw->outbuf_pos;
++ ret = vma_queue_write(vmaw, vmaw->outbuf, bytes);
++ if (ret != bytes) {
++ vma_writer_set_error(vmaw, "vma_writer_flush: failed write");
++ }
++
++ vmaw->outbuf_count = 0;
++ vmaw->outbuf_pos = VMA_EXTENT_HEADER_SIZE;
++
++ for (i = 0; i < VMA_BLOCKS_PER_EXTENT; i++) {
++ vmaw->outbuf_block_info[i] = 0;
++ }
++
++ return vmaw->status;
++}
++
++static int vma_count_open_streams(VmaWriter *vmaw)
++{
++ g_assert(vmaw != NULL);
++
++ int i;
++ int open_drives = 0;
++ for (i = 0; i <= 255; i++) {
++ if (vmaw->stream_info[i].size && !vmaw->stream_info[i].finished) {
++ open_drives++;
++ }
++ }
++
++ return open_drives;
++}
++
++
++/**
++ * You need to call this if the vma archive does not contain
++ * any data stream.
++ */
++int coroutine_fn
++vma_writer_flush_output(VmaWriter *vmaw)
++{
++ qemu_co_mutex_lock(&vmaw->flush_lock);
++ int ret = vma_writer_flush(vmaw);
++ qemu_co_mutex_unlock(&vmaw->flush_lock);
++ if (ret < 0) {
++ vma_writer_set_error(vmaw, "vma_writer_flush_header failed");
++ }
++ return ret;
++}
++
++/**
++ * all jobs should call this when there is no more data
++ * Returns: number of remaining stream (0 ==> finished)
++ */
++int coroutine_fn
++vma_writer_close_stream(VmaWriter *vmaw, uint8_t dev_id)
++{
++ g_assert(vmaw != NULL);
++
++ DPRINTF("vma_writer_set_status %d\n", dev_id);
++ if (!vmaw->stream_info[dev_id].size) {
++ vma_writer_set_error(vmaw, "vma_writer_close_stream: "
++ "no such stream %d", dev_id);
++ return -1;
++ }
++ if (vmaw->stream_info[dev_id].finished) {
++ vma_writer_set_error(vmaw, "vma_writer_close_stream: "
++ "stream already closed %d", dev_id);
++ return -1;
++ }
++
++ vmaw->stream_info[dev_id].finished = true;
++
++ int open_drives = vma_count_open_streams(vmaw);
++
++ if (open_drives <= 0) {
++ DPRINTF("vma_writer_set_status all drives completed\n");
++ vma_writer_flush_output(vmaw);
++ }
++
++ return open_drives;
++}
++
++int vma_writer_get_status(VmaWriter *vmaw, VmaStatus *status)
++{
++ int i;
++
++ g_assert(vmaw != NULL);
++
++ if (status) {
++ status->status = vmaw->status;
++ g_strlcpy(status->errmsg, vmaw->errmsg, sizeof(status->errmsg));
++ for (i = 0; i <= 255; i++) {
++ status->stream_info[i] = vmaw->stream_info[i];
++ }
++
++ uuid_unparse_lower(vmaw->uuid, status->uuid_str);
++ }
++
++ status->closed = vmaw->closed;
++
++ return vmaw->status;
++}
++
++static int vma_writer_get_buffer(VmaWriter *vmaw)
++{
++ int ret = 0;
++
++ qemu_co_mutex_lock(&vmaw->flush_lock);
++
++ /* wait until buffer is available */
++ while (vmaw->outbuf_count >= (VMA_BLOCKS_PER_EXTENT - 1)) {
++ ret = vma_writer_flush(vmaw);
++ if (ret < 0) {
++ vma_writer_set_error(vmaw, "vma_writer_get_buffer: flush failed");
++ break;
++ }
++ }
++
++ qemu_co_mutex_unlock(&vmaw->flush_lock);
++
++ return ret;
++}
++
++
++int64_t coroutine_fn
++vma_writer_write(VmaWriter *vmaw, uint8_t dev_id, int64_t cluster_num,
++ const unsigned char *buf, size_t *zero_bytes)
++{
++ g_assert(vmaw != NULL);
++ g_assert(zero_bytes != NULL);
++
++ *zero_bytes = 0;
++
++ if (vmaw->status < 0) {
++ return vmaw->status;
++ }
++
++ if (!dev_id || !vmaw->stream_info[dev_id].size) {
++ vma_writer_set_error(vmaw, "vma_writer_write: "
++ "no such stream %d", dev_id);
++ return -1;
++ }
++
++ if (vmaw->stream_info[dev_id].finished) {
++ vma_writer_set_error(vmaw, "vma_writer_write: "
++ "stream already closed %d", dev_id);
++ return -1;
++ }
++
++
++ if (cluster_num >= (((uint64_t)1)<<32)) {
++ vma_writer_set_error(vmaw, "vma_writer_write: "
++ "cluster number out of range");
++ return -1;
++ }
++
++ if (dev_id == vmaw->vmstate_stream) {
++ if (cluster_num != vmaw->vmstate_clusters) {
++ vma_writer_set_error(vmaw, "vma_writer_write: "
++ "non sequential vmstate write");
++ }
++ vmaw->vmstate_clusters++;
++ } else if (cluster_num >= vmaw->stream_info[dev_id].cluster_count) {
++ vma_writer_set_error(vmaw, "vma_writer_write: cluster number too big");
++ return -1;
++ }
++
++ /* wait until buffer is available */
++ if (vma_writer_get_buffer(vmaw) < 0) {
++ vma_writer_set_error(vmaw, "vma_writer_write: "
++ "vma_writer_get_buffer failed");
++ return -1;
++ }
++
++ DPRINTF("VMA WRITE %d %zd\n", dev_id, cluster_num);
++
++ uint16_t mask = 0;
++
++ if (buf) {
++ int i;
++ int bit = 1;
++ for (i = 0; i < 16; i++) {
++ const unsigned char *vmablock = buf + (i*VMA_BLOCK_SIZE);
++ if (!buffer_is_zero(vmablock, VMA_BLOCK_SIZE)) {
++ mask |= bit;
++ memcpy(vmaw->outbuf + vmaw->outbuf_pos, vmablock,
++ VMA_BLOCK_SIZE);
++ vmaw->outbuf_pos += VMA_BLOCK_SIZE;
++ } else {
++ DPRINTF("VMA WRITE %zd ZERO BLOCK %d\n", cluster_num, i);
++ vmaw->stream_info[dev_id].zero_bytes += VMA_BLOCK_SIZE;
++ *zero_bytes += VMA_BLOCK_SIZE;
++ }
++
++ bit = bit << 1;
++ }
++ } else {
++ DPRINTF("VMA WRITE %zd ZERO CLUSTER\n", cluster_num);
++ vmaw->stream_info[dev_id].zero_bytes += VMA_CLUSTER_SIZE;
++ *zero_bytes += VMA_CLUSTER_SIZE;
++ }
++
++ uint64_t block_info = ((uint64_t)mask) << (32+16);
++ block_info |= ((uint64_t)dev_id) << 32;
++ block_info |= (cluster_num & 0xffffffff);
++ vmaw->outbuf_block_info[vmaw->outbuf_count] = block_info;
++
++ DPRINTF("VMA WRITE MASK %zd %zx\n", cluster_num, block_info);
++
++ vmaw->outbuf_count++;
++
++ /** NOTE: We allways write whole clusters, but we correctly set
++ * transferred bytes. So transferred == size when when everything
++ * went OK.
++ */
++ size_t transferred = VMA_CLUSTER_SIZE;
++
++ if (dev_id != vmaw->vmstate_stream) {
++ uint64_t last = (cluster_num + 1) * VMA_CLUSTER_SIZE;
++ if (last > vmaw->stream_info[dev_id].size) {
++ uint64_t diff = last - vmaw->stream_info[dev_id].size;
++ if (diff >= VMA_CLUSTER_SIZE) {
++ vma_writer_set_error(vmaw, "vma_writer_write: "
++ "read after last cluster");
++ return -1;
++ }
++ transferred -= diff;
++ }
++ }
++
++ vmaw->stream_info[dev_id].transferred += transferred;
++
++ return transferred;
++}
++
++void vma_writer_error_propagate(VmaWriter *vmaw, Error **errp)
++{
++ if (vmaw->status < 0 && *errp == NULL) {
++ error_setg(errp, "%s", vmaw->errmsg);
++ }
++}
++
++int vma_writer_close(VmaWriter *vmaw, Error **errp)
++{
++ g_assert(vmaw != NULL);
++
++ int i;
++
++ while (vmaw->co_writer) {
++ aio_poll(qemu_get_aio_context(), true);
++ }
++
++ assert(vmaw->co_writer == NULL);
++
++ if (vmaw->cmd) {
++ if (pclose(vmaw->cmd) < 0) {
++ vma_writer_set_error(vmaw, "vma_writer_close: "
++ "pclose failed - %s", g_strerror(errno));
++ }
++ } else {
++ if (close(vmaw->fd) < 0) {
++ vma_writer_set_error(vmaw, "vma_writer_close: "
++ "close failed - %s", g_strerror(errno));
++ }
++ }
++
++ for (i = 0; i <= 255; i++) {
++ VmaStreamInfo *si = &vmaw->stream_info[i];
++ if (si->size) {
++ if (!si->finished) {
++ vma_writer_set_error(vmaw, "vma_writer_close: "
++ "detected open stream '%s'", si->devname);
++ } else if ((si->transferred != si->size) &&
++ (i != vmaw->vmstate_stream)) {
++ vma_writer_set_error(vmaw, "vma_writer_close: "
++ "incomplete stream '%s' (%zd != %zd)",
++ si->devname, si->transferred, si->size);
++ }
++ }
++ }
++
++ for (i = 0; i <= 255; i++) {
++ vmaw->stream_info[i].finished = 1; /* mark as closed */
++ }
++
++ vmaw->closed = 1;
++
++ if (vmaw->status < 0 && *errp == NULL) {
++ error_setg(errp, "%s", vmaw->errmsg);
++ }
++
++ return vmaw->status;
++}
++
++void vma_writer_destroy(VmaWriter *vmaw)
++{
++ assert(vmaw);
++
++ int i;
++
++ for (i = 0; i <= 255; i++) {
++ if (vmaw->stream_info[i].devname) {
++ g_free(vmaw->stream_info[i].devname);
++ }
++ }
++
++ if (vmaw->md5csum) {
++ g_checksum_free(vmaw->md5csum);
++ }
++
++ g_free(vmaw);
++}
+diff --git a/vma.c b/vma.c
+new file mode 100644
+index 0000000000..1b59fd1555
+--- /dev/null
++++ b/vma.c
+@@ -0,0 +1,756 @@
++/*
++ * VMA: Virtual Machine Archive
++ *
++ * Copyright (C) 2012-2013 Proxmox Server Solutions
++ *
++ * Authors:
++ * Dietmar Maurer (dietmar@proxmox.com)
++ *
++ * This work is licensed under the terms of the GNU GPL, version 2 or later.
++ * See the COPYING file in the top-level directory.
++ *
++ */
++
++#include "qemu/osdep.h"
++#include <glib.h>
++
++#include "vma.h"
++#include "qemu-common.h"
++#include "qemu/error-report.h"
++#include "qemu/main-loop.h"
++#include "qapi/qmp/qstring.h"
++#include "sysemu/block-backend.h"
++
++static void help(void)
++{
++ const char *help_msg =
++ "usage: vma command [command options]\n"
++ "\n"
++ "vma list <filename>\n"
++ "vma config <filename> [-c config]\n"
++ "vma create <filename> [-c config] pathname ...\n"
++ "vma extract <filename> [-r <fifo>] <targetdir>\n"
++ "vma verify <filename> [-v]\n"
++ ;
++
++ printf("%s", help_msg);
++ exit(1);
++}
++
++static const char *extract_devname(const char *path, char **devname, int index)
++{
++ assert(path);
++
++ const char *sep = strchr(path, '=');
++
++ if (sep) {
++ *devname = g_strndup(path, sep - path);
++ path = sep + 1;
++ } else {
++ if (index >= 0) {
++ *devname = g_strdup_printf("disk%d", index);
++ } else {
++ *devname = NULL;
++ }
++ }
++
++ return path;
++}
++
++static void print_content(VmaReader *vmar)
++{
++ assert(vmar);
++
++ VmaHeader *head = vma_reader_get_header(vmar);
++
++ GList *l = vma_reader_get_config_data(vmar);
++ while (l && l->data) {
++ VmaConfigData *cdata = (VmaConfigData *)l->data;
++ l = g_list_next(l);
++ printf("CFG: size: %d name: %s\n", cdata->len, cdata->name);
++ }
++
++ int i;
++ VmaDeviceInfo *di;
++ for (i = 1; i < 255; i++) {
++ di = vma_reader_get_device_info(vmar, i);
++ if (di) {
++ if (strcmp(di->devname, "vmstate") == 0) {
++ printf("VMSTATE: dev_id=%d memory: %zd\n", i, di->size);
++ } else {
++ printf("DEV: dev_id=%d size: %zd devname: %s\n",
++ i, di->size, di->devname);
++ }
++ }
++ }
++ /* ctime is the last entry we print */
++ printf("CTIME: %s", ctime(&head->ctime));
++ fflush(stdout);
++}
++
++static int list_content(int argc, char **argv)
++{
++ int c, ret = 0;
++ const char *filename;
++
++ for (;;) {
++ c = getopt(argc, argv, "h");
++ if (c == -1) {
++ break;
++ }
++ switch (c) {
++ case '?':
++ case 'h':
++ help();
++ break;
++ default:
++ g_assert_not_reached();
++ }
++ }
++
++ /* Get the filename */
++ if ((optind + 1) != argc) {
++ help();
++ }
++ filename = argv[optind++];
++
++ Error *errp = NULL;
++ VmaReader *vmar = vma_reader_create(filename, &errp);
++
++ if (!vmar) {
++ g_error("%s", error_get_pretty(errp));
++ }
++
++ print_content(vmar);
++
++ vma_reader_destroy(vmar);
++
++ return ret;
++}
++
++typedef struct RestoreMap {
++ char *devname;
++ char *path;
++ char *format;
++ bool write_zero;
++} RestoreMap;
++
++static int extract_content(int argc, char **argv)
++{
++ int c, ret = 0;
++ int verbose = 0;
++ const char *filename;
++ const char *dirname;
++ const char *readmap = NULL;
++
++ for (;;) {
++ c = getopt(argc, argv, "hvr:");
++ if (c == -1) {
++ break;
++ }
++ switch (c) {
++ case '?':
++ case 'h':
++ help();
++ break;
++ case 'r':
++ readmap = optarg;
++ break;
++ case 'v':
++ verbose = 1;
++ break;
++ default:
++ help();
++ }
++ }
++
++ /* Get the filename */
++ if ((optind + 2) != argc) {
++ help();
++ }
++ filename = argv[optind++];
++ dirname = argv[optind++];
++
++ Error *errp = NULL;
++ VmaReader *vmar = vma_reader_create(filename, &errp);
++
++ if (!vmar) {
++ g_error("%s", error_get_pretty(errp));
++ }
++
++ if (mkdir(dirname, 0777) < 0) {
++ g_error("unable to create target directory %s - %s",
++ dirname, g_strerror(errno));
++ }
++
++ GList *l = vma_reader_get_config_data(vmar);
++ while (l && l->data) {
++ VmaConfigData *cdata = (VmaConfigData *)l->data;
++ l = g_list_next(l);
++ char *cfgfn = g_strdup_printf("%s/%s", dirname, cdata->name);
++ GError *err = NULL;
++ if (!g_file_set_contents(cfgfn, (gchar *)cdata->data, cdata->len,
++ &err)) {
++ g_error("unable to write file: %s", err->message);
++ }
++ }
++
++ GHashTable *devmap = g_hash_table_new(g_str_hash, g_str_equal);
++
++ if (readmap) {
++ print_content(vmar);
++
++ FILE *map = fopen(readmap, "r");
++ if (!map) {
++ g_error("unable to open fifo %s - %s", readmap, g_strerror(errno));
++ }
++
++ while (1) {
++ char inbuf[8192];
++ char *line = fgets(inbuf, sizeof(inbuf), map);
++ if (!line || line[0] == '\0' || !strcmp(line, "done\n")) {
++ break;
++ }
++ int len = strlen(line);
++ if (line[len - 1] == '\n') {
++ line[len - 1] = '\0';
++ if (len == 1) {
++ break;
++ }
++ }
++
++ char *format = NULL;
++ if (strncmp(line, "format=", sizeof("format=")-1) == 0) {
++ format = line + sizeof("format=")-1;
++ char *colon = strchr(format, ':');
++ if (!colon) {
++ g_error("read map failed - found only a format ('%s')", inbuf);
++ }
++ format = g_strndup(format, colon - format);
++ line = colon+1;
++ }
++
++ const char *path;
++ bool write_zero;
++ if (line[0] == '0' && line[1] == ':') {
++ path = line + 2;
++ write_zero = false;
++ } else if (line[0] == '1' && line[1] == ':') {
++ path = line + 2;
++ write_zero = true;
++ } else {
++ g_error("read map failed - parse error ('%s')", inbuf);
++ }
++
++ char *devname = NULL;
++ path = extract_devname(path, &devname, -1);
++ if (!devname) {
++ g_error("read map failed - no dev name specified ('%s')",
++ inbuf);
++ }
++
++ RestoreMap *map = g_new0(RestoreMap, 1);
++ map->devname = g_strdup(devname);
++ map->path = g_strdup(path);
++ map->format = format;
++ map->write_zero = write_zero;
++
++ g_hash_table_insert(devmap, map->devname, map);
++
++ };
++ }
++
++ int i;
++ int vmstate_fd = -1;
++ guint8 vmstate_stream = 0;
++
++ BlockBackend *blk = NULL;
++
++ for (i = 1; i < 255; i++) {
++ VmaDeviceInfo *di = vma_reader_get_device_info(vmar, i);
++ if (di && (strcmp(di->devname, "vmstate") == 0)) {
++ vmstate_stream = i;
++ char *statefn = g_strdup_printf("%s/vmstate.bin", dirname);
++ vmstate_fd = open(statefn, O_WRONLY|O_CREAT|O_EXCL, 0644);
++ if (vmstate_fd < 0) {
++ g_error("create vmstate file '%s' failed - %s", statefn,
++ g_strerror(errno));
++ }
++ g_free(statefn);
++ } else if (di) {
++ char *devfn = NULL;
++ const char *format = NULL;
++ int flags = BDRV_O_RDWR | BDRV_O_NO_FLUSH;
++ bool write_zero = true;
++
++ if (readmap) {
++ RestoreMap *map;
++ map = (RestoreMap *)g_hash_table_lookup(devmap, di->devname);
++ if (map == NULL) {
++ g_error("no device name mapping for %s", di->devname);
++ }
++ devfn = map->path;
++ format = map->format;
++ write_zero = map->write_zero;
++ } else {
++ devfn = g_strdup_printf("%s/tmp-disk-%s.raw",
++ dirname, di->devname);
++ printf("DEVINFO %s %zd\n", devfn, di->size);
++
++ bdrv_img_create(devfn, "raw", NULL, NULL, NULL, di->size,
++ flags, true, &errp);
++ if (errp) {
++ g_error("can't create file %s: %s", devfn,
++ error_get_pretty(errp));
++ }
++
++ /* Note: we created an empty file above, so there is no
++ * need to write zeroes (so we generate a sparse file)
++ */
++ write_zero = false;
++ }
++
++ size_t devlen = strlen(devfn);
++ QDict *options = NULL;
++ if (format) {
++ /* explicit format from commandline */
++ options = qdict_new();
++ qdict_put(options, "driver", qstring_from_str(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(options, "driver", qstring_from_str("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 (vma_reader_register_bs(vmar, i, blk, write_zero, &errp) < 0) {
++ g_error("%s", error_get_pretty(errp));
++ }
++
++ if (!readmap) {
++ g_free(devfn);
++ }
++ }
++ }
++
++ if (vma_reader_restore(vmar, vmstate_fd, verbose, &errp) < 0) {
++ g_error("restore failed - %s", error_get_pretty(errp));
++ }
++
++ if (!readmap) {
++ for (i = 1; i < 255; i++) {
++ VmaDeviceInfo *di = vma_reader_get_device_info(vmar, i);
++ if (di && (i != vmstate_stream)) {
++ char *tmpfn = g_strdup_printf("%s/tmp-disk-%s.raw",
++ dirname, di->devname);
++ char *fn = g_strdup_printf("%s/disk-%s.raw",
++ dirname, di->devname);
++ if (rename(tmpfn, fn) != 0) {
++ g_error("rename %s to %s failed - %s",
++ tmpfn, fn, g_strerror(errno));
++ }
++ }
++ }
++ }
++
++ vma_reader_destroy(vmar);
++
++ blk_unref(blk);
++
++ bdrv_close_all();
++
++ return ret;
++}
++
++static int verify_content(int argc, char **argv)
++{
++ int c, ret = 0;
++ int verbose = 0;
++ const char *filename;
++
++ for (;;) {
++ c = getopt(argc, argv, "hv");
++ if (c == -1) {
++ break;
++ }
++ switch (c) {
++ case '?':
++ case 'h':
++ help();
++ break;
++ case 'v':
++ verbose = 1;
++ break;
++ default:
++ help();
++ }
++ }
++
++ /* Get the filename */
++ if ((optind + 1) != argc) {
++ help();
++ }
++ filename = argv[optind++];
++
++ Error *errp = NULL;
++ VmaReader *vmar = vma_reader_create(filename, &errp);
++
++ if (!vmar) {
++ g_error("%s", error_get_pretty(errp));
++ }
++
++ if (verbose) {
++ print_content(vmar);
++ }
++
++ if (vma_reader_verify(vmar, verbose, &errp) < 0) {
++ g_error("verify failed - %s", error_get_pretty(errp));
++ }
++
++ vma_reader_destroy(vmar);
++
++ bdrv_close_all();
++
++ return ret;
++}
++
++typedef struct BackupJob {
++ BlockBackend *target;
++ int64_t len;
++ VmaWriter *vmaw;
++ uint8_t dev_id;
++} BackupJob;
++
++#define BACKUP_SECTORS_PER_CLUSTER (VMA_CLUSTER_SIZE / BDRV_SECTOR_SIZE)
++
++static void coroutine_fn backup_run_empty(void *opaque)
++{
++ VmaWriter *vmaw = (VmaWriter *)opaque;
++
++ vma_writer_flush_output(vmaw);
++
++ Error *err = NULL;
++ if (vma_writer_close(vmaw, &err) != 0) {
++ g_warning("vma_writer_close failed %s", error_get_pretty(err));
++ }
++}
++
++static void coroutine_fn backup_run(void *opaque)
++{
++ BackupJob *job = (BackupJob *)opaque;
++ struct iovec iov;
++ QEMUIOVector qiov;
++
++ int64_t start, end;
++ int ret = 0;
++
++ unsigned char *buf = blk_blockalign(job->target, VMA_CLUSTER_SIZE);
++
++ start = 0;
++ end = DIV_ROUND_UP(job->len / BDRV_SECTOR_SIZE,
++ BACKUP_SECTORS_PER_CLUSTER);
++
++ for (; start < end; start++) {
++ iov.iov_base = buf;
++ iov.iov_len = VMA_CLUSTER_SIZE;
++ qemu_iovec_init_external(&qiov, &iov, 1);
++
++ ret = blk_co_preadv(job->target, start * VMA_CLUSTER_SIZE,
++ VMA_CLUSTER_SIZE, &qiov, 0);
++ if (ret < 0) {
++ vma_writer_set_error(job->vmaw, "read error", -1);
++ goto out;
++ }
++
++ size_t zb = 0;
++ if (vma_writer_write(job->vmaw, job->dev_id, start, buf, &zb) < 0) {
++ vma_writer_set_error(job->vmaw, "backup_dump_cb vma_writer_write failed", -1);
++ goto out;
++ }
++ }
++
++
++out:
++ if (vma_writer_close_stream(job->vmaw, job->dev_id) <= 0) {
++ Error *err = NULL;
++ if (vma_writer_close(job->vmaw, &err) != 0) {
++ g_warning("vma_writer_close failed %s", error_get_pretty(err));
++ }
++ }
++}
++
++static int create_archive(int argc, char **argv)
++{
++ int i, c;
++ int verbose = 0;
++ const char *archivename;
++ GList *config_files = NULL;
++
++ for (;;) {
++ c = getopt(argc, argv, "hvc:");
++ if (c == -1) {
++ break;
++ }
++ switch (c) {
++ case '?':
++ case 'h':
++ help();
++ break;
++ case 'c':
++ config_files = g_list_append(config_files, optarg);
++ break;
++ case 'v':
++ verbose = 1;
++ break;
++ default:
++ g_assert_not_reached();
++ }
++ }
++
++
++ /* make sure we an archive name */
++ if ((optind + 1) > argc) {
++ help();
++ }
++
++ archivename = argv[optind++];
++
++ uuid_t uuid;
++ uuid_generate(uuid);
++
++ Error *local_err = NULL;
++ VmaWriter *vmaw = vma_writer_create(archivename, uuid, &local_err);
++
++ if (vmaw == NULL) {
++ g_error("%s", error_get_pretty(local_err));
++ }
++
++ GList *l = config_files;
++ while (l && l->data) {
++ char *name = l->data;
++ char *cdata = NULL;
++ gsize clen = 0;
++ GError *err = NULL;
++ if (!g_file_get_contents(name, &cdata, &clen, &err)) {
++ unlink(archivename);
++ g_error("Unable to read file: %s", err->message);
++ }
++
++ if (vma_writer_add_config(vmaw, name, cdata, clen) != 0) {
++ unlink(archivename);
++ g_error("Unable to append config data %s (len = %zd)",
++ name, clen);
++ }
++ l = g_list_next(l);
++ }
++
++ int devcount = 0;
++ while (optind < argc) {
++ const char *path = argv[optind++];
++ char *devname = NULL;
++ path = extract_devname(path, &devname, devcount++);
++
++ Error *errp = NULL;
++ BlockBackend *target;
++
++ target = blk_new_open(path, NULL, NULL, 0, &errp);
++ if (!target) {
++ unlink(archivename);
++ g_error("bdrv_open '%s' failed - %s", path, error_get_pretty(errp));
++ }
++ int64_t size = blk_getlength(target);
++ int dev_id = vma_writer_register_stream(vmaw, devname, size);
++ if (dev_id <= 0) {
++ unlink(archivename);
++ g_error("vma_writer_register_stream '%s' failed", devname);
++ }
++
++ BackupJob *job = g_new0(BackupJob, 1);
++ job->len = size;
++ job->target = target;
++ job->vmaw = vmaw;
++ job->dev_id = dev_id;
++
++ Coroutine *co = qemu_coroutine_create(backup_run, job);
++ qemu_coroutine_enter(co);
++ }
++
++ VmaStatus vmastat;
++ int percent = 0;
++ int last_percent = -1;
++
++ if (devcount) {
++ while (1) {
++ main_loop_wait(false);
++ vma_writer_get_status(vmaw, &vmastat);
++
++ if (verbose) {
++
++ uint64_t total = 0;
++ uint64_t transferred = 0;
++ uint64_t zero_bytes = 0;
++
++ int i;
++ for (i = 0; i < 256; i++) {
++ if (vmastat.stream_info[i].size) {
++ total += vmastat.stream_info[i].size;
++ transferred += vmastat.stream_info[i].transferred;
++ zero_bytes += vmastat.stream_info[i].zero_bytes;
++ }
++ }
++ percent = (transferred*100)/total;
++ if (percent != last_percent) {
++ fprintf(stderr, "progress %d%% %zd/%zd %zd\n", percent,
++ transferred, total, zero_bytes);
++ fflush(stderr);
++
++ last_percent = percent;
++ }
++ }
++
++ if (vmastat.closed) {
++ break;
++ }
++ }
++ } else {
++ Coroutine *co = qemu_coroutine_create(backup_run_empty, vmaw);
++ qemu_coroutine_enter(co);
++ while (1) {
++ main_loop_wait(false);
++ vma_writer_get_status(vmaw, &vmastat);
++ if (vmastat.closed) {
++ break;
++ }
++ }
++ }
++
++ bdrv_drain_all();
++
++ vma_writer_get_status(vmaw, &vmastat);
++
++ if (verbose) {
++ for (i = 0; i < 256; i++) {
++ VmaStreamInfo *si = &vmastat.stream_info[i];
++ if (si->size) {
++ fprintf(stderr, "image %s: size=%zd zeros=%zd saved=%zd\n",
++ si->devname, si->size, si->zero_bytes,
++ si->size - si->zero_bytes);
++ }
++ }
++ }
++
++ if (vmastat.status < 0) {
++ unlink(archivename);
++ g_error("creating vma archive failed");
++ }
++
++ return 0;
++}
++
++static int dump_config(int argc, char **argv)
++{
++ int c, ret = 0;
++ const char *filename;
++ const char *config_name = "qemu-server.conf";
++
++ for (;;) {
++ c = getopt(argc, argv, "hc:");
++ if (c == -1) {
++ break;
++ }
++ switch (c) {
++ case '?':
++ case 'h':
++ help();
++ break;
++ case 'c':
++ config_name = optarg;
++ break;
++ default:
++ help();
++ }
++ }
++
++ /* Get the filename */
++ if ((optind + 1) != argc) {
++ help();
++ }
++ filename = argv[optind++];
++
++ Error *errp = NULL;
++ VmaReader *vmar = vma_reader_create(filename, &errp);
++
++ if (!vmar) {
++ g_error("%s", error_get_pretty(errp));
++ }
++
++ int found = 0;
++ GList *l = vma_reader_get_config_data(vmar);
++ while (l && l->data) {
++ VmaConfigData *cdata = (VmaConfigData *)l->data;
++ l = g_list_next(l);
++ if (strcmp(cdata->name, config_name) == 0) {
++ found = 1;
++ fwrite(cdata->data, cdata->len, 1, stdout);
++ break;
++ }
++ }
++
++ vma_reader_destroy(vmar);
++
++ bdrv_close_all();
++
++ if (!found) {
++ fprintf(stderr, "unable to find configuration data '%s'\n", config_name);
++ return -1;
++ }
++
++ return ret;
++}
++
++int main(int argc, char **argv)
++{
++ const char *cmdname;
++ Error *main_loop_err = NULL;
++
++ error_set_progname(argv[0]);
++
++ if (qemu_init_main_loop(&main_loop_err)) {
++ g_error("%s", error_get_pretty(main_loop_err));
++ }
++
++ bdrv_init();
++
++ if (argc < 2) {
++ help();
++ }
++
++ cmdname = argv[1];
++ argc--; argv++;
++
++
++ if (!strcmp(cmdname, "list")) {
++ return list_content(argc, argv);
++ } else if (!strcmp(cmdname, "create")) {
++ return create_archive(argc, argv);
++ } else if (!strcmp(cmdname, "extract")) {
++ return extract_content(argc, argv);
++ } else if (!strcmp(cmdname, "verify")) {
++ return verify_content(argc, argv);
++ } else if (!strcmp(cmdname, "config")) {
++ return dump_config(argc, argv);
++ }
++
++ help();
++ return 0;
++}
+diff --git a/vma.h b/vma.h
+new file mode 100644
+index 0000000000..c895c97f6d
+--- /dev/null
++++ b/vma.h
+@@ -0,0 +1,150 @@
++/*
++ * VMA: Virtual Machine Archive
++ *
++ * Copyright (C) Proxmox Server Solutions
++ *
++ * Authors:
++ * Dietmar Maurer (dietmar@proxmox.com)
++ *
++ * This work is licensed under the terms of the GNU GPL, version 2 or later.
++ * See the COPYING file in the top-level directory.
++ *
++ */
++
++#ifndef BACKUP_VMA_H
++#define BACKUP_VMA_H
++
++#include <uuid/uuid.h>
++#include "qapi/error.h"
++#include "block/block.h"
++
++#define VMA_BLOCK_BITS 12
++#define VMA_BLOCK_SIZE (1<<VMA_BLOCK_BITS)
++#define VMA_CLUSTER_BITS (VMA_BLOCK_BITS+4)
++#define VMA_CLUSTER_SIZE (1<<VMA_CLUSTER_BITS)
++
++#if VMA_CLUSTER_SIZE != 65536
++#error unexpected cluster size
++#endif
++
++#define VMA_EXTENT_HEADER_SIZE 512
++#define VMA_BLOCKS_PER_EXTENT 59
++#define VMA_MAX_CONFIGS 256
++
++#define VMA_MAX_EXTENT_SIZE \
++ (VMA_EXTENT_HEADER_SIZE+VMA_CLUSTER_SIZE*VMA_BLOCKS_PER_EXTENT)
++#if VMA_MAX_EXTENT_SIZE != 3867136
++#error unexpected VMA_EXTENT_SIZE
++#endif
++
++/* File Format Definitions */
++
++#define VMA_MAGIC (GUINT32_TO_BE(('V'<<24)|('M'<<16)|('A'<<8)|0x00))
++#define VMA_EXTENT_MAGIC (GUINT32_TO_BE(('V'<<24)|('M'<<16)|('A'<<8)|'E'))
++
++typedef struct VmaDeviceInfoHeader {
++ uint32_t devname_ptr; /* offset into blob_buffer table */
++ uint32_t reserved0;
++ uint64_t size; /* device size in bytes */
++ uint64_t reserved1;
++ uint64_t reserved2;
++} VmaDeviceInfoHeader;
++
++typedef struct VmaHeader {
++ uint32_t magic;
++ uint32_t version;
++ unsigned char uuid[16];
++ int64_t ctime;
++ unsigned char md5sum[16];
++
++ uint32_t blob_buffer_offset;
++ uint32_t blob_buffer_size;
++ uint32_t header_size;
++
++ unsigned char reserved[1984];
++
++ uint32_t config_names[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
++ uint32_t config_data[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
++
++ uint32_t reserved1;
++
++ VmaDeviceInfoHeader dev_info[256];
++} VmaHeader;
++
++typedef struct VmaExtentHeader {
++ uint32_t magic;
++ uint16_t reserved1;
++ uint16_t block_count;
++ unsigned char uuid[16];
++ unsigned char md5sum[16];
++ uint64_t blockinfo[VMA_BLOCKS_PER_EXTENT];
++} VmaExtentHeader;
++
++/* functions/definitions to read/write vma files */
++
++typedef struct VmaReader VmaReader;
++
++typedef struct VmaWriter VmaWriter;
++
++typedef struct VmaConfigData {
++ const char *name;
++ const void *data;
++ uint32_t len;
++} VmaConfigData;
++
++typedef struct VmaStreamInfo {
++ uint64_t size;
++ uint64_t cluster_count;
++ uint64_t transferred;
++ uint64_t zero_bytes;
++ int finished;
++ char *devname;
++} VmaStreamInfo;
++
++typedef struct VmaStatus {
++ int status;
++ bool closed;
++ char errmsg[8192];
++ char uuid_str[37];
++ VmaStreamInfo stream_info[256];
++} VmaStatus;
++
++typedef struct VmaDeviceInfo {
++ uint64_t size; /* device size in bytes */
++ const char *devname;
++} VmaDeviceInfo;
++
++VmaWriter *vma_writer_create(const char *filename, uuid_t uuid, Error **errp);
++int vma_writer_close(VmaWriter *vmaw, Error **errp);
++void vma_writer_error_propagate(VmaWriter *vmaw, Error **errp);
++void vma_writer_destroy(VmaWriter *vmaw);
++int vma_writer_add_config(VmaWriter *vmaw, const char *name, gpointer data,
++ size_t len);
++int vma_writer_register_stream(VmaWriter *vmaw, const char *devname,
++ size_t size);
++
++int64_t coroutine_fn vma_writer_write(VmaWriter *vmaw, uint8_t dev_id,
++ int64_t cluster_num,
++ const unsigned char *buf,
++ size_t *zero_bytes);
++
++int coroutine_fn vma_writer_close_stream(VmaWriter *vmaw, uint8_t dev_id);
++int coroutine_fn vma_writer_flush_output(VmaWriter *vmaw);
++
++int vma_writer_get_status(VmaWriter *vmaw, VmaStatus *status);
++void vma_writer_set_error(VmaWriter *vmaw, const char *fmt, ...);
++
++
++VmaReader *vma_reader_create(const char *filename, Error **errp);
++void vma_reader_destroy(VmaReader *vmar);
++VmaHeader *vma_reader_get_header(VmaReader *vmar);
++GList *vma_reader_get_config_data(VmaReader *vmar);
++VmaDeviceInfo *vma_reader_get_device_info(VmaReader *vmar, guint8 dev_id);
++int vma_reader_register_bs(VmaReader *vmar, guint8 dev_id,
++ BlockBackend *target, bool write_zeroes,
++ Error **errp);
++int vma_reader_restore(VmaReader *vmar, int vmstate_fd, bool verbose,
++ Error **errp);
++int vma_reader_verify(VmaReader *vmar, bool verbose, Error **errp);
++
++#endif /* BACKUP_VMA_H */
+--
+2.11.0
+
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Wed, 2 Aug 2017 13:51:02 +0200
-Subject: [PATCH] backup: introduce vma archive format
-
----
- MAINTAINERS | 6 +
- block/Makefile.objs | 3 +
- block/vma.c | 424 +++++++++++++++++++++++++++++++++++++++++++
- blockdev.c | 498 +++++++++++++++++++++++++++++++++++++++++++++++++++
- configure | 30 ++++
- hmp-commands-info.hx | 13 ++
- hmp-commands.hx | 31 ++++
- hmp.c | 63 +++++++
- hmp.h | 3 +
- qapi-schema.json | 91 ++++++++++
- qapi/block-core.json | 20 ++-
- 11 files changed, 1180 insertions(+), 2 deletions(-)
- create mode 100644 block/vma.c
-
-diff --git a/MAINTAINERS b/MAINTAINERS
-index 430efb0ab7..6a7d338aad 100644
---- a/MAINTAINERS
-+++ b/MAINTAINERS
-@@ -1811,6 +1811,12 @@ L: qemu-block@nongnu.org
- S: Supported
- F: block/vvfat.c
-
-+VMA
-+M: Wolfgang Bumiller <w.bumiller@proxmox.com>.
-+L: pve-devel@proxmox.com
-+S: Supported
-+F: block/vma.c
-+
- Image format fuzzer
- M: Stefan Hajnoczi <stefanha@redhat.com>
- L: qemu-block@nongnu.org
-diff --git a/block/Makefile.objs b/block/Makefile.objs
-index 8cdac08db5..6df5567dd3 100644
---- a/block/Makefile.objs
-+++ b/block/Makefile.objs
-@@ -21,6 +21,7 @@ block-obj-$(CONFIG_CURL) += curl.o
- block-obj-$(CONFIG_RBD) += rbd.o
- block-obj-$(CONFIG_GLUSTERFS) += gluster.o
- block-obj-$(CONFIG_LIBSSH2) += ssh.o
-+block-obj-$(CONFIG_VMA) += vma.o
- block-obj-y += accounting.o dirty-bitmap.o
- block-obj-y += write-threshold.o
- block-obj-y += backup.o
-@@ -45,3 +46,5 @@ block-obj-$(if $(CONFIG_BZIP2),m,n) += dmg-bz2.o
- dmg-bz2.o-libs := $(BZIP2_LIBS)
- qcow.o-libs := -lz
- linux-aio.o-libs := -laio
-+vma.o-cflags := $(VMA_CFLAGS)
-+vma.o-libs := $(VMA_LIBS)
-diff --git a/block/vma.c b/block/vma.c
-new file mode 100644
-index 0000000000..7151514f94
---- /dev/null
-+++ b/block/vma.c
-@@ -0,0 +1,424 @@
-+/*
-+ * VMA archive backend for QEMU, container object
-+ *
-+ * Copyright (C) 2017 Proxmox Server Solutions GmbH
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ *
-+ */
-+#include <vma/vma.h>
-+
-+#include "qemu/osdep.h"
-+#include "qemu/uuid.h"
-+#include "qemu-common.h"
-+#include "qapi/error.h"
-+#include "qapi/qmp/qerror.h"
-+#include "qapi/qmp/qstring.h"
-+#include "qom/object.h"
-+#include "qom/object_interfaces.h"
-+#include "block/block_int.h"
-+
-+/* exported interface */
-+void vma_object_add_config_file(Object *obj, const char *name,
-+ const char *contents, size_t len,
-+ Error **errp);
-+
-+#define TYPE_VMA_OBJECT "vma"
-+#define VMA_OBJECT(obj) \
-+ OBJECT_CHECK(VMAObjectState, (obj), TYPE_VMA_OBJECT)
-+#define VMA_OBJECT_GET_CLASS(obj) \
-+ OBJECT_GET_CLASS(VMAObjectClass, (obj), TYPE_VMA_OBJECT)
-+
-+typedef struct VMAObjectClass {
-+ ObjectClass parent_class;
-+} VMAObjectClass;
-+
-+typedef struct VMAObjectState {
-+ Object parent;
-+
-+ char *filename;
-+
-+ QemuUUID uuid;
-+ bool blocked;
-+ VMAWriter *vma;
-+ QemuMutex mutex;
-+} VMAObjectState;
-+
-+static VMAObjectState *vma_by_id(const char *name)
-+{
-+ Object *container;
-+ Object *obj;
-+
-+ container = object_get_objects_root();
-+ obj = object_resolve_path_component(container, name);
-+
-+ return VMA_OBJECT(obj);
-+}
-+
-+static void vma_object_class_complete(UserCreatable *uc, Error **errp)
-+{
-+ int rc;
-+ VMAObjectState *vo = VMA_OBJECT(uc);
-+ VMAObjectClass *voc = VMA_OBJECT_GET_CLASS(uc);
-+ (void)!vo;
-+ (void)!voc;
-+
-+ if (!vo->filename) {
-+ error_setg(errp, "Parameter 'filename' is required");
-+ return;
-+ }
-+
-+ rc = VMAWriter_fopen(vo->filename, &vo->vma);
-+ if (rc < 0) {
-+ error_setg_errno(errp, -rc, "failed to create VMA archive");
-+ return;
-+ }
-+
-+ rc = VMAWriter_set_uuid(vo->vma, vo->uuid.data, sizeof(vo->uuid.data));
-+ if (rc < 0) {
-+ error_setg_errno(errp, -rc, "failed to set UUID of VMA archive");
-+ return;
-+ }
-+
-+ qemu_mutex_init(&vo->mutex);
-+}
-+
-+static bool vma_object_can_be_deleted(UserCreatable *uc, Error **errp)
-+{
-+ //VMAObjectState *vo = VMA_OBJECT(uc);
-+ //if (!vo->vma) {
-+ // return true;
-+ //}
-+ //return false;
-+ return true;
-+}
-+
-+static void vma_object_class_init(ObjectClass *oc, void *data)
-+{
-+ UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
-+
-+ ucc->can_be_deleted = vma_object_can_be_deleted;
-+ ucc->complete = vma_object_class_complete;
-+}
-+
-+static char *vma_object_get_filename(Object *obj, Error **errp)
-+{
-+ VMAObjectState *vo = VMA_OBJECT(obj);
-+
-+ return g_strdup(vo->filename);
-+}
-+
-+static void vma_object_set_filename(Object *obj, const char *str, Error **errp)
-+{
-+ VMAObjectState *vo = VMA_OBJECT(obj);
-+
-+ if (vo->vma) {
-+ error_setg(errp, "filename cannot be changed after creation");
-+ return;
-+ }
-+
-+ g_free(vo->filename);
-+ vo->filename = g_strdup(str);
-+}
-+
-+static char *vma_object_get_uuid(Object *obj, Error **errp)
-+{
-+ VMAObjectState *vo = VMA_OBJECT(obj);
-+
-+ return qemu_uuid_unparse_strdup(&vo->uuid);
-+}
-+
-+static void vma_object_set_uuid(Object *obj, const char *str, Error **errp)
-+{
-+ VMAObjectState *vo = VMA_OBJECT(obj);
-+
-+ if (vo->vma) {
-+ error_setg(errp, "uuid cannot be changed after creation");
-+ return;
-+ }
-+
-+ qemu_uuid_parse(str, &vo->uuid);
-+}
-+
-+static bool vma_object_get_blocked(Object *obj, Error **errp)
-+{
-+ VMAObjectState *vo = VMA_OBJECT(obj);
-+
-+ return vo->blocked;
-+}
-+
-+static void vma_object_set_blocked(Object *obj, bool blocked, Error **errp)
-+{
-+ VMAObjectState *vo = VMA_OBJECT(obj);
-+
-+ (void)errp;
-+
-+ vo->blocked = blocked;
-+}
-+
-+void vma_object_add_config_file(Object *obj, const char *name,
-+ const char *contents, size_t len,
-+ Error **errp)
-+{
-+ int rc;
-+ VMAObjectState *vo = VMA_OBJECT(obj);
-+
-+ if (!vo || !vo->vma) {
-+ error_setg(errp, "not a valid vma object to add config files to");
-+ return;
-+ }
-+
-+ rc = VMAWriter_addConfigFile(vo->vma, name, contents, len);
-+ if (rc < 0) {
-+ error_setg_errno(errp, -rc, "failed to add config file to VMA");
-+ return;
-+ }
-+}
-+
-+static void vma_object_init(Object *obj)
-+{
-+ VMAObjectState *vo = VMA_OBJECT(obj);
-+ (void)!vo;
-+
-+ object_property_add_str(obj, "filename",
-+ vma_object_get_filename, vma_object_set_filename,
-+ NULL);
-+ object_property_add_str(obj, "uuid",
-+ vma_object_get_uuid, vma_object_set_uuid,
-+ NULL);
-+ object_property_add_bool(obj, "blocked",
-+ vma_object_get_blocked, vma_object_set_blocked,
-+ NULL);
-+}
-+
-+static void vma_object_finalize(Object *obj)
-+{
-+ VMAObjectState *vo = VMA_OBJECT(obj);
-+ VMAObjectClass *voc = VMA_OBJECT_GET_CLASS(obj);
-+ (void)!voc;
-+
-+ qemu_mutex_destroy(&vo->mutex);
-+
-+ VMAWriter_destroy(vo->vma, true);
-+ g_free(vo->filename);
-+}
-+
-+static const TypeInfo vma_object_info = {
-+ .name = TYPE_VMA_OBJECT,
-+ .parent = TYPE_OBJECT,
-+ .class_size = sizeof(VMAObjectClass),
-+ .class_init = vma_object_class_init,
-+ .instance_size = sizeof(VMAObjectState),
-+ .instance_init = vma_object_init,
-+ .instance_finalize = vma_object_finalize,
-+ .interfaces = (InterfaceInfo[]) {
-+ { TYPE_USER_CREATABLE },
-+ { }
-+ }
-+};
-+
-+static void register_types(void)
-+{
-+ type_register_static(&vma_object_info);
-+}
-+
-+type_init(register_types);
-+
-+typedef struct {
-+ VMAObjectState *vma_obj;
-+ char *name;
-+ size_t device_id;
-+ uint64_t byte_size;
-+} BDRVVMAState;
-+
-+static void qemu_vma_parse_filename(const char *filename, QDict *options,
-+ Error **errp)
-+{
-+ char *sep;
-+
-+ sep = strchr(filename, '/');
-+ if (!sep || sep == filename) {
-+ error_setg(errp, "VMA filename should be <vma-object>/<device-name>");
-+ return;
-+ }
-+
-+ qdict_put(options, "vma", qstring_from_substr(filename, 0, sep-filename-1));
-+
-+ while (*sep && *sep == '/')
-+ ++sep;
-+ if (!*sep) {
-+ error_setg(errp, "missing device name\n");
-+ return;
-+ }
-+
-+ qdict_put(options, "name", qstring_from_str(sep));
-+}
-+
-+static QemuOptsList runtime_opts = {
-+ .name = "vma-drive",
-+ .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
-+ .desc = {
-+ {
-+ .name = "vma",
-+ .type = QEMU_OPT_STRING,
-+ .help = "VMA Object name",
-+ },
-+ {
-+ .name = "name",
-+ .type = QEMU_OPT_STRING,
-+ .help = "VMA device name",
-+ },
-+ {
-+ .name = BLOCK_OPT_SIZE,
-+ .type = QEMU_OPT_SIZE,
-+ .help = "Virtual disk size"
-+ },
-+ { /* end of list */ }
-+ },
-+};
-+static int qemu_vma_open(BlockDriverState *bs, QDict *options, int flags,
-+ Error **errp)
-+{
-+ Error *local_err = NULL;
-+ BDRVVMAState *s = bs->opaque;
-+ QemuOpts *opts;
-+ const char *vma_id, *device_name;
-+ ssize_t dev_id;
-+ int64_t bytes = 0;
-+ int ret;
-+
-+ opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
-+ qemu_opts_absorb_qdict(opts, options, &local_err);
-+ if (local_err) {
-+ error_propagate(errp, local_err);
-+ ret = -EINVAL;
-+ goto failed_opts;
-+ }
-+
-+ bytes = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
-+ BDRV_SECTOR_SIZE);
-+
-+ vma_id = qemu_opt_get(opts, "vma");
-+ device_name = qemu_opt_get(opts, "name");
-+
-+ VMAObjectState *vma = vma_by_id(vma_id);
-+ if (!vma) {
-+ ret = -EINVAL;
-+ error_setg(errp, "no such VMA object: %s", vma_id);
-+ goto failed_opts;
-+ }
-+
-+ dev_id = VMAWriter_findDevice(vma->vma, device_name);
-+ if (dev_id >= 0) {
-+ error_setg(errp, "drive already exists in VMA object");
-+ ret = -EIO;
-+ goto failed_opts;
-+ }
-+
-+ dev_id = VMAWriter_addDevice(vma->vma, device_name, (uint64_t)bytes);
-+ if (dev_id < 0) {
-+ error_setg_errno(errp, -dev_id, "failed to add VMA device");
-+ ret = -EIO;
-+ goto failed_opts;
-+ }
-+
-+ object_ref(OBJECT(vma));
-+ s->vma_obj = vma;
-+ s->name = g_strdup(device_name);
-+ s->device_id = (size_t)dev_id;
-+ s->byte_size = bytes;
-+
-+ ret = 0;
-+
-+failed_opts:
-+ qemu_opts_del(opts);
-+ return ret;
-+}
-+
-+static void qemu_vma_close(BlockDriverState *bs)
-+{
-+ BDRVVMAState *s = bs->opaque;
-+
-+ (void)VMAWriter_finishDevice(s->vma_obj->vma, s->device_id);
-+ object_unref(OBJECT(s->vma_obj));
-+
-+ g_free(s->name);
-+}
-+
-+static int64_t qemu_vma_getlength(BlockDriverState *bs)
-+{
-+ BDRVVMAState *s = bs->opaque;
-+
-+ return s->byte_size;
-+}
-+
-+static coroutine_fn int qemu_vma_co_writev(BlockDriverState *bs,
-+ int64_t sector_num,
-+ int nb_sectors,
-+ QEMUIOVector *qiov)
-+{
-+ size_t i;
-+ ssize_t rc;
-+ BDRVVMAState *s = bs->opaque;
-+ VMAObjectState *vo = s->vma_obj;
-+ off_t offset = sector_num * BDRV_SECTOR_SIZE;
-+
-+ qemu_mutex_lock(&vo->mutex);
-+ if (vo->blocked) {
-+ return -EPERM;
-+ }
-+ for (i = 0; i != qiov->niov; ++i) {
-+ const struct iovec *v = &qiov->iov[i];
-+ size_t blocks = v->iov_len / VMA_BLOCK_SIZE;
-+ if (blocks * VMA_BLOCK_SIZE != v->iov_len) {
-+ return -EIO;
-+ }
-+ rc = VMAWriter_writeBlocks(vo->vma, s->device_id,
-+ v->iov_base, blocks, offset);
-+ if (errno) {
-+ return -errno;
-+ }
-+ if (rc != blocks) {
-+ return -EIO;
-+ }
-+ offset += v->iov_len;
-+ }
-+ qemu_mutex_unlock(&vo->mutex);
-+ return 0;
-+}
-+
-+static int qemu_vma_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
-+{
-+ bdi->cluster_size = VMA_CLUSTER_SIZE;
-+ bdi->unallocated_blocks_are_zero = true;
-+ bdi->can_write_zeroes_with_unmap = false;
-+ return 0;
-+}
-+
-+static BlockDriver bdrv_vma_drive = {
-+ .format_name = "vma-drive",
-+ .instance_size = sizeof(BDRVVMAState),
-+
-+#if 0
-+ .bdrv_create = qemu_vma_create,
-+ .create_opts = &qemu_vma_create_opts,
-+#endif
-+
-+ .bdrv_parse_filename = qemu_vma_parse_filename,
-+ .bdrv_file_open = qemu_vma_open,
-+
-+ .bdrv_close = qemu_vma_close,
-+ .bdrv_has_zero_init = bdrv_has_zero_init_1,
-+ .bdrv_getlength = qemu_vma_getlength,
-+ .bdrv_get_info = qemu_vma_get_info,
-+
-+ .bdrv_co_writev = qemu_vma_co_writev,
-+};
-+
-+static void bdrv_vma_init(void)
-+{
-+ bdrv_register(&bdrv_vma_drive);
-+}
-+
-+block_init(bdrv_vma_init);
-diff --git a/blockdev.c b/blockdev.c
-index 9b6cfafd33..534c00f5da 100644
---- a/blockdev.c
-+++ b/blockdev.c
-@@ -31,10 +31,12 @@
- */
-
- #include "qemu/osdep.h"
-+#include "qemu/uuid.h"
- #include "sysemu/block-backend.h"
- #include "sysemu/blockdev.h"
- #include "hw/block/block.h"
- #include "block/blockjob.h"
-+#include "block/blockjob_int.h"
- #include "block/throttle-groups.h"
- #include "monitor/monitor.h"
- #include "qemu/error-report.h"
-@@ -2932,6 +2934,502 @@ out:
- aio_context_release(aio_context);
- }
-
-+/* PVE backup related function */
-+
-+static struct PVEBackupState {
-+ Error *error;
-+ bool cancel;
-+ QemuUUID uuid;
-+ char uuid_str[37];
-+ int64_t speed;
-+ time_t start_time;
-+ time_t end_time;
-+ char *backup_file;
-+ Object *vmaobj;
-+ GList *di_list;
-+ size_t next_job;
-+ size_t total;
-+ size_t transferred;
-+ size_t zero_bytes;
-+} backup_state;
-+
-+typedef struct PVEBackupDevInfo {
-+ BlockDriverState *bs;
-+ size_t size;
-+ uint8_t dev_id;
-+ bool completed;
-+ char targetfile[PATH_MAX];
-+ BlockDriverState *target;
-+} PVEBackupDevInfo;
-+
-+static void pvebackup_run_next_job(void);
-+
-+static void pvebackup_cleanup(void)
-+{
-+ backup_state.end_time = time(NULL);
-+
-+ if (backup_state.vmaobj) {
-+ object_unparent(backup_state.vmaobj);
-+ backup_state.vmaobj = NULL;
-+ }
-+
-+ if (backup_state.di_list) {
-+ GList *l = backup_state.di_list;
-+ while (l) {
-+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+ l = g_list_next(l);
-+ g_free(di);
-+ }
-+ g_list_free(backup_state.di_list);
-+ backup_state.di_list = NULL;
-+ }
-+}
-+
-+static void pvebackup_complete_cb(void *opaque, int ret)
-+{
-+ PVEBackupDevInfo *di = opaque;
-+
-+ di->completed = true;
-+
-+ if (ret < 0 && !backup_state.error) {
-+ error_setg(&backup_state.error, "job failed with err %d - %s",
-+ ret, strerror(-ret));
-+ }
-+
-+ di->bs = NULL;
-+ di->target = NULL;
-+
-+ if (backup_state.vmaobj) {
-+ object_unparent(backup_state.vmaobj);
-+ backup_state.vmaobj = NULL;
-+ }
-+
-+ if (!backup_state.cancel) {
-+ pvebackup_run_next_job();
-+ }
-+}
-+
-+static void pvebackup_cancel(void *opaque)
-+{
-+ backup_state.cancel = true;
-+
-+ if (!backup_state.error) {
-+ error_setg(&backup_state.error, "backup cancelled");
-+ }
-+
-+ if (backup_state.vmaobj) {
-+ Error *err;
-+ /* make sure vma writer does not block anymore */
-+ if (!object_set_props(backup_state.vmaobj, &err, "blocked", "yes", NULL)) {
-+ if (err) {
-+ error_report_err(err);
-+ }
-+ }
-+ }
-+
-+ GList *l = backup_state.di_list;
-+ while (l) {
-+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+ l = g_list_next(l);
-+ if (!di->completed && di->bs) {
-+ BlockJob *job = di->bs->job;
-+ if (job) {
-+ if (!di->completed) {
-+ block_job_cancel_sync(job);
-+ }
-+ }
-+ }
-+ }
-+
-+ pvebackup_cleanup();
-+}
-+
-+void qmp_backup_cancel(Error **errp)
-+{
-+ Coroutine *co = qemu_coroutine_create(pvebackup_cancel, NULL);
-+ qemu_coroutine_enter(co);
-+
-+ while (backup_state.vmaobj) {
-+ /* FIXME: Find something better for this */
-+ aio_poll(qemu_get_aio_context(), true);
-+ }
-+}
-+
-+void vma_object_add_config_file(Object *obj, const char *name,
-+ const char *contents, size_t len,
-+ Error **errp);
-+static int config_to_vma(const char *file, BackupFormat format,
-+ Object *vmaobj,
-+ const char *backup_dir,
-+ Error **errp)
-+{
-+ char *cdata = NULL;
-+ gsize clen = 0;
-+ GError *err = NULL;
-+ if (!g_file_get_contents(file, &cdata, &clen, &err)) {
-+ error_setg(errp, "unable to read file '%s'", file);
-+ return 1;
-+ }
-+
-+ char *basename = g_path_get_basename(file);
-+
-+ if (format == BACKUP_FORMAT_VMA) {
-+ vma_object_add_config_file(vmaobj, basename, cdata, clen, errp);
-+ } else if (format == BACKUP_FORMAT_DIR) {
-+ char config_path[PATH_MAX];
-+ snprintf(config_path, PATH_MAX, "%s/%s", backup_dir, basename);
-+ if (!g_file_set_contents(config_path, cdata, clen, &err)) {
-+ error_setg(errp, "unable to write config file '%s'", config_path);
-+ g_free(cdata);
-+ g_free(basename);
-+ return 1;
-+ }
-+ }
-+
-+ g_free(basename);
-+ g_free(cdata);
-+
-+ return 0;
-+}
-+
-+static void pvebackup_run_next_job(void)
-+{
-+ bool cancel = backup_state.error || backup_state.cancel;
-+ GList *next = g_list_nth(backup_state.di_list, backup_state.next_job);
-+ while (next) {
-+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)next->data;
-+ backup_state.next_job++;
-+ if (!di->completed && di->bs && di->bs->job) {
-+ BlockJob *job = di->bs->job;
-+ if (cancel) {
-+ block_job_cancel(job);
-+ } else {
-+ block_job_resume(job);
-+ }
-+ return;
-+ }
-+ next = g_list_next(next);
-+ }
-+ pvebackup_cleanup();
-+}
-+
-+UuidInfo *qmp_backup(const char *backup_file, bool has_format,
-+ BackupFormat format,
-+ bool has_config_file, const char *config_file,
-+ bool has_firewall_file, const char *firewall_file,
-+ bool has_devlist, const char *devlist,
-+ bool has_speed, int64_t speed, Error **errp)
-+{
-+ BlockBackend *blk;
-+ BlockDriverState *bs = NULL;
-+ const char *backup_dir = NULL;
-+ Error *local_err = NULL;
-+ QemuUUID uuid;
-+ gchar **devs = NULL;
-+ GList *di_list = NULL;
-+ GList *l;
-+ UuidInfo *uuid_info;
-+ BlockJob *job;
-+
-+ if (backup_state.di_list || backup_state.vmaobj) {
-+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
-+ "previous backup not finished");
-+ return NULL;
-+ }
-+
-+ /* Todo: try to auto-detect format based on file name */
-+ format = has_format ? format : BACKUP_FORMAT_VMA;
-+
-+ if (has_devlist) {
-+ devs = g_strsplit_set(devlist, ",;:", -1);
-+
-+ gchar **d = devs;
-+ while (d && *d) {
-+ blk = blk_by_name(*d);
-+ if (blk) {
-+ bs = blk_bs(blk);
-+ if (bdrv_is_read_only(bs)) {
-+ error_setg(errp, "Node '%s' is read only", *d);
-+ goto err;
-+ }
-+ if (!bdrv_is_inserted(bs)) {
-+ error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
-+ goto err;
-+ }
-+ PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
-+ di->bs = bs;
-+ di_list = g_list_append(di_list, di);
-+ } else {
-+ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
-+ "Device '%s' not found", *d);
-+ goto err;
-+ }
-+ d++;
-+ }
-+
-+ } else {
-+ BdrvNextIterator it;
-+
-+ bs = NULL;
-+ for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
-+ if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) {
-+ continue;
-+ }
-+
-+ PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
-+ di->bs = bs;
-+ di_list = g_list_append(di_list, di);
-+ }
-+ }
-+
-+ if (!di_list) {
-+ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
-+ goto err;
-+ }
-+
-+ size_t total = 0;
-+
-+ l = di_list;
-+ while (l) {
-+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+ l = g_list_next(l);
-+ if (bdrv_op_is_blocked(di->bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) {
-+ goto err;
-+ }
-+
-+ ssize_t size = bdrv_getlength(di->bs);
-+ if (size < 0) {
-+ error_setg_errno(errp, -di->size, "bdrv_getlength failed");
-+ goto err;
-+ }
-+ di->size = size;
-+ total += size;
-+ }
-+
-+ qemu_uuid_generate(&uuid);
-+
-+ if (format == BACKUP_FORMAT_VMA) {
-+ char uuidstr[UUID_FMT_LEN+1];
-+ qemu_uuid_unparse(&uuid, uuidstr);
-+ uuidstr[UUID_FMT_LEN] = 0;
-+ backup_state.vmaobj =
-+ object_new_with_props("vma", object_get_objects_root(),
-+ "vma-backup-obj", &local_err,
-+ "filename", backup_file,
-+ "uuid", uuidstr,
-+ NULL);
-+ if (!backup_state.vmaobj) {
-+ if (local_err) {
-+ error_propagate(errp, local_err);
-+ }
-+ goto err;
-+ }
-+
-+ l = di_list;
-+ while (l) {
-+ QDict *options = qdict_new();
-+
-+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+ l = g_list_next(l);
-+
-+ const char *devname = bdrv_get_device_name(di->bs);
-+ snprintf(di->targetfile, PATH_MAX, "vma-backup-obj/%s.raw", devname);
-+
-+ qdict_put(options, "driver", qstring_from_str("vma-drive"));
-+ qdict_put(options, "size", qint_from_int(di->size));
-+ di->target = bdrv_open(di->targetfile, NULL, options, BDRV_O_RDWR, &local_err);
-+ if (!di->target) {
-+ error_propagate(errp, local_err);
-+ goto err;
-+ }
-+ }
-+ } else if (format == BACKUP_FORMAT_DIR) {
-+ if (mkdir(backup_file, 0640) != 0) {
-+ error_setg_errno(errp, errno, "can't create directory '%s'\n",
-+ backup_file);
-+ goto err;
-+ }
-+ backup_dir = backup_file;
-+
-+ l = di_list;
-+ while (l) {
-+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+ l = g_list_next(l);
-+
-+ const char *devname = bdrv_get_device_name(di->bs);
-+ snprintf(di->targetfile, PATH_MAX, "%s/%s.raw", backup_dir, devname);
-+
-+ int flags = BDRV_O_RDWR;
-+ bdrv_img_create(di->targetfile, "raw", NULL, NULL, NULL,
-+ di->size, flags, &local_err, false);
-+ if (local_err) {
-+ error_propagate(errp, local_err);
-+ goto err;
-+ }
-+
-+ di->target = bdrv_open(di->targetfile, NULL, NULL, flags, &local_err);
-+ if (!di->target) {
-+ error_propagate(errp, local_err);
-+ goto err;
-+ }
-+ }
-+ } else {
-+ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
-+ goto err;
-+ }
-+
-+ /* add configuration file to archive */
-+ if (has_config_file) {
-+ if(config_to_vma(config_file, format, backup_state.vmaobj, backup_dir, errp) != 0) {
-+ goto err;
-+ }
-+ }
-+
-+ /* add firewall file to archive */
-+ if (has_firewall_file) {
-+ if(config_to_vma(firewall_file, format, backup_state.vmaobj, backup_dir, errp) != 0) {
-+ goto err;
-+ }
-+ }
-+ /* initialize global backup_state now */
-+
-+ backup_state.cancel = false;
-+
-+ if (backup_state.error) {
-+ error_free(backup_state.error);
-+ backup_state.error = NULL;
-+ }
-+
-+ backup_state.speed = (has_speed && speed > 0) ? speed : 0;
-+
-+ backup_state.start_time = time(NULL);
-+ backup_state.end_time = 0;
-+
-+ if (backup_state.backup_file) {
-+ g_free(backup_state.backup_file);
-+ }
-+ backup_state.backup_file = g_strdup(backup_file);
-+
-+ memcpy(&backup_state.uuid, &uuid, sizeof(uuid));
-+ qemu_uuid_unparse(&uuid, backup_state.uuid_str);
-+
-+ backup_state.di_list = di_list;
-+ backup_state.next_job = 0;
-+
-+ backup_state.total = total;
-+ backup_state.transferred = 0;
-+ backup_state.zero_bytes = 0;
-+
-+ /* start all jobs (paused state) */
-+ l = di_list;
-+ while (l) {
-+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+ l = g_list_next(l);
-+
-+ job = backup_job_create(NULL, di->bs, di->target, speed, MIRROR_SYNC_MODE_FULL, NULL,
-+ false, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
-+ BLOCK_JOB_DEFAULT,
-+ pvebackup_complete_cb, di, 2, NULL, &local_err);
-+ if (di->target) {
-+ bdrv_unref(di->target);
-+ di->target = NULL;
-+ }
-+ if (!job || local_err != NULL) {
-+ error_setg(&backup_state.error, "backup_job_create failed");
-+ pvebackup_cancel(NULL);
-+ } else {
-+ block_job_start(job);
-+ }
-+ }
-+
-+ if (!backup_state.error) {
-+ pvebackup_run_next_job(); // run one job
-+ }
-+
-+ uuid_info = g_malloc0(sizeof(*uuid_info));
-+ uuid_info->UUID = g_strdup(backup_state.uuid_str);
-+
-+ return uuid_info;
-+
-+err:
-+
-+ l = di_list;
-+ while (l) {
-+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+ l = g_list_next(l);
-+
-+ if (di->target) {
-+ bdrv_unref(di->target);
-+ }
-+
-+ if (di->targetfile[0]) {
-+ unlink(di->targetfile);
-+ }
-+ g_free(di);
-+ }
-+ g_list_free(di_list);
-+
-+ if (devs) {
-+ g_strfreev(devs);
-+ }
-+
-+ if (backup_state.vmaobj) {
-+ object_unparent(backup_state.vmaobj);
-+ backup_state.vmaobj = NULL;
-+ }
-+
-+ if (backup_dir) {
-+ rmdir(backup_dir);
-+ }
-+
-+ return NULL;
-+}
-+
-+BackupStatus *qmp_query_backup(Error **errp)
-+{
-+ BackupStatus *info = g_malloc0(sizeof(*info));
-+
-+ if (!backup_state.start_time) {
-+ /* not started, return {} */
-+ return info;
-+ }
-+
-+ info->has_status = true;
-+ info->has_start_time = true;
-+ info->start_time = backup_state.start_time;
-+
-+ if (backup_state.backup_file) {
-+ info->has_backup_file = true;
-+ info->backup_file = g_strdup(backup_state.backup_file);
-+ }
-+
-+ info->has_uuid = true;
-+ info->uuid = g_strdup(backup_state.uuid_str);
-+
-+ if (backup_state.end_time) {
-+ if (backup_state.error) {
-+ info->status = g_strdup("error");
-+ info->has_errmsg = true;
-+ info->errmsg = g_strdup(error_get_pretty(backup_state.error));
-+ } else {
-+ info->status = g_strdup("done");
-+ }
-+ info->has_end_time = true;
-+ info->end_time = backup_state.end_time;
-+ } else {
-+ info->status = g_strdup("active");
-+ }
-+
-+ info->has_total = true;
-+ info->total = backup_state.total;
-+ info->has_zero_bytes = true;
-+ info->zero_bytes = backup_state.zero_bytes;
-+ info->has_transferred = true;
-+ info->transferred = backup_state.transferred;
-+
-+ return info;
-+}
-+
- void qmp_block_stream(bool has_job_id, const char *job_id, const char *device,
- bool has_base, const char *base,
- bool has_base_node, const char *base_node,
-diff --git a/configure b/configure
-index be4d326ae0..841f7a8fae 100755
---- a/configure
-+++ b/configure
-@@ -320,6 +320,7 @@ numa=""
- tcmalloc="no"
- jemalloc="no"
- replication="yes"
-+vma=""
-
- supported_cpu="no"
- supported_os="no"
-@@ -1183,6 +1184,10 @@ for opt do
- ;;
- --enable-replication) replication="yes"
- ;;
-+ --disable-vma) vma="no"
-+ ;;
-+ --enable-vma) vma="yes"
-+ ;;
- *)
- echo "ERROR: unknown option $opt"
- echo "Try '$0 --help' for more information"
-@@ -1427,6 +1432,7 @@ disabled with --disable-FEATURE, default is enabled if available:
- xfsctl xfsctl support
- qom-cast-debug cast debugging support
- tools build qemu-io, qemu-nbd and qemu-image tools
-+ vma VMA archive backend
-
- NOTE: The object files are built at the place where configure is launched
- EOF
-@@ -3705,6 +3711,23 @@ EOF
- fi
-
- ##########################################
-+# vma probe
-+if test "$vma" != "no" ; then
-+ if $pkg_config --exact-version=0.1.0 vma; then
-+ vma="yes"
-+ vma_cflags=$($pkg_config --cflags vma)
-+ vma_libs=$($pkg_config --libs vma)
-+ else
-+ if test "$vma" = "yes" ; then
-+ feature_not_found "VMA Archive backend support" \
-+ "Install libvma devel"
-+ fi
-+ vma="no"
-+ fi
-+fi
-+
-+
-+##########################################
- # signalfd probe
- signalfd="no"
- cat > $TMPC << EOF
-@@ -5146,6 +5169,7 @@ echo "tcmalloc support $tcmalloc"
- echo "jemalloc support $jemalloc"
- echo "avx2 optimization $avx2_opt"
- echo "replication support $replication"
-+echo "VMA support $vma"
-
- if test "$sdl_too_old" = "yes"; then
- echo "-> Your SDL version is too old - please upgrade to have SDL support"
-@@ -5703,6 +5727,12 @@ if test "$libssh2" = "yes" ; then
- echo "LIBSSH2_LIBS=$libssh2_libs" >> $config_host_mak
- fi
-
-+if test "$vma" = "yes" ; then
-+ echo "CONFIG_VMA=y" >> $config_host_mak
-+ echo "VMA_CFLAGS=$vma_cflags" >> $config_host_mak
-+ echo "VMA_LIBS=$vma_libs" >> $config_host_mak
-+fi
-+
- # USB host support
- if test "$libusb" = "yes"; then
- echo "HOST_USB=libusb legacy" >> $config_host_mak
-diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
-index 5fc57a2210..3b5a0f95e4 100644
---- a/hmp-commands-info.hx
-+++ b/hmp-commands-info.hx
-@@ -487,6 +487,19 @@ STEXI
- Show CPU statistics.
- ETEXI
-
-+ {
-+ .name = "backup",
-+ .args_type = "",
-+ .params = "",
-+ .help = "show backup status",
-+ .cmd = hmp_info_backup,
-+ },
-+
-+STEXI
-+@item info backup
-+show backup status
-+ETEXI
-+
- #if defined(CONFIG_SLIRP)
- {
- .name = "usernet",
-diff --git a/hmp-commands.hx b/hmp-commands.hx
-index 58940a762b..a2867b56f2 100644
---- a/hmp-commands.hx
-+++ b/hmp-commands.hx
-@@ -87,6 +87,37 @@ STEXI
- Copy data from a backing file into a block device.
- ETEXI
-
-+ {
-+ .name = "backup",
-+ .args_type = "directory:-d,backupfile:s,speed:o?,devlist:s?",
-+ .params = "[-d] backupfile [speed [devlist]]",
-+ .help = "create a VM Backup."
-+ "\n\t\t\t Use -d to dump data into a directory instead"
-+ "\n\t\t\t of using VMA format.",
-+ .cmd = hmp_backup,
-+ },
-+
-+STEXI
-+@item backup
-+@findex backup
-+Create a VM backup.
-+ETEXI
-+
-+ {
-+ .name = "backup_cancel",
-+ .args_type = "",
-+ .params = "",
-+ .help = "cancel the current VM backup",
-+ .cmd = hmp_backup_cancel,
-+ },
-+
-+STEXI
-+@item backup_cancel
-+@findex backup_cancel
-+Cancel the current VM backup.
-+
-+ETEXI
-+
- {
- .name = "block_job_set_speed",
- .args_type = "device:B,speed:o",
-diff --git a/hmp.c b/hmp.c
-index f725d061e6..12f1f46125 100644
---- a/hmp.c
-+++ b/hmp.c
-@@ -151,6 +151,44 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
- qapi_free_MouseInfoList(mice_list);
- }
-
-+void hmp_info_backup(Monitor *mon, const QDict *qdict)
-+{
-+ BackupStatus *info;
-+
-+ info = qmp_query_backup(NULL);
-+ if (info->has_status) {
-+ if (info->has_errmsg) {
-+ monitor_printf(mon, "Backup status: %s - %s\n",
-+ info->status, info->errmsg);
-+ } else {
-+ monitor_printf(mon, "Backup status: %s\n", info->status);
-+ }
-+ }
-+
-+ if (info->has_backup_file) {
-+ monitor_printf(mon, "Start time: %s", ctime(&info->start_time));
-+ if (info->end_time) {
-+ monitor_printf(mon, "End time: %s", ctime(&info->end_time));
-+ }
-+
-+ int per = (info->has_total && info->total &&
-+ info->has_transferred && info->transferred) ?
-+ (info->transferred * 100)/info->total : 0;
-+ int zero_per = (info->has_total && info->total &&
-+ info->has_zero_bytes && info->zero_bytes) ?
-+ (info->zero_bytes * 100)/info->total : 0;
-+ monitor_printf(mon, "Backup file: %s\n", info->backup_file);
-+ monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
-+ monitor_printf(mon, "Total size: %zd\n", info->total);
-+ monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
-+ info->transferred, per);
-+ monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
-+ info->zero_bytes, zero_per);
-+ }
-+
-+ qapi_free_BackupStatus(info);
-+}
-+
- void hmp_info_migrate(Monitor *mon, const QDict *qdict)
- {
- MigrationInfo *info;
-@@ -1613,6 +1651,31 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict)
- hmp_handle_error(mon, &error);
- }
-
-+void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
-+{
-+ Error *error = NULL;
-+
-+ qmp_backup_cancel(&error);
-+
-+ hmp_handle_error(mon, &error);
-+}
-+
-+void hmp_backup(Monitor *mon, const QDict *qdict)
-+{
-+ Error *error = NULL;
-+
-+ int dir = qdict_get_try_bool(qdict, "directory", 0);
-+ const char *backup_file = qdict_get_str(qdict, "backupfile");
-+ const char *devlist = qdict_get_try_str(qdict, "devlist");
-+ int64_t speed = qdict_get_try_int(qdict, "speed", 0);
-+
-+ qmp_backup(backup_file, true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
-+ false, NULL, false, NULL, !!devlist,
-+ devlist, qdict_haskey(qdict, "speed"), speed, &error);
-+
-+ hmp_handle_error(mon, &error);
-+}
-+
- void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict)
- {
- Error *error = NULL;
-diff --git a/hmp.h b/hmp.h
-index 0497afbf65..8c1b4846b3 100644
---- a/hmp.h
-+++ b/hmp.h
-@@ -31,6 +31,7 @@ void hmp_info_migrate(Monitor *mon, const QDict *qdict);
- void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict);
- void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict);
- void hmp_info_migrate_cache_size(Monitor *mon, const QDict *qdict);
-+void hmp_info_backup(Monitor *mon, const QDict *qdict);
- void hmp_info_cpus(Monitor *mon, const QDict *qdict);
- void hmp_info_block(Monitor *mon, const QDict *qdict);
- void hmp_info_blockstats(Monitor *mon, const QDict *qdict);
-@@ -80,6 +81,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict);
- void hmp_change(Monitor *mon, const QDict *qdict);
- void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict);
- void hmp_block_stream(Monitor *mon, const QDict *qdict);
-+void hmp_backup(Monitor *mon, const QDict *qdict);
-+void hmp_backup_cancel(Monitor *mon, const QDict *qdict);
- void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict);
- void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
- void hmp_block_job_pause(Monitor *mon, const QDict *qdict);
-diff --git a/qapi-schema.json b/qapi-schema.json
-index 5e82933ca1..b20020a054 100644
---- a/qapi-schema.json
-+++ b/qapi-schema.json
-@@ -571,6 +571,97 @@
- { 'command': 'query-events', 'returns': ['EventInfo'] }
-
- ##
-+# @BackupStatus:
-+#
-+# Detailed backup status.
-+#
-+# @status: string describing the current backup status.
-+# This can be 'active', 'done', 'error'. If this field is not
-+# returned, no backup process has been initiated
-+#
-+# @errmsg: error message (only returned if status is 'error')
-+#
-+# @total: total amount of bytes involved in the backup process
-+#
-+# @transferred: amount of bytes already backed up.
-+#
-+# @zero-bytes: amount of 'zero' bytes detected.
-+#
-+# @start-time: time (epoch) when backup job started.
-+#
-+# @end-time: time (epoch) when backup job finished.
-+#
-+# @backup-file: backup file name
-+#
-+# @uuid: uuid for this backup job
-+#
-+##
-+{ 'struct': 'BackupStatus',
-+ 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
-+ '*transferred': 'int', '*zero-bytes': 'int',
-+ '*start-time': 'int', '*end-time': 'int',
-+ '*backup-file': 'str', '*uuid': 'str' } }
-+
-+##
-+# @BackupFormat:
-+#
-+# An enumeration of supported backup formats.
-+#
-+# @vma: Proxmox vma backup format
-+##
-+{ 'enum': 'BackupFormat',
-+ 'data': [ 'vma', 'dir' ] }
-+
-+##
-+# @backup:
-+#
-+# Starts a VM backup.
-+#
-+# @backup-file: the backup file name
-+#
-+# @format: format of the backup file
-+#
-+# @config-file: a configuration file to include into
-+# the backup archive.
-+#
-+# @speed: the maximum speed, in bytes per second
-+#
-+# @devlist: list of block device names (separated by ',', ';'
-+# or ':'). By default the backup includes all writable block devices.
-+#
-+# Returns: the uuid of the backup job
-+#
-+##
-+{ 'command': 'backup', 'data': { 'backup-file': 'str',
-+ '*format': 'BackupFormat',
-+ '*config-file': 'str',
-+ '*firewall-file': 'str',
-+ '*devlist': 'str', '*speed': 'int' },
-+ 'returns': 'UuidInfo' }
-+
-+##
-+# @query-backup:
-+#
-+# Returns information about current/last backup task.
-+#
-+# Returns: @BackupStatus
-+#
-+##
-+{ 'command': 'query-backup', 'returns': 'BackupStatus' }
-+
-+##
-+# @backup-cancel:
-+#
-+# Cancel the current executing backup process.
-+#
-+# Returns: nothing on success
-+#
-+# Notes: This command succeeds even if there is no backup process running.
-+#
-+##
-+{ 'command': 'backup-cancel' }
-+
-+##
- # @MigrationStats:
- #
- # Detailed migration status.
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index 7ce90ec940..b0ffd3de4d 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -2118,7 +2118,7 @@
- 'host_device', 'http', 'https', 'iscsi', 'luks', 'nbd', 'nfs',
- 'null-aio', 'null-co', 'parallels', 'qcow', 'qcow2', 'qed',
- 'quorum', 'raw', 'rbd', 'replication', 'sheepdog', 'ssh',
-- 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat' ] }
-+ 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat', 'vma-drive' ] }
-
- ##
- # @BlockdevOptionsFile:
-@@ -2895,6 +2895,21 @@
- 'data': { '*offset': 'int', '*size': 'int' } }
-
- ##
-+# @BlockdevOptionsVMADrive:
-+#
-+# Driver specific block device options for VMA Drives
-+#
-+# @filename: vma-drive path
-+#
-+# @size: drive size in bytes
-+#
-+# Since: 2.9
-+##
-+{ 'struct': 'BlockdevOptionsVMADrive',
-+ 'data': { 'filename': 'str',
-+ 'size': 'int' } }
-+
-+##
- # @BlockdevOptions:
- #
- # Options for creating a block device. Many options are available for all
-@@ -2956,7 +2971,8 @@
- 'vhdx': 'BlockdevOptionsGenericFormat',
- 'vmdk': 'BlockdevOptionsGenericCOWFormat',
- 'vpc': 'BlockdevOptionsGenericFormat',
-- 'vvfat': 'BlockdevOptionsVVFAT'
-+ 'vvfat': 'BlockdevOptionsVVFAT',
-+ 'vma-drive': 'BlockdevOptionsVMADrive'
- } }
-
- ##
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Mon, 7 Aug 2017 08:51:16 +0200
-Subject: [PATCH] adding old vma files
-
----
- Makefile | 3 +-
- Makefile.objs | 1 +
- block/backup.c | 132 ++++---
- block/replication.c | 1 +
- blockdev.c | 249 +++++++++-----
- blockjob.c | 11 +-
- include/block/block_int.h | 4 +
- vma-reader.c | 857 ++++++++++++++++++++++++++++++++++++++++++++++
- vma-writer.c | 771 +++++++++++++++++++++++++++++++++++++++++
- vma.c | 757 ++++++++++++++++++++++++++++++++++++++++
- vma.h | 149 ++++++++
- 11 files changed, 2802 insertions(+), 133 deletions(-)
- create mode 100644 vma-reader.c
- create mode 100644 vma-writer.c
- create mode 100644 vma.c
- create mode 100644 vma.h
-
-diff --git a/Makefile b/Makefile
-index 6c359b2f86..edbc8b50f0 100644
---- a/Makefile
-+++ b/Makefile
-@@ -284,7 +284,7 @@ ifneq ($(wildcard config-host.mak),)
- include $(SRC_PATH)/tests/Makefile.include
- endif
-
--all: $(DOCS) $(TOOLS) $(HELPERS-y) recurse-all modules
-+all: $(DOCS) $(TOOLS) vma$(EXESUF) $(HELPERS-y) recurse-all modules
-
- qemu-version.h: FORCE
- $(call quiet-command, \
-@@ -377,6 +377,7 @@ qemu-img.o: qemu-img-cmds.h
- qemu-img$(EXESUF): qemu-img.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
- qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
- qemu-io$(EXESUF): qemu-io.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
-+vma$(EXESUF): vma.o vma-reader.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
-
- qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o $(COMMON_LDADDS)
-
-diff --git a/Makefile.objs b/Makefile.objs
-index fbfbbb7f70..f5f8dbab3b 100644
---- a/Makefile.objs
-+++ b/Makefile.objs
-@@ -14,6 +14,7 @@ block-obj-y += block.o blockjob.o
- block-obj-y += block/
- block-obj-y += qemu-io-cmds.o
- block-obj-$(CONFIG_REPLICATION) += replication.o
-+block-obj-y += vma-writer.o
-
- block-obj-m = block/
-
-diff --git a/block/backup.c b/block/backup.c
-index 1ede70c061..7c5febc434 100644
---- a/block/backup.c
-+++ b/block/backup.c
-@@ -36,6 +36,7 @@ typedef struct BackupBlockJob {
- BdrvDirtyBitmap *sync_bitmap;
- MirrorSyncMode sync_mode;
- RateLimit limit;
-+ BackupDumpFunc *dump_cb;
- BlockdevOnError on_source_error;
- BlockdevOnError on_target_error;
- CoRwlock flush_rwlock;
-@@ -145,13 +146,24 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
- goto out;
- }
-
-+ int64_t start_sec = start * sectors_per_cluster;
- if (buffer_is_zero(iov.iov_base, iov.iov_len)) {
-- ret = blk_co_pwrite_zeroes(job->target, start * job->cluster_size,
-- bounce_qiov.size, BDRV_REQ_MAY_UNMAP);
-+ if (job->dump_cb) {
-+ ret = job->dump_cb(job->common.opaque, job->target, start_sec, n, NULL);
-+ }
-+ if (job->target) {
-+ ret = blk_co_pwrite_zeroes(job->target, start * job->cluster_size,
-+ bounce_qiov.size, BDRV_REQ_MAY_UNMAP);
-+ }
- } else {
-- ret = blk_co_pwritev(job->target, start * job->cluster_size,
-- bounce_qiov.size, &bounce_qiov,
-- job->compress ? BDRV_REQ_WRITE_COMPRESSED : 0);
-+ if (job->dump_cb) {
-+ ret = job->dump_cb(job->common.opaque, job->target, start_sec, n, bounce_buffer);
-+ }
-+ if (job->target) {
-+ ret = blk_co_pwritev(job->target, start * job->cluster_size,
-+ bounce_qiov.size, &bounce_qiov,
-+ job->compress ? BDRV_REQ_WRITE_COMPRESSED : 0);
-+ }
- }
- if (ret < 0) {
- trace_backup_do_cow_write_fail(job, start, ret);
-@@ -246,6 +258,9 @@ static void backup_abort(BlockJob *job)
- static void backup_clean(BlockJob *job)
- {
- BackupBlockJob *s = container_of(job, BackupBlockJob, common);
-+ if (!s->target) {
-+ return;
-+ }
- assert(s->target);
- blk_unref(s->target);
- s->target = NULL;
-@@ -255,7 +270,9 @@ static void backup_attached_aio_context(BlockJob *job, AioContext *aio_context)
- {
- BackupBlockJob *s = container_of(job, BackupBlockJob, common);
-
-- blk_set_aio_context(s->target, aio_context);
-+ if (s->target) {
-+ blk_set_aio_context(s->target, aio_context);
-+ }
- }
-
- void backup_do_checkpoint(BlockJob *job, Error **errp)
-@@ -330,9 +347,11 @@ static BlockErrorAction backup_error_action(BackupBlockJob *job,
- if (read) {
- return block_job_error_action(&job->common, job->on_source_error,
- true, error);
-- } else {
-+ } else if (job->target) {
- return block_job_error_action(&job->common, job->on_target_error,
- false, error);
-+ } else {
-+ return BLOCK_ERROR_ACTION_REPORT;
- }
- }
-
-@@ -557,6 +576,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
- BlockdevOnError on_source_error,
- BlockdevOnError on_target_error,
- int creation_flags,
-+ BackupDumpFunc *dump_cb,
- BlockCompletionFunc *cb, void *opaque,
- int pause_count,
- BlockJobTxn *txn, Error **errp)
-@@ -567,7 +587,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
- int ret;
-
- assert(bs);
-- assert(target);
-+ assert(target || dump_cb);
-
- if (bs == target) {
- error_setg(errp, "Source and target cannot be the same");
-@@ -580,13 +600,13 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
- return NULL;
- }
-
-- if (!bdrv_is_inserted(target)) {
-+ if (target && !bdrv_is_inserted(target)) {
- error_setg(errp, "Device is not inserted: %s",
- bdrv_get_device_name(target));
- return NULL;
- }
-
-- if (compress && target->drv->bdrv_co_pwritev_compressed == NULL) {
-+ if (target && compress && target->drv->bdrv_co_pwritev_compressed == NULL) {
- error_setg(errp, "Compression is not supported for this drive %s",
- bdrv_get_device_name(target));
- return NULL;
-@@ -596,7 +616,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
- return NULL;
- }
-
-- if (bdrv_op_is_blocked(target, BLOCK_OP_TYPE_BACKUP_TARGET, errp)) {
-+ if (target && bdrv_op_is_blocked(target, BLOCK_OP_TYPE_BACKUP_TARGET, errp)) {
- return NULL;
- }
-
-@@ -636,15 +656,18 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
- goto error;
- }
-
-- /* The target must match the source in size, so no resize here either */
-- job->target = blk_new(BLK_PERM_WRITE,
-- BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE |
-- BLK_PERM_WRITE_UNCHANGED | BLK_PERM_GRAPH_MOD);
-- ret = blk_insert_bs(job->target, target, errp);
-- if (ret < 0) {
-- goto error;
-+ if (target) {
-+ /* The target must match the source in size, so no resize here either */
-+ job->target = blk_new(BLK_PERM_WRITE,
-+ BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE |
-+ BLK_PERM_WRITE_UNCHANGED | BLK_PERM_GRAPH_MOD);
-+ ret = blk_insert_bs(job->target, target, errp);
-+ if (ret < 0) {
-+ goto error;
-+ }
- }
-
-+ job->dump_cb = dump_cb;
- job->on_source_error = on_source_error;
- job->on_target_error = on_target_error;
- job->sync_mode = sync_mode;
-@@ -652,38 +675,55 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
- sync_bitmap : NULL;
- job->compress = compress;
-
-- /* If there is no backing file on the target, we cannot rely on COW if our
-- * backup cluster size is smaller than the target cluster size. Even for
-- * targets with a backing file, try to avoid COW if possible. */
-- ret = bdrv_get_info(target, &bdi);
-- if (ret == -ENOTSUP && !target->backing) {
-- /* Cluster size is not defined */
-- error_report("WARNING: The target block device doesn't provide "
-- "information about the block size and it doesn't have a "
-- "backing file. The default block size of %u bytes is "
-- "used. If the actual block size of the target exceeds "
-- "this default, the backup may be unusable",
-- BACKUP_CLUSTER_SIZE_DEFAULT);
-- job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
-- } else if (ret < 0 && !target->backing) {
-- error_setg_errno(errp, -ret,
-- "Couldn't determine the cluster size of the target image, "
-- "which has no backing file");
-- error_append_hint(errp,
-- "Aborting, since this may create an unusable destination image\n");
-- goto error;
-- } else if (ret < 0 && target->backing) {
-- /* Not fatal; just trudge on ahead. */
-- job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
-+ if (target) {
-+ /* If there is no backing file on the target, we cannot rely on COW if our
-+ * backup cluster size is smaller than the target cluster size. Even for
-+ * targets with a backing file, try to avoid COW if possible. */
-+ ret = bdrv_get_info(target, &bdi);
-+ if (ret == -ENOTSUP && !target->backing) {
-+ /* Cluster size is not defined */
-+ error_report("WARNING: The target block device doesn't provide "
-+ "information about the block size and it doesn't have a "
-+ "backing file. The default block size of %u bytes is "
-+ "used. If the actual block size of the target exceeds "
-+ "this default, the backup may be unusable",
-+ BACKUP_CLUSTER_SIZE_DEFAULT);
-+ job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
-+ } else if (ret < 0 && !target->backing) {
-+ error_setg_errno(errp, -ret,
-+ "Couldn't determine the cluster size of the target image, "
-+ "which has no backing file");
-+ error_append_hint(errp,
-+ "Aborting, since this may create an unusable destination image\n");
-+ goto error;
-+ } else if (ret < 0 && target->backing) {
-+ /* Not fatal; just trudge on ahead. */
-+ job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
-+ } else {
-+ job->cluster_size = MAX(BACKUP_CLUSTER_SIZE_DEFAULT, bdi.cluster_size);
-+ }
- } else {
-- job->cluster_size = MAX(BACKUP_CLUSTER_SIZE_DEFAULT, bdi.cluster_size);
-+ ret = bdrv_get_info(bs, &bdi);
-+ if (ret < 0) {
-+ job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
-+ } else {
-+ /* round down to nearest BACKUP_CLUSTER_SIZE_DEFAULT */
-+ job->cluster_size = (bdi.cluster_size / BACKUP_CLUSTER_SIZE_DEFAULT) * BACKUP_CLUSTER_SIZE_DEFAULT;
-+ if (job->cluster_size == 0) {
-+ /* but we can't go below it */
-+ job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
-+ }
-+ }
- }
-
-- /* Required permissions are already taken with target's blk_new() */
-- block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
-- &error_abort);
-+ if (target) {
-+ /* Required permissions are already taken with target's blk_new() */
-+ block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
-+ &error_abort);
-+ } else {
-+ job->common.pause_count = pause_count;
-+ }
- job->common.len = len;
-- job->common.pause_count = pause_count;
- block_job_txn_add_job(txn, &job->common);
-
- return &job->common;
-diff --git a/block/replication.c b/block/replication.c
-index 1c41d9e6bf..60c6524417 100644
---- a/block/replication.c
-+++ b/block/replication.c
-@@ -531,6 +531,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
- 0, MIRROR_SYNC_MODE_NONE, NULL, false,
- BLOCKDEV_ON_ERROR_REPORT,
- BLOCKDEV_ON_ERROR_REPORT, BLOCK_JOB_INTERNAL,
-+ NULL,
- backup_job_completed, bs, 0, NULL, &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
-diff --git a/blockdev.c b/blockdev.c
-index 534c00f5da..19a82e8774 100644
---- a/blockdev.c
-+++ b/blockdev.c
-@@ -31,7 +31,6 @@
- */
-
- #include "qemu/osdep.h"
--#include "qemu/uuid.h"
- #include "sysemu/block-backend.h"
- #include "sysemu/blockdev.h"
- #include "hw/block/block.h"
-@@ -55,6 +54,7 @@
- #include "qemu/cutils.h"
- #include "qemu/help_option.h"
- #include "qemu/throttle-options.h"
-+#include "vma.h"
-
- static QTAILQ_HEAD(, BlockDriverState) monitor_bdrv_states =
- QTAILQ_HEAD_INITIALIZER(monitor_bdrv_states);
-@@ -2934,20 +2934,44 @@ out:
- aio_context_release(aio_context);
- }
-
-+void block_job_event_cancelled(BlockJob *job);
-+void block_job_event_completed(BlockJob *job, const char *msg);
-+static void block_job_cb(void *opaque, int ret)
-+{
-+ /* Note that this function may be executed from another AioContext besides
-+ * the QEMU main loop. If you need to access anything that assumes the
-+ * QEMU global mutex, use a BH or introduce a mutex.
-+ */
-+
-+ BlockDriverState *bs = opaque;
-+ const char *msg = NULL;
-+
-+ assert(bs->job);
-+
-+ if (ret < 0) {
-+ msg = strerror(-ret);
-+ }
-+
-+ if (block_job_is_cancelled(bs->job)) {
-+ block_job_event_cancelled(bs->job);
-+ } else {
-+ block_job_event_completed(bs->job, msg);
-+ }
-+}
-+
- /* PVE backup related function */
-
- static struct PVEBackupState {
- Error *error;
- bool cancel;
-- QemuUUID uuid;
-+ uuid_t uuid;
- char uuid_str[37];
- int64_t speed;
- time_t start_time;
- time_t end_time;
- char *backup_file;
-- Object *vmaobj;
-+ VmaWriter *vmaw;
- GList *di_list;
-- size_t next_job;
- size_t total;
- size_t transferred;
- size_t zero_bytes;
-@@ -2957,6 +2981,7 @@ typedef struct PVEBackupDevInfo {
- BlockDriverState *bs;
- size_t size;
- uint8_t dev_id;
-+ //bool started;
- bool completed;
- char targetfile[PATH_MAX];
- BlockDriverState *target;
-@@ -2964,13 +2989,79 @@ typedef struct PVEBackupDevInfo {
-
- static void pvebackup_run_next_job(void);
-
-+static int pvebackup_dump_cb(void *opaque, BlockBackend *target,
-+ int64_t sector_num, int n_sectors,
-+ unsigned char *buf)
-+{
-+ PVEBackupDevInfo *di = opaque;
-+
-+ int size = n_sectors * BDRV_SECTOR_SIZE;
-+ if (backup_state.cancel) {
-+ return size; // return success
-+ }
-+
-+ if (sector_num & 0x7f) {
-+ if (!backup_state.error) {
-+ error_setg(&backup_state.error,
-+ "got unaligned write inside backup dump "
-+ "callback (sector %ld)", sector_num);
-+ }
-+ return -1; // not aligned to cluster size
-+ }
-+
-+ int64_t cluster_num = sector_num >> 7;
-+
-+ int ret = -1;
-+
-+ if (backup_state.vmaw) {
-+ size_t zero_bytes = 0;
-+ int64_t remaining = n_sectors * BDRV_SECTOR_SIZE;
-+ while (remaining > 0) {
-+ ret = vma_writer_write(backup_state.vmaw, di->dev_id, cluster_num,
-+ buf, &zero_bytes);
-+ ++cluster_num;
-+ if (buf) {
-+ buf += VMA_CLUSTER_SIZE;
-+ }
-+ if (ret < 0) {
-+ if (!backup_state.error) {
-+ vma_writer_error_propagate(backup_state.vmaw, &backup_state.error);
-+ }
-+ if (di->bs && di->bs->job) {
-+ block_job_cancel(di->bs->job);
-+ }
-+ break;
-+ } else {
-+ backup_state.zero_bytes += zero_bytes;
-+ if (remaining >= VMA_CLUSTER_SIZE) {
-+ backup_state.transferred += VMA_CLUSTER_SIZE;
-+ } else {
-+ backup_state.transferred += remaining;
-+ }
-+ remaining -= VMA_CLUSTER_SIZE;
-+ }
-+ }
-+ } else {
-+ if (!buf) {
-+ backup_state.zero_bytes += size;
-+ }
-+ backup_state.transferred += size;
-+ }
-+
-+ // Note: always return success, because we want that writes succeed anyways.
-+
-+ return size;
-+}
-+
- static void pvebackup_cleanup(void)
- {
- backup_state.end_time = time(NULL);
-
-- if (backup_state.vmaobj) {
-- object_unparent(backup_state.vmaobj);
-- backup_state.vmaobj = NULL;
-+ if (backup_state.vmaw) {
-+ Error *local_err = NULL;
-+ vma_writer_close(backup_state.vmaw, &local_err);
-+ error_propagate(&backup_state.error, local_err);
-+ backup_state.vmaw = NULL;
- }
-
- if (backup_state.di_list) {
-@@ -2985,6 +3076,13 @@ static void pvebackup_cleanup(void)
- }
- }
-
-+static void coroutine_fn backup_close_vma_stream(void *opaque)
-+{
-+ PVEBackupDevInfo *di = opaque;
-+
-+ vma_writer_close_stream(backup_state.vmaw, di->dev_id);
-+}
-+
- static void pvebackup_complete_cb(void *opaque, int ret)
- {
- PVEBackupDevInfo *di = opaque;
-@@ -2996,14 +3094,18 @@ static void pvebackup_complete_cb(void *opaque, int ret)
- ret, strerror(-ret));
- }
-
-+ BlockDriverState *bs = di->bs;
-+
- di->bs = NULL;
- di->target = NULL;
-
-- if (backup_state.vmaobj) {
-- object_unparent(backup_state.vmaobj);
-- backup_state.vmaobj = NULL;
-+ if (backup_state.vmaw) {
-+ Coroutine *co = qemu_coroutine_create(backup_close_vma_stream, di);
-+ qemu_coroutine_enter(co);
- }
-
-+ block_job_cb(bs, ret);
-+
- if (!backup_state.cancel) {
- pvebackup_run_next_job();
- }
-@@ -3017,14 +3119,9 @@ static void pvebackup_cancel(void *opaque)
- error_setg(&backup_state.error, "backup cancelled");
- }
-
-- if (backup_state.vmaobj) {
-- Error *err;
-+ if (backup_state.vmaw) {
- /* make sure vma writer does not block anymore */
-- if (!object_set_props(backup_state.vmaobj, &err, "blocked", "yes", NULL)) {
-- if (err) {
-- error_report_err(err);
-- }
-- }
-+ vma_writer_set_error(backup_state.vmaw, "backup cancelled");
- }
-
- GList *l = backup_state.di_list;
-@@ -3049,19 +3146,15 @@ void qmp_backup_cancel(Error **errp)
- Coroutine *co = qemu_coroutine_create(pvebackup_cancel, NULL);
- qemu_coroutine_enter(co);
-
-- while (backup_state.vmaobj) {
-- /* FIXME: Find something better for this */
-+ while (backup_state.vmaw) {
-+ /* vma writer use main aio context */
- aio_poll(qemu_get_aio_context(), true);
- }
- }
-
--void vma_object_add_config_file(Object *obj, const char *name,
-- const char *contents, size_t len,
-- Error **errp);
- static int config_to_vma(const char *file, BackupFormat format,
-- Object *vmaobj,
-- const char *backup_dir,
-- Error **errp)
-+ const char *backup_dir, VmaWriter *vmaw,
-+ Error **errp)
- {
- char *cdata = NULL;
- gsize clen = 0;
-@@ -3074,12 +3167,17 @@ static int config_to_vma(const char *file, BackupFormat format,
- char *basename = g_path_get_basename(file);
-
- if (format == BACKUP_FORMAT_VMA) {
-- vma_object_add_config_file(vmaobj, basename, cdata, clen, errp);
-+ if (vma_writer_add_config(vmaw, basename, cdata, clen) != 0) {
-+ error_setg(errp, "unable to add %s config data to vma archive", file);
-+ g_free(cdata);
-+ g_free(basename);
-+ return 1;
-+ }
- } else if (format == BACKUP_FORMAT_DIR) {
- char config_path[PATH_MAX];
- snprintf(config_path, PATH_MAX, "%s/%s", backup_dir, basename);
- if (!g_file_set_contents(config_path, cdata, clen, &err)) {
-- error_setg(errp, "unable to write config file '%s'", config_path);
-+ error_setg(errp, "unable to write config file '%s'", config_path);
- g_free(cdata);
- g_free(basename);
- return 1;
-@@ -3089,34 +3187,37 @@ static int config_to_vma(const char *file, BackupFormat format,
- g_free(basename);
- g_free(cdata);
-
-- return 0;
-+ return 0;
- }
-
-+bool block_job_should_pause(BlockJob *job);
- static void pvebackup_run_next_job(void)
- {
-- bool cancel = backup_state.error || backup_state.cancel;
-- GList *next = g_list_nth(backup_state.di_list, backup_state.next_job);
-- while (next) {
-- PVEBackupDevInfo *di = (PVEBackupDevInfo *)next->data;
-- backup_state.next_job++;
-+ GList *l = backup_state.di_list;
-+ while (l) {
-+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-+ l = g_list_next(l);
- if (!di->completed && di->bs && di->bs->job) {
- BlockJob *job = di->bs->job;
-- if (cancel) {
-- block_job_cancel(job);
-- } else {
-- block_job_resume(job);
-+ if (block_job_should_pause(job)) {
-+ bool cancel = backup_state.error || backup_state.cancel;
-+ if (cancel) {
-+ block_job_cancel(job);
-+ } else {
-+ block_job_resume(job);
-+ }
- }
- return;
- }
-- next = g_list_next(next);
- }
-+
- pvebackup_cleanup();
- }
-
- UuidInfo *qmp_backup(const char *backup_file, bool has_format,
- BackupFormat format,
- bool has_config_file, const char *config_file,
-- bool has_firewall_file, const char *firewall_file,
-+ bool has_firewall_file, const char *firewall_file,
- bool has_devlist, const char *devlist,
- bool has_speed, int64_t speed, Error **errp)
- {
-@@ -3124,14 +3225,15 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
- BlockDriverState *bs = NULL;
- const char *backup_dir = NULL;
- Error *local_err = NULL;
-- QemuUUID uuid;
-+ uuid_t uuid;
-+ VmaWriter *vmaw = NULL;
- gchar **devs = NULL;
- GList *di_list = NULL;
- GList *l;
- UuidInfo *uuid_info;
- BlockJob *job;
-
-- if (backup_state.di_list || backup_state.vmaobj) {
-+ if (backup_state.di_list) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR,
- "previous backup not finished");
- return NULL;
-@@ -3206,40 +3308,28 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
- total += size;
- }
-
-- qemu_uuid_generate(&uuid);
-+ uuid_generate(uuid);
-
- if (format == BACKUP_FORMAT_VMA) {
-- char uuidstr[UUID_FMT_LEN+1];
-- qemu_uuid_unparse(&uuid, uuidstr);
-- uuidstr[UUID_FMT_LEN] = 0;
-- backup_state.vmaobj =
-- object_new_with_props("vma", object_get_objects_root(),
-- "vma-backup-obj", &local_err,
-- "filename", backup_file,
-- "uuid", uuidstr,
-- NULL);
-- if (!backup_state.vmaobj) {
-+ vmaw = vma_writer_create(backup_file, uuid, &local_err);
-+ if (!vmaw) {
- if (local_err) {
- error_propagate(errp, local_err);
- }
- goto err;
- }
-
-+ /* register all devices for vma writer */
- l = di_list;
- while (l) {
-- QDict *options = qdict_new();
--
- PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
- l = g_list_next(l);
-
- const char *devname = bdrv_get_device_name(di->bs);
-- snprintf(di->targetfile, PATH_MAX, "vma-backup-obj/%s.raw", devname);
--
-- qdict_put(options, "driver", qstring_from_str("vma-drive"));
-- qdict_put(options, "size", qint_from_int(di->size));
-- di->target = bdrv_open(di->targetfile, NULL, options, BDRV_O_RDWR, &local_err);
-- if (!di->target) {
-- error_propagate(errp, local_err);
-+ di->dev_id = vma_writer_register_stream(vmaw, devname, di->size);
-+ if (di->dev_id <= 0) {
-+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
-+ "register_stream failed");
- goto err;
- }
- }
-@@ -3280,15 +3370,15 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
-
- /* add configuration file to archive */
- if (has_config_file) {
-- if(config_to_vma(config_file, format, backup_state.vmaobj, backup_dir, errp) != 0) {
-- goto err;
-+ if(config_to_vma(config_file, format, backup_dir, vmaw, errp) != 0) {
-+ goto err;
- }
- }
-
- /* add firewall file to archive */
- if (has_firewall_file) {
-- if(config_to_vma(firewall_file, format, backup_state.vmaobj, backup_dir, errp) != 0) {
-- goto err;
-+ if(config_to_vma(firewall_file, format, backup_dir, vmaw, errp) != 0) {
-+ goto err;
- }
- }
- /* initialize global backup_state now */
-@@ -3310,11 +3400,12 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
- }
- backup_state.backup_file = g_strdup(backup_file);
-
-- memcpy(&backup_state.uuid, &uuid, sizeof(uuid));
-- qemu_uuid_unparse(&uuid, backup_state.uuid_str);
-+ backup_state.vmaw = vmaw;
-+
-+ uuid_copy(backup_state.uuid, uuid);
-+ uuid_unparse_lower(uuid, backup_state.uuid_str);
-
- backup_state.di_list = di_list;
-- backup_state.next_job = 0;
-
- backup_state.total = total;
- backup_state.transferred = 0;
-@@ -3325,21 +3416,16 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
- while (l) {
- PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
- l = g_list_next(l);
--
- job = backup_job_create(NULL, di->bs, di->target, speed, MIRROR_SYNC_MODE_FULL, NULL,
- false, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
- BLOCK_JOB_DEFAULT,
-- pvebackup_complete_cb, di, 2, NULL, &local_err);
-- if (di->target) {
-- bdrv_unref(di->target);
-- di->target = NULL;
-- }
-+ pvebackup_dump_cb, pvebackup_complete_cb, di,
-+ 2, NULL, &local_err);
- if (!job || local_err != NULL) {
- error_setg(&backup_state.error, "backup_job_create failed");
- pvebackup_cancel(NULL);
-- } else {
-- block_job_start(job);
- }
-+ block_job_start(job);
- }
-
- if (!backup_state.error) {
-@@ -3373,9 +3459,10 @@ err:
- g_strfreev(devs);
- }
-
-- if (backup_state.vmaobj) {
-- object_unparent(backup_state.vmaobj);
-- backup_state.vmaobj = NULL;
-+ if (vmaw) {
-+ Error *err = NULL;
-+ vma_writer_close(vmaw, &err);
-+ unlink(backup_file);
- }
-
- if (backup_dir) {
-@@ -3760,7 +3847,7 @@ static BlockJob *do_drive_backup(DriveBackup *backup, BlockJobTxn *txn,
- job = backup_job_create(backup->job_id, bs, target_bs, backup->speed,
- backup->sync, bmap, backup->compress,
- backup->on_source_error, backup->on_target_error,
-- BLOCK_JOB_DEFAULT, NULL, NULL, 0, txn, &local_err);
-+ BLOCK_JOB_DEFAULT, NULL, NULL, NULL, 0, txn, &local_err);
- bdrv_unref(target_bs);
- if (local_err != NULL) {
- error_propagate(errp, local_err);
-@@ -3839,7 +3926,7 @@ BlockJob *do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn,
- job = backup_job_create(backup->job_id, bs, target_bs, backup->speed,
- backup->sync, NULL, backup->compress,
- backup->on_source_error, backup->on_target_error,
-- BLOCK_JOB_DEFAULT, NULL, NULL, 0, txn, &local_err);
-+ BLOCK_JOB_DEFAULT, NULL, NULL, NULL, 0, txn, &local_err);
- if (local_err != NULL) {
- error_propagate(errp, local_err);
- }
-diff --git a/blockjob.c b/blockjob.c
-index 764d41863e..cb3741f6dd 100644
---- a/blockjob.c
-+++ b/blockjob.c
-@@ -37,8 +37,8 @@
- #include "qemu/timer.h"
- #include "qapi-event.h"
-
--static void block_job_event_cancelled(BlockJob *job);
--static void block_job_event_completed(BlockJob *job, const char *msg);
-+void block_job_event_cancelled(BlockJob *job);
-+void block_job_event_completed(BlockJob *job, const char *msg);
-
- /* Transactional group of block jobs */
- struct BlockJobTxn {
-@@ -473,7 +473,8 @@ void block_job_user_pause(BlockJob *job)
- block_job_pause(job);
- }
-
--static bool block_job_should_pause(BlockJob *job)
-+bool block_job_should_pause(BlockJob *job);
-+bool block_job_should_pause(BlockJob *job)
- {
- return job->pause_count > 0;
- }
-@@ -687,7 +688,7 @@ static void block_job_iostatus_set_err(BlockJob *job, int error)
- }
- }
-
--static void block_job_event_cancelled(BlockJob *job)
-+void block_job_event_cancelled(BlockJob *job)
- {
- if (block_job_is_internal(job)) {
- return;
-@@ -701,7 +702,7 @@ static void block_job_event_cancelled(BlockJob *job)
- &error_abort);
- }
-
--static void block_job_event_completed(BlockJob *job, const char *msg)
-+void block_job_event_completed(BlockJob *job, const char *msg)
- {
- if (block_job_is_internal(job)) {
- return;
-diff --git a/include/block/block_int.h b/include/block/block_int.h
-index 19b84b027f..fc1c53a059 100644
---- a/include/block/block_int.h
-+++ b/include/block/block_int.h
-@@ -59,6 +59,9 @@
-
- #define BLOCK_PROBE_BUF_SIZE 512
-
-+typedef int BackupDumpFunc(void *opaque, BlockBackend *be,
-+ int64_t sector_num, int n_sectors, unsigned char *buf);
-+
- enum BdrvTrackedRequestType {
- BDRV_TRACKED_READ,
- BDRV_TRACKED_WRITE,
-@@ -878,6 +881,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
- BlockdevOnError on_source_error,
- BlockdevOnError on_target_error,
- int creation_flags,
-+ BackupDumpFunc *dump_cb,
- BlockCompletionFunc *cb, void *opaque,
- int pause_count,
- BlockJobTxn *txn, Error **errp);
-diff --git a/vma-reader.c b/vma-reader.c
-new file mode 100644
-index 0000000000..2000889bd3
---- /dev/null
-+++ b/vma-reader.c
-@@ -0,0 +1,857 @@
-+/*
-+ * VMA: Virtual Machine Archive
-+ *
-+ * Copyright (C) 2012 Proxmox Server Solutions
-+ *
-+ * Authors:
-+ * Dietmar Maurer (dietmar@proxmox.com)
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ *
-+ */
-+
-+#include "qemu/osdep.h"
-+#include <glib.h>
-+#include <uuid/uuid.h>
-+
-+#include "qemu-common.h"
-+#include "qemu/timer.h"
-+#include "qemu/ratelimit.h"
-+#include "vma.h"
-+#include "block/block.h"
-+#include "sysemu/block-backend.h"
-+
-+static unsigned char zero_vma_block[VMA_BLOCK_SIZE];
-+
-+typedef struct VmaRestoreState {
-+ BlockBackend *target;
-+ bool write_zeroes;
-+ unsigned long *bitmap;
-+ int bitmap_size;
-+} VmaRestoreState;
-+
-+struct VmaReader {
-+ int fd;
-+ GChecksum *md5csum;
-+ GHashTable *blob_hash;
-+ unsigned char *head_data;
-+ VmaDeviceInfo devinfo[256];
-+ VmaRestoreState rstate[256];
-+ GList *cdata_list;
-+ guint8 vmstate_stream;
-+ uint32_t vmstate_clusters;
-+ /* to show restore percentage if run with -v */
-+ time_t start_time;
-+ int64_t cluster_count;
-+ int64_t clusters_read;
-+ int64_t zero_cluster_data;
-+ int64_t partial_zero_cluster_data;
-+ int clusters_read_per;
-+};
-+
-+static guint
-+g_int32_hash(gconstpointer v)
-+{
-+ return *(const uint32_t *)v;
-+}
-+
-+static gboolean
-+g_int32_equal(gconstpointer v1, gconstpointer v2)
-+{
-+ return *((const uint32_t *)v1) == *((const uint32_t *)v2);
-+}
-+
-+static int vma_reader_get_bitmap(VmaRestoreState *rstate, int64_t cluster_num)
-+{
-+ assert(rstate);
-+ assert(rstate->bitmap);
-+
-+ unsigned long val, idx, bit;
-+
-+ idx = cluster_num / BITS_PER_LONG;
-+
-+ assert(rstate->bitmap_size > idx);
-+
-+ bit = cluster_num % BITS_PER_LONG;
-+ val = rstate->bitmap[idx];
-+
-+ return !!(val & (1UL << bit));
-+}
-+
-+static void vma_reader_set_bitmap(VmaRestoreState *rstate, int64_t cluster_num,
-+ int dirty)
-+{
-+ assert(rstate);
-+ assert(rstate->bitmap);
-+
-+ unsigned long val, idx, bit;
-+
-+ idx = cluster_num / BITS_PER_LONG;
-+
-+ assert(rstate->bitmap_size > idx);
-+
-+ bit = cluster_num % BITS_PER_LONG;
-+ val = rstate->bitmap[idx];
-+ if (dirty) {
-+ if (!(val & (1UL << bit))) {
-+ val |= 1UL << bit;
-+ }
-+ } else {
-+ if (val & (1UL << bit)) {
-+ val &= ~(1UL << bit);
-+ }
-+ }
-+ rstate->bitmap[idx] = val;
-+}
-+
-+typedef struct VmaBlob {
-+ uint32_t start;
-+ uint32_t len;
-+ void *data;
-+} VmaBlob;
-+
-+static const VmaBlob *get_header_blob(VmaReader *vmar, uint32_t pos)
-+{
-+ assert(vmar);
-+ assert(vmar->blob_hash);
-+
-+ return g_hash_table_lookup(vmar->blob_hash, &pos);
-+}
-+
-+static const char *get_header_str(VmaReader *vmar, uint32_t pos)
-+{
-+ const VmaBlob *blob = get_header_blob(vmar, pos);
-+ if (!blob) {
-+ return NULL;
-+ }
-+ const char *res = (char *)blob->data;
-+ if (res[blob->len-1] != '\0') {
-+ return NULL;
-+ }
-+ return res;
-+}
-+
-+static ssize_t
-+safe_read(int fd, unsigned char *buf, size_t count)
-+{
-+ ssize_t n;
-+
-+ do {
-+ n = read(fd, buf, count);
-+ } while (n < 0 && errno == EINTR);
-+
-+ return n;
-+}
-+
-+static ssize_t
-+full_read(int fd, unsigned char *buf, size_t len)
-+{
-+ ssize_t n;
-+ size_t total;
-+
-+ total = 0;
-+
-+ while (len > 0) {
-+ n = safe_read(fd, buf, len);
-+
-+ if (n == 0) {
-+ return total;
-+ }
-+
-+ if (n <= 0) {
-+ break;
-+ }
-+
-+ buf += n;
-+ total += n;
-+ len -= n;
-+ }
-+
-+ if (len) {
-+ return -1;
-+ }
-+
-+ return total;
-+}
-+
-+void vma_reader_destroy(VmaReader *vmar)
-+{
-+ assert(vmar);
-+
-+ if (vmar->fd >= 0) {
-+ close(vmar->fd);
-+ }
-+
-+ if (vmar->cdata_list) {
-+ g_list_free(vmar->cdata_list);
-+ }
-+
-+ int i;
-+ for (i = 1; i < 256; i++) {
-+ if (vmar->rstate[i].bitmap) {
-+ g_free(vmar->rstate[i].bitmap);
-+ }
-+ }
-+
-+ if (vmar->md5csum) {
-+ g_checksum_free(vmar->md5csum);
-+ }
-+
-+ if (vmar->blob_hash) {
-+ g_hash_table_destroy(vmar->blob_hash);
-+ }
-+
-+ if (vmar->head_data) {
-+ g_free(vmar->head_data);
-+ }
-+
-+ g_free(vmar);
-+
-+};
-+
-+static int vma_reader_read_head(VmaReader *vmar, Error **errp)
-+{
-+ assert(vmar);
-+ assert(errp);
-+ assert(*errp == NULL);
-+
-+ unsigned char md5sum[16];
-+ int i;
-+ int ret = 0;
-+
-+ vmar->head_data = g_malloc(sizeof(VmaHeader));
-+
-+ if (full_read(vmar->fd, vmar->head_data, sizeof(VmaHeader)) !=
-+ sizeof(VmaHeader)) {
-+ error_setg(errp, "can't read vma header - %s",
-+ errno ? g_strerror(errno) : "got EOF");
-+ return -1;
-+ }
-+
-+ VmaHeader *h = (VmaHeader *)vmar->head_data;
-+
-+ if (h->magic != VMA_MAGIC) {
-+ error_setg(errp, "not a vma file - wrong magic number");
-+ return -1;
-+ }
-+
-+ uint32_t header_size = GUINT32_FROM_BE(h->header_size);
-+ int need = header_size - sizeof(VmaHeader);
-+ if (need <= 0) {
-+ error_setg(errp, "wrong vma header size %d", header_size);
-+ return -1;
-+ }
-+
-+ vmar->head_data = g_realloc(vmar->head_data, header_size);
-+ h = (VmaHeader *)vmar->head_data;
-+
-+ if (full_read(vmar->fd, vmar->head_data + sizeof(VmaHeader), need) !=
-+ need) {
-+ error_setg(errp, "can't read vma header data - %s",
-+ errno ? g_strerror(errno) : "got EOF");
-+ return -1;
-+ }
-+
-+ memcpy(md5sum, h->md5sum, 16);
-+ memset(h->md5sum, 0, 16);
-+
-+ g_checksum_reset(vmar->md5csum);
-+ g_checksum_update(vmar->md5csum, vmar->head_data, header_size);
-+ gsize csize = 16;
-+ g_checksum_get_digest(vmar->md5csum, (guint8 *)(h->md5sum), &csize);
-+
-+ if (memcmp(md5sum, h->md5sum, 16) != 0) {
-+ error_setg(errp, "wrong vma header chechsum");
-+ return -1;
-+ }
-+
-+ /* we can modify header data after checksum verify */
-+ h->header_size = header_size;
-+
-+ h->version = GUINT32_FROM_BE(h->version);
-+ if (h->version != 1) {
-+ error_setg(errp, "wrong vma version %d", h->version);
-+ return -1;
-+ }
-+
-+ h->ctime = GUINT64_FROM_BE(h->ctime);
-+ h->blob_buffer_offset = GUINT32_FROM_BE(h->blob_buffer_offset);
-+ h->blob_buffer_size = GUINT32_FROM_BE(h->blob_buffer_size);
-+
-+ uint32_t bstart = h->blob_buffer_offset + 1;
-+ uint32_t bend = h->blob_buffer_offset + h->blob_buffer_size;
-+
-+ if (bstart <= sizeof(VmaHeader)) {
-+ error_setg(errp, "wrong vma blob buffer offset %d",
-+ h->blob_buffer_offset);
-+ return -1;
-+ }
-+
-+ if (bend > header_size) {
-+ error_setg(errp, "wrong vma blob buffer size %d/%d",
-+ h->blob_buffer_offset, h->blob_buffer_size);
-+ return -1;
-+ }
-+
-+ while ((bstart + 2) <= bend) {
-+ uint32_t size = vmar->head_data[bstart] +
-+ (vmar->head_data[bstart+1] << 8);
-+ if ((bstart + size + 2) <= bend) {
-+ VmaBlob *blob = g_new0(VmaBlob, 1);
-+ blob->start = bstart - h->blob_buffer_offset;
-+ blob->len = size;
-+ blob->data = vmar->head_data + bstart + 2;
-+ g_hash_table_insert(vmar->blob_hash, &blob->start, blob);
-+ }
-+ bstart += size + 2;
-+ }
-+
-+
-+ int count = 0;
-+ for (i = 1; i < 256; i++) {
-+ VmaDeviceInfoHeader *dih = &h->dev_info[i];
-+ uint32_t devname_ptr = GUINT32_FROM_BE(dih->devname_ptr);
-+ uint64_t size = GUINT64_FROM_BE(dih->size);
-+ const char *devname = get_header_str(vmar, devname_ptr);
-+
-+ if (size && devname) {
-+ count++;
-+ vmar->devinfo[i].size = size;
-+ vmar->devinfo[i].devname = devname;
-+
-+ if (strcmp(devname, "vmstate") == 0) {
-+ vmar->vmstate_stream = i;
-+ }
-+ }
-+ }
-+
-+ for (i = 0; i < VMA_MAX_CONFIGS; i++) {
-+ uint32_t name_ptr = GUINT32_FROM_BE(h->config_names[i]);
-+ uint32_t data_ptr = GUINT32_FROM_BE(h->config_data[i]);
-+
-+ if (!(name_ptr && data_ptr)) {
-+ continue;
-+ }
-+ const char *name = get_header_str(vmar, name_ptr);
-+ const VmaBlob *blob = get_header_blob(vmar, data_ptr);
-+
-+ if (!(name && blob)) {
-+ error_setg(errp, "vma contains invalid data pointers");
-+ return -1;
-+ }
-+
-+ VmaConfigData *cdata = g_new0(VmaConfigData, 1);
-+ cdata->name = name;
-+ cdata->data = blob->data;
-+ cdata->len = blob->len;
-+
-+ vmar->cdata_list = g_list_append(vmar->cdata_list, cdata);
-+ }
-+
-+ return ret;
-+};
-+
-+VmaReader *vma_reader_create(const char *filename, Error **errp)
-+{
-+ assert(filename);
-+ assert(errp);
-+
-+ VmaReader *vmar = g_new0(VmaReader, 1);
-+
-+ if (strcmp(filename, "-") == 0) {
-+ vmar->fd = dup(0);
-+ } else {
-+ vmar->fd = open(filename, O_RDONLY);
-+ }
-+
-+ if (vmar->fd < 0) {
-+ error_setg(errp, "can't open file %s - %s\n", filename,
-+ g_strerror(errno));
-+ goto err;
-+ }
-+
-+ vmar->md5csum = g_checksum_new(G_CHECKSUM_MD5);
-+ if (!vmar->md5csum) {
-+ error_setg(errp, "can't allocate cmsum\n");
-+ goto err;
-+ }
-+
-+ vmar->blob_hash = g_hash_table_new_full(g_int32_hash, g_int32_equal,
-+ NULL, g_free);
-+
-+ if (vma_reader_read_head(vmar, errp) < 0) {
-+ goto err;
-+ }
-+
-+ return vmar;
-+
-+err:
-+ if (vmar) {
-+ vma_reader_destroy(vmar);
-+ }
-+
-+ return NULL;
-+}
-+
-+VmaHeader *vma_reader_get_header(VmaReader *vmar)
-+{
-+ assert(vmar);
-+ assert(vmar->head_data);
-+
-+ return (VmaHeader *)(vmar->head_data);
-+}
-+
-+GList *vma_reader_get_config_data(VmaReader *vmar)
-+{
-+ assert(vmar);
-+ assert(vmar->head_data);
-+
-+ return vmar->cdata_list;
-+}
-+
-+VmaDeviceInfo *vma_reader_get_device_info(VmaReader *vmar, guint8 dev_id)
-+{
-+ assert(vmar);
-+ assert(dev_id);
-+
-+ if (vmar->devinfo[dev_id].size && vmar->devinfo[dev_id].devname) {
-+ return &vmar->devinfo[dev_id];
-+ }
-+
-+ return NULL;
-+}
-+
-+static void allocate_rstate(VmaReader *vmar, guint8 dev_id,
-+ BlockBackend *target, bool write_zeroes)
-+{
-+ assert(vmar);
-+ assert(dev_id);
-+
-+ vmar->rstate[dev_id].target = target;
-+ vmar->rstate[dev_id].write_zeroes = write_zeroes;
-+
-+ int64_t size = vmar->devinfo[dev_id].size;
-+
-+ int64_t bitmap_size = (size/BDRV_SECTOR_SIZE) +
-+ (VMA_CLUSTER_SIZE/BDRV_SECTOR_SIZE) * BITS_PER_LONG - 1;
-+ bitmap_size /= (VMA_CLUSTER_SIZE/BDRV_SECTOR_SIZE) * BITS_PER_LONG;
-+
-+ vmar->rstate[dev_id].bitmap_size = bitmap_size;
-+ vmar->rstate[dev_id].bitmap = g_new0(unsigned long, bitmap_size);
-+
-+ vmar->cluster_count += size/VMA_CLUSTER_SIZE;
-+}
-+
-+int vma_reader_register_bs(VmaReader *vmar, guint8 dev_id, BlockBackend *target,
-+ bool write_zeroes, Error **errp)
-+{
-+ assert(vmar);
-+ assert(target != NULL);
-+ assert(dev_id);
-+ assert(vmar->rstate[dev_id].target == NULL);
-+
-+ int64_t size = blk_getlength(target);
-+ int64_t size_diff = size - vmar->devinfo[dev_id].size;
-+
-+ /* storage types can have different size restrictions, so it
-+ * is not always possible to create an image with exact size.
-+ * So we tolerate a size difference up to 4MB.
-+ */
-+ if ((size_diff < 0) || (size_diff > 4*1024*1024)) {
-+ error_setg(errp, "vma_reader_register_bs for stream %s failed - "
-+ "unexpected size %zd != %zd", vmar->devinfo[dev_id].devname,
-+ size, vmar->devinfo[dev_id].size);
-+ return -1;
-+ }
-+
-+ allocate_rstate(vmar, dev_id, target, write_zeroes);
-+
-+ return 0;
-+}
-+
-+static ssize_t safe_write(int fd, void *buf, size_t count)
-+{
-+ ssize_t n;
-+
-+ do {
-+ n = write(fd, buf, count);
-+ } while (n < 0 && errno == EINTR);
-+
-+ return n;
-+}
-+
-+static size_t full_write(int fd, void *buf, size_t len)
-+{
-+ ssize_t n;
-+ size_t total;
-+
-+ total = 0;
-+
-+ while (len > 0) {
-+ n = safe_write(fd, buf, len);
-+ if (n < 0) {
-+ return n;
-+ }
-+ buf += n;
-+ total += n;
-+ len -= n;
-+ }
-+
-+ if (len) {
-+ /* incomplete write ? */
-+ return -1;
-+ }
-+
-+ return total;
-+}
-+
-+static int restore_write_data(VmaReader *vmar, guint8 dev_id,
-+ BlockBackend *target, int vmstate_fd,
-+ unsigned char *buf, int64_t sector_num,
-+ int nb_sectors, Error **errp)
-+{
-+ assert(vmar);
-+
-+ if (dev_id == vmar->vmstate_stream) {
-+ if (vmstate_fd >= 0) {
-+ int len = nb_sectors * BDRV_SECTOR_SIZE;
-+ int res = full_write(vmstate_fd, buf, len);
-+ if (res < 0) {
-+ error_setg(errp, "write vmstate failed %d", res);
-+ return -1;
-+ }
-+ }
-+ } else {
-+ int res = blk_pwrite(target, sector_num * BDRV_SECTOR_SIZE, buf, nb_sectors * BDRV_SECTOR_SIZE, 0);
-+ if (res < 0) {
-+ error_setg(errp, "blk_pwrite to %s failed (%d)",
-+ bdrv_get_device_name(blk_bs(target)), res);
-+ return -1;
-+ }
-+ }
-+ return 0;
-+}
-+
-+static int restore_extent(VmaReader *vmar, unsigned char *buf,
-+ int extent_size, int vmstate_fd,
-+ bool verbose, bool verify, Error **errp)
-+{
-+ assert(vmar);
-+ assert(buf);
-+
-+ VmaExtentHeader *ehead = (VmaExtentHeader *)buf;
-+ int start = VMA_EXTENT_HEADER_SIZE;
-+ int i;
-+
-+ for (i = 0; i < VMA_BLOCKS_PER_EXTENT; i++) {
-+ uint64_t block_info = GUINT64_FROM_BE(ehead->blockinfo[i]);
-+ uint64_t cluster_num = block_info & 0xffffffff;
-+ uint8_t dev_id = (block_info >> 32) & 0xff;
-+ uint16_t mask = block_info >> (32+16);
-+ int64_t max_sector;
-+
-+ if (!dev_id) {
-+ continue;
-+ }
-+
-+ VmaRestoreState *rstate = &vmar->rstate[dev_id];
-+ BlockBackend *target = NULL;
-+
-+ if (dev_id != vmar->vmstate_stream) {
-+ target = rstate->target;
-+ if (!verify && !target) {
-+ error_setg(errp, "got wrong dev id %d", dev_id);
-+ return -1;
-+ }
-+
-+ if (vma_reader_get_bitmap(rstate, cluster_num)) {
-+ error_setg(errp, "found duplicated cluster %zd for stream %s",
-+ cluster_num, vmar->devinfo[dev_id].devname);
-+ return -1;
-+ }
-+ vma_reader_set_bitmap(rstate, cluster_num, 1);
-+
-+ max_sector = vmar->devinfo[dev_id].size/BDRV_SECTOR_SIZE;
-+ } else {
-+ max_sector = G_MAXINT64;
-+ if (cluster_num != vmar->vmstate_clusters) {
-+ error_setg(errp, "found out of order vmstate data");
-+ return -1;
-+ }
-+ vmar->vmstate_clusters++;
-+ }
-+
-+ vmar->clusters_read++;
-+
-+ if (verbose) {
-+ time_t duration = time(NULL) - vmar->start_time;
-+ int percent = (vmar->clusters_read*100)/vmar->cluster_count;
-+ if (percent != vmar->clusters_read_per) {
-+ printf("progress %d%% (read %zd bytes, duration %zd sec)\n",
-+ percent, vmar->clusters_read*VMA_CLUSTER_SIZE,
-+ duration);
-+ fflush(stdout);
-+ vmar->clusters_read_per = percent;
-+ }
-+ }
-+
-+ /* try to write whole clusters to speedup restore */
-+ if (mask == 0xffff) {
-+ if ((start + VMA_CLUSTER_SIZE) > extent_size) {
-+ error_setg(errp, "short vma extent - too many blocks");
-+ return -1;
-+ }
-+ int64_t sector_num = (cluster_num * VMA_CLUSTER_SIZE) /
-+ BDRV_SECTOR_SIZE;
-+ int64_t end_sector = sector_num +
-+ VMA_CLUSTER_SIZE/BDRV_SECTOR_SIZE;
-+
-+ if (end_sector > max_sector) {
-+ end_sector = max_sector;
-+ }
-+
-+ if (end_sector <= sector_num) {
-+ error_setg(errp, "got wrong block address - write bejond end");
-+ return -1;
-+ }
-+
-+ if (!verify) {
-+ int nb_sectors = end_sector - sector_num;
-+ if (restore_write_data(vmar, dev_id, target, vmstate_fd,
-+ buf + start, sector_num, nb_sectors,
-+ errp) < 0) {
-+ return -1;
-+ }
-+ }
-+
-+ start += VMA_CLUSTER_SIZE;
-+ } else {
-+ int j;
-+ int bit = 1;
-+
-+ for (j = 0; j < 16; j++) {
-+ int64_t sector_num = (cluster_num*VMA_CLUSTER_SIZE +
-+ j*VMA_BLOCK_SIZE)/BDRV_SECTOR_SIZE;
-+
-+ int64_t end_sector = sector_num +
-+ VMA_BLOCK_SIZE/BDRV_SECTOR_SIZE;
-+ if (end_sector > max_sector) {
-+ end_sector = max_sector;
-+ }
-+
-+ if (mask & bit) {
-+ if ((start + VMA_BLOCK_SIZE) > extent_size) {
-+ error_setg(errp, "short vma extent - too many blocks");
-+ return -1;
-+ }
-+
-+ if (end_sector <= sector_num) {
-+ error_setg(errp, "got wrong block address - "
-+ "write bejond end");
-+ return -1;
-+ }
-+
-+ if (!verify) {
-+ int nb_sectors = end_sector - sector_num;
-+ if (restore_write_data(vmar, dev_id, target, vmstate_fd,
-+ buf + start, sector_num,
-+ nb_sectors, errp) < 0) {
-+ return -1;
-+ }
-+ }
-+
-+ start += VMA_BLOCK_SIZE;
-+
-+ } else {
-+
-+
-+ if (end_sector > sector_num) {
-+ /* Todo: use bdrv_co_write_zeroes (but that need to
-+ * be run inside coroutine?)
-+ */
-+ int nb_sectors = end_sector - sector_num;
-+ int zero_size = BDRV_SECTOR_SIZE*nb_sectors;
-+ vmar->zero_cluster_data += zero_size;
-+ if (mask != 0) {
-+ vmar->partial_zero_cluster_data += zero_size;
-+ }
-+
-+ if (rstate->write_zeroes && !verify) {
-+ if (restore_write_data(vmar, dev_id, target, vmstate_fd,
-+ zero_vma_block, sector_num,
-+ nb_sectors, errp) < 0) {
-+ return -1;
-+ }
-+ }
-+ }
-+ }
-+
-+ bit = bit << 1;
-+ }
-+ }
-+ }
-+
-+ if (start != extent_size) {
-+ error_setg(errp, "vma extent error - missing blocks");
-+ return -1;
-+ }
-+
-+ return 0;
-+}
-+
-+static int vma_reader_restore_full(VmaReader *vmar, int vmstate_fd,
-+ bool verbose, bool verify,
-+ Error **errp)
-+{
-+ assert(vmar);
-+ assert(vmar->head_data);
-+
-+ int ret = 0;
-+ unsigned char buf[VMA_MAX_EXTENT_SIZE];
-+ int buf_pos = 0;
-+ unsigned char md5sum[16];
-+ VmaHeader *h = (VmaHeader *)vmar->head_data;
-+
-+ vmar->start_time = time(NULL);
-+
-+ while (1) {
-+ int bytes = full_read(vmar->fd, buf + buf_pos, sizeof(buf) - buf_pos);
-+ if (bytes < 0) {
-+ error_setg(errp, "read failed - %s", g_strerror(errno));
-+ return -1;
-+ }
-+
-+ buf_pos += bytes;
-+
-+ if (!buf_pos) {
-+ break; /* EOF */
-+ }
-+
-+ if (buf_pos < VMA_EXTENT_HEADER_SIZE) {
-+ error_setg(errp, "read short extent (%d bytes)", buf_pos);
-+ return -1;
-+ }
-+
-+ VmaExtentHeader *ehead = (VmaExtentHeader *)buf;
-+
-+ /* extract md5sum */
-+ memcpy(md5sum, ehead->md5sum, sizeof(ehead->md5sum));
-+ memset(ehead->md5sum, 0, sizeof(ehead->md5sum));
-+
-+ g_checksum_reset(vmar->md5csum);
-+ g_checksum_update(vmar->md5csum, buf, VMA_EXTENT_HEADER_SIZE);
-+ gsize csize = 16;
-+ g_checksum_get_digest(vmar->md5csum, ehead->md5sum, &csize);
-+
-+ if (memcmp(md5sum, ehead->md5sum, 16) != 0) {
-+ error_setg(errp, "wrong vma extent header chechsum");
-+ return -1;
-+ }
-+
-+ if (memcmp(h->uuid, ehead->uuid, sizeof(ehead->uuid)) != 0) {
-+ error_setg(errp, "wrong vma extent uuid");
-+ return -1;
-+ }
-+
-+ if (ehead->magic != VMA_EXTENT_MAGIC || ehead->reserved1 != 0) {
-+ error_setg(errp, "wrong vma extent header magic");
-+ return -1;
-+ }
-+
-+ int block_count = GUINT16_FROM_BE(ehead->block_count);
-+ int extent_size = VMA_EXTENT_HEADER_SIZE + block_count*VMA_BLOCK_SIZE;
-+
-+ if (buf_pos < extent_size) {
-+ error_setg(errp, "short vma extent (%d < %d)", buf_pos,
-+ extent_size);
-+ return -1;
-+ }
-+
-+ if (restore_extent(vmar, buf, extent_size, vmstate_fd, verbose,
-+ verify, errp) < 0) {
-+ return -1;
-+ }
-+
-+ if (buf_pos > extent_size) {
-+ memmove(buf, buf + extent_size, buf_pos - extent_size);
-+ buf_pos = buf_pos - extent_size;
-+ } else {
-+ buf_pos = 0;
-+ }
-+ }
-+
-+ bdrv_drain_all();
-+
-+ int i;
-+ for (i = 1; i < 256; i++) {
-+ VmaRestoreState *rstate = &vmar->rstate[i];
-+ if (!rstate->target) {
-+ continue;
-+ }
-+
-+ if (blk_flush(rstate->target) < 0) {
-+ error_setg(errp, "vma blk_flush %s failed",
-+ vmar->devinfo[i].devname);
-+ return -1;
-+ }
-+
-+ if (vmar->devinfo[i].size &&
-+ (strcmp(vmar->devinfo[i].devname, "vmstate") != 0)) {
-+ assert(rstate->bitmap);
-+
-+ int64_t cluster_num, end;
-+
-+ end = (vmar->devinfo[i].size + VMA_CLUSTER_SIZE - 1) /
-+ VMA_CLUSTER_SIZE;
-+
-+ for (cluster_num = 0; cluster_num < end; cluster_num++) {
-+ if (!vma_reader_get_bitmap(rstate, cluster_num)) {
-+ error_setg(errp, "detected missing cluster %zd "
-+ "for stream %s", cluster_num,
-+ vmar->devinfo[i].devname);
-+ return -1;
-+ }
-+ }
-+ }
-+ }
-+
-+ if (verbose) {
-+ if (vmar->clusters_read) {
-+ printf("total bytes read %zd, sparse bytes %zd (%.3g%%)\n",
-+ vmar->clusters_read*VMA_CLUSTER_SIZE,
-+ vmar->zero_cluster_data,
-+ (double)(100.0*vmar->zero_cluster_data)/
-+ (vmar->clusters_read*VMA_CLUSTER_SIZE));
-+
-+ int64_t datasize = vmar->clusters_read*VMA_CLUSTER_SIZE-vmar->zero_cluster_data;
-+ if (datasize) { // this does not make sense for empty files
-+ printf("space reduction due to 4K zero blocks %.3g%%\n",
-+ (double)(100.0*vmar->partial_zero_cluster_data) / datasize);
-+ }
-+ } else {
-+ printf("vma archive contains no image data\n");
-+ }
-+ }
-+ return ret;
-+}
-+
-+int vma_reader_restore(VmaReader *vmar, int vmstate_fd, bool verbose,
-+ Error **errp)
-+{
-+ return vma_reader_restore_full(vmar, vmstate_fd, verbose, false, errp);
-+}
-+
-+int vma_reader_verify(VmaReader *vmar, bool verbose, Error **errp)
-+{
-+ guint8 dev_id;
-+
-+ for (dev_id = 1; dev_id < 255; dev_id++) {
-+ if (vma_reader_get_device_info(vmar, dev_id)) {
-+ allocate_rstate(vmar, dev_id, NULL, false);
-+ }
-+ }
-+
-+ return vma_reader_restore_full(vmar, -1, verbose, true, errp);
-+}
-+
-diff --git a/vma-writer.c b/vma-writer.c
-new file mode 100644
-index 0000000000..9001cbdd2b
---- /dev/null
-+++ b/vma-writer.c
-@@ -0,0 +1,771 @@
-+/*
-+ * VMA: Virtual Machine Archive
-+ *
-+ * Copyright (C) 2012 Proxmox Server Solutions
-+ *
-+ * Authors:
-+ * Dietmar Maurer (dietmar@proxmox.com)
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ *
-+ */
-+
-+#include "qemu/osdep.h"
-+#include <glib.h>
-+#include <uuid/uuid.h>
-+
-+#include "vma.h"
-+#include "block/block.h"
-+#include "monitor/monitor.h"
-+#include "qemu/main-loop.h"
-+#include "qemu/coroutine.h"
-+#include "qemu/cutils.h"
-+
-+#define DEBUG_VMA 0
-+
-+#define DPRINTF(fmt, ...)\
-+ do { if (DEBUG_VMA) { printf("vma: " fmt, ## __VA_ARGS__); } } while (0)
-+
-+#define WRITE_BUFFERS 5
-+#define HEADER_CLUSTERS 8
-+#define HEADERBUF_SIZE (VMA_CLUSTER_SIZE*HEADER_CLUSTERS)
-+
-+struct VmaWriter {
-+ int fd;
-+ FILE *cmd;
-+ int status;
-+ char errmsg[8192];
-+ uuid_t uuid;
-+ bool header_written;
-+ bool closed;
-+
-+ /* we always write extents */
-+ unsigned char *outbuf;
-+ int outbuf_pos; /* in bytes */
-+ int outbuf_count; /* in VMA_BLOCKS */
-+ uint64_t outbuf_block_info[VMA_BLOCKS_PER_EXTENT];
-+
-+ unsigned char *headerbuf;
-+
-+ GChecksum *md5csum;
-+ CoMutex flush_lock;
-+ Coroutine *co_writer;
-+
-+ /* drive informations */
-+ VmaStreamInfo stream_info[256];
-+ guint stream_count;
-+
-+ guint8 vmstate_stream;
-+ uint32_t vmstate_clusters;
-+
-+ /* header blob table */
-+ char *header_blob_table;
-+ uint32_t header_blob_table_size;
-+ uint32_t header_blob_table_pos;
-+
-+ /* store for config blobs */
-+ uint32_t config_names[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
-+ uint32_t config_data[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
-+ uint32_t config_count;
-+};
-+
-+void vma_writer_set_error(VmaWriter *vmaw, const char *fmt, ...)
-+{
-+ va_list ap;
-+
-+ if (vmaw->status < 0) {
-+ return;
-+ }
-+
-+ vmaw->status = -1;
-+
-+ va_start(ap, fmt);
-+ g_vsnprintf(vmaw->errmsg, sizeof(vmaw->errmsg), fmt, ap);
-+ va_end(ap);
-+
-+ DPRINTF("vma_writer_set_error: %s\n", vmaw->errmsg);
-+}
-+
-+static uint32_t allocate_header_blob(VmaWriter *vmaw, const char *data,
-+ size_t len)
-+{
-+ if (len > 65535) {
-+ return 0;
-+ }
-+
-+ if (!vmaw->header_blob_table ||
-+ (vmaw->header_blob_table_size <
-+ (vmaw->header_blob_table_pos + len + 2))) {
-+ int newsize = vmaw->header_blob_table_size + ((len + 2 + 511)/512)*512;
-+
-+ vmaw->header_blob_table = g_realloc(vmaw->header_blob_table, newsize);
-+ memset(vmaw->header_blob_table + vmaw->header_blob_table_size,
-+ 0, newsize - vmaw->header_blob_table_size);
-+ vmaw->header_blob_table_size = newsize;
-+ }
-+
-+ uint32_t cpos = vmaw->header_blob_table_pos;
-+ vmaw->header_blob_table[cpos] = len & 255;
-+ vmaw->header_blob_table[cpos+1] = (len >> 8) & 255;
-+ memcpy(vmaw->header_blob_table + cpos + 2, data, len);
-+ vmaw->header_blob_table_pos += len + 2;
-+ return cpos;
-+}
-+
-+static uint32_t allocate_header_string(VmaWriter *vmaw, const char *str)
-+{
-+ assert(vmaw);
-+
-+ size_t len = strlen(str) + 1;
-+
-+ return allocate_header_blob(vmaw, str, len);
-+}
-+
-+int vma_writer_add_config(VmaWriter *vmaw, const char *name, gpointer data,
-+ gsize len)
-+{
-+ assert(vmaw);
-+ assert(!vmaw->header_written);
-+ assert(vmaw->config_count < VMA_MAX_CONFIGS);
-+ assert(name);
-+ assert(data);
-+
-+ gchar *basename = g_path_get_basename(name);
-+ uint32_t name_ptr = allocate_header_string(vmaw, basename);
-+ g_free(basename);
-+
-+ if (!name_ptr) {
-+ return -1;
-+ }
-+
-+ uint32_t data_ptr = allocate_header_blob(vmaw, data, len);
-+ if (!data_ptr) {
-+ return -1;
-+ }
-+
-+ vmaw->config_names[vmaw->config_count] = name_ptr;
-+ vmaw->config_data[vmaw->config_count] = data_ptr;
-+
-+ vmaw->config_count++;
-+
-+ return 0;
-+}
-+
-+int vma_writer_register_stream(VmaWriter *vmaw, const char *devname,
-+ size_t size)
-+{
-+ assert(vmaw);
-+ assert(devname);
-+ assert(!vmaw->status);
-+
-+ if (vmaw->header_written) {
-+ vma_writer_set_error(vmaw, "vma_writer_register_stream: header "
-+ "already written");
-+ return -1;
-+ }
-+
-+ guint n = vmaw->stream_count + 1;
-+
-+ /* we can have dev_ids form 1 to 255 (0 reserved)
-+ * 255(-1) reseverd for safety
-+ */
-+ if (n > 254) {
-+ vma_writer_set_error(vmaw, "vma_writer_register_stream: "
-+ "too many drives");
-+ return -1;
-+ }
-+
-+ if (size <= 0) {
-+ vma_writer_set_error(vmaw, "vma_writer_register_stream: "
-+ "got strange size %zd", size);
-+ return -1;
-+ }
-+
-+ DPRINTF("vma_writer_register_stream %s %zu %d\n", devname, size, n);
-+
-+ vmaw->stream_info[n].devname = g_strdup(devname);
-+ vmaw->stream_info[n].size = size;
-+
-+ vmaw->stream_info[n].cluster_count = (size + VMA_CLUSTER_SIZE - 1) /
-+ VMA_CLUSTER_SIZE;
-+
-+ vmaw->stream_count = n;
-+
-+ if (strcmp(devname, "vmstate") == 0) {
-+ vmaw->vmstate_stream = n;
-+ }
-+
-+ return n;
-+}
-+
-+static void vma_co_continue_write(void *opaque)
-+{
-+ VmaWriter *vmaw = opaque;
-+
-+ DPRINTF("vma_co_continue_write\n");
-+ qemu_coroutine_enter(vmaw->co_writer);
-+}
-+
-+static ssize_t coroutine_fn
-+vma_queue_write(VmaWriter *vmaw, const void *buf, size_t bytes)
-+{
-+ DPRINTF("vma_queue_write enter %zd\n", bytes);
-+
-+ assert(vmaw);
-+ assert(buf);
-+ assert(bytes <= VMA_MAX_EXTENT_SIZE);
-+
-+ size_t done = 0;
-+ ssize_t ret;
-+
-+ assert(vmaw->co_writer == NULL);
-+
-+ vmaw->co_writer = qemu_coroutine_self();
-+
-+ while (done < bytes) {
-+ aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, false, NULL, vma_co_continue_write, NULL, vmaw);
-+ qemu_coroutine_yield();
-+ aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, false, NULL, NULL, NULL, NULL);
-+ if (vmaw->status < 0) {
-+ DPRINTF("vma_queue_write detected canceled backup\n");
-+ done = -1;
-+ break;
-+ }
-+ ret = write(vmaw->fd, buf + done, bytes - done);
-+ if (ret > 0) {
-+ done += ret;
-+ DPRINTF("vma_queue_write written %zd %zd\n", done, ret);
-+ } else if (ret < 0) {
-+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
-+ /* try again */
-+ } else {
-+ vma_writer_set_error(vmaw, "vma_queue_write: write error - %s",
-+ g_strerror(errno));
-+ done = -1; /* always return failure for partial writes */
-+ break;
-+ }
-+ } else if (ret == 0) {
-+ /* should not happen - simply try again */
-+ }
-+ }
-+
-+ vmaw->co_writer = NULL;
-+
-+ return (done == bytes) ? bytes : -1;
-+}
-+
-+VmaWriter *vma_writer_create(const char *filename, uuid_t uuid, Error **errp)
-+{
-+ const char *p;
-+
-+ assert(sizeof(VmaHeader) == (4096 + 8192));
-+ assert(G_STRUCT_OFFSET(VmaHeader, config_names) == 2044);
-+ assert(G_STRUCT_OFFSET(VmaHeader, config_data) == 3068);
-+ assert(G_STRUCT_OFFSET(VmaHeader, dev_info) == 4096);
-+ assert(sizeof(VmaExtentHeader) == 512);
-+
-+ VmaWriter *vmaw = g_new0(VmaWriter, 1);
-+ vmaw->fd = -1;
-+
-+ vmaw->md5csum = g_checksum_new(G_CHECKSUM_MD5);
-+ if (!vmaw->md5csum) {
-+ error_setg(errp, "can't allocate cmsum\n");
-+ goto err;
-+ }
-+
-+ if (strstart(filename, "exec:", &p)) {
-+ vmaw->cmd = popen(p, "w");
-+ if (vmaw->cmd == NULL) {
-+ error_setg(errp, "can't popen command '%s' - %s\n", p,
-+ g_strerror(errno));
-+ goto err;
-+ }
-+ vmaw->fd = fileno(vmaw->cmd);
-+
-+ /* try to use O_NONBLOCK */
-+ fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_NONBLOCK);
-+
-+ } else {
-+ struct stat st;
-+ int oflags;
-+ const char *tmp_id_str;
-+
-+ if ((stat(filename, &st) == 0) && S_ISFIFO(st.st_mode)) {
-+ oflags = O_NONBLOCK|O_WRONLY;
-+ vmaw->fd = qemu_open(filename, oflags, 0644);
-+ } else if (strstart(filename, "/dev/fdset/", &tmp_id_str)) {
-+ oflags = O_NONBLOCK|O_WRONLY;
-+ vmaw->fd = qemu_open(filename, oflags, 0644);
-+ } else if (strstart(filename, "/dev/fdname/", &tmp_id_str)) {
-+ vmaw->fd = monitor_get_fd(cur_mon, tmp_id_str, errp);
-+ if (vmaw->fd < 0) {
-+ goto err;
-+ }
-+ /* try to use O_NONBLOCK */
-+ fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_NONBLOCK);
-+ } else {
-+ oflags = O_NONBLOCK|O_DIRECT|O_WRONLY|O_CREAT|O_EXCL;
-+ vmaw->fd = qemu_open(filename, oflags, 0644);
-+ }
-+
-+ if (vmaw->fd < 0) {
-+ error_setg(errp, "can't open file %s - %s\n", filename,
-+ g_strerror(errno));
-+ goto err;
-+ }
-+ }
-+
-+ /* we use O_DIRECT, so we need to align IO buffers */
-+
-+ vmaw->outbuf = qemu_memalign(512, VMA_MAX_EXTENT_SIZE);
-+ vmaw->headerbuf = qemu_memalign(512, HEADERBUF_SIZE);
-+
-+ vmaw->outbuf_count = 0;
-+ vmaw->outbuf_pos = VMA_EXTENT_HEADER_SIZE;
-+
-+ vmaw->header_blob_table_pos = 1; /* start at pos 1 */
-+
-+ qemu_co_mutex_init(&vmaw->flush_lock);
-+
-+ uuid_copy(vmaw->uuid, uuid);
-+
-+ return vmaw;
-+
-+err:
-+ if (vmaw) {
-+ if (vmaw->cmd) {
-+ pclose(vmaw->cmd);
-+ } else if (vmaw->fd >= 0) {
-+ close(vmaw->fd);
-+ }
-+
-+ if (vmaw->md5csum) {
-+ g_checksum_free(vmaw->md5csum);
-+ }
-+
-+ g_free(vmaw);
-+ }
-+
-+ return NULL;
-+}
-+
-+static int coroutine_fn vma_write_header(VmaWriter *vmaw)
-+{
-+ assert(vmaw);
-+ unsigned char *buf = vmaw->headerbuf;
-+ VmaHeader *head = (VmaHeader *)buf;
-+
-+ int i;
-+
-+ DPRINTF("VMA WRITE HEADER\n");
-+
-+ if (vmaw->status < 0) {
-+ return vmaw->status;
-+ }
-+
-+ memset(buf, 0, HEADERBUF_SIZE);
-+
-+ head->magic = VMA_MAGIC;
-+ head->version = GUINT32_TO_BE(1); /* v1 */
-+ memcpy(head->uuid, vmaw->uuid, 16);
-+
-+ time_t ctime = time(NULL);
-+ head->ctime = GUINT64_TO_BE(ctime);
-+
-+ for (i = 0; i < VMA_MAX_CONFIGS; i++) {
-+ head->config_names[i] = GUINT32_TO_BE(vmaw->config_names[i]);
-+ head->config_data[i] = GUINT32_TO_BE(vmaw->config_data[i]);
-+ }
-+
-+ /* 32 bytes per device (12 used currently) = 8192 bytes max */
-+ for (i = 1; i <= 254; i++) {
-+ VmaStreamInfo *si = &vmaw->stream_info[i];
-+ if (si->size) {
-+ assert(si->devname);
-+ uint32_t devname_ptr = allocate_header_string(vmaw, si->devname);
-+ if (!devname_ptr) {
-+ return -1;
-+ }
-+ head->dev_info[i].devname_ptr = GUINT32_TO_BE(devname_ptr);
-+ head->dev_info[i].size = GUINT64_TO_BE(si->size);
-+ }
-+ }
-+
-+ uint32_t header_size = sizeof(VmaHeader) + vmaw->header_blob_table_size;
-+ head->header_size = GUINT32_TO_BE(header_size);
-+
-+ if (header_size > HEADERBUF_SIZE) {
-+ return -1; /* just to be sure */
-+ }
-+
-+ uint32_t blob_buffer_offset = sizeof(VmaHeader);
-+ memcpy(buf + blob_buffer_offset, vmaw->header_blob_table,
-+ vmaw->header_blob_table_size);
-+ head->blob_buffer_offset = GUINT32_TO_BE(blob_buffer_offset);
-+ head->blob_buffer_size = GUINT32_TO_BE(vmaw->header_blob_table_pos);
-+
-+ g_checksum_reset(vmaw->md5csum);
-+ g_checksum_update(vmaw->md5csum, (const guchar *)buf, header_size);
-+ gsize csize = 16;
-+ g_checksum_get_digest(vmaw->md5csum, (guint8 *)(head->md5sum), &csize);
-+
-+ return vma_queue_write(vmaw, buf, header_size);
-+}
-+
-+static int coroutine_fn vma_writer_flush(VmaWriter *vmaw)
-+{
-+ assert(vmaw);
-+
-+ int ret;
-+ int i;
-+
-+ if (vmaw->status < 0) {
-+ return vmaw->status;
-+ }
-+
-+ if (!vmaw->header_written) {
-+ vmaw->header_written = true;
-+ ret = vma_write_header(vmaw);
-+ if (ret < 0) {
-+ vma_writer_set_error(vmaw, "vma_writer_flush: write header failed");
-+ return ret;
-+ }
-+ }
-+
-+ DPRINTF("VMA WRITE FLUSH %d %d\n", vmaw->outbuf_count, vmaw->outbuf_pos);
-+
-+
-+ VmaExtentHeader *ehead = (VmaExtentHeader *)vmaw->outbuf;
-+
-+ ehead->magic = VMA_EXTENT_MAGIC;
-+ ehead->reserved1 = 0;
-+
-+ for (i = 0; i < VMA_BLOCKS_PER_EXTENT; i++) {
-+ ehead->blockinfo[i] = GUINT64_TO_BE(vmaw->outbuf_block_info[i]);
-+ }
-+
-+ guint16 block_count = (vmaw->outbuf_pos - VMA_EXTENT_HEADER_SIZE) /
-+ VMA_BLOCK_SIZE;
-+
-+ ehead->block_count = GUINT16_TO_BE(block_count);
-+
-+ memcpy(ehead->uuid, vmaw->uuid, sizeof(ehead->uuid));
-+ memset(ehead->md5sum, 0, sizeof(ehead->md5sum));
-+
-+ g_checksum_reset(vmaw->md5csum);
-+ g_checksum_update(vmaw->md5csum, vmaw->outbuf, VMA_EXTENT_HEADER_SIZE);
-+ gsize csize = 16;
-+ g_checksum_get_digest(vmaw->md5csum, ehead->md5sum, &csize);
-+
-+ int bytes = vmaw->outbuf_pos;
-+ ret = vma_queue_write(vmaw, vmaw->outbuf, bytes);
-+ if (ret != bytes) {
-+ vma_writer_set_error(vmaw, "vma_writer_flush: failed write");
-+ }
-+
-+ vmaw->outbuf_count = 0;
-+ vmaw->outbuf_pos = VMA_EXTENT_HEADER_SIZE;
-+
-+ for (i = 0; i < VMA_BLOCKS_PER_EXTENT; i++) {
-+ vmaw->outbuf_block_info[i] = 0;
-+ }
-+
-+ return vmaw->status;
-+}
-+
-+static int vma_count_open_streams(VmaWriter *vmaw)
-+{
-+ g_assert(vmaw != NULL);
-+
-+ int i;
-+ int open_drives = 0;
-+ for (i = 0; i <= 255; i++) {
-+ if (vmaw->stream_info[i].size && !vmaw->stream_info[i].finished) {
-+ open_drives++;
-+ }
-+ }
-+
-+ return open_drives;
-+}
-+
-+
-+/**
-+ * You need to call this if the vma archive does not contain
-+ * any data stream.
-+ */
-+int coroutine_fn
-+vma_writer_flush_output(VmaWriter *vmaw)
-+{
-+ qemu_co_mutex_lock(&vmaw->flush_lock);
-+ int ret = vma_writer_flush(vmaw);
-+ qemu_co_mutex_unlock(&vmaw->flush_lock);
-+ if (ret < 0) {
-+ vma_writer_set_error(vmaw, "vma_writer_flush_header failed");
-+ }
-+ return ret;
-+}
-+
-+/**
-+ * all jobs should call this when there is no more data
-+ * Returns: number of remaining stream (0 ==> finished)
-+ */
-+int coroutine_fn
-+vma_writer_close_stream(VmaWriter *vmaw, uint8_t dev_id)
-+{
-+ g_assert(vmaw != NULL);
-+
-+ DPRINTF("vma_writer_set_status %d\n", dev_id);
-+ if (!vmaw->stream_info[dev_id].size) {
-+ vma_writer_set_error(vmaw, "vma_writer_close_stream: "
-+ "no such stream %d", dev_id);
-+ return -1;
-+ }
-+ if (vmaw->stream_info[dev_id].finished) {
-+ vma_writer_set_error(vmaw, "vma_writer_close_stream: "
-+ "stream already closed %d", dev_id);
-+ return -1;
-+ }
-+
-+ vmaw->stream_info[dev_id].finished = true;
-+
-+ int open_drives = vma_count_open_streams(vmaw);
-+
-+ if (open_drives <= 0) {
-+ DPRINTF("vma_writer_set_status all drives completed\n");
-+ vma_writer_flush_output(vmaw);
-+ }
-+
-+ return open_drives;
-+}
-+
-+int vma_writer_get_status(VmaWriter *vmaw, VmaStatus *status)
-+{
-+ int i;
-+
-+ g_assert(vmaw != NULL);
-+
-+ if (status) {
-+ status->status = vmaw->status;
-+ g_strlcpy(status->errmsg, vmaw->errmsg, sizeof(status->errmsg));
-+ for (i = 0; i <= 255; i++) {
-+ status->stream_info[i] = vmaw->stream_info[i];
-+ }
-+
-+ uuid_unparse_lower(vmaw->uuid, status->uuid_str);
-+ }
-+
-+ status->closed = vmaw->closed;
-+
-+ return vmaw->status;
-+}
-+
-+static int vma_writer_get_buffer(VmaWriter *vmaw)
-+{
-+ int ret = 0;
-+
-+ qemu_co_mutex_lock(&vmaw->flush_lock);
-+
-+ /* wait until buffer is available */
-+ while (vmaw->outbuf_count >= (VMA_BLOCKS_PER_EXTENT - 1)) {
-+ ret = vma_writer_flush(vmaw);
-+ if (ret < 0) {
-+ vma_writer_set_error(vmaw, "vma_writer_get_buffer: flush failed");
-+ break;
-+ }
-+ }
-+
-+ qemu_co_mutex_unlock(&vmaw->flush_lock);
-+
-+ return ret;
-+}
-+
-+
-+int64_t coroutine_fn
-+vma_writer_write(VmaWriter *vmaw, uint8_t dev_id, int64_t cluster_num,
-+ unsigned char *buf, size_t *zero_bytes)
-+{
-+ g_assert(vmaw != NULL);
-+ g_assert(zero_bytes != NULL);
-+
-+ *zero_bytes = 0;
-+
-+ if (vmaw->status < 0) {
-+ return vmaw->status;
-+ }
-+
-+ if (!dev_id || !vmaw->stream_info[dev_id].size) {
-+ vma_writer_set_error(vmaw, "vma_writer_write: "
-+ "no such stream %d", dev_id);
-+ return -1;
-+ }
-+
-+ if (vmaw->stream_info[dev_id].finished) {
-+ vma_writer_set_error(vmaw, "vma_writer_write: "
-+ "stream already closed %d", dev_id);
-+ return -1;
-+ }
-+
-+
-+ if (cluster_num >= (((uint64_t)1)<<32)) {
-+ vma_writer_set_error(vmaw, "vma_writer_write: "
-+ "cluster number out of range");
-+ return -1;
-+ }
-+
-+ if (dev_id == vmaw->vmstate_stream) {
-+ if (cluster_num != vmaw->vmstate_clusters) {
-+ vma_writer_set_error(vmaw, "vma_writer_write: "
-+ "non sequential vmstate write");
-+ }
-+ vmaw->vmstate_clusters++;
-+ } else if (cluster_num >= vmaw->stream_info[dev_id].cluster_count) {
-+ vma_writer_set_error(vmaw, "vma_writer_write: cluster number too big");
-+ return -1;
-+ }
-+
-+ /* wait until buffer is available */
-+ if (vma_writer_get_buffer(vmaw) < 0) {
-+ vma_writer_set_error(vmaw, "vma_writer_write: "
-+ "vma_writer_get_buffer failed");
-+ return -1;
-+ }
-+
-+ DPRINTF("VMA WRITE %d %zd\n", dev_id, cluster_num);
-+
-+ uint16_t mask = 0;
-+
-+ if (buf) {
-+ int i;
-+ int bit = 1;
-+ for (i = 0; i < 16; i++) {
-+ unsigned char *vmablock = buf + (i*VMA_BLOCK_SIZE);
-+ if (!buffer_is_zero(vmablock, VMA_BLOCK_SIZE)) {
-+ mask |= bit;
-+ memcpy(vmaw->outbuf + vmaw->outbuf_pos, vmablock,
-+ VMA_BLOCK_SIZE);
-+ vmaw->outbuf_pos += VMA_BLOCK_SIZE;
-+ } else {
-+ DPRINTF("VMA WRITE %zd ZERO BLOCK %d\n", cluster_num, i);
-+ vmaw->stream_info[dev_id].zero_bytes += VMA_BLOCK_SIZE;
-+ *zero_bytes += VMA_BLOCK_SIZE;
-+ }
-+
-+ bit = bit << 1;
-+ }
-+ } else {
-+ DPRINTF("VMA WRITE %zd ZERO CLUSTER\n", cluster_num);
-+ vmaw->stream_info[dev_id].zero_bytes += VMA_CLUSTER_SIZE;
-+ *zero_bytes += VMA_CLUSTER_SIZE;
-+ }
-+
-+ uint64_t block_info = ((uint64_t)mask) << (32+16);
-+ block_info |= ((uint64_t)dev_id) << 32;
-+ block_info |= (cluster_num & 0xffffffff);
-+ vmaw->outbuf_block_info[vmaw->outbuf_count] = block_info;
-+
-+ DPRINTF("VMA WRITE MASK %zd %zx\n", cluster_num, block_info);
-+
-+ vmaw->outbuf_count++;
-+
-+ /** NOTE: We allways write whole clusters, but we correctly set
-+ * transferred bytes. So transferred == size when when everything
-+ * went OK.
-+ */
-+ size_t transferred = VMA_CLUSTER_SIZE;
-+
-+ if (dev_id != vmaw->vmstate_stream) {
-+ uint64_t last = (cluster_num + 1) * VMA_CLUSTER_SIZE;
-+ if (last > vmaw->stream_info[dev_id].size) {
-+ uint64_t diff = last - vmaw->stream_info[dev_id].size;
-+ if (diff >= VMA_CLUSTER_SIZE) {
-+ vma_writer_set_error(vmaw, "vma_writer_write: "
-+ "read after last cluster");
-+ return -1;
-+ }
-+ transferred -= diff;
-+ }
-+ }
-+
-+ vmaw->stream_info[dev_id].transferred += transferred;
-+
-+ return transferred;
-+}
-+
-+void vma_writer_error_propagate(VmaWriter *vmaw, Error **errp)
-+{
-+ if (vmaw->status < 0 && *errp == NULL) {
-+ error_setg(errp, "%s", vmaw->errmsg);
-+ }
-+}
-+
-+int vma_writer_close(VmaWriter *vmaw, Error **errp)
-+{
-+ g_assert(vmaw != NULL);
-+
-+ int i;
-+
-+ while (vmaw->co_writer) {
-+ aio_poll(qemu_get_aio_context(), true);
-+ }
-+
-+ assert(vmaw->co_writer == NULL);
-+
-+ if (vmaw->cmd) {
-+ if (pclose(vmaw->cmd) < 0) {
-+ vma_writer_set_error(vmaw, "vma_writer_close: "
-+ "pclose failed - %s", g_strerror(errno));
-+ }
-+ } else {
-+ if (close(vmaw->fd) < 0) {
-+ vma_writer_set_error(vmaw, "vma_writer_close: "
-+ "close failed - %s", g_strerror(errno));
-+ }
-+ }
-+
-+ for (i = 0; i <= 255; i++) {
-+ VmaStreamInfo *si = &vmaw->stream_info[i];
-+ if (si->size) {
-+ if (!si->finished) {
-+ vma_writer_set_error(vmaw, "vma_writer_close: "
-+ "detected open stream '%s'", si->devname);
-+ } else if ((si->transferred != si->size) &&
-+ (i != vmaw->vmstate_stream)) {
-+ vma_writer_set_error(vmaw, "vma_writer_close: "
-+ "incomplete stream '%s' (%zd != %zd)",
-+ si->devname, si->transferred, si->size);
-+ }
-+ }
-+ }
-+
-+ for (i = 0; i <= 255; i++) {
-+ vmaw->stream_info[i].finished = 1; /* mark as closed */
-+ }
-+
-+ vmaw->closed = 1;
-+
-+ if (vmaw->status < 0 && *errp == NULL) {
-+ error_setg(errp, "%s", vmaw->errmsg);
-+ }
-+
-+ return vmaw->status;
-+}
-+
-+void vma_writer_destroy(VmaWriter *vmaw)
-+{
-+ assert(vmaw);
-+
-+ int i;
-+
-+ for (i = 0; i <= 255; i++) {
-+ if (vmaw->stream_info[i].devname) {
-+ g_free(vmaw->stream_info[i].devname);
-+ }
-+ }
-+
-+ if (vmaw->md5csum) {
-+ g_checksum_free(vmaw->md5csum);
-+ }
-+
-+ g_free(vmaw);
-+}
-diff --git a/vma.c b/vma.c
-new file mode 100644
-index 0000000000..04915427c8
---- /dev/null
-+++ b/vma.c
-@@ -0,0 +1,757 @@
-+/*
-+ * VMA: Virtual Machine Archive
-+ *
-+ * Copyright (C) 2012-2013 Proxmox Server Solutions
-+ *
-+ * Authors:
-+ * Dietmar Maurer (dietmar@proxmox.com)
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ *
-+ */
-+
-+#include "qemu/osdep.h"
-+#include <glib.h>
-+
-+#include "vma.h"
-+#include "qemu-common.h"
-+#include "qemu/error-report.h"
-+#include "qemu/main-loop.h"
-+#include "qapi/qmp/qstring.h"
-+#include "sysemu/char.h" /* qstring_from_str */
-+#include "sysemu/block-backend.h"
-+
-+static void help(void)
-+{
-+ const char *help_msg =
-+ "usage: vma command [command options]\n"
-+ "\n"
-+ "vma list <filename>\n"
-+ "vma config <filename> [-c config]\n"
-+ "vma create <filename> [-c config] pathname ...\n"
-+ "vma extract <filename> [-r <fifo>] <targetdir>\n"
-+ "vma verify <filename> [-v]\n"
-+ ;
-+
-+ printf("%s", help_msg);
-+ exit(1);
-+}
-+
-+static const char *extract_devname(const char *path, char **devname, int index)
-+{
-+ assert(path);
-+
-+ const char *sep = strchr(path, '=');
-+
-+ if (sep) {
-+ *devname = g_strndup(path, sep - path);
-+ path = sep + 1;
-+ } else {
-+ if (index >= 0) {
-+ *devname = g_strdup_printf("disk%d", index);
-+ } else {
-+ *devname = NULL;
-+ }
-+ }
-+
-+ return path;
-+}
-+
-+static void print_content(VmaReader *vmar)
-+{
-+ assert(vmar);
-+
-+ VmaHeader *head = vma_reader_get_header(vmar);
-+
-+ GList *l = vma_reader_get_config_data(vmar);
-+ while (l && l->data) {
-+ VmaConfigData *cdata = (VmaConfigData *)l->data;
-+ l = g_list_next(l);
-+ printf("CFG: size: %d name: %s\n", cdata->len, cdata->name);
-+ }
-+
-+ int i;
-+ VmaDeviceInfo *di;
-+ for (i = 1; i < 255; i++) {
-+ di = vma_reader_get_device_info(vmar, i);
-+ if (di) {
-+ if (strcmp(di->devname, "vmstate") == 0) {
-+ printf("VMSTATE: dev_id=%d memory: %zd\n", i, di->size);
-+ } else {
-+ printf("DEV: dev_id=%d size: %zd devname: %s\n",
-+ i, di->size, di->devname);
-+ }
-+ }
-+ }
-+ /* ctime is the last entry we print */
-+ printf("CTIME: %s", ctime(&head->ctime));
-+ fflush(stdout);
-+}
-+
-+static int list_content(int argc, char **argv)
-+{
-+ int c, ret = 0;
-+ const char *filename;
-+
-+ for (;;) {
-+ c = getopt(argc, argv, "h");
-+ if (c == -1) {
-+ break;
-+ }
-+ switch (c) {
-+ case '?':
-+ case 'h':
-+ help();
-+ break;
-+ default:
-+ g_assert_not_reached();
-+ }
-+ }
-+
-+ /* Get the filename */
-+ if ((optind + 1) != argc) {
-+ help();
-+ }
-+ filename = argv[optind++];
-+
-+ Error *errp = NULL;
-+ VmaReader *vmar = vma_reader_create(filename, &errp);
-+
-+ if (!vmar) {
-+ g_error("%s", error_get_pretty(errp));
-+ }
-+
-+ print_content(vmar);
-+
-+ vma_reader_destroy(vmar);
-+
-+ return ret;
-+}
-+
-+typedef struct RestoreMap {
-+ char *devname;
-+ char *path;
-+ char *format;
-+ bool write_zero;
-+} RestoreMap;
-+
-+static int extract_content(int argc, char **argv)
-+{
-+ int c, ret = 0;
-+ int verbose = 0;
-+ const char *filename;
-+ const char *dirname;
-+ const char *readmap = NULL;
-+
-+ for (;;) {
-+ c = getopt(argc, argv, "hvr:");
-+ if (c == -1) {
-+ break;
-+ }
-+ switch (c) {
-+ case '?':
-+ case 'h':
-+ help();
-+ break;
-+ case 'r':
-+ readmap = optarg;
-+ break;
-+ case 'v':
-+ verbose = 1;
-+ break;
-+ default:
-+ help();
-+ }
-+ }
-+
-+ /* Get the filename */
-+ if ((optind + 2) != argc) {
-+ help();
-+ }
-+ filename = argv[optind++];
-+ dirname = argv[optind++];
-+
-+ Error *errp = NULL;
-+ VmaReader *vmar = vma_reader_create(filename, &errp);
-+
-+ if (!vmar) {
-+ g_error("%s", error_get_pretty(errp));
-+ }
-+
-+ if (mkdir(dirname, 0777) < 0) {
-+ g_error("unable to create target directory %s - %s",
-+ dirname, g_strerror(errno));
-+ }
-+
-+ GList *l = vma_reader_get_config_data(vmar);
-+ while (l && l->data) {
-+ VmaConfigData *cdata = (VmaConfigData *)l->data;
-+ l = g_list_next(l);
-+ char *cfgfn = g_strdup_printf("%s/%s", dirname, cdata->name);
-+ GError *err = NULL;
-+ if (!g_file_set_contents(cfgfn, (gchar *)cdata->data, cdata->len,
-+ &err)) {
-+ g_error("unable to write file: %s", err->message);
-+ }
-+ }
-+
-+ GHashTable *devmap = g_hash_table_new(g_str_hash, g_str_equal);
-+
-+ if (readmap) {
-+ print_content(vmar);
-+
-+ FILE *map = fopen(readmap, "r");
-+ if (!map) {
-+ g_error("unable to open fifo %s - %s", readmap, g_strerror(errno));
-+ }
-+
-+ while (1) {
-+ char inbuf[8192];
-+ char *line = fgets(inbuf, sizeof(inbuf), map);
-+ if (!line || line[0] == '\0' || !strcmp(line, "done\n")) {
-+ break;
-+ }
-+ int len = strlen(line);
-+ if (line[len - 1] == '\n') {
-+ line[len - 1] = '\0';
-+ if (len == 1) {
-+ break;
-+ }
-+ }
-+
-+ char *format = NULL;
-+ if (strncmp(line, "format=", sizeof("format=")-1) == 0) {
-+ format = line + sizeof("format=")-1;
-+ char *colon = strchr(format, ':');
-+ if (!colon) {
-+ g_error("read map failed - found only a format ('%s')", inbuf);
-+ }
-+ format = g_strndup(format, colon - format);
-+ line = colon+1;
-+ }
-+
-+ const char *path;
-+ bool write_zero;
-+ if (line[0] == '0' && line[1] == ':') {
-+ path = line + 2;
-+ write_zero = false;
-+ } else if (line[0] == '1' && line[1] == ':') {
-+ path = line + 2;
-+ write_zero = true;
-+ } else {
-+ g_error("read map failed - parse error ('%s')", inbuf);
-+ }
-+
-+ char *devname = NULL;
-+ path = extract_devname(path, &devname, -1);
-+ if (!devname) {
-+ g_error("read map failed - no dev name specified ('%s')",
-+ inbuf);
-+ }
-+
-+ RestoreMap *map = g_new0(RestoreMap, 1);
-+ map->devname = g_strdup(devname);
-+ map->path = g_strdup(path);
-+ map->format = format;
-+ map->write_zero = write_zero;
-+
-+ g_hash_table_insert(devmap, map->devname, map);
-+
-+ };
-+ }
-+
-+ int i;
-+ int vmstate_fd = -1;
-+ guint8 vmstate_stream = 0;
-+
-+ BlockBackend *blk = NULL;
-+
-+ for (i = 1; i < 255; i++) {
-+ VmaDeviceInfo *di = vma_reader_get_device_info(vmar, i);
-+ if (di && (strcmp(di->devname, "vmstate") == 0)) {
-+ vmstate_stream = i;
-+ char *statefn = g_strdup_printf("%s/vmstate.bin", dirname);
-+ vmstate_fd = open(statefn, O_WRONLY|O_CREAT|O_EXCL, 0644);
-+ if (vmstate_fd < 0) {
-+ g_error("create vmstate file '%s' failed - %s", statefn,
-+ g_strerror(errno));
-+ }
-+ g_free(statefn);
-+ } else if (di) {
-+ char *devfn = NULL;
-+ const char *format = NULL;
-+ int flags = BDRV_O_RDWR | BDRV_O_NO_FLUSH;
-+ bool write_zero = true;
-+
-+ if (readmap) {
-+ RestoreMap *map;
-+ map = (RestoreMap *)g_hash_table_lookup(devmap, di->devname);
-+ if (map == NULL) {
-+ g_error("no device name mapping for %s", di->devname);
-+ }
-+ devfn = map->path;
-+ format = map->format;
-+ write_zero = map->write_zero;
-+ } else {
-+ devfn = g_strdup_printf("%s/tmp-disk-%s.raw",
-+ dirname, di->devname);
-+ printf("DEVINFO %s %zd\n", devfn, di->size);
-+
-+ bdrv_img_create(devfn, "raw", NULL, NULL, NULL, di->size,
-+ flags, &errp, 0);
-+ if (errp) {
-+ g_error("can't create file %s: %s", devfn,
-+ error_get_pretty(errp));
-+ }
-+
-+ /* Note: we created an empty file above, so there is no
-+ * need to write zeroes (so we generate a sparse file)
-+ */
-+ write_zero = false;
-+ }
-+
-+ size_t devlen = strlen(devfn);
-+ QDict *options = NULL;
-+ if (format) {
-+ /* explicit format from commandline */
-+ options = qdict_new();
-+ qdict_put(options, "driver", qstring_from_str(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(options, "driver", qstring_from_str("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 (vma_reader_register_bs(vmar, i, blk, write_zero, &errp) < 0) {
-+ g_error("%s", error_get_pretty(errp));
-+ }
-+
-+ if (!readmap) {
-+ g_free(devfn);
-+ }
-+ }
-+ }
-+
-+ if (vma_reader_restore(vmar, vmstate_fd, verbose, &errp) < 0) {
-+ g_error("restore failed - %s", error_get_pretty(errp));
-+ }
-+
-+ if (!readmap) {
-+ for (i = 1; i < 255; i++) {
-+ VmaDeviceInfo *di = vma_reader_get_device_info(vmar, i);
-+ if (di && (i != vmstate_stream)) {
-+ char *tmpfn = g_strdup_printf("%s/tmp-disk-%s.raw",
-+ dirname, di->devname);
-+ char *fn = g_strdup_printf("%s/disk-%s.raw",
-+ dirname, di->devname);
-+ if (rename(tmpfn, fn) != 0) {
-+ g_error("rename %s to %s failed - %s",
-+ tmpfn, fn, g_strerror(errno));
-+ }
-+ }
-+ }
-+ }
-+
-+ vma_reader_destroy(vmar);
-+
-+ blk_unref(blk);
-+
-+ bdrv_close_all();
-+
-+ return ret;
-+}
-+
-+static int verify_content(int argc, char **argv)
-+{
-+ int c, ret = 0;
-+ int verbose = 0;
-+ const char *filename;
-+
-+ for (;;) {
-+ c = getopt(argc, argv, "hv");
-+ if (c == -1) {
-+ break;
-+ }
-+ switch (c) {
-+ case '?':
-+ case 'h':
-+ help();
-+ break;
-+ case 'v':
-+ verbose = 1;
-+ break;
-+ default:
-+ help();
-+ }
-+ }
-+
-+ /* Get the filename */
-+ if ((optind + 1) != argc) {
-+ help();
-+ }
-+ filename = argv[optind++];
-+
-+ Error *errp = NULL;
-+ VmaReader *vmar = vma_reader_create(filename, &errp);
-+
-+ if (!vmar) {
-+ g_error("%s", error_get_pretty(errp));
-+ }
-+
-+ if (verbose) {
-+ print_content(vmar);
-+ }
-+
-+ if (vma_reader_verify(vmar, verbose, &errp) < 0) {
-+ g_error("verify failed - %s", error_get_pretty(errp));
-+ }
-+
-+ vma_reader_destroy(vmar);
-+
-+ bdrv_close_all();
-+
-+ return ret;
-+}
-+
-+typedef struct BackupJob {
-+ BlockBackend *target;
-+ int64_t len;
-+ VmaWriter *vmaw;
-+ uint8_t dev_id;
-+} BackupJob;
-+
-+#define BACKUP_SECTORS_PER_CLUSTER (VMA_CLUSTER_SIZE / BDRV_SECTOR_SIZE)
-+
-+static void coroutine_fn backup_run_empty(void *opaque)
-+{
-+ VmaWriter *vmaw = (VmaWriter *)opaque;
-+
-+ vma_writer_flush_output(vmaw);
-+
-+ Error *err = NULL;
-+ if (vma_writer_close(vmaw, &err) != 0) {
-+ g_warning("vma_writer_close failed %s", error_get_pretty(err));
-+ }
-+}
-+
-+static void coroutine_fn backup_run(void *opaque)
-+{
-+ BackupJob *job = (BackupJob *)opaque;
-+ struct iovec iov;
-+ QEMUIOVector qiov;
-+
-+ int64_t start, end;
-+ int ret = 0;
-+
-+ unsigned char *buf = blk_blockalign(job->target, VMA_CLUSTER_SIZE);
-+
-+ start = 0;
-+ end = DIV_ROUND_UP(job->len / BDRV_SECTOR_SIZE,
-+ BACKUP_SECTORS_PER_CLUSTER);
-+
-+ for (; start < end; start++) {
-+ iov.iov_base = buf;
-+ iov.iov_len = VMA_CLUSTER_SIZE;
-+ qemu_iovec_init_external(&qiov, &iov, 1);
-+
-+ ret = blk_co_preadv(job->target, start * VMA_CLUSTER_SIZE,
-+ VMA_CLUSTER_SIZE, &qiov, 0);
-+ if (ret < 0) {
-+ vma_writer_set_error(job->vmaw, "read error", -1);
-+ goto out;
-+ }
-+
-+ size_t zb = 0;
-+ if (vma_writer_write(job->vmaw, job->dev_id, start, buf, &zb) < 0) {
-+ vma_writer_set_error(job->vmaw, "backup_dump_cb vma_writer_write failed", -1);
-+ goto out;
-+ }
-+ }
-+
-+
-+out:
-+ if (vma_writer_close_stream(job->vmaw, job->dev_id) <= 0) {
-+ Error *err = NULL;
-+ if (vma_writer_close(job->vmaw, &err) != 0) {
-+ g_warning("vma_writer_close failed %s", error_get_pretty(err));
-+ }
-+ }
-+}
-+
-+static int create_archive(int argc, char **argv)
-+{
-+ int i, c;
-+ int verbose = 0;
-+ const char *archivename;
-+ GList *config_files = NULL;
-+
-+ for (;;) {
-+ c = getopt(argc, argv, "hvc:");
-+ if (c == -1) {
-+ break;
-+ }
-+ switch (c) {
-+ case '?':
-+ case 'h':
-+ help();
-+ break;
-+ case 'c':
-+ config_files = g_list_append(config_files, optarg);
-+ break;
-+ case 'v':
-+ verbose = 1;
-+ break;
-+ default:
-+ g_assert_not_reached();
-+ }
-+ }
-+
-+
-+ /* make sure we an archive name */
-+ if ((optind + 1) > argc) {
-+ help();
-+ }
-+
-+ archivename = argv[optind++];
-+
-+ uuid_t uuid;
-+ uuid_generate(uuid);
-+
-+ Error *local_err = NULL;
-+ VmaWriter *vmaw = vma_writer_create(archivename, uuid, &local_err);
-+
-+ if (vmaw == NULL) {
-+ g_error("%s", error_get_pretty(local_err));
-+ }
-+
-+ GList *l = config_files;
-+ while (l && l->data) {
-+ char *name = l->data;
-+ char *cdata = NULL;
-+ gsize clen = 0;
-+ GError *err = NULL;
-+ if (!g_file_get_contents(name, &cdata, &clen, &err)) {
-+ unlink(archivename);
-+ g_error("Unable to read file: %s", err->message);
-+ }
-+
-+ if (vma_writer_add_config(vmaw, name, cdata, clen) != 0) {
-+ unlink(archivename);
-+ g_error("Unable to append config data %s (len = %zd)",
-+ name, clen);
-+ }
-+ l = g_list_next(l);
-+ }
-+
-+ int devcount = 0;
-+ while (optind < argc) {
-+ const char *path = argv[optind++];
-+ char *devname = NULL;
-+ path = extract_devname(path, &devname, devcount++);
-+
-+ Error *errp = NULL;
-+ BlockBackend *target;
-+
-+ target = blk_new_open(path, NULL, NULL, 0, &errp);
-+ if (!target) {
-+ unlink(archivename);
-+ g_error("bdrv_open '%s' failed - %s", path, error_get_pretty(errp));
-+ }
-+ int64_t size = blk_getlength(target);
-+ int dev_id = vma_writer_register_stream(vmaw, devname, size);
-+ if (dev_id <= 0) {
-+ unlink(archivename);
-+ g_error("vma_writer_register_stream '%s' failed", devname);
-+ }
-+
-+ BackupJob *job = g_new0(BackupJob, 1);
-+ job->len = size;
-+ job->target = target;
-+ job->vmaw = vmaw;
-+ job->dev_id = dev_id;
-+
-+ Coroutine *co = qemu_coroutine_create(backup_run, job);
-+ qemu_coroutine_enter(co);
-+ }
-+
-+ VmaStatus vmastat;
-+ int percent = 0;
-+ int last_percent = -1;
-+
-+ if (devcount) {
-+ while (1) {
-+ main_loop_wait(false);
-+ vma_writer_get_status(vmaw, &vmastat);
-+
-+ if (verbose) {
-+
-+ uint64_t total = 0;
-+ uint64_t transferred = 0;
-+ uint64_t zero_bytes = 0;
-+
-+ int i;
-+ for (i = 0; i < 256; i++) {
-+ if (vmastat.stream_info[i].size) {
-+ total += vmastat.stream_info[i].size;
-+ transferred += vmastat.stream_info[i].transferred;
-+ zero_bytes += vmastat.stream_info[i].zero_bytes;
-+ }
-+ }
-+ percent = (transferred*100)/total;
-+ if (percent != last_percent) {
-+ fprintf(stderr, "progress %d%% %zd/%zd %zd\n", percent,
-+ transferred, total, zero_bytes);
-+ fflush(stderr);
-+
-+ last_percent = percent;
-+ }
-+ }
-+
-+ if (vmastat.closed) {
-+ break;
-+ }
-+ }
-+ } else {
-+ Coroutine *co = qemu_coroutine_create(backup_run_empty, vmaw);
-+ qemu_coroutine_enter(co);
-+ while (1) {
-+ main_loop_wait(false);
-+ vma_writer_get_status(vmaw, &vmastat);
-+ if (vmastat.closed) {
-+ break;
-+ }
-+ }
-+ }
-+
-+ bdrv_drain_all();
-+
-+ vma_writer_get_status(vmaw, &vmastat);
-+
-+ if (verbose) {
-+ for (i = 0; i < 256; i++) {
-+ VmaStreamInfo *si = &vmastat.stream_info[i];
-+ if (si->size) {
-+ fprintf(stderr, "image %s: size=%zd zeros=%zd saved=%zd\n",
-+ si->devname, si->size, si->zero_bytes,
-+ si->size - si->zero_bytes);
-+ }
-+ }
-+ }
-+
-+ if (vmastat.status < 0) {
-+ unlink(archivename);
-+ g_error("creating vma archive failed");
-+ }
-+
-+ return 0;
-+}
-+
-+static int dump_config(int argc, char **argv)
-+{
-+ int c, ret = 0;
-+ const char *filename;
-+ const char *config_name = "qemu-server.conf";
-+
-+ for (;;) {
-+ c = getopt(argc, argv, "hc:");
-+ if (c == -1) {
-+ break;
-+ }
-+ switch (c) {
-+ case '?':
-+ case 'h':
-+ help();
-+ break;
-+ case 'c':
-+ config_name = optarg;
-+ break;
-+ default:
-+ help();
-+ }
-+ }
-+
-+ /* Get the filename */
-+ if ((optind + 1) != argc) {
-+ help();
-+ }
-+ filename = argv[optind++];
-+
-+ Error *errp = NULL;
-+ VmaReader *vmar = vma_reader_create(filename, &errp);
-+
-+ if (!vmar) {
-+ g_error("%s", error_get_pretty(errp));
-+ }
-+
-+ int found = 0;
-+ GList *l = vma_reader_get_config_data(vmar);
-+ while (l && l->data) {
-+ VmaConfigData *cdata = (VmaConfigData *)l->data;
-+ l = g_list_next(l);
-+ if (strcmp(cdata->name, config_name) == 0) {
-+ found = 1;
-+ fwrite(cdata->data, cdata->len, 1, stdout);
-+ break;
-+ }
-+ }
-+
-+ vma_reader_destroy(vmar);
-+
-+ bdrv_close_all();
-+
-+ if (!found) {
-+ fprintf(stderr, "unable to find configuration data '%s'\n", config_name);
-+ return -1;
-+ }
-+
-+ return ret;
-+}
-+
-+int main(int argc, char **argv)
-+{
-+ const char *cmdname;
-+ Error *main_loop_err = NULL;
-+
-+ error_set_progname(argv[0]);
-+
-+ if (qemu_init_main_loop(&main_loop_err)) {
-+ g_error("%s", error_get_pretty(main_loop_err));
-+ }
-+
-+ bdrv_init();
-+
-+ if (argc < 2) {
-+ help();
-+ }
-+
-+ cmdname = argv[1];
-+ argc--; argv++;
-+
-+
-+ if (!strcmp(cmdname, "list")) {
-+ return list_content(argc, argv);
-+ } else if (!strcmp(cmdname, "create")) {
-+ return create_archive(argc, argv);
-+ } else if (!strcmp(cmdname, "extract")) {
-+ return extract_content(argc, argv);
-+ } else if (!strcmp(cmdname, "verify")) {
-+ return verify_content(argc, argv);
-+ } else if (!strcmp(cmdname, "config")) {
-+ return dump_config(argc, argv);
-+ }
-+
-+ help();
-+ return 0;
-+}
-diff --git a/vma.h b/vma.h
-new file mode 100644
-index 0000000000..fa6f4df7e7
---- /dev/null
-+++ b/vma.h
-@@ -0,0 +1,149 @@
-+/*
-+ * VMA: Virtual Machine Archive
-+ *
-+ * Copyright (C) Proxmox Server Solutions
-+ *
-+ * Authors:
-+ * Dietmar Maurer (dietmar@proxmox.com)
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ *
-+ */
-+
-+#ifndef BACKUP_VMA_H
-+#define BACKUP_VMA_H
-+
-+#include <uuid/uuid.h>
-+#include "qapi/error.h"
-+#include "block/block.h"
-+
-+#define VMA_BLOCK_BITS 12
-+#define VMA_BLOCK_SIZE (1<<VMA_BLOCK_BITS)
-+#define VMA_CLUSTER_BITS (VMA_BLOCK_BITS+4)
-+#define VMA_CLUSTER_SIZE (1<<VMA_CLUSTER_BITS)
-+
-+#if VMA_CLUSTER_SIZE != 65536
-+#error unexpected cluster size
-+#endif
-+
-+#define VMA_EXTENT_HEADER_SIZE 512
-+#define VMA_BLOCKS_PER_EXTENT 59
-+#define VMA_MAX_CONFIGS 256
-+
-+#define VMA_MAX_EXTENT_SIZE \
-+ (VMA_EXTENT_HEADER_SIZE+VMA_CLUSTER_SIZE*VMA_BLOCKS_PER_EXTENT)
-+#if VMA_MAX_EXTENT_SIZE != 3867136
-+#error unexpected VMA_EXTENT_SIZE
-+#endif
-+
-+/* File Format Definitions */
-+
-+#define VMA_MAGIC (GUINT32_TO_BE(('V'<<24)|('M'<<16)|('A'<<8)|0x00))
-+#define VMA_EXTENT_MAGIC (GUINT32_TO_BE(('V'<<24)|('M'<<16)|('A'<<8)|'E'))
-+
-+typedef struct VmaDeviceInfoHeader {
-+ uint32_t devname_ptr; /* offset into blob_buffer table */
-+ uint32_t reserved0;
-+ uint64_t size; /* device size in bytes */
-+ uint64_t reserved1;
-+ uint64_t reserved2;
-+} VmaDeviceInfoHeader;
-+
-+typedef struct VmaHeader {
-+ uint32_t magic;
-+ uint32_t version;
-+ unsigned char uuid[16];
-+ int64_t ctime;
-+ unsigned char md5sum[16];
-+
-+ uint32_t blob_buffer_offset;
-+ uint32_t blob_buffer_size;
-+ uint32_t header_size;
-+
-+ unsigned char reserved[1984];
-+
-+ uint32_t config_names[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
-+ uint32_t config_data[VMA_MAX_CONFIGS]; /* offset into blob_buffer table */
-+
-+ uint32_t reserved1;
-+
-+ VmaDeviceInfoHeader dev_info[256];
-+} VmaHeader;
-+
-+typedef struct VmaExtentHeader {
-+ uint32_t magic;
-+ uint16_t reserved1;
-+ uint16_t block_count;
-+ unsigned char uuid[16];
-+ unsigned char md5sum[16];
-+ uint64_t blockinfo[VMA_BLOCKS_PER_EXTENT];
-+} VmaExtentHeader;
-+
-+/* functions/definitions to read/write vma files */
-+
-+typedef struct VmaReader VmaReader;
-+
-+typedef struct VmaWriter VmaWriter;
-+
-+typedef struct VmaConfigData {
-+ const char *name;
-+ const void *data;
-+ uint32_t len;
-+} VmaConfigData;
-+
-+typedef struct VmaStreamInfo {
-+ uint64_t size;
-+ uint64_t cluster_count;
-+ uint64_t transferred;
-+ uint64_t zero_bytes;
-+ int finished;
-+ char *devname;
-+} VmaStreamInfo;
-+
-+typedef struct VmaStatus {
-+ int status;
-+ bool closed;
-+ char errmsg[8192];
-+ char uuid_str[37];
-+ VmaStreamInfo stream_info[256];
-+} VmaStatus;
-+
-+typedef struct VmaDeviceInfo {
-+ uint64_t size; /* device size in bytes */
-+ const char *devname;
-+} VmaDeviceInfo;
-+
-+VmaWriter *vma_writer_create(const char *filename, uuid_t uuid, Error **errp);
-+int vma_writer_close(VmaWriter *vmaw, Error **errp);
-+void vma_writer_error_propagate(VmaWriter *vmaw, Error **errp);
-+void vma_writer_destroy(VmaWriter *vmaw);
-+int vma_writer_add_config(VmaWriter *vmaw, const char *name, gpointer data,
-+ size_t len);
-+int vma_writer_register_stream(VmaWriter *vmaw, const char *devname,
-+ size_t size);
-+
-+int64_t coroutine_fn vma_writer_write(VmaWriter *vmaw, uint8_t dev_id,
-+ int64_t cluster_num, unsigned char *buf,
-+ size_t *zero_bytes);
-+
-+int coroutine_fn vma_writer_close_stream(VmaWriter *vmaw, uint8_t dev_id);
-+int coroutine_fn vma_writer_flush_output(VmaWriter *vmaw);
-+
-+int vma_writer_get_status(VmaWriter *vmaw, VmaStatus *status);
-+void vma_writer_set_error(VmaWriter *vmaw, const char *fmt, ...);
-+
-+
-+VmaReader *vma_reader_create(const char *filename, Error **errp);
-+void vma_reader_destroy(VmaReader *vmar);
-+VmaHeader *vma_reader_get_header(VmaReader *vmar);
-+GList *vma_reader_get_config_data(VmaReader *vmar);
-+VmaDeviceInfo *vma_reader_get_device_info(VmaReader *vmar, guint8 dev_id);
-+int vma_reader_register_bs(VmaReader *vmar, guint8 dev_id,
-+ BlockBackend *target, bool write_zeroes,
-+ Error **errp);
-+int vma_reader_restore(VmaReader *vmar, int vmstate_fd, bool verbose,
-+ Error **errp);
-+int vma_reader_verify(VmaReader *vmar, bool verbose, Error **errp);
-+
-+#endif /* BACKUP_VMA_H */
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Tue, 5 Dec 2017 12:12:15 +0100
-Subject: [PATCH] backup: fix race in backup-stop command
-
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
----
- blockdev.c | 90 +++++++++++++++++++++++++++++++++-----------------------------
- blockjob.c | 8 +++---
- 2 files changed, 52 insertions(+), 46 deletions(-)
-
-diff --git a/blockdev.c b/blockdev.c
-index 19a82e8774..d3490936d8 100644
---- a/blockdev.c
-+++ b/blockdev.c
-@@ -2934,31 +2934,6 @@ out:
- aio_context_release(aio_context);
- }
-
--void block_job_event_cancelled(BlockJob *job);
--void block_job_event_completed(BlockJob *job, const char *msg);
--static void block_job_cb(void *opaque, int ret)
--{
-- /* Note that this function may be executed from another AioContext besides
-- * the QEMU main loop. If you need to access anything that assumes the
-- * QEMU global mutex, use a BH or introduce a mutex.
-- */
--
-- BlockDriverState *bs = opaque;
-- const char *msg = NULL;
--
-- assert(bs->job);
--
-- if (ret < 0) {
-- msg = strerror(-ret);
-- }
--
-- if (block_job_is_cancelled(bs->job)) {
-- block_job_event_cancelled(bs->job);
-- } else {
-- block_job_event_completed(bs->job, msg);
-- }
--}
--
- /* PVE backup related function */
-
- static struct PVEBackupState {
-@@ -2975,6 +2950,8 @@ static struct PVEBackupState {
- size_t total;
- size_t transferred;
- size_t zero_bytes;
-+ QemuMutex backup_mutex;
-+ bool backup_mutex_initialized;
- } backup_state;
-
- typedef struct PVEBackupDevInfo {
-@@ -3055,6 +3032,13 @@ static int pvebackup_dump_cb(void *opaque, BlockBackend *target,
-
- static void pvebackup_cleanup(void)
- {
-+ qemu_mutex_lock(&backup_state.backup_mutex);
-+ // Avoid race between block jobs and backup-cancel command:
-+ if (!backup_state.vmaw) {
-+ qemu_mutex_unlock(&backup_state.backup_mutex);
-+ return;
-+ }
-+
- backup_state.end_time = time(NULL);
-
- if (backup_state.vmaw) {
-@@ -3064,16 +3048,9 @@ static void pvebackup_cleanup(void)
- backup_state.vmaw = NULL;
- }
-
-- if (backup_state.di_list) {
-- GList *l = backup_state.di_list;
-- while (l) {
-- PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
-- l = g_list_next(l);
-- g_free(di);
-- }
-- g_list_free(backup_state.di_list);
-- backup_state.di_list = NULL;
-- }
-+ g_list_free(backup_state.di_list);
-+ backup_state.di_list = NULL;
-+ qemu_mutex_unlock(&backup_state.backup_mutex);
- }
-
- static void coroutine_fn backup_close_vma_stream(void *opaque)
-@@ -3085,6 +3062,8 @@ static void coroutine_fn backup_close_vma_stream(void *opaque)
-
- static void pvebackup_complete_cb(void *opaque, int ret)
- {
-+ // This always runs in the main loop
-+
- PVEBackupDevInfo *di = opaque;
-
- di->completed = true;
-@@ -3094,8 +3073,6 @@ static void pvebackup_complete_cb(void *opaque, int ret)
- ret, strerror(-ret));
- }
-
-- BlockDriverState *bs = di->bs;
--
- di->bs = NULL;
- di->target = NULL;
-
-@@ -3104,7 +3081,11 @@ static void pvebackup_complete_cb(void *opaque, int ret)
- qemu_coroutine_enter(co);
- }
-
-- block_job_cb(bs, ret);
-+ // remove self from job queue
-+ qemu_mutex_lock(&backup_state.backup_mutex);
-+ backup_state.di_list = g_list_remove(backup_state.di_list, di);
-+ g_free(di);
-+ qemu_mutex_unlock(&backup_state.backup_mutex);
-
- if (!backup_state.cancel) {
- pvebackup_run_next_job();
-@@ -3114,6 +3095,12 @@ static void pvebackup_complete_cb(void *opaque, int ret)
- static void pvebackup_cancel(void *opaque)
- {
- backup_state.cancel = true;
-+ qemu_mutex_lock(&backup_state.backup_mutex);
-+ // Avoid race between block jobs and backup-cancel command:
-+ if (!backup_state.vmaw) {
-+ qemu_mutex_unlock(&backup_state.backup_mutex);
-+ return;
-+ }
-
- if (!backup_state.error) {
- error_setg(&backup_state.error, "backup cancelled");
-@@ -3131,13 +3118,17 @@ static void pvebackup_cancel(void *opaque)
- if (!di->completed && di->bs) {
- BlockJob *job = di->bs->job;
- if (job) {
-+ AioContext *aio_context = blk_get_aio_context(job->blk);
-+ aio_context_acquire(aio_context);
- if (!di->completed) {
-- block_job_cancel_sync(job);
-+ block_job_cancel(job);
- }
-+ aio_context_release(aio_context);
- }
- }
- }
-
-+ qemu_mutex_unlock(&backup_state.backup_mutex);
- pvebackup_cleanup();
- }
-
-@@ -3193,24 +3184,31 @@ static int config_to_vma(const char *file, BackupFormat format,
- bool block_job_should_pause(BlockJob *job);
- static void pvebackup_run_next_job(void)
- {
-+ qemu_mutex_lock(&backup_state.backup_mutex);
-+
- GList *l = backup_state.di_list;
- while (l) {
- PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
- l = g_list_next(l);
- if (!di->completed && di->bs && di->bs->job) {
- BlockJob *job = di->bs->job;
-+ AioContext *aio_context = blk_get_aio_context(job->blk);
-+ aio_context_acquire(aio_context);
-+ qemu_mutex_unlock(&backup_state.backup_mutex);
- if (block_job_should_pause(job)) {
-- bool cancel = backup_state.error || backup_state.cancel;
-- if (cancel) {
-- block_job_cancel(job);
-+ if (backup_state.error || backup_state.cancel) {
-+ block_job_cancel_sync(job);
- } else {
- block_job_resume(job);
- }
- }
-+ aio_context_release(aio_context);
- return;
- }
- }
-+ qemu_mutex_unlock(&backup_state.backup_mutex);
-
-+ // no more jobs, run the cleanup
- pvebackup_cleanup();
- }
-
-@@ -3233,6 +3231,11 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
- UuidInfo *uuid_info;
- BlockJob *job;
-
-+ if (!backup_state.backup_mutex_initialized) {
-+ qemu_mutex_init(&backup_state.backup_mutex);
-+ backup_state.backup_mutex_initialized = true;
-+ }
-+
- if (backup_state.di_list) {
- error_set(errp, ERROR_CLASS_GENERIC_ERROR,
- "previous backup not finished");
-@@ -3405,6 +3408,7 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
- uuid_copy(backup_state.uuid, uuid);
- uuid_unparse_lower(uuid, backup_state.uuid_str);
-
-+ qemu_mutex_lock(&backup_state.backup_mutex);
- backup_state.di_list = di_list;
-
- backup_state.total = total;
-@@ -3428,6 +3432,8 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format,
- block_job_start(job);
- }
-
-+ qemu_mutex_unlock(&backup_state.backup_mutex);
-+
- if (!backup_state.error) {
- pvebackup_run_next_job(); // run one job
- }
-diff --git a/blockjob.c b/blockjob.c
-index cb3741f6dd..199d5852db 100644
---- a/blockjob.c
-+++ b/blockjob.c
-@@ -37,8 +37,8 @@
- #include "qemu/timer.h"
- #include "qapi-event.h"
-
--void block_job_event_cancelled(BlockJob *job);
--void block_job_event_completed(BlockJob *job, const char *msg);
-+static void block_job_event_cancelled(BlockJob *job);
-+static void block_job_event_completed(BlockJob *job, const char *msg);
-
- /* Transactional group of block jobs */
- struct BlockJobTxn {
-@@ -688,7 +688,7 @@ static void block_job_iostatus_set_err(BlockJob *job, int error)
- }
- }
-
--void block_job_event_cancelled(BlockJob *job)
-+static void block_job_event_cancelled(BlockJob *job)
- {
- if (block_job_is_internal(job)) {
- return;
-@@ -702,7 +702,7 @@ void block_job_event_cancelled(BlockJob *job)
- &error_abort);
- }
-
--void block_job_event_completed(BlockJob *job, const char *msg)
-+static void block_job_event_completed(BlockJob *job, const char *msg)
- {
- if (block_job_is_internal(job)) {
- return;
---
-2.11.0
-
+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wolfgang Bumiller <w.bumiller@proxmox.com>
-Date: Thu, 15 Feb 2018 11:07:56 +0100
-Subject: [PATCH] vma: add throttling options to drive mapping fifo protocol
-
-Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
----
- vma.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
- 1 file changed, 73 insertions(+), 9 deletions(-)
-
-diff --git a/vma.c b/vma.c
-index 04915427c8..91ae14cdc3 100644
---- a/vma.c
-+++ b/vma.c
-@@ -18,7 +18,9 @@
- #include "qemu-common.h"
- #include "qemu/error-report.h"
- #include "qemu/main-loop.h"
-+#include "qemu/cutils.h"
- #include "qapi/qmp/qstring.h"
-+#include "qapi/qmp/qint.h"
- #include "sysemu/char.h" /* qstring_from_str */
- #include "sysemu/block-backend.h"
-
-@@ -133,9 +135,39 @@ typedef struct RestoreMap {
- char *devname;
- char *path;
- char *format;
-+ uint64_t throttling_bps;
-+ char *throttling_group;
- bool write_zero;
- } RestoreMap;
-
-+static bool try_parse_option(char **line, const char *optname, char **out, const char *inbuf) {
-+ size_t optlen = strlen(optname);
-+ if (strncmp(*line, optname, optlen) != 0 || (*line)[optlen] != '=') {
-+ return false;
-+ }
-+ if (*out) {
-+ g_error("read map failed - duplicate value for option '%s'", optname);
-+ }
-+ char *value = (*line) + optlen + 1; /* including a '=' */
-+ char *colon = strchr(value, ':');
-+ if (!colon) {
-+ g_error("read map failed - option '%s' not terminated ('%s')",
-+ optname, inbuf);
-+ }
-+ *line = colon+1;
-+ *out = g_strndup(value, colon - value);
-+ return true;
-+}
-+
-+static uint64_t verify_u64(const char *text) {
-+ uint64_t value;
-+ const char *endptr = NULL;
-+ if (qemu_strtou64(text, &endptr, 0, &value) != 0 || !endptr || *endptr) {
-+ g_error("read map failed - not a number: %s", text);
-+ }
-+ return value;
-+}
-+
- static int extract_content(int argc, char **argv)
- {
- int c, ret = 0;
-@@ -209,6 +241,9 @@ static int extract_content(int argc, char **argv)
- while (1) {
- char inbuf[8192];
- char *line = fgets(inbuf, sizeof(inbuf), map);
-+ char *format = NULL;
-+ char *bps = NULL;
-+ char *group = NULL;
- if (!line || line[0] == '\0' || !strcmp(line, "done\n")) {
- break;
- }
-@@ -220,15 +255,19 @@ static int extract_content(int argc, char **argv)
- }
- }
-
-- char *format = NULL;
-- if (strncmp(line, "format=", sizeof("format=")-1) == 0) {
-- format = line + sizeof("format=")-1;
-- char *colon = strchr(format, ':');
-- if (!colon) {
-- g_error("read map failed - found only a format ('%s')", inbuf);
-+ 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))
-+ {
-+ break;
- }
-- format = g_strndup(format, colon - format);
-- line = colon+1;
-+ }
-+
-+ uint64_t bps_value = 0;
-+ if (bps) {
-+ bps_value = verify_u64(bps);
-+ g_free(bps);
- }
-
- const char *path;
-@@ -254,6 +293,8 @@ static int extract_content(int argc, char **argv)
- map->devname = g_strdup(devname);
- map->path = g_strdup(path);
- map->format = format;
-+ map->throttling_bps = bps_value;
-+ map->throttling_group = group;
- map->write_zero = write_zero;
-
- g_hash_table_insert(devmap, map->devname, map);
-@@ -281,6 +322,8 @@ static int extract_content(int argc, char **argv)
- } else if (di) {
- char *devfn = NULL;
- const char *format = NULL;
-+ uint64_t throttling_bps = 0;
-+ const char *throttling_group = NULL;
- int flags = BDRV_O_RDWR | BDRV_O_NO_FLUSH;
- bool write_zero = true;
-
-@@ -292,6 +335,8 @@ static int extract_content(int argc, char **argv)
- }
- devfn = map->path;
- format = map->format;
-+ throttling_bps = map->throttling_bps;
-+ throttling_group = map->throttling_group;
- write_zero = map->write_zero;
- } else {
- devfn = g_strdup_printf("%s/tmp-disk-%s.raw",
-@@ -328,12 +373,31 @@ static int extract_content(int argc, char **argv)
- qdict_put(options, "driver", qstring_from_str("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 (throttling_group) {
-+ blk_io_limits_enable(blk, throttling_group);
-+ }
-+
-+ if (throttling_bps) {
-+ if (!throttling_group) {
-+ blk_io_limits_enable(blk, devfn);
-+ }
-+
-+ ThrottleConfig cfg;
-+ throttle_config_init(&cfg);
-+ cfg.buckets[THROTTLE_BPS_WRITE].avg = throttling_bps;
-+ Error *err = NULL;
-+ if (!throttle_is_valid(&cfg, &err)) {
-+ error_report_err(err);
-+ g_error("failed to apply throttling");
-+ }
-+ blk_set_io_limits(blk, &cfg);
-+ }
-+
- if (vma_reader_register_bs(vmar, i, blk, write_zero, &errp) < 0) {
- g_error("%s", error_get_pretty(errp));
- }
---
-2.11.0
-
-pve/0001-fr-ca-keymap-corrections.patch
+pve/0001-block-file-change-locking-default-to-off.patch
pve/0002-Adjust-network-script-path-to-etc-kvm.patch
pve/0003-qemu-img-return-success-on-info-without-snapshots.patch
pve/0004-use-kvm-by-default.patch
pve/0015-vnc-altgr-emulation.patch
pve/0016-vnc-make-x509-imply-tls-again.patch
pve/0017-vnc-PVE-VNC-authentication.patch
-pve/0018-migrate-fix-possible-unitialised-return-value.patch
-pve/0019-block-rbd-disable-rbd_cache_writethrough_until_flush.patch
-pve/0020-block-snapshot-qmp_snapshot_drive-add-aiocontext.patch
-pve/0021-block-snapshot-qmp_delete_drive_snapshot-add-aiocont.patch
-pve/0022-glusterfs-no-default-logfile-if-daemonized.patch
-pve/0023-glusterfs-allow-partial-reads.patch
-pve/0024-block-add-the-zeroinit-block-driver-filter.patch
-pve/0025-qemu-img-dd-add-osize-and-read-from-to-stdin-stdout.patch
-pve/0026-backup-modify-job-api.patch
-pve/0027-backup-introduce-vma-archive-format.patch
-pve/0028-adding-old-vma-files.patch
-pve/0029-backup-fix-race-in-backup-stop-command.patch
-pve/0030-vma-add-throttling-options-to-drive-mapping-fifo-pro.patch
+pve/0018-block-rbd-disable-rbd_cache_writethrough_until_flush.patch
+pve/0019-block-snapshot-qmp_snapshot_drive-add-aiocontext.patch
+pve/0020-block-snapshot-qmp_delete_drive_snapshot-add-aiocont.patch
+pve/0021-glusterfs-no-default-logfile-if-daemonized.patch
+pve/0022-glusterfs-allow-partial-reads.patch
+pve/0023-block-add-the-zeroinit-block-driver-filter.patch
+pve/0024-qemu-img-dd-add-osize-and-read-from-to-stdin-stdout.patch
+pve/0025-backup-modify-job-api.patch
+pve/0026-backup-introduce-vma-archive-format.patch
+pve/0027-adding-old-vma-files.patch
extra/0001-Revert-target-i386-disable-LINT0-after-reset.patch
-extra/0002-virtio-serial-fix-segfault-on-disconnect.patch
-extra/0003-megasas-always-store-SCSIRequest-into-MegasasCmd.patch
-extra/0004-slirp-check-len-against-dhcp-options-array-end.patch
-extra/0005-IDE-Do-not-flush-empty-CDROM-drives.patch
-extra/0006-bitmap-add-bitmap_copy_and_clear_atomic.patch
-extra/0007-memory-add-support-getting-and-using-a-dirty-bitmap-.patch
-extra/0008-vga-add-vga_scanline_invalidated-helper.patch
-extra/0009-vga-make-display-updates-thread-safe.patch
-extra/0010-vga-fix-display-update-region-calculation.patch
-extra/0011-vga-fix-display-update-region-calculation-split-scre.patch
-extra/0012-vga-stop-passing-pointers-to-vga_draw_line-functions.patch
-extra/0013-multiboot-validate-multiboot-header-address-values.patch
-extra/0014-virtio-fix-descriptor-counting-in-virtqueue_pop.patch
-extra/0015-nbd-server-CVE-2017-15119-Reject-options-larger-than.patch
-extra/0016-vga-migration-Update-memory-map-in-post_load.patch
-extra/0017-vga-drop-line_offset-variable.patch
-extra/0018-vga-handle-cirrus-vbe-mode-wraparounds.patch
-extra/0019-vga-add-ram_addr_t-cast.patch
-extra/0020-vga-fix-region-checks-in-wraparound-case.patch
-extra/0021-io-monitor-encoutput-buffer-size-from-websocket-GSou.patch
-extra/0022-9pfs-use-g_malloc0-to-allocate-space-for-xattr.patch
-extra/0023-cirrus-fix-oob-access-in-mode4and5-write-functions.patch
-extra/0024-virtio-check-VirtQueue-Vring-object-is-set.patch
-extra/0025-block-gluster-glfs_lseek-workaround.patch
-extra/0026-gluster-add-support-for-PREALLOC_MODE_FALLOC.patch
-extra/0027-target-i386-Use-host_vendor_fms-in-max_x86_cpu_initf.patch
-extra/0028-target-i386-Define-CPUID_MODEL_ID_SZ-macro.patch
-extra/0029-target-i386-Don-t-use-x86_cpu_load_def-on-max-CPU-mo.patch
-extra/0030-i386-Change-X86CPUDefinition-model_id-to-const-char.patch
-extra/0031-i386-Add-support-for-SPEC_CTRL-MSR.patch
-extra/0032-i386-Add-spec-ctrl-CPUID-bit.patch
-extra/0033-i386-Add-FEAT_8000_0008_EBX-CPUID-feature-word.patch
-extra/0034-i386-Add-new-IBRS-versions-of-Intel-CPU-models.patch
-extra/0035-ratelimit-don-t-align-wait-time-with-slices.patch
-extra/0036-nbd-make-it-thread-safe-fix-qcow2-over-nbd.patch
-extra/0037-nbd-strict-nbd_wr_syncv.patch
-extra/0038-nbd-read_sync-and-friends-return-0-on-success.patch
-extra/0039-nbd-make-nbd_drop-public.patch
-extra/0040-nbd-server-get-rid-of-nbd_negotiate_read-and-friends.patch
-extra/0041-nbd-client-Fix-regression-when-server-sends-garbage.patch
-extra/0042-fix-build-failure-in-nbd_read_reply_entry.patch
-extra/0043-nbd-client-avoid-spurious-qio_channel_yield-re-entry.patch
-extra/0044-nbd-client-avoid-read_reply_co-entry-if-send-failed.patch
-extra/0045-qemu-iotests-improve-nbd-fault-injector.py-startup-p.patch
-extra/0046-qemu-iotests-test-NBD-over-UNIX-domain-sockets-in-08.patch
-extra/0047-block-nbd-client-nbd_co_send_request-fix-return-code.patch
-extra/0048-target-i386-cpu-Add-new-EPYC-CPU-model.patch
-extra/0049-i386-Add-EPYC-IBPB-CPU-model.patch
+extra/0002-ratelimit-don-t-align-wait-time-with-slices.patch
--- /dev/null
+Copyright (c) Individual contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. Neither the name of PyCA Cryptography nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null
+ Key code / scan code / key symbol mapping database
+ ==================================================
+
+This module provides a database that maps between different
+key code / scan code / key symbol sets:
+
+ - Linux evdev
+ - OS-X
+ - AT Set 1
+ - AT Set 2
+ - AT Set 3
+ - XT
+ - Linux XT KBD driver
+ - USB HID
+ - Win32
+ - XWin XT
+ - XKBD XT
+ - Xorg Evdev
+ - Xorg KBD
+ - Xorg OS-X
+ - XOrg Cygwin
+ - RFB
+
+Licensing
+---------
+
+The contents of this package are dual licensed under the terms of:
+
+ - GNU General Public License (version 2 or later)
+ - 3-clause BSD License
+
+The output files generated by keymap-gen may be distributed & used under
+the terms of either of the above licenses.
+
+Data formats
+------------
+
+The following output formats are possible
+
+ - Code map
+
+ An array mapping between key code sets values
+
+ Indexes in the array are values from the source code set.
+ Entries in the array are values from the target code set
+
+
+ - Code table
+
+ An array listing all values in a key code set
+
+ Indexes in the array are simply a numeric counter
+ Entries in the array are values from the key code set
+
+ The size of the array matches the total number of entries in
+ the keycode database.
+
+
+ - Name map
+
+ An array mapping between key code sets values and names
+
+ Indexes in the array are values from the source code set
+ Entries in the array are names from the target code set
+
+
+ - Name table
+
+ An array listing all names in a key code set
+
+ Indexes in the array are simply a numeric counter
+ Entries in the array are values from the key code set
+
+ The size of the array matches the total number of entries in
+ the keycode database.
+
+
+Output languages
+----------------
+
+The tool is capable of generating data tables for the following
+programming languages / environments
+
+ - Standard C
+ - GLib2 (standard C, but with GLib2 data types)
+ - Python
+ - Perl
+
+
+Usage
+-----
+
+Map values from AT Set 1 to USB HID, generating tables for the
+C programming language
+
+ $ keymap-gen --lang stdc code-map data/keymaps.csv atset1 usb
+
+Generate a tables of names for Linux key codes, OS-X key codes,
+in python - equivalent array indexes map between the two sets.
+A variable name override is used
+
+ $ keymap-gen --varname linux_keycodes --lang stdc \
+ code-table data/keymaps.csv linux
+ $ keymap-gen --varname osx_keycodes --lang stdc \
+ code-table data/keymaps.csv os-x
+
+Generate a mapping from XOrg XWin values to Win32 names
+
+ $ keymap-gen --lang perl name-map data/keymaps.csv xorgxwin win32
+
+Generate a table of names for Linux key codes in Perl
+
+ $ keymap-gen --lang perl name-table data/keymaps.csv linux
+
--- /dev/null
+This directory contains the raw data for mapping between different
+keyboard codes. Naming if often based on the US keyboard layout, but
+does not indicate the symbol actually generated by the key.
+
+The columns currently in this data set are:
+
+Linux
+-----
+
+Name and value of the hardware independent keycodes used by the linux
+kernel and exposed through the input subsystem.
+
+References: linux/input.h
+
+macOS
+-----
+
+Low level key codes as exposed by Mac OS X/macOS.
+
+References: Carbon/HIToolbox/Events.h
+
+PC scan code sets
+-----------------
+
+Scan codes for the three orignal PC keyboard generations:
+
+ Set 1: XT
+ Set 2: AT
+ Set 3: PS/2
+
+The sets include codes for modern keys as well and not just the keys
+present on those original keyboards.
+
+References: linux/drivers/input/keyboard/atkbd.c
+
+USB HID
+-------
+
+Codes as specified by the HID profile in USB.
+
+References: linux/drivers/hid/usbhid/usbkbd.c
+
+Windows Virtual-key codes
+-------------------------
+
+The low level, hardware independent "VKEYs" exposed by Windows.
+
+References: mingw32/winuser.h
+
+XWin XT
+-------
+
+X11 keycodes generated by the XWin server. Based on the XT scan code
+set.
+
+References: xorg-server/hw/xwin/{winkeybd.c,winkeynames.h}
+
+Xfree86 KBD XT
+--------------
+
+X11 keycodes generated by the Xfree86 keyboard drivers. Based on the XT
+scan code set.
+
+References: xf86-input-keyboard/src/at_scancode.c
+
+X11 keysyms
+-----------
+
+Corresponding X11 keysym value(s) for a US keyboard layout.
+
+WARNING: These columns represent symbols, not physical keys, and should
+ be used with extreme care.
+
+References: http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h
+
+HTML KeyboardEvent.code
+-----------------------
+
+Key codes seen in the KeyboardEvent.code attribute as part of the
+UI Events specification.
+
+References: https://www.w3.org/TR/uievents-code/
+
+XKEYBOARD key names
+-------------------
+
+Hardware independent key names as used in the XKEYBOARD extension.
+
+References: /usr/share/X11/xkb/keycodes/
--- /dev/null
+"Linux Name","Linux Keycode","OS-X Name","OS-X Keycode","AT set1 keycode","AT set2 keycode","AT set3 keycode","USB Keycodes","Win32 Name","Win32 Keycode","Xwin XT","Xfree86 KBD XT","X11 keysym name","X11 keysym","HTML code","XKB key name","QEMU QKeyCode","Sun KBD","Apple ADB"
+KEY_RESERVED,0,,0xff,,,,,,,,,,,,,unmapped,,0xff
+KEY_ESC,1,Escape,0x35,0x01,0x76,0x08,41,VK_ESCAPE,0x1b,1,1,XK_Escape,0xff1b,Escape,ESC,esc,0x1d,0x35
+KEY_1,2,ANSI_1,0x12,0x02,0x16,0x16,30,VK_1,0x31,2,2,XK_1,0x0031,Digit1,AE01,1,0x1e,0x12
+KEY_1,2,ANSI_1,0x12,0x02,0x16,0x16,30,VK_1,0x31,2,2,XK_exclam,0x0021,Digit1,AE01,1,0x1e,0x12
+KEY_2,3,ANSI_2,0x13,0x03,0x1e,0x1e,31,VK_2,0x32,3,3,XK_2,0x0032,Digit2,AE02,2,0x1f,0x13
+KEY_2,3,ANSI_2,0x13,0x03,0x1e,0x1e,31,VK_2,0x32,3,3,XK_at,0x0040,Digit2,AE02,2,0x1f,0x13
+KEY_3,4,ANSI_3,0x14,0x04,0x26,0x26,32,VK_3,0x33,4,4,XK_3,0x0033,Digit3,AE03,3,0x20,0x14
+KEY_3,4,ANSI_3,0x14,0x04,0x26,0x26,32,VK_3,0x33,4,4,XK_numbersign,0x0023,Digit3,AE03,3,0x20,0x14
+KEY_4,5,ANSI_4,0x15,0x05,0x25,0x25,33,VK_4,0x34,5,5,XK_4,0x0034,Digit4,AE04,4,0x21,0x15
+KEY_4,5,ANSI_4,0x15,0x05,0x25,0x25,33,VK_4,0x34,5,5,XK_dollar,0x0024,Digit4,AE04,4,0x21,0x15
+KEY_5,6,ANSI_5,0x17,0x06,0x2e,0x2e,34,VK_5,0x35,6,6,XK_5,0x0035,Digit5,AE05,5,0x22,0x17
+KEY_5,6,ANSI_5,0x17,0x06,0x2e,0x2e,34,VK_5,0x35,6,6,XK_percent,0x0025,Digit5,AE05,5,0x22,0x17
+KEY_6,7,ANSI_6,0x16,0x07,0x36,0x36,35,VK_6,0x36,7,7,XK_6,0x0036,Digit6,AE06,6,0x23,0x16
+KEY_6,7,ANSI_6,0x16,0x07,0x36,0x36,35,VK_6,0x36,7,7,XK_asciicircum,0x005e,Digit6,AE06,6,0x23,0x16
+KEY_7,8,ANSI_7,0x1a,0x08,0x3d,0x3d,36,VK_7,0x37,8,8,XK_7,0x0037,Digit7,AE07,7,0x24,0x1a
+KEY_7,8,ANSI_7,0x1a,0x08,0x3d,0x3d,36,VK_7,0x37,8,8,XK_ampersand,0x0026,Digit7,AE07,7,0x24,0x1a
+KEY_8,9,ANSI_8,0x1c,0x09,0x3e,0x3e,37,VK_8,0x38,9,9,XK_8,0x0038,Digit8,AE08,8,0x25,0x1c
+KEY_8,9,ANSI_8,0x1c,0x09,0x3e,0x3e,37,VK_8,0x38,9,9,XK_asterisk,0x002a,Digit8,AE08,8,0x25,0x1c
+KEY_9,10,ANSI_9,0x19,0x0a,0x46,0x46,38,VK_9,0x39,10,10,XK_9,0x0039,Digit9,AE09,9,0x26,0x19
+KEY_9,10,ANSI_9,0x19,0x0a,0x46,0x46,38,VK_9,0x39,10,10,XK_parenleft,0x0028,Digit9,AE09,9,0x26,0x19
+KEY_0,11,ANSI_0,0x1d,0x0b,0x45,0x45,39,VK_0,0x30,11,11,XK_0,0x0030,Digit0,AE10,0,0x27,0x1d
+KEY_0,11,ANSI_0,0x1d,0x0b,0x45,0x45,39,VK_0,0x30,11,11,XK_parenright,0x0029,Digit0,AE10,0,0x27,0x1d
+KEY_MINUS,12,ANSI_Minus,0x1b,0x0c,0x4e,0x4e,45,VK_OEM_MINUS,0xbd,12,12,XK_minus,0x002d,Minus,AE11,minus,0x28,0x1b
+KEY_MINUS,12,ANSI_Minus,0x1b,0x0c,0x4e,0x4e,45,VK_OEM_MINUS,0xbd,12,12,XK_underscore,0x005f,Minus,AE11,minus,0x28,0x1b
+KEY_EQUAL,13,ANSI_Equal,0x18,0x0d,0x55,0x55,46,VK_OEM_PLUS,0xbb,13,13,XK_equal,0x003d,Equal,AE12,equal,0x29,0x18
+KEY_EQUAL,13,ANSI_Equal,0x18,0x0d,0x55,0x55,46,VK_OEM_PLUS,0xbb,13,13,XK_plus,0x002b,Equal,AE12,equal,0x29,0x18
+KEY_BACKSPACE,14,Delete,0x33,0x0e,0x66,0x66,42,VK_BACK,0x08,14,14,XK_BackSpace,0xff08,Backspace,BKSP,backspace,0x2b,0x33
+KEY_TAB,15,Tab,0x30,0x0f,0x0d,0x0d,43,VK_TAB,0x09,15,15,XK_Tab,0xff09,Tab,TAB,tab,0x35,0x30
+KEY_Q,16,ANSI_Q,0xc,0x10,0x15,0x15,20,VK_Q,0x51,16,16,XK_Q,0x0051,KeyQ,AD01,q,0x36,0xc
+KEY_Q,16,ANSI_Q,0xc,0x10,0x15,0x15,20,VK_Q,0x51,16,16,XK_q,0x0071,KeyQ,AD01,q,0x36,0xc
+KEY_W,17,ANSI_W,0xd,0x11,0x1d,0x1d,26,VK_W,0x57,17,17,XK_W,0x0057,KeyW,AD02,w,0x37,0xd
+KEY_W,17,ANSI_W,0xd,0x11,0x1d,0x1d,26,VK_W,0x57,17,17,XK_w,0x0077,KeyW,AD02,w,0x37,0xd
+KEY_E,18,ANSI_E,0xe,0x12,0x24,0x24,8,VK_E,0x45,18,18,XK_E,0x0045,KeyE,AD03,e,0x38,0xe
+KEY_E,18,ANSI_E,0xe,0x12,0x24,0x24,8,VK_E,0x45,18,18,XK_e,0x0065,KeyE,AD03,e,0x38,0xe
+KEY_R,19,ANSI_R,0xf,0x13,0x2d,0x2d,21,VK_R,0x52,19,19,XK_R,0x0052,KeyR,AD04,r,0x39,0xf
+KEY_R,19,ANSI_R,0xf,0x13,0x2d,0x2d,21,VK_R,0x52,19,19,XK_r,0x0072,KeyR,AD04,r,0x39,0xf
+KEY_T,20,ANSI_T,0x11,0x14,0x2c,0x2c,23,VK_T,0x54,20,20,XK_T,0x0054,KeyT,AD05,t,0x3a,0x11
+KEY_T,20,ANSI_T,0x11,0x14,0x2c,0x2c,23,VK_T,0x54,20,20,XK_t,0x0074,KeyT,AD05,t,0x3a,0x11
+KEY_Y,21,ANSI_Y,0x10,0x15,0x35,0x35,28,VK_Y,0x59,21,21,XK_Y,0x0059,KeyY,AD06,y,0x3b,0x10
+KEY_Y,21,ANSI_Y,0x10,0x15,0x35,0x35,28,VK_Y,0x59,21,21,XK_y,0x0079,KeyY,AD06,y,0x3b,0x10
+KEY_U,22,ANSI_U,0x20,0x16,0x3c,0x3c,24,VK_U,0x55,22,22,XK_U,0x0055,KeyU,AD07,u,0x3c,0x20
+KEY_U,22,ANSI_U,0x20,0x16,0x3c,0x3c,24,VK_U,0x55,22,22,XK_u,0x0075,KeyU,AD07,u,0x3c,0x20
+KEY_I,23,ANSI_I,0x22,0x17,0x43,0x43,12,VK_I,0x49,23,23,XK_I,0x0049,KeyI,AD08,i,0x3d,0x22
+KEY_I,23,ANSI_I,0x22,0x17,0x43,0x43,12,VK_I,0x49,23,23,XK_i,0x0069,KeyI,AD08,i,0x3d,0x22
+KEY_O,24,ANSI_O,0x1f,0x18,0x44,0x44,18,VK_O,0x4f,24,24,XK_O,0x004f,KeyO,AD09,o,0x3e,0x1f
+KEY_O,24,ANSI_O,0x1f,0x18,0x44,0x44,18,VK_O,0x4f,24,24,XK_o,0x006f,KeyO,AD09,o,0x3e,0x1f
+KEY_P,25,ANSI_P,0x23,0x19,0x4d,0x4d,19,VK_P,0x50,25,25,XK_P,0x0050,KeyP,AD10,p,0x3f,0x23
+KEY_P,25,ANSI_P,0x23,0x19,0x4d,0x4d,19,VK_P,0x50,25,25,XK_p,0x0070,KeyP,AD10,p,0x3f,0x23
+KEY_LEFTBRACE,26,ANSI_LeftBracket,0x21,0x1a,0x54,0x54,47,VK_OEM_4,0xdb,26,26,XK_bracketleft,0x005b,BracketLeft,AD11,bracket_left,0x40,0x21
+KEY_LEFTBRACE,26,ANSI_LeftBracket,0x21,0x1a,0x54,0x54,47,VK_OEM_4,0xdb,26,26,XK_braceleft,0x007b,BracketLeft,AD11,bracket_left,0x40,0x21
+KEY_RIGHTBRACE,27,ANSI_RightBracket,0x1e,0x1b,0x5b,0x5b,48,VK_OEM_6,0xdd,27,27,XK_bracketright,0x005d,BracketRight,AD12,bracket_right,0x41,0x1e
+KEY_RIGHTBRACE,27,ANSI_RightBracket,0x1e,0x1b,0x5b,0x5b,48,VK_OEM_6,0xdd,27,27,XK_braceright,0x007d,BracketRight,AD12,bracket_right,0x41,0x1e
+KEY_ENTER,28,Return,0x24,0x1c,0x5a,0x5a,40,VK_RETURN,0x0d,28,28,XK_Return,0xff0d,Enter,RTRN,ret,0x59,0x24
+KEY_LEFTCTRL,29,Control,0x3b,0x1d,0x14,0x11,224,VK_LCONTROL,0xa2,29,29,XK_Control_L,0xffe3,ControlLeft,LCTL,ctrl,0x4c,0x36
+KEY_LEFTCTRL,29,Control,0x3b,0x1d,0x14,0x11,224,VK_CONTROL,0x11,29,29,XK_Control_L,0xffe3,ControlLeft,LCTL,ctrl,0x4c,0x36
+KEY_A,30,ANSI_A,0x0,0x1e,0x1c,0x1c,4,VK_A,0x41,30,30,XK_A,0x0041,KeyA,AC01,a,0x4d,0x0
+KEY_A,30,ANSI_A,0x0,0x1e,0x1c,0x1c,4,VK_A,0x41,30,30,XK_a,0x0061,KeyA,AC01,a,0x4d,0x0
+KEY_S,31,ANSI_S,0x1,0x1f,0x1b,0x1b,22,VK_S,0x53,31,31,XK_S,0x0053,KeyS,AC02,s,0x4e,0x1
+KEY_S,31,ANSI_S,0x1,0x1f,0x1b,0x1b,22,VK_S,0x53,31,31,XK_s,0x0073,KeyS,AC02,s,0x4e,0x1
+KEY_D,32,ANSI_D,0x2,0x20,0x23,0x23,7,VK_D,0x44,32,32,XK_D,0x0044,KeyD,AC03,d,0x4f,0x2
+KEY_D,32,ANSI_D,0x2,0x20,0x23,0x23,7,VK_D,0x44,32,32,XK_d,0x0064,KeyD,AC03,d,0x4f,0x2
+KEY_F,33,ANSI_F,0x3,0x21,0x2b,0x2b,9,VK_F,0x46,33,33,XK_F,0x0046,KeyF,AC04,f,0x50,0x3
+KEY_F,33,ANSI_F,0x3,0x21,0x2b,0x2b,9,VK_F,0x46,33,33,XK_f,0x0066,KeyF,AC04,f,0x50,0x3
+KEY_G,34,ANSI_G,0x5,0x22,0x34,0x34,10,VK_G,0x47,34,34,XK_G,0x0047,KeyG,AC05,g,0x51,0x5
+KEY_G,34,ANSI_G,0x5,0x22,0x34,0x34,10,VK_G,0x47,34,34,XK_g,0x0067,KeyG,AC05,g,0x51,0x5
+KEY_H,35,ANSI_H,0x4,0x23,0x33,0x33,11,VK_H,0x48,35,35,XK_H,0x0048,KeyH,AC06,h,0x52,0x4
+KEY_H,35,ANSI_H,0x4,0x23,0x33,0x33,11,VK_H,0x48,35,35,XK_h,0x0068,KeyH,AC06,h,0x52,0x4
+KEY_J,36,ANSI_J,0x26,0x24,0x3b,0x3b,13,VK_J,0x4a,36,36,XK_J,0x004a,KeyJ,AC07,j,0x53,0x26
+KEY_J,36,ANSI_J,0x26,0x24,0x3b,0x3b,13,VK_J,0x4a,36,36,XK_j,0x006a,KeyJ,AC07,j,0x53,0x26
+KEY_K,37,ANSI_K,0x28,0x25,0x42,0x42,14,VK_K,0x4b,37,37,XK_K,0x004b,KeyK,AC08,k,0x54,0x28
+KEY_K,37,ANSI_K,0x28,0x25,0x42,0x42,14,VK_K,0x4b,37,37,XK_k,0x006b,KeyK,AC08,k,0x54,0x28
+KEY_L,38,ANSI_L,0x25,0x26,0x4b,0x4b,15,VK_L,0x4c,38,38,XK_L,0x004c,KeyL,AC09,l,0x55,0x25
+KEY_L,38,ANSI_L,0x25,0x26,0x4b,0x4b,15,VK_L,0x4c,38,38,XK_l,0x006c,KeyL,AC09,l,0x55,0x25
+KEY_SEMICOLON,39,ANSI_Semicolon,0x29,0x27,0x4c,0x4c,51,VK_OEM_1,0xba,39,39,XK_semicolon,0x003b,Semicolon,AC10,semicolon,0x56,0x29
+KEY_SEMICOLON,39,ANSI_Semicolon,0x29,0x27,0x4c,0x4c,51,VK_OEM_1,0xba,39,39,XK_colon,0x003a,Semicolon,AC10,semicolon,0x56,0x29
+KEY_APOSTROPHE,40,ANSI_Quote,0x27,0x28,0x52,0x52,52,VK_OEM_7,0xde,40,40,XK_apostrophe,0x0027,Quote,AC11,apostrophe,0x57,0x27
+KEY_APOSTROPHE,40,ANSI_Quote,0x27,0x28,0x52,0x52,52,VK_OEM_7,0xde,40,40,XK_quotedbl,0x0022,Quote,AC11,apostrophe,0x57,0x27
+KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_grave,0x0060,Backquote,TLDE,grave_accent,0x2a,0x32
+KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_grave,0x0060,Backquote,AB00,grave_accent,0x2a,0x32
+KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_asciitilde,0x007e,Backquote,TLDE,grave_accent,0x2a,0x32
+KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_asciitilde,0x007e,Backquote,AB00,grave_accent,0x2a,0x32
+KEY_SHIFT,42,Shift,0x38,0x2a,0x12,0x12,225,VK_SHIFT,0x10,42,42,XK_Shift_L,0xffe1,ShiftLeft,LFSH,shift,0x63,0x38
+KEY_LEFTSHIFT,42,Shift,0x38,0x2a,0x12,0x12,225,VK_LSHIFT,0xa0,42,42,XK_Shift_L,0xffe1,ShiftLeft,LFSH,shift,0x63,0x38
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,BKSL,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,AC12,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,BKSL,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,AC12,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,BKSL,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,AC12,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,BKSL,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,AC12,backslash,0x58,0x2a
+KEY_Z,44,ANSI_Z,0x6,0x2c,0x1a,0x1a,29,VK_Z,0x5a,44,44,XK_Z,0x005a,KeyZ,AB01,z,0x64,0x6
+KEY_Z,44,ANSI_Z,0x6,0x2c,0x1a,0x1a,29,VK_Z,0x5a,44,44,XK_z,0x007a,KeyZ,AB01,z,0x64,0x6
+KEY_X,45,ANSI_X,0x7,0x2d,0x22,0x22,27,VK_X,0x58,45,45,XK_X,0x0058,KeyX,AB02,x,0x65,0x7
+KEY_X,45,ANSI_X,0x7,0x2d,0x22,0x22,27,VK_X,0x58,45,45,XK_x,0x0078,KeyX,AB02,x,0x65,0x7
+KEY_C,46,ANSI_C,0x8,0x2e,0x21,0x21,6,VK_C,0x43,46,46,XK_C,0x0043,KeyC,AB03,c,0x66,0x8
+KEY_C,46,ANSI_C,0x8,0x2e,0x21,0x21,6,VK_C,0x43,46,46,XK_c,0x0063,KeyC,AB03,c,0x66,0x8
+KEY_V,47,ANSI_V,0x9,0x2f,0x2a,0x2a,25,VK_V,0x56,47,47,XK_V,0x0056,KeyV,AB04,v,0x67,0x9
+KEY_V,47,ANSI_V,0x9,0x2f,0x2a,0x2a,25,VK_V,0x56,47,47,XK_v,0x0076,KeyV,AB04,v,0x67,0x9
+KEY_B,48,ANSI_B,0xb,0x30,0x32,0x32,5,VK_B,0x42,48,48,XK_B,0x0042,KeyB,AB05,b,0x68,0xb
+KEY_B,48,ANSI_B,0xb,0x30,0x32,0x32,5,VK_B,0x42,48,48,XK_b,0x0062,KeyB,AB05,b,0x68,0xb
+KEY_N,49,ANSI_N,0x2d,0x31,0x31,0x31,17,VK_N,0x4e,49,49,XK_N,0x004e,KeyN,AB06,n,0x69,0x2d
+KEY_N,49,ANSI_N,0x2d,0x31,0x31,0x31,17,VK_N,0x4e,49,49,XK_n,0x006e,KeyN,AB06,n,0x69,0x2d
+KEY_M,50,ANSI_M,0x2e,0x32,0x3a,0x3a,16,VK_M,0x4d,50,50,XK_M,0x004d,KeyM,AB07,m,0x6a,0x2e
+KEY_M,50,ANSI_M,0x2e,0x32,0x3a,0x3a,16,VK_M,0x4d,50,50,XK_m,0x006d,KeyM,AB07,m,0x6a,0x2e
+KEY_COMMA,51,ANSI_Comma,0x2b,0x33,0x41,0x41,54,VK_OEM_COMMA,0xbc,51,51,XK_comma,0x002c,Comma,AB08,comma,0x6b,0x2b
+KEY_COMMA,51,ANSI_Comma,0x2b,0x33,0x41,0x41,54,VK_OEM_COMMA,0xbc,51,51,XK_less,0x003c,Comma,AB08,comma,0x6b,0x2b
+KEY_DOT,52,ANSI_Period,0x2f,0x34,0x49,0x49,55,VK_OEM_PERIOD,0xbe,52,52,XK_period,0x002e,Period,AB09,dot,0x6c,0x2f
+KEY_DOT,52,ANSI_Period,0x2f,0x34,0x49,0x49,55,VK_OEM_PERIOD,0xbe,52,52,XK_greater,0x003e,Period,AB09,dot,0x6c,0x2f
+KEY_SLASH,53,ANSI_Slash,0x2c,0x35,0x4a,0x4a,56,VK_OEM_2,0xbf,53,53,XK_slash,0x002f,Slash,AB10,slash,0x6d,0x2c
+KEY_SLASH,53,ANSI_Slash,0x2c,0x35,0x4a,0x4a,56,VK_OEM_2,0xbf,53,53,XK_question,0x003f,Slash,AB10,slash,0x6d,0x2c
+KEY_RIGHTSHIFT,54,RightShift,0x3c,0x36,0x59,0x59,229,VK_RSHIFT,0xa1,54,54,XK_Shift_R,0xffe2,ShiftRight,RTSH,shift_r,0x6e,0x7b
+KEY_KPASTERISK,55,ANSI_KeypadMultiply,0x43,0x37,0x7c,0x7e,85,VK_MULTIPLY,0x6a,55,55,XK_multiply,0x00d7,NumpadMultiply,KPMU,asterisk,0x2f,0x43
+KEY_KPASTERISK,55,ANSI_KeypadMultiply,0x43,0x37,0x7c,0x7e,85,VK_MULTIPLY,0x6a,55,55,XK_multiply,0x00d7,NumpadMultiply,KPMU,kp_multiply,0x2f,0x43
+KEY_LEFTALT,56,Option,0x3a,0x38,0x11,0x19,226,VK_LMENU,0xa4,56,56,XK_Alt_L,0xffe9,AltLeft,LALT,alt,0x13,0x3a
+KEY_LEFTALT,56,Option,0x3a,0x38,0x11,0x19,226,VK_MENU,0x12,56,56,XK_Alt_L,0xffe9,AltLeft,LALT,alt,0x13,0x3a
+KEY_SPACE,57,Space,0x31,0x39,0x29,0x29,44,VK_SPACE,0x20,57,57,XK_space,0x0020,Space,SPCE,spc,0x79,0x31
+KEY_CAPSLOCK,58,CapsLock,0x39,0x3a,0x58,0x14,57,VK_CAPITAL,0x14,58,58,XK_Caps_Lock,0xffe5,CapsLock,CAPS,caps_lock,0x77,0x39
+KEY_F1,59,F1,0x7a,0x3b,0x05,0x07,58,VK_F1,0x70,59,59,XK_F1,0xffbe,F1,FK01,f1,0x05,0x7a
+KEY_F2,60,F2,0x78,0x3c,0x06,0x0f,59,VK_F2,0x71,60,60,XK_F2,0xffbf,F2,FK02,f2,0x06,0x78
+KEY_F3,61,F3,0x63,0x3d,0x04,0x17,60,VK_F3,0x72,61,61,XK_F3,0xffc0,F3,FK03,f3,0x08,0x63
+KEY_F4,62,F4,0x76,0x3e,0x0c,0x1f,61,VK_F4,0x73,62,62,XK_F4,0xffc1,F4,FK04,f4,0x0a,0x76
+KEY_F5,63,F5,0x60,0x3f,0x03,0x27,62,VK_F5,0x74,63,63,XK_F5,0xffc2,F5,FK05,f5,0x0c,0x60
+KEY_F6,64,F6,0x61,0x40,0x0b,0x2f,63,VK_F6,0x75,64,64,XK_F6,0xffc3,F6,FK06,f6,0x0e,0x61
+KEY_F7,65,F7,0x62,0x41,0x83,0x37,64,VK_F7,0x76,65,65,XK_F7,0xffc4,F7,FK07,f7,0x10,0x62
+KEY_F8,66,F8,0x64,0x42,0x0a,0x3f,65,VK_F8,0x77,66,66,XK_F8,0xffc5,F8,FK08,f8,0x11,0x64
+KEY_F9,67,F9,0x65,0x43,0x01,0x47,66,VK_F9,0x78,67,67,XK_F9,0xffc6,F9,FK09,f9,0x12,0x65
+KEY_F10,68,F10,0x6d,0x44,0x09,0x4f,67,VK_F10,0x79,68,68,XK_F10,0xffc7,F10,FK10,f10,0x07,0x6d
+KEY_NUMLOCK,69,ANSI_KeypadClear,0x47,0x45,0x77,0x76,83,VK_NUMLOCK,0x90,69,69,XK_Num_Lock,0xff7f,NumLock,NMLK,num_lock,0x62,0x47
+KEY_SCROLLLOCK,70,,,0x46,0x7e,0x5f,71,VK_SCROLL,0x91,70,70,XK_Scroll_Lock,0xff14,ScrollLock,SCLK,scroll_lock,0x17,0x6b
+KEY_KP7,71,ANSI_Keypad7,0x59,0x47,0x6c,0x6c,95,VK_NUMPAD7,0x67,71,71,XK_KP_7,0xffb7,Numpad7,KP7,kp_7,0x44,0x59
+KEY_KP8,72,ANSI_Keypad8,0x5b,0x48,0x75,0x75,96,VK_NUMPAD8,0x68,72,72,XK_KP_8,0xffb8,Numpad8,KP8,kp_8,0x45,0x5b
+KEY_KP9,73,ANSI_Keypad9,0x5c,0x49,0x7d,0x7d,97,VK_NUMPAD9,0x69,73,73,XK_KP_9,0xffb9,Numpad9,KP9,kp_9,0x46,0x5c
+KEY_KPMINUS,74,ANSI_KeypadMinus,0x4e,0x4a,0x7b,0x4e,86,VK_SUBTRACT,0x6d,74,74,XK_KP_Subtract,0xffad,NumpadSubtract,KPSU,kp_subtract,0x47,0x4e
+KEY_KP4,75,ANSI_Keypad4,0x56,0x4b,0x6b,0x6b,92,VK_NUMPAD4,0x64,75,75,XK_KP_4,0xffb4,Numpad4,KP4,kp_4,0x5b,0x56
+KEY_KP5,76,ANSI_Keypad5,0x57,0x4c,0x73,0x73,93,VK_NUMPAD5,0x65,76,76,XK_KP_5,0xffb5,Numpad5,KP5,kp_5,0x5c,0x57
+KEY_KP6,77,ANSI_Keypad6,0x58,0x4d,0x74,0x74,94,VK_NUMPAD6,0x66,77,77,XK_KP_6,0xffb6,Numpad6,KP6,kp_6,0x5d,0x58
+KEY_KPPLUS,78,ANSI_KeypadPlus,0x45,0x4e,0x79,0x7c,87,VK_ADD,0x6b,78,78,XK_KP_Add,0xffab,NumpadAdd,KPAD,kp_add,0x7d,0x45
+KEY_KP1,79,ANSI_Keypad1,0x53,0x4f,0x69,0x69,89,VK_NUMPAD1,0x61,79,79,XK_KP_1,0xffb1,Numpad1,KP1,kp_1,0x70,0x53
+KEY_KP2,80,ANSI_Keypad2,0x54,0x50,0x72,0x72,90,VK_NUMPAD2,0x62,80,80,XK_KP_2,0xffb2,Numpad2,KP2,kp_2,0x71,0x54
+KEY_KP3,81,ANSI_Keypad3,0x55,0x51,0x7a,0x7a,91,VK_NUMPAD3,0x63,81,81,XK_KP_3,0xffb3,Numpad3,KP3,kp_3,0x72,0x55
+KEY_KP0,82,ANSI_Keypad0,0x52,0x52,0x70,0x70,98,VK_NUMPAD0,0x60,82,82,XK_KP_0,0xffb0,Numpad0,KP0,kp_0,0x5e,0x52
+KEY_KPDOT,83,ANSI_KeypadDecimal,0x41,0x53,0x71,0x71,99,VK_DECIMAL,0x6e,83,83,XK_KP_Decimal,0xffae,NumpadDecimal,KPDL,kp_decimal,0x32,0x41
+KEY_KPDOT,83,ANSI_KeypadDecimal,0x41,0x53,0x71,0x71,99,VK_DECIMAL,0x6e,83,83,XK_KP_Decimal,0xffae,NumpadDecimal,KPDC,kp_decimal,0x32,0x41
+,84,,,0x54,,,,,,,,,,,,,,
+KEY_ZENKAKUHANKAKU,85,,,0x76,0x5f,,148,,,,,,,Lang5,HZTG,,,
+KEY_102ND,86,,,0x56,0x61,0x13,100,VK_OEM_102,0xe1,86,86,,,IntlBackslash,LSGT,less,0x7c,
+KEY_F11,87,F11,0x67,0x57,0x78,0x56,68,VK_F11,0x7a,87,87,XK_F11,0xffc8,F11,FK11,f11,0x09,0x67
+KEY_F12,88,F12,0x6f,0x58,0x07,0x5e,69,VK_F12,0x7b,88,88,XK_F12,0xffc9,F12,FK12,f12,0x0b,0x6f
+KEY_RO,89,,,0x73,0x51,,135,,,,,,,IntlRo,AB11,ro,,
+KEY_KATAKANA,90,JIS_Kana,0x68,0x78,0x63,,146,VK_KANA,0x15,,,,,Katakana,KATA,,,
+KEY_KATAKANA,90,JIS_Kana,0x68,0x78,0x63,,146,VK_KANA,0x15,,,,,Lang3,KATA,,,
+KEY_HIRAGANA,91,,,0x77,0x62,0x87,147,,,,,,,Hiragana,HIRA,hiragana,,
+KEY_HIRAGANA,91,,,0x77,0x62,0x87,147,,,,,,,Lang4,HIRA,hiragana,,
+KEY_HENKAN,92,,,0x79,0x64,0x86,138,,,,,,,Convert,HENK,henkan,,
+KEY_KATAKANAHIRAGANA,93,,,0x70,0x13,0x87,136,,,0xc8,0xc8,,,KanaMode,HKTG,,,
+KEY_MUHENKAN,94,,,0x7b,0x67,0x85,139,,,,,,,NonConvert,NFER,,,
+KEY_MUHENKAN,94,,,0x7b,0x67,0x85,139,,,,,,,NonConvert,MUHE,,,
+KEY_KPJPCOMMA,95,JIS_KeypadComma,0x5f,0x5c,0x27,,140,,,,,XK_KP_Separator,0xffac,,KPSP,,,
+KEY_KPJPCOMMA,95,JIS_KeypadComma,0x5f,0x5c,0x27,,140,,,,,XK_KP_Separator,0xffac,,JPCM,,,
+KEY_KPENTER,96,ANSI_KeypadEnter,0x4c,0xe01c,0xe05a,0x79,88,,,0x64,0x64,XK_KP_Enter,0xff8d,NumpadEnter,KPEN,kp_enter,0x5a,0x4c
+KEY_RIGHTCTRL,97,RightControl,0x3e,0xe01d,0xe014,0x58,228,VK_RCONTROL,0xa3,0x65,0x65,XK_Control_R,0xffe4,ControlRight,RCTL,ctrl_r,0x4c,0x7d
+KEY_KPSLASH,98,ANSI_KeypadDivide,0x4b,0xe035,0xe04a,0x4a,84,VK_DIVIDE,0x6f,0x68,0x68,XK_KP_Divide,0xffaf,NumpadDivide,KPDV,kp_divide,0x2e,0x4b
+KEY_SYSRQ,99,,,0x54,0x7f,0x57,70,VK_SNAPSHOT,0x2c,0x67,0x67,XK_Sys_Req,0xff15,PrintScreen,PRSC,print,0x16,0x69
+KEY_SYSRQ,99,,,0x54,0x7f,0x57,70,VK_SNAPSHOT,0x2c,0x67,0x67,XK_Sys_Req,0xff15,PrintScreen,SYRQ,sysrq,0x16,0x69
+KEY_RIGHTALT,100,RightOption,0x3d,0xe038,0xe011,0x39,230,VK_RMENU,0xa5,0x69,0x69,XK_Alt_R,0xffea,AltRight,ALGR,alt_r,0x0d,0x7c
+KEY_RIGHTALT,100,RightOption,0x3d,0xe038,0xe011,0x39,230,VK_RMENU,0xa5,0x69,0x69,XK_Alt_R,0xffea,AltRight,RALT,alt_r,0x0d,0x7c
+KEY_LINEFEED,101,,,0x5b,,,,,,,,,,,LNFD,lf,0x6f,
+KEY_HOME,102,Home,0x73,0xe047,0xe06c,0x6e,74,VK_HOME,0x24,0x59,0x59,XK_Home,0xff50,Home,HOME,home,0x34,0x73
+KEY_UP,103,UpArrow,0x7e,0xe048,0xe075,0x63,82,VK_UP,0x26,0x5a,0x5a,XK_Up,0xff52,ArrowUp,UP,up,0x14,0x3e
+KEY_PAGEUP,104,PageUp,0x74,0xe049,0xe07d,0x6f,75,VK_PRIOR,0x21,0x5b,0x5b,XK_Page_Up,0xff55,PageUp,PGUP,pgup,0x60,0x74
+KEY_LEFT,105,LeftArrow,0x7b,0xe04b,0xe06b,0x61,80,VK_LEFT,0x25,0x5c,0x5c,XK_Left,0xff51,ArrowLeft,LEFT,left,0x18,0x3b
+KEY_RIGHT,106,RightArrow,0x7c,0xe04d,0xe074,0x6a,79,VK_RIGHT,0x27,0x5e,0x5e,XK_Right,0xff53,ArrowRight,RGHT,right,0x1c,0x3c
+KEY_END,107,End,0x77,0xe04f,0xe069,0x65,77,VK_END,0x23,0x5f,0x5f,XK_End,0xff57,End,END,end,0x4a,0x77
+KEY_DOWN,108,DownArrow,0x7d,0xe050,0xe072,0x60,81,VK_DOWN,0x28,0x60,0x60,XK_Down,0xff54,ArrowDown,DOWN,down,0x1b,0x3d
+KEY_PAGEDOWN,109,PageDown,0x79,0xe051,0xe07a,0x6d,78,VK_NEXT,0x22,0x61,0x61,XK_Page_Down,0xff56,PageDown,PGDN,pgdn,0x7b,0x79
+KEY_INSERT,110,,,0xe052,0xe070,0x67,73,VK_INSERT,0x2d,0x62,0x62,XK_Insert,0xff63,Insert,INS,insert,0x2c,0x72
+KEY_DELETE,111,ForwardDelete,0x75,0xe053,0xe071,0x64,76,VK_DELETE,0x2e,0x63,0x63,XK_Delete,0xffff,Delete,DEL,delete,0x42,0x75
+KEY_DELETE,111,ForwardDelete,0x75,0xe053,0xe071,0x64,76,VK_DELETE,0x2e,0x63,0x63,XK_Delete,0xffff,Delete,DELE,,0x42,0x75
+KEY_MACRO,112,,,0xe06f,0xe06f,0x8e,,,,,,,,,I120,,,
+KEY_MUTE,113,Mute,0x4a,0xe020,0xe023,0x9c,127,VK_VOLUME_MUTE,0xad,,,,,AudioVolumeMute,MUTE,audiomute,,
+KEY_MUTE,113,Mute,0x4a,0xe020,0xe023,0x9c,239,VK_VOLUME_MUTE,0xad,,,,,AudioVolumeMute,MUTE,audiomute,,
+KEY_VOLUMEDOWN,114,VolumeDown,0x49,0xe02e,0xe021,0x9d,129,VK_VOLUME_DOWN,0xae,,,,,AudioVolumeDown,VOL-,volumedown,,
+KEY_VOLUMEDOWN,114,VolumeDown,0x49,0xe02e,0xe021,0x9d,238,VK_VOLUME_DOWN,0xae,,,,,AudioVolumeDown,VOL-,volumedown,,
+KEY_VOLUMEUP,115,VolumeUp,0x48,0xe030,0xe032,0x95,128,VK_VOLUME_UP,0xaf,,,,,AudioVolumeUp,VOL+,volumeup,,
+KEY_VOLUMEUP,115,VolumeUp,0x48,0xe030,0xe032,0x95,237,VK_VOLUME_UP,0xaf,,,,,AudioVolumeUp,VOL+,volumeup,,
+KEY_POWER,116,,,0xe05e,0xe037,,102,,,,,,,Power,POWR,power,,0x7f7f
+KEY_KPEQUAL,117,ANSI_KeypadEquals,0x51,0x59,0x0f,,103,,,0x76,0x76,XK_KP_Equal,0xffbd,NumpadEqual,KPEQ,kp_equals,0x2d,0x51
+KEY_KPPLUSMINUS,118,,,0xe04e,0xe079,,,,,,,,,,I126,,,
+KEY_PAUSE,119,,,0xe046,0xe077,0x62,72,VK_PAUSE,0x013,0x66,0x66,XK_Pause,0xff13,Pause,PAUS,pause,0x15,0x71
+KEY_SCALE,120,,,0xe00b,,,,,,,,,,,I128,,,
+KEY_KPCOMMA,121,,,0x7e,0x6d,,133,VK_SEPARATOR??,0x6c,,,,,NumpadComma,KPCO,kp_comma,,
+KEY_KPCOMMA,121,,,0x7e,0x6d,,133,VK_SEPARATOR??,0x6c,,,,,NumpadComma,I129,,,
+KEY_HANGEUL,122,,,,,,144,VK_HANGEUL,0x15,,,,,,HNGL,,,
+KEY_HANJA,123,,,0xe00d,,,145,VK_HANJA,0x19,,,,,,HJCV,,,
+KEY_YEN,124,JIS_Yen,0x5d,0x7d,0x6a,0x5d,137,,,0x7d,0x7d,,,IntlYen,AE13,yen,,
+KEY_LEFTMETA,125,Command,0x37,0xe05b,0xe01f,0x8b,227,VK_LWIN,0x5b,0x6b,0x6b,XK_Meta_L,0xffe7,MetaLeft,LMTA,meta_l,0x78,0x37
+KEY_LEFTMETA,125,Command,0x37,0xe05b,0xe01f,0x8b,227,VK_LWIN,0x5b,0x6b,0x6b,XK_Meta_L,0xffe7,MetaLeft,LWIN,meta_l,0x78,0x37
+KEY_RIGHTMETA,126,RightCommand,0x36,0xe05c,0xe027,0x8c,231,VK_RWIN,0x5c,0x6c,0x6c,XK_Meta_R,0xffe8,MetaRight,RMTA,meta_r,0x7a,0x37
+KEY_RIGHTMETA,126,RightCommand,0x36,0xe05c,0xe027,0x8c,231,VK_RWIN,0x5c,0x6c,0x6c,XK_Meta_R,0xffe8,MetaRight,RWIN,meta_r,0x7a,0x37
+KEY_COMPOSE,127,,0x6e,0xe05d,0xe02f,0x8d,101,VK_APPS,0x5d,0x6d,0x6d,,,ContextMenu,MENU,compose,0x43,
+KEY_COMPOSE,127,,0x6e,0xe05d,0xe02f,0x8d,101,VK_APPS,0x5d,0x6d,0x6d,,,ContextMenu,COMP,compose,0x43,
+KEY_STOP,128,,,0xe068,0xe028,0x0a,120,VK_BROWSER_STOP,0xa9,,,,,BrowserStop,STOP,stop,0x01,
+KEY_STOP,128,,,0xe068,0xe028,0x0a,243,VK_BROWSER_STOP,0xa9,,,,,BrowserStop,STOP,stop,0x01,
+KEY_AGAIN,129,,,0xe005,,0x0b,121,,,,,,,Again,AGAI,again,0x03,
+KEY_PROPS,130,,,0xe006,,0x0c,,,,,,,,Props,PROP,props,0x19,
+KEY_UNDO,131,,,0xe007,,0x10,122,,,,,,,Undo,UNDO,undo,0x1a,
+KEY_FRONT,132,,,0xe00c,,,119,,,,,,,,FRNT,front,0x31,
+KEY_COPY,133,,,0xe078,,0x18,124,,,,,,,Copy,COPY,copy,0x33,
+KEY_OPEN,134,,,0x64,,0x20,116,,,,,,,Open,OPEN,open,0x48,
+KEY_PASTE,135,,,0x65,,0x28,125,,,,,,,Paste,PAST,paste,0x49,
+KEY_FIND,136,,,0xe041,,0x30,126,,,,,,,Find,FIND,find,0x5f,
+KEY_FIND,136,,,0xe041,,0x30,244,,,,,,,Find,FIND,find,0x5f,
+KEY_CUT,137,,,0xe03c,,0x38,123,,,,,,,Cut,CUT,cut,0x61,
+KEY_HELP,138,Help,0x72,0xe075,,0x09,117,VK_HELP,0x2f,,,XK_Help,0xff6a,Help,HELP,help,0x76,
+KEY_MENU,139,,,0xe01e,,0x91,118,,,,,,,,I147,menu,,
+KEY_CALC,140,,,0xe021,0xe02b,0xa3,251,,,,,,,LaunchApp2,I148,calculator,,
+KEY_SETUP,141,,,0x66,,,,,,,,,,,I149,,,
+KEY_SLEEP,142,,,0xe05f,0xe03f,,248,VK_SLEEP,0x5f,,,,,Sleep,I150,sleep,,
+KEY_WAKEUP,143,,,0xe063,0xe05e,,,,,,,,,WakeUp,I151,wake,,
+KEY_FILE,144,,,0x67,,,,,,,,,,,I152,,,
+KEY_SENDFILE,145,,,0x68,,,,,,,,,,,I153,,,
+KEY_DELETEFILE,146,,,0x69,,,,,,,,,,,I154,,,
+KEY_XFER,147,,,0xe013,,0xa2,,,,,,,,,XFER,,,
+KEY_XFER,147,,,0xe013,,0xa2,,,,,,,,,I155,,,
+KEY_PROG1,148,,,0xe01f,,0xa0,,,,,,,,,I156,,,
+KEY_PROG2,149,,,0xe017,,0xa1,,,,,,,,,I157,,,
+KEY_WWW,150,,,0xe002,,,240,,,,,,,,I158,,,
+KEY_MSDOS,151,,,0x6a,,,,,,,,,,,I159,,,
+KEY_SCREENLOCK,152,,,0xe012,,0x96,249,,,,,,,,I160,,,
+KEY_DIRECTION,153,,,0x6b,,,,,,,,,,,I161,,,
+KEY_CYCLEWINDOWS,154,,,0xe026,,0x9b,,,,,,,,,I162,,,
+KEY_MAIL,155,,,0xe06c,0xe048,,,,,,,,,LaunchMail,I163,mail,,
+KEY_BOOKMARKS,156,,,0xe066,0xe018,,,,,,,,,BrowserFavorites,I164,ac_bookmarks,,
+KEY_COMPUTER,157,,,0xe06b,0xe040,,,,,,,,,LaunchApp1,I165,computer,,
+KEY_BACK,158,,,0xe06a,0xe038,,241,VK_BROWSER_BACK,0xa6,,,,,BrowserBack,I166,ac_back,,
+KEY_FORWARD,159,,,0xe069,0xe030,,242,VK_BROWSER_FORWARD,0xa7,,,,,BrowserForward,I167,ac_forward,,
+KEY_CLOSECD,160,,,0xe023,,0x9a,,,,,,,,,I168,,,
+KEY_EJECTCD,161,,,0x6c,,,236,,,,,,,,I169,,,
+KEY_EJECTCLOSECD,162,,,0xe07d,,,,,,,,,,Eject,I170,,,
+KEY_NEXTSONG,163,,,0xe019,0xe04d,0x93,235,VK_MEDIA_NEXT_TRACK,0xb0,,,,,MediaTrackNext,I171,audionext,,
+KEY_PLAYPAUSE,164,,,0xe022,0xe034,,232,VK_MEDIA_PLAY_PAUSE,0xb3,,,,,MediaPlayPause,I172,audioplay,,
+KEY_PREVIOUSSONG,165,,,0xe010,0xe015,0x94,234,VK_MEDIA_PREV_TRACK,0xb1,,,,,MediaTrackPrevious,I173,audioprev,,
+KEY_STOPCD,166,,,0xe024,0xe03b,0x98,233,VK_MEDIA_STOP,0xb2,,,,,MediaStop,I174,audiostop,,
+KEY_RECORD,167,,,0xe031,,0x9e,,,,,,,,,I175,,,
+KEY_REWIND,168,,,0xe018,,0x9f,,,,,,,,,I176,,,
+KEY_PHONE,169,,,0x63,,,,,,,,,,,I177,,,
+KEY_ISO,170,ISO_Section,0xa,0x70,,,,,,,,,,,I178,,,
+KEY_CONFIG,171,,,0xe001,,,,,,,,,,,I179,,,
+KEY_HOMEPAGE,172,,,0xe032,0xe03a,0x97,,VK_BROWSER_HOME,0xac,,,,,BrowserHome,I180,ac_home,,
+KEY_REFRESH,173,,,0xe067,0xe020,,250,VK_BROWSER_REFRESH,0xa8,,,,,BrowserRefresh,I181,ac_refresh,,
+KEY_EXIT,174,,,0x71,,,,,,,,,,,I182,,,
+KEY_MOVE,175,,,0x72,,,,,,,,,,,I183,,,
+KEY_EDIT,176,,,0xe008,,,247,,,,,,,,I184,,,
+KEY_SCROLLUP,177,,,0x75,,,245,,,,,,,,I185,,,
+KEY_SCROLLDOWN,178,,,0xe00f,,,246,,,,,,,,I186,,,
+KEY_KPLEFTPAREN,179,,,0xe076,,,182,,,,,,,NumpadParenLeft,I187,,,
+KEY_KPRIGHTPAREN,180,,,0xe07b,,,183,,,,,,,NumpadParenRight,I188,,,
+KEY_NEW,181,,,0xe009,,,,,,,,,,,I189,,,
+KEY_REDO,182,,,0xe00a,,,,,,,,,,,I190,,,
+KEY_F13,183,F13,0x69,0x5d,0x2f,0x7f,104,VK_F13,0x7c,0x6e,0x6e,,,F13,FK13,,,0x69
+KEY_F14,184,F14,0x6b,0x5e,0x37,0x80,105,VK_F14,0x7d,0x6f,0x6f,,,F14,FK14,,,0x6b
+KEY_F15,185,F15,0x71,0x5f,0x3f,0x81,106,VK_F15,0x7e,0x70,0x70,,,F15,FK15,,,0x71
+KEY_F16,186,F16,0x6a,0x55,,0x82,107,VK_F16,0x7f,0x71,0x71,,,F16,FK16,,,
+KEY_F17,187,F17,0x40,0xe003,,0x83,108,VK_F17,0x80,0x72,0x72,,,F17,FK17,,,
+KEY_F18,188,F18,0x4f,0xe077,,,109,VK_F18,0x81,,,,,F18,FK18,,,
+KEY_F19,189,F19,0x50,0xe004,,,110,VK_F19,0x82,,,,,F19,FK19,,,
+KEY_F20,190,F20,0x5a,0x5a,,,111,VK_F20,0x83,,,,,F20,FK20,,,
+KEY_F21,191,,,0x74,,,112,VK_F21,0x84,,,,,F21,FK21,,,
+KEY_F22,192,,,0xe079,,,113,VK_F22,0x85,,,,,F22,FK22,,,
+KEY_F23,193,,,0x6d,,,114,VK_F23,0x86,,,,,F23,FK23,,,
+KEY_F24,194,,,0x6f,,,115,VK_F24,0x87,,,,,F24,FK24,,,
+,195,,,0xe015,,,,,,,,,,,,,,
+,196,,,0xe016,,,,,,,,,,,,,,
+,197,,,0xe01a,,,,,,,,,,,,,,
+,198,,,0xe01b,,,,,,,,,,,,,,
+,199,,,0xe027,,,,,,,,,,,,,,
+KEY_PLAYCD,200,,,0xe028,,,,,,,,,,,I208,,,
+KEY_PAUSECD,201,,,0xe029,,,,,,,,,,,I209,,,
+KEY_PROG3,202,,,0xe02b,,,,,,,,,,,I210,,,
+KEY_PROG4,203,,,0xe02c,,,,,,,,,,,I211,,,
+KEY_DASHBOARD,204,,,0xe02d,,,,,,,,,,,I212,,,
+KEY_SUSPEND,205,,,0xe025,,,,,,,,,,Suspend,I213,,,
+KEY_CLOSE,206,,,0xe02f,,,,,,,,,,,I214,,,
+KEY_PLAY,207,,,0xe033,,,,VK_PLAY,0xfa,,,,,,I215,,,
+KEY_FASTFORWARD,208,,,0xe034,,,,,,,,,,,I216,,,
+KEY_BASSBOOST,209,,,0xe036,,,,,,,,,,,I217,,,
+KEY_PRINT,210,,,0xe039,,,,VK_PRINT,0x2a,,,,,,I218,,,
+KEY_HP,211,,,0xe03a,,,,,,,,,,,I219,,,
+KEY_CAMERA,212,,,0xe03b,,,,,,,,,,,I220,,,
+KEY_SOUND,213,,,0xe03d,,,,,,,,,,,I221,,,
+KEY_QUESTION,214,,,0xe03e,,,,,,,,,,,I222,,,
+KEY_EMAIL,215,,,0xe03f,,,,VK_LAUNCH_MAIL,0xb4,,,,,,I223,,,
+KEY_CHAT,216,,,0xe040,,,,,,,,,,,I224,,,
+KEY_SEARCH,217,,,0xe065,0xe010,,,VK_BROWSER_SEARCH,0xaa,,,,,BrowserSearch,I225,,,
+KEY_CONNECT,218,,,0xe042,,,,,,,,,,,I226,,,
+KEY_FINANCE,219,,,0xe043,,,,,,,,,,,I227,,,
+KEY_SPORT,220,,,0xe044,,,,,,,,,,,I228,,,
+KEY_SHOP,221,,,0xe045,,,,,,,,,,,I229,,,
+KEY_ALTERASE,222,,,0xe014,,,,,,,,,,,I230,,,
+KEY_CANCEL,223,,,0xe04a,,,,,,,,,,,I231,,,
+KEY_BRIGHTNESSDOWN,224,,,0xe04c,,,,,,,,,,,I232,,,
+KEY_BRIGHTNESSUP,225,,,0xe054,,,,,,,,,,,I233,,,
+KEY_MEDIA,226,,,0xe06d,0xe050,,,,,,,,,MediaSelect,I234,mediaselect,,
+KEY_SWITCHVIDEOMODE,227,,,0xe056,,,,,,,,,,,I235,,,
+KEY_KBDILLUMTOGGLE,228,,,0xe057,,,,,,,,,,,I236,,,
+KEY_KBDILLUMDOWN,229,,,0xe058,,,,,,,,,,,I237,,,
+KEY_KBDILLUMUP,230,,,0xe059,,,,,,,,,,,I238,,,
+KEY_SEND,231,,,0xe05a,,,,,,,,,,,I239,,,
+KEY_REPLY,232,,,0xe064,,,,,,,,,,,I240,,,
+KEY_FORWARDMAIL,233,,,0xe00e,,,,,,,,,,,I241,,,
+KEY_SAVE,234,,,0xe055,,,,,,,,,,,I242,,,
+KEY_DOCUMENTS,235,,,0xe070,,,,,,,,,,,I243,,,
+KEY_BATTERY,236,,,0xe071,,,,,,,,,,,I244,,,
+KEY_BLUETOOTH,237,,,0xe072,,,,,,,,,,,I245,,,
+KEY_WLAN,238,,,0xe073,,,,,,,,,,,I246,,,
+KEY_UWB,239,,,0xe074,,,,,,,,,,,I247,,,
+KEY_UNKNOWN,240,,,,,,,,,,,,,,I248,,,
+KEY_VIDEO_NEXT,241,,,,,,,,,,,,,,I249,,,
+KEY_VIDEO_PREV,242,,,,,,,,,,,,,,I250,,,
+KEY_BRIGHTNESS_CYCLE,243,,,,,,,,,,,,,,I251,,,
+KEY_BRIGHTNESS_ZERO,244,,,,,,,,,,,,,,I252,,,
+KEY_DISPLAY_OFF,245,,,,,,,,,,,,,,I253,,,
+KEY_WIMAX,246,,,,,,,,,,,,,,,,,
+,247,,,,,,,,,,,,,,,,,
+,248,,,,,,,,,,,,,,,,,
+,249,,,,,,,,,,,,,,,,,
+,250,,,,,,,,,,,,,,,,,
+,251,,,,,,,,,,,,,,,,,
+,252,,,,,,,,,,,,,,,,,
+,253,,,,,,,,,,,,,,,,,
+,254,,,,,,,,,,,,,,,,,
+,255,,,,0xe012,,,,,,,,,,,,,
+BTN_MISC,0x100,,,,,,,,,,,,,,,,,
+BTN_0,0x100,,,,,,,VK_LBUTTON,0x01,,,,,,,,,
+BTN_1,0x101,,,,,,,VK_RBUTTON,0x02,,,,,,,,,
+BTN_2,0x102,,,,,,,VK_MBUTTON,0x04,,,,,,,,,
+BTN_3,0x103,,,,,,,VK_XBUTTON1,0x05,,,,,,,,,
+BTN_4,0x104,,,,,,,VK_XBUTTON2,0x06,,,,,,,,,
+BTN_5,0x105,,,,,,,,,,,,,,,,,
+BTN_6,0x106,,,,,,,,,,,,,,,,,
+BTN_7,0x107,,,,,,,,,,,,,,,,,
+BTN_8,0x108,,,,,,,,,,,,,,,,,
+BTN_9,0x109,,,,,,,,,,,,,,,,,
+BTN_MOUSE,0x110,,,,,,,,,,,,,,,,,
+BTN_LEFT,0x110,,,,,,,,,,,,,,,,,
+BTN_RIGHT,0x111,,,,,,,,,,,,,,,,,
+BTN_MIDDLE,0x112,,,,,,,,,,,,,,,,,
+BTN_SIDE,0x113,,,,,,,,,,,,,,,,,
+BTN_EXTRA,0x114,,,,,,,,,,,,,,,,,
+BTN_FORWARD,0x115,,,,,,,,,,,,,,,,,
+BTN_BACK,0x116,,,,,,,,,,,,,,,,,
+BTN_TASK,0x117,,,,,,,,,,,,,,,,,
+BTN_JOYSTICK,0x120,,,,,,,,,,,,,,,,,
+BTN_TRIGGER,0x120,,,,,,,,,,,,,,,,,
+BTN_THUMB,0x121,,,,,,,,,,,,,,,,,
+BTN_THUMB2,0x122,,,,,,,,,,,,,,,,,
+BTN_TOP,0x123,,,,,,,,,,,,,,,,,
+BTN_TOP2,0x124,,,,,,,,,,,,,,,,,
+BTN_PINKIE,0x125,,,,,,,,,,,,,,,,,
+BTN_BASE,0x126,,,,,,,,,,,,,,,,,
+BTN_BASE2,0x127,,,,,,,,,,,,,,,,,
+BTN_BASE3,0x128,,,,,,,,,,,,,,,,,
+BTN_BASE4,0x129,,,,,,,,,,,,,,,,,
+BTN_BASE5,0x12a,,,,,,,,,,,,,,,,,
+BTN_BASE6,0x12b,,,,,,,,,,,,,,,,,
+BTN_DEAD,0x12f,,,,,,,,,,,,,,,,,
+BTN_GAMEPAD,0x130,,,,,,,,,,,,,,,,,
+BTN_A,0x130,,,,,,,,,,,,,,,,,
+BTN_B,0x131,,,,,,,,,,,,,,,,,
+BTN_C,0x132,,,,,,,,,,,,,,,,,
+BTN_X,0x133,,,,,,,,,,,,,,,,,
+BTN_Y,0x134,,,,,,,,,,,,,,,,,
+BTN_Z,0x135,,,,,,,,,,,,,,,,,
+BTN_TL,0x136,,,,,,,,,,,,,,,,,
+BTN_TR,0x137,,,,,,,,,,,,,,,,,
+BTN_TL2,0x138,,,,,,,,,,,,,,,,,
+BTN_TR2,0x139,,,,,,,,,,,,,,,,,
+BTN_SELECT,0x13a,,,,,,,,,,,,,,,,,
+BTN_START,0x13b,,,,,,,,,,,,,,,,,
+BTN_MODE,0x13c,,,,,,,,,,,,,,,,,
+BTN_THUMBL,0x13d,,,,,,,,,,,,,,,,,
+BTN_THUMBR,0x13e,,,,,,,,,,,,,,,,,
+BTN_DIGI,0x140,,,,,,,,,,,,,,,,,
+BTN_TOOL_PEN,0x140,,,,,,,,,,,,,,,,,
+BTN_TOOL_RUBBER,0x141,,,,,,,,,,,,,,,,,
+BTN_TOOL_BRUSH,0x142,,,,,,,,,,,,,,,,,
+BTN_TOOL_PENCIL,0x143,,,,,,,,,,,,,,,,,
+BTN_TOOL_AIRBRUSH,0x144,,,,,,,,,,,,,,,,,
+BTN_TOOL_FINGER,0x145,,,,,,,,,,,,,,,,,
+BTN_TOOL_MOUSE,0x146,,,,,,,,,,,,,,,,,
+BTN_TOOL_LENS,0x147,,,,,,,,,,,,,,,,,
+BTN_TOUCH,0x14a,,,,,,,,,,,,,,,,,
+BTN_STYLUS,0x14b,,,,,,,,,,,,,,,,,
+BTN_STYLUS2,0x14c,,,,,,,,,,,,,,,,,
+BTN_TOOL_DOUBLETAP,0x14d,,,,,,,,,,,,,,,,,
+BTN_TOOL_TRIPLETAP,0x14e,,,,,,,,,,,,,,,,,
+BTN_TOOL_QUADTAP,0x14f,,,,,,,,,,,,,,,,,
+BTN_WHEEL,0x150,,,,,,,,,,,,,,,,,
+BTN_GEAR_DOWN,0x150,,,,,,,,,,,,,,,,,
+BTN_GEAR_UP,0x151,,,,,,,,,,,,,,,,,
+KEY_OK,0x160,,,,,,,,,,,,,,,,,
+KEY_SELECT,0x161,,,,,,,VK_SELECT,0x29,,,XK_Select,0xff60,Select,SELE,,,
+KEY_GOTO,0x162,,,,,,,,,,,,,,,,,
+KEY_CLEAR,0x163,,,,,,,,,,,,,NumpadClear,CLR,,,
+KEY_POWER2,0x164,,,,,,,,,,,,,,,,,
+KEY_OPTION,0x165,,,,,,,,,,,,,,,,,
+KEY_INFO,0x166,,,,,,,,,,,,,,,,,
+KEY_TIME,0x167,,,,,,,,,,,,,,,,,
+KEY_VENDOR,0x168,,,,,,,,,,,,,,,,,
+KEY_ARCHIVE,0x169,,,,,,,,,,,,,,,,,
+KEY_PROGRAM,0x16a,,,,,,,,,,,,,,,,,
+KEY_CHANNEL,0x16b,,,,,,,,,,,,,,,,,
+KEY_FAVORITES,0x16c,,,,,,,VK_BROWSER_FAVOURITES,0xab,,,,,,,,,
+KEY_EPG,0x16d,,,,,,,,,,,,,,,,,
+KEY_PVR,0x16e,,,,,,,,,,,,,,,,,
+KEY_MHP,0x16f,,,,,,,,,,,,,,,,,
+KEY_LANGUAGE,0x170,,,,,,,,,,,,,,,,,
+KEY_TITLE,0x171,,,,,,,,,,,,,,,,,
+KEY_SUBTITLE,0x172,,,,,,,,,,,,,,,,,
+KEY_ANGLE,0x173,,,,,,,,,,,,,,,,,
+KEY_ZOOM,0x174,,,,,,,VK_ZOOM,0xfb,,,,,,,,,
+KEY_MODE,0x175,,,,,,,,,,,,,,,,,
+KEY_KEYBOARD,0x176,,,,,,,,,,,,,,,,,
+KEY_SCREEN,0x177,,,,,,,,,,,,,,,,,
+KEY_PC,0x178,,,,,,,,,,,,,,,,,
+KEY_TV,0x179,,,,,,,,,,,,,,,,,
+KEY_TV2,0x17a,,,,,,,,,,,,,,,,,
+KEY_VCR,0x17b,,,,,,,,,,,,,,,,,
+KEY_VCR2,0x17c,,,,,,,,,,,,,,,,,
+KEY_SAT,0x17d,,,,,,,,,,,,,,,,,
+KEY_SAT2,0x17e,,,,,,,,,,,,,,,,,
+KEY_CD,0x17f,,,,,,,,,,,,,,,,,
+KEY_TAPE,0x180,,,,,,,,,,,,,,,,,
+KEY_RADIO,0x181,,,,,,,,,,,,,,,,,
+KEY_TUNER,0x182,,,,,,,,,,,,,,,,,
+KEY_PLAYER,0x183,,,,,,,,,,,,,,,,,
+KEY_TEXT,0x184,,,,,,,,,,,,,,,,,
+KEY_DVD,0x185,,,,,,,,,,,,,,,,,
+KEY_AUX,0x186,,,,,,,,,,,,,,,,,
+KEY_MP3,0x187,,,,,,,,,,,,,,,,,
+KEY_AUDIO,0x188,,,,,,,,,,,,,,,,,
+KEY_VIDEO,0x189,,,,,,,,,,,,,,,,,
+KEY_DIRECTORY,0x18a,,,,,,,,,,,,,,,,,
+KEY_LIST,0x18b,,,,,,,,,,,,,,,,,
+KEY_MEMO,0x18c,,,,,,,,,,,,,,,,,
+KEY_CALENDAR,0x18d,,,,,,,,,,,,,,,,,
+KEY_RED,0x18e,,,,,,,,,,,,,,,,,
+KEY_GREEN,0x18f,,,,,,,,,,,,,,,,,
+KEY_YELLOW,0x190,,,,,,,,,,,,,,,,,
+KEY_BLUE,0x191,,,,,,,,,,,,,,,,,
+KEY_CHANNELUP,0x192,,,,,,,,,,,,,,,,,
+KEY_CHANNELDOWN,0x193,,,,,,,,,,,,,,,,,
+KEY_FIRST,0x194,,,,,,,,,,,,,,,,,
+KEY_LAST,0x195,,,,,,,,,,,,,,,,,
+KEY_AB,0x196,,,,,,,,,,,,,,,,,
+KEY_NEXT,0x197,,,,,,,,,,,,,,,,,
+KEY_RESTART,0x198,,,,,,,,,,,,,,,,,
+KEY_SLOW,0x199,,,,,,,,,,,,,,,,,
+KEY_SHUFFLE,0x19a,,,,,,,,,,,,,,,,,
+KEY_BREAK,0x19b,,,,,,,,,,,,,,BREA,,,
+KEY_BREAK,0x19b,,,,,,,,,,,,,,BRK,,,
+KEY_PREVIOUS,0x19c,,,,,,,,,,,,,,,,,
+KEY_DIGITS,0x19d,,,,,,,,,,,,,,,,,
+KEY_TEEN,0x19e,,,,,,,,,,,,,,,,,
+KEY_TWEN,0x19f,,,,,,,,,,,,,,,,,
+KEY_VIDEOPHONE,0x1a0,,,,,,,,,,,,,,,,,
+KEY_GAMES,0x1a1,,,,,,,,,,,,,,,,,
+KEY_ZOOMIN,0x1a2,,,,,,,,,,,,,,,,,
+KEY_ZOOMOUT,0x1a3,,,,,,,,,,,,,,,,,
+KEY_ZOOMRESET,0x1a4,,,,,,,,,,,,,,,,,
+KEY_WORDPROCESSOR,0x1a5,,,,,,,,,,,,,,,,,
+KEY_EDITOR,0x1a6,,,,,,,,,,,,,,,,,
+KEY_SPREADSHEET,0x1a7,,,,,,,,,,,,,,,,,
+KEY_GRAPHICSEDITOR,0x1a8,,,,,,,,,,,,,,,,,
+KEY_PRESENTATION,0x1a9,,,,,,,,,,,,,,,,,
+KEY_DATABASE,0x1aa,,,,,,,,,,,,,,,,,
+KEY_NEWS,0x1ab,,,,,,,,,,,,,,,,,
+KEY_VOICEMAIL,0x1ac,,,,,,,,,,,,,,,,,
+KEY_ADDRESSBOOK,0x1ad,,,,,,,,,,,,,,,,,
+KEY_MESSENGER,0x1ae,,,,,,,,,,,,,,,,,
+KEY_DISPLAYTOGGLE,0x1af,,,,,,,,,,,,,,,,,
+KEY_SPELLCHECK,0x1b0,,,,,,,,,,,,,,,,,
+KEY_LOGOFF,0x1b1,,,,,,,,,,,,,,,,,
+KEY_DOLLAR,0x1b2,,,,,,,,,,,,,,,,,
+KEY_EURO,0x1b3,,,,,,,,,,,,,,,,,
+KEY_FRAMEBACK,0x1b4,,,,,,,,,,,,,,,,,
+KEY_FRAMEFORWARD,0x1b5,,,,,,,,,,,,,,,,,
+KEY_CONTEXT_MENU,0x1b6,,,,,,,,,,,,,,,,,
+KEY_MEDIA_REPEAT,0x1b7,,,,,,,,,,,,,,,,,
+KEY_DEL_EOL,0x1c0,,,,,,,,,,,,,,,,,
+KEY_DEL_EOS,0x1c1,,,,,,,,,,,,,,,,,
+KEY_INS_LINE,0x1c2,,,,,,,,,,,,,,,,,
+KEY_DEL_LINE,0x1c3,,,,,,,,,,,,,,,,,
+KEY_FN,0x1d0,Function,0x3f,,,,,,,,,,,Fn,,,,
+KEY_FN_ESC,0x1d1,,,,,,,,,,,,,,,,,
+KEY_FN_F1,0x1d2,,,,,,,,,,,,,,,,,
+KEY_FN_F2,0x1d3,,,,,,,,,,,,,,,,,
+KEY_FN_F3,0x1d4,,,,,,,,,,,,,,,,,
+KEY_FN_F4,0x1d5,,,,,,,,,,,,,,,,,
+KEY_FN_F5,0x1d6,,,,,,,,,,,,,,,,,
+KEY_FN_F6,0x1d7,,,,,,,,,,,,,,,,,
+KEY_FN_F7,0x1d8,,,,,,,,,,,,,,,,,
+KEY_FN_F8,0x1d9,,,,,,,,,,,,,,,,,
+KEY_FN_F9,0x1da,,,,,,,,,,,,,,,,,
+KEY_FN_F10,0x1db,,,,,,,,,,,,,,,,,
+KEY_FN_F11,0x1dc,,,,,,,,,,,,,,,,,
+KEY_FN_F12,0x1dd,,,,,,,,,,,,,,,,,
+KEY_FN_1,0x1de,,,,,,,,,,,,,,,,,
+KEY_FN_2,0x1df,,,,,,,,,,,,,,,,,
+KEY_FN_D,0x1e0,,,,,,,,,,,,,,,,,
+KEY_FN_E,0x1e1,,,,,,,,,,,,,,,,,
+KEY_FN_F,0x1e2,,,,,,,,,,,,,,,,,
+KEY_FN_S,0x1e3,,,,,,,,,,,,,,,,,
+KEY_FN_B,0x1e4,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT1,0x1f1,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT2,0x1f2,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT3,0x1f3,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT4,0x1f4,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT5,0x1f5,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT6,0x1f6,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT7,0x1f7,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT8,0x1f8,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT9,0x1f9,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT10,0x1fa,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_0,0x200,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_1,0x201,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_2,0x202,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_3,0x203,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_4,0x204,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_5,0x205,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_6,0x206,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_7,0x207,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_8,0x208,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_9,0x209,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_STAR,0x20a,,,,,,,,,,,,,NumpadStar,,,,
+KEY_NUMERIC_POUND,0x20b,,,,,,,,,,,,,NumpadHash,,,,
+KEY_RFKILL,0x20c,,,,,,,,,,,,,,,,,
--- /dev/null
+osx2win32.*
+osx2win32_name.*
+osx2xkb.*
+osx2xkb_name.*
+html2win32.*
+html2win32_name.*
+osx.*
+osx_name.*
+stdc
+stdc++
+node_modules/
--- /dev/null
+TESTS := stdc stdc++ python2 python3 javascript
+
+check: $(TESTS)
+ @set -e; for fn in $(TESTS); do \
+ ./$$fn; \
+ echo $$fn: OK; \
+ done
+ @echo Done.
+
+GEN := ../tools/keymap-gen
+DATA := ../data/keymaps.csv
+SOURCES := $(GEN) $(DATA)
+
+.DELETE_ON_ERROR:
+
+stdc: stdc.c osx2win32.h osx2win32_name.h \
+ osx2xkb.h osx2xkb_name.h \
+ html2win32.h html2win32_name.h \
+ osx.h osx_name.h
+ $(CC) -Wall -o $@ $^
+osx2win32.h: $(SOURCES)
+ $(GEN) --lang stdc code-map $(DATA) osx win32 > $@
+osx2win32_name.h: $(SOURCES)
+ $(GEN) --lang stdc name-map $(DATA) osx win32 > $@
+osx2xkb.h: $(SOURCES)
+ $(GEN) --lang stdc code-map $(DATA) osx xkb > $@
+osx2xkb_name.h: $(SOURCES)
+ $(GEN) --lang stdc name-map $(DATA) osx xkb > $@
+html2win32.h: $(SOURCES)
+ $(GEN) --lang stdc code-map $(DATA) html win32 > $@
+html2win32_name.h: $(SOURCES)
+ $(GEN) --lang stdc name-map $(DATA) html win32 > $@
+osx.h: $(SOURCES)
+ $(GEN) --lang stdc code-table $(DATA) osx > $@
+osx_name.h: $(SOURCES)
+ $(GEN) --lang stdc name-table $(DATA) osx > $@
+
+stdc++: stdc++.cc osx2win32.hh osx2win32_name.hh \
+ osx2xkb.hh osx2xkb_name.hh \
+ html2win32.hh html2win32_name.hh \
+ osx.hh osx_name.hh
+ $(CXX) -Wall -std=c++11 -o $@ $^
+osx2win32.hh: $(SOURCES)
+ $(GEN) --lang stdc++ code-map $(DATA) osx win32 > $@
+osx2win32_name.hh: $(SOURCES)
+ $(GEN) --lang stdc++ name-map $(DATA) osx win32 > $@
+osx2xkb.hh: $(SOURCES)
+ $(GEN) --lang stdc++ code-map $(DATA) osx xkb > $@
+osx2xkb_name.hh: $(SOURCES)
+ $(GEN) --lang stdc++ name-map $(DATA) osx xkb > $@
+html2win32.hh: $(SOURCES)
+ $(GEN) --lang stdc++ code-map $(DATA) html win32 > $@
+html2win32_name.hh: $(SOURCES)
+ $(GEN) --lang stdc++ name-map $(DATA) html win32 > $@
+osx.hh: $(SOURCES)
+ $(GEN) --lang stdc++ code-table $(DATA) osx > $@
+osx_name.hh: $(SOURCES)
+ $(GEN) --lang stdc++ name-table $(DATA) osx > $@
+
+python2: osx2win32.py osx2win32_name.py \
+ osx2xkb.py osx2xkb_name.py \
+ html2win32.py html2win32_name.py \
+ osx.py osx_name.py
+osx2win32.py: $(SOURCES)
+ $(GEN) --lang python2 code-map $(DATA) osx win32 > $@
+osx2win32_name.py: $(SOURCES)
+ $(GEN) --lang python2 name-map $(DATA) osx win32 > $@
+osx2xkb.py: $(SOURCES)
+ $(GEN) --lang python2 code-map $(DATA) osx xkb > $@
+osx2xkb_name.py: $(SOURCES)
+ $(GEN) --lang python2 name-map $(DATA) osx xkb > $@
+html2win32.py: $(SOURCES)
+ $(GEN) --lang python2 code-map $(DATA) html win32 > $@
+html2win32_name.py: $(SOURCES)
+ $(GEN) --lang python2 name-map $(DATA) html win32 > $@
+osx.py: $(SOURCES)
+ $(GEN) --lang python2 code-table $(DATA) osx > $@
+osx_name.py: $(SOURCES)
+ $(GEN) --lang python2 name-table $(DATA) osx > $@
+
+javascript: node_modules/babel-core \
+ node_modules/babel-plugin-transform-es2015-modules-commonjs \
+ osx2win32.js osx2win32_name.js \
+ osx2xkb.js osx2xkb_name.js \
+ html2win32.js html2win32_name.js \
+ osx.js osx_name.js
+node_modules/babel-core:
+ npm install babel-core
+node_modules/babel-plugin-transform-es2015-modules-commonjs:
+ npm install babel-plugin-transform-es2015-modules-commonjs
+osx2win32.js: $(SOURCES)
+ $(GEN) --lang js code-map $(DATA) osx win32 > $@
+osx2win32_name.js: $(SOURCES)
+ $(GEN) --lang js name-map $(DATA) osx win32 > $@
+osx2xkb.js: $(SOURCES)
+ $(GEN) --lang js code-map $(DATA) osx xkb > $@
+osx2xkb_name.js: $(SOURCES)
+ $(GEN) --lang js name-map $(DATA) osx xkb > $@
+html2win32.js: $(SOURCES)
+ $(GEN) --lang js code-map $(DATA) html win32 > $@
+html2win32_name.js: $(SOURCES)
+ $(GEN) --lang js name-map $(DATA) html win32 > $@
+osx.js: $(SOURCES)
+ $(GEN) --lang js code-table $(DATA) osx > $@
+osx_name.js: $(SOURCES)
+ $(GEN) --lang js name-table $(DATA) osx > $@
+
+clean:
+ rm -rf node_modules
+ rm -f osx2win32.*
+ rm -f osx2win32_name.*
+ rm -f osx2xkb.*
+ rm -f osx2xkb_name.*
+ rm -f html2win32.*
+ rm -f html2win32_name.*
+ rm -f osx.*
+ rm -f osx_name.*
+ rm -f stdc stdc++
--- /dev/null
+#!/usr/bin/env node
+/*
+ * Keycode Map Generator JavaScript Tests
+ *
+ * Copyright 2017 Pierre Ossman for Cendio AB
+ *
+ * This file is dual license under the terms of the GPLv2 or later
+ * and 3-clause BSD licenses.
+ */
+
+"use strict";
+
+var assert = require('assert');
+var babel = require('babel-core');
+var fs = require('fs');
+
+function include(fn) {
+ var options = {
+ plugins: ["transform-es2015-modules-commonjs"]
+ };
+
+ var code = babel.transformFileSync(fn, options).code;
+ fs.writeFileSync("." + fn + "_nodejs.js", code);
+ var imp = require("./." + fn + "_nodejs.js");
+ fs.unlinkSync("./." + fn + "_nodejs.js");
+
+ return imp
+}
+
+var code_map_osx_to_win32 = include("osx2win32.js").default;
+var name_map_osx_to_win32 = include("osx2win32_name.js").default;
+
+var code_map_osx_to_xkb = include("osx2xkb.js").default;
+var name_map_osx_to_xkb = include("osx2xkb_name.js").default;
+
+var code_map_html_to_win32 = include("html2win32.js").default;
+var name_map_html_to_win32 = include("html2win32_name.js").default;
+
+var code_table_osx = include("osx.js").default;
+var name_table_osx = include("osx_name.js").default;
+
+assert.equal(code_map_osx_to_win32[0x1d], 0x30);
+assert.equal(name_map_osx_to_win32[0x1d], "VK_0");
+
+assert.equal(code_map_osx_to_xkb[0x1d], "AE10");
+assert.equal(name_map_osx_to_xkb[0x1d], "AE10");
+
+assert.equal(code_map_html_to_win32["ControlLeft"], 0x11);
+assert.equal(name_map_html_to_win32["ControlLeft"], "VK_CONTROL");
+
+assert.equal(code_table_osx[0x1d], 0x3b);
+assert.equal(name_table_osx[0x1d], "Control");
+
--- /dev/null
+#!/bin/sh
+
+python ./test.py
--- /dev/null
+#!/bin/sh
+
+python3 ./test.py
--- /dev/null
+/*
+ * Keycode Map Generator C++ Tests
+ *
+ * Copyright 2017 Pierre Ossman for Cendio AB
+ *
+ * This file is dual license under the terms of the GPLv2 or later
+ * and 3-clause BSD licenses.
+ */
+
+#include <assert.h>
+#include <string.h>
+
+#include "osx2win32.hh"
+#include "osx2win32_name.hh"
+
+#include "osx2xkb.hh"
+#include "osx2xkb_name.hh"
+
+#include "html2win32.hh"
+#include "html2win32_name.hh"
+
+#include "osx.hh"
+#include "osx_name.hh"
+
+int main(int argc, char** argv)
+{
+ assert(code_map_osx_to_win32[0x1d] == 0x30);
+ assert(strcmp(name_map_osx_to_win32[0x1d], "VK_0") == 0);
+
+ assert(strcmp(code_map_osx_to_xkb[0x1d], "AE10") == 0);
+ assert(strcmp(name_map_osx_to_xkb[0x1d], "AE10") == 0);
+
+ assert(code_map_html_to_win32.at("ControlLeft") == 0x11);
+ assert(strcmp(name_map_html_to_win32.at("ControlLeft"), "VK_CONTROL") == 0);
+
+ assert(code_table_osx[0x1d] == 0x3b);
+ assert(strcmp(name_table_osx[0x1d], "Control") == 0);
+
+ return 0;
+}
--- /dev/null
+/*
+ * Keycode Map Generator C Tests
+ *
+ * Copyright 2017 Pierre Ossman for Cendio AB
+ *
+ * This file is dual license under the terms of the GPLv2 or later
+ * and 3-clause BSD licenses.
+ */
+
+#include <assert.h>
+#include <string.h>
+
+#include "osx2win32.h"
+#include "osx2win32_name.h"
+
+#include "osx2xkb.h"
+#include "osx2xkb_name.h"
+
+#include "html2win32.h"
+#include "html2win32_name.h"
+
+#include "osx.h"
+#include "osx_name.h"
+
+#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
+
+int main(int argc, char** argv)
+{
+ unsigned i;
+
+ assert(code_map_osx_to_win32_len == ARRAY_SIZE(code_map_osx_to_win32));
+ assert(code_map_osx_to_win32[0x1d] == 0x30);
+ assert(name_map_osx_to_win32_len == ARRAY_SIZE(name_map_osx_to_win32));
+ assert(strcmp(name_map_osx_to_win32[0x1d], "VK_0") == 0);
+
+ assert(code_map_osx_to_xkb_len == ARRAY_SIZE(code_map_osx_to_xkb));
+ assert(strcmp(code_map_osx_to_xkb[0x1d], "AE10") == 0);
+ assert(name_map_osx_to_xkb_len == ARRAY_SIZE(name_map_osx_to_xkb));
+ assert(strcmp(name_map_osx_to_xkb[0x1d], "AE10") == 0);
+
+ assert(code_map_html_to_win32_len == ARRAY_SIZE(code_map_html_to_win32));
+ for (i = 0;i < code_map_html_to_win32_len;i++) {
+ if (strcmp(code_map_html_to_win32[i].from, "ControlLeft") == 0) {
+ assert(code_map_html_to_win32[i].to == 0x11);
+ break;
+ }
+ }
+ assert(i != code_map_html_to_win32_len);
+ assert(name_map_html_to_win32_len == ARRAY_SIZE(name_map_html_to_win32));
+ for (i = 0;i < name_map_html_to_win32_len;i++) {
+ if (strcmp(name_map_html_to_win32[i].from, "ControlLeft") == 0) {
+ assert(strcmp(name_map_html_to_win32[i].to, "VK_CONTROL") == 0);
+ break;
+ }
+ }
+ assert(i != name_map_html_to_win32_len);
+
+ assert(code_table_osx_len == ARRAY_SIZE(code_table_osx));
+ assert(code_table_osx[0x1d] == 0x3b);
+ assert(name_table_osx_len == ARRAY_SIZE(name_table_osx));
+ assert(strcmp(name_table_osx[0x1d], "Control") == 0);
+
+ return 0;
+}
--- /dev/null
+# Keycode Map Generator Python Tests
+#
+# Copyright 2017 Pierre Ossman for Cendio AB
+#
+# This file is dual license under the terms of the GPLv2 or later
+# and 3-clause BSD licenses.
+
+import osx2win32
+import osx2win32_name
+
+import osx2xkb
+import osx2xkb_name
+
+import html2win32
+import html2win32_name
+
+import osx
+import osx_name
+
+assert osx2win32.code_map_osx_to_win32[0x1d] == 0x30
+assert osx2win32_name.name_map_osx_to_win32[0x1d] == "VK_0"
+
+assert osx2xkb.code_map_osx_to_xkb[0x1d] == "AE10"
+assert osx2xkb_name.name_map_osx_to_xkb[0x1d] == "AE10"
+
+assert html2win32.code_map_html_to_win32["ControlLeft"] == 0x11
+assert html2win32_name.name_map_html_to_win32["ControlLeft"] == "VK_CONTROL"
+
+assert osx.code_table_osx[0x1d] == 0x3b;
+assert osx_name.name_table_osx[0x1d] == "Control";
--- /dev/null
+argparse is (c) 2006-2009 Steven J. Bethard <steven.bethard@gmail.com>.
+
+The argparse module was contributed to Python as of Python 2.7 and thus
+was licensed under the Python license. Same license applies to all files in
+the argparse package project.
+
+For details about the Python License, please see doc/Python-License.txt.
+
+History
+-------
+
+Before (and including) argparse 1.1, the argparse package was licensed under
+Apache License v2.0.
+
+After argparse 1.1, all project files from the argparse project were deleted
+due to license compatibility issues between Apache License 2.0 and GNU GPL v2.
+
+The project repository then had a clean start with some files taken from
+Python 2.7.1, so definitely all files are under Python License now.
+
--- /dev/null
+# Author: Steven J. Bethard <steven.bethard@gmail.com>.
+# Maintainer: Thomas Waldmann <tw@waldmann-edv.de>
+
+"""Command-line parsing library
+
+This module is an optparse-inspired command-line parsing library that:
+
+ - handles both optional and positional arguments
+ - produces highly informative usage messages
+ - supports parsers that dispatch to sub-parsers
+
+The following is a simple usage example that sums integers from the
+command-line and writes the result to a file::
+
+ parser = argparse.ArgumentParser(
+ description='sum the integers at the command line')
+ parser.add_argument(
+ 'integers', metavar='int', nargs='+', type=int,
+ help='an integer to be summed')
+ parser.add_argument(
+ '--log', default=sys.stdout, type=argparse.FileType('w'),
+ help='the file where the sum should be written')
+ args = parser.parse_args()
+ args.log.write('%s' % sum(args.integers))
+ args.log.close()
+
+The module contains the following public classes:
+
+ - ArgumentParser -- The main entry point for command-line parsing. As the
+ example above shows, the add_argument() method is used to populate
+ the parser with actions for optional and positional arguments. Then
+ the parse_args() method is invoked to convert the args at the
+ command-line into an object with attributes.
+
+ - ArgumentError -- The exception raised by ArgumentParser objects when
+ there are errors with the parser's actions. Errors raised while
+ parsing the command-line are caught by ArgumentParser and emitted
+ as command-line messages.
+
+ - FileType -- A factory for defining types of files to be created. As the
+ example above shows, instances of FileType are typically passed as
+ the type= argument of add_argument() calls.
+
+ - Action -- The base class for parser actions. Typically actions are
+ selected by passing strings like 'store_true' or 'append_const' to
+ the action= argument of add_argument(). However, for greater
+ customization of ArgumentParser actions, subclasses of Action may
+ be defined and passed as the action= argument.
+
+ - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter,
+ ArgumentDefaultsHelpFormatter -- Formatter classes which
+ may be passed as the formatter_class= argument to the
+ ArgumentParser constructor. HelpFormatter is the default,
+ RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser
+ not to change the formatting for help text, and
+ ArgumentDefaultsHelpFormatter adds information about argument defaults
+ to the help.
+
+All other classes in this module are considered implementation details.
+(Also note that HelpFormatter and RawDescriptionHelpFormatter are only
+considered public as object names -- the API of the formatter objects is
+still considered an implementation detail.)
+"""
+
+__version__ = '1.4.0' # we use our own version number independant of the
+ # one in stdlib and we release this on pypi.
+
+__external_lib__ = True # to make sure the tests really test THIS lib,
+ # not the builtin one in Python stdlib
+
+__all__ = [
+ 'ArgumentParser',
+ 'ArgumentError',
+ 'ArgumentTypeError',
+ 'FileType',
+ 'HelpFormatter',
+ 'ArgumentDefaultsHelpFormatter',
+ 'RawDescriptionHelpFormatter',
+ 'RawTextHelpFormatter',
+ 'Namespace',
+ 'Action',
+ 'ONE_OR_MORE',
+ 'OPTIONAL',
+ 'PARSER',
+ 'REMAINDER',
+ 'SUPPRESS',
+ 'ZERO_OR_MORE',
+]
+
+
+import copy as _copy
+import os as _os
+import re as _re
+import sys as _sys
+import textwrap as _textwrap
+
+from gettext import gettext as _
+
+try:
+ set
+except NameError:
+ # for python < 2.4 compatibility (sets module is there since 2.3):
+ from sets import Set as set
+
+try:
+ basestring
+except NameError:
+ basestring = str
+
+try:
+ sorted
+except NameError:
+ # for python < 2.4 compatibility:
+ def sorted(iterable, reverse=False):
+ result = list(iterable)
+ result.sort()
+ if reverse:
+ result.reverse()
+ return result
+
+
+def _callable(obj):
+ return hasattr(obj, '__call__') or hasattr(obj, '__bases__')
+
+
+SUPPRESS = '==SUPPRESS=='
+
+OPTIONAL = '?'
+ZERO_OR_MORE = '*'
+ONE_OR_MORE = '+'
+PARSER = 'A...'
+REMAINDER = '...'
+_UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args'
+
+# =============================
+# Utility functions and classes
+# =============================
+
+class _AttributeHolder(object):
+ """Abstract base class that provides __repr__.
+
+ The __repr__ method returns a string in the format::
+ ClassName(attr=name, attr=name, ...)
+ The attributes are determined either by a class-level attribute,
+ '_kwarg_names', or by inspecting the instance __dict__.
+ """
+
+ def __repr__(self):
+ type_name = type(self).__name__
+ arg_strings = []
+ for arg in self._get_args():
+ arg_strings.append(repr(arg))
+ for name, value in self._get_kwargs():
+ arg_strings.append('%s=%r' % (name, value))
+ return '%s(%s)' % (type_name, ', '.join(arg_strings))
+
+ def _get_kwargs(self):
+ return sorted(self.__dict__.items())
+
+ def _get_args(self):
+ return []
+
+
+def _ensure_value(namespace, name, value):
+ if getattr(namespace, name, None) is None:
+ setattr(namespace, name, value)
+ return getattr(namespace, name)
+
+
+# ===============
+# Formatting Help
+# ===============
+
+class HelpFormatter(object):
+ """Formatter for generating usage messages and argument help strings.
+
+ Only the name of this class is considered a public API. All the methods
+ provided by the class are considered an implementation detail.
+ """
+
+ def __init__(self,
+ prog,
+ indent_increment=2,
+ max_help_position=24,
+ width=None):
+
+ # default setting for width
+ if width is None:
+ try:
+ width = int(_os.environ['COLUMNS'])
+ except (KeyError, ValueError):
+ width = 80
+ width -= 2
+
+ self._prog = prog
+ self._indent_increment = indent_increment
+ self._max_help_position = max_help_position
+ self._width = width
+
+ self._current_indent = 0
+ self._level = 0
+ self._action_max_length = 0
+
+ self._root_section = self._Section(self, None)
+ self._current_section = self._root_section
+
+ self._whitespace_matcher = _re.compile(r'\s+')
+ self._long_break_matcher = _re.compile(r'\n\n\n+')
+
+ # ===============================
+ # Section and indentation methods
+ # ===============================
+ def _indent(self):
+ self._current_indent += self._indent_increment
+ self._level += 1
+
+ def _dedent(self):
+ self._current_indent -= self._indent_increment
+ assert self._current_indent >= 0, 'Indent decreased below 0.'
+ self._level -= 1
+
+ class _Section(object):
+
+ def __init__(self, formatter, parent, heading=None):
+ self.formatter = formatter
+ self.parent = parent
+ self.heading = heading
+ self.items = []
+
+ def format_help(self):
+ # format the indented section
+ if self.parent is not None:
+ self.formatter._indent()
+ join = self.formatter._join_parts
+ for func, args in self.items:
+ func(*args)
+ item_help = join([func(*args) for func, args in self.items])
+ if self.parent is not None:
+ self.formatter._dedent()
+
+ # return nothing if the section was empty
+ if not item_help:
+ return ''
+
+ # add the heading if the section was non-empty
+ if self.heading is not SUPPRESS and self.heading is not None:
+ current_indent = self.formatter._current_indent
+ heading = '%*s%s:\n' % (current_indent, '', self.heading)
+ else:
+ heading = ''
+
+ # join the section-initial newline, the heading and the help
+ return join(['\n', heading, item_help, '\n'])
+
+ def _add_item(self, func, args):
+ self._current_section.items.append((func, args))
+
+ # ========================
+ # Message building methods
+ # ========================
+ def start_section(self, heading):
+ self._indent()
+ section = self._Section(self, self._current_section, heading)
+ self._add_item(section.format_help, [])
+ self._current_section = section
+
+ def end_section(self):
+ self._current_section = self._current_section.parent
+ self._dedent()
+
+ def add_text(self, text):
+ if text is not SUPPRESS and text is not None:
+ self._add_item(self._format_text, [text])
+
+ def add_usage(self, usage, actions, groups, prefix=None):
+ if usage is not SUPPRESS:
+ args = usage, actions, groups, prefix
+ self._add_item(self._format_usage, args)
+
+ def add_argument(self, action):
+ if action.help is not SUPPRESS:
+
+ # find all invocations
+ get_invocation = self._format_action_invocation
+ invocations = [get_invocation(action)]
+ for subaction in self._iter_indented_subactions(action):
+ invocations.append(get_invocation(subaction))
+
+ # update the maximum item length
+ invocation_length = max([len(s) for s in invocations])
+ action_length = invocation_length + self._current_indent
+ self._action_max_length = max(self._action_max_length,
+ action_length)
+
+ # add the item to the list
+ self._add_item(self._format_action, [action])
+
+ def add_arguments(self, actions):
+ for action in actions:
+ self.add_argument(action)
+
+ # =======================
+ # Help-formatting methods
+ # =======================
+ def format_help(self):
+ help = self._root_section.format_help()
+ if help:
+ help = self._long_break_matcher.sub('\n\n', help)
+ help = help.strip('\n') + '\n'
+ return help
+
+ def _join_parts(self, part_strings):
+ return ''.join([part
+ for part in part_strings
+ if part and part is not SUPPRESS])
+
+ def _format_usage(self, usage, actions, groups, prefix):
+ if prefix is None:
+ prefix = _('usage: ')
+
+ # if usage is specified, use that
+ if usage is not None:
+ usage = usage % dict(prog=self._prog)
+
+ # if no optionals or positionals are available, usage is just prog
+ elif usage is None and not actions:
+ usage = '%(prog)s' % dict(prog=self._prog)
+
+ # if optionals and positionals are available, calculate usage
+ elif usage is None:
+ prog = '%(prog)s' % dict(prog=self._prog)
+
+ # split optionals from positionals
+ optionals = []
+ positionals = []
+ for action in actions:
+ if action.option_strings:
+ optionals.append(action)
+ else:
+ positionals.append(action)
+
+ # build full usage string
+ format = self._format_actions_usage
+ action_usage = format(optionals + positionals, groups)
+ usage = ' '.join([s for s in [prog, action_usage] if s])
+
+ # wrap the usage parts if it's too long
+ text_width = self._width - self._current_indent
+ if len(prefix) + len(usage) > text_width:
+
+ # break usage into wrappable parts
+ part_regexp = r'\(.*?\)+|\[.*?\]+|\S+'
+ opt_usage = format(optionals, groups)
+ pos_usage = format(positionals, groups)
+ opt_parts = _re.findall(part_regexp, opt_usage)
+ pos_parts = _re.findall(part_regexp, pos_usage)
+ assert ' '.join(opt_parts) == opt_usage
+ assert ' '.join(pos_parts) == pos_usage
+
+ # helper for wrapping lines
+ def get_lines(parts, indent, prefix=None):
+ lines = []
+ line = []
+ if prefix is not None:
+ line_len = len(prefix) - 1
+ else:
+ line_len = len(indent) - 1
+ for part in parts:
+ if line_len + 1 + len(part) > text_width:
+ lines.append(indent + ' '.join(line))
+ line = []
+ line_len = len(indent) - 1
+ line.append(part)
+ line_len += len(part) + 1
+ if line:
+ lines.append(indent + ' '.join(line))
+ if prefix is not None:
+ lines[0] = lines[0][len(indent):]
+ return lines
+
+ # if prog is short, follow it with optionals or positionals
+ if len(prefix) + len(prog) <= 0.75 * text_width:
+ indent = ' ' * (len(prefix) + len(prog) + 1)
+ if opt_parts:
+ lines = get_lines([prog] + opt_parts, indent, prefix)
+ lines.extend(get_lines(pos_parts, indent))
+ elif pos_parts:
+ lines = get_lines([prog] + pos_parts, indent, prefix)
+ else:
+ lines = [prog]
+
+ # if prog is long, put it on its own line
+ else:
+ indent = ' ' * len(prefix)
+ parts = opt_parts + pos_parts
+ lines = get_lines(parts, indent)
+ if len(lines) > 1:
+ lines = []
+ lines.extend(get_lines(opt_parts, indent))
+ lines.extend(get_lines(pos_parts, indent))
+ lines = [prog] + lines
+
+ # join lines into usage
+ usage = '\n'.join(lines)
+
+ # prefix with 'usage:'
+ return '%s%s\n\n' % (prefix, usage)
+
+ def _format_actions_usage(self, actions, groups):
+ # find group indices and identify actions in groups
+ group_actions = set()
+ inserts = {}
+ for group in groups:
+ try:
+ start = actions.index(group._group_actions[0])
+ except ValueError:
+ continue
+ else:
+ end = start + len(group._group_actions)
+ if actions[start:end] == group._group_actions:
+ for action in group._group_actions:
+ group_actions.add(action)
+ if not group.required:
+ if start in inserts:
+ inserts[start] += ' ['
+ else:
+ inserts[start] = '['
+ inserts[end] = ']'
+ else:
+ if start in inserts:
+ inserts[start] += ' ('
+ else:
+ inserts[start] = '('
+ inserts[end] = ')'
+ for i in range(start + 1, end):
+ inserts[i] = '|'
+
+ # collect all actions format strings
+ parts = []
+ for i, action in enumerate(actions):
+
+ # suppressed arguments are marked with None
+ # remove | separators for suppressed arguments
+ if action.help is SUPPRESS:
+ parts.append(None)
+ if inserts.get(i) == '|':
+ inserts.pop(i)
+ elif inserts.get(i + 1) == '|':
+ inserts.pop(i + 1)
+
+ # produce all arg strings
+ elif not action.option_strings:
+ part = self._format_args(action, action.dest)
+
+ # if it's in a group, strip the outer []
+ if action in group_actions:
+ if part[0] == '[' and part[-1] == ']':
+ part = part[1:-1]
+
+ # add the action string to the list
+ parts.append(part)
+
+ # produce the first way to invoke the option in brackets
+ else:
+ option_string = action.option_strings[0]
+
+ # if the Optional doesn't take a value, format is:
+ # -s or --long
+ if action.nargs == 0:
+ part = '%s' % option_string
+
+ # if the Optional takes a value, format is:
+ # -s ARGS or --long ARGS
+ else:
+ default = action.dest.upper()
+ args_string = self._format_args(action, default)
+ part = '%s %s' % (option_string, args_string)
+
+ # make it look optional if it's not required or in a group
+ if not action.required and action not in group_actions:
+ part = '[%s]' % part
+
+ # add the action string to the list
+ parts.append(part)
+
+ # insert things at the necessary indices
+ for i in sorted(inserts, reverse=True):
+ parts[i:i] = [inserts[i]]
+
+ # join all the action items with spaces
+ text = ' '.join([item for item in parts if item is not None])
+
+ # clean up separators for mutually exclusive groups
+ open = r'[\[(]'
+ close = r'[\])]'
+ text = _re.sub(r'(%s) ' % open, r'\1', text)
+ text = _re.sub(r' (%s)' % close, r'\1', text)
+ text = _re.sub(r'%s *%s' % (open, close), r'', text)
+ text = _re.sub(r'\(([^|]*)\)', r'\1', text)
+ text = text.strip()
+
+ # return the text
+ return text
+
+ def _format_text(self, text):
+ if '%(prog)' in text:
+ text = text % dict(prog=self._prog)
+ text_width = self._width - self._current_indent
+ indent = ' ' * self._current_indent
+ return self._fill_text(text, text_width, indent) + '\n\n'
+
+ def _format_action(self, action):
+ # determine the required width and the entry label
+ help_position = min(self._action_max_length + 2,
+ self._max_help_position)
+ help_width = self._width - help_position
+ action_width = help_position - self._current_indent - 2
+ action_header = self._format_action_invocation(action)
+
+ # ho nelp; start on same line and add a final newline
+ if not action.help:
+ tup = self._current_indent, '', action_header
+ action_header = '%*s%s\n' % tup
+
+ # short action name; start on the same line and pad two spaces
+ elif len(action_header) <= action_width:
+ tup = self._current_indent, '', action_width, action_header
+ action_header = '%*s%-*s ' % tup
+ indent_first = 0
+
+ # long action name; start on the next line
+ else:
+ tup = self._current_indent, '', action_header
+ action_header = '%*s%s\n' % tup
+ indent_first = help_position
+
+ # collect the pieces of the action help
+ parts = [action_header]
+
+ # if there was help for the action, add lines of help text
+ if action.help:
+ help_text = self._expand_help(action)
+ help_lines = self._split_lines(help_text, help_width)
+ parts.append('%*s%s\n' % (indent_first, '', help_lines[0]))
+ for line in help_lines[1:]:
+ parts.append('%*s%s\n' % (help_position, '', line))
+
+ # or add a newline if the description doesn't end with one
+ elif not action_header.endswith('\n'):
+ parts.append('\n')
+
+ # if there are any sub-actions, add their help as well
+ for subaction in self._iter_indented_subactions(action):
+ parts.append(self._format_action(subaction))
+
+ # return a single string
+ return self._join_parts(parts)
+
+ def _format_action_invocation(self, action):
+ if not action.option_strings:
+ metavar, = self._metavar_formatter(action, action.dest)(1)
+ return metavar
+
+ else:
+ parts = []
+
+ # if the Optional doesn't take a value, format is:
+ # -s, --long
+ if action.nargs == 0:
+ parts.extend(action.option_strings)
+
+ # if the Optional takes a value, format is:
+ # -s ARGS, --long ARGS
+ else:
+ default = action.dest.upper()
+ args_string = self._format_args(action, default)
+ for option_string in action.option_strings:
+ parts.append('%s %s' % (option_string, args_string))
+
+ return ', '.join(parts)
+
+ def _metavar_formatter(self, action, default_metavar):
+ if action.metavar is not None:
+ result = action.metavar
+ elif action.choices is not None:
+ choice_strs = [str(choice) for choice in action.choices]
+ result = '{%s}' % ','.join(choice_strs)
+ else:
+ result = default_metavar
+
+ def format(tuple_size):
+ if isinstance(result, tuple):
+ return result
+ else:
+ return (result, ) * tuple_size
+ return format
+
+ def _format_args(self, action, default_metavar):
+ get_metavar = self._metavar_formatter(action, default_metavar)
+ if action.nargs is None:
+ result = '%s' % get_metavar(1)
+ elif action.nargs == OPTIONAL:
+ result = '[%s]' % get_metavar(1)
+ elif action.nargs == ZERO_OR_MORE:
+ result = '[%s [%s ...]]' % get_metavar(2)
+ elif action.nargs == ONE_OR_MORE:
+ result = '%s [%s ...]' % get_metavar(2)
+ elif action.nargs == REMAINDER:
+ result = '...'
+ elif action.nargs == PARSER:
+ result = '%s ...' % get_metavar(1)
+ else:
+ formats = ['%s' for _ in range(action.nargs)]
+ result = ' '.join(formats) % get_metavar(action.nargs)
+ return result
+
+ def _expand_help(self, action):
+ params = dict(vars(action), prog=self._prog)
+ for name in list(params):
+ if params[name] is SUPPRESS:
+ del params[name]
+ for name in list(params):
+ if hasattr(params[name], '__name__'):
+ params[name] = params[name].__name__
+ if params.get('choices') is not None:
+ choices_str = ', '.join([str(c) for c in params['choices']])
+ params['choices'] = choices_str
+ return self._get_help_string(action) % params
+
+ def _iter_indented_subactions(self, action):
+ try:
+ get_subactions = action._get_subactions
+ except AttributeError:
+ pass
+ else:
+ self._indent()
+ for subaction in get_subactions():
+ yield subaction
+ self._dedent()
+
+ def _split_lines(self, text, width):
+ text = self._whitespace_matcher.sub(' ', text).strip()
+ return _textwrap.wrap(text, width)
+
+ def _fill_text(self, text, width, indent):
+ text = self._whitespace_matcher.sub(' ', text).strip()
+ return _textwrap.fill(text, width, initial_indent=indent,
+ subsequent_indent=indent)
+
+ def _get_help_string(self, action):
+ return action.help
+
+
+class RawDescriptionHelpFormatter(HelpFormatter):
+ """Help message formatter which retains any formatting in descriptions.
+
+ Only the name of this class is considered a public API. All the methods
+ provided by the class are considered an implementation detail.
+ """
+
+ def _fill_text(self, text, width, indent):
+ return ''.join([indent + line for line in text.splitlines(True)])
+
+
+class RawTextHelpFormatter(RawDescriptionHelpFormatter):
+ """Help message formatter which retains formatting of all help text.
+
+ Only the name of this class is considered a public API. All the methods
+ provided by the class are considered an implementation detail.
+ """
+
+ def _split_lines(self, text, width):
+ return text.splitlines()
+
+
+class ArgumentDefaultsHelpFormatter(HelpFormatter):
+ """Help message formatter which adds default values to argument help.
+
+ Only the name of this class is considered a public API. All the methods
+ provided by the class are considered an implementation detail.
+ """
+
+ def _get_help_string(self, action):
+ help = action.help
+ if '%(default)' not in action.help:
+ if action.default is not SUPPRESS:
+ defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
+ if action.option_strings or action.nargs in defaulting_nargs:
+ help += ' (default: %(default)s)'
+ return help
+
+
+# =====================
+# Options and Arguments
+# =====================
+
+def _get_action_name(argument):
+ if argument is None:
+ return None
+ elif argument.option_strings:
+ return '/'.join(argument.option_strings)
+ elif argument.metavar not in (None, SUPPRESS):
+ return argument.metavar
+ elif argument.dest not in (None, SUPPRESS):
+ return argument.dest
+ else:
+ return None
+
+
+class ArgumentError(Exception):
+ """An error from creating or using an argument (optional or positional).
+
+ The string value of this exception is the message, augmented with
+ information about the argument that caused it.
+ """
+
+ def __init__(self, argument, message):
+ self.argument_name = _get_action_name(argument)
+ self.message = message
+
+ def __str__(self):
+ if self.argument_name is None:
+ format = '%(message)s'
+ else:
+ format = 'argument %(argument_name)s: %(message)s'
+ return format % dict(message=self.message,
+ argument_name=self.argument_name)
+
+
+class ArgumentTypeError(Exception):
+ """An error from trying to convert a command line string to a type."""
+ pass
+
+
+# ==============
+# Action classes
+# ==============
+
+class Action(_AttributeHolder):
+ """Information about how to convert command line strings to Python objects.
+
+ Action objects are used by an ArgumentParser to represent the information
+ needed to parse a single argument from one or more strings from the
+ command line. The keyword arguments to the Action constructor are also
+ all attributes of Action instances.
+
+ Keyword Arguments:
+
+ - option_strings -- A list of command-line option strings which
+ should be associated with this action.
+
+ - dest -- The name of the attribute to hold the created object(s)
+
+ - nargs -- The number of command-line arguments that should be
+ consumed. By default, one argument will be consumed and a single
+ value will be produced. Other values include:
+ - N (an integer) consumes N arguments (and produces a list)
+ - '?' consumes zero or one arguments
+ - '*' consumes zero or more arguments (and produces a list)
+ - '+' consumes one or more arguments (and produces a list)
+ Note that the difference between the default and nargs=1 is that
+ with the default, a single value will be produced, while with
+ nargs=1, a list containing a single value will be produced.
+
+ - const -- The value to be produced if the option is specified and the
+ option uses an action that takes no values.
+
+ - default -- The value to be produced if the option is not specified.
+
+ - type -- The type which the command-line arguments should be converted
+ to, should be one of 'string', 'int', 'float', 'complex' or a
+ callable object that accepts a single string argument. If None,
+ 'string' is assumed.
+
+ - choices -- A container of values that should be allowed. If not None,
+ after a command-line argument has been converted to the appropriate
+ type, an exception will be raised if it is not a member of this
+ collection.
+
+ - required -- True if the action must always be specified at the
+ command line. This is only meaningful for optional command-line
+ arguments.
+
+ - help -- The help string describing the argument.
+
+ - metavar -- The name to be used for the option's argument with the
+ help string. If None, the 'dest' value will be used as the name.
+ """
+
+ def __init__(self,
+ option_strings,
+ dest,
+ nargs=None,
+ const=None,
+ default=None,
+ type=None,
+ choices=None,
+ required=False,
+ help=None,
+ metavar=None):
+ self.option_strings = option_strings
+ self.dest = dest
+ self.nargs = nargs
+ self.const = const
+ self.default = default
+ self.type = type
+ self.choices = choices
+ self.required = required
+ self.help = help
+ self.metavar = metavar
+
+ def _get_kwargs(self):
+ names = [
+ 'option_strings',
+ 'dest',
+ 'nargs',
+ 'const',
+ 'default',
+ 'type',
+ 'choices',
+ 'help',
+ 'metavar',
+ ]
+ return [(name, getattr(self, name)) for name in names]
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ raise NotImplementedError(_('.__call__() not defined'))
+
+
+class _StoreAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ nargs=None,
+ const=None,
+ default=None,
+ type=None,
+ choices=None,
+ required=False,
+ help=None,
+ metavar=None):
+ if nargs == 0:
+ raise ValueError('nargs for store actions must be > 0; if you '
+ 'have nothing to store, actions such as store '
+ 'true or store const may be more appropriate')
+ if const is not None and nargs != OPTIONAL:
+ raise ValueError('nargs must be %r to supply const' % OPTIONAL)
+ super(_StoreAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=nargs,
+ const=const,
+ default=default,
+ type=type,
+ choices=choices,
+ required=required,
+ help=help,
+ metavar=metavar)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ setattr(namespace, self.dest, values)
+
+
+class _StoreConstAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ const,
+ default=None,
+ required=False,
+ help=None,
+ metavar=None):
+ super(_StoreConstAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=0,
+ const=const,
+ default=default,
+ required=required,
+ help=help)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ setattr(namespace, self.dest, self.const)
+
+
+class _StoreTrueAction(_StoreConstAction):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ default=False,
+ required=False,
+ help=None):
+ super(_StoreTrueAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ const=True,
+ default=default,
+ required=required,
+ help=help)
+
+
+class _StoreFalseAction(_StoreConstAction):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ default=True,
+ required=False,
+ help=None):
+ super(_StoreFalseAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ const=False,
+ default=default,
+ required=required,
+ help=help)
+
+
+class _AppendAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ nargs=None,
+ const=None,
+ default=None,
+ type=None,
+ choices=None,
+ required=False,
+ help=None,
+ metavar=None):
+ if nargs == 0:
+ raise ValueError('nargs for append actions must be > 0; if arg '
+ 'strings are not supplying the value to append, '
+ 'the append const action may be more appropriate')
+ if const is not None and nargs != OPTIONAL:
+ raise ValueError('nargs must be %r to supply const' % OPTIONAL)
+ super(_AppendAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=nargs,
+ const=const,
+ default=default,
+ type=type,
+ choices=choices,
+ required=required,
+ help=help,
+ metavar=metavar)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ items = _copy.copy(_ensure_value(namespace, self.dest, []))
+ items.append(values)
+ setattr(namespace, self.dest, items)
+
+
+class _AppendConstAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ const,
+ default=None,
+ required=False,
+ help=None,
+ metavar=None):
+ super(_AppendConstAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=0,
+ const=const,
+ default=default,
+ required=required,
+ help=help,
+ metavar=metavar)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ items = _copy.copy(_ensure_value(namespace, self.dest, []))
+ items.append(self.const)
+ setattr(namespace, self.dest, items)
+
+
+class _CountAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ default=None,
+ required=False,
+ help=None):
+ super(_CountAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=0,
+ default=default,
+ required=required,
+ help=help)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ new_count = _ensure_value(namespace, self.dest, 0) + 1
+ setattr(namespace, self.dest, new_count)
+
+
+class _HelpAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest=SUPPRESS,
+ default=SUPPRESS,
+ help=None):
+ super(_HelpAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ default=default,
+ nargs=0,
+ help=help)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ parser.print_help()
+ parser.exit()
+
+
+class _VersionAction(Action):
+
+ def __init__(self,
+ option_strings,
+ version=None,
+ dest=SUPPRESS,
+ default=SUPPRESS,
+ help="show program's version number and exit"):
+ super(_VersionAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ default=default,
+ nargs=0,
+ help=help)
+ self.version = version
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ version = self.version
+ if version is None:
+ version = parser.version
+ formatter = parser._get_formatter()
+ formatter.add_text(version)
+ parser.exit(message=formatter.format_help())
+
+
+class _SubParsersAction(Action):
+
+ class _ChoicesPseudoAction(Action):
+
+ def __init__(self, name, aliases, help):
+ metavar = dest = name
+ if aliases:
+ metavar += ' (%s)' % ', '.join(aliases)
+ sup = super(_SubParsersAction._ChoicesPseudoAction, self)
+ sup.__init__(option_strings=[], dest=dest, help=help,
+ metavar=metavar)
+
+ def __init__(self,
+ option_strings,
+ prog,
+ parser_class,
+ dest=SUPPRESS,
+ help=None,
+ metavar=None):
+
+ self._prog_prefix = prog
+ self._parser_class = parser_class
+ self._name_parser_map = {}
+ self._choices_actions = []
+
+ super(_SubParsersAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=PARSER,
+ choices=self._name_parser_map,
+ help=help,
+ metavar=metavar)
+
+ def add_parser(self, name, **kwargs):
+ # set prog from the existing prefix
+ if kwargs.get('prog') is None:
+ kwargs['prog'] = '%s %s' % (self._prog_prefix, name)
+
+ aliases = kwargs.pop('aliases', ())
+
+ # create a pseudo-action to hold the choice help
+ if 'help' in kwargs:
+ help = kwargs.pop('help')
+ choice_action = self._ChoicesPseudoAction(name, aliases, help)
+ self._choices_actions.append(choice_action)
+
+ # create the parser and add it to the map
+ parser = self._parser_class(**kwargs)
+ self._name_parser_map[name] = parser
+
+ # make parser available under aliases also
+ for alias in aliases:
+ self._name_parser_map[alias] = parser
+
+ return parser
+
+ def _get_subactions(self):
+ return self._choices_actions
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ parser_name = values[0]
+ arg_strings = values[1:]
+
+ # set the parser name if requested
+ if self.dest is not SUPPRESS:
+ setattr(namespace, self.dest, parser_name)
+
+ # select the parser
+ try:
+ parser = self._name_parser_map[parser_name]
+ except KeyError:
+ tup = parser_name, ', '.join(self._name_parser_map)
+ msg = _('unknown parser %r (choices: %s)' % tup)
+ raise ArgumentError(self, msg)
+
+ # parse all the remaining options into the namespace
+ # store any unrecognized options on the object, so that the top
+ # level parser can decide what to do with them
+ namespace, arg_strings = parser.parse_known_args(arg_strings, namespace)
+ if arg_strings:
+ vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
+ getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
+
+
+# ==============
+# Type classes
+# ==============
+
+class FileType(object):
+ """Factory for creating file object types
+
+ Instances of FileType are typically passed as type= arguments to the
+ ArgumentParser add_argument() method.
+
+ Keyword Arguments:
+ - mode -- A string indicating how the file is to be opened. Accepts the
+ same values as the builtin open() function.
+ - bufsize -- The file's desired buffer size. Accepts the same values as
+ the builtin open() function.
+ """
+
+ def __init__(self, mode='r', bufsize=None):
+ self._mode = mode
+ self._bufsize = bufsize
+
+ def __call__(self, string):
+ # the special argument "-" means sys.std{in,out}
+ if string == '-':
+ if 'r' in self._mode:
+ return _sys.stdin
+ elif 'w' in self._mode:
+ return _sys.stdout
+ else:
+ msg = _('argument "-" with mode %r' % self._mode)
+ raise ValueError(msg)
+
+ try:
+ # all other arguments are used as file names
+ if self._bufsize:
+ return open(string, self._mode, self._bufsize)
+ else:
+ return open(string, self._mode)
+ except IOError:
+ err = _sys.exc_info()[1]
+ message = _("can't open '%s': %s")
+ raise ArgumentTypeError(message % (string, err))
+
+ def __repr__(self):
+ args = [self._mode, self._bufsize]
+ args_str = ', '.join([repr(arg) for arg in args if arg is not None])
+ return '%s(%s)' % (type(self).__name__, args_str)
+
+# ===========================
+# Optional and Positional Parsing
+# ===========================
+
+class Namespace(_AttributeHolder):
+ """Simple object for storing attributes.
+
+ Implements equality by attribute names and values, and provides a simple
+ string representation.
+ """
+
+ def __init__(self, **kwargs):
+ for name in kwargs:
+ setattr(self, name, kwargs[name])
+
+ __hash__ = None
+
+ def __eq__(self, other):
+ return vars(self) == vars(other)
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def __contains__(self, key):
+ return key in self.__dict__
+
+
+class _ActionsContainer(object):
+
+ def __init__(self,
+ description,
+ prefix_chars,
+ argument_default,
+ conflict_handler):
+ super(_ActionsContainer, self).__init__()
+
+ self.description = description
+ self.argument_default = argument_default
+ self.prefix_chars = prefix_chars
+ self.conflict_handler = conflict_handler
+
+ # set up registries
+ self._registries = {}
+
+ # register actions
+ self.register('action', None, _StoreAction)
+ self.register('action', 'store', _StoreAction)
+ self.register('action', 'store_const', _StoreConstAction)
+ self.register('action', 'store_true', _StoreTrueAction)
+ self.register('action', 'store_false', _StoreFalseAction)
+ self.register('action', 'append', _AppendAction)
+ self.register('action', 'append_const', _AppendConstAction)
+ self.register('action', 'count', _CountAction)
+ self.register('action', 'help', _HelpAction)
+ self.register('action', 'version', _VersionAction)
+ self.register('action', 'parsers', _SubParsersAction)
+
+ # raise an exception if the conflict handler is invalid
+ self._get_handler()
+
+ # action storage
+ self._actions = []
+ self._option_string_actions = {}
+
+ # groups
+ self._action_groups = []
+ self._mutually_exclusive_groups = []
+
+ # defaults storage
+ self._defaults = {}
+
+ # determines whether an "option" looks like a negative number
+ self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$')
+
+ # whether or not there are any optionals that look like negative
+ # numbers -- uses a list so it can be shared and edited
+ self._has_negative_number_optionals = []
+
+ # ====================
+ # Registration methods
+ # ====================
+ def register(self, registry_name, value, object):
+ registry = self._registries.setdefault(registry_name, {})
+ registry[value] = object
+
+ def _registry_get(self, registry_name, value, default=None):
+ return self._registries[registry_name].get(value, default)
+
+ # ==================================
+ # Namespace default accessor methods
+ # ==================================
+ def set_defaults(self, **kwargs):
+ self._defaults.update(kwargs)
+
+ # if these defaults match any existing arguments, replace
+ # the previous default on the object with the new one
+ for action in self._actions:
+ if action.dest in kwargs:
+ action.default = kwargs[action.dest]
+
+ def get_default(self, dest):
+ for action in self._actions:
+ if action.dest == dest and action.default is not None:
+ return action.default
+ return self._defaults.get(dest, None)
+
+
+ # =======================
+ # Adding argument actions
+ # =======================
+ def add_argument(self, *args, **kwargs):
+ """
+ add_argument(dest, ..., name=value, ...)
+ add_argument(option_string, option_string, ..., name=value, ...)
+ """
+
+ # if no positional args are supplied or only one is supplied and
+ # it doesn't look like an option string, parse a positional
+ # argument
+ chars = self.prefix_chars
+ if not args or len(args) == 1 and args[0][0] not in chars:
+ if args and 'dest' in kwargs:
+ raise ValueError('dest supplied twice for positional argument')
+ kwargs = self._get_positional_kwargs(*args, **kwargs)
+
+ # otherwise, we're adding an optional argument
+ else:
+ kwargs = self._get_optional_kwargs(*args, **kwargs)
+
+ # if no default was supplied, use the parser-level default
+ if 'default' not in kwargs:
+ dest = kwargs['dest']
+ if dest in self._defaults:
+ kwargs['default'] = self._defaults[dest]
+ elif self.argument_default is not None:
+ kwargs['default'] = self.argument_default
+
+ # create the action object, and add it to the parser
+ action_class = self._pop_action_class(kwargs)
+ if not _callable(action_class):
+ raise ValueError('unknown action "%s"' % action_class)
+ action = action_class(**kwargs)
+
+ # raise an error if the action type is not callable
+ type_func = self._registry_get('type', action.type, action.type)
+ if not _callable(type_func):
+ raise ValueError('%r is not callable' % type_func)
+
+ return self._add_action(action)
+
+ def add_argument_group(self, *args, **kwargs):
+ group = _ArgumentGroup(self, *args, **kwargs)
+ self._action_groups.append(group)
+ return group
+
+ def add_mutually_exclusive_group(self, **kwargs):
+ group = _MutuallyExclusiveGroup(self, **kwargs)
+ self._mutually_exclusive_groups.append(group)
+ return group
+
+ def _add_action(self, action):
+ # resolve any conflicts
+ self._check_conflict(action)
+
+ # add to actions list
+ self._actions.append(action)
+ action.container = self
+
+ # index the action by any option strings it has
+ for option_string in action.option_strings:
+ self._option_string_actions[option_string] = action
+
+ # set the flag if any option strings look like negative numbers
+ for option_string in action.option_strings:
+ if self._negative_number_matcher.match(option_string):
+ if not self._has_negative_number_optionals:
+ self._has_negative_number_optionals.append(True)
+
+ # return the created action
+ return action
+
+ def _remove_action(self, action):
+ self._actions.remove(action)
+
+ def _add_container_actions(self, container):
+ # collect groups by titles
+ title_group_map = {}
+ for group in self._action_groups:
+ if group.title in title_group_map:
+ msg = _('cannot merge actions - two groups are named %r')
+ raise ValueError(msg % (group.title))
+ title_group_map[group.title] = group
+
+ # map each action to its group
+ group_map = {}
+ for group in container._action_groups:
+
+ # if a group with the title exists, use that, otherwise
+ # create a new group matching the container's group
+ if group.title not in title_group_map:
+ title_group_map[group.title] = self.add_argument_group(
+ title=group.title,
+ description=group.description,
+ conflict_handler=group.conflict_handler)
+
+ # map the actions to their new group
+ for action in group._group_actions:
+ group_map[action] = title_group_map[group.title]
+
+ # add container's mutually exclusive groups
+ # NOTE: if add_mutually_exclusive_group ever gains title= and
+ # description= then this code will need to be expanded as above
+ for group in container._mutually_exclusive_groups:
+ mutex_group = self.add_mutually_exclusive_group(
+ required=group.required)
+
+ # map the actions to their new mutex group
+ for action in group._group_actions:
+ group_map[action] = mutex_group
+
+ # add all actions to this container or their group
+ for action in container._actions:
+ group_map.get(action, self)._add_action(action)
+
+ def _get_positional_kwargs(self, dest, **kwargs):
+ # make sure required is not specified
+ if 'required' in kwargs:
+ msg = _("'required' is an invalid argument for positionals")
+ raise TypeError(msg)
+
+ # mark positional arguments as required if at least one is
+ # always required
+ if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
+ kwargs['required'] = True
+ if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
+ kwargs['required'] = True
+
+ # return the keyword arguments with no option strings
+ return dict(kwargs, dest=dest, option_strings=[])
+
+ def _get_optional_kwargs(self, *args, **kwargs):
+ # determine short and long option strings
+ option_strings = []
+ long_option_strings = []
+ for option_string in args:
+ # error on strings that don't start with an appropriate prefix
+ if not option_string[0] in self.prefix_chars:
+ msg = _('invalid option string %r: '
+ 'must start with a character %r')
+ tup = option_string, self.prefix_chars
+ raise ValueError(msg % tup)
+
+ # strings starting with two prefix characters are long options
+ option_strings.append(option_string)
+ if option_string[0] in self.prefix_chars:
+ if len(option_string) > 1:
+ if option_string[1] in self.prefix_chars:
+ long_option_strings.append(option_string)
+
+ # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
+ dest = kwargs.pop('dest', None)
+ if dest is None:
+ if long_option_strings:
+ dest_option_string = long_option_strings[0]
+ else:
+ dest_option_string = option_strings[0]
+ dest = dest_option_string.lstrip(self.prefix_chars)
+ if not dest:
+ msg = _('dest= is required for options like %r')
+ raise ValueError(msg % option_string)
+ dest = dest.replace('-', '_')
+
+ # return the updated keyword arguments
+ return dict(kwargs, dest=dest, option_strings=option_strings)
+
+ def _pop_action_class(self, kwargs, default=None):
+ action = kwargs.pop('action', default)
+ return self._registry_get('action', action, action)
+
+ def _get_handler(self):
+ # determine function from conflict handler string
+ handler_func_name = '_handle_conflict_%s' % self.conflict_handler
+ try:
+ return getattr(self, handler_func_name)
+ except AttributeError:
+ msg = _('invalid conflict_resolution value: %r')
+ raise ValueError(msg % self.conflict_handler)
+
+ def _check_conflict(self, action):
+
+ # find all options that conflict with this option
+ confl_optionals = []
+ for option_string in action.option_strings:
+ if option_string in self._option_string_actions:
+ confl_optional = self._option_string_actions[option_string]
+ confl_optionals.append((option_string, confl_optional))
+
+ # resolve any conflicts
+ if confl_optionals:
+ conflict_handler = self._get_handler()
+ conflict_handler(action, confl_optionals)
+
+ def _handle_conflict_error(self, action, conflicting_actions):
+ message = _('conflicting option string(s): %s')
+ conflict_string = ', '.join([option_string
+ for option_string, action
+ in conflicting_actions])
+ raise ArgumentError(action, message % conflict_string)
+
+ def _handle_conflict_resolve(self, action, conflicting_actions):
+
+ # remove all conflicting options
+ for option_string, action in conflicting_actions:
+
+ # remove the conflicting option
+ action.option_strings.remove(option_string)
+ self._option_string_actions.pop(option_string, None)
+
+ # if the option now has no option string, remove it from the
+ # container holding it
+ if not action.option_strings:
+ action.container._remove_action(action)
+
+
+class _ArgumentGroup(_ActionsContainer):
+
+ def __init__(self, container, title=None, description=None, **kwargs):
+ # add any missing keyword arguments by checking the container
+ update = kwargs.setdefault
+ update('conflict_handler', container.conflict_handler)
+ update('prefix_chars', container.prefix_chars)
+ update('argument_default', container.argument_default)
+ super_init = super(_ArgumentGroup, self).__init__
+ super_init(description=description, **kwargs)
+
+ # group attributes
+ self.title = title
+ self._group_actions = []
+
+ # share most attributes with the container
+ self._registries = container._registries
+ self._actions = container._actions
+ self._option_string_actions = container._option_string_actions
+ self._defaults = container._defaults
+ self._has_negative_number_optionals = \
+ container._has_negative_number_optionals
+
+ def _add_action(self, action):
+ action = super(_ArgumentGroup, self)._add_action(action)
+ self._group_actions.append(action)
+ return action
+
+ def _remove_action(self, action):
+ super(_ArgumentGroup, self)._remove_action(action)
+ self._group_actions.remove(action)
+
+
+class _MutuallyExclusiveGroup(_ArgumentGroup):
+
+ def __init__(self, container, required=False):
+ super(_MutuallyExclusiveGroup, self).__init__(container)
+ self.required = required
+ self._container = container
+
+ def _add_action(self, action):
+ if action.required:
+ msg = _('mutually exclusive arguments must be optional')
+ raise ValueError(msg)
+ action = self._container._add_action(action)
+ self._group_actions.append(action)
+ return action
+
+ def _remove_action(self, action):
+ self._container._remove_action(action)
+ self._group_actions.remove(action)
+
+
+class ArgumentParser(_AttributeHolder, _ActionsContainer):
+ """Object for parsing command line strings into Python objects.
+
+ Keyword Arguments:
+ - prog -- The name of the program (default: sys.argv[0])
+ - usage -- A usage message (default: auto-generated from arguments)
+ - description -- A description of what the program does
+ - epilog -- Text following the argument descriptions
+ - parents -- Parsers whose arguments should be copied into this one
+ - formatter_class -- HelpFormatter class for printing help messages
+ - prefix_chars -- Characters that prefix optional arguments
+ - fromfile_prefix_chars -- Characters that prefix files containing
+ additional arguments
+ - argument_default -- The default value for all arguments
+ - conflict_handler -- String indicating how to handle conflicts
+ - add_help -- Add a -h/-help option
+ """
+
+ def __init__(self,
+ prog=None,
+ usage=None,
+ description=None,
+ epilog=None,
+ version=None,
+ parents=[],
+ formatter_class=HelpFormatter,
+ prefix_chars='-',
+ fromfile_prefix_chars=None,
+ argument_default=None,
+ conflict_handler='error',
+ add_help=True):
+
+ if version is not None:
+ import warnings
+ warnings.warn(
+ """The "version" argument to ArgumentParser is deprecated. """
+ """Please use """
+ """"add_argument(..., action='version', version="N", ...)" """
+ """instead""", DeprecationWarning)
+
+ superinit = super(ArgumentParser, self).__init__
+ superinit(description=description,
+ prefix_chars=prefix_chars,
+ argument_default=argument_default,
+ conflict_handler=conflict_handler)
+
+ # default setting for prog
+ if prog is None:
+ prog = _os.path.basename(_sys.argv[0])
+
+ self.prog = prog
+ self.usage = usage
+ self.epilog = epilog
+ self.version = version
+ self.formatter_class = formatter_class
+ self.fromfile_prefix_chars = fromfile_prefix_chars
+ self.add_help = add_help
+
+ add_group = self.add_argument_group
+ self._positionals = add_group(_('positional arguments'))
+ self._optionals = add_group(_('optional arguments'))
+ self._subparsers = None
+
+ # register types
+ def identity(string):
+ return string
+ self.register('type', None, identity)
+
+ # add help and version arguments if necessary
+ # (using explicit default to override global argument_default)
+ if '-' in prefix_chars:
+ default_prefix = '-'
+ else:
+ default_prefix = prefix_chars[0]
+ if self.add_help:
+ self.add_argument(
+ default_prefix+'h', default_prefix*2+'help',
+ action='help', default=SUPPRESS,
+ help=_('show this help message and exit'))
+ if self.version:
+ self.add_argument(
+ default_prefix+'v', default_prefix*2+'version',
+ action='version', default=SUPPRESS,
+ version=self.version,
+ help=_("show program's version number and exit"))
+
+ # add parent arguments and defaults
+ for parent in parents:
+ self._add_container_actions(parent)
+ try:
+ defaults = parent._defaults
+ except AttributeError:
+ pass
+ else:
+ self._defaults.update(defaults)
+
+ # =======================
+ # Pretty __repr__ methods
+ # =======================
+ def _get_kwargs(self):
+ names = [
+ 'prog',
+ 'usage',
+ 'description',
+ 'version',
+ 'formatter_class',
+ 'conflict_handler',
+ 'add_help',
+ ]
+ return [(name, getattr(self, name)) for name in names]
+
+ # ==================================
+ # Optional/Positional adding methods
+ # ==================================
+ def add_subparsers(self, **kwargs):
+ if self._subparsers is not None:
+ self.error(_('cannot have multiple subparser arguments'))
+
+ # add the parser class to the arguments if it's not present
+ kwargs.setdefault('parser_class', type(self))
+
+ if 'title' in kwargs or 'description' in kwargs:
+ title = _(kwargs.pop('title', 'subcommands'))
+ description = _(kwargs.pop('description', None))
+ self._subparsers = self.add_argument_group(title, description)
+ else:
+ self._subparsers = self._positionals
+
+ # prog defaults to the usage message of this parser, skipping
+ # optional arguments and with no "usage:" prefix
+ if kwargs.get('prog') is None:
+ formatter = self._get_formatter()
+ positionals = self._get_positional_actions()
+ groups = self._mutually_exclusive_groups
+ formatter.add_usage(self.usage, positionals, groups, '')
+ kwargs['prog'] = formatter.format_help().strip()
+
+ # create the parsers action and add it to the positionals list
+ parsers_class = self._pop_action_class(kwargs, 'parsers')
+ action = parsers_class(option_strings=[], **kwargs)
+ self._subparsers._add_action(action)
+
+ # return the created parsers action
+ return action
+
+ def _add_action(self, action):
+ if action.option_strings:
+ self._optionals._add_action(action)
+ else:
+ self._positionals._add_action(action)
+ return action
+
+ def _get_optional_actions(self):
+ return [action
+ for action in self._actions
+ if action.option_strings]
+
+ def _get_positional_actions(self):
+ return [action
+ for action in self._actions
+ if not action.option_strings]
+
+ # =====================================
+ # Command line argument parsing methods
+ # =====================================
+ def parse_args(self, args=None, namespace=None):
+ args, argv = self.parse_known_args(args, namespace)
+ if argv:
+ msg = _('unrecognized arguments: %s')
+ self.error(msg % ' '.join(argv))
+ return args
+
+ def parse_known_args(self, args=None, namespace=None):
+ # args default to the system args
+ if args is None:
+ args = _sys.argv[1:]
+
+ # default Namespace built from parser defaults
+ if namespace is None:
+ namespace = Namespace()
+
+ # add any action defaults that aren't present
+ for action in self._actions:
+ if action.dest is not SUPPRESS:
+ if not hasattr(namespace, action.dest):
+ if action.default is not SUPPRESS:
+ setattr(namespace, action.dest, action.default)
+
+ # add any parser defaults that aren't present
+ for dest in self._defaults:
+ if not hasattr(namespace, dest):
+ setattr(namespace, dest, self._defaults[dest])
+
+ # parse the arguments and exit if there are any errors
+ try:
+ namespace, args = self._parse_known_args(args, namespace)
+ if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
+ args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
+ delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
+ return namespace, args
+ except ArgumentError:
+ err = _sys.exc_info()[1]
+ self.error(str(err))
+
+ def _parse_known_args(self, arg_strings, namespace):
+ # replace arg strings that are file references
+ if self.fromfile_prefix_chars is not None:
+ arg_strings = self._read_args_from_files(arg_strings)
+
+ # map all mutually exclusive arguments to the other arguments
+ # they can't occur with
+ action_conflicts = {}
+ for mutex_group in self._mutually_exclusive_groups:
+ group_actions = mutex_group._group_actions
+ for i, mutex_action in enumerate(mutex_group._group_actions):
+ conflicts = action_conflicts.setdefault(mutex_action, [])
+ conflicts.extend(group_actions[:i])
+ conflicts.extend(group_actions[i + 1:])
+
+ # find all option indices, and determine the arg_string_pattern
+ # which has an 'O' if there is an option at an index,
+ # an 'A' if there is an argument, or a '-' if there is a '--'
+ option_string_indices = {}
+ arg_string_pattern_parts = []
+ arg_strings_iter = iter(arg_strings)
+ for i, arg_string in enumerate(arg_strings_iter):
+
+ # all args after -- are non-options
+ if arg_string == '--':
+ arg_string_pattern_parts.append('-')
+ for arg_string in arg_strings_iter:
+ arg_string_pattern_parts.append('A')
+
+ # otherwise, add the arg to the arg strings
+ # and note the index if it was an option
+ else:
+ option_tuple = self._parse_optional(arg_string)
+ if option_tuple is None:
+ pattern = 'A'
+ else:
+ option_string_indices[i] = option_tuple
+ pattern = 'O'
+ arg_string_pattern_parts.append(pattern)
+
+ # join the pieces together to form the pattern
+ arg_strings_pattern = ''.join(arg_string_pattern_parts)
+
+ # converts arg strings to the appropriate and then takes the action
+ seen_actions = set()
+ seen_non_default_actions = set()
+
+ def take_action(action, argument_strings, option_string=None):
+ seen_actions.add(action)
+ argument_values = self._get_values(action, argument_strings)
+
+ # error if this argument is not allowed with other previously
+ # seen arguments, assuming that actions that use the default
+ # value don't really count as "present"
+ if argument_values is not action.default:
+ seen_non_default_actions.add(action)
+ for conflict_action in action_conflicts.get(action, []):
+ if conflict_action in seen_non_default_actions:
+ msg = _('not allowed with argument %s')
+ action_name = _get_action_name(conflict_action)
+ raise ArgumentError(action, msg % action_name)
+
+ # take the action if we didn't receive a SUPPRESS value
+ # (e.g. from a default)
+ if argument_values is not SUPPRESS:
+ action(self, namespace, argument_values, option_string)
+
+ # function to convert arg_strings into an optional action
+ def consume_optional(start_index):
+
+ # get the optional identified at this index
+ option_tuple = option_string_indices[start_index]
+ action, option_string, explicit_arg = option_tuple
+
+ # identify additional optionals in the same arg string
+ # (e.g. -xyz is the same as -x -y -z if no args are required)
+ match_argument = self._match_argument
+ action_tuples = []
+ while True:
+
+ # if we found no optional action, skip it
+ if action is None:
+ extras.append(arg_strings[start_index])
+ return start_index + 1
+
+ # if there is an explicit argument, try to match the
+ # optional's string arguments to only this
+ if explicit_arg is not None:
+ arg_count = match_argument(action, 'A')
+
+ # if the action is a single-dash option and takes no
+ # arguments, try to parse more single-dash options out
+ # of the tail of the option string
+ chars = self.prefix_chars
+ if arg_count == 0 and option_string[1] not in chars:
+ action_tuples.append((action, [], option_string))
+ char = option_string[0]
+ option_string = char + explicit_arg[0]
+ new_explicit_arg = explicit_arg[1:] or None
+ optionals_map = self._option_string_actions
+ if option_string in optionals_map:
+ action = optionals_map[option_string]
+ explicit_arg = new_explicit_arg
+ else:
+ msg = _('ignored explicit argument %r')
+ raise ArgumentError(action, msg % explicit_arg)
+
+ # if the action expect exactly one argument, we've
+ # successfully matched the option; exit the loop
+ elif arg_count == 1:
+ stop = start_index + 1
+ args = [explicit_arg]
+ action_tuples.append((action, args, option_string))
+ break
+
+ # error if a double-dash option did not use the
+ # explicit argument
+ else:
+ msg = _('ignored explicit argument %r')
+ raise ArgumentError(action, msg % explicit_arg)
+
+ # if there is no explicit argument, try to match the
+ # optional's string arguments with the following strings
+ # if successful, exit the loop
+ else:
+ start = start_index + 1
+ selected_patterns = arg_strings_pattern[start:]
+ arg_count = match_argument(action, selected_patterns)
+ stop = start + arg_count
+ args = arg_strings[start:stop]
+ action_tuples.append((action, args, option_string))
+ break
+
+ # add the Optional to the list and return the index at which
+ # the Optional's string args stopped
+ assert action_tuples
+ for action, args, option_string in action_tuples:
+ take_action(action, args, option_string)
+ return stop
+
+ # the list of Positionals left to be parsed; this is modified
+ # by consume_positionals()
+ positionals = self._get_positional_actions()
+
+ # function to convert arg_strings into positional actions
+ def consume_positionals(start_index):
+ # match as many Positionals as possible
+ match_partial = self._match_arguments_partial
+ selected_pattern = arg_strings_pattern[start_index:]
+ arg_counts = match_partial(positionals, selected_pattern)
+
+ # slice off the appropriate arg strings for each Positional
+ # and add the Positional and its args to the list
+ for action, arg_count in zip(positionals, arg_counts):
+ args = arg_strings[start_index: start_index + arg_count]
+ start_index += arg_count
+ take_action(action, args)
+
+ # slice off the Positionals that we just parsed and return the
+ # index at which the Positionals' string args stopped
+ positionals[:] = positionals[len(arg_counts):]
+ return start_index
+
+ # consume Positionals and Optionals alternately, until we have
+ # passed the last option string
+ extras = []
+ start_index = 0
+ if option_string_indices:
+ max_option_string_index = max(option_string_indices)
+ else:
+ max_option_string_index = -1
+ while start_index <= max_option_string_index:
+
+ # consume any Positionals preceding the next option
+ next_option_string_index = min([
+ index
+ for index in option_string_indices
+ if index >= start_index])
+ if start_index != next_option_string_index:
+ positionals_end_index = consume_positionals(start_index)
+
+ # only try to parse the next optional if we didn't consume
+ # the option string during the positionals parsing
+ if positionals_end_index > start_index:
+ start_index = positionals_end_index
+ continue
+ else:
+ start_index = positionals_end_index
+
+ # if we consumed all the positionals we could and we're not
+ # at the index of an option string, there were extra arguments
+ if start_index not in option_string_indices:
+ strings = arg_strings[start_index:next_option_string_index]
+ extras.extend(strings)
+ start_index = next_option_string_index
+
+ # consume the next optional and any arguments for it
+ start_index = consume_optional(start_index)
+
+ # consume any positionals following the last Optional
+ stop_index = consume_positionals(start_index)
+
+ # if we didn't consume all the argument strings, there were extras
+ extras.extend(arg_strings[stop_index:])
+
+ # if we didn't use all the Positional objects, there were too few
+ # arg strings supplied.
+ if positionals:
+ self.error(_('too few arguments'))
+
+ # make sure all required actions were present, and convert defaults.
+ for action in self._actions:
+ if action not in seen_actions:
+ if action.required:
+ name = _get_action_name(action)
+ self.error(_('argument %s is required') % name)
+ else:
+ # Convert action default now instead of doing it before
+ # parsing arguments to avoid calling convert functions
+ # twice (which may fail) if the argument was given, but
+ # only if it was defined already in the namespace
+ if (action.default is not None and
+ isinstance(action.default, basestring) and
+ hasattr(namespace, action.dest) and
+ action.default is getattr(namespace, action.dest)):
+ setattr(namespace, action.dest,
+ self._get_value(action, action.default))
+
+ # make sure all required groups had one option present
+ for group in self._mutually_exclusive_groups:
+ if group.required:
+ for action in group._group_actions:
+ if action in seen_non_default_actions:
+ break
+
+ # if no actions were used, report the error
+ else:
+ names = [_get_action_name(action)
+ for action in group._group_actions
+ if action.help is not SUPPRESS]
+ msg = _('one of the arguments %s is required')
+ self.error(msg % ' '.join(names))
+
+ # return the updated namespace and the extra arguments
+ return namespace, extras
+
+ def _read_args_from_files(self, arg_strings):
+ # expand arguments referencing files
+ new_arg_strings = []
+ for arg_string in arg_strings:
+
+ # for regular arguments, just add them back into the list
+ if arg_string[0] not in self.fromfile_prefix_chars:
+ new_arg_strings.append(arg_string)
+
+ # replace arguments referencing files with the file content
+ else:
+ try:
+ args_file = open(arg_string[1:])
+ try:
+ arg_strings = []
+ for arg_line in args_file.read().splitlines():
+ for arg in self.convert_arg_line_to_args(arg_line):
+ arg_strings.append(arg)
+ arg_strings = self._read_args_from_files(arg_strings)
+ new_arg_strings.extend(arg_strings)
+ finally:
+ args_file.close()
+ except IOError:
+ err = _sys.exc_info()[1]
+ self.error(str(err))
+
+ # return the modified argument list
+ return new_arg_strings
+
+ def convert_arg_line_to_args(self, arg_line):
+ return [arg_line]
+
+ def _match_argument(self, action, arg_strings_pattern):
+ # match the pattern for this action to the arg strings
+ nargs_pattern = self._get_nargs_pattern(action)
+ match = _re.match(nargs_pattern, arg_strings_pattern)
+
+ # raise an exception if we weren't able to find a match
+ if match is None:
+ nargs_errors = {
+ None: _('expected one argument'),
+ OPTIONAL: _('expected at most one argument'),
+ ONE_OR_MORE: _('expected at least one argument'),
+ }
+ default = _('expected %s argument(s)') % action.nargs
+ msg = nargs_errors.get(action.nargs, default)
+ raise ArgumentError(action, msg)
+
+ # return the number of arguments matched
+ return len(match.group(1))
+
+ def _match_arguments_partial(self, actions, arg_strings_pattern):
+ # progressively shorten the actions list by slicing off the
+ # final actions until we find a match
+ result = []
+ for i in range(len(actions), 0, -1):
+ actions_slice = actions[:i]
+ pattern = ''.join([self._get_nargs_pattern(action)
+ for action in actions_slice])
+ match = _re.match(pattern, arg_strings_pattern)
+ if match is not None:
+ result.extend([len(string) for string in match.groups()])
+ break
+
+ # return the list of arg string counts
+ return result
+
+ def _parse_optional(self, arg_string):
+ # if it's an empty string, it was meant to be a positional
+ if not arg_string:
+ return None
+
+ # if it doesn't start with a prefix, it was meant to be positional
+ if not arg_string[0] in self.prefix_chars:
+ return None
+
+ # if the option string is present in the parser, return the action
+ if arg_string in self._option_string_actions:
+ action = self._option_string_actions[arg_string]
+ return action, arg_string, None
+
+ # if it's just a single character, it was meant to be positional
+ if len(arg_string) == 1:
+ return None
+
+ # if the option string before the "=" is present, return the action
+ if '=' in arg_string:
+ option_string, explicit_arg = arg_string.split('=', 1)
+ if option_string in self._option_string_actions:
+ action = self._option_string_actions[option_string]
+ return action, option_string, explicit_arg
+
+ # search through all possible prefixes of the option string
+ # and all actions in the parser for possible interpretations
+ option_tuples = self._get_option_tuples(arg_string)
+
+ # if multiple actions match, the option string was ambiguous
+ if len(option_tuples) > 1:
+ options = ', '.join([option_string
+ for action, option_string, explicit_arg in option_tuples])
+ tup = arg_string, options
+ self.error(_('ambiguous option: %s could match %s') % tup)
+
+ # if exactly one action matched, this segmentation is good,
+ # so return the parsed action
+ elif len(option_tuples) == 1:
+ option_tuple, = option_tuples
+ return option_tuple
+
+ # if it was not found as an option, but it looks like a negative
+ # number, it was meant to be positional
+ # unless there are negative-number-like options
+ if self._negative_number_matcher.match(arg_string):
+ if not self._has_negative_number_optionals:
+ return None
+
+ # if it contains a space, it was meant to be a positional
+ if ' ' in arg_string:
+ return None
+
+ # it was meant to be an optional but there is no such option
+ # in this parser (though it might be a valid option in a subparser)
+ return None, arg_string, None
+
+ def _get_option_tuples(self, option_string):
+ result = []
+
+ # option strings starting with two prefix characters are only
+ # split at the '='
+ chars = self.prefix_chars
+ if option_string[0] in chars and option_string[1] in chars:
+ if '=' in option_string:
+ option_prefix, explicit_arg = option_string.split('=', 1)
+ else:
+ option_prefix = option_string
+ explicit_arg = None
+ for option_string in self._option_string_actions:
+ if option_string.startswith(option_prefix):
+ action = self._option_string_actions[option_string]
+ tup = action, option_string, explicit_arg
+ result.append(tup)
+
+ # single character options can be concatenated with their arguments
+ # but multiple character options always have to have their argument
+ # separate
+ elif option_string[0] in chars and option_string[1] not in chars:
+ option_prefix = option_string
+ explicit_arg = None
+ short_option_prefix = option_string[:2]
+ short_explicit_arg = option_string[2:]
+
+ for option_string in self._option_string_actions:
+ if option_string == short_option_prefix:
+ action = self._option_string_actions[option_string]
+ tup = action, option_string, short_explicit_arg
+ result.append(tup)
+ elif option_string.startswith(option_prefix):
+ action = self._option_string_actions[option_string]
+ tup = action, option_string, explicit_arg
+ result.append(tup)
+
+ # shouldn't ever get here
+ else:
+ self.error(_('unexpected option string: %s') % option_string)
+
+ # return the collected option tuples
+ return result
+
+ def _get_nargs_pattern(self, action):
+ # in all examples below, we have to allow for '--' args
+ # which are represented as '-' in the pattern
+ nargs = action.nargs
+
+ # the default (None) is assumed to be a single argument
+ if nargs is None:
+ nargs_pattern = '(-*A-*)'
+
+ # allow zero or one arguments
+ elif nargs == OPTIONAL:
+ nargs_pattern = '(-*A?-*)'
+
+ # allow zero or more arguments
+ elif nargs == ZERO_OR_MORE:
+ nargs_pattern = '(-*[A-]*)'
+
+ # allow one or more arguments
+ elif nargs == ONE_OR_MORE:
+ nargs_pattern = '(-*A[A-]*)'
+
+ # allow any number of options or arguments
+ elif nargs == REMAINDER:
+ nargs_pattern = '([-AO]*)'
+
+ # allow one argument followed by any number of options or arguments
+ elif nargs == PARSER:
+ nargs_pattern = '(-*A[-AO]*)'
+
+ # all others should be integers
+ else:
+ nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs)
+
+ # if this is an optional action, -- is not allowed
+ if action.option_strings:
+ nargs_pattern = nargs_pattern.replace('-*', '')
+ nargs_pattern = nargs_pattern.replace('-', '')
+
+ # return the pattern
+ return nargs_pattern
+
+ # ========================
+ # Value conversion methods
+ # ========================
+ def _get_values(self, action, arg_strings):
+ # for everything but PARSER args, strip out '--'
+ if action.nargs not in [PARSER, REMAINDER]:
+ arg_strings = [s for s in arg_strings if s != '--']
+
+ # optional argument produces a default when not present
+ if not arg_strings and action.nargs == OPTIONAL:
+ if action.option_strings:
+ value = action.const
+ else:
+ value = action.default
+ if isinstance(value, basestring):
+ value = self._get_value(action, value)
+ self._check_value(action, value)
+
+ # when nargs='*' on a positional, if there were no command-line
+ # args, use the default if it is anything other than None
+ elif (not arg_strings and action.nargs == ZERO_OR_MORE and
+ not action.option_strings):
+ if action.default is not None:
+ value = action.default
+ else:
+ value = arg_strings
+ self._check_value(action, value)
+
+ # single argument or optional argument produces a single value
+ elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
+ arg_string, = arg_strings
+ value = self._get_value(action, arg_string)
+ self._check_value(action, value)
+
+ # REMAINDER arguments convert all values, checking none
+ elif action.nargs == REMAINDER:
+ value = [self._get_value(action, v) for v in arg_strings]
+
+ # PARSER arguments convert all values, but check only the first
+ elif action.nargs == PARSER:
+ value = [self._get_value(action, v) for v in arg_strings]
+ self._check_value(action, value[0])
+
+ # all other types of nargs produce a list
+ else:
+ value = [self._get_value(action, v) for v in arg_strings]
+ for v in value:
+ self._check_value(action, v)
+
+ # return the converted value
+ return value
+
+ def _get_value(self, action, arg_string):
+ type_func = self._registry_get('type', action.type, action.type)
+ if not _callable(type_func):
+ msg = _('%r is not callable')
+ raise ArgumentError(action, msg % type_func)
+
+ # convert the value to the appropriate type
+ try:
+ result = type_func(arg_string)
+
+ # ArgumentTypeErrors indicate errors
+ except ArgumentTypeError:
+ name = getattr(action.type, '__name__', repr(action.type))
+ msg = str(_sys.exc_info()[1])
+ raise ArgumentError(action, msg)
+
+ # TypeErrors or ValueErrors also indicate errors
+ except (TypeError, ValueError):
+ name = getattr(action.type, '__name__', repr(action.type))
+ msg = _('invalid %s value: %r')
+ raise ArgumentError(action, msg % (name, arg_string))
+
+ # return the converted value
+ return result
+
+ def _check_value(self, action, value):
+ # converted value must be one of the choices (if specified)
+ if action.choices is not None and value not in action.choices:
+ tup = value, ', '.join(map(repr, action.choices))
+ msg = _('invalid choice: %r (choose from %s)') % tup
+ raise ArgumentError(action, msg)
+
+ # =======================
+ # Help-formatting methods
+ # =======================
+ def format_usage(self):
+ formatter = self._get_formatter()
+ formatter.add_usage(self.usage, self._actions,
+ self._mutually_exclusive_groups)
+ return formatter.format_help()
+
+ def format_help(self):
+ formatter = self._get_formatter()
+
+ # usage
+ formatter.add_usage(self.usage, self._actions,
+ self._mutually_exclusive_groups)
+
+ # description
+ formatter.add_text(self.description)
+
+ # positionals, optionals and user-defined groups
+ for action_group in self._action_groups:
+ formatter.start_section(action_group.title)
+ formatter.add_text(action_group.description)
+ formatter.add_arguments(action_group._group_actions)
+ formatter.end_section()
+
+ # epilog
+ formatter.add_text(self.epilog)
+
+ # determine help from format above
+ return formatter.format_help()
+
+ def format_version(self):
+ import warnings
+ warnings.warn(
+ 'The format_version method is deprecated -- the "version" '
+ 'argument to ArgumentParser is no longer supported.',
+ DeprecationWarning)
+ formatter = self._get_formatter()
+ formatter.add_text(self.version)
+ return formatter.format_help()
+
+ def _get_formatter(self):
+ return self.formatter_class(prog=self.prog)
+
+ # =====================
+ # Help-printing methods
+ # =====================
+ def print_usage(self, file=None):
+ if file is None:
+ file = _sys.stdout
+ self._print_message(self.format_usage(), file)
+
+ def print_help(self, file=None):
+ if file is None:
+ file = _sys.stdout
+ self._print_message(self.format_help(), file)
+
+ def print_version(self, file=None):
+ import warnings
+ warnings.warn(
+ 'The print_version method is deprecated -- the "version" '
+ 'argument to ArgumentParser is no longer supported.',
+ DeprecationWarning)
+ self._print_message(self.format_version(), file)
+
+ def _print_message(self, message, file=None):
+ if message:
+ if file is None:
+ file = _sys.stderr
+ file.write(message)
+
+ # ===============
+ # Exiting methods
+ # ===============
+ def exit(self, status=0, message=None):
+ if message:
+ self._print_message(message, _sys.stderr)
+ _sys.exit(status)
+
+ def error(self, message):
+ """error(message: string)
+
+ Prints a usage message incorporating the message to stderr and
+ exits.
+
+ If you override this in a subclass, it should not return -- it
+ should either exit or raise an exception.
+ """
+ self.print_usage(_sys.stderr)
+ self.exit(2, _('%s: error: %s\n') % (self.prog, message))
--- /dev/null
+#!/usr/bin/python
+# -*- python -*-
+#
+# Keycode Map Generator
+#
+# Copyright (C) 2009-2017 Red Hat, Inc.
+#
+# This file is dual license under the terms of the GPLv2 or later
+# and 3-clause BSD licenses.
+#
+
+# Requires >= 2.6
+from __future__ import print_function
+
+import csv
+try:
+ import argparse
+except:
+ import os, sys
+ sys.path.append(os.path.join(os.path.dirname(__file__), "../thirdparty"))
+ import argparse
+import hashlib
+import time
+import sys
+
+class Database:
+
+ # Linux: linux/input.h
+ MAP_LINUX = "linux"
+
+ # OS-X: Carbon/HIToolbox/Events.h
+ MAP_OSX = "osx"
+
+ # AT Set 1: linux/drivers/input/keyboard/atkbd.c
+ # (atkbd_set2_keycode + atkbd_unxlate_table)
+ MAP_ATSET1 = "atset1"
+
+ # AT Set 2: linux/drivers/input/keyboard/atkbd.c
+ # (atkbd_set2_keycode)
+ MAP_ATSET2 = "atset2"
+
+ # AT Set 3: linux/drivers/input/keyboard/atkbd.c
+ # (atkbd_set3_keycode)
+ MAP_ATSET3 = "atset3"
+
+ # Linux RAW: linux/drivers/char/keyboard.c (x86_keycodes)
+ MAP_XTKBD = "xtkbd"
+
+ # USB HID: linux/drivers/hid/usbhid/usbkbd.c (usb_kbd_keycode)
+ MAP_USB = "usb"
+
+ # Win32: mingw32/winuser.h
+ MAP_WIN32 = "win32"
+
+ # XWin XT: xorg-server/hw/xwin/{winkeybd.c,winkeynames.h}
+ # (xt + manually transcribed)
+ MAP_XWINXT = "xwinxt"
+
+ # X11: http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h
+ MAP_X11 = "x11"
+
+ # XKBD XT: xf86-input-keyboard/src/at_scancode.c
+ # (xt + manually transcribed)
+ MAP_XKBDXT = "xkbdxt"
+
+ # Xorg with evdev: linux + an offset
+ MAP_XORGEVDEV = "xorgevdev"
+
+ # Xorg with kbd: xkbdxt + an offset
+ MAP_XORGKBD = "xorgkbd"
+
+ # Xorg with OS-X: osx + an offset
+ MAP_XORGXQUARTZ = "xorgxquartz"
+
+ # Xorg + Cygwin: xwinxt + an offset
+ MAP_XORGXWIN = "xorgxwin"
+
+ # QEMU key numbers: xtkbd + special re-encoding of high bit
+ MAP_QNUM = "qnum"
+
+ # HTML codes
+ MAP_HTML = "html"
+
+ # XKB key names
+ MAP_XKB = "xkb"
+
+ # QEMU keycodes
+ MAP_QCODE = "qcode"
+
+ # Sun / Sparc scan codes
+ # Reference: "SPARC International Keyboard Spec 1", page 7 "US scan set"
+ MAP_SUN = "sun"
+
+ # Apple Desktop Bus
+ # Reference: http://www.archive.org/stream/apple-guide-macintosh-family-hardware/Apple_Guide_to_the_Macintosh_Family_Hardware_2e#page/n345/mode/2up
+ MAP_ADB = "adb"
+
+ MAP_LIST = (
+ MAP_LINUX,
+ MAP_OSX,
+ MAP_ATSET1,
+ MAP_ATSET2,
+ MAP_ATSET3,
+ MAP_USB,
+ MAP_WIN32,
+ MAP_XWINXT,
+ MAP_XKBDXT,
+ MAP_X11,
+ MAP_HTML,
+ MAP_XKB,
+ MAP_QCODE,
+ MAP_SUN,
+ MAP_ADB,
+
+ # These are derived from maps above
+ MAP_XTKBD,
+ MAP_XORGEVDEV,
+ MAP_XORGKBD,
+ MAP_XORGXQUARTZ,
+ MAP_XORGXWIN,
+ MAP_QNUM,
+ )
+
+ CODE_COLUMNS = {
+ MAP_LINUX: 1,
+ MAP_OSX: 3,
+ MAP_ATSET1: 4,
+ MAP_ATSET2: 5,
+ MAP_ATSET3: 6,
+ MAP_USB: 7,
+ MAP_WIN32: 9,
+ MAP_XWINXT: 10,
+ MAP_XKBDXT: 11,
+ MAP_X11: 13,
+ MAP_HTML: 14,
+ MAP_XKB: 15,
+ MAP_SUN: 17,
+ MAP_ADB: 18,
+ }
+
+ ENUM_COLUMNS = {
+ MAP_QCODE: 14,
+ }
+
+ NAME_COLUMNS = {
+ MAP_LINUX: 0,
+ MAP_OSX: 2,
+ MAP_WIN32: 8,
+ MAP_X11: 12,
+ MAP_HTML: 14,
+ MAP_XKB: 15,
+ MAP_QCODE: 16,
+ }
+
+ ENUM_BOUND = {
+ MAP_QCODE: "Q_KEY_CODE__MAX",
+ }
+
+ def __init__(self):
+
+ self.mapto = {}
+ self.mapfrom = {}
+ self.mapname = {}
+ self.mapchecksum = None
+
+ for name in self.MAP_LIST:
+ # Key is a MAP_LINUX, value is a MAP_XXX
+ self.mapto[name] = {}
+ # key is a MAP_XXX, value is a MAP_LINUX
+ self.mapfrom[name] = {}
+
+ for name in self.NAME_COLUMNS.keys():
+ # key is a MAP_LINUX, value is a string
+ self.mapname[name] = {}
+
+ def _generate_checksum(self, filename):
+ hash = hashlib.sha256()
+ with open(filename, "rb") as f:
+ for chunk in iter(lambda: f.read(4096), b""):
+ hash.update(chunk)
+ self.mapchecksum = hash.hexdigest()
+
+ def load(self, filename):
+ self._generate_checksum(filename)
+
+ with open(filename, 'r') as f:
+ reader = csv.reader(f)
+
+ first = True
+
+ for row in reader:
+ # Discard column headings
+ if first:
+ first = False
+ continue
+
+ # We special case MAP_LINUX since that is out
+ # master via which all other mappings are done
+ linux = self.load_linux(row)
+
+ # Now load all the remaining master data values
+ self.load_data(row, linux)
+
+ # Then load all the keycode names
+ self.load_names(row, linux)
+
+ # Finally calculate derived key maps
+ self.derive_data(row, linux)
+
+ def load_linux(self, row):
+ col = self.CODE_COLUMNS[self.MAP_LINUX]
+ linux = row[col]
+
+ if linux.startswith("0x"):
+ linux = int(linux, 16)
+ else:
+ linux = int(linux, 10)
+
+ self.mapto[self.MAP_LINUX][linux] = linux
+ self.mapfrom[self.MAP_LINUX][linux] = linux
+
+ return linux
+
+
+ def load_data(self, row, linux):
+ for mapname in self.CODE_COLUMNS:
+ if mapname == self.MAP_LINUX:
+ continue
+
+ col = self.CODE_COLUMNS[mapname]
+ val = row[col]
+
+ if val == "":
+ continue
+
+ if val.startswith("0x"):
+ val = int(val, 16)
+ elif val.isdigit():
+ val = int(val, 10)
+
+ self.mapto[mapname][linux] = val
+ self.mapfrom[mapname][val] = linux
+
+ def load_names(self, row, linux):
+ for mapname in self.NAME_COLUMNS:
+ col = self.NAME_COLUMNS[mapname]
+ val = row[col]
+
+ if val == "":
+ continue
+
+ self.mapname[mapname][linux] = val
+
+
+ def derive_data(self, row, linux):
+ # Linux RAW is XT scan codes with special encoding of the
+ # 0xe0 scan codes
+ if linux in self.mapto[self.MAP_ATSET1]:
+ at1 = self.mapto[self.MAP_ATSET1][linux]
+ if at1 > 0x7f:
+ assert((at1 & ~0x7f) == 0xe000)
+ xtkbd = 0x100 | (at1 & 0x7f)
+ else:
+ xtkbd = at1
+ self.mapto[self.MAP_XTKBD][linux] = xtkbd
+ self.mapfrom[self.MAP_XTKBD][xtkbd] = linux
+
+ # Xorg KBD is XKBD XT offset by 8
+ if linux in self.mapto[self.MAP_XKBDXT]:
+ xorgkbd = self.mapto[self.MAP_XKBDXT][linux] + 8
+ self.mapto[self.MAP_XORGKBD][linux] = xorgkbd
+ self.mapfrom[self.MAP_XORGKBD][xorgkbd] = linux
+
+ # Xorg evdev is Linux offset by 8
+ self.mapto[self.MAP_XORGEVDEV][linux] = linux + 8
+ self.mapfrom[self.MAP_XORGEVDEV][linux + 8] = linux
+
+ # Xorg XQuartx is OS-X offset by 8
+ if linux in self.mapto[self.MAP_OSX]:
+ xorgxquartz = self.mapto[self.MAP_OSX][linux] + 8
+ self.mapto[self.MAP_XORGXQUARTZ][linux] = xorgxquartz
+ self.mapfrom[self.MAP_XORGXQUARTZ][xorgxquartz] = linux
+
+ # Xorg Xwin (aka Cygwin) is XWin XT offset by 8
+ if linux in self.mapto[self.MAP_XWINXT]:
+ xorgxwin = self.mapto[self.MAP_XWINXT][linux] + 8
+ self.mapto[self.MAP_XORGXWIN][linux] = xorgxwin
+ self.mapfrom[self.MAP_XORGXWIN][xorgxwin] = linux
+
+ # QNUM keycodes are XT scan codes with a slightly
+ # different encoding of 0xe0 scan codes
+ if linux in self.mapto[self.MAP_ATSET1]:
+ at1 = self.mapto[self.MAP_ATSET1][linux]
+ if at1 > 0x7f:
+ assert((at1 & ~0x7f) == 0xe000)
+ qnum = 0x80 | (at1 & 0x7f)
+ else:
+ qnum = at1
+ self.mapto[self.MAP_QNUM][linux] = qnum
+ self.mapfrom[self.MAP_QNUM][qnum] = linux
+
+ # Hack for compatibility with previous mistakes in handling
+ # Print/SysRq. The preferred qnum for Print/SysRq is 0x54,
+ # but QEMU previously allowed 0xb7 too
+ if qnum == 0x54:
+ self.mapfrom[self.MAP_QNUM][0xb7] = self.mapfrom[self.MAP_QNUM][0x54]
+
+ if linux in self.mapname[self.MAP_QCODE]:
+ qcodeenum = self.mapname[self.MAP_QCODE][linux]
+ qcodeenum = "Q_KEY_CODE_" + qcodeenum.upper()
+ self.mapto[self.MAP_QCODE][linux] = qcodeenum
+ self.mapfrom[self.MAP_QCODE][qcodeenum] = linux
+
+class LanguageGenerator(object):
+
+ def _boilerplate(self, lines):
+ raise NotImplementedError()
+
+ def generate_header(self, database, args):
+ today = time.strftime("%Y-%m-%d %H:%M")
+ self._boilerplate([
+ "This file is auto-generated from keymaps.csv on %s" % today,
+ "Database checksum sha256(%s)" % database.mapchecksum,
+ "To re-generate, run:",
+ " %s" % args,
+ ])
+
+class LanguageSrcGenerator(LanguageGenerator):
+
+ TYPE_INT = "integer"
+ TYPE_STRING = "string"
+ TYPE_ENUM = "enum"
+
+ def _array_start(self, varname, length, defvalue, fromtype, totype):
+ raise NotImplementedError()
+
+ def _array_end(self, fromtype, totype):
+ raise NotImplementedError()
+
+ def _array_entry(self, index, value, comment, fromtype, totype):
+ raise NotImplementedError()
+
+ def generate_code_map(self, varname, database, frommapname, tomapname):
+ if frommapname not in database.mapfrom:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ frommapname, ", ".join(database.mapfrom.keys())))
+ if tomapname not in database.mapto:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ tomapname, ", ".join(database.mapto.keys())))
+
+ tolinux = database.mapfrom[frommapname]
+ fromlinux = database.mapto[tomapname]
+
+ if varname is None:
+ varname = "code_map_%s_to_%s" % (frommapname, tomapname)
+
+ if frommapname in database.ENUM_COLUMNS:
+ fromtype = self.TYPE_ENUM
+ elif type(tolinux.keys()[0]) == str:
+ fromtype = self.TYPE_STRING
+ else:
+ fromtype = self.TYPE_INT
+
+ if tomapname in database.ENUM_COLUMNS:
+ totype = self.TYPE_ENUM
+ elif type(fromlinux.values()[0]) == str:
+ totype = self.TYPE_STRING
+ else:
+ totype = self.TYPE_INT
+
+ keys = tolinux.keys()
+ keys.sort()
+ if fromtype == self.TYPE_INT:
+ keys = range(keys[-1] + 1)
+
+ if fromtype == self.TYPE_ENUM:
+ keymax = database.ENUM_BOUND[frommapname]
+ else:
+ keymax = len(keys)
+
+ defvalue = fromlinux.get(0, None)
+ if fromtype == self.TYPE_ENUM:
+ self._array_start(varname, keymax, defvalue, fromtype, totype)
+ else:
+ self._array_start(varname, keymax, None, fromtype, totype)
+
+ for src in keys:
+ linux = tolinux.get(src, None)
+ if linux is None:
+ dst = None
+ else:
+ dst = fromlinux.get(linux, defvalue)
+
+ comment = "%s -> %s -> %s" % (self._label(database, frommapname, src, linux),
+ self._label(database, Database.MAP_LINUX, linux, linux),
+ self._label(database, tomapname, dst, linux))
+ self._array_entry(src, dst, comment, fromtype, totype)
+ self._array_end(fromtype, totype)
+
+ def generate_code_table(self, varname, database, mapname):
+ if mapname not in database.mapto:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ mapname, ", ".join(database.mapto.keys())))
+
+ keys = database.mapto[Database.MAP_LINUX].keys()
+ keys.sort()
+ names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys]
+
+ if varname is None:
+ varname = "code_table_%s" % mapname
+
+ if mapname in database.ENUM_COLUMNS:
+ totype = self.TYPE_ENUM
+ elif type(database.mapto[mapname].values()[0]) == str:
+ totype = self.TYPE_STRING
+ else:
+ totype = self.TYPE_INT
+
+ self._array_start(varname, len(keys), None, self.TYPE_INT, totype)
+
+ defvalue = database.mapto[mapname].get(0, None)
+ for i in range(len(keys)):
+ key = keys[i]
+ dst = database.mapto[mapname].get(key, defvalue)
+ self._array_entry(i, dst, names[i], self.TYPE_INT, totype)
+
+ self._array_end(self.TYPE_INT, totype)
+
+ def generate_name_map(self, varname, database, frommapname, tomapname):
+ if frommapname not in database.mapfrom:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ frommapname, ", ".join(database.mapfrom.keys())))
+ if tomapname not in database.mapname:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ tomapname, ", ".join(database.mapname.keys())))
+
+ tolinux = database.mapfrom[frommapname]
+ fromlinux = database.mapname[tomapname]
+
+ if varname is None:
+ varname = "name_map_%s_to_%s" % (frommapname, tomapname)
+
+ keys = tolinux.keys()
+ keys.sort()
+ if type(keys[0]) == int:
+ keys = range(keys[-1] + 1)
+
+ if type(keys[0]) == int:
+ fromtype = self.TYPE_INT
+ else:
+ fromtype = self.TYPE_STRING
+
+ self._array_start(varname, len(keys), None, fromtype, self.TYPE_STRING)
+
+ for src in keys:
+ linux = tolinux.get(src, None)
+ if linux is None:
+ dst = None
+ else:
+ dst = fromlinux.get(linux, None)
+
+ comment = "%s -> %s -> %s" % (self._label(database, frommapname, src, linux),
+ self._label(database, Database.MAP_LINUX, linux, linux),
+ self._label(database, tomapname, dst, linux))
+ self._array_entry(src, dst, comment, fromtype, self.TYPE_STRING)
+ self._array_end(fromtype, self.TYPE_STRING)
+
+ def generate_name_table(self, varname, database, mapname):
+ if mapname not in database.mapname:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ mapname, ", ".join(database.mapname.keys())))
+
+ keys = database.mapto[Database.MAP_LINUX].keys()
+ keys.sort()
+ names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys]
+
+ if varname is None:
+ varname = "name_table_%s" % mapname
+
+ self._array_start(varname, len(keys), None, self.TYPE_INT, self.TYPE_STRING)
+
+ for i in range(len(keys)):
+ key = keys[i]
+ dst = database.mapname[mapname].get(key, None)
+ self._array_entry(i, dst, names[i], self.TYPE_INT, self.TYPE_STRING)
+
+ self._array_end(self.TYPE_INT, self.TYPE_STRING)
+
+ def _label(self, database, mapname, val, linux):
+ if mapname in database.mapname:
+ return "%s:%s (%s)" % (mapname, val, database.mapname[mapname].get(linux, "unnamed"))
+ else:
+ return "%s:%s" % (mapname, val)
+
+class LanguageDocGenerator(LanguageGenerator):
+
+ def _array_start_name_doc(self, varname, namemap):
+ raise NotImplementedError()
+
+ def _array_start_code_doc(self, varname, namemap, codemap):
+ raise NotImplementedError()
+
+ def _array_end(self):
+ raise NotImplementedError()
+
+ def _array_name_entry(self, value, name):
+ raise NotImplementedError()
+
+ def _array_code_entry(self, value, name):
+ raise NotImplementedError()
+
+ def generate_name_docs(self, varname, database, mapname):
+ if mapname not in database.mapname:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ mapname, ", ".join(database.mapname.keys())))
+
+ keys = database.mapto[Database.MAP_LINUX].keys()
+ keys.sort()
+ names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys]
+
+ if varname is None:
+ varname = mapname
+
+ self._array_start_name_doc(varname, mapname)
+
+ for i in range(len(keys)):
+ key = keys[i]
+ dst = database.mapname[mapname].get(key, None)
+ self._array_name_entry(key, dst)
+
+ self._array_end()
+
+
+ def generate_code_docs(self, varname, database, mapname):
+ if mapname not in database.mapfrom:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ mapname, ", ".join(database.mapfrom.keys())))
+
+ tolinux = database.mapfrom[mapname]
+ keys = tolinux.keys()
+ keys.sort()
+ if mapname in database.mapname:
+ names = database.mapname[mapname]
+ namemap = mapname
+ else:
+ names = database.mapname[Database.MAP_LINUX]
+ namemap = Database.MAP_LINUX
+
+ if varname is None:
+ varname = mapname
+
+ self._array_start_code_doc(varname, mapname, namemap)
+
+ for i in range(len(keys)):
+ key = keys[i]
+ self._array_code_entry(key, names.get(tolinux[key], "unnamed"))
+
+ self._array_end()
+
+class CLanguageGenerator(LanguageSrcGenerator):
+
+ def __init__(self, inttypename, strtypename, lentypename):
+ self.inttypename = inttypename
+ self.strtypename = strtypename
+ self.lentypename = lentypename
+
+ def _boilerplate(self, lines):
+ print("/*")
+ for line in lines:
+ print(" * %s" % line)
+ print("*/")
+
+ def _array_start(self, varname, length, defvalue, fromtype, totype):
+ self._varname = varname;
+ totypename = self.strtypename if totype == self.TYPE_STRING else self.inttypename
+ if fromtype in (self.TYPE_INT, self.TYPE_ENUM):
+ if type(length) == str:
+ print("const %s %s[%s] = {" % (totypename, varname, length))
+ else:
+ print("const %s %s[%d] = {" % (totypename, varname, length))
+ else:
+ print("const struct _%s {" % varname)
+ print(" const %s from;" % self.strtypename)
+ print(" const %s to;" % totypename)
+ print("} %s[] = {" % varname)
+
+ if defvalue != None:
+ if totype == self.TYPE_ENUM:
+ if type(length) == str:
+ print(" [0 ... %s-1] = %s," % (length, defvalue))
+ else:
+ print(" [0 ... 0x%x-1] = %s," % (length, defvalue))
+ else:
+ if type(length) == str:
+ print(" [0 ... %s-1] = 0x%x," % (length, defvalue))
+ else:
+ print(" [0 ... 0x%x-1] = 0x%x," % (length, defvalue))
+
+ def _array_end(self, fromtype, totype):
+ print("};")
+ print("const %s %s_len = sizeof(%s)/sizeof(%s[0]);" %
+ (self.lentypename, self._varname, self._varname, self._varname))
+
+ def _array_entry(self, index, value, comment, fromtype, totype):
+ if value is None:
+ return
+ if fromtype == self.TYPE_INT:
+ indexfmt = "0x%x"
+ elif fromtype == self.TYPE_ENUM:
+ indexfmt = "%s"
+ else:
+ indexfmt = "\"%s\""
+
+ if totype == self.TYPE_INT:
+ valuefmt = "0x%x"
+ elif totype == self.TYPE_ENUM:
+ valuefmt = "%s"
+ else:
+ valuefmt = "\"%s\""
+
+ if fromtype != self.TYPE_STRING:
+ print((" [" + indexfmt + "] = " + valuefmt + ", /* %s */") % (index, value, comment))
+ else:
+ print((" {" + indexfmt + ", " + valuefmt + "}, /* %s */") % (index, value, comment))
+
+class CppLanguageGenerator(CLanguageGenerator):
+
+ def _array_start(self, varname, length, defvalue, fromtype, totype):
+ if fromtype == self.TYPE_ENUM:
+ raise NotImplementedError("Enums not supported as source in C++ generator")
+ totypename = "const " + self.strtypename if totype == self.TYPE_STRING else self.inttypename
+ if fromtype == self.TYPE_INT:
+ print("#include <vector>")
+ print("const std::vector<%s> %s = {" % (totypename, varname))
+ else:
+ print("#include <map>")
+ print("#include <string>")
+ print("const std::map<const std::string, %s> %s = {" % (totypename, varname))
+
+ def _array_end(self, fromtype, totype):
+ print("};")
+
+ # designated initializers not available in C++
+ def _array_entry(self, index, value, comment, fromtype, totype):
+ if fromtype == self.TYPE_STRING:
+ return super(CppLanguageGenerator, self)._array_entry(index, value, comment, fromtype, totype)
+
+ if value is None:
+ print(" 0, /* %s */" % comment)
+ elif totype == self.TYPE_INT:
+ print(" 0x%x, /* %s */" % (value, comment))
+ elif totype == self.TYPE_ENUM:
+ print(" %s, /* %s */" % (value, comment))
+ else:
+ print(" \"%s\", /* %s */" % (value, comment))
+
+class StdCLanguageGenerator(CLanguageGenerator):
+
+ def __init__(self):
+ super(StdCLanguageGenerator, self).__init__("unsigned short", "char *", "unsigned int")
+
+class StdCppLanguageGenerator(CppLanguageGenerator):
+
+ def __init__(self):
+ super(StdCppLanguageGenerator, self).__init__("unsigned short", "char *", "unsigned int")
+
+class GLib2LanguageGenerator(CLanguageGenerator):
+
+ def __init__(self):
+ super(GLib2LanguageGenerator, self).__init__("guint16", "gchar *", "guint")
+
+class PythonLanguageGenerator(LanguageSrcGenerator):
+
+ def _boilerplate(self, lines):
+ print("#")
+ for line in lines:
+ print("# %s" % line)
+ print("#")
+
+ def _array_start(self, varname, length, defvalue, fromtype, totype):
+ if fromtype == self.TYPE_ENUM:
+ raise NotImplementedError("Enums not supported as source in Python generator")
+
+ if fromtype != self.TYPE_STRING:
+ print("%s = [" % varname)
+ else:
+ print("%s = {" % varname)
+
+ def _array_end(self, fromtype, totype):
+ if fromtype != self.TYPE_STRING:
+ print("]")
+ else:
+ print("}")
+
+ def _array_entry(self, index, value, comment, fromtype, totype):
+ if fromtype == self.TYPE_INT:
+ if value is None:
+ print(" None, # %s" % (comment))
+ elif totype == self.TYPE_INT:
+ print(" 0x%x, # %s" % (value, comment))
+ elif totype == self.TYPE_ENUM:
+ print(" %s, # %s" % (value, comment))
+ else:
+ print(" \"%s\", # %s" % (value, comment))
+ else:
+ if value is None:
+ print(" \"%s\": None, # %s" % (index, comment))
+ elif totype == self.TYPE_INT:
+ print(" \"%s\": 0x%x, # %s" % (index, value, comment))
+ elif totype == self.TYPE_ENUM:
+ print(" \"%s\": %s, # %s" % (index, value, comment))
+ else:
+ print(" \"%s\": \"%s\", # %s" % (index, value, comment))
+
+class PerlLanguageGenerator(LanguageSrcGenerator):
+
+ def _boilerplate(self, lines):
+ print("#")
+ for line in lines:
+ print("# %s" % line)
+ print("#")
+
+ def _array_start(self, varname, length, defvalue, fromtype, totype):
+ if fromtype == self.TYPE_ENUN:
+ raise NotImplementedError("Enums not supported as source in Python generator")
+ if fromtype == self.TYPE_INT:
+ print("my @%s = (" % varname)
+ else:
+ print("my %%%s = (" % varname)
+
+ def _array_end(self, fromtype, totype):
+ print(");")
+
+ def _array_entry(self, index, value, comment, fromtype, totype):
+ if fromtype == self.TYPE_INT:
+ if value is None:
+ print(" undef, # %s" % (comment))
+ elif totype == self.TYPE_INT:
+ print(" 0x%x, # %s" % (value, comment))
+ elif totype == self.TYPE_ENUM:
+ print(" %s, # %s" % (value, comment))
+ else:
+ print(" \"%s\", # %s" % (value, comment))
+ else:
+ if value is None:
+ print(" \"%s\", undef, # %s" % (index, comment))
+ elif totype == self.TYPE_INT:
+ print(" \"%s\", 0x%x, # %s" % (index, value, comment))
+ elif totype == self.TYPE_ENUM:
+ print(" \"%s\", 0x%x, # %s" % (index, value, comment))
+ else:
+ print(" \"%s\", \"%s\", # %s" % (index, value, comment))
+
+class JavaScriptLanguageGenerator(LanguageSrcGenerator):
+
+ def _boilerplate(self, lines):
+ print("/*")
+ for line in lines:
+ print(" * %s" % line)
+ print("*/")
+
+ def _array_start(self, varname, length, defvalue, fromtype, totype):
+ print("export default {")
+
+ def _array_end(self, fromtype, totype):
+ print("};")
+
+ def _array_entry(self, index, value, comment, fromtype, totype):
+ if value is None:
+ return
+
+ if fromtype == self.TYPE_INT:
+ fromfmt = "0x%x"
+ elif fromtype == self.TYPE_ENUM:
+ fromfmt = "%s"
+ else:
+ fromfmt = "\"%s\""
+
+ if totype == self.TYPE_INT:
+ tofmt = "0x%x"
+ elif totype == self.TYPE_ENUM:
+ tofmt = "%s"
+ else:
+ tofmt = "\"%s\""
+
+ print((" " + fromfmt + ": " + tofmt + ", /* %s */") % (index, value, comment))
+
+class PodLanguageGenerator(LanguageDocGenerator):
+
+ def _boilerplate(self, lines):
+ print("#")
+ for line in lines:
+ print("# %s" % line)
+ print("#")
+
+ def _array_start_name_doc(self, varname, namemap):
+ print("=head1 %s" % varname)
+ print("")
+ print("List of %s key code names, with corresponding key code values" % namemap)
+ print("")
+ print("=over 4")
+ print("")
+
+ def _array_start_code_doc(self, varname, codemap, namemap):
+ print("=head1 %s" % varname)
+ print("")
+ print("List of %s key code values, with corresponding %s key code names" % (codemap, namemap))
+ print("")
+ print("=over 4")
+ print("")
+
+ def _array_end(self):
+ print("=back")
+ print("")
+
+ def _array_name_entry(self, value, name):
+ print("=item %s" % name)
+ print("")
+ print("Key value %d (0x%x)" % (value, value))
+ print("")
+
+ def _array_code_entry(self, value, name):
+ print("=item %d (0x%x)" % (value, value))
+ print("")
+ print("Key name %s" % name)
+ print("")
+
+SRC_GENERATORS = {
+ "stdc": StdCLanguageGenerator(),
+ "stdc++": StdCppLanguageGenerator(),
+ "glib2": GLib2LanguageGenerator(),
+ "python2": PythonLanguageGenerator(),
+ "python3": PythonLanguageGenerator(),
+ "perl": PerlLanguageGenerator(),
+ "js": JavaScriptLanguageGenerator(),
+}
+DOC_GENERATORS = {
+ "pod": PodLanguageGenerator(),
+}
+
+def code_map(args):
+ database = Database()
+ database.load(args.keymaps)
+
+ cliargs = ["keymap-gen", "--lang=%s" % args.lang]
+ if args.varname is not None:
+ cliargs.append("--varname=%s" % args.varname)
+ cliargs.extend(["code-map", "keymaps.csv", args.frommapname, args.tomapname])
+ SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
+
+ SRC_GENERATORS[args.lang].generate_code_map(args.varname, database, args.frommapname, args.tomapname)
+
+def code_table(args):
+ database = Database()
+ database.load(args.keymaps)
+
+ cliargs = ["keymap-gen", "--lang=%s" % args.lang]
+ if args.varname is not None:
+ cliargs.append("--varname=%s" % args.varname)
+ cliargs.extend(["code-table", "keymaps.csv", args.mapname])
+ SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
+
+ SRC_GENERATORS[args.lang].generate_code_table(args.varname, database, args.mapname)
+
+def name_map(args):
+ database = Database()
+ database.load(args.keymaps)
+
+ cliargs = ["keymap-gen", "--lang=%s" % args.lang]
+ if args.varname is not None:
+ cliargs.append("--varname=%s" % args.varname)
+ cliargs.extend(["name-map", "keymaps.csv", args.frommapname, args.tomapname])
+ SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
+
+ SRC_GENERATORS[args.lang].generate_name_map(args.varname, database, args.frommapname, args.tomapname)
+
+def name_table(args):
+ database = Database()
+ database.load(args.keymaps)
+
+
+ cliargs = ["keymap-gen", "--lang=%s" % args.lang]
+ if args.varname is not None:
+ cliargs.append("--varname=%s" % args.varname)
+ cliargs.extend(["name-table", "keymaps.csv", args.mapname])
+ SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
+
+ SRC_GENERATORS[args.lang].generate_name_table(args.varname, database, args.mapname)
+
+def code_docs(args):
+ database = Database()
+ database.load(args.keymaps)
+
+
+ cliargs = ["keymap-gen", "--lang=%s" % args.lang]
+ if args.varname is not None:
+ cliargs.append("--varname=%s" % args.varname)
+ cliargs.extend(["code-docs", "keymaps.csv", args.mapname])
+ DOC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
+
+ DOC_GENERATORS[args.lang].generate_code_docs(args.varname, database, args.mapname)
+
+def name_docs(args):
+ database = Database()
+ database.load(args.keymaps)
+
+
+ cliargs = ["keymap-gen", "--lang=%s" % args.lang]
+ if args.varname is not None:
+ cliargs.append("--varname=%s" % args.varname)
+ cliargs.extend(["name-docs", "keymaps.csv", args.mapname])
+ DOC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
+
+ DOC_GENERATORS[args.lang].generate_name_docs(args.varname, database, args.mapname)
+
+def usage():
+ print ("Please select a command:")
+ print (" 'code-map', 'code-table', 'name-map', 'name-table', 'docs'")
+ sys.exit(1)
+
+def main():
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument("--lang", default="stdc",
+ help="Output language, (src=%s, doc=%s)" % (
+ ",".join(SRC_GENERATORS.keys()),
+ ",".join(DOC_GENERATORS.keys())))
+ parser.add_argument("--varname", default=None,
+ help="Data variable name")
+
+ subparsers = parser.add_subparsers(help="sub-command help")
+
+ codemapparser = subparsers.add_parser("code-map", help="Generate a mapping between code tables")
+ codemapparser.add_argument("keymaps", help="Path to keymap CSV data file")
+ codemapparser.add_argument("frommapname", help="Source code table name")
+ codemapparser.add_argument("tomapname", help="Target code table name")
+ codemapparser.set_defaults(func=code_map)
+
+ codetableparser = subparsers.add_parser("code-table", help="Generate a flat code table")
+ codetableparser.add_argument("keymaps", help="Path to keymap CSV data file")
+ codetableparser.add_argument("mapname", help="Code table name")
+ codetableparser.set_defaults(func=code_table)
+
+ namemapparser = subparsers.add_parser("name-map", help="Generate a mapping to names")
+ namemapparser.add_argument("keymaps", help="Path to keymap CSV data file")
+ namemapparser.add_argument("frommapname", help="Source code table name")
+ namemapparser.add_argument("tomapname", help="Target name table name")
+ namemapparser.set_defaults(func=name_map)
+
+ nametableparser = subparsers.add_parser("name-table", help="Generate a flat name table")
+ nametableparser.add_argument("keymaps", help="Path to keymap CSV data file")
+ nametableparser.add_argument("mapname", help="Name table name")
+ nametableparser.set_defaults(func=name_table)
+
+ codedocsparser = subparsers.add_parser("code-docs", help="Generate code documentation")
+ codedocsparser.add_argument("keymaps", help="Path to keymap CSV data file")
+ codedocsparser.add_argument("mapname", help="Code table name")
+ codedocsparser.set_defaults(func=code_docs)
+
+ namedocsparser = subparsers.add_parser("name-docs", help="Generate name documentation")
+ namedocsparser.add_argument("keymaps", help="Path to keymap CSV data file")
+ namedocsparser.add_argument("mapname", help="Name table name")
+ namedocsparser.set_defaults(func=name_docs)
+
+ args = parser.parse_args()
+ if hasattr(args, "func"):
+ args.func(args)
+ else:
+ usage()
+
+
+main()
-Subproject commit 4cd42653f5c1df326a2678a84f24a78fb9601277
+Subproject commit 7c1beb52ed86191d9e965444d934adaa2531710f