[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH 1/5] block/prl-xml: add Parallels xml BlockDriver
From: |
Vladimir Sementsov-Ogievskiy |
Subject: |
[PATCH 1/5] block/prl-xml: add Parallels xml BlockDriver |
Date: |
Fri, 13 Nov 2020 15:58:44 +0300 |
From: Klim Kireev <klim.kireev@virtuozzo.com>
This patch introduces new BlockDriver: prl-xml.
It adds opening and closing capabilities.
All operations are performed using libxml2.
Signed-off-by: Klim Kireev <klim.kireev@virtuozzo.com>
---
block/prl-xml.c | 492 ++++++++++++++++++++++++++++++++++++++++++++
block/Makefile.objs | 5 +-
2 files changed, 495 insertions(+), 2 deletions(-)
create mode 100644 block/prl-xml.c
diff --git a/block/prl-xml.c b/block/prl-xml.c
new file mode 100644
index 0000000000..fa9c4fd5fa
--- /dev/null
+++ b/block/prl-xml.c
@@ -0,0 +1,492 @@
+/*
+* Block driver for Parallels disk image format
+* Copyright (c) 2015-2017, Virtuozzo, Inc.
+* Authors:
+* 2016-2017 Klim S. Kireev <klim.kireev@virtuozzo.com>
+* 2015 Denis V. Lunev <den@openvz.org>
+*
+* This code was originally based on comparing different disk images created
+* by Parallels. Currently it is based on opened OpenVZ sources
+* available at
+* https://github.com/OpenVZ/ploop
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "block/block_int.h"
+#include "qemu/uuid.h"
+#include "qemu/cutils.h"
+#include "qemu/option.h"
+#include "qapi/qmp/qdict.h"
+
+#include <libxml/parser.h>
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <string.h> /* For basename */
+
+#define DEF_TOP_SNAPSHOT "5fbaabe3-6958-40ff-92a7-860e329aab41"
+#define GUID_LEN strlen(DEF_TOP_SNAPSHOT)
+#define PRL_XML_FILENAME "DiskDescriptor.xml"
+
+typedef struct BDRVPrlXmlState {
+ xmlDoc *xml;
+ BdrvChild *image;
+} BDRVPrlXmlState;
+
+enum TopSnapMode {
+ ERROR_MODE = -1,
+ NODE_MODE,
+ GUID_MODE
+};
+
+static QemuOptsList prl_xml_create_opts = {
+ .name = "prl-xml-create-opts",
+ .head = QTAILQ_HEAD_INITIALIZER(prl_xml_create_opts.head),
+ .desc = {
+ {
+ .name = BLOCK_OPT_SIZE,
+ .type = QEMU_OPT_SIZE,
+ .help = "Virtual disk size",
+ },
+ {
+ .name = BLOCK_OPT_CLUSTER_SIZE,
+ .type = QEMU_OPT_SIZE,
+ .help = "Parallels XML back image cluster size",
+ .def_value_str = stringify(DEFAULT_CLUSTER_SIZE),
+ },
+ { /* end of list */ }
+ }
+};
+
+static xmlNodePtr xml_find_element_child(xmlNodePtr node, const char *elem)
+{
+ xmlNodePtr child;
+
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next) {
+ if (child->type == XML_ELEMENT_NODE &&
+ !xmlStrcmp(child->name, (const xmlChar *)elem))
+ {
+ return child;
+ }
+ }
+ return NULL;
+}
+
+static xmlNodePtr xml_seek_va(xmlNodePtr root, va_list args)
+{
+ const char *elem;
+
+ while ((elem = va_arg(args, const char *)) != NULL) {
+ root = xml_find_element_child(root, elem);
+ if (root == NULL) {
+ return NULL;
+ }
+ }
+ return root;
+}
+
+static xmlNodePtr xml_seek(xmlNodePtr root, ...)
+{
+ va_list args;
+ va_start(args, root);
+ root = xml_seek_va(root, args);
+ va_end(args);
+ return root;
+}
+
+static const char *xml_get_text(xmlNodePtr node, ...)
+{
+ xmlNodePtr child;
+ va_list args;
+
+ if (node == NULL) {
+ return NULL;
+ }
+
+ va_start(args, node);
+ node = xml_seek_va(node, args);
+ va_end(args);
+
+ if (node == NULL) {
+ return NULL;
+ }
+
+ for (child = node->xmlChildrenNode; child; child = child->next) {
+ if (child->type == XML_TEXT_NODE) {
+ return (const char *)child->content;
+ }
+ }
+ return NULL;
+}
+
+static inline int get_addr_mode(xmlDocPtr doc)
+{
+ xmlNodePtr root = xmlDocGetRootElement(doc);
+ if (root == NULL) {
+ return ERROR_MODE;
+ }
+
+ xmlNodePtr cur = xml_seek(root, "Snapshots", "TopGUID", NULL);
+ if (cur == NULL) {
+ return GUID_MODE;
+ } else {
+ return NODE_MODE;
+ }
+};
+
+static int xml_check(xmlNodePtr root, Error **errp)
+{
+ xmlNodePtr image;
+ const char *data;
+
+ data = (const char *)xmlGetProp(root, (const xmlChar *)"Version");
+ if (data == NULL) {
+ error_setg(errp, "There is no version attribute in xml root");
+ return -EINVAL;
+ }
+
+ if (strcmp(data, "1.0") != 0) {
+ error_setg(errp, "Format versions differing from 1.0 are unsupported");
+ return -ENOTSUP;
+ }
+
+ image = xml_seek(root, "StorageData", "Storage", "Image", NULL);
+ if (image == NULL) {
+ error_setg(errp, "There is no image nodes in xml");
+ return -EINVAL;
+ }
+ while (image != NULL) {
+ data = ""; /* make gcc happy */
+ if (image->type != XML_ELEMENT_NODE) {
+ image = image->next;
+ continue;
+ }
+
+ data = xml_get_text(image, "Type", NULL);
+ if (data == NULL) {
+ error_setg(errp, "There is no type node in xml");
+ return -EINVAL;
+ }
+
+ if (strcmp(data, "Plain") == 0) {
+ error_setg(errp, "Plain Parallels images are unsupported");
+ return -ENOTSUP;
+ }
+
+ if (strcmp(data, "Compressed") != 0) {
+ error_setg(errp, "Invalid value of type node: %s", data);
+ return -EINVAL;
+ }
+
+ data = xml_get_text(image, "File", NULL);
+ if (data == NULL) {
+ error_setg(errp, "Invalid image xml node");
+ return -EINVAL;
+ }
+ image = image->next;
+ }
+
+ image = xml_seek(root, "Snapshots", "Shot", NULL);
+ if (image == NULL) {
+ error_setg(errp, "There is no snapshots in xml");
+ return -EINVAL;
+ }
+ while (image != NULL) {
+ data = ""; /* make gcc happy */
+ if (image->type != XML_ELEMENT_NODE) {
+ image = image->next;
+ continue;
+ }
+
+ data = xml_get_text(image, "ParentGUID", NULL);
+ if (data == NULL) {
+ error_setg(errp, "There is no ParentGUID node in Snapshot");
+ return -EINVAL;
+ }
+
+ data = xml_get_text(image, "GUID", NULL);
+ if (data == NULL) {
+ error_setg(errp, "There is no GUID node in Snapshot");
+ return -EINVAL;
+ }
+
+ image = image->next;
+ }
+
+ return 0;
+}
+
+static xmlNodePtr uuid_seek(xmlNodePtr node, const char *uuid)
+{
+ while (node != NULL) {
+ const char *cur_uuid;
+ if (node->type != XML_ELEMENT_NODE) {
+ node = node->next;
+ continue;
+ }
+ cur_uuid = xml_get_text(node, "GUID", NULL);
+ if (cur_uuid == NULL) {
+ return NULL;
+ }
+ if (strcmp(uuid, cur_uuid) == 0) {
+ return node;
+ }
+ node = node->next;
+ }
+
+ return NULL;
+}
+
+static xmlNodePtr uuid_image_seek(xmlNodePtr root, const char *uuid)
+{
+ if (uuid == NULL) {
+ return NULL;
+ }
+
+ xmlNodePtr image = xml_seek(root, "StorageData", "Storage", "Image", NULL);
+
+ return uuid_seek(image, uuid);
+}
+
+static xmlNodePtr uuid_snap_seek(xmlNodePtr root, const char *uuid)
+{
+ if (uuid == NULL) {
+ return NULL;
+ }
+
+ xmlNodePtr snap = xml_seek(root, "Snapshots", "Shot", NULL);
+
+ return uuid_seek(snap, uuid);
+}
+
+static inline xmlNodePtr get_parent_snap(xmlNodePtr root, xmlNodePtr snap)
+{
+ const char *uuid = xml_get_text(snap, "ParentGUID", NULL);
+ if (uuid && strcmp(uuid, "{"UUID_NONE"}") == 0) {
+ return NULL;
+ }
+
+ return uuid_snap_seek(root, uuid);
+}
+
+static inline xmlNodePtr snap2image(xmlNodePtr root, xmlNodePtr snap)
+{
+ const char *uuid = xml_get_text(snap, "GUID", NULL);
+ return uuid_image_seek(root, uuid);
+}
+
+static inline xmlNodePtr image2snap(xmlNodePtr root, xmlNodePtr image)
+{
+ const char *uuid = xml_get_text(image, "GUID", NULL);
+ return uuid_snap_seek(root, uuid);
+}
+
+static int snap_open_xml(BlockDriverState *bs, xmlNodePtr snap,
+ QDict *opts, Error **errp)
+{
+ BDRVPrlXmlState *s = bs->opaque;
+ BlockDriverState *last_bs = s->image->bs;
+ int ret = 0;
+ const char *filename = NULL;
+ size_t len = 0;
+ xmlNodePtr root = xmlDocGetRootElement(s->xml);
+ xmlNodePtr image = snap2image(root, snap);
+
+ if (image == NULL) {
+ error_setg(errp, "Incorrent xml");
+ return -EINVAL;
+ }
+ filename = xml_get_text(image, "File", NULL);
+ if (filename == NULL) {
+ error_setg(errp, "Incorrent xml");
+ return -EINVAL;
+ }
+
+ while (last_bs->backing != NULL) {
+ last_bs = last_bs->backing->bs;
+ }
+
+ len = strlen(filename);
+ if (len > sizeof(last_bs->backing_file)) {
+ error_setg(errp, "Incorrent filename %s", filename);
+ return -EINVAL;
+ }
+ pstrcpy(last_bs->backing_file, sizeof(last_bs->backing_file),
+ filename);
+
+ assert(strlen("parallels") < sizeof(last_bs->backing_format));
+ pstrcpy(last_bs->backing_format, sizeof(last_bs->backing_format),
+ "parallels");
+ ret = bdrv_open_backing_file(last_bs, NULL, "backing", errp);
+ return ret;
+}
+
+static int first_open_xml(BlockDriverState *bs, xmlNodePtr snap,
+ QDict *opts, Error **errp)
+{
+ char image_path[PATH_MAX] = {};
+ BDRVPrlXmlState *s = bs->opaque;
+ xmlNodePtr root = xmlDocGetRootElement(s->xml);
+ xmlNodePtr image = snap2image(root, snap);
+ if (image == NULL) {
+ error_setg(errp, "Incorrent xml");
+ return -EINVAL;
+ }
+ const char *filename = xml_get_text(image, "File", NULL);
+ if (s->image != NULL) {
+ return -EINVAL;
+ }
+ path_combine(image_path, sizeof(image_path),
+ bs->filename, filename);
+ qdict_del(opts, "file");
+ s->image = bdrv_open_child(image_path, opts, "image", bs,
+ &child_format, false, errp);
+ if (s->image == NULL) {
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int64_t prl_xml_get_length(xmlNodePtr root)
+{
+ const char *data;
+ int64_t ret;
+
+ data = xml_get_text(root, "Disk_Parameters", "Disk_size", NULL);
+ if (data == NULL) {
+ return -EINVAL;
+ } else {
+ const char *endptr;
+ qemu_strtoi64(data, &endptr, 0, &ret);
+ if (*endptr != '\0') {
+ return -EINVAL;
+ }
+ }
+
+ return ret;
+}
+
+static int prl_open_xml(BlockDriverState *bs, QDict *opts, int flags,
+ Error **errp)
+{
+ int ret = -EINVAL, snap_mode;
+ xmlDoc *doc = NULL;
+ xmlNodePtr root;
+ xmlNodePtr snap;
+ BDRVPrlXmlState *s = bs->opaque;
+
+ if (strcmp(basename(bs->filename), PRL_XML_FILENAME) != 0) {
+ error_setg(errp, "Invalid xml name");
+ goto fail;
+ }
+
+ doc = xmlReadFile(bs->filename, NULL, XML_PARSE_NOERROR |
+ XML_PARSE_NOWARNING);
+
+ if (doc == NULL) {
+ error_setg(errp, "Can't open xml");
+ goto fail;
+ }
+
+ s->xml = doc;
+ root = xmlDocGetRootElement(doc);
+ if (root == NULL) {
+ ret = -EINVAL;
+ error_setg(errp, "Invalid xml");
+ goto fail;
+ }
+
+ ret = xml_check(root, errp);
+ if (ret < 0) {
+ goto fail;
+ }
+
+ bs->total_sectors = prl_xml_get_length(root);
+ if (bs->total_sectors < 0) {
+ ret = -EINVAL;
+ error_setg(errp, "Invalid xml");
+ goto fail;
+ }
+
+ snap_mode = get_addr_mode(doc);
+ if (snap_mode == ERROR_MODE) {
+ ret = -EINVAL;
+ error_setg(errp, "Can't determine an address mode");
+ goto fail;
+ }
+ if (snap_mode == NODE_MODE) {
+ ret = -ENOTSUP;
+ error_setg(errp, "The node addressing mode is unsupported now");
+ goto fail;
+ }
+
+ snap = uuid_snap_seek(root, "{"DEF_TOP_SNAPSHOT"}");
+ if (snap == NULL) {
+ ret = -EINVAL;
+ error_setg(errp, "Can't find top image");
+ goto fail;
+ }
+ ret = first_open_xml(bs, snap, opts, errp);
+ if (ret < 0) {
+ error_append_hint(errp, "Can't open top image\n");
+ goto fail;
+ }
+
+ snap = get_parent_snap(root, snap);
+ while (snap != NULL) {
+ ret = snap_open_xml(bs, snap, opts, errp);
+ if (ret < 0) {
+ error_append_hint(errp, "Can't open image\n");
+ goto fail;
+ }
+ snap = get_parent_snap(root, snap);
+ }
+
+ return 0;
+fail:
+ xmlFreeDoc(doc);
+ return ret;
+}
+
+static void prl_close_xml(BlockDriverState *bs)
+{
+ BDRVPrlXmlState *s = bs->opaque;
+ bdrv_unref_child(bs, s->image);
+ xmlFreeDoc(s->xml);
+}
+
+static BlockDriver bdrv_prl_xml = {
+ .format_name = "prl-xml",
+ .instance_size = sizeof(BDRVPrlXmlState),
+ .bdrv_open = prl_open_xml,
+ .bdrv_close = prl_close_xml,
+ .create_opts = &prl_xml_create_opts,
+ .bdrv_child_perm = bdrv_filter_default_perms,
+ .is_filter = true
+};
+
+static void bdrv_prl_init_xml(void)
+{
+ bdrv_register(&bdrv_prl_xml);
+}
+
+block_init(bdrv_prl_init_xml);
diff --git a/block/Makefile.objs b/block/Makefile.objs
index d644bac60a..df146c77fa 100644
--- a/block/Makefile.objs
+++ b/block/Makefile.objs
@@ -6,6 +6,7 @@ block-obj-y += vhdx.o vhdx-endian.o vhdx-log.o
block-obj-y += quorum.o
block-obj-y += parallels.o blkdebug.o blkverify.o blkreplay.o
block-obj-y += block-backend.o snapshot.o qapi.o
+block-obj-$(CONFIG_LIBXML2) += prl-xml.o
block-obj-$(CONFIG_WIN32) += file-win32.o win32-aio.o
block-obj-$(CONFIG_POSIX) += file-posix.o
block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
@@ -48,5 +49,5 @@ block-obj-$(if $(CONFIG_BZIP2),m,n) += dmg-bz2.o
dmg-bz2.o-libs := $(BZIP2_LIBS)
qcow.o-libs := -lz
linux-aio.o-libs := -laio
-parallels.o-cflags := $(LIBXML2_CFLAGS)
-parallels.o-libs := $(LIBXML2_LIBS)
+prl-xml.o-cflags := $(LIBXML2_CFLAGS)
+prl-xml.o-libs := $(LIBXML2_LIBS)
--
2.21.3