From: Wolfgang Bumiller Date: Mon, 18 Jan 2016 10:21:27 +0000 (+0100) Subject: adding 2.5 pve patches and left-over extra fixes X-Git-Url: https://git.proxmox.com/?p=pve-qemu-kvm.git;a=commitdiff_plain;h=ca0fe5f5a34450a040edc30d3e4076971059b0b0 adding 2.5 pve patches and left-over extra fixes --- diff --git a/debian/patches/extra/CVE-2015-8558-ehci_make_idt_processing_more_robust.patch b/debian/patches/extra/CVE-2015-8558-ehci_make_idt_processing_more_robust.patch new file mode 100644 index 0000000..743b68d --- /dev/null +++ b/debian/patches/extra/CVE-2015-8558-ehci_make_idt_processing_more_robust.patch @@ -0,0 +1,49 @@ +From: Gerd Hoffmann +Subject: [Qemu-devel] [PULL 5/5] ehci: make idt processing more robust + +Make ehci_process_itd return an error in case we didn't do any actual +iso transfer because we've found no active transaction. That'll avoid +ehci happily run in circles forever if the guest builds a loop out of +idts. + +This is CVE-2015-8558. + +Cc: qemu-stable@nongnu.org +Reported-by: Qinghao Tang +Tested-by: P J P +Signed-off-by: Gerd Hoffmann +--- + hw/usb/hcd-ehci.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c +index 4e2161b..d07f228 100644 +--- a/hw/usb/hcd-ehci.c ++++ b/hw/usb/hcd-ehci.c +@@ -1389,7 +1389,7 @@ static int ehci_process_itd(EHCIState *ehci, + { + USBDevice *dev; + USBEndpoint *ep; +- uint32_t i, len, pid, dir, devaddr, endp; ++ uint32_t i, len, pid, dir, devaddr, endp, xfers = 0; + uint32_t pg, off, ptr1, ptr2, max, mult; + + ehci->periodic_sched_active = PERIODIC_ACTIVE; +@@ -1479,9 +1479,10 @@ static int ehci_process_itd(EHCIState *ehci, + ehci_raise_irq(ehci, USBSTS_INT); + } + itd->transact[i] &= ~ITD_XACT_ACTIVE; ++ xfers++; + } + } +- return 0; ++ return xfers ? 0 : -1; + } + + +-- +1.8.3.1 + + + + diff --git a/debian/patches/extra/CVE-2015-8613-scsi-initialize-info-object.patch b/debian/patches/extra/CVE-2015-8613-scsi-initialize-info-object.patch new file mode 100644 index 0000000..ee0f36d --- /dev/null +++ b/debian/patches/extra/CVE-2015-8613-scsi-initialize-info-object.patch @@ -0,0 +1,31 @@ +From 5823b4a214ede884f4ba597fdd629862620e0f92 Mon Sep 17 00:00:00 2001 +From: Prasad J Pandit +Date: Mon, 21 Dec 2015 14:48:18 +0530 +Subject: [PATCH] scsi: initialise info object with appropriate size + +While processing controller 'CTRL_GET_INFO' command, the routine +'megasas_ctrl_get_info' overflows the '&info' object size. Use its +appropriate size to null initialise it. + +Reported-by: Qinghao Tang +Signed-off-by: Prasad J Pandit +--- + hw/scsi/megasas.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c +index d7dc667..576f56c 100644 +--- a/hw/scsi/megasas.c ++++ b/hw/scsi/megasas.c +@@ -718,7 +718,7 @@ static int megasas_ctrl_get_info(MegasasState *s, MegasasCmd *cmd) + BusChild *kid; + int num_pd_disks = 0; + +- memset(&info, 0x0, cmd->iov_size); ++ memset(&info, 0x0, dcmd_size); + if (cmd->iov_size < dcmd_size) { + trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size, + dcmd_size); +-- +2.4.3 +=== diff --git a/debian/patches/extra/CVE-2015-8619-hmp-sendkey-oob-fix.patch b/debian/patches/extra/CVE-2015-8619-hmp-sendkey-oob-fix.patch new file mode 100644 index 0000000..86e26db --- /dev/null +++ b/debian/patches/extra/CVE-2015-8619-hmp-sendkey-oob-fix.patch @@ -0,0 +1,116 @@ +From 136dd5ac96fc21654a31aff7fa88b86570c8fc72 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 13 Jan 2016 08:46:31 +0100 +Subject: [PATCH] hmp: fix sendkey out of bounds write (CVE-2015-8619) + +When processing 'sendkey' command, hmp_sendkey routine null +terminates the 'keyname_buf' array. This results in an OOB +write issue, if 'keyname_len' was to fall outside of +'keyname_buf' array. + +Since the keyname's length is known the keyname_buf can be +removed altogether by adding a length parameter to +index_from_key() and using it for the error output as well. + +Reported-by: Ling Liu +Signed-off-by: Wolfgang Bumiller +--- + hmp.c | 17 +++++++---------- + include/ui/console.h | 2 +- + ui/input-legacy.c | 5 +++-- + 3 files changed, 11 insertions(+), 13 deletions(-) + +diff --git a/hmp.c b/hmp.c +index c2b2c16..066ccf8 100644 +--- a/hmp.c ++++ b/hmp.c +@@ -1742,21 +1742,18 @@ void hmp_sendkey(Monitor *mon, const QDict *qdict) + int has_hold_time = qdict_haskey(qdict, "hold-time"); + int hold_time = qdict_get_try_int(qdict, "hold-time", -1); + Error *err = NULL; +- char keyname_buf[16]; + char *separator; + int keyname_len; + + while (1) { + separator = strchr(keys, '-'); + keyname_len = separator ? separator - keys : strlen(keys); +- pstrcpy(keyname_buf, sizeof(keyname_buf), keys); + + /* Be compatible with old interface, convert user inputted "<" */ +- if (!strncmp(keyname_buf, "<", 1) && keyname_len == 1) { +- pstrcpy(keyname_buf, sizeof(keyname_buf), "less"); ++ if (!strncmp(keys, "<", 1) && keyname_len == 1) { ++ keys = "less"; + keyname_len = 4; + } +- keyname_buf[keyname_len] = 0; + + keylist = g_malloc0(sizeof(*keylist)); + keylist->value = g_malloc0(sizeof(*keylist->value)); +@@ -1769,16 +1766,16 @@ void hmp_sendkey(Monitor *mon, const QDict *qdict) + } + tmp = keylist; + +- if (strstart(keyname_buf, "0x", NULL)) { ++ if (strstart(keys, "0x", NULL)) { + char *endp; +- int value = strtoul(keyname_buf, &endp, 0); +- if (*endp != '\0') { ++ int value = strtoul(keys, &endp, 0); ++ if (*endp != '\0' && *endp != '-') { + goto err_out; + } + keylist->value->type = KEY_VALUE_KIND_NUMBER; + keylist->value->u.number = value; + } else { +- int idx = index_from_key(keyname_buf); ++ int idx = index_from_key(keys, keyname_len); + if (idx == Q_KEY_CODE_MAX) { + goto err_out; + } +@@ -1800,7 +1797,7 @@ out: + return; + + err_out: +- monitor_printf(mon, "invalid parameter: %s\n", keyname_buf); ++ monitor_printf(mon, "invalid parameter: %.*s\n", keyname_len, keys); + goto out; + } + +diff --git a/include/ui/console.h b/include/ui/console.h +index adac36d..116bc2b 100644 +--- a/include/ui/console.h ++++ b/include/ui/console.h +@@ -448,7 +448,7 @@ static inline int vnc_display_pw_expire(const char *id, time_t expires) + void curses_display_init(DisplayState *ds, int full_screen); + + /* input.c */ +-int index_from_key(const char *key); ++int index_from_key(const char *key, size_t key_length); + + /* gtk.c */ + void early_gtk_display_init(int opengl); +diff --git a/ui/input-legacy.c b/ui/input-legacy.c +index 35dfc27..3454055 100644 +--- a/ui/input-legacy.c ++++ b/ui/input-legacy.c +@@ -57,12 +57,13 @@ struct QEMUPutLEDEntry { + static QTAILQ_HEAD(, QEMUPutLEDEntry) led_handlers = + QTAILQ_HEAD_INITIALIZER(led_handlers); + +-int index_from_key(const char *key) ++int index_from_key(const char *key, size_t key_length) + { + int i; + + for (i = 0; QKeyCode_lookup[i] != NULL; i++) { +- if (!strcmp(key, QKeyCode_lookup[i])) { ++ if (!strncmp(key, QKeyCode_lookup[i], key_length) && ++ !QKeyCode_lookup[i][key_length]) { + break; + } + } +-- +2.1.4 + diff --git a/debian/patches/extra/CVE-2015-8701-net-rocker-off-by-one.patch b/debian/patches/extra/CVE-2015-8701-net-rocker-off-by-one.patch new file mode 100644 index 0000000..7b17355 --- /dev/null +++ b/debian/patches/extra/CVE-2015-8701-net-rocker-off-by-one.patch @@ -0,0 +1,47 @@ +From 60e8fd72b0faaf940e220a0514001b86b7149e09 Mon Sep 17 00:00:00 2001 +From: Prasad J Pandit +Date: Mon, 28 Dec 2015 16:24:08 +0530 +Subject: [PATCH] net: rocker: fix an incorrect array bounds check + +While processing transmit(tx) descriptors in 'tx_consume' routine +the switch emulator suffers from an off-by-one error, if a +descriptor was to have more than allowed(ROCKER_TX_FRAGS_MAX=16) +fragments. Fix an incorrect bounds check to avoid it. + +Reported-by: Qinghao Tang +Cc: qemu-stable@nongnu.org +Signed-off-by: Prasad J Pandit +Signed-off-by: Jason Wang +--- + hw/net/rocker/rocker.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/hw/net/rocker/rocker.c b/hw/net/rocker/rocker.c +index c57f1a6..2e77e50 100644 +--- a/hw/net/rocker/rocker.c ++++ b/hw/net/rocker/rocker.c +@@ -232,6 +232,9 @@ static int tx_consume(Rocker *r, DescInfo *info) + frag_addr = rocker_tlv_get_le64(tlvs[ROCKER_TLV_TX_FRAG_ATTR_ADDR]); + frag_len = rocker_tlv_get_le16(tlvs[ROCKER_TLV_TX_FRAG_ATTR_LEN]); + ++ if (iovcnt >= ROCKER_TX_FRAGS_MAX) { ++ goto err_too_many_frags; ++ } + iov[iovcnt].iov_len = frag_len; + iov[iovcnt].iov_base = g_malloc(frag_len); + if (!iov[iovcnt].iov_base) { +@@ -244,10 +247,7 @@ static int tx_consume(Rocker *r, DescInfo *info) + err = -ROCKER_ENXIO; + goto err_bad_io; + } +- +- if (++iovcnt > ROCKER_TX_FRAGS_MAX) { +- goto err_too_many_frags; +- } ++ iovcnt++; + } + + if (iovcnt) { +-- +2.1.4 + diff --git a/debian/patches/extra/CVE-2015-8743-ne2000-ioport-bounds-check.patch b/debian/patches/extra/CVE-2015-8743-ne2000-ioport-bounds-check.patch new file mode 100644 index 0000000..5b34ecf --- /dev/null +++ b/debian/patches/extra/CVE-2015-8743-ne2000-ioport-bounds-check.patch @@ -0,0 +1,48 @@ +From ab216355b6d509dce42fda4391f61b49df2ddc93 Mon Sep 17 00:00:00 2001 +From: Prasad J Pandit +Date: Thu, 31 Dec 2015 17:05:27 +0530 +Subject: [PATCH] net: ne2000: fix bounds check in ioport operations + +While doing ioport r/w operations, ne2000 device emulation suffers +from OOB r/w errors. Update respective array bounds check to avoid +OOB access. + +Reported-by: Ling Liu +Cc: qemu-stable@nongnu.org +Signed-off-by: Prasad J Pandit +Signed-off-by: Jason Wang +--- + hw/net/ne2000.c | 10 ++++++---- + 1 file changed, 6 insertions(+), 4 deletions(-) + +diff --git a/hw/net/ne2000.c b/hw/net/ne2000.c +index 010f9ef..a3dffff 100644 +--- a/hw/net/ne2000.c ++++ b/hw/net/ne2000.c +@@ -467,8 +467,9 @@ static inline void ne2000_mem_writel(NE2000State *s, uint32_t addr, + uint32_t val) + { + addr &= ~1; /* XXX: check exact behaviour if not even */ +- if (addr < 32 || +- (addr >= NE2000_PMEM_START && addr < NE2000_MEM_SIZE)) { ++ if (addr < 32 ++ || (addr >= NE2000_PMEM_START ++ && addr + sizeof(uint32_t) <= NE2000_MEM_SIZE)) { + stl_le_p(s->mem + addr, val); + } + } +@@ -497,8 +498,9 @@ static inline uint32_t ne2000_mem_readw(NE2000State *s, uint32_t addr) + static inline uint32_t ne2000_mem_readl(NE2000State *s, uint32_t addr) + { + addr &= ~1; /* XXX: check exact behaviour if not even */ +- if (addr < 32 || +- (addr >= NE2000_PMEM_START && addr < NE2000_MEM_SIZE)) { ++ if (addr < 32 ++ || (addr >= NE2000_PMEM_START ++ && addr + sizeof(uint32_t) <= NE2000_MEM_SIZE)) { + return ldl_le_p(s->mem + addr); + } else { + return 0xffffffff; +-- +2.1.4 + diff --git a/debian/patches/extra/vmxnet3-host-memory-leakage.patch b/debian/patches/extra/vmxnet3-host-memory-leakage.patch new file mode 100644 index 0000000..dcc76b9 --- /dev/null +++ b/debian/patches/extra/vmxnet3-host-memory-leakage.patch @@ -0,0 +1,91 @@ +From 3ef66b01874fcc2fe3bfc73d2b61ee3a5b29fdb6 Mon Sep 17 00:00:00 2001 +From: Prasad J Pandit +Date: Tue, 15 Dec 2015 12:17:28 +0530 +Subject: [PATCH] net: vmxnet3: avoid memory leakage in activate_device + +Vmxnet3 device emulator does not check if the device is active +before activating it, also it did not free the transmit & receive +buffers while deactivating the device, thus resulting in memory +leakage on the host. This patch fixes both these issues to avoid +host memory leakage. + +Reported-by: Qinghao Tang +Signed-off-by: Prasad J Pandit +Reviewed-by: Dmitry Fleytman +--- + hw/net/vmxnet3.c | 24 ++++++++++++++++-------- + 1 file changed, 16 insertions(+), 8 deletions(-) + +diff --git a/hw/net/vmxnet3.c b/hw/net/vmxnet3.c +index 37373e5..2b4aad7 100644 +--- a/hw/net/vmxnet3.c ++++ b/hw/net/vmxnet3.c +@@ -1194,8 +1194,13 @@ static void vmxnet3_reset_mac(VMXNET3State *s) + + static void vmxnet3_deactivate_device(VMXNET3State *s) + { +- VMW_CBPRN("Deactivating vmxnet3..."); +- s->device_active = false; ++ if (s->device_active) { ++ VMW_CBPRN("Deactivating vmxnet3..."); ++ vmxnet_tx_pkt_reset(s->tx_pkt); ++ vmxnet_tx_pkt_uninit(s->tx_pkt); ++ vmxnet_rx_pkt_uninit(s->rx_pkt); ++ s->device_active = false; ++ } + } + + static void vmxnet3_reset(VMXNET3State *s) +@@ -1204,7 +1209,6 @@ static void vmxnet3_reset(VMXNET3State *s) + + vmxnet3_deactivate_device(s); + vmxnet3_reset_interrupt_states(s); +- vmxnet_tx_pkt_reset(s->tx_pkt); + s->drv_shmem = 0; + s->tx_sop = true; + s->skip_current_tx_pkt = false; +@@ -1431,6 +1435,12 @@ static void vmxnet3_activate_device(VMXNET3State *s) + return; + } + ++ /* Verify if device is active */ ++ if (s->device_active) { ++ VMW_CFPRN("Vmxnet3 device is active"); ++ return; ++ } ++ + vmxnet3_adjust_by_guest_type(s); + vmxnet3_update_features(s); + vmxnet3_update_pm_state(s); +@@ -1627,7 +1637,7 @@ static void vmxnet3_handle_command(VMXNET3State *s, uint64_t cmd) + break; + + case VMXNET3_CMD_QUIESCE_DEV: +- VMW_CBPRN("Set: VMXNET3_CMD_QUIESCE_DEV - pause the device"); ++ VMW_CBPRN("Set: VMXNET3_CMD_QUIESCE_DEV - deactivate the device"); + vmxnet3_deactivate_device(s); + break; + +@@ -1741,7 +1751,7 @@ vmxnet3_io_bar1_write(void *opaque, + * shared address only after we get the high part + */ + if (val == 0) { +- s->device_active = false; ++ vmxnet3_deactivate_device(s); + } + s->temp_shared_guest_driver_memory = val; + s->drv_shmem = 0; +@@ -2021,9 +2031,7 @@ static bool vmxnet3_peer_has_vnet_hdr(VMXNET3State *s) + static void vmxnet3_net_uninit(VMXNET3State *s) + { + g_free(s->mcast_list); +- vmxnet_tx_pkt_reset(s->tx_pkt); +- vmxnet_tx_pkt_uninit(s->tx_pkt); +- vmxnet_rx_pkt_uninit(s->rx_pkt); ++ vmxnet3_deactivate_device(s); + qemu_del_nic(s->nic); + } + +-- +2.4.3 + diff --git a/debian/patches/pve/0001-fr-ca-keymap-corrections.patch b/debian/patches/pve/0001-fr-ca-keymap-corrections.patch new file mode 100644 index 0000000..61a787c --- /dev/null +++ b/debian/patches/pve/0001-fr-ca-keymap-corrections.patch @@ -0,0 +1,48 @@ +From 8b78306f012aee8d9c24dca9c21b6d584535a152 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 14:15:49 +0100 +Subject: [PATCH 01/41] 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 b645208..9291240 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.1.4 + diff --git a/debian/patches/pve/0002-Adjust-network-script-path-to-etc-kvm.patch b/debian/patches/pve/0002-Adjust-network-script-path-to-etc-kvm.patch new file mode 100644 index 0000000..0620e6a --- /dev/null +++ b/debian/patches/pve/0002-Adjust-network-script-path-to-etc-kvm.patch @@ -0,0 +1,28 @@ +From 2644578e27b6f57a1008b0df56c02c9c96570077 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 14:16:49 +0100 +Subject: [PATCH 02/41] Adjust network script path to /etc/kvm/ + +--- + include/net/net.h | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/include/net/net.h b/include/net/net.h +index 7af3e15..f003ce0 100644 +--- a/include/net/net.h ++++ b/include/net/net.h +@@ -198,8 +198,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); + +-#define DEFAULT_NETWORK_SCRIPT "/etc/qemu-ifup" +-#define DEFAULT_NETWORK_DOWN_SCRIPT "/etc/qemu-ifdown" ++#define DEFAULT_NETWORK_SCRIPT "/etc/kvm/kvm-ifup" ++#define DEFAULT_NETWORK_DOWN_SCRIPT "/etc/kvm/kvm-ifdown" ++ + #define DEFAULT_BRIDGE_HELPER CONFIG_QEMU_HELPERDIR "/qemu-bridge-helper" + #define DEFAULT_BRIDGE_INTERFACE "br0" + +-- +2.1.4 + diff --git a/debian/patches/pve/0003-vnc-altgr-emulation.patch b/debian/patches/pve/0003-vnc-altgr-emulation.patch new file mode 100644 index 0000000..6d78360 --- /dev/null +++ b/debian/patches/pve/0003-vnc-altgr-emulation.patch @@ -0,0 +1,62 @@ +From 6a05de70fb7a5c34fdee915a0aa080a6bf76aba0 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 14:17:38 +0100 +Subject: [PATCH 03/41] vnc: altgr emulation + +--- + ui/vnc.c | 24 +++++++++++++++++++++++- + 1 file changed, 23 insertions(+), 1 deletion(-) + +diff --git a/ui/vnc.c b/ui/vnc.c +index cbe4d33..ae5a4fe 100644 +--- a/ui/vnc.c ++++ b/ui/vnc.c +@@ -1797,6 +1797,10 @@ static void kbd_leds(void *opaque, int ledstate) + + static void do_key_event(VncState *vs, int down, int keycode, int sym) + { ++ int mods = keycode & 0xf00; ++ ++ keycode &= SCANCODE_KEYMASK; ++ + /* QEMU console switch */ + switch(keycode) { + case 0x2a: /* Left Shift */ +@@ -1877,7 +1881,24 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym) + } + + if (qemu_console_is_graphic(NULL)) { ++ ++ /* our java vnc client never sends ALTGR, so we create ++ an artificial up/down event */ ++ ++ int emul_altgr = (mods & SCANCODE_ALTGR) && ++ !vs->modifiers_state[0xb8]; ++ ++ if (emul_altgr) { ++ reset_keys(vs); ++ qemu_input_event_send_key_number(vs->vd->dcl.con, 0xb8, true); ++ } ++ + qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, down); ++ ++ if (emul_altgr) { ++ qemu_input_event_send_key_number(vs->vd->dcl.con, 0xb8, false); ++ } ++ + } else { + bool numlock = vs->modifiers_state[0x45]; + bool control = (vs->modifiers_state[0x1d] || +@@ -2016,7 +2037,8 @@ static void key_event(VncState *vs, int down, uint32_t sym) + lsym = lsym - 'A' + 'a'; + } + +- keycode = keysym2scancode(vs->vd->kbd_layout, lsym & 0xFFFF) & SCANCODE_KEYMASK; ++ keycode = keysym2scancode(vs->vd->kbd_layout, lsym & 0xFFFF); ++ + trace_vnc_key_event_map(down, sym, keycode, code2name(keycode)); + do_key_event(vs, down, keycode, sym); + } +-- +2.1.4 + diff --git a/debian/patches/pve/0004-qemu-img-return-success-on-info-without-snapshots.patch b/debian/patches/pve/0004-qemu-img-return-success-on-info-without-snapshots.patch new file mode 100644 index 0000000..212fc61 --- /dev/null +++ b/debian/patches/pve/0004-qemu-img-return-success-on-info-without-snapshots.patch @@ -0,0 +1,26 @@ +From 12fbca3c45d694c89e84fb9522e9855b2b8a9952 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 14:18:46 +0100 +Subject: [PATCH 04/41] qemu-img: return success on info without snapshots + +--- + qemu-img.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/qemu-img.c b/qemu-img.c +index 033011c..e0637f2 100644 +--- a/qemu-img.c ++++ b/qemu-img.c +@@ -2112,7 +2112,8 @@ static int img_info(int argc, char **argv) + + list = collect_image_info_list(filename, fmt, chain); + if (!list) { +- return 1; ++ // return success if snapshot does not exists ++ return 0; + } + + switch (output_format) { +-- +2.1.4 + diff --git a/debian/patches/pve/0005-use-kvm-by-default.patch b/debian/patches/pve/0005-use-kvm-by-default.patch new file mode 100644 index 0000000..91e665a --- /dev/null +++ b/debian/patches/pve/0005-use-kvm-by-default.patch @@ -0,0 +1,27 @@ +From d6e2e550fee6c0c3468585c4885237a080468ecf Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 14:27:05 +0100 +Subject: [PATCH 05/41] use kvm by default + +--- + accel.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/accel.c b/accel.c +index 74e41da..66e2e58 100644 +--- a/accel.c ++++ b/accel.c +@@ -87,8 +87,8 @@ int configure_accelerator(MachineState *ms) + + p = qemu_opt_get(qemu_get_machine_opts(), "accel"); + if (p == NULL) { +- /* Use the default "accelerator", tcg */ +- p = "tcg"; ++ /* Use the default "accelerator", kvm */ ++ p = "kvm"; + } + + while (!accel_initialised && *p != '\0') { +-- +2.1.4 + diff --git a/debian/patches/pve/0006-virtio-balloon-fix-query.patch b/debian/patches/pve/0006-virtio-balloon-fix-query.patch new file mode 100644 index 0000000..a73a01f --- /dev/null +++ b/debian/patches/pve/0006-virtio-balloon-fix-query.patch @@ -0,0 +1,169 @@ +From f447be06707ec553c22a1f364b5a93e49409084f Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 14:27:49 +0100 +Subject: [PATCH 06/41] virtio-balloon: fix query + +Actually provide memory information via the query-balloon +command. +--- + hmp.c | 30 +++++++++++++++++++++++++++++- + hw/virtio/virtio-balloon.c | 33 +++++++++++++++++++++++++++++++-- + qapi-schema.json | 23 +++++++++++++++++++++-- + qmp-commands.hx | 13 +++++++++++++ + 4 files changed, 94 insertions(+), 5 deletions(-) + +diff --git a/hmp.c b/hmp.c +index 2140605..0e63ea8 100644 +--- a/hmp.c ++++ b/hmp.c +@@ -676,7 +676,35 @@ void hmp_info_balloon(Monitor *mon, const QDict *qdict) + return; + } + +- monitor_printf(mon, "balloon: actual=%" PRId64 "\n", info->actual >> 20); ++ monitor_printf(mon, "balloon: actual=%" PRId64, info->actual >> 20); ++ monitor_printf(mon, " max_mem=%" PRId64, info->max_mem >> 20); ++ if (info->has_total_mem) { ++ monitor_printf(mon, " total_mem=%" PRId64, info->total_mem >> 20); ++ } ++ if (info->has_free_mem) { ++ monitor_printf(mon, " free_mem=%" PRId64, info->free_mem >> 20); ++ } ++ ++ if (info->has_mem_swapped_in) { ++ monitor_printf(mon, " mem_swapped_in=%" PRId64, info->mem_swapped_in); ++ } ++ if (info->has_mem_swapped_out) { ++ monitor_printf(mon, " mem_swapped_out=%" PRId64, info->mem_swapped_out); ++ } ++ if (info->has_major_page_faults) { ++ monitor_printf(mon, " major_page_faults=%" PRId64, ++ info->major_page_faults); ++ } ++ if (info->has_minor_page_faults) { ++ monitor_printf(mon, " minor_page_faults=%" PRId64, ++ info->minor_page_faults); ++ } ++ if (info->has_last_update) { ++ monitor_printf(mon, " last_update=%" PRId64, ++ info->last_update); ++ } ++ ++ monitor_printf(mon, "\n"); + + qapi_free_BalloonInfo(info); + } +diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c +index 9671635..369c856 100644 +--- a/hw/virtio/virtio-balloon.c ++++ b/hw/virtio/virtio-balloon.c +@@ -324,8 +324,37 @@ static uint64_t virtio_balloon_get_features(VirtIODevice *vdev, uint64_t f, + static void virtio_balloon_stat(void *opaque, BalloonInfo *info) + { + VirtIOBalloon *dev = opaque; +- info->actual = get_current_ram_size() - ((uint64_t) dev->actual << +- VIRTIO_BALLOON_PFN_SHIFT); ++ ram_addr_t ram_size = get_current_ram_size(); ++ info->actual = ram_size - ((uint64_t) dev->actual << ++ VIRTIO_BALLOON_PFN_SHIFT); ++ ++ info->max_mem = ram_size; ++ ++ if (!(balloon_stats_enabled(dev) && balloon_stats_supported(dev) && ++ dev->stats_last_update)) { ++ return; ++ } ++ ++ info->last_update = dev->stats_last_update; ++ info->has_last_update = true; ++ ++ info->mem_swapped_in = dev->stats[VIRTIO_BALLOON_S_SWAP_IN]; ++ info->has_mem_swapped_in = info->mem_swapped_in >= 0 ? true : false; ++ ++ info->mem_swapped_out = dev->stats[VIRTIO_BALLOON_S_SWAP_OUT]; ++ info->has_mem_swapped_out = info->mem_swapped_out >= 0 ? true : false; ++ ++ info->major_page_faults = dev->stats[VIRTIO_BALLOON_S_MAJFLT]; ++ info->has_major_page_faults = info->major_page_faults >= 0 ? true : false; ++ ++ info->minor_page_faults = dev->stats[VIRTIO_BALLOON_S_MINFLT]; ++ info->has_minor_page_faults = info->minor_page_faults >= 0 ? true : false; ++ ++ info->free_mem = dev->stats[VIRTIO_BALLOON_S_MEMFREE]; ++ info->has_free_mem = info->free_mem >= 0 ? true : false; ++ ++ info->total_mem = dev->stats[VIRTIO_BALLOON_S_MEMTOT]; ++ info->has_total_mem = info->total_mem >= 0 ? true : false; + } + + static void virtio_balloon_to_target(void *opaque, ram_addr_t target) +diff --git a/qapi-schema.json b/qapi-schema.json +index 8b1a423..9f54ebf 100644 +--- a/qapi-schema.json ++++ b/qapi-schema.json +@@ -1145,10 +1145,29 @@ + # + # @actual: the number of bytes the balloon currently contains + # +-# Since: 0.14.0 ++# @last_update: #optional time when stats got updated from guest ++# ++# @mem_swapped_in: #optional number of pages swapped in within the guest ++# ++# @mem_swapped_out: #optional number of pages swapped out within the guest ++# ++# @major_page_faults: #optional number of major page faults within the guest + # ++# @minor_page_faults: #optional number of minor page faults within the guest ++# ++# @free_mem: #optional amount of memory (in bytes) free in the guest ++# ++# @total_mem: #optional amount of memory (in bytes) visible to the guest ++# ++# @max_mem: amount of memory (in bytes) assigned to the guest ++# ++# Since: 0.14.0 + ## +-{ 'struct': 'BalloonInfo', 'data': {'actual': 'int' } } ++{ 'struct': 'BalloonInfo', ++ 'data': {'actual': 'int', '*last_update': 'int', '*mem_swapped_in': 'int', ++ '*mem_swapped_out': 'int', '*major_page_faults': 'int', ++ '*minor_page_faults': 'int', '*free_mem': 'int', ++ '*total_mem': 'int', 'max_mem': 'int' } } + + ## + # @query-balloon: +diff --git a/qmp-commands.hx b/qmp-commands.hx +index 20a92f9..5c23d91 100644 +--- a/qmp-commands.hx ++++ b/qmp-commands.hx +@@ -3708,6 +3708,13 @@ Make an asynchronous request for balloon info. When the request completes a + json-object will be returned containing the following data: + + - "actual": current balloon value in bytes (json-int) ++- "mem_swapped_in": Amount of memory swapped in bytes (json-int, optional) ++- "mem_swapped_out": Amount of memory swapped out in bytes (json-int, optional) ++- "major_page_faults": Number of major faults (json-int, optional) ++- "minor_page_faults": Number of minor faults (json-int, optional) ++- "free_mem": Total amount of free and unused memory in ++ bytes (json-int, optional) ++- "total_mem": Total amount of available memory in bytes (json-int, optional) + + Example: + +@@ -3715,6 +3722,12 @@ Example: + <- { + "return":{ + "actual":1073741824, ++ "mem_swapped_in":0, ++ "mem_swapped_out":0, ++ "major_page_faults":142, ++ "minor_page_faults":239245, ++ "free_mem":1014185984, ++ "total_mem":1044668416 + } + } + +-- +2.1.4 + diff --git a/debian/patches/pve/0007-set-the-CPU-model-to-kvm64-32-instead-of-qemu64-32.patch b/debian/patches/pve/0007-set-the-CPU-model-to-kvm64-32-instead-of-qemu64-32.patch new file mode 100644 index 0000000..7ade5c2 --- /dev/null +++ b/debian/patches/pve/0007-set-the-CPU-model-to-kvm64-32-instead-of-qemu64-32.patch @@ -0,0 +1,28 @@ +From c868ddb3ae37b5d8932c491b3b8de46ac83c3189 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 14:30:21 +0100 +Subject: [PATCH 07/41] set the CPU model to kvm64/32 instead of qemu64/32 + +--- + hw/i386/pc.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/hw/i386/pc.c b/hw/i386/pc.c +index 5e20e07..a20df3c 100644 +--- a/hw/i386/pc.c ++++ b/hw/i386/pc.c +@@ -1128,9 +1128,9 @@ void pc_cpus_init(PCMachineState *pcms) + /* init CPUs */ + if (machine->cpu_model == NULL) { + #ifdef TARGET_X86_64 +- machine->cpu_model = "qemu64"; ++ machine->cpu_model = "kvm64"; + #else +- machine->cpu_model = "qemu32"; ++ machine->cpu_model = "kvm32"; + #endif + } + +-- +2.1.4 + diff --git a/debian/patches/pve/0008-qapi-modify-query-machines.patch b/debian/patches/pve/0008-qapi-modify-query-machines.patch new file mode 100644 index 0000000..eacc23b --- /dev/null +++ b/debian/patches/pve/0008-qapi-modify-query-machines.patch @@ -0,0 +1,52 @@ +From f3aa6523c91f0259f7168a8bbcf978b2e42168f7 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 14:31:18 +0100 +Subject: [PATCH 08/41] qapi: modify query machines + +provide '*is-current' in MachineInfo struct +--- + qapi-schema.json | 4 +++- + vl.c | 5 +++++ + 2 files changed, 8 insertions(+), 1 deletion(-) + +diff --git a/qapi-schema.json b/qapi-schema.json +index 9f54ebf..3ac59c5 100644 +--- a/qapi-schema.json ++++ b/qapi-schema.json +@@ -2790,6 +2790,8 @@ + # + # @default: #optional whether the machine is default + # ++# @current: #optional whether this machine is currently used ++# + # @cpu-max: maximum number of CPUs supported by the machine type + # (since 1.5.0) + # +@@ -2797,7 +2799,7 @@ + ## + { 'struct': 'MachineInfo', + 'data': { 'name': 'str', '*alias': 'str', +- '*is-default': 'bool', 'cpu-max': 'int' } } ++ '*is-default': 'bool', '*is-current': 'bool', 'cpu-max': 'int' } } + + ## + # @query-machines: +diff --git a/vl.c b/vl.c +index 4211ff1..77c734e 100644 +--- a/vl.c ++++ b/vl.c +@@ -1523,6 +1523,11 @@ MachineInfoList *qmp_query_machines(Error **errp) + info->name = g_strdup(mc->name); + info->cpu_max = !mc->max_cpus ? 1 : mc->max_cpus; + ++ if (strcmp(mc->name, MACHINE_GET_CLASS(current_machine)->name) == 0) { ++ info->has_is_current = true; ++ info->is_current = true; ++ } ++ + entry = g_malloc0(sizeof(*entry)); + entry->value = info; + entry->next = mach_list; +-- +2.1.4 + diff --git a/debian/patches/pve/0009-qapi-modify-spice-query.patch b/debian/patches/pve/0009-qapi-modify-spice-query.patch new file mode 100644 index 0000000..b726305 --- /dev/null +++ b/debian/patches/pve/0009-qapi-modify-spice-query.patch @@ -0,0 +1,49 @@ +From d72cdaed95ca8c8d39b367e187bcc03f0c3c3e42 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 14:32:11 +0100 +Subject: [PATCH 09/41] qapi: modify spice query + +Provide the last ticket in the SpiceInfo struct optionally. +--- + qapi-schema.json | 3 +++ + ui/spice-core.c | 5 +++++ + 2 files changed, 8 insertions(+) + +diff --git a/qapi-schema.json b/qapi-schema.json +index 3ac59c5..1aa434d 100644 +--- a/qapi-schema.json ++++ b/qapi-schema.json +@@ -1120,11 +1120,14 @@ + # + # @channels: a list of @SpiceChannel for each active spice channel + # ++# @ticket: #optional The last ticket set with set_password ++# + # Since: 0.14.0 + ## + { 'struct': 'SpiceInfo', + 'data': {'enabled': 'bool', 'migrated': 'bool', '*host': 'str', '*port': 'int', + '*tls-port': 'int', '*auth': 'str', '*compiled-version': 'str', ++ '*ticket': 'str', + 'mouse-mode': 'SpiceQueryMouseMode', '*channels': ['SpiceChannel']} } + + ## +diff --git a/ui/spice-core.c b/ui/spice-core.c +index 6a62d71..05d24a1 100644 +--- a/ui/spice-core.c ++++ b/ui/spice-core.c +@@ -537,6 +537,11 @@ SpiceInfo *qmp_query_spice(Error **errp) + micro = SPICE_SERVER_VERSION & 0xff; + info->compiled_version = g_strdup_printf("%d.%d.%d", major, minor, micro); + ++ if (auth_passwd) { ++ info->has_ticket = true; ++ info->ticket = g_strdup(auth_passwd); ++ } ++ + if (port) { + info->has_port = true; + info->port = port; +-- +2.1.4 + diff --git a/debian/patches/pve/0010-ui-spice-default-to-pve-certs-unless-otherwise-speci.patch b/debian/patches/pve/0010-ui-spice-default-to-pve-certs-unless-otherwise-speci.patch new file mode 100644 index 0000000..c25ed76 --- /dev/null +++ b/debian/patches/pve/0010-ui-spice-default-to-pve-certs-unless-otherwise-speci.patch @@ -0,0 +1,59 @@ +From 91c8e884d18a4b76d30422b977ee03386eebe73d Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 14:33:34 +0100 +Subject: [PATCH 10/41] ui/spice: default to pve certs unless otherwise + specified + +--- + ui/spice-core.c | 15 +++++++++------ + 1 file changed, 9 insertions(+), 6 deletions(-) + +diff --git a/ui/spice-core.c b/ui/spice-core.c +index 05d24a1..6ed91f6 100644 +--- a/ui/spice-core.c ++++ b/ui/spice-core.c +@@ -669,32 +669,35 @@ void qemu_spice_init(void) + + if (tls_port) { + x509_dir = qemu_opt_get(opts, "x509-dir"); +- if (!x509_dir) { +- x509_dir = "."; +- } + + str = qemu_opt_get(opts, "x509-key-file"); + if (str) { + x509_key_file = g_strdup(str); +- } else { ++ } else if (x509_dir) { + x509_key_file = g_strdup_printf("%s/%s", x509_dir, + X509_SERVER_KEY_FILE); ++ } else { ++ x509_key_file = g_strdup("/etc/pve/local/pve-ssl.key"); + } + + str = qemu_opt_get(opts, "x509-cert-file"); + if (str) { + x509_cert_file = g_strdup(str); +- } else { ++ } else if (x509_dir) { + x509_cert_file = g_strdup_printf("%s/%s", x509_dir, + X509_SERVER_CERT_FILE); ++ } else { ++ x509_cert_file = g_strdup("/etc/pve/local/pve-ssl.pem"); + } + + str = qemu_opt_get(opts, "x509-cacert-file"); + if (str) { + x509_cacert_file = g_strdup(str); +- } else { ++ } else if (x509_dir) { + x509_cacert_file = g_strdup_printf("%s/%s", x509_dir, + X509_CA_CERT_FILE); ++ } else { ++ x509_cacert_file = g_strdup("/etc/pve/pve-root-ca.pem"); + } + + x509_key_password = qemu_opt_get(opts, "x509-key-password"); +-- +2.1.4 + diff --git a/debian/patches/pve/0011-introduce-new-vma-archive-format.patch b/debian/patches/pve/0011-introduce-new-vma-archive-format.patch new file mode 100644 index 0000000..3e0fab3 --- /dev/null +++ b/debian/patches/pve/0011-introduce-new-vma-archive-format.patch @@ -0,0 +1,2484 @@ +From 04b6468a295871877b55fe792c566839e2afe29c Mon Sep 17 00:00:00 2001 +From: Dietmar Maurer +Date: Tue, 13 Nov 2012 11:11:38 +0100 +Subject: [PATCH 11/41] introduce new vma archive format + +This is a very simple archive format, see docs/specs/vma_spec.txt + +Signed-off-by: Dietmar Maurer +--- + Makefile | 3 +- + Makefile.objs | 1 + + vma-reader.c | 799 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + vma-writer.c | 876 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + vma.c | 582 ++++++++++++++++++++++++++++++++++++++ + vma.h | 146 ++++++++++ + 6 files changed, 2406 insertions(+), 1 deletion(-) + 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 930ac27..6cedc40 100644 +--- a/Makefile ++++ b/Makefile +@@ -166,7 +166,7 @@ ifneq ($(wildcard config-host.mak),) + include $(SRC_PATH)/tests/Makefile + endif + +-all: $(DOCS) $(TOOLS) $(HELPERS-y) recurse-all modules ++all: $(DOCS) $(TOOLS) vma$(EXESUF) $(HELPERS-y) recurse-all modules + + config-host.h: config-host.h-timestamp + config-host.h-timestamp: config-host.mak +@@ -235,6 +235,7 @@ qemu-img.o: qemu-img-cmds.h + qemu-img$(EXESUF): qemu-img.o $(block-obj-y) $(crypto-obj-y) $(qom-obj-y) libqemuutil.a libqemustub.a + qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) $(crypto-obj-y) $(qom-obj-y) libqemuutil.a libqemustub.a + qemu-io$(EXESUF): qemu-io.o $(block-obj-y) $(crypto-obj-y) $(qom-obj-y) libqemuutil.a libqemustub.a ++vma$(EXESUF): vma.o vma-reader.o $(block-obj-y) $(crypto-obj-y) $(qom-obj-y) libqemuutil.a libqemustub.a + + qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o + +diff --git a/Makefile.objs b/Makefile.objs +index 77be052..97d2bcc 100644 +--- a/Makefile.objs ++++ b/Makefile.objs +@@ -14,6 +14,7 @@ block-obj-$(CONFIG_POSIX) += aio-posix.o + block-obj-$(CONFIG_WIN32) += aio-win32.o + block-obj-y += block/ + block-obj-y += qemu-io-cmds.o ++block-obj-y += vma-writer.o + + block-obj-m = block/ + +diff --git a/vma-reader.c b/vma-reader.c +new file mode 100644 +index 0000000..bc36cba +--- /dev/null ++++ b/vma-reader.c +@@ -0,0 +1,799 @@ ++/* ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "qemu-common.h" ++#include "qemu/timer.h" ++#include "qemu/ratelimit.h" ++#include "vma.h" ++#include "block/block.h" ++ ++#define BITS_PER_LONG (sizeof(unsigned long) * CHAR_BIT) ++ ++static unsigned char zero_vma_block[VMA_BLOCK_SIZE]; ++ ++typedef struct VmaRestoreState { ++ BlockDriverState *bs; ++ 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; ++ 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; ++ } ++ } ++ } ++ ++ if (!count) { ++ error_setg(errp, "vma does not contain data"); ++ return -1; ++ } ++ ++ 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; ++} ++ ++int vma_reader_register_bs(VmaReader *vmar, guint8 dev_id, BlockDriverState *bs, ++ bool write_zeroes, Error **errp) ++{ ++ assert(vmar); ++ assert(bs != NULL); ++ assert(dev_id); ++ assert(vmar->rstate[dev_id].bs == NULL); ++ ++ int64_t size = bdrv_getlength(bs); ++ if (size != vmar->devinfo[dev_id].size) { ++ 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; ++ } ++ ++ vmar->rstate[dev_id].bs = bs; ++ vmar->rstate[dev_id].write_zeroes = write_zeroes; ++ ++ 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; ++ ++ 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, ++ BlockDriverState *bs, 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 = bdrv_write(bs, sector_num, buf, nb_sectors); ++ if (res < 0) { ++ error_setg(errp, "bdrv_write to %s failed (%d)", ++ bdrv_get_device_name(bs), res); ++ return -1; ++ } ++ } ++ return 0; ++} ++static int restore_extent(VmaReader *vmar, unsigned char *buf, ++ int extent_size, int vmstate_fd, ++ bool verbose, 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]; ++ BlockDriverState *bs = NULL; ++ ++ if (dev_id != vmar->vmstate_stream) { ++ bs = rstate->bs; ++ if (!bs) { ++ 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; ++ } ++ ++ int nb_sectors = end_sector - sector_num; ++ if (restore_write_data(vmar, dev_id, bs, 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; ++ } ++ ++ int nb_sectors = end_sector - sector_num; ++ if (restore_write_data(vmar, dev_id, bs, vmstate_fd, ++ buf + start, sector_num, ++ nb_sectors, errp) < 0) { ++ return -1; ++ } ++ ++ start += VMA_BLOCK_SIZE; ++ ++ } else { ++ ++ if (rstate->write_zeroes && (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; ++ if (restore_write_data(vmar, dev_id, bs, 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; ++} ++ ++int vma_reader_restore(VmaReader *vmar, int vmstate_fd, bool verbose, ++ 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, ++ 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->bs) { ++ continue; ++ } ++ ++ if (bdrv_flush(rstate->bs) < 0) { ++ error_setg(errp, "vma bdrv_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; ++ } ++ } ++ } ++ } ++ ++ return ret; ++} ++ +diff --git a/vma-writer.c b/vma-writer.c +new file mode 100644 +index 0000000..8a3fa1c +--- /dev/null ++++ b/vma-writer.c +@@ -0,0 +1,876 @@ ++/* ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "qemu-common.h" ++#include "vma.h" ++#include "block/block.h" ++#include "monitor/monitor.h" ++#include "qemu/main-loop.h" ++ ++#define DEBUG_VMA 0 ++ ++#define DPRINTF(fmt, ...)\ ++ do { if (DEBUG_VMA) { printf("vma: " fmt, ## __VA_ARGS__); } } while (0) ++ ++#define WRITE_BUFFERS 5 ++ ++typedef struct VmaAIOCB VmaAIOCB; ++struct VmaAIOCB { ++ unsigned char buffer[VMA_MAX_EXTENT_SIZE]; ++ VmaWriter *vmaw; ++ size_t bytes; ++ Coroutine *co; ++}; ++ ++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[VMA_MAX_EXTENT_SIZE]; ++ int outbuf_pos; /* in bytes */ ++ int outbuf_count; /* in VMA_BLOCKS */ ++ uint64_t outbuf_block_info[VMA_BLOCKS_PER_EXTENT]; ++ ++ VmaAIOCB *aiocbs[WRITE_BUFFERS]; ++ CoQueue wqueue; ++ ++ GChecksum *md5csum; ++ CoMutex writer_lock; ++ 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); ++ assert(len); ++ ++ 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, NULL); ++} ++ ++static ssize_t coroutine_fn ++vma_co_write(VmaWriter *vmaw, const void *buf, size_t bytes) ++{ ++ size_t done = 0; ++ ssize_t ret; ++ ++ /* atomic writes (we cannot interleave writes) */ ++ qemu_co_mutex_lock(&vmaw->writer_lock); ++ ++ DPRINTF("vma_co_write enter %zd\n", bytes); ++ ++ assert(vmaw->co_writer == NULL); ++ ++ vmaw->co_writer = qemu_coroutine_self(); ++ ++ aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, NULL, vma_co_continue_write, vmaw); ++ ++ DPRINTF("vma_co_write wait until writable\n"); ++ qemu_coroutine_yield(); ++ DPRINTF("vma_co_write starting %zd\n", bytes); ++ ++ while (done < bytes) { ++ ret = write(vmaw->fd, buf + done, bytes - done); ++ if (ret > 0) { ++ done += ret; ++ DPRINTF("vma_co_write written %zd %zd\n", done, ret); ++ } else if (ret < 0) { ++ if (errno == EAGAIN || errno == EWOULDBLOCK) { ++ DPRINTF("vma_co_write yield %zd\n", done); ++ qemu_coroutine_yield(); ++ DPRINTF("vma_co_write restart %zd\n", done); ++ } else { ++ vma_writer_set_error(vmaw, "vma_co_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 */ ++ } ++ } ++ ++ aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, NULL, NULL, NULL); ++ ++ vmaw->co_writer = NULL; ++ ++ qemu_co_mutex_unlock(&vmaw->writer_lock); ++ ++ DPRINTF("vma_co_write leave %zd\n", done); ++ return done; ++} ++ ++static void coroutine_fn vma_co_writer_task(void *opaque) ++{ ++ VmaAIOCB *cb = opaque; ++ ++ DPRINTF("vma_co_writer_task start\n"); ++ ++ int64_t done = vma_co_write(cb->vmaw, cb->buffer, cb->bytes); ++ DPRINTF("vma_co_writer_task write done %zd\n", done); ++ ++ if (done != cb->bytes) { ++ DPRINTF("vma_co_writer_task failed write %zd %zd", cb->bytes, done); ++ vma_writer_set_error(cb->vmaw, "vma_co_writer_task failed write %zd", ++ done); ++ } ++ ++ cb->bytes = 0; ++ ++ qemu_co_queue_next(&cb->vmaw->wqueue); ++ ++ DPRINTF("vma_co_writer_task end\n"); ++} ++ ++static void coroutine_fn vma_queue_flush(VmaWriter *vmaw) ++{ ++ DPRINTF("vma_queue_flush enter\n"); ++ ++ assert(vmaw); ++ ++ while (1) { ++ int i; ++ VmaAIOCB *cb = NULL; ++ for (i = 0; i < WRITE_BUFFERS; i++) { ++ if (vmaw->aiocbs[i]->bytes) { ++ cb = vmaw->aiocbs[i]; ++ DPRINTF("FOUND USED AIO BUFFER %d %zd\n", i, ++ vmaw->aiocbs[i]->bytes); ++ break; ++ } ++ } ++ if (!cb) { ++ break; ++ } ++ qemu_co_queue_wait(&vmaw->wqueue); ++ } ++ ++ DPRINTF("vma_queue_flush leave\n"); ++} ++ ++/** ++ * NOTE: pipe buffer size in only 4096 bytes on linux (see 'ulimit -a') ++ * So we need to create a coroutione to allow 'parallel' execution. ++ */ ++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); ++ ++ VmaAIOCB *cb = NULL; ++ while (!cb) { ++ int i; ++ for (i = 0; i < WRITE_BUFFERS; i++) { ++ if (!vmaw->aiocbs[i]->bytes) { ++ cb = vmaw->aiocbs[i]; ++ break; ++ } ++ } ++ if (!cb) { ++ qemu_co_queue_wait(&vmaw->wqueue); ++ } ++ } ++ ++ memcpy(cb->buffer, buf, bytes); ++ cb->bytes = bytes; ++ cb->vmaw = vmaw; ++ ++ DPRINTF("vma_queue_write start %zd\n", bytes); ++ cb->co = qemu_coroutine_create(vma_co_writer_task); ++ qemu_coroutine_enter(cb->co, cb); ++ ++ DPRINTF("vma_queue_write leave\n"); ++ ++ return bytes; ++} ++ ++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 and O_DIRECT */ ++ fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_NONBLOCK); ++ fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_DIRECT); ++ ++ } 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_DIRECT|O_WRONLY; ++ vmaw->fd = qemu_open(filename, oflags, 0644); ++ } else if (strstart(filename, "/dev/fdset/", &tmp_id_str)) { ++ oflags = O_NONBLOCK|O_DIRECT|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 and O_DIRECT */ ++ fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_NONBLOCK); ++ fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_DIRECT); ++ } 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 */ ++ int i; ++ for (i = 0; i < WRITE_BUFFERS; i++) { ++ vmaw->aiocbs[i] = qemu_memalign(512, sizeof(VmaAIOCB)); ++ memset(vmaw->aiocbs[i], 0, sizeof(VmaAIOCB)); ++ } ++ ++ 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->writer_lock); ++ qemu_co_mutex_init(&vmaw->flush_lock); ++ qemu_co_queue_init(&vmaw->wqueue); ++ ++ 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); ++ int header_clusters = 8; ++ char buf[65536*header_clusters]; ++ VmaHeader *head = (VmaHeader *)buf; ++ ++ int i; ++ ++ DPRINTF("VMA WRITE HEADER\n"); ++ ++ if (vmaw->status < 0) { ++ return vmaw->status; ++ } ++ ++ memset(buf, 0, sizeof(buf)); ++ ++ 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); ++ ++ if (!vmaw->stream_count) { ++ return -1; ++ } ++ ++ 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 > sizeof(buf)) { ++ 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; ++} ++ ++/** ++ * 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"); ++ 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_close_stream: flush failed"); ++ } ++ } ++ ++ 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; ++} ++ ++int vma_writer_close(VmaWriter *vmaw, Error **errp) ++{ ++ g_assert(vmaw != NULL); ++ ++ int i; ++ ++ vma_queue_flush(vmaw); ++ ++ /* this should not happen - just to be sure */ ++ while (!qemu_co_queue_empty(&vmaw->wqueue)) { ++ DPRINTF("vma_writer_close wait\n"); ++ co_aio_sleep_ns(qemu_get_aio_context(), QEMU_CLOCK_REALTIME, 1000000); ++ } ++ ++ 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); ++ } ++ ++ for (i = 0; i < WRITE_BUFFERS; i++) { ++ free(vmaw->aiocbs[i]); ++ } ++ ++ g_free(vmaw); ++} +diff --git a/vma.c b/vma.c +new file mode 100644 +index 0000000..86c117b +--- /dev/null ++++ b/vma.c +@@ -0,0 +1,582 @@ ++/* ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "vma.h" ++#include "qemu-common.h" ++#include "qemu/error-report.h" ++#include "qemu/main-loop.h" ++ ++static void help(void) ++{ ++ const char *help_msg = ++ "usage: vma command [command options]\n" ++ "\n" ++ "vma list \n" ++ "vma create [-c config] pathname ...\n" ++ "vma extract [-r ] \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; ++ 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; ++ } ++ } ++ ++ const char *path; ++ bool write_zero; ++ if (line[0] == '0' && line[1] == ':') { ++ path = inbuf + 2; ++ write_zero = false; ++ } else if (line[0] == '1' && line[1] == ':') { ++ path = inbuf + 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->write_zero = write_zero; ++ ++ g_hash_table_insert(devmap, map->devname, map); ++ ++ }; ++ } ++ ++ int i; ++ int vmstate_fd = -1; ++ guint8 vmstate_stream = 0; ++ ++ for (i = 1; i < 255; i++) { ++ VmaDeviceInfo *di = vma_reader_get_device_info(vmar, i); ++ if (di && (strcmp(di->devname, "vmstate") == 0)) { ++ vmstate_stream = i; ++ char *statefn = g_strdup_printf("%s/vmstate.bin", dirname); ++ vmstate_fd = open(statefn, O_WRONLY|O_CREAT|O_EXCL, 0644); ++ if (vmstate_fd < 0) { ++ g_error("create vmstate file '%s' failed - %s", statefn, ++ g_strerror(errno)); ++ } ++ g_free(statefn); ++ } else if (di) { ++ char *devfn = NULL; ++ int flags = BDRV_O_RDWR|BDRV_O_CACHE_WB; ++ 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; ++ 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; ++ } ++ ++ BlockDriverState *bs = bdrv_new(); ++ if (errp || bdrv_open(&bs, devfn, NULL, NULL, flags, NULL, &errp)) { ++ g_error("can't open file %s - %s", devfn, ++ error_get_pretty(errp)); ++ } ++ if (vma_reader_register_bs(vmar, i, bs, write_zero, &errp) < 0) { ++ g_error("%s", error_get_pretty(errp)); ++ } ++ ++ if (!readmap) { ++ g_free(devfn); ++ } ++ } ++ } ++ ++ if (vma_reader_restore(vmar, vmstate_fd, verbose, &errp) < 0) { ++ g_error("restore failed - %s", error_get_pretty(errp)); ++ } ++ ++ if (!readmap) { ++ for (i = 1; i < 255; i++) { ++ VmaDeviceInfo *di = vma_reader_get_device_info(vmar, i); ++ if (di && (i != vmstate_stream)) { ++ char *tmpfn = g_strdup_printf("%s/tmp-disk-%s.raw", ++ dirname, di->devname); ++ char *fn = g_strdup_printf("%s/disk-%s.raw", ++ dirname, di->devname); ++ if (rename(tmpfn, fn) != 0) { ++ g_error("rename %s to %s failed - %s", ++ tmpfn, fn, g_strerror(errno)); ++ } ++ } ++ } ++ } ++ ++ vma_reader_destroy(vmar); ++ ++ bdrv_close_all(); ++ ++ return ret; ++} ++ ++typedef struct BackupJob { ++ BlockDriverState *bs; ++ 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(void *opaque) ++{ ++ BackupJob *job = (BackupJob *)opaque; ++ struct iovec iov; ++ QEMUIOVector qiov; ++ ++ int64_t start, end; ++ int ret = 0; ++ ++ unsigned char *buf = qemu_blockalign(job->bs, 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 = bdrv_co_readv(job->bs, start * BACKUP_SECTORS_PER_CLUSTER, ++ BACKUP_SECTORS_PER_CLUSTER, &qiov); ++ 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, res; ++ 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 have archive name and at least one path */ ++ if ((optind + 2) > 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 ind = 0; ++ while (optind < argc) { ++ const char *path = argv[optind++]; ++ char *devname = NULL; ++ path = extract_devname(path, &devname, ind++); ++ ++ BlockDriver *drv = NULL; ++ Error *errp = NULL; ++ BlockDriverState *bs = bdrv_new(); ++ ++ res = bdrv_open(&bs, path, NULL, NULL, BDRV_O_CACHE_WB , drv, &errp); ++ if (res < 0) { ++ unlink(archivename); ++ g_error("bdrv_open '%s' failed - %s", path, error_get_pretty(errp)); ++ } ++ int64_t size = bdrv_getlength(bs); ++ 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->bs = bs; ++ job->vmaw = vmaw; ++ job->dev_id = dev_id; ++ ++ Coroutine *co = qemu_coroutine_create(backup_run); ++ qemu_coroutine_enter(co, job); ++ } ++ ++ VmaStatus vmastat; ++ int percent = 0; ++ int last_percent = -1; ++ ++ 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; ++ } ++ } ++ ++ 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; ++} ++ ++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); ++ } ++ ++ help(); ++ return 0; ++} +diff --git a/vma.h b/vma.h +new file mode 100644 +index 0000000..6625eb9 +--- /dev/null ++++ b/vma.h +@@ -0,0 +1,146 @@ ++/* ++ * 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 ++#include "qapi/error.h" ++#include "block/block.h" ++ ++#define VMA_BLOCK_BITS 12 ++#define VMA_BLOCK_SIZE (1< +Date: Mon, 11 Mar 2013 07:07:46 +0100 +Subject: [PATCH 12/41] vma: add verify command + +Users wants to verify the archive after backup. + +Examples: + + # vma verify -v test.vma + + # lzop -d -c test.vma.lzo |vma verify - + +Signed-off-by: Dietmar Maurer +--- + vma-reader.c | 121 ++++++++++++++++++++++++++++++++++++++++++++--------------- + vma.c | 55 +++++++++++++++++++++++++++ + vma.h | 1 + + 3 files changed, 147 insertions(+), 30 deletions(-) + +diff --git a/vma-reader.c b/vma-reader.c +index bc36cba..9d92c6a 100644 +--- a/vma-reader.c ++++ b/vma-reader.c +@@ -53,6 +53,8 @@ struct VmaReader { + 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; + }; + +@@ -433,6 +435,27 @@ VmaDeviceInfo *vma_reader_get_device_info(VmaReader *vmar, guint8 dev_id) + return NULL; + } + ++static void allocate_rstate(VmaReader *vmar, guint8 dev_id, ++ BlockDriverState *bs, bool write_zeroes) ++{ ++ assert(vmar); ++ assert(dev_id); ++ ++ vmar->rstate[dev_id].bs = bs; ++ 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, BlockDriverState *bs, + bool write_zeroes, Error **errp) + { +@@ -449,17 +472,7 @@ int vma_reader_register_bs(VmaReader *vmar, guint8 dev_id, BlockDriverState *bs, + return -1; + } + +- vmar->rstate[dev_id].bs = bs; +- vmar->rstate[dev_id].write_zeroes = write_zeroes; +- +- 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; ++ allocate_rstate(vmar, dev_id, bs, write_zeroes); + + return 0; + } +@@ -526,9 +539,10 @@ static int restore_write_data(VmaReader *vmar, guint8 dev_id, + } + return 0; + } ++ + static int restore_extent(VmaReader *vmar, unsigned char *buf, + int extent_size, int vmstate_fd, +- bool verbose, Error **errp) ++ bool verbose, bool verify, Error **errp) + { + assert(vmar); + assert(buf); +@@ -553,7 +567,7 @@ static int restore_extent(VmaReader *vmar, unsigned char *buf, + + if (dev_id != vmar->vmstate_stream) { + bs = rstate->bs; +- if (!bs) { ++ if (!verify && !bs) { + error_setg(errp, "got wrong dev id %d", dev_id); + return -1; + } +@@ -609,10 +623,13 @@ static int restore_extent(VmaReader *vmar, unsigned char *buf, + return -1; + } + +- int nb_sectors = end_sector - sector_num; +- if (restore_write_data(vmar, dev_id, bs, vmstate_fd, buf + start, +- sector_num, nb_sectors, errp) < 0) { +- return -1; ++ if (!verify) { ++ int nb_sectors = end_sector - sector_num; ++ if (restore_write_data(vmar, dev_id, bs, vmstate_fd, ++ buf + start, sector_num, nb_sectors, ++ errp) < 0) { ++ return -1; ++ } + } + + start += VMA_CLUSTER_SIZE; +@@ -642,26 +659,37 @@ static int restore_extent(VmaReader *vmar, unsigned char *buf, + return -1; + } + +- int nb_sectors = end_sector - sector_num; +- if (restore_write_data(vmar, dev_id, bs, vmstate_fd, +- buf + start, sector_num, +- nb_sectors, errp) < 0) { +- return -1; ++ if (!verify) { ++ int nb_sectors = end_sector - sector_num; ++ if (restore_write_data(vmar, dev_id, bs, vmstate_fd, ++ buf + start, sector_num, ++ nb_sectors, errp) < 0) { ++ return -1; ++ } + } + + start += VMA_BLOCK_SIZE; + + } else { + +- if (rstate->write_zeroes && (end_sector > sector_num)) { ++ ++ 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; +- if (restore_write_data(vmar, dev_id, bs, vmstate_fd, +- zero_vma_block, sector_num, +- nb_sectors, errp) < 0) { +- return -1; ++ 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, bs, vmstate_fd, ++ zero_vma_block, sector_num, ++ nb_sectors, errp) < 0) { ++ return -1; ++ } + } + } + } +@@ -679,8 +707,9 @@ static int restore_extent(VmaReader *vmar, unsigned char *buf, + return 0; + } + +-int vma_reader_restore(VmaReader *vmar, int vmstate_fd, bool verbose, +- Error **errp) ++static int vma_reader_restore_full(VmaReader *vmar, int vmstate_fd, ++ bool verbose, bool verify, ++ Error **errp) + { + assert(vmar); + assert(vmar->head_data); +@@ -747,7 +776,7 @@ int vma_reader_restore(VmaReader *vmar, int vmstate_fd, bool verbose, + } + + if (restore_extent(vmar, buf, extent_size, vmstate_fd, verbose, +- errp) < 0) { ++ verify, errp) < 0) { + return -1; + } + +@@ -794,6 +823,38 @@ int vma_reader_restore(VmaReader *vmar, int vmstate_fd, bool verbose, + } + } + ++ if (verbose) { ++ 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); ++ } ++ } + 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.c b/vma.c +index 86c117b..3ad2f18 100644 +--- a/vma.c ++++ b/vma.c +@@ -34,6 +34,7 @@ static void help(void) + "vma list \n" + "vma create [-c config] pathname ...\n" + "vma extract [-r ] \n" ++ "vma verify [-v]\n" + ; + + printf("%s", help_msg); +@@ -338,6 +339,58 @@ static int extract_content(int argc, char **argv) + 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 { + BlockDriverState *bs; + int64_t len; +@@ -575,6 +628,8 @@ int main(int argc, char **argv) + 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); + } + + help(); +diff --git a/vma.h b/vma.h +index 6625eb9..9bb6ea4 100644 +--- a/vma.h ++++ b/vma.h +@@ -142,5 +142,6 @@ int vma_reader_register_bs(VmaReader *vmar, guint8 dev_id, + 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.1.4 + diff --git a/debian/patches/pve/0013-vma-add-config-command-to-dump-the-config.patch b/debian/patches/pve/0013-vma-add-config-command-to-dump-the-config.patch new file mode 100644 index 0000000..6e1e0dc --- /dev/null +++ b/debian/patches/pve/0013-vma-add-config-command-to-dump-the-config.patch @@ -0,0 +1,101 @@ +From 540980ecff5c1b5b1c21ef8493db51dbe5a4be69 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 14:46:49 +0100 +Subject: [PATCH 13/41] vma: add 'config' command to dump the config + +--- + vma.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 64 insertions(+) + +diff --git a/vma.c b/vma.c +index 3ad2f18..6a33352 100644 +--- a/vma.c ++++ b/vma.c +@@ -32,6 +32,7 @@ static void help(void) + "usage: vma command [command options]\n" + "\n" + "vma list \n" ++ "vma config [-c config]\n" + "vma create [-c config] pathname ...\n" + "vma extract [-r ] \n" + "vma verify [-v]\n" +@@ -601,6 +602,67 @@ static int create_archive(int argc, char **argv) + 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; +@@ -630,6 +692,8 @@ int main(int argc, char **argv) + 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(); +-- +2.1.4 + diff --git a/debian/patches/pve/0014-vma-restore-tolerate-a-size-difference-up-to-4M.patch b/debian/patches/pve/0014-vma-restore-tolerate-a-size-difference-up-to-4M.patch new file mode 100644 index 0000000..a867d28 --- /dev/null +++ b/debian/patches/pve/0014-vma-restore-tolerate-a-size-difference-up-to-4M.patch @@ -0,0 +1,32 @@ +From e80fcf151b28c470fbf97cf38b68acec48ec7d18 Mon Sep 17 00:00:00 2001 +From: Dietmar Maurer +Date: Tue, 26 Mar 2013 06:21:16 +0100 +Subject: [PATCH 14/41] vma restore: tolerate a size difference up to 4M + +Signed-off-by: Dietmar Maurer +--- + vma-reader.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/vma-reader.c b/vma-reader.c +index 9d92c6a..d9f43fe 100644 +--- a/vma-reader.c ++++ b/vma-reader.c +@@ -465,7 +465,13 @@ int vma_reader_register_bs(VmaReader *vmar, guint8 dev_id, BlockDriverState *bs, + assert(vmar->rstate[dev_id].bs == NULL); + + int64_t size = bdrv_getlength(bs); +- if (size != vmar->devinfo[dev_id].size) { ++ 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); +-- +2.1.4 + diff --git a/debian/patches/pve/0015-backup-modify-job-api.patch b/debian/patches/pve/0015-backup-modify-job-api.patch new file mode 100644 index 0000000..a4730f4 --- /dev/null +++ b/debian/patches/pve/0015-backup-modify-job-api.patch @@ -0,0 +1,214 @@ +From 2ec6f9ec8443f3389c74e8e21353d01e516c4f46 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 15:04:57 +0100 +Subject: [PATCH 15/41] backup: modify job api + +Introduces a BackupDump function callback and a pause_count +for backup_start. For a dump-backup the target parameter +can now be NULL so access to target needs to be guarded now. +--- + block/backup.c | 57 +++++++++++++++++++++++++++++++---------------- + blockdev.c | 6 ++--- + include/block/block_int.h | 5 +++++ + 3 files changed, 46 insertions(+), 22 deletions(-) + +diff --git a/block/backup.c b/block/backup.c +index 705bb77..7ac6b29 100644 +--- a/block/backup.c ++++ b/block/backup.c +@@ -43,6 +43,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; +@@ -147,14 +148,21 @@ static int coroutine_fn backup_do_cow(BlockDriverState *bs, + goto out; + } + ++ int64_t start_sec = start * BACKUP_SECTORS_PER_CLUSTER; + if (buffer_is_zero(iov.iov_base, iov.iov_len)) { +- ret = bdrv_co_write_zeroes(job->target, +- start * BACKUP_SECTORS_PER_CLUSTER, +- n, 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 = bdrv_co_write_zeroes(job->target, start_sec, n, BDRV_REQ_MAY_UNMAP); ++ } + } else { +- ret = bdrv_co_writev(job->target, +- start * BACKUP_SECTORS_PER_CLUSTER, n, +- &bounce_qiov); ++ if (job->dump_cb) { ++ ret = job->dump_cb(job->common.opaque, job->target, start_sec, n, bounce_buffer); ++ } ++ if (job->target) { ++ ret = bdrv_co_writev(job->target, start_sec, n, &bounce_qiov); ++ } + } + if (ret < 0) { + trace_backup_do_cow_write_fail(job, start, ret); +@@ -216,7 +224,7 @@ static void backup_iostatus_reset(BlockJob *job) + { + BackupBlockJob *s = container_of(job, BackupBlockJob, common); + +- if (s->target->blk) { ++ if (s->target && s->target->blk) { + blk_iostatus_reset(s->target->blk); + } + } +@@ -268,9 +276,11 @@ static BlockErrorAction backup_error_action(BackupBlockJob *job, + if (read) { + return block_job_error_action(&job->common, job->common.bs, + job->on_source_error, true, error); +- } else { ++ } else if (job->target) { + return block_job_error_action(&job->common, job->target, + job->on_target_error, false, error); ++ } else { ++ return BLOCK_ERROR_ACTION_REPORT; + } + } + +@@ -396,10 +406,12 @@ static void coroutine_fn backup_run(void *opaque) + + job->bitmap = hbitmap_alloc(end, 0); + +- bdrv_set_enable_write_cache(target, true); +- if (target->blk) { +- blk_set_on_error(target->blk, on_target_error, on_target_error); +- blk_iostatus_enable(target->blk); ++ if (target) { ++ bdrv_set_enable_write_cache(target, true); ++ if (target->blk) { ++ blk_set_on_error(target->blk, on_target_error, on_target_error); ++ blk_iostatus_enable(target->blk); ++ } + } + + bdrv_add_before_write_notifier(bs, &before_write); +@@ -477,10 +489,12 @@ static void coroutine_fn backup_run(void *opaque) + qemu_co_rwlock_unlock(&job->flush_rwlock); + hbitmap_free(job->bitmap); + +- if (target->blk) { +- blk_iostatus_disable(target->blk); ++ if (target) { ++ if (target->blk) { ++ blk_iostatus_disable(target->blk); ++ } ++ bdrv_op_unblock_all(target, job->common.blocker); + } +- bdrv_op_unblock_all(target, job->common.blocker); + + data = g_malloc(sizeof(*data)); + data->ret = ret; +@@ -492,13 +506,15 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, + BdrvDirtyBitmap *sync_bitmap, + BlockdevOnError on_source_error, + BlockdevOnError on_target_error, ++ BackupDumpFunc *dump_cb, + BlockCompletionFunc *cb, void *opaque, ++ int pause_count, + BlockJobTxn *txn, Error **errp) + { + int64_t len; + + assert(bs); +- assert(target); ++ assert(target || dump_cb); + assert(cb); + + if (bs == target) { +@@ -519,7 +535,7 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, + return; + } + +- 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; +@@ -529,7 +545,7 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, + return; + } + +- 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; + } + +@@ -565,14 +581,17 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, + goto error; + } + +- bdrv_op_block_all(target, job->common.blocker); ++ if (target) ++ bdrv_op_block_all(target, job->common.blocker); + ++ job->dump_cb = dump_cb; + job->on_source_error = on_source_error; + job->on_target_error = on_target_error; + job->target = target; + job->sync_mode = sync_mode; + job->sync_bitmap = sync_mode == MIRROR_SYNC_MODE_INCREMENTAL ? + sync_bitmap : NULL; ++ job->common.pause_count = pause_count; + job->common.len = len; + job->common.co = qemu_coroutine_create(backup_run); + block_job_txn_add_job(txn, &job->common); +diff --git a/blockdev.c b/blockdev.c +index 80932e8..1796eaf 100644 +--- a/blockdev.c ++++ b/blockdev.c +@@ -3178,8 +3178,8 @@ static void do_drive_backup(const char *device, const char *target, + } + + backup_start(bs, target_bs, speed, sync, bmap, +- on_source_error, on_target_error, +- block_job_cb, bs, txn, &local_err); ++ on_source_error, on_target_error, NULL, ++ block_job_cb, bs, 0, txn, &local_err); + if (local_err != NULL) { + bdrv_unref(target_bs); + error_propagate(errp, local_err); +@@ -3268,7 +3268,7 @@ void do_blockdev_backup(const char *device, const char *target, + bdrv_ref(target_bs); + bdrv_set_aio_context(target_bs, aio_context); + backup_start(bs, target_bs, speed, sync, NULL, on_source_error, +- on_target_error, block_job_cb, bs, txn, &local_err); ++ on_target_error, NULL, block_job_cb, bs, 0, txn, &local_err); + if (local_err != NULL) { + bdrv_unref(target_bs); + error_propagate(errp, local_err); +diff --git a/include/block/block_int.h b/include/block/block_int.h +index 4012e36..f4b6ecd 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, BlockDriverState *bs, ++ int64_t sector_num, int n_sectors, unsigned char *buf); ++ + enum BdrvTrackedRequestType { + BDRV_TRACKED_READ, + BDRV_TRACKED_WRITE, +@@ -679,7 +682,9 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, + BdrvDirtyBitmap *sync_bitmap, + BlockdevOnError on_source_error, + BlockdevOnError on_target_error, ++ BackupDumpFunc *dump_cb, + BlockCompletionFunc *cb, void *opaque, ++ int pause_count, + BlockJobTxn *txn, Error **errp); + + void blk_set_bs(BlockBackend *blk, BlockDriverState *bs); +-- +2.1.4 + diff --git a/debian/patches/pve/0016-backup-add-pve-monitor-commands.patch b/debian/patches/pve/0016-backup-add-pve-monitor-commands.patch new file mode 100644 index 0000000..7105e63 --- /dev/null +++ b/debian/patches/pve/0016-backup-add-pve-monitor-commands.patch @@ -0,0 +1,767 @@ +From af106cee7626a76c7d24c838d07b437f48c2da2c Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 15:20:56 +0100 +Subject: [PATCH 16/41] backup: add pve monitor commands + +--- + blockdev.c | 438 +++++++++++++++++++++++++++++++++++++++++++++++++++ + hmp-commands-info.hx | 13 ++ + hmp-commands.hx | 29 ++++ + hmp.c | 61 +++++++ + hmp.h | 3 + + qapi-schema.json | 89 +++++++++++ + qmp-commands.hx | 18 +++ + 7 files changed, 651 insertions(+) + +diff --git a/blockdev.c b/blockdev.c +index 1796eaf..a346c55 100644 +--- a/blockdev.c ++++ b/blockdev.c +@@ -49,6 +49,7 @@ + #include "qmp-commands.h" + #include "trace.h" + #include "sysemu/arch_init.h" ++#include "vma.h" + + static const char *const if_name[IF_COUNT] = { + [IF_NONE] = "none", +@@ -2875,6 +2876,443 @@ static void block_job_cb(void *opaque, int ret) + } + } + ++/* PVE backup related function */ ++ ++static struct PVEBackupState { ++ Error *error; ++ bool cancel; ++ uuid_t uuid; ++ char uuid_str[37]; ++ int64_t speed; ++ time_t start_time; ++ time_t end_time; ++ char *backup_file; ++ VmaWriter *vmaw; ++ GList *di_list; ++ 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 started; ++ bool completed; ++} PVEBackupDevInfo; ++ ++static void pvebackup_run_next_job(void); ++ ++static int pvebackup_dump_cb(void *opaque, BlockDriverState *target, ++ int64_t sector_num, int n_sectors, ++ unsigned char *buf) ++{ ++ PVEBackupDevInfo *di = opaque; ++ ++ 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 size = n_sectors * BDRV_SECTOR_SIZE; ++ ++ int ret = -1; ++ ++ if (backup_state.vmaw) { ++ size_t zero_bytes = 0; ++ ret = vma_writer_write(backup_state.vmaw, di->dev_id, cluster_num, ++ buf, &zero_bytes); ++ backup_state.zero_bytes += zero_bytes; ++ } else { ++ ret = size; ++ if (!buf) { ++ backup_state.zero_bytes += size; ++ } ++ } ++ ++ backup_state.transferred += size; ++ ++ return ret; ++} ++ ++static void pvebackup_cleanup(void) ++{ ++ backup_state.end_time = time(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) { ++ 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; ++ ++ assert(backup_state.vmaw); ++ ++ di->completed = true; ++ ++ if (ret < 0 && !backup_state.error) { ++ error_setg(&backup_state.error, "job failed with err %d - %s", ++ ret, strerror(-ret)); ++ } ++ ++ BlockDriverState *bs = di->bs; ++ ++ di->bs = NULL; ++ ++ vma_writer_close_stream(backup_state.vmaw, di->dev_id); ++ ++ block_job_cb(bs, ret); ++ ++ 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"); ++ } ++ ++ /* drain all i/o (awake jobs waiting for aio) */ ++ bdrv_drain_all(); ++ ++ 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); ++ qemu_coroutine_enter(co, NULL); ++ ++ while (backup_state.vmaw) { ++ /* vma writer use main aio context */ ++ aio_poll(qemu_get_aio_context(), true); ++ } ++} ++ ++static void pvebackup_run_next_job(void) ++{ ++ 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 (block_job_is_paused(job)) { ++ bool cancel = backup_state.error || backup_state.cancel; ++ if (cancel) { ++ block_job_cancel(job); ++ } else { ++ block_job_resume(job); ++ } ++ } ++ return; ++ } ++ } ++ ++ pvebackup_cleanup(); ++} ++ ++UuidInfo *qmp_backup(const char *backup_file, bool has_format, ++ BackupFormat format, ++ bool has_config_file, const char *config_file, ++ bool has_devlist, const char *devlist, ++ bool has_speed, int64_t speed, Error **errp) ++{ ++ BlockBackend *blk; ++ BlockDriverState *bs = NULL; ++ Error *local_err = NULL; ++ uuid_t uuid; ++ VmaWriter *vmaw = NULL; ++ gchar **devs = NULL; ++ GList *di_list = NULL; ++ GList *l; ++ UuidInfo *uuid_info; ++ ++ if (backup_state.di_list) { ++ 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 (format != BACKUP_FORMAT_VMA) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format"); ++ return NULL; ++ } ++ ++ 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 { ++ ++ bs = NULL; ++ while ((bs = bdrv_next(bs))) { ++ ++ 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; ++ } ++ ++ uuid_generate(uuid); ++ ++ 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) { ++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; ++ l = g_list_next(l); ++ ++ const char *devname = bdrv_get_device_name(di->bs); ++ di->dev_id = vma_writer_register_stream(vmaw, devname, di->size); ++ if (di->dev_id <= 0) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, ++ "register_stream failed"); ++ goto err; ++ } ++ } ++ ++ /* add configuration file to archive */ ++ if (has_config_file) { ++ char *cdata = NULL; ++ gsize clen = 0; ++ GError *err = NULL; ++ if (!g_file_get_contents(config_file, &cdata, &clen, &err)) { ++ error_setg(errp, "unable to read file '%s'", config_file); ++ goto err; ++ } ++ ++ const char *basename = g_path_get_basename(config_file); ++ if (vma_writer_add_config(vmaw, basename, cdata, clen) != 0) { ++ error_setg(errp, "unable to add config data to vma archive"); ++ g_free(cdata); ++ goto err; ++ } ++ g_free(cdata); ++ } ++ ++ /* 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); ++ ++ 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.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); ++ ++ backup_start(di->bs, NULL, speed, MIRROR_SYNC_MODE_FULL, NULL, ++ BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT, ++ pvebackup_dump_cb, pvebackup_complete_cb, di, ++ 1, &local_err); ++ if (local_err != NULL) { ++ error_setg(&backup_state.error, "backup_job_create failed"); ++ pvebackup_cancel(NULL); ++ } ++ } ++ ++ 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) { ++ g_free(l->data); ++ l = g_list_next(l); ++ } ++ g_list_free(di_list); ++ ++ if (devs) { ++ g_strfreev(devs); ++ } ++ ++ if (vmaw) { ++ Error *err = NULL; ++ vma_writer_close(vmaw, &err); ++ unlink(backup_file); ++ } ++ ++ 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(const char *device, + bool has_base, const char *base, + bool has_backing_file, const char *backing_file, +diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx +index 9b71351..355289a 100644 +--- a/hmp-commands-info.hx ++++ b/hmp-commands-info.hx +@@ -502,6 +502,19 @@ STEXI + Show CPU statistics. + ETEXI + ++ { ++ .name = "backup", ++ .args_type = "", ++ .params = "", ++ .help = "show backup status", ++ .mhandler.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 bb52e4d..324125f 100644 +--- a/hmp-commands.hx ++++ b/hmp-commands.hx +@@ -87,6 +87,35 @@ STEXI + Copy data from a backing file into a block device. + ETEXI + ++ { ++ .name = "backup", ++ .args_type = "backupfile:s,speed:o?,devlist:s?", ++ .params = "backupfile [speed [devlist]]", ++ .help = "create a VM Backup.", ++ .mhandler.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", ++ .mhandler.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 0e63ea8..ebb16c1 100644 +--- a/hmp.c ++++ b/hmp.c +@@ -146,6 +146,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; +@@ -1461,6 +1499,29 @@ 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; ++ ++ 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, BACKUP_FORMAT_VMA, 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 a8c5b5a..0b39c25 100644 +--- a/hmp.h ++++ b/hmp.h +@@ -30,6 +30,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); +@@ -76,6 +77,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 1aa434d..edfb3a4 100644 +--- a/qapi-schema.json ++++ b/qapi-schema.json +@@ -358,6 +358,95 @@ + ## + { 'command': 'query-events', 'returns': ['EventInfo'] } + ++# @BackupStatus: ++# ++# Detailed backup status. ++# ++# @status: #optional 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: #optional error message (only returned if status is 'error') ++# ++# @total: #optional total amount of bytes involved in the backup process ++# ++# @transferred: #optional amount of bytes already backed up. ++# ++# @zero-bytes: #optional amount of 'zero' bytes detected. ++# ++# @start-time: #optional time (epoch) when backup job started. ++# ++# @end-time: #optional time (epoch) when backup job finished. ++# ++# @backupfile: #optional backup file name ++# ++# @uuid: #optional 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' ] } ++ ++## ++# @backup: ++# ++# Starts a VM backup. ++# ++# @backup-file: the backup file name ++# ++# @format: format of the backup file ++# ++# @config-filename: #optional name of a configuration file to include into ++# the backup archive. ++# ++# @speed: #optional the maximum speed, in bytes per second ++# ++# @devlist: #optional 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', ++ '*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 + # +diff --git a/qmp-commands.hx b/qmp-commands.hx +index 5c23d91..3dea794 100644 +--- a/qmp-commands.hx ++++ b/qmp-commands.hx +@@ -1254,6 +1254,24 @@ Example: + EQMP + + { ++ .name = "backup", ++ .args_type = "backup-file:s,format:s?,config-file:F?,speed:o?,devlist:s?", ++ .mhandler.cmd_new = qmp_marshal_input_backup, ++ }, ++ ++ { ++ .name = "backup-cancel", ++ .args_type = "", ++ .mhandler.cmd_new = qmp_marshal_input_backup_cancel, ++ }, ++ ++ { ++ .name = "query-backup", ++ .args_type = "", ++ .mhandler.cmd_new = qmp_marshal_input_query_backup, ++ }, ++ ++ { + .name = "block-job-set-speed", + .args_type = "device:B,speed:o", + .mhandler.cmd_new = qmp_marshal_block_job_set_speed, +-- +2.1.4 + diff --git a/debian/patches/pve/0017-backup-vma-add-dir-format.patch b/debian/patches/pve/0017-backup-vma-add-dir-format.patch new file mode 100644 index 0000000..42ba370 --- /dev/null +++ b/debian/patches/pve/0017-backup-vma-add-dir-format.patch @@ -0,0 +1,277 @@ +From bbf1558fc870eb4e72604b4d340ec317a59ac459 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 15:21:54 +0100 +Subject: [PATCH 17/41] backup: vma: add dir format + +--- + blockdev.c | 126 +++++++++++++++++++++++++++++++++++++++++-------------- + hmp-commands.hx | 8 ++-- + hmp.c | 4 +- + qapi-schema.json | 2 +- + 4 files changed, 104 insertions(+), 36 deletions(-) + +diff --git a/blockdev.c b/blockdev.c +index a346c55..29a33c0 100644 +--- a/blockdev.c ++++ b/blockdev.c +@@ -2900,6 +2900,8 @@ typedef struct PVEBackupDevInfo { + uint8_t dev_id; + //bool started; + bool completed; ++ char targetfile[PATH_MAX]; ++ BlockDriverState *target; + } PVEBackupDevInfo; + + static void pvebackup_run_next_job(void); +@@ -2968,8 +2970,6 @@ static void pvebackup_complete_cb(void *opaque, int ret) + { + PVEBackupDevInfo *di = opaque; + +- assert(backup_state.vmaw); +- + di->completed = true; + + if (ret < 0 && !backup_state.error) { +@@ -2980,8 +2980,11 @@ static void pvebackup_complete_cb(void *opaque, int ret) + BlockDriverState *bs = di->bs; + + di->bs = NULL; ++ di->target = NULL; + +- vma_writer_close_stream(backup_state.vmaw, di->dev_id); ++ if (backup_state.vmaw) { ++ vma_writer_close_stream(backup_state.vmaw, di->dev_id); ++ } + + block_job_cb(bs, ret); + +@@ -3060,6 +3063,7 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format, + { + BlockBackend *blk; + BlockDriverState *bs = NULL; ++ const char *backup_dir = NULL; + Error *local_err = NULL; + uuid_t uuid; + VmaWriter *vmaw = NULL; +@@ -3077,11 +3081,6 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format, + /* Todo: try to auto-detect format based on file name */ + format = has_format ? format : BACKUP_FORMAT_VMA; + +- if (format != BACKUP_FORMAT_VMA) { +- error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format"); +- return NULL; +- } +- + if (has_devlist) { + devs = g_strsplit_set(devlist, ",;:", -1); + +@@ -3150,27 +3149,63 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format, + + uuid_generate(uuid); + +- vmaw = vma_writer_create(backup_file, uuid, &local_err); +- if (!vmaw) { +- if (local_err) { +- error_propagate(errp, local_err); ++ if (format == BACKUP_FORMAT_VMA) { ++ vmaw = vma_writer_create(backup_file, uuid, &local_err); ++ if (!vmaw) { ++ if (local_err) { ++ error_propagate(errp, local_err); ++ } ++ goto err; + } +- goto err; +- } + +- /* register all devices for vma writer */ +- l = di_list; +- while (l) { +- PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; +- l = g_list_next(l); ++ /* register all devices for vma writer */ ++ l = di_list; ++ while (l) { ++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; ++ l = g_list_next(l); + +- const char *devname = bdrv_get_device_name(di->bs); +- di->dev_id = vma_writer_register_stream(vmaw, devname, di->size); +- if (di->dev_id <= 0) { +- error_set(errp, ERROR_CLASS_GENERIC_ERROR, +- "register_stream failed"); ++ const char *devname = bdrv_get_device_name(di->bs); ++ di->dev_id = vma_writer_register_stream(vmaw, devname, di->size); ++ if (di->dev_id <= 0) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, ++ "register_stream failed"); ++ goto err; ++ } ++ } ++ } 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_O_CACHE_WB; ++ 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_new(); ++ if (bdrv_open(&di->target, di->targetfile, NULL, NULL, flags, NULL, &local_err) < 0) { ++ bdrv_unref(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 */ +@@ -3183,12 +3218,27 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format, + goto err; + } + +- const char *basename = g_path_get_basename(config_file); +- if (vma_writer_add_config(vmaw, basename, cdata, clen) != 0) { +- error_setg(errp, "unable to add config data to vma archive"); +- g_free(cdata); +- goto err; ++ char *basename = g_path_get_basename(config_file); ++ ++ if (format == BACKUP_FORMAT_VMA) { ++ if (vma_writer_add_config(vmaw, basename, cdata, clen) != 0) { ++ error_setg(errp, "unable to add config data to vma archive"); ++ g_free(cdata); ++ g_free(basename); ++ goto err; ++ } ++ } 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); ++ goto err; ++ } + } ++ ++ g_free(basename); + g_free(cdata); + } + +@@ -3228,10 +3278,11 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format, + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; + l = g_list_next(l); + +- backup_start(di->bs, NULL, speed, MIRROR_SYNC_MODE_FULL, NULL, ++ backup_start(di->bs, di->target, speed, MIRROR_SYNC_MODE_FULL, NULL, + BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT, + pvebackup_dump_cb, pvebackup_complete_cb, di, + 1, &local_err); ++ + if (local_err != NULL) { + error_setg(&backup_state.error, "backup_job_create failed"); + pvebackup_cancel(NULL); +@@ -3250,8 +3301,17 @@ err: + + l = di_list; + while (l) { +- g_free(l->data); ++ 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); + +@@ -3265,6 +3325,10 @@ err: + unlink(backup_file); + } + ++ if (backup_dir) { ++ rmdir(backup_dir); ++ } ++ + return NULL; + } + +diff --git a/hmp-commands.hx b/hmp-commands.hx +index 324125f..f6cc0e5 100644 +--- a/hmp-commands.hx ++++ b/hmp-commands.hx +@@ -89,9 +89,11 @@ ETEXI + + { + .name = "backup", +- .args_type = "backupfile:s,speed:o?,devlist:s?", +- .params = "backupfile [speed [devlist]]", +- .help = "create a VM 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.", + .mhandler.cmd = hmp_backup, + }, + +diff --git a/hmp.c b/hmp.c +index ebb16c1..acc4aea 100644 +--- a/hmp.c ++++ b/hmp.c +@@ -1512,11 +1512,13 @@ 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, BACKUP_FORMAT_VMA, false, NULL, !!devlist, ++ qmp_backup(backup_file, true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA, ++ false, NULL, !!devlist, + devlist, qdict_haskey(qdict, "speed"), speed, &error); + + hmp_handle_error(mon, &error); +diff --git a/qapi-schema.json b/qapi-schema.json +index edfb3a4..84978bd 100644 +--- a/qapi-schema.json ++++ b/qapi-schema.json +@@ -397,7 +397,7 @@ + # @vma: Proxmox vma backup format + ## + { 'enum': 'BackupFormat', +- 'data': [ 'vma' ] } ++ 'data': [ 'vma', 'dir' ] } + + ## + # @backup: +-- +2.1.4 + diff --git a/debian/patches/pve/0018-backup-do-not-return-errors-in-dump-callback.patch b/debian/patches/pve/0018-backup-do-not-return-errors-in-dump-callback.patch new file mode 100644 index 0000000..2f07955 --- /dev/null +++ b/debian/patches/pve/0018-backup-do-not-return-errors-in-dump-callback.patch @@ -0,0 +1,77 @@ +From 9b6ccb4512735cf5455a473f99a946371a466a3d Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 15:22:19 +0100 +Subject: [PATCH 18/41] backup: do not return errors in dump callback + +--- + blockdev.c | 26 ++++++++++++++++++++------ + 1 file changed, 20 insertions(+), 6 deletions(-) + +diff --git a/blockdev.c b/blockdev.c +index 29a33c0..9bb354c 100644 +--- a/blockdev.c ++++ b/blockdev.c +@@ -2912,6 +2912,11 @@ static int pvebackup_dump_cb(void *opaque, BlockDriverState *target, + { + 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, +@@ -2922,7 +2927,6 @@ static int pvebackup_dump_cb(void *opaque, BlockDriverState *target, + } + + int64_t cluster_num = sector_num >> 7; +- int size = n_sectors * BDRV_SECTOR_SIZE; + + int ret = -1; + +@@ -2930,17 +2934,27 @@ static int pvebackup_dump_cb(void *opaque, BlockDriverState *target, + size_t zero_bytes = 0; + ret = vma_writer_write(backup_state.vmaw, di->dev_id, cluster_num, + buf, &zero_bytes); +- backup_state.zero_bytes += zero_bytes; ++ if (ret < 0) { ++ if (!backup_state.error) { ++ error_setg(&backup_state.error, "vma_writer_write error %d", ret); ++ } ++ if (di->bs && di->bs->job) { ++ block_job_cancel(di->bs->job); ++ } ++ } else { ++ backup_state.zero_bytes += zero_bytes; ++ backup_state.transferred += size; ++ } + } else { +- ret = size; + if (!buf) { + backup_state.zero_bytes += size; + } ++ backup_state.transferred += size; + } + +- backup_state.transferred += size; ++ // Note: always return success, because we want that writes succeed anyways. + +- return ret; ++ return size; + } + + static void pvebackup_cleanup(void) +@@ -3012,7 +3026,7 @@ static void pvebackup_cancel(void *opaque) + BlockJob *job = di->bs->job; + if (job) { + if (!di->completed) { +- block_job_cancel_sync(job); ++ block_job_cancel_sync(job); + } + } + } +-- +2.1.4 + diff --git a/debian/patches/pve/0019-backup-vma-correctly-propagate-error.patch b/debian/patches/pve/0019-backup-vma-correctly-propagate-error.patch new file mode 100644 index 0000000..3760ea3 --- /dev/null +++ b/debian/patches/pve/0019-backup-vma-correctly-propagate-error.patch @@ -0,0 +1,57 @@ +From e086d832f697115cc1f53ff535d677fcc09a86ef Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 15:39:36 +0100 +Subject: [PATCH 19/41] backup: vma: correctly propagate error + +--- + blockdev.c | 2 +- + vma-writer.c | 7 +++++++ + vma.h | 1 + + 3 files changed, 9 insertions(+), 1 deletion(-) + +diff --git a/blockdev.c b/blockdev.c +index 9bb354c..91aaf10 100644 +--- a/blockdev.c ++++ b/blockdev.c +@@ -2936,7 +2936,7 @@ static int pvebackup_dump_cb(void *opaque, BlockDriverState *target, + buf, &zero_bytes); + if (ret < 0) { + if (!backup_state.error) { +- error_setg(&backup_state.error, "vma_writer_write error %d", ret); ++ vma_writer_error_propagate(backup_state.vmaw, &backup_state.error); + } + if (di->bs && di->bs->job) { + block_job_cancel(di->bs->job); +diff --git a/vma-writer.c b/vma-writer.c +index 8a3fa1c..425c4b9 100644 +--- a/vma-writer.c ++++ b/vma-writer.c +@@ -798,6 +798,13 @@ vma_writer_write(VmaWriter *vmaw, uint8_t dev_id, int64_t cluster_num, + 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); +diff --git a/vma.h b/vma.h +index 9bb6ea4..98377e4 100644 +--- a/vma.h ++++ b/vma.h +@@ -116,6 +116,7 @@ typedef struct 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); +-- +2.1.4 + diff --git a/debian/patches/pve/0020-backup-vma-remove-async-queue.patch b/debian/patches/pve/0020-backup-vma-remove-async-queue.patch new file mode 100644 index 0000000..d13e178 --- /dev/null +++ b/debian/patches/pve/0020-backup-vma-remove-async-queue.patch @@ -0,0 +1,317 @@ +From dccc1b41302d94857bf959ef2f25978f0ef674f1 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 15:40:00 +0100 +Subject: [PATCH 20/41] backup: vma: remove async queue + +--- + blockdev.c | 6 ++ + vma-writer.c | 179 +++++++++++------------------------------------------------ + 2 files changed, 38 insertions(+), 147 deletions(-) + +diff --git a/blockdev.c b/blockdev.c +index 91aaf10..c36888d 100644 +--- a/blockdev.c ++++ b/blockdev.c +@@ -3015,6 +3015,11 @@ static void pvebackup_cancel(void *opaque) + error_setg(&backup_state.error, "backup cancelled"); + } + ++ if (backup_state.vmaw) { ++ /* make sure vma writer does not block anymore */ ++ vma_writer_set_error(backup_state.vmaw, "backup cancelled"); ++ } ++ + /* drain all i/o (awake jobs waiting for aio) */ + bdrv_drain_all(); + +@@ -3027,6 +3032,7 @@ static void pvebackup_cancel(void *opaque) + if (job) { + if (!di->completed) { + block_job_cancel_sync(job); ++ bdrv_drain_all(); /* drain all i/o (awake jobs waiting for aio) */ + } + } + } +diff --git a/vma-writer.c b/vma-writer.c +index 425c4b9..2558fe1 100644 +--- a/vma-writer.c ++++ b/vma-writer.c +@@ -34,14 +34,8 @@ + do { if (DEBUG_VMA) { printf("vma: " fmt, ## __VA_ARGS__); } } while (0) + + #define WRITE_BUFFERS 5 +- +-typedef struct VmaAIOCB VmaAIOCB; +-struct VmaAIOCB { +- unsigned char buffer[VMA_MAX_EXTENT_SIZE]; +- VmaWriter *vmaw; +- size_t bytes; +- Coroutine *co; +-}; ++#define HEADER_CLUSTERS 8 ++#define HEADERBUF_SIZE (VMA_CLUSTER_SIZE*HEADER_CLUSTERS) + + struct VmaWriter { + int fd; +@@ -53,16 +47,14 @@ struct VmaWriter { + bool closed; + + /* we always write extents */ +- unsigned char outbuf[VMA_MAX_EXTENT_SIZE]; ++ unsigned char *outbuf; + int outbuf_pos; /* in bytes */ + int outbuf_count; /* in VMA_BLOCKS */ + uint64_t outbuf_block_info[VMA_BLOCKS_PER_EXTENT]; + +- VmaAIOCB *aiocbs[WRITE_BUFFERS]; +- CoQueue wqueue; ++ unsigned char *headerbuf; + + GChecksum *md5csum; +- CoMutex writer_lock; + CoMutex flush_lock; + Coroutine *co_writer; + +@@ -223,38 +215,39 @@ static void vma_co_continue_write(void *opaque) + } + + static ssize_t coroutine_fn +-vma_co_write(VmaWriter *vmaw, const void *buf, size_t bytes) ++vma_queue_write(VmaWriter *vmaw, const void *buf, size_t bytes) + { +- size_t done = 0; +- ssize_t ret; ++ DPRINTF("vma_queue_write enter %zd\n", bytes); + +- /* atomic writes (we cannot interleave writes) */ +- qemu_co_mutex_lock(&vmaw->writer_lock); ++ assert(vmaw); ++ assert(buf); ++ assert(bytes <= VMA_MAX_EXTENT_SIZE); + +- DPRINTF("vma_co_write enter %zd\n", bytes); ++ size_t done = 0; ++ ssize_t ret; + + assert(vmaw->co_writer == NULL); + + vmaw->co_writer = qemu_coroutine_self(); + +- aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, NULL, vma_co_continue_write, vmaw); +- +- DPRINTF("vma_co_write wait until writable\n"); +- qemu_coroutine_yield(); +- DPRINTF("vma_co_write starting %zd\n", bytes); +- + while (done < bytes) { ++ aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, NULL, vma_co_continue_write, vmaw); ++ qemu_coroutine_yield(); ++ aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, 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_co_write written %zd %zd\n", done, ret); ++ DPRINTF("vma_queue_write written %zd %zd\n", done, ret); + } else if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { +- DPRINTF("vma_co_write yield %zd\n", done); +- qemu_coroutine_yield(); +- DPRINTF("vma_co_write restart %zd\n", done); +- } else { +- vma_writer_set_error(vmaw, "vma_co_write write error - %s", ++ /* 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; +@@ -264,102 +257,9 @@ vma_co_write(VmaWriter *vmaw, const void *buf, size_t bytes) + } + } + +- aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, NULL, NULL, NULL); +- + vmaw->co_writer = NULL; +- +- qemu_co_mutex_unlock(&vmaw->writer_lock); +- +- DPRINTF("vma_co_write leave %zd\n", done); +- return done; +-} +- +-static void coroutine_fn vma_co_writer_task(void *opaque) +-{ +- VmaAIOCB *cb = opaque; +- +- DPRINTF("vma_co_writer_task start\n"); +- +- int64_t done = vma_co_write(cb->vmaw, cb->buffer, cb->bytes); +- DPRINTF("vma_co_writer_task write done %zd\n", done); +- +- if (done != cb->bytes) { +- DPRINTF("vma_co_writer_task failed write %zd %zd", cb->bytes, done); +- vma_writer_set_error(cb->vmaw, "vma_co_writer_task failed write %zd", +- done); +- } +- +- cb->bytes = 0; +- +- qemu_co_queue_next(&cb->vmaw->wqueue); +- +- DPRINTF("vma_co_writer_task end\n"); +-} +- +-static void coroutine_fn vma_queue_flush(VmaWriter *vmaw) +-{ +- DPRINTF("vma_queue_flush enter\n"); +- +- assert(vmaw); +- +- while (1) { +- int i; +- VmaAIOCB *cb = NULL; +- for (i = 0; i < WRITE_BUFFERS; i++) { +- if (vmaw->aiocbs[i]->bytes) { +- cb = vmaw->aiocbs[i]; +- DPRINTF("FOUND USED AIO BUFFER %d %zd\n", i, +- vmaw->aiocbs[i]->bytes); +- break; +- } +- } +- if (!cb) { +- break; +- } +- qemu_co_queue_wait(&vmaw->wqueue); +- } +- +- DPRINTF("vma_queue_flush leave\n"); +-} +- +-/** +- * NOTE: pipe buffer size in only 4096 bytes on linux (see 'ulimit -a') +- * So we need to create a coroutione to allow 'parallel' execution. +- */ +-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); +- +- VmaAIOCB *cb = NULL; +- while (!cb) { +- int i; +- for (i = 0; i < WRITE_BUFFERS; i++) { +- if (!vmaw->aiocbs[i]->bytes) { +- cb = vmaw->aiocbs[i]; +- break; +- } +- } +- if (!cb) { +- qemu_co_queue_wait(&vmaw->wqueue); +- } +- } +- +- memcpy(cb->buffer, buf, bytes); +- cb->bytes = bytes; +- cb->vmaw = vmaw; +- +- DPRINTF("vma_queue_write start %zd\n", bytes); +- cb->co = qemu_coroutine_create(vma_co_writer_task); +- qemu_coroutine_enter(cb->co, cb); +- +- DPRINTF("vma_queue_write leave\n"); +- +- return bytes; ++ ++ return (done == bytes) ? bytes : -1; + } + + VmaWriter *vma_writer_create(const char *filename, uuid_t uuid, Error **errp) +@@ -426,20 +326,16 @@ VmaWriter *vma_writer_create(const char *filename, uuid_t uuid, Error **errp) + } + + /* we use O_DIRECT, so we need to align IO buffers */ +- int i; +- for (i = 0; i < WRITE_BUFFERS; i++) { +- vmaw->aiocbs[i] = qemu_memalign(512, sizeof(VmaAIOCB)); +- memset(vmaw->aiocbs[i], 0, sizeof(VmaAIOCB)); +- } ++ ++ 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->writer_lock); + qemu_co_mutex_init(&vmaw->flush_lock); +- qemu_co_queue_init(&vmaw->wqueue); + + uuid_copy(vmaw->uuid, uuid); + +@@ -466,8 +362,7 @@ err: + static int coroutine_fn vma_write_header(VmaWriter *vmaw) + { + assert(vmaw); +- int header_clusters = 8; +- char buf[65536*header_clusters]; ++ unsigned char *buf = vmaw->headerbuf; + VmaHeader *head = (VmaHeader *)buf; + + int i; +@@ -478,7 +373,7 @@ static int coroutine_fn vma_write_header(VmaWriter *vmaw) + return vmaw->status; + } + +- memset(buf, 0, sizeof(buf)); ++ memset(buf, 0, HEADERBUF_SIZE); + + head->magic = VMA_MAGIC; + head->version = GUINT32_TO_BE(1); /* v1 */ +@@ -513,7 +408,7 @@ static int coroutine_fn vma_write_header(VmaWriter *vmaw) + uint32_t header_size = sizeof(VmaHeader) + vmaw->header_blob_table_size; + head->header_size = GUINT32_TO_BE(header_size); + +- if (header_size > sizeof(buf)) { ++ if (header_size > HEADERBUF_SIZE) { + return -1; /* just to be sure */ + } + +@@ -811,13 +706,7 @@ int vma_writer_close(VmaWriter *vmaw, Error **errp) + + int i; + +- vma_queue_flush(vmaw); +- +- /* this should not happen - just to be sure */ +- while (!qemu_co_queue_empty(&vmaw->wqueue)) { +- DPRINTF("vma_writer_close wait\n"); +- co_aio_sleep_ns(qemu_get_aio_context(), QEMU_CLOCK_REALTIME, 1000000); +- } ++ assert(vmaw->co_writer == NULL); + + if (vmaw->cmd) { + if (pclose(vmaw->cmd) < 0) { +@@ -875,9 +764,5 @@ void vma_writer_destroy(VmaWriter *vmaw) + g_checksum_free(vmaw->md5csum); + } + +- for (i = 0; i < WRITE_BUFFERS; i++) { +- free(vmaw->aiocbs[i]); +- } +- + g_free(vmaw); + } +-- +2.1.4 + diff --git a/debian/patches/pve/0021-backup-vma-run-flush-inside-coroutine.patch b/debian/patches/pve/0021-backup-vma-run-flush-inside-coroutine.patch new file mode 100644 index 0000000..a62c3e3 --- /dev/null +++ b/debian/patches/pve/0021-backup-vma-run-flush-inside-coroutine.patch @@ -0,0 +1,56 @@ +From fe57d97e87fd49b03ce11d381f88fed546ea674d Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 15:40:42 +0100 +Subject: [PATCH 21/41] backup: vma: run flush inside coroutine + +--- + blockdev.c | 10 +++++++++- + vma-writer.c | 4 ++++ + 2 files changed, 13 insertions(+), 1 deletion(-) + +diff --git a/blockdev.c b/blockdev.c +index c36888d..8df70cb 100644 +--- a/blockdev.c ++++ b/blockdev.c +@@ -2980,6 +2980,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; +@@ -2997,7 +3004,8 @@ static void pvebackup_complete_cb(void *opaque, int ret) + di->target = NULL; + + if (backup_state.vmaw) { +- vma_writer_close_stream(backup_state.vmaw, di->dev_id); ++ Coroutine *co = qemu_coroutine_create(backup_close_vma_stream); ++ qemu_coroutine_enter(co, di); + } + + block_job_cb(bs, ret); +diff --git a/vma-writer.c b/vma-writer.c +index 2558fe1..3c77e98 100644 +--- a/vma-writer.c ++++ b/vma-writer.c +@@ -706,6 +706,10 @@ int vma_writer_close(VmaWriter *vmaw, Error **errp) + + int i; + ++ while (vmaw->co_writer) { ++ aio_poll(qemu_get_aio_context(), true); ++ } ++ + assert(vmaw->co_writer == NULL); + + if (vmaw->cmd) { +-- +2.1.4 + diff --git a/debian/patches/pve/0022-backup-do-not-use-bdrv_drain_all.patch b/debian/patches/pve/0022-backup-do-not-use-bdrv_drain_all.patch new file mode 100644 index 0000000..f572c4b --- /dev/null +++ b/debian/patches/pve/0022-backup-do-not-use-bdrv_drain_all.patch @@ -0,0 +1,36 @@ +From 297edbbf4fcdb1ad1515fbfaeff530f97e9259ac Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 15:41:13 +0100 +Subject: [PATCH 22/41] backup: do not use bdrv_drain_all + +--- + blockdev.c | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +diff --git a/blockdev.c b/blockdev.c +index 8df70cb..4d182d6 100644 +--- a/blockdev.c ++++ b/blockdev.c +@@ -3028,9 +3028,6 @@ static void pvebackup_cancel(void *opaque) + vma_writer_set_error(backup_state.vmaw, "backup cancelled"); + } + +- /* drain all i/o (awake jobs waiting for aio) */ +- bdrv_drain_all(); +- + GList *l = backup_state.di_list; + while (l) { + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; +@@ -3039,8 +3036,7 @@ static void pvebackup_cancel(void *opaque) + BlockJob *job = di->bs->job; + if (job) { + if (!di->completed) { +- block_job_cancel_sync(job); +- bdrv_drain_all(); /* drain all i/o (awake jobs waiting for aio) */ ++ block_job_cancel_sync(job); + } + } + } +-- +2.1.4 + diff --git a/debian/patches/pve/0023-internal-snapshot-async.patch b/debian/patches/pve/0023-internal-snapshot-async.patch new file mode 100644 index 0000000..224e99e --- /dev/null +++ b/debian/patches/pve/0023-internal-snapshot-async.patch @@ -0,0 +1,996 @@ +From de27ffeb5f55764a51b435533311f1d627d26177 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 16:04:32 +0100 +Subject: [PATCH 23/41] internal snapshot async + +--- + Makefile.objs | 1 + + block.c | 2 +- + hmp-commands-info.hx | 13 ++ + hmp-commands.hx | 32 +++ + hmp.c | 57 ++++++ + hmp.h | 5 + + include/block/block.h | 1 + + include/sysemu/sysemu.h | 5 +- + migration/savevm.c | 12 +- + qapi-schema.json | 46 +++++ + qemu-options.hx | 13 ++ + qmp-commands.hx | 30 +++ + savevm-async.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++++ + vl.c | 8 + + 14 files changed, 739 insertions(+), 8 deletions(-) + create mode 100644 savevm-async.c + +diff --git a/Makefile.objs b/Makefile.objs +index 97d2bcc..7da098b 100644 +--- a/Makefile.objs ++++ b/Makefile.objs +@@ -48,6 +48,7 @@ common-obj-y += migration/ + common-obj-y += qemu-char.o #aio.o + common-obj-y += page_cache.o + common-obj-y += qjson.o ++common-obj-y += savevm-async.o + + common-obj-$(CONFIG_SPICE) += spice-qemu-char.o + +diff --git a/block.c b/block.c +index 3a7324b..5505afb 100644 +--- a/block.c ++++ b/block.c +@@ -2117,7 +2117,7 @@ void bdrv_replace_in_backing_chain(BlockDriverState *old, BlockDriverState *new) + bdrv_unref(old); + } + +-static void bdrv_delete(BlockDriverState *bs) ++void bdrv_delete(BlockDriverState *bs) + { + assert(!bs->job); + assert(bdrv_op_blocker_is_empty(bs)); +diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx +index 355289a..1de0d77 100644 +--- a/hmp-commands-info.hx ++++ b/hmp-commands-info.hx +@@ -588,6 +588,19 @@ Show current migration xbzrle cache size. + ETEXI + + { ++ .name = "savevm", ++ .args_type = "", ++ .params = "", ++ .help = "show savevm status", ++ .mhandler.cmd = hmp_info_savevm, ++ }, ++ ++STEXI ++@item info savevm ++show savevm status ++ETEXI ++ ++ { + .name = "balloon", + .args_type = "", + .params = "", +diff --git a/hmp-commands.hx b/hmp-commands.hx +index f6cc0e5..1e1eb5d 100644 +--- a/hmp-commands.hx ++++ b/hmp-commands.hx +@@ -1789,3 +1789,35 @@ ETEXI + STEXI + @end table + ETEXI ++ ++ { ++ .name = "savevm-start", ++ .args_type = "statefile:s?", ++ .params = "[statefile]", ++ .help = "Prepare for snapshot and halt VM. Save VM state to statefile.", ++ .mhandler.cmd = hmp_savevm_start, ++ }, ++ ++ { ++ .name = "snapshot-drive", ++ .args_type = "device:s,name:s", ++ .params = "device name", ++ .help = "Create internal snapshot.", ++ .mhandler.cmd = hmp_snapshot_drive, ++ }, ++ ++ { ++ .name = "delete-drive-snapshot", ++ .args_type = "device:s,name:s", ++ .params = "device name", ++ .help = "Delete internal snapshot.", ++ .mhandler.cmd = hmp_delete_drive_snapshot, ++ }, ++ ++ { ++ .name = "savevm-end", ++ .args_type = "", ++ .params = "", ++ .help = "Resume VM after snaphot.", ++ .mhandler.cmd = hmp_savevm_end, ++ }, +diff --git a/hmp.c b/hmp.c +index acc4aea..724b5d3 100644 +--- a/hmp.c ++++ b/hmp.c +@@ -2107,6 +2107,63 @@ void hmp_info_memory_devices(Monitor *mon, const QDict *qdict) + qapi_free_MemoryDeviceInfoList(info_list); + } + ++void hmp_savevm_start(Monitor *mon, const QDict *qdict) ++{ ++ Error *errp = NULL; ++ const char *statefile = qdict_get_try_str(qdict, "statefile"); ++ ++ qmp_savevm_start(statefile != NULL, statefile, &errp); ++ hmp_handle_error(mon, &errp); ++} ++ ++void hmp_snapshot_drive(Monitor *mon, const QDict *qdict) ++{ ++ Error *errp = NULL; ++ const char *name = qdict_get_str(qdict, "name"); ++ const char *device = qdict_get_str(qdict, "device"); ++ ++ qmp_snapshot_drive(device, name, &errp); ++ hmp_handle_error(mon, &errp); ++} ++ ++void hmp_delete_drive_snapshot(Monitor *mon, const QDict *qdict) ++{ ++ Error *errp = NULL; ++ const char *name = qdict_get_str(qdict, "name"); ++ const char *device = qdict_get_str(qdict, "device"); ++ ++ qmp_delete_drive_snapshot(device, name, &errp); ++ hmp_handle_error(mon, &errp); ++} ++ ++void hmp_savevm_end(Monitor *mon, const QDict *qdict) ++{ ++ Error *errp = NULL; ++ ++ qmp_savevm_end(&errp); ++ hmp_handle_error(mon, &errp); ++} ++ ++void hmp_info_savevm(Monitor *mon, const QDict *qdict) ++{ ++ SaveVMInfo *info; ++ info = qmp_query_savevm(NULL); ++ ++ if (info->has_status) { ++ monitor_printf(mon, "savevm status: %s\n", info->status); ++ monitor_printf(mon, "total time: %" PRIu64 " milliseconds\n", ++ info->total_time); ++ } else { ++ monitor_printf(mon, "savevm status: not running\n"); ++ } ++ if (info->has_bytes) { ++ monitor_printf(mon, "Bytes saved: %"PRIu64"\n", info->bytes); ++ } ++ if (info->has_error) { ++ monitor_printf(mon, "Error: %s\n", info->error); ++ } ++} ++ + void hmp_info_iothreads(Monitor *mon, const QDict *qdict) + { + IOThreadInfoList *info_list = qmp_query_iothreads(NULL); +diff --git a/hmp.h b/hmp.h +index 0b39c25..86d2672 100644 +--- a/hmp.h ++++ b/hmp.h +@@ -26,6 +26,7 @@ void hmp_info_status(Monitor *mon, const QDict *qdict); + void hmp_info_uuid(Monitor *mon, const QDict *qdict); + void hmp_info_chardev(Monitor *mon, const QDict *qdict); + void hmp_info_mice(Monitor *mon, const QDict *qdict); ++void hmp_info_savevm(Monitor *mon, const QDict *qdict); + void hmp_info_migrate(Monitor *mon, const QDict *qdict); + void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict); + void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict); +@@ -92,6 +93,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_savevm_start(Monitor *mon, const QDict *qdict); ++void hmp_snapshot_drive(Monitor *mon, const QDict *qdict); ++void hmp_delete_drive_snapshot(Monitor *mon, const QDict *qdict); ++void hmp_savevm_end(Monitor *mon, const QDict *qdict); + void hmp_sendkey(Monitor *mon, const QDict *qdict); + void hmp_screendump(Monitor *mon, const QDict *qdict); + void hmp_nbd_server_start(Monitor *mon, const QDict *qdict); +diff --git a/include/block/block.h b/include/block/block.h +index 3477328..030599e 100644 +--- a/include/block/block.h ++++ b/include/block/block.h +@@ -265,6 +265,7 @@ BlockDriverState *bdrv_find_backing_image(BlockDriverState *bs, + int bdrv_get_backing_file_depth(BlockDriverState *bs); + void bdrv_refresh_filename(BlockDriverState *bs); + int bdrv_truncate(BlockDriverState *bs, int64_t offset); ++void bdrv_delete(BlockDriverState *bs); + int64_t bdrv_nb_sectors(BlockDriverState *bs); + int64_t bdrv_getlength(BlockDriverState *bs); + int64_t bdrv_get_allocated_file_size(BlockDriverState *bs); +diff --git a/include/sysemu/sysemu.h b/include/sysemu/sysemu.h +index 3bb8897..cc47e29 100644 +--- a/include/sysemu/sysemu.h ++++ b/include/sysemu/sysemu.h +@@ -79,6 +79,7 @@ void qemu_add_machine_init_done_notifier(Notifier *notify); + + void hmp_savevm(Monitor *mon, const QDict *qdict); + 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); + +@@ -106,13 +107,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 0ad1b93..a1cf4b5 100644 +--- a/migration/savevm.c ++++ b/migration/savevm.c +@@ -891,11 +891,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) { +@@ -923,6 +923,7 @@ void qemu_savevm_state_begin(QEMUFile *f, + break; + } + } ++ return ret; + } + + /* +@@ -1026,7 +1027,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; +@@ -1060,12 +1061,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; + } + } + + if (iterable_only) { +- return; ++ return ret; + } + + vmdesc = qjson_new(); +@@ -1113,6 +1114,7 @@ void qemu_savevm_state_complete_precopy(QEMUFile *f, bool iterable_only) + object_unref(OBJECT(vmdesc)); + + qemu_fflush(f); ++ return qemu_file_get_error(f); + } + + /* Give an estimate of the amount left to be transferred, +diff --git a/qapi-schema.json b/qapi-schema.json +index 84978bd..be51fa2 100644 +--- a/qapi-schema.json ++++ b/qapi-schema.json +@@ -587,6 +587,42 @@ + '*setup-time': 'int', + '*x-cpu-throttle-percentage': 'int'} } + ++ ++# @SaveVMInfo ++# ++# Information about current migration process. ++# ++# @status: #optional string describing the current savevm status. ++# This can be 'active', 'completed', 'failed'. ++# If this field is not returned, no savevm process ++# has been initiated ++# ++# @error: #optional string containing error message is status is failed. ++# ++# @total-time: #optional total amount of milliseconds since savevm started. ++# If savevm has ended, it returns the total save time ++# ++# @bytes: #optional total amount of data transfered ++# ++# Since: 1.3 ++## ++{ 'struct': 'SaveVMInfo', ++ 'data': {'*status': 'str', '*error': 'str', ++ '*total-time': 'int', '*bytes': 'int'} } ++ ++## ++# @query-savevm ++# ++# Returns information about current savevm process. ++# ++# Returns: @SaveVMInfo ++# ++# Since: 1.3 ++## ++{ 'command': 'query-savevm', 'returns': 'SaveVMInfo' } ++ ++## ++ + ## + # @query-migrate + # +@@ -3046,8 +3082,18 @@ + # + # Since: 1.2.0 + ## ++ + { 'command': 'query-target', 'returns': 'TargetInfo' } + ++{ 'command': 'savevm-start', 'data': { '*statefile': 'str' } } ++ ++{ 'command': 'snapshot-drive', 'data': { 'device': 'str', 'name': 'str' } } ++ ++{ 'command': 'delete-drive-snapshot', 'data': { 'device': 'str', 'name': 'str' } } ++ ++{ 'command': 'savevm-end' } ++ ++ + ## + # @QKeyCode: + # +diff --git a/qemu-options.hx b/qemu-options.hx +index 0eea4ee..cd465d5 100644 +--- a/qemu-options.hx ++++ b/qemu-options.hx +@@ -3099,6 +3099,19 @@ STEXI + Start right away with a saved state (@code{loadvm} in monitor) + ETEXI + ++DEF("loadstate", HAS_ARG, QEMU_OPTION_loadstate, \ ++ "-loadstate file\n" \ ++ " start right away with a saved state\n", ++ QEMU_ARCH_ALL) ++STEXI ++@item -loadstate @var{file} ++@findex -loadstate ++Start right away with a saved state. This option does not rollback ++disk state like @code{loadvm}, so user must make sure that disk ++have correct state. @var{file} can be any valid device URL. See the section ++for "Device URL Syntax" for more information. ++ETEXI ++ + #ifndef _WIN32 + DEF("daemonize", 0, QEMU_OPTION_daemonize, \ + "-daemonize daemonize QEMU after initializing\n", QEMU_ARCH_ALL) +diff --git a/qmp-commands.hx b/qmp-commands.hx +index 3dea794..5ef8540 100644 +--- a/qmp-commands.hx ++++ b/qmp-commands.hx +@@ -4675,6 +4675,36 @@ Example: + EQMP + + { ++ .name = "savevm-start", ++ .args_type = "statefile:s?", ++ .mhandler.cmd_new = qmp_marshal_input_savevm_start, ++ }, ++ ++ { ++ .name = "snapshot-drive", ++ .args_type = "device:s,name:s", ++ .mhandler.cmd_new = qmp_marshal_input_snapshot_drive, ++ }, ++ ++ { ++ .name = "delete-drive-snapshot", ++ .args_type = "device:s,name:s", ++ .mhandler.cmd_new = qmp_marshal_input_delete_drive_snapshot, ++ }, ++ ++ { ++ .name = "savevm-end", ++ .args_type = "", ++ .mhandler.cmd_new = qmp_marshal_input_savevm_end, ++ }, ++ ++ { ++ .name = "query-savevm", ++ .args_type = "", ++ .mhandler.cmd_new = qmp_marshal_input_query_savevm, ++ }, ++ ++ { + .name = "query-rocker", + .args_type = "name:s", + .mhandler.cmd_new = qmp_marshal_query_rocker, +diff --git a/savevm-async.c b/savevm-async.c +new file mode 100644 +index 0000000..8117443 +--- /dev/null ++++ b/savevm-async.c +@@ -0,0 +1,522 @@ ++#include "qemu-common.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" ++ ++/* #define DEBUG_SAVEVM_STATE */ ++ ++#ifdef DEBUG_SAVEVM_STATE ++#define DPRINTF(fmt, ...) \ ++ do { printf("savevm-async: " fmt, ## __VA_ARGS__); } while (0) ++#else ++#define DPRINTF(fmt, ...) \ ++ do { } while (0) ++#endif ++ ++enum { ++ SAVE_STATE_DONE, ++ SAVE_STATE_ERROR, ++ SAVE_STATE_ACTIVE, ++ SAVE_STATE_COMPLETED, ++ SAVE_STATE_CANCELLED ++}; ++ ++ ++static struct SnapshotState { ++ BlockDriverState *bs; ++ size_t bs_pos; ++ int state; ++ Error *error; ++ Error *blocker; ++ int saved_vm_running; ++ QEMUFile *file; ++ int64_t total_time; ++} snap_state; ++ ++SaveVMInfo *qmp_query_savevm(Error **errp) ++{ ++ SaveVMInfo *info = g_malloc0(sizeof(*info)); ++ struct SnapshotState *s = &snap_state; ++ ++ if (s->state != SAVE_STATE_DONE) { ++ info->has_bytes = true; ++ info->bytes = s->bs_pos; ++ switch (s->state) { ++ case SAVE_STATE_ERROR: ++ info->has_status = true; ++ info->status = g_strdup("failed"); ++ info->has_total_time = true; ++ info->total_time = s->total_time; ++ if (s->error) { ++ info->has_error = true; ++ info->error = g_strdup(error_get_pretty(s->error)); ++ } ++ break; ++ case SAVE_STATE_ACTIVE: ++ info->has_status = true; ++ info->status = g_strdup("active"); ++ info->has_total_time = true; ++ info->total_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) ++ - s->total_time; ++ break; ++ case SAVE_STATE_COMPLETED: ++ info->has_status = true; ++ info->status = g_strdup("completed"); ++ info->has_total_time = true; ++ info->total_time = s->total_time; ++ break; ++ } ++ } ++ ++ return info; ++} ++ ++static int save_snapshot_cleanup(void) ++{ ++ int ret = 0; ++ ++ DPRINTF("save_snapshot_cleanup\n"); ++ ++ snap_state.total_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - ++ snap_state.total_time; ++ ++ if (snap_state.file) { ++ ret = qemu_fclose(snap_state.file); ++ } ++ ++ if (snap_state.bs) { ++ /* try to truncate, but ignore errors (will fail on block devices). ++ * note: bdrv_read() need whole blocks, so we round up ++ */ ++ size_t size = (snap_state.bs_pos + BDRV_SECTOR_SIZE) & BDRV_SECTOR_MASK; ++ bdrv_truncate(snap_state.bs, size); ++ bdrv_op_unblock_all(snap_state.bs, snap_state.blocker); ++ error_free(snap_state.blocker); ++ snap_state.blocker = NULL; ++ bdrv_unref(snap_state.bs); ++ snap_state.bs = NULL; ++ } ++ ++ return ret; ++} ++ ++static void save_snapshot_error(const char *fmt, ...) ++{ ++ va_list ap; ++ char *msg; ++ ++ va_start(ap, fmt); ++ msg = g_strdup_vprintf(fmt, ap); ++ va_end(ap); ++ ++ DPRINTF("save_snapshot_error: %s\n", msg); ++ ++ if (!snap_state.error) { ++ error_set(&snap_state.error, ERROR_CLASS_GENERIC_ERROR, "%s", msg); ++ } ++ ++ g_free (msg); ++ ++ snap_state.state = SAVE_STATE_ERROR; ++ ++ save_snapshot_cleanup(); ++} ++ ++static void save_snapshot_completed(void) ++{ ++ DPRINTF("save_snapshot_completed\n"); ++ ++ if (save_snapshot_cleanup() < 0) { ++ snap_state.state = SAVE_STATE_ERROR; ++ } else { ++ snap_state.state = SAVE_STATE_COMPLETED; ++ } ++} ++ ++static int block_state_close(void *opaque) ++{ ++ snap_state.file = NULL; ++ return bdrv_flush(snap_state.bs); ++} ++ ++static int block_state_put_buffer(void *opaque, const uint8_t *buf, ++ int64_t pos, int size) ++{ ++ int ret; ++ ++ assert(pos == snap_state.bs_pos); ++ ++ if ((ret = bdrv_pwrite(snap_state.bs, snap_state.bs_pos, buf, size)) > 0) { ++ snap_state.bs_pos += ret; ++ } ++ ++ return ret; ++} ++ ++static int store_and_stop(void) { ++ if (global_state_store()) { ++ save_snapshot_error("Error saving global state"); ++ return 1; ++ } ++ if (runstate_is_running()) { ++ vm_stop(RUN_STATE_SAVE_VM); ++ } ++ return 0; ++} ++ ++static void process_savevm_co(void *opaque) ++{ ++ 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_mutex_lock_iothread(); ++ ++ if (ret < 0) { ++ save_snapshot_error("qemu_savevm_state_begin failed"); ++ return; ++ } ++ ++ while (snap_state.state == SAVE_STATE_ACTIVE) { ++ uint64_t pending_size; ++ ++ pending_size = qemu_savevm_state_pending(snap_state.file, 0); ++ ++ if (pending_size) { ++ ret = qemu_savevm_state_iterate(snap_state.file); ++ if (ret < 0) { ++ save_snapshot_error("qemu_savevm_state_iterate error %d", ret); ++ break; ++ } ++ DPRINTF("savevm inerate pending size %lu ret %d\n", pending_size, ret); ++ } else { ++ DPRINTF("done iterating\n"); ++ if (store_and_stop()) ++ break; ++ DPRINTF("savevm inerate finished\n"); ++ qemu_savevm_state_complete_precopy(snap_state.file); ++ DPRINTF("save complete\n"); ++ save_snapshot_completed(); ++ break; ++ } ++ ++ /* stop the VM if we get to the end of available space, ++ * or if pending_size is just a few MB ++ */ ++ maxlen = bdrv_getlength(snap_state.bs) - 30*1024*1024; ++ if ((pending_size < 100000) || ++ ((snap_state.bs_pos + pending_size) >= maxlen)) { ++ if (store_and_stop()) ++ break; ++ } ++ } ++ ++ if(snap_state.state == SAVE_STATE_CANCELLED) { ++ save_snapshot_completed(); ++ Error *errp = NULL; ++ qmp_savevm_end(&errp); ++ } ++ ++} ++ ++static const QEMUFileOps block_file_ops = { ++ .put_buffer = block_state_put_buffer, ++ .close = block_state_close, ++}; ++ ++ ++void qmp_savevm_start(bool has_statefile, const char *statefile, Error **errp) ++{ ++ BlockDriver *drv = NULL; ++ Error *local_err = NULL; ++ ++ int bdrv_oflags = BDRV_O_CACHE_WB | BDRV_O_RDWR; ++ int ret; ++ ++ if (snap_state.state != SAVE_STATE_DONE) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, ++ "VM snapshot already started\n"); ++ return; ++ } ++ ++ /* initialize snapshot info */ ++ snap_state.saved_vm_running = runstate_is_running(); ++ snap_state.bs_pos = 0; ++ snap_state.total_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); ++ snap_state.blocker = NULL; ++ ++ if (snap_state.error) { ++ error_free(snap_state.error); ++ snap_state.error = NULL; ++ } ++ ++ if (!has_statefile) { ++ vm_stop(RUN_STATE_SAVE_VM); ++ snap_state.state = SAVE_STATE_COMPLETED; ++ return; ++ } ++ ++ if (qemu_savevm_state_blocked(errp)) { ++ return; ++ } ++ ++ /* Open the image */ ++ snap_state.bs = bdrv_new(); ++ ++ QDict *options = NULL; ++ options = qdict_new(); ++ qdict_put(options, "driver", qstring_from_str("raw")); ++ ret = bdrv_open(&snap_state.bs, statefile, NULL, options, bdrv_oflags, drv, &local_err); ++ if (ret < 0) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "failed to open '%s'", statefile); ++ goto restart; ++ } ++ ++ snap_state.file = qemu_fopen_ops(&snap_state, &block_file_ops); ++ ++ if (!snap_state.file) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "failed to open '%s'", statefile); ++ goto restart; ++ } ++ ++ ++ error_setg(&snap_state.blocker, "block device is in use by savevm"); ++ bdrv_op_block_all(snap_state.bs, snap_state.blocker); ++ ++ Coroutine *co = qemu_coroutine_create(process_savevm_co); ++ qemu_coroutine_enter(co, NULL); ++ ++ return; ++ ++restart: ++ ++ save_snapshot_error("setup failed"); ++ ++ if (snap_state.saved_vm_running) { ++ vm_start(); ++ } ++} ++ ++void qmp_savevm_end(Error **errp) ++{ ++ if (snap_state.state == SAVE_STATE_DONE) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, ++ "VM snapshot not started\n"); ++ return; ++ } ++ ++ if (snap_state.state == SAVE_STATE_ACTIVE) { ++ snap_state.state = SAVE_STATE_CANCELLED; ++ return; ++ } ++ ++ if (snap_state.saved_vm_running) { ++ vm_start(); ++ } ++ ++ snap_state.state = SAVE_STATE_DONE; ++} ++ ++void qmp_snapshot_drive(const char *device, const char *name, Error **errp) ++{ ++ BlockBackend *blk; ++ BlockDriverState *bs; ++ QEMUSnapshotInfo sn1, *sn = &sn1; ++ int ret; ++#ifdef _WIN32 ++ struct _timeb tb; ++#else ++ struct timeval tv; ++#endif ++ ++ if (snap_state.state != SAVE_STATE_COMPLETED) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, ++ "VM snapshot not ready/started\n"); ++ return; ++ } ++ ++ blk = blk_by_name(device); ++ if (!blk) { ++ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, ++ "Device '%s' not found", device); ++ return; ++ } ++ ++ bs = blk_bs(blk); ++ if (!bdrv_is_inserted(bs)) { ++ error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, device); ++ return; ++ } ++ ++ if (bdrv_is_read_only(bs)) { ++ error_setg(errp, "Node '%s' is read only", device); ++ return; ++ } ++ ++ if (!bdrv_can_snapshot(bs)) { ++ error_setg(errp, QERR_UNSUPPORTED); ++ return; ++ } ++ ++ if (bdrv_snapshot_find(bs, sn, name) >= 0) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, ++ "snapshot '%s' already exists", name); ++ return; ++ } ++ ++ sn = &sn1; ++ memset(sn, 0, sizeof(*sn)); ++ ++#ifdef _WIN32 ++ _ftime(&tb); ++ sn->date_sec = tb.time; ++ sn->date_nsec = tb.millitm * 1000000; ++#else ++ gettimeofday(&tv, NULL); ++ sn->date_sec = tv.tv_sec; ++ sn->date_nsec = tv.tv_usec * 1000; ++#endif ++ sn->vm_clock_nsec = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); ++ ++ pstrcpy(sn->name, sizeof(sn->name), name); ++ ++ sn->vm_state_size = 0; /* do not save state */ ++ ++ ret = bdrv_snapshot_create(bs, sn); ++ if (ret < 0) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, ++ "Error while creating snapshot on '%s'\n", device); ++ return; ++ } ++} ++ ++void qmp_delete_drive_snapshot(const char *device, const char *name, ++ Error **errp) ++{ ++ BlockBackend *blk; ++ BlockDriverState *bs; ++ QEMUSnapshotInfo sn1, *sn = &sn1; ++ Error *local_err = NULL; ++ ++ int ret; ++ ++ blk = blk_by_name(device); ++ if (!blk) { ++ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, ++ "Device '%s' not found", device); ++ return; ++ } ++ ++ bs = blk_bs(blk); ++ if (bdrv_is_read_only(bs)) { ++ error_setg(errp, "Node '%s' is read only", device); ++ return; ++ } ++ ++ if (!bdrv_can_snapshot(bs)) { ++ error_setg(errp, QERR_UNSUPPORTED); ++ return; ++ } ++ ++ if (bdrv_snapshot_find(bs, sn, name) < 0) { ++ /* return success if snapshot does not exists */ ++ return; ++ } ++ ++ 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; ++ } ++} ++ ++static int loadstate_get_buffer(void *opaque, uint8_t *buf, int64_t pos, ++ int size) ++{ ++ BlockDriverState *bs = (BlockDriverState *)opaque; ++ int64_t maxlen = bdrv_getlength(bs); ++ if (pos > maxlen) { ++ return -EIO; ++ } ++ if ((pos + size) > maxlen) { ++ size = maxlen - pos - 1; ++ } ++ if (size == 0) { ++ return 0; ++ } ++ return bdrv_pread(bs, pos, buf, size); ++} ++ ++static const QEMUFileOps loadstate_file_ops = { ++ .get_buffer = loadstate_get_buffer, ++}; ++ ++int load_state_from_blockdev(const char *filename) ++{ ++ BlockDriverState *bs = NULL; ++ BlockDriver *drv = NULL; ++ Error *local_err = NULL; ++ Error *blocker = NULL; ++ ++ QEMUFile *f; ++ int ret; ++ ++ bs = bdrv_new(); ++ ret = bdrv_open(&bs, filename, NULL, NULL, BDRV_O_CACHE_WB, drv, &local_err); ++ error_setg(&blocker, "block device is in use by load state"); ++ bdrv_op_block_all(bs, blocker); ++ ++ if (ret < 0) { ++ error_report("Could not open VM state file"); ++ goto the_end; ++ } ++ ++ /* restore the VM state */ ++ f = qemu_fopen_ops(bs, &loadstate_file_ops); ++ if (!f) { ++ error_report("Could not open VM state file"); ++ ret = -EINVAL; ++ goto the_end; ++ } ++ ++ qemu_system_reset(VMRESET_SILENT); ++ migration_incoming_state_new(f); ++ ret = qemu_loadvm_state(f); ++ ++ qemu_fclose(f); ++ migration_incoming_state_destroy(); ++ if (ret < 0) { ++ error_report("Error %d while loading VM state", ret); ++ goto the_end; ++ } ++ ++ ret = 0; ++ ++ the_end: ++ if (bs) { ++ bdrv_op_unblock_all(bs, blocker); ++ error_free(blocker); ++ bdrv_unref(bs); ++ } ++ return ret; ++} +diff --git a/vl.c b/vl.c +index 77c734e..5379941 100644 +--- a/vl.c ++++ b/vl.c +@@ -2985,6 +2985,7 @@ int main(int argc, char **argv, char **envp) + int optind; + const char *optarg; + const char *loadvm = NULL; ++ const char *loadstate = NULL; + MachineClass *machine_class; + const char *cpu_model; + const char *vga_model = NULL; +@@ -3611,6 +3612,9 @@ int main(int argc, char **argv, char **envp) + case QEMU_OPTION_loadvm: + loadvm = optarg; + break; ++ case QEMU_OPTION_loadstate: ++ loadstate = optarg; ++ break; + case QEMU_OPTION_full_screen: + full_screen = 1; + break; +@@ -4656,6 +4660,10 @@ int main(int argc, char **argv, char **envp) + if (load_vmstate(loadvm) < 0) { + autostart = 0; + } ++ } else if (loadstate) { ++ if (load_state_from_blockdev(loadstate) < 0) { ++ autostart = 0; ++ } + } + + qdev_prop_check_globals(); +-- +2.1.4 + diff --git a/debian/patches/pve/0024-backup-vma-allow-empty-backups.patch b/debian/patches/pve/0024-backup-vma-allow-empty-backups.patch new file mode 100644 index 0000000..5fbc749 --- /dev/null +++ b/debian/patches/pve/0024-backup-vma-allow-empty-backups.patch @@ -0,0 +1,268 @@ +From b2dde9133b9369c29bc2902ac4d674a5ee268cf2 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 16:31:51 +0100 +Subject: [PATCH 24/41] backup: vma: allow empty backups + +--- + vma-reader.c | 29 ++++++++++----------- + vma-writer.c | 30 ++++++++++++++-------- + vma.c | 84 ++++++++++++++++++++++++++++++++++++++---------------------- + vma.h | 1 + + 4 files changed, 88 insertions(+), 56 deletions(-) + +diff --git a/vma-reader.c b/vma-reader.c +index d9f43fe..5d0d3ea 100644 +--- a/vma-reader.c ++++ b/vma-reader.c +@@ -334,11 +334,6 @@ static int vma_reader_read_head(VmaReader *vmar, Error **errp) + } + } + +- if (!count) { +- error_setg(errp, "vma does not contain data"); +- return -1; +- } +- + 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]); +@@ -830,16 +825,20 @@ static int vma_reader_restore_full(VmaReader *vmar, int vmstate_fd, + } + + if (verbose) { +- 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); ++ 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; +diff --git a/vma-writer.c b/vma-writer.c +index 3c77e98..5cc4564 100644 +--- a/vma-writer.c ++++ b/vma-writer.c +@@ -258,7 +258,7 @@ vma_queue_write(VmaWriter *vmaw, const void *buf, size_t bytes) + } + + vmaw->co_writer = NULL; +- ++ + return (done == bytes) ? bytes : -1; + } + +@@ -382,10 +382,6 @@ static int coroutine_fn vma_write_header(VmaWriter *vmaw) + time_t ctime = time(NULL); + head->ctime = GUINT64_TO_BE(ctime); + +- if (!vmaw->stream_count) { +- return -1; +- } +- + 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]); +@@ -502,6 +498,23 @@ static int vma_count_open_streams(VmaWriter *vmaw) + 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) +@@ -529,12 +542,7 @@ vma_writer_close_stream(VmaWriter *vmaw, uint8_t dev_id) + + if (open_drives <= 0) { + DPRINTF("vma_writer_set_status all drives completed\n"); +- 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_close_stream: flush failed"); +- } ++ vma_writer_flush_output(vmaw); + } + + return open_drives; +diff --git a/vma.c b/vma.c +index 6a33352..00f20b0 100644 +--- a/vma.c ++++ b/vma.c +@@ -33,7 +33,7 @@ static void help(void) + "\n" + "vma list \n" + "vma config [-c config]\n" +- "vma create [-c config] pathname ...\n" ++ "vma create [-c config] pathname ...\n" + "vma extract [-r ] \n" + "vma verify [-v]\n" + ; +@@ -401,6 +401,18 @@ typedef struct 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; +@@ -474,8 +486,8 @@ static int create_archive(int argc, char **argv) + } + + +- /* make sure we have archive name and at least one path */ +- if ((optind + 2) > argc) { ++ /* make sure we an archive name */ ++ if ((optind + 1) > argc) { + help(); + } + +@@ -510,11 +522,11 @@ static int create_archive(int argc, char **argv) + l = g_list_next(l); + } + +- int ind = 0; ++ int devcount = 0; + while (optind < argc) { + const char *path = argv[optind++]; + char *devname = NULL; +- path = extract_devname(path, &devname, ind++); ++ path = extract_devname(path, &devname, devcount++); + + BlockDriver *drv = NULL; + Error *errp = NULL; +@@ -546,37 +558,49 @@ static int create_archive(int argc, char **argv) + int percent = 0; + int last_percent = -1; + +- 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; ++ 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); + +- 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; ++ last_percent = percent; + } + } +- 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; + } + } +- +- if (vmastat.closed) { +- break; +- } ++ } else { ++ Coroutine *co = qemu_coroutine_create(backup_run_empty); ++ qemu_coroutine_enter(co, vmaw); ++ while (1) { ++ main_loop_wait(false); ++ vma_writer_get_status(vmaw, &vmastat); ++ if (vmastat.closed) { ++ break; ++ } ++ } + } + + bdrv_drain_all(); +diff --git a/vma.h b/vma.h +index 98377e4..365ceb2 100644 +--- a/vma.h ++++ b/vma.h +@@ -128,6 +128,7 @@ int64_t coroutine_fn vma_writer_write(VmaWriter *vmaw, uint8_t dev_id, + 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, ...); +-- +2.1.4 + diff --git a/debian/patches/pve/0025-backup-vma-add-BlockDriver-to-bdrv_open-in-extract_c.patch b/debian/patches/pve/0025-backup-vma-add-BlockDriver-to-bdrv_open-in-extract_c.patch new file mode 100644 index 0000000..263f44f --- /dev/null +++ b/debian/patches/pve/0025-backup-vma-add-BlockDriver-to-bdrv_open-in-extract_c.patch @@ -0,0 +1,32 @@ +From 194ceac1b39413a6a1eaeb8da62188effe3b5742 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 16:32:39 +0100 +Subject: [PATCH 25/41] backup: vma: add BlockDriver to bdrv_open in + extract_content + +--- + vma.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/vma.c b/vma.c +index 00f20b0..543e7d8 100644 +--- a/vma.c ++++ b/vma.c +@@ -299,7 +299,13 @@ static int extract_content(int argc, char **argv) + } + + BlockDriverState *bs = bdrv_new(); +- if (errp || bdrv_open(&bs, devfn, NULL, NULL, flags, NULL, &errp)) { ++ ++ const char *tmp = g_strrstr(devfn, "."); ++ const char *format = (tmp == NULL) ? "raw" : ++tmp; ++ ++ BlockDriver *drv = bdrv_find_format(format); ++ ++ if (errp || bdrv_open(&bs, devfn, NULL, NULL, flags, drv, &errp)) { + g_error("can't open file %s - %s", devfn, + error_get_pretty(errp)); + } +-- +2.1.4 + diff --git a/debian/patches/pve/0026-glusterfs-daemonize.patch b/debian/patches/pve/0026-glusterfs-daemonize.patch new file mode 100644 index 0000000..c7c212d --- /dev/null +++ b/debian/patches/pve/0026-glusterfs-daemonize.patch @@ -0,0 +1,31 @@ +From 34ccb14434cfc6b61e568aa076bc09231df45981 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 16:33:10 +0100 +Subject: [PATCH 26/41] glusterfs: daemonize + +--- + block/gluster.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/block/gluster.c b/block/gluster.c +index 0857c14..e3c0dbc 100644 +--- a/block/gluster.c ++++ b/block/gluster.c +@@ -196,9 +196,11 @@ static struct glfs *qemu_gluster_init(GlusterConf *gconf, const char *filename, + * TODO: Use GF_LOG_ERROR instead of hard code value of 4 here when + * GlusterFS makes GF_LOG_* macros available to libgfapi users. + */ +- ret = glfs_set_logging(glfs, "-", 4); +- if (ret < 0) { +- goto out; ++ if (!is_daemonized()) { ++ ret = glfs_set_logging(glfs, "-", 4); ++ if (ret < 0) { ++ goto out; ++ } + } + + ret = glfs_init(glfs); +-- +2.1.4 + diff --git a/debian/patches/pve/0027-gluster-possiblity-to-specify-a-secondary-server.patch b/debian/patches/pve/0027-gluster-possiblity-to-specify-a-secondary-server.patch new file mode 100644 index 0000000..439a39b --- /dev/null +++ b/debian/patches/pve/0027-gluster-possiblity-to-specify-a-secondary-server.patch @@ -0,0 +1,127 @@ +From 7b58868a321a38585c20a6eecdaa52d2a85e8385 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 16:33:25 +0100 +Subject: [PATCH 27/41] gluster: possiblity to specify a secondary server + +--- + block/gluster.c | 40 +++++++++++++++++++++++++++++++--------- + 1 file changed, 31 insertions(+), 9 deletions(-) + +diff --git a/block/gluster.c b/block/gluster.c +index e3c0dbc..8946e82 100644 +--- a/block/gluster.c ++++ b/block/gluster.c +@@ -26,6 +26,7 @@ typedef struct BDRVGlusterState { + + typedef struct GlusterConf { + char *server; ++ char *backupserver; + int port; + char *volname; + char *image; +@@ -36,6 +37,7 @@ static void qemu_gluster_gconf_free(GlusterConf *gconf) + { + if (gconf) { + g_free(gconf->server); ++ g_free(gconf->backupserver); + g_free(gconf->volname); + g_free(gconf->image); + g_free(gconf->transport); +@@ -69,7 +71,7 @@ static int parse_volume_options(GlusterConf *gconf, char *path) + } + + /* +- * file=gluster[+transport]://[server[:port]]/volname/image[?socket=...] ++ * file=gluster[+transport]://[server[:port]]/volname/image[?socket=...|?s2=...] + * + * 'gluster' is the protocol. + * +@@ -85,6 +87,8 @@ static int parse_volume_options(GlusterConf *gconf, char *path) + * The 'socket' field needs to be populated with the path to unix domain + * socket. + * ++ * 's2' can be used to specifies a second volfile server. ++ * + * 'port' is the port number on which glusterd is listening. This is optional + * and if not specified, QEMU will send 0 which will make gluster to use the + * default port. If the transport type is unix, then 'port' should not be +@@ -97,6 +101,7 @@ static int parse_volume_options(GlusterConf *gconf, char *path) + * Examples: + * + * file=gluster://1.2.3.4/testvol/a.img ++ * file=gluster://1.2.3.4/testvol/a.img?s2=1.2.3.5 + * file=gluster+tcp://1.2.3.4/testvol/a.img + * file=gluster+tcp://1.2.3.4:24007/testvol/dir/a.img + * file=gluster+tcp://[1:2:3:4:5:6:7:8]/testvol/dir/a.img +@@ -111,6 +116,8 @@ static int qemu_gluster_parseuri(GlusterConf *gconf, const char *filename) + QueryParams *qp = NULL; + bool is_unix = false; + int ret = 0; ++ int i; ++ char *socket = NULL; + + uri = uri_parse(filename); + if (!uri) { +@@ -138,21 +145,28 @@ static int qemu_gluster_parseuri(GlusterConf *gconf, const char *filename) + } + + qp = query_params_parse(uri->query); +- if (qp->n > 1 || (is_unix && !qp->n) || (!is_unix && qp->n)) { ++ for (i = 0; i < qp->n; i++) { ++ if (!is_unix && strcmp(qp->p[i].name, "s2") == 0) { ++ gconf->backupserver = g_strdup(qp->p[i].value); ++ } else if (is_unix && strcmp(qp->p[i].name, "socket") == 0) { ++ socket = qp->p[i].value; ++ } else { ++ ret = -EINVAL; ++ goto out; ++ } ++ } ++ ++ if (is_unix && !socket) { + ret = -EINVAL; + goto out; + } + + if (is_unix) { +- if (uri->server || uri->port) { ++ if (!socket || uri->server || uri->port) { + ret = -EINVAL; + goto out; + } +- if (strcmp(qp->p[0].name, "socket")) { +- ret = -EINVAL; +- goto out; +- } +- gconf->server = g_strdup(qp->p[0].value); ++ gconf->server = g_strdup(socket); + } else { + gconf->server = g_strdup(uri->server ? uri->server : "localhost"); + gconf->port = uri->port; +@@ -176,7 +190,7 @@ static struct glfs *qemu_gluster_init(GlusterConf *gconf, const char *filename, + ret = qemu_gluster_parseuri(gconf, filename); + if (ret < 0) { + error_setg(errp, "Usage: file=gluster[+transport]://[server[:port]]/" +- "volname/image[?socket=...]"); ++ "volname/image[?socket=...|?s2=...]"); + errno = -ret; + goto out; + } +@@ -192,6 +206,14 @@ static struct glfs *qemu_gluster_init(GlusterConf *gconf, const char *filename, + goto out; + } + ++ if (gconf->backupserver) { ++ ret = glfs_set_volfile_server(glfs, gconf->transport, gconf->backupserver, ++ gconf->port); ++ if (ret < 0) { ++ goto out; ++ } ++ } ++ + /* + * TODO: Use GF_LOG_ERROR instead of hard code value of 4 here when + * GlusterFS makes GF_LOG_* macros available to libgfapi users. +-- +2.1.4 + diff --git a/debian/patches/pve/0028-qmp-add-get_link_status.patch b/debian/patches/pve/0028-qmp-add-get_link_status.patch new file mode 100644 index 0000000..9ad59dd --- /dev/null +++ b/debian/patches/pve/0028-qmp-add-get_link_status.patch @@ -0,0 +1,126 @@ +From 1f5975e6b428b0f62fd538b36880a23ec25da9f5 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 16:34:41 +0100 +Subject: [PATCH 28/41] qmp: add get_link_status + +--- + net/net.c | 27 +++++++++++++++++++++++++++ + qapi-schema.json | 15 +++++++++++++++ + qmp-commands.hx | 23 +++++++++++++++++++++++ + scripts/qapi.py | 2 ++ + 4 files changed, 67 insertions(+) + +diff --git a/net/net.c b/net/net.c +index ade6051..afd6ea6 100644 +--- a/net/net.c ++++ b/net/net.c +@@ -1289,6 +1289,33 @@ void hmp_info_network(Monitor *mon, const QDict *qdict) + } + } + ++int64_t qmp_get_link_status(const char *name, Error **errp) ++{ ++ NetClientState *ncs[MAX_QUEUE_NUM]; ++ NetClientState *nc; ++ int queues; ++ bool ret; ++ ++ queues = qemu_find_net_clients_except(name, ncs, ++ NET_CLIENT_OPTIONS_KIND_MAX, ++ MAX_QUEUE_NUM); ++ ++ if (queues == 0) { ++ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, ++ "Device '%s' not found", name); ++ return (int64_t) -1; ++ } ++ ++ nc = ncs[0]; ++ ret = ncs[0]->link_down; ++ ++ if (nc->peer->info->type == NET_CLIENT_OPTIONS_KIND_NIC) { ++ ret = ncs[0]->peer->link_down; ++ } ++ ++ return (int64_t) ret ? 0 : 1; ++} ++ + 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 be51fa2..172e200 100644 +--- a/qapi-schema.json ++++ b/qapi-schema.json +@@ -1653,6 +1653,21 @@ + { 'command': 'set_link', 'data': {'name': 'str', 'up': 'bool'} } + + ## ++# @get_link_status ++# ++# Get the current link state of the nics or nic. ++# ++# @name: name of the nic you get the state of ++# ++# Return: If link is up 1 ++# If link is down 0 ++# If an error occure an empty string. ++# ++# Notes: this is an Proxmox VE extension and not offical part of Qemu. ++## ++{ 'command': 'get_link_status', 'data': {'name': 'str'}, 'returns': 'int'} ++ ++## + # @balloon: + # + # Request the balloon driver to change its balloon size. +diff --git a/qmp-commands.hx b/qmp-commands.hx +index 5ef8540..0ce913a 100644 +--- a/qmp-commands.hx ++++ b/qmp-commands.hx +@@ -1771,6 +1771,29 @@ Example: + EQMP + + { ++ .name = "get_link_status", ++ .args_type = "name:s", ++ .mhandler.cmd_new = qmp_marshal_input_get_link_status, ++ }, ++ ++SQMP ++get_link_status ++-------- ++ ++Get the link status of a network adapter. ++ ++Arguments: ++ ++- "name": network device name (json-string) ++ ++Example: ++ ++-> { "execute": "get_link_status", "arguments": { "name": "e1000.0" } } ++<- { "return": {1} } ++ ++EQMP ++ ++ { + .name = "getfd", + .args_type = "fdname:s", + .params = "getfd name", +diff --git a/scripts/qapi.py b/scripts/qapi.py +index 7c50cc4..8cb0f78 100644 +--- a/scripts/qapi.py ++++ b/scripts/qapi.py +@@ -38,6 +38,8 @@ builtin_types = { + + # Whitelist of commands allowed to return a non-dictionary + returns_whitelist = [ ++ 'get_link_status', ++ + # From QMP: + 'human-monitor-command', + 'qom-get', +-- +2.1.4 + diff --git a/debian/patches/pve/0029-smm_available-false.patch b/debian/patches/pve/0029-smm_available-false.patch new file mode 100644 index 0000000..6ba51c9 --- /dev/null +++ b/debian/patches/pve/0029-smm_available-false.patch @@ -0,0 +1,26 @@ +From 67eb6fa3ea9193e43e6744c17e0cbaf1294de1b9 Mon Sep 17 00:00:00 2001 +From: Alexandre Derumier +Date: Tue, 29 Sep 2015 15:37:44 +0200 +Subject: [PATCH 29/41] smm_available = false + +Signed-off-by: Alexandre Derumier +--- + hw/i386/pc.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/hw/i386/pc.c b/hw/i386/pc.c +index a20df3c..ba166d7 100644 +--- a/hw/i386/pc.c ++++ b/hw/i386/pc.c +@@ -1839,7 +1839,7 @@ bool pc_machine_is_smm_enabled(PCMachineState *pcms) + if (tcg_enabled() || qtest_enabled()) { + smm_available = true; + } else if (kvm_enabled()) { +- smm_available = kvm_has_smm(); ++ smm_available = false; + } + + if (smm_available) { +-- +2.1.4 + diff --git a/debian/patches/pve/0030-use-whitespace-between-VERSION-and-PKGVERSION.patch b/debian/patches/pve/0030-use-whitespace-between-VERSION-and-PKGVERSION.patch new file mode 100644 index 0000000..870f861 --- /dev/null +++ b/debian/patches/pve/0030-use-whitespace-between-VERSION-and-PKGVERSION.patch @@ -0,0 +1,27 @@ +From 2e29179b3ea1bd1f5db7c03aedc8dcea19980063 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 16:50:05 +0100 +Subject: [PATCH 30/41] use whitespace between VERSION and PKGVERSION + +Our kvm version parser expects a white space or comma after +the version string, see PVE::QemuServer::kvm_user_version() +--- + vl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/vl.c b/vl.c +index 5379941..63c6798 100644 +--- a/vl.c ++++ b/vl.c +@@ -1934,7 +1934,7 @@ static void main_loop(void) + + static void version(void) + { +- printf("QEMU emulator version " QEMU_VERSION QEMU_PKGVERSION ", Copyright (c) 2003-2008 Fabrice Bellard\n"); ++ printf("QEMU emulator version " QEMU_VERSION " " QEMU_PKGVERSION ", Copyright (c) 2003-2008 Fabrice Bellard\n"); + } + + static void help(int exitcode) +-- +2.1.4 + diff --git a/debian/patches/pve/0031-vma-add-firewall.patch b/debian/patches/pve/0031-vma-add-firewall.patch new file mode 100644 index 0000000..bf89625 --- /dev/null +++ b/debian/patches/pve/0031-vma-add-firewall.patch @@ -0,0 +1,158 @@ +From b71180331cfe834000b469770c879a1277dbc273 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Wed, 9 Dec 2015 16:51:23 +0100 +Subject: [PATCH 31/41] vma: add firewall + +--- + blockdev.c | 78 ++++++++++++++++++++++++++++++++++---------------------- + hmp.c | 2 +- + qapi-schema.json | 1 + + qmp-commands.hx | 2 +- + 4 files changed, 51 insertions(+), 32 deletions(-) + +diff --git a/blockdev.c b/blockdev.c +index 4d182d6..5e1234b 100644 +--- a/blockdev.c ++++ b/blockdev.c +@@ -3056,6 +3056,44 @@ void qmp_backup_cancel(Error **errp) + } + } + ++static int config_to_vma(const char *file, BackupFormat format, ++ const char *backup_dir, VmaWriter *vmaw, ++ 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) { ++ 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); ++ g_free(cdata); ++ g_free(basename); ++ return 1; ++ } ++ } ++ ++ g_free(basename); ++ g_free(cdata); ++ ++ return 0; ++} ++ + static void pvebackup_run_next_job(void) + { + GList *l = backup_state.di_list; +@@ -3082,6 +3120,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_devlist, const char *devlist, + bool has_speed, int64_t speed, Error **errp) + { +@@ -3234,38 +3273,17 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format, + + /* add configuration file to archive */ + if (has_config_file) { +- char *cdata = NULL; +- gsize clen = 0; +- GError *err = NULL; +- if (!g_file_get_contents(config_file, &cdata, &clen, &err)) { +- error_setg(errp, "unable to read file '%s'", config_file); +- goto err; +- } +- +- char *basename = g_path_get_basename(config_file); +- +- if (format == BACKUP_FORMAT_VMA) { +- if (vma_writer_add_config(vmaw, basename, cdata, clen) != 0) { +- error_setg(errp, "unable to add config data to vma archive"); +- g_free(cdata); +- g_free(basename); +- goto err; +- } +- } 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); +- goto err; +- } +- } +- +- g_free(basename); +- g_free(cdata); ++ 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_dir, vmaw, errp) != 0) { ++ goto err; ++ } ++ } + /* initialize global backup_state now */ + + backup_state.cancel = false; +diff --git a/hmp.c b/hmp.c +index 724b5d3..a361e26 100644 +--- a/hmp.c ++++ b/hmp.c +@@ -1518,7 +1518,7 @@ void hmp_backup(Monitor *mon, const QDict *qdict) + int64_t speed = qdict_get_try_int(qdict, "speed", 0); + + qmp_backup(backup_file, true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA, +- false, NULL, !!devlist, ++ false, NULL, false, NULL, !!devlist, + devlist, qdict_haskey(qdict, "speed"), speed, &error); + + hmp_handle_error(mon, &error); +diff --git a/qapi-schema.json b/qapi-schema.json +index 172e200..a02158e 100644 +--- a/qapi-schema.json ++++ b/qapi-schema.json +@@ -422,6 +422,7 @@ + { 'command': 'backup', 'data': { 'backup-file': 'str', + '*format': 'BackupFormat', + '*config-file': 'str', ++ '*firewall-file': 'str', + '*devlist': 'str', '*speed': 'int' }, + 'returns': 'UuidInfo' } + +diff --git a/qmp-commands.hx b/qmp-commands.hx +index 0ce913a..de07f54 100644 +--- a/qmp-commands.hx ++++ b/qmp-commands.hx +@@ -1255,7 +1255,7 @@ EQMP + + { + .name = "backup", +- .args_type = "backup-file:s,format:s?,config-file:F?,speed:o?,devlist:s?", ++ .args_type = "backup-file:s,format:s?,config-file:F?,firewall-file:F?,speed:o?,devlist:s?", + .mhandler.cmd_new = qmp_marshal_input_backup, + }, + +-- +2.1.4 + diff --git a/debian/patches/pve/0032-vma-writer-aio_set_fd_handler-update.patch b/debian/patches/pve/0032-vma-writer-aio_set_fd_handler-update.patch new file mode 100644 index 0000000..bf87033 --- /dev/null +++ b/debian/patches/pve/0032-vma-writer-aio_set_fd_handler-update.patch @@ -0,0 +1,33 @@ +From 712f8526b54566361c9d1e4cc81a40714f29a26f Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Thu, 10 Dec 2015 14:25:01 +0100 +Subject: [PATCH 32/41] vma-writer: aio_set_fd_handler update + +passing 'false' as new is_external parameter to +aio_set_fd_handler as per commit +dca21e23ba: aio: Add "is_external" flag for event handlers + +For-patch: introduce new vma archive format +--- + vma-writer.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/vma-writer.c b/vma-writer.c +index 5cc4564..f5a7abd 100644 +--- a/vma-writer.c ++++ b/vma-writer.c +@@ -231,9 +231,9 @@ vma_queue_write(VmaWriter *vmaw, const void *buf, size_t bytes) + vmaw->co_writer = qemu_coroutine_self(); + + while (done < bytes) { +- aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, NULL, vma_co_continue_write, vmaw); ++ aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, false, NULL, vma_co_continue_write, vmaw); + qemu_coroutine_yield(); +- aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, NULL, NULL, NULL); ++ aio_set_fd_handler(qemu_get_aio_context(), vmaw->fd, false, NULL, NULL, NULL); + if (vmaw->status < 0) { + DPRINTF("vma_queue_write detected canceled backup\n"); + done = -1; +-- +2.1.4 + diff --git a/debian/patches/pve/0033-vma-bdrv_open-dropped-the-drv-parameter.patch b/debian/patches/pve/0033-vma-bdrv_open-dropped-the-drv-parameter.patch new file mode 100644 index 0000000..ae17be5 --- /dev/null +++ b/debian/patches/pve/0033-vma-bdrv_open-dropped-the-drv-parameter.patch @@ -0,0 +1,56 @@ +From fecf3abe8512f12a33f8250cfb4e2687c62d4105 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Thu, 10 Dec 2015 14:43:07 +0100 +Subject: [PATCH 33/41] vma: bdrv_open dropped the drv parameter + +we now pass the 'drive' option via a qdict. +As per commit: +6ebf9aa2ef: block: Drop drv parameter from bdrv_open() + +For-patch: introduce new vma archive format +--- + vma.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/vma.c b/vma.c +index 543e7d8..cc48013 100644 +--- a/vma.c ++++ b/vma.c +@@ -25,6 +25,7 @@ + #include "qemu-common.h" + #include "qemu/error-report.h" + #include "qemu/main-loop.h" ++#include "sysemu/char.h" /* qstring_from_str */ + + static void help(void) + { +@@ -302,10 +303,10 @@ static int extract_content(int argc, char **argv) + + const char *tmp = g_strrstr(devfn, "."); + const char *format = (tmp == NULL) ? "raw" : ++tmp; ++ QDict *options = qdict_new(); ++ qdict_put(options, "driver", qstring_from_str(format)); + +- BlockDriver *drv = bdrv_find_format(format); +- +- if (errp || bdrv_open(&bs, devfn, NULL, NULL, flags, drv, &errp)) { ++ if (errp || bdrv_open(&bs, devfn, NULL, options, flags, &errp)) { + g_error("can't open file %s - %s", devfn, + error_get_pretty(errp)); + } +@@ -534,11 +535,10 @@ static int create_archive(int argc, char **argv) + char *devname = NULL; + path = extract_devname(path, &devname, devcount++); + +- BlockDriver *drv = NULL; + Error *errp = NULL; + BlockDriverState *bs = bdrv_new(); + +- res = bdrv_open(&bs, path, NULL, NULL, BDRV_O_CACHE_WB , drv, &errp); ++ res = bdrv_open(&bs, path, NULL, NULL, BDRV_O_CACHE_WB, &errp); + if (res < 0) { + unlink(archivename); + g_error("bdrv_open '%s' failed - %s", path, error_get_pretty(errp)); +-- +2.1.4 + diff --git a/debian/patches/pve/0034-blockdev-bdrv_open-dropped-the-drv-parameter.patch b/debian/patches/pve/0034-blockdev-bdrv_open-dropped-the-drv-parameter.patch new file mode 100644 index 0000000..e099efe --- /dev/null +++ b/debian/patches/pve/0034-blockdev-bdrv_open-dropped-the-drv-parameter.patch @@ -0,0 +1,29 @@ +From 25a24c9dc28d7f2304b8667ff1d076630adc300a Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Thu, 10 Dec 2015 14:46:59 +0100 +Subject: [PATCH 34/41] blockdev: bdrv_open dropped the drv parameter + +As per commit: +6ebf9aa2ef: block: Drop drv parameter from bdrv_open() + +For-patch: backup: vma: add dir format +--- + blockdev.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/blockdev.c b/blockdev.c +index 5e1234b..9cf76f1 100644 +--- a/blockdev.c ++++ b/blockdev.c +@@ -3260,7 +3260,7 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format, + } + + di->target = bdrv_new(); +- if (bdrv_open(&di->target, di->targetfile, NULL, NULL, flags, NULL, &local_err) < 0) { ++ if (bdrv_open(&di->target, di->targetfile, NULL, NULL, flags, &local_err) < 0) { + bdrv_unref(di->target); + error_propagate(errp, local_err); + goto err; +-- +2.1.4 + diff --git a/debian/patches/pve/0035-blockdev-backup_start-now-takes-a-BlockJobTxn.patch b/debian/patches/pve/0035-blockdev-backup_start-now-takes-a-BlockJobTxn.patch new file mode 100644 index 0000000..c55a811 --- /dev/null +++ b/debian/patches/pve/0035-blockdev-backup_start-now-takes-a-BlockJobTxn.patch @@ -0,0 +1,29 @@ +From 825a46a3cfb266ebc12266555898ca3c27d4dda9 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Thu, 10 Dec 2015 14:50:05 +0100 +Subject: [PATCH 35/41] blockdev: backup_start now takes a BlockJobTxn + +As per commit 78f51fde88d1: +block: Add BlockJobTxn support to backup_run + +For-patch: backup: add pve monitor commands +--- + blockdev.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/blockdev.c b/blockdev.c +index 9cf76f1..ac504a7 100644 +--- a/blockdev.c ++++ b/blockdev.c +@@ -3323,7 +3323,7 @@ UuidInfo *qmp_backup(const char *backup_file, bool has_format, + backup_start(di->bs, di->target, speed, MIRROR_SYNC_MODE_FULL, NULL, + BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT, + pvebackup_dump_cb, pvebackup_complete_cb, di, +- 1, &local_err); ++ 1, NULL, &local_err); + + if (local_err != NULL) { + error_setg(&backup_state.error, "backup_job_create failed"); +-- +2.1.4 + diff --git a/debian/patches/pve/0036-savevm-async-migration-and-bdrv_open-update.patch b/debian/patches/pve/0036-savevm-async-migration-and-bdrv_open-update.patch new file mode 100644 index 0000000..1161165 --- /dev/null +++ b/debian/patches/pve/0036-savevm-async-migration-and-bdrv_open-update.patch @@ -0,0 +1,101 @@ +From 9e97dac64087b5aefe40b99ab0f21ae722d30034 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Thu, 10 Dec 2015 15:14:00 +0100 +Subject: [PATCH 36/41] savevm-async: migration and bdrv_open update + +--- + savevm-async.c | 25 ++++++++++++------------- + 1 file changed, 12 insertions(+), 13 deletions(-) + +diff --git a/savevm-async.c b/savevm-async.c +index 8117443..bb4372c 100644 +--- a/savevm-async.c ++++ b/savevm-async.c +@@ -150,10 +150,10 @@ static int block_state_close(void *opaque) + return bdrv_flush(snap_state.bs); + } + +-static int block_state_put_buffer(void *opaque, const uint8_t *buf, +- int64_t pos, int size) ++static ssize_t block_state_put_buffer(void *opaque, const uint8_t *buf, ++ int64_t pos, size_t size) + { +- int ret; ++ ssize_t ret; + + assert(pos == snap_state.bs_pos); + +@@ -197,12 +197,13 @@ static void process_savevm_co(void *opaque) + } + + while (snap_state.state == SAVE_STATE_ACTIVE) { +- uint64_t pending_size; ++ uint64_t pending_size, pend_post, pend_nonpost; + +- pending_size = qemu_savevm_state_pending(snap_state.file, 0); ++ qemu_savevm_state_pending(snap_state.file, 0, &pend_nonpost, &pend_post); ++ pending_size = pend_post + pend_nonpost; + + if (pending_size) { +- ret = qemu_savevm_state_iterate(snap_state.file); ++ ret = qemu_savevm_state_iterate(snap_state.file, false); + if (ret < 0) { + save_snapshot_error("qemu_savevm_state_iterate error %d", ret); + break; +@@ -213,7 +214,7 @@ static void process_savevm_co(void *opaque) + if (store_and_stop()) + break; + DPRINTF("savevm inerate finished\n"); +- qemu_savevm_state_complete_precopy(snap_state.file); ++ qemu_savevm_state_complete_precopy(snap_state.file, false); + DPRINTF("save complete\n"); + save_snapshot_completed(); + break; +@@ -246,7 +247,6 @@ static const QEMUFileOps block_file_ops = { + + void qmp_savevm_start(bool has_statefile, const char *statefile, Error **errp) + { +- BlockDriver *drv = NULL; + Error *local_err = NULL; + + int bdrv_oflags = BDRV_O_CACHE_WB | BDRV_O_RDWR; +@@ -285,7 +285,7 @@ void qmp_savevm_start(bool has_statefile, const char *statefile, Error **errp) + QDict *options = NULL; + options = qdict_new(); + qdict_put(options, "driver", qstring_from_str("raw")); +- ret = bdrv_open(&snap_state.bs, statefile, NULL, options, bdrv_oflags, drv, &local_err); ++ ret = bdrv_open(&snap_state.bs, statefile, NULL, options, bdrv_oflags, &local_err); + if (ret < 0) { + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "failed to open '%s'", statefile); + goto restart; +@@ -450,8 +450,8 @@ void qmp_delete_drive_snapshot(const char *device, const char *name, + } + } + +-static int loadstate_get_buffer(void *opaque, uint8_t *buf, int64_t pos, +- int size) ++static ssize_t loadstate_get_buffer(void *opaque, uint8_t *buf, int64_t pos, ++ size_t size) + { + BlockDriverState *bs = (BlockDriverState *)opaque; + int64_t maxlen = bdrv_getlength(bs); +@@ -474,7 +474,6 @@ static const QEMUFileOps loadstate_file_ops = { + int load_state_from_blockdev(const char *filename) + { + BlockDriverState *bs = NULL; +- BlockDriver *drv = NULL; + Error *local_err = NULL; + Error *blocker = NULL; + +@@ -482,7 +481,7 @@ int load_state_from_blockdev(const char *filename) + int ret; + + bs = bdrv_new(); +- ret = bdrv_open(&bs, filename, NULL, NULL, BDRV_O_CACHE_WB, drv, &local_err); ++ ret = bdrv_open(&bs, filename, NULL, NULL, BDRV_O_CACHE_WB, &local_err); + error_setg(&blocker, "block device is in use by load state"); + bdrv_op_block_all(bs, blocker); + +-- +2.1.4 + diff --git a/debian/patches/pve/0037-qapi-qmp_marshal_-renames-for-pve-monitor-commands.patch b/debian/patches/pve/0037-qapi-qmp_marshal_-renames-for-pve-monitor-commands.patch new file mode 100644 index 0000000..5bdc0ff --- /dev/null +++ b/debian/patches/pve/0037-qapi-qmp_marshal_-renames-for-pve-monitor-commands.patch @@ -0,0 +1,43 @@ +From 6c9fa84fe9a676616fc9bdbf148c255c79f37f99 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Thu, 10 Dec 2015 15:19:42 +0100 +Subject: [PATCH 37/41] qapi: qmp_marshal_* renames for pve monitor commands + +As per 7fad30f0: +qapi: Rename qmp_marshal_input_FOO() to qmp_marshal_FOO() + +For-patch: backup: add pve monitor commands +--- + qmp-commands.hx | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/qmp-commands.hx b/qmp-commands.hx +index de07f54..478de38 100644 +--- a/qmp-commands.hx ++++ b/qmp-commands.hx +@@ -1256,19 +1256,19 @@ EQMP + { + .name = "backup", + .args_type = "backup-file:s,format:s?,config-file:F?,firewall-file:F?,speed:o?,devlist:s?", +- .mhandler.cmd_new = qmp_marshal_input_backup, ++ .mhandler.cmd_new = qmp_marshal_backup, + }, + + { + .name = "backup-cancel", + .args_type = "", +- .mhandler.cmd_new = qmp_marshal_input_backup_cancel, ++ .mhandler.cmd_new = qmp_marshal_backup_cancel, + }, + + { + .name = "query-backup", + .args_type = "", +- .mhandler.cmd_new = qmp_marshal_input_query_backup, ++ .mhandler.cmd_new = qmp_marshal_query_backup, + }, + + { +-- +2.1.4 + diff --git a/debian/patches/pve/0038-qapi-qmp_mashal_-renames-for-async-snapshot.patch b/debian/patches/pve/0038-qapi-qmp_mashal_-renames-for-async-snapshot.patch new file mode 100644 index 0000000..99228aa --- /dev/null +++ b/debian/patches/pve/0038-qapi-qmp_mashal_-renames-for-async-snapshot.patch @@ -0,0 +1,57 @@ +From 610ba269757b48a75efce7a05cfd2bc4d9016da7 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Thu, 10 Dec 2015 15:21:10 +0100 +Subject: [PATCH 38/41] qapi: qmp_mashal_* renames for async snapshot + +As per 7fad30f0: +qapi: Rename qmp_marshal_input_FOO() to qmp_marshal_FOO() + +For-patch: internal snapshot async +--- + qmp-commands.hx | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/qmp-commands.hx b/qmp-commands.hx +index 478de38..129e1b3 100644 +--- a/qmp-commands.hx ++++ b/qmp-commands.hx +@@ -4700,31 +4700,31 @@ EQMP + { + .name = "savevm-start", + .args_type = "statefile:s?", +- .mhandler.cmd_new = qmp_marshal_input_savevm_start, ++ .mhandler.cmd_new = qmp_marshal_savevm_start, + }, + + { + .name = "snapshot-drive", + .args_type = "device:s,name:s", +- .mhandler.cmd_new = qmp_marshal_input_snapshot_drive, ++ .mhandler.cmd_new = qmp_marshal_snapshot_drive, + }, + + { + .name = "delete-drive-snapshot", + .args_type = "device:s,name:s", +- .mhandler.cmd_new = qmp_marshal_input_delete_drive_snapshot, ++ .mhandler.cmd_new = qmp_marshal_delete_drive_snapshot, + }, + + { + .name = "savevm-end", + .args_type = "", +- .mhandler.cmd_new = qmp_marshal_input_savevm_end, ++ .mhandler.cmd_new = qmp_marshal_savevm_end, + }, + + { + .name = "query-savevm", + .args_type = "", +- .mhandler.cmd_new = qmp_marshal_input_query_savevm, ++ .mhandler.cmd_new = qmp_marshal_query_savevm, + }, + + { +-- +2.1.4 + diff --git a/debian/patches/pve/0039-qapi-qmp_mashal_-renames-for-get_link_status.patch b/debian/patches/pve/0039-qapi-qmp_mashal_-renames-for-get_link_status.patch new file mode 100644 index 0000000..97e1e9a --- /dev/null +++ b/debian/patches/pve/0039-qapi-qmp_mashal_-renames-for-get_link_status.patch @@ -0,0 +1,29 @@ +From 729410b4b0c9fc9a8d8462480ef580b5b97782d9 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Thu, 10 Dec 2015 15:21:59 +0100 +Subject: [PATCH 39/41] qapi: qmp_mashal_* renames for get_link_status + +As per 7fad30f0: +qapi: Rename qmp_marshal_input_FOO() to qmp_marshal_FOO() + +For-patch: qmp: add get_link_status +--- + qmp-commands.hx | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/qmp-commands.hx b/qmp-commands.hx +index 129e1b3..12b884c 100644 +--- a/qmp-commands.hx ++++ b/qmp-commands.hx +@@ -1773,7 +1773,7 @@ EQMP + { + .name = "get_link_status", + .args_type = "name:s", +- .mhandler.cmd_new = qmp_marshal_input_get_link_status, ++ .mhandler.cmd_new = qmp_marshal_get_link_status, + }, + + SQMP +-- +2.1.4 + diff --git a/debian/patches/pve/0040-vnc-make-x509-imply-tls-again.patch b/debian/patches/pve/0040-vnc-make-x509-imply-tls-again.patch new file mode 100644 index 0000000..a8237ae --- /dev/null +++ b/debian/patches/pve/0040-vnc-make-x509-imply-tls-again.patch @@ -0,0 +1,28 @@ +From b75743d1277b90c2248ce06c0ced957212531441 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Tue, 12 Jan 2016 09:09:49 +0100 +Subject: [PATCH 40/41] vnc: make x509 imply tls again + +--- + ui/vnc.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/ui/vnc.c b/ui/vnc.c +index ae5a4fe..ba99636 100644 +--- a/ui/vnc.c ++++ b/ui/vnc.c +@@ -3732,9 +3732,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); +- if (tls) { +- path = qemu_opt_get(opts, "x509"); +- ++ path = qemu_opt_get(opts, "x509"); ++ if (tls || path) { + if (path) { + x509 = true; + } else { +-- +2.1.4 + diff --git a/debian/patches/pve/0041-PVE-VNC-authentication.patch b/debian/patches/pve/0041-PVE-VNC-authentication.patch new file mode 100644 index 0000000..2277467 --- /dev/null +++ b/debian/patches/pve/0041-PVE-VNC-authentication.patch @@ -0,0 +1,652 @@ +From 19a3e72ea86fb723d7facba1f07d7ae99520e461 Mon Sep 17 00:00:00 2001 +From: Wolfgang Bumiller +Date: Mon, 11 Jan 2016 10:40:31 +0100 +Subject: [PATCH 41/41] PVE VNC authentication + +--- + crypto/tlscreds.c | 32 +++++++++ + crypto/tlscredspriv.h | 2 + + crypto/tlscredsx509.c | 13 ++-- + crypto/tlssession.c | 1 + + include/crypto/tlscreds.h | 1 + + include/ui/console.h | 1 + + qemu-options.hx | 3 + + ui/vnc-auth-vencrypt.c | 173 +++++++++++++++++++++++++++++++++++++++------- + ui/vnc.c | 140 ++++++++++++++++++++++++++++++++++++- + ui/vnc.h | 4 ++ + vl.c | 9 +++ + 11 files changed, 344 insertions(+), 35 deletions(-) + +diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c +index e7d9c1c..27e5c78 100644 +--- a/crypto/tlscreds.c ++++ b/crypto/tlscreds.c +@@ -156,6 +156,33 @@ qcrypto_tls_creds_prop_get_verify(Object *obj, + + + static void ++qcrypto_tls_creds_prop_set_pve(Object *obj, ++ bool value, ++ Error **errp G_GNUC_UNUSED) ++{ ++ QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); ++ ++ creds->pve = value; ++} ++ ++ ++static bool ++qcrypto_tls_creds_prop_get_pve(Object *obj, ++ Error **errp G_GNUC_UNUSED) ++{ ++ QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); ++ ++ return creds->pve; ++} ++ ++bool qcrypto_tls_creds_is_pve(QCryptoTLSCreds *creds) ++{ ++ Error *errp = NULL; ++ return qcrypto_tls_creds_prop_get_pve((Object*)creds, &errp); ++} ++ ++ ++static void + qcrypto_tls_creds_prop_set_dir(Object *obj, + const char *value, + Error **errp G_GNUC_UNUSED) +@@ -203,11 +230,16 @@ qcrypto_tls_creds_init(Object *obj) + QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj); + + creds->verifyPeer = true; ++ creds->pve = false; + + object_property_add_bool(obj, "verify-peer", + qcrypto_tls_creds_prop_get_verify, + qcrypto_tls_creds_prop_set_verify, + NULL); ++ object_property_add_bool(obj, "pve", ++ qcrypto_tls_creds_prop_get_pve, ++ qcrypto_tls_creds_prop_set_pve, ++ NULL); + object_property_add_str(obj, "dir", + qcrypto_tls_creds_prop_get_dir, + qcrypto_tls_creds_prop_set_dir, +diff --git a/crypto/tlscredspriv.h b/crypto/tlscredspriv.h +index 9222be4..3a34279 100644 +--- a/crypto/tlscredspriv.h ++++ b/crypto/tlscredspriv.h +@@ -36,6 +36,8 @@ int qcrypto_tls_creds_get_dh_params_file(QCryptoTLSCreds *creds, + gnutls_dh_params_t *dh_params, + Error **errp); + ++bool qcrypto_tls_creds_is_pve(QCryptoTLSCreds *creds); ++ + #endif + + #endif /* QCRYPTO_TLSCRED_PRIV_H__ */ +diff --git a/crypto/tlscredsx509.c b/crypto/tlscredsx509.c +index 26f18cb..12e67c8 100644 +--- a/crypto/tlscredsx509.c ++++ b/crypto/tlscredsx509.c +@@ -547,22 +547,23 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, + *key = NULL, *dhparams = NULL; + int ret; + int rv = -1; ++ bool pve = qcrypto_tls_creds_is_pve(&creds->parent_obj); + + trace_qcrypto_tls_creds_x509_load(creds, + creds->parent_obj.dir ? creds->parent_obj.dir : ""); + + if (creds->parent_obj.endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, +- QCRYPTO_TLS_CREDS_X509_CA_CERT, ++ pve ? "pve-root-ca.pem" : QCRYPTO_TLS_CREDS_X509_CA_CERT, + true, &cacert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_X509_CA_CRL, + false, &cacrl, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, +- QCRYPTO_TLS_CREDS_X509_SERVER_CERT, ++ pve ? "local/pve-ssl.pem" : QCRYPTO_TLS_CREDS_X509_SERVER_CERT, + true, &cert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, +- QCRYPTO_TLS_CREDS_X509_SERVER_KEY, ++ pve ? "local/pve-ssl.key" : QCRYPTO_TLS_CREDS_X509_SERVER_KEY, + true, &key, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, + QCRYPTO_TLS_CREDS_DH_PARAMS, +@@ -571,13 +572,13 @@ qcrypto_tls_creds_x509_load(QCryptoTLSCredsX509 *creds, + } + } else { + if (qcrypto_tls_creds_get_path(&creds->parent_obj, +- QCRYPTO_TLS_CREDS_X509_CA_CERT, ++ pve ? "pve-root-ca.pem" : QCRYPTO_TLS_CREDS_X509_CA_CERT, + true, &cacert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, +- QCRYPTO_TLS_CREDS_X509_CLIENT_CERT, ++ pve ? "local/pve-ssl.pem" : QCRYPTO_TLS_CREDS_X509_CLIENT_CERT, + false, &cert, errp) < 0 || + qcrypto_tls_creds_get_path(&creds->parent_obj, +- QCRYPTO_TLS_CREDS_X509_CLIENT_KEY, ++ pve ? "local/pve-ssl.key" : QCRYPTO_TLS_CREDS_X509_CLIENT_KEY, + false, &key, errp) < 0) { + goto cleanup; + } +diff --git a/crypto/tlssession.c b/crypto/tlssession.c +index 3735529..a7ed50a 100644 +--- a/crypto/tlssession.c ++++ b/crypto/tlssession.c +@@ -21,6 +21,7 @@ + #include "crypto/tlssession.h" + #include "crypto/tlscredsanon.h" + #include "crypto/tlscredsx509.h" ++#include "crypto/tlscredspriv.h" + #include "qemu/acl.h" + #include "trace.h" + +diff --git a/include/crypto/tlscreds.h b/include/crypto/tlscreds.h +index 21761b7..2cc0ce0 100644 +--- a/include/crypto/tlscreds.h ++++ b/include/crypto/tlscreds.h +@@ -56,6 +56,7 @@ struct QCryptoTLSCreds { + gnutls_dh_params_t dh_params; + #endif + bool verifyPeer; ++ bool pve; + }; + + +diff --git a/include/ui/console.h b/include/ui/console.h +index c249db4..399b42d 100644 +--- a/include/ui/console.h ++++ b/include/ui/console.h +@@ -409,6 +409,7 @@ void sdl_display_init(DisplayState *ds, int full_screen, int no_frame); + void cocoa_display_init(DisplayState *ds, int full_screen); + + /* vnc.c */ ++void pve_auth_setup(int vmid); + 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/qemu-options.hx b/qemu-options.hx +index cd465d5..bf87c00 100644 +--- a/qemu-options.hx ++++ b/qemu-options.hx +@@ -419,6 +419,9 @@ STEXI + @table @option + ETEXI + ++DEF("id", HAS_ARG, QEMU_OPTION_id, ++ "-id n set the VMID\n", QEMU_ARCH_ALL) ++ + DEF("fda", HAS_ARG, QEMU_OPTION_fda, + "-fda/-fdb file use 'file' as floppy disk 0/1 image\n", QEMU_ARCH_ALL) + DEF("fdb", HAS_ARG, QEMU_OPTION_fdb, "", QEMU_ARCH_ALL) +diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c +index 44ac2fa..480a1ee 100644 +--- a/ui/vnc-auth-vencrypt.c ++++ b/ui/vnc-auth-vencrypt.c +@@ -26,6 +26,107 @@ + + #include "vnc.h" + #include "qemu/main-loop.h" ++#include "qemu/sockets.h" ++ ++static int protocol_client_auth_plain(VncState *vs, uint8_t *data, size_t len) ++{ ++ const char *err = NULL; ++ char username[256]; ++ char passwd[512]; ++ ++ char clientip[256]; ++ clientip[0] = 0; ++ struct sockaddr_in client; ++ socklen_t addrlen = sizeof(client); ++ if (getpeername(vs->csock, &client, &addrlen) == 0) { ++ inet_ntop(client.sin_family, &client.sin_addr, ++ clientip, sizeof(clientip)); ++ } ++ ++ if ((len != (vs->username_len + vs->password_len)) || ++ (vs->username_len >= (sizeof(username)-1)) || ++ (vs->password_len >= (sizeof(passwd)-1)) ) { ++ err = "Got unexpected data length"; ++ goto err; ++ } ++ ++ strncpy(username, (char *)data, vs->username_len); ++ username[vs->username_len] = 0; ++ strncpy(passwd, (char *)data + vs->username_len, vs->password_len); ++ passwd[vs->password_len] = 0; ++ ++ VNC_DEBUG("AUTH PLAIN username: %s pw: %s\n", username, passwd); ++ ++ if (pve_auth_verify(clientip, username, passwd) == 0) { ++ vnc_write_u32(vs, 0); /* Accept auth completion */ ++ start_client_init(vs); ++ return 0; ++ } ++ ++ err = "Authentication failed"; ++err: ++ if (err) { ++ VNC_DEBUG("AUTH PLAIN ERROR: %s\n", err); ++ vnc_write_u32(vs, 1); /* Reject auth */ ++ if (vs->minor >= 8) { ++ int elen = strlen(err); ++ vnc_write_u32(vs, elen); ++ vnc_write(vs, err, elen); ++ } ++ } ++ vnc_flush(vs); ++ vnc_client_error(vs); ++ ++ return 0; ++ ++} ++ ++static int protocol_client_auth_plain_start(VncState *vs, uint8_t *data, size_t len) ++{ ++ uint32_t ulen = read_u32(data, 0); ++ uint32_t pwlen = read_u32(data, 4); ++ const char *err = NULL; ++ ++ VNC_DEBUG("AUTH PLAIN START %u %u\n", ulen, pwlen); ++ ++ if (!ulen) { ++ err = "No User name."; ++ goto err; ++ } ++ if (ulen >= 255) { ++ err = "User name too long."; ++ goto err; ++ } ++ if (!pwlen) { ++ err = "Password too short"; ++ goto err; ++ } ++ if (pwlen >= 511) { ++ err = "Password too long."; ++ goto err; ++ } ++ ++ vs->username_len = ulen; ++ vs->password_len = pwlen; ++ ++ vnc_read_when(vs, protocol_client_auth_plain, ulen + pwlen); ++ ++ return 0; ++err: ++ if (err) { ++ VNC_DEBUG("AUTH PLAIN ERROR: %s\n", err); ++ vnc_write_u32(vs, 1); /* Reject auth */ ++ if (vs->minor >= 8) { ++ int elen = strlen(err); ++ vnc_write_u32(vs, elen); ++ vnc_write(vs, err, elen); ++ } ++ } ++ vnc_flush(vs); ++ vnc_client_error(vs); ++ ++ return 0; ++} + + static void start_auth_vencrypt_subauth(VncState *vs) + { +@@ -37,6 +138,17 @@ static void start_auth_vencrypt_subauth(VncState *vs) + start_client_init(vs); + break; + ++ case VNC_AUTH_VENCRYPT_TLSPLAIN: ++ case VNC_AUTH_VENCRYPT_X509PLAIN: ++ VNC_DEBUG("Start TLS auth PLAIN\n"); ++ vnc_read_when(vs, protocol_client_auth_plain_start, 8); ++ break; ++ ++ case VNC_AUTH_VENCRYPT_PLAIN: ++ VNC_DEBUG("Start auth PLAIN\n"); ++ vnc_read_when(vs, protocol_client_auth_plain_start, 8); ++ break; ++ + case VNC_AUTH_VENCRYPT_TLSVNC: + case VNC_AUTH_VENCRYPT_X509VNC: + VNC_DEBUG("Start TLS auth VNC\n"); +@@ -118,39 +230,47 @@ static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len + { + int auth = read_u32(data, 0); + +- if (auth != vs->subauth) { ++ if (auth != vs->subauth && auth != VNC_AUTH_VENCRYPT_PLAIN) { + VNC_DEBUG("Rejecting auth %d\n", auth); + vnc_write_u8(vs, 0); /* Reject auth */ + vnc_flush(vs); + vnc_client_error(vs); + } else { +- Error *err = NULL; +- VNC_DEBUG("Accepting auth %d, setting up TLS for handshake\n", auth); +- vnc_write_u8(vs, 1); /* Accept auth */ +- vnc_flush(vs); + +- 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; ++ if (auth == VNC_AUTH_VENCRYPT_PLAIN) { ++ vs->subauth = auth; ++ start_auth_vencrypt_subauth(vs); + } ++ else ++ { ++ Error *err = NULL; ++ VNC_DEBUG("Accepting auth %d, setting up TLS for handshake\n", auth); ++ vnc_write_u8(vs, 1); /* Accept auth */ ++ vnc_flush(vs); ++ ++ 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; ++ } + +- qcrypto_tls_session_set_callbacks(vs->tls, +- vnc_tls_push, +- vnc_tls_pull, +- vs); ++ qcrypto_tls_session_set_callbacks(vs->tls, ++ vnc_tls_push, ++ vnc_tls_pull, ++ vs); + +- VNC_DEBUG("Start TLS VeNCrypt handshake process\n"); +- if (vnc_start_vencrypt_handshake(vs) < 0) { +- VNC_DEBUG("Failed to start TLS handshake\n"); +- return 0; ++ VNC_DEBUG("Start TLS VeNCrypt handshake process\n"); ++ if (vnc_start_vencrypt_handshake(vs) < 0) { ++ VNC_DEBUG("Failed to start TLS handshake\n"); ++ return 0; ++ } + } + } + return 0; +@@ -165,10 +285,11 @@ static int protocol_client_vencrypt_init(VncState *vs, uint8_t *data, size_t len + vnc_flush(vs); + 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_write_u32(vs, vs->subauth); /* The supported auth */ ++ vnc_write_u32(vs, VNC_AUTH_VENCRYPT_PLAIN); /* Alternative supported auth */ + vnc_flush(vs); + vnc_read_when(vs, protocol_client_vencrypt_auth, 4); + } +diff --git a/ui/vnc.c b/ui/vnc.c +index ba99636..612cab2 100644 +--- a/ui/vnc.c ++++ b/ui/vnc.c +@@ -54,6 +54,125 @@ static const struct timeval VNC_REFRESH_LOSSY = { 2, 0 }; + #include "vnc_keysym.h" + #include "crypto/cipher.h" + ++static int pve_vmid = 0; ++ ++void pve_auth_setup(int vmid) { ++ pve_vmid = vmid; ++} ++ ++static char * ++urlencode(char *buf, const char *value) ++{ ++ static const char *hexchar = "0123456789abcdef"; ++ char *p = buf; ++ int i; ++ int l = strlen(value); ++ for (i = 0; i < l; i++) { ++ char c = value[i]; ++ if (('a' <= c && c <= 'z') || ++ ('A' <= c && c <= 'Z') || ++ ('0' <= c && c <= '9')) { ++ *p++ = c; ++ } else if (c == 32) { ++ *p++ = '+'; ++ } else { ++ *p++ = '%'; ++ *p++ = hexchar[c >> 4]; ++ *p++ = hexchar[c & 15]; ++ } ++ } ++ *p = 0; ++ ++ return p; ++} ++ ++int ++pve_auth_verify(const char *clientip, const char *username, const char *passwd) ++{ ++ struct sockaddr_in server; ++ ++ int sfd = socket(AF_INET, SOCK_STREAM, 0); ++ if (sfd == -1) { ++ perror("pve_auth_verify: socket failed"); ++ return -1; ++ } ++ ++ struct hostent *he; ++ if ((he = gethostbyname("localhost")) == NULL) { ++ fprintf(stderr, "pve_auth_verify: error resolving hostname\n"); ++ goto err; ++ } ++ ++ memcpy(&server.sin_addr, he->h_addr_list[0], he->h_length); ++ server.sin_family = AF_INET; ++ server.sin_port = htons(85); ++ ++ if (connect(sfd, (struct sockaddr *)&server, sizeof(server))) { ++ perror("pve_auth_verify: error connecting to server"); ++ goto err; ++ } ++ ++ char buf[8192]; ++ char form[8192]; ++ ++ char *p = form; ++ p = urlencode(p, "username"); ++ *p++ = '='; ++ p = urlencode(p, username); ++ ++ *p++ = '&'; ++ p = urlencode(p, "password"); ++ *p++ = '='; ++ p = urlencode(p, passwd); ++ ++ *p++ = '&'; ++ p = urlencode(p, "path"); ++ *p++ = '='; ++ char authpath[256]; ++ sprintf(authpath, "/vms/%d", pve_vmid); ++ p = urlencode(p, authpath); ++ ++ *p++ = '&'; ++ p = urlencode(p, "privs"); ++ *p++ = '='; ++ p = urlencode(p, "VM.Console"); ++ ++ sprintf(buf, "POST /api2/json/access/ticket HTTP/1.1\n" ++ "Host: localhost:85\n" ++ "Connection: close\n" ++ "PVEClientIP: %s\n" ++ "Content-Type: application/x-www-form-urlencoded\n" ++ "Content-Length: %zd\n\n%s\n", clientip, strlen(form), form); ++ ssize_t len = strlen(buf); ++ ssize_t sb = send(sfd, buf, len, 0); ++ if (sb < 0) { ++ perror("pve_auth_verify: send failed"); ++ goto err; ++ } ++ if (sb != len) { ++ fprintf(stderr, "pve_auth_verify: partial send error\n"); ++ goto err; ++ } ++ ++ len = recv(sfd, buf, sizeof(buf) - 1, 0); ++ if (len < 0) { ++ perror("pve_auth_verify: recv failed"); ++ goto err; ++ } ++ ++ buf[len] = 0; ++ ++ //printf("DATA:%s\n", buf); ++ ++ shutdown(sfd, SHUT_RDWR); ++ ++ return strncmp(buf, "HTTP/1.1 200 OK", 15); ++ ++err: ++ shutdown(sfd, SHUT_RDWR); ++ return -1; ++} ++ + static QTAILQ_HEAD(, VncDisplay) vnc_displays = + QTAILQ_HEAD_INITIALIZER(vnc_displays); + +@@ -3427,11 +3546,17 @@ vnc_display_setup_auth(VncDisplay *vs, + if (object_dynamic_cast(OBJECT(vs->tlscreds), + TYPE_QCRYPTO_TLS_CREDS_X509)) { + VNC_DEBUG("Initializing VNC server with x509 password auth\n"); +- vs->subauth = VNC_AUTH_VENCRYPT_X509VNC; ++ if (vs->tlscreds->pve) ++ vs->subauth = VNC_AUTH_VENCRYPT_X509PLAIN; ++ else ++ vs->subauth = VNC_AUTH_VENCRYPT_X509VNC; + } else if (object_dynamic_cast(OBJECT(vs->tlscreds), + TYPE_QCRYPTO_TLS_CREDS_ANON)) { + VNC_DEBUG("Initializing VNC server with TLS password auth\n"); +- vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC; ++ if (vs->tlscreds->pve) ++ vs->subauth = VNC_AUTH_VENCRYPT_TLSPLAIN; ++ else ++ vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC; + } else { + error_setg(errp, + "Unsupported TLS cred type %s", +@@ -3522,6 +3647,7 @@ vnc_display_create_creds(bool x509, + bool x509verify, + const char *dir, + const char *id, ++ bool pve, + Error **errp) + { + gchar *credsid = g_strdup_printf("tlsvnc%s", id); +@@ -3537,6 +3663,7 @@ vnc_display_create_creds(bool x509, + "endpoint", "server", + "dir", dir, + "verify-peer", x509verify ? "yes" : "no", ++ "pve", pve ? "yes" : "no", + NULL); + } else { + creds = object_new_with_props(TYPE_QCRYPTO_TLS_CREDS_ANON, +@@ -3544,6 +3671,7 @@ vnc_display_create_creds(bool x509, + credsid, + &err, + "endpoint", "server", ++ "pve", pve ? "yes" : "no", + NULL); + } + +@@ -3730,12 +3858,17 @@ void vnc_display_open(const char *id, Error **errp) + } + } else { + const char *path; +- bool tls = false, x509 = false, x509verify = false; ++ bool tls = false, x509 = false, x509verify = false, pve = false; + tls = qemu_opt_get_bool(opts, "tls", false); + path = qemu_opt_get(opts, "x509"); + if (tls || path) { + if (path) { + x509 = true; ++ if (!strcmp(path, "on")) { ++ /* magic to default to /etc/pve */ ++ path = "/etc/pve"; ++ pve = true; ++ } + } else { + path = qemu_opt_get(opts, "x509verify"); + if (path) { +@@ -3747,6 +3880,7 @@ void vnc_display_open(const char *id, Error **errp) + x509verify, + path, + vs->id, ++ pve, + errp); + if (!vs->tlscreds) { + goto fail; +diff --git a/ui/vnc.h b/ui/vnc.h +index 2863f58..1c4fee2 100644 +--- a/ui/vnc.h ++++ b/ui/vnc.h +@@ -275,6 +275,8 @@ struct VncState + int auth; + int subauth; /* Used by VeNCrypt */ + char challenge[VNC_AUTH_CHALLENGE_SIZE]; ++ int username_len; ++ int password_len; + QCryptoTLSSession *tls; + #ifdef CONFIG_VNC_SASL + VncStateSASL sasl; +@@ -578,4 +580,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); + ++int pve_auth_verify(const char *clientip, const char *username, const char *passwd); ++ + #endif /* __QEMU_VNC_H */ +diff --git a/vl.c b/vl.c +index 63c6798..e8034b0 100644 +--- a/vl.c ++++ b/vl.c +@@ -2973,6 +2973,7 @@ static void set_memory_options(uint64_t *ram_slots, ram_addr_t *maxram_size, + int main(int argc, char **argv, char **envp) + { + int i; ++ long int vm_id_long = 0; + int snapshot, linux_boot; + const char *initrd_filename; + const char *kernel_filename, *kernel_cmdline; +@@ -3736,6 +3737,14 @@ int main(int argc, char **argv, char **envp) + exit(1); + } + break; ++ case QEMU_OPTION_id: ++ vm_id_long = strtol(optarg, (char **) &optarg, 10); ++ if (*optarg != 0 || vm_id_long < 100 || vm_id_long > INT_MAX) { ++ fprintf(stderr, "Invalid ID\n"); ++ exit(1); ++ } ++ pve_auth_setup(vm_id_long); ++ break; + case QEMU_OPTION_vnc: + { + #ifdef CONFIG_VNC +-- +2.1.4 + diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..7ff49cf --- /dev/null +++ b/debian/patches/series @@ -0,0 +1,47 @@ +pve/0001-fr-ca-keymap-corrections.patch +pve/0002-Adjust-network-script-path-to-etc-kvm.patch +pve/0003-vnc-altgr-emulation.patch +pve/0004-qemu-img-return-success-on-info-without-snapshots.patch +pve/0005-use-kvm-by-default.patch +pve/0006-virtio-balloon-fix-query.patch +pve/0007-set-the-CPU-model-to-kvm64-32-instead-of-qemu64-32.patch +pve/0008-qapi-modify-query-machines.patch +pve/0009-qapi-modify-spice-query.patch +pve/0010-ui-spice-default-to-pve-certs-unless-otherwise-speci.patch +pve/0011-introduce-new-vma-archive-format.patch +pve/0012-vma-add-verify-command.patch +pve/0013-vma-add-config-command-to-dump-the-config.patch +pve/0014-vma-restore-tolerate-a-size-difference-up-to-4M.patch +pve/0015-backup-modify-job-api.patch +pve/0016-backup-add-pve-monitor-commands.patch +pve/0017-backup-vma-add-dir-format.patch +pve/0018-backup-do-not-return-errors-in-dump-callback.patch +pve/0019-backup-vma-correctly-propagate-error.patch +pve/0020-backup-vma-remove-async-queue.patch +pve/0021-backup-vma-run-flush-inside-coroutine.patch +pve/0022-backup-do-not-use-bdrv_drain_all.patch +pve/0023-internal-snapshot-async.patch +pve/0024-backup-vma-allow-empty-backups.patch +pve/0025-backup-vma-add-BlockDriver-to-bdrv_open-in-extract_c.patch +pve/0026-glusterfs-daemonize.patch +pve/0027-gluster-possiblity-to-specify-a-secondary-server.patch +pve/0028-qmp-add-get_link_status.patch +pve/0029-smm_available-false.patch +pve/0030-use-whitespace-between-VERSION-and-PKGVERSION.patch +pve/0031-vma-add-firewall.patch +pve/0032-vma-writer-aio_set_fd_handler-update.patch +pve/0033-vma-bdrv_open-dropped-the-drv-parameter.patch +pve/0034-blockdev-bdrv_open-dropped-the-drv-parameter.patch +pve/0035-blockdev-backup_start-now-takes-a-BlockJobTxn.patch +pve/0036-savevm-async-migration-and-bdrv_open-update.patch +pve/0037-qapi-qmp_marshal_-renames-for-pve-monitor-commands.patch +pve/0038-qapi-qmp_mashal_-renames-for-async-snapshot.patch +pve/0039-qapi-qmp_mashal_-renames-for-get_link_status.patch +pve/0040-vnc-make-x509-imply-tls-again.patch +pve/0041-PVE-VNC-authentication.patch +extra/CVE-2015-8558-ehci_make_idt_processing_more_robust.patch +extra/CVE-2015-8613-scsi-initialize-info-object.patch +extra/CVE-2015-8701-net-rocker-off-by-one.patch +extra/CVE-2015-8743-ne2000-ioport-bounds-check.patch +extra/vmxnet3-host-memory-leakage.patch +extra/CVE-2015-8619-hmp-sendkey-oob-fix.patch