qemu-devel
[Top][All Lists]
Advanced

[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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]