From: Klim Kireev <klim.kir...@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.kir...@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.kir...@virtuozzo.com> +* 2015 Denis V. Lunev <d...@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