From patchwork Wed Oct 29 13:38:07 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Denis V. Lunev" X-Patchwork-Id: 404634 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 552C014008B for ; Thu, 30 Oct 2014 00:37:52 +1100 (AEDT) Received: from localhost ([::1]:46141 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XjTRe-0007Rg-DB for incoming@patchwork.ozlabs.org; Wed, 29 Oct 2014 09:37:50 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:60605) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XjTR2-0006WX-Vh for qemu-devel@nongnu.org; Wed, 29 Oct 2014 09:37:18 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1XjTQy-0004XP-F0 for qemu-devel@nongnu.org; Wed, 29 Oct 2014 09:37:12 -0400 Received: from mailhub.sw.ru ([195.214.232.25]:6513 helo=relay.sw.ru) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XjTQx-0004Ae-Qo for qemu-devel@nongnu.org; Wed, 29 Oct 2014 09:37:08 -0400 Received: from hades.sw.ru ([10.30.8.132]) by relay.sw.ru (8.13.4/8.13.4) with ESMTP id s9TDaTri018416; Wed, 29 Oct 2014 17:36:34 +0400 (MSK) From: "Denis V. Lunev" To: Date: Wed, 29 Oct 2014 16:38:07 +0300 Message-Id: <1414589891-32736-3-git-send-email-den@openvz.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1414589891-32736-1-git-send-email-den@openvz.org> References: <1414589891-32736-1-git-send-email-den@openvz.org> X-detected-operating-system: by eggs.gnu.org: OpenBSD 3.x X-Received-From: 195.214.232.25 Cc: Kevin Wolf , "Denis V. Lunev" , Jeff Cody , qemu-devel@nongnu.org, Stefan Hajnoczi Subject: [Qemu-devel] [PATCH 2/6] block/parallels: allow to specify DiskDescriptor.xml instead of image file X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Typically Parallels disk bundle consists of several images which are glued by XML disk descriptor. Also XML hides inside several important parameters which are not available in the image header. This patch allows to specify this XML file as a filename for an image to open. It is allowed only to open Compressed images with the only snapshot inside. No additional options are parsed at the moment. The code itself is dumb enough for a while. If XML file is specified, the file is parsed and the image is reopened as bs->file to keep the rest of the driver untouched. This would be changed later with more features added. Signed-off-by: Denis V. Lunev Acked-by: Roman Kagan CC: Jeff Cody CC: Kevin Wolf CC: Stefan Hajnoczi --- block/parallels.c | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 226 insertions(+), 5 deletions(-) diff --git a/block/parallels.c b/block/parallels.c index 4f9cd8d..201c0f1 100644 --- a/block/parallels.c +++ b/block/parallels.c @@ -27,6 +27,11 @@ #include "block/block_int.h" #include "qemu/module.h" +#if CONFIG_LIBXML2 +#include +#include +#endif + /**************************************************************/ #define HEADER_MAGIC "WithoutFreeSpace" @@ -34,6 +39,10 @@ #define HEADER_VERSION 2 #define HEADER_SIZE 64 +#define PARALLELS_XML 100 +#define PARALLELS_IMAGE 101 + + // always little-endian struct parallels_header { char magic[16]; // "WithoutFreeSpace" @@ -59,6 +68,194 @@ typedef struct BDRVParallelsState { unsigned int off_multiplier; } BDRVParallelsState; +static int parallels_open_image(BlockDriverState *bs, Error **errp); + +#if CONFIG_LIBXML2 +static xmlNodePtr xml_find(xmlNode *node, const char *elem) +{ + xmlNode *child; + + for (child = node->xmlChildrenNode; child != NULL; child = child->next) { + if (!xmlStrcmp(child->name, (const xmlChar *)elem) && + child->type == XML_ELEMENT_NODE) { + return child; + } + } + return NULL; +} + +static xmlNodePtr xml_seek(xmlNode *root, const char *elem) +{ + xmlNode *child = root; + const char *path; + char nodename[128]; + int last = 0; + + path = elem; + if (path[0] == '/') { + path++; + } + if (path[0] == 0) { + return NULL; + } + while (!last) { + const char *p = strchr(path, '/'); + int length; + if (p == NULL) { + length = strlen(path); + last = 1; + } else { + length = p - path; + } + memcpy(nodename, path, length); + nodename[length] = 0; + child = xml_find(child, nodename); + if (child == NULL) { + return NULL; + } + path = ++p; + } + return child; +} + +static const char *xml_get_text(xmlNode *node, const char *name) +{ + xmlNode *child; + + node = xml_seek(node, name); + 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 int parallels_open_xml(BlockDriverState *bs, int flags, Error **errp) +{ + int size, ret; + xmlDoc *doc = NULL; + xmlNode *root, *image; + char *xml = NULL; + const char *data; + char image_path[PATH_MAX]; + Error *local_err = NULL; + + ret = size = bdrv_getlength(bs->file); + if (ret < 0) { + goto fail; + } + /* XML file size should be resonable */ + ret = -EFBIG; + if (size > 65536) { + goto fail; + } + + xml = g_malloc(size + 1); + + ret = bdrv_pread(bs->file, 0, xml, size); + if (ret != size) { + goto fail; + } + xml[size] = 0; + + ret = -EINVAL; + doc = xmlReadMemory(xml, size, NULL, NULL, + XML_PARSE_NOERROR | XML_PARSE_NOWARNING); + if (doc == NULL) { + goto fail; + } + root = xmlDocGetRootElement(doc); + if (root == NULL) { + goto fail; + } + image = xml_seek(root, "/StorageData/Storage/Image"); + data = ""; /* make gcc happy */ + for (size = 0; image != NULL; image = image->next) { + if (image->type != XML_ELEMENT_NODE) { + continue; + } + + size++; + data = xml_get_text(image, "Type"); + if (data != NULL && strcmp(data, "Compressed")) { + error_setg(errp, "Only compressed Parallels images are supported"); + goto done; + } + + data = xml_get_text(image, "File"); + if (data == NULL) { + goto fail; + } + } + /* Images with more than 1 snapshots are not supported at the moment */ + if (size != 1) { + error_setg(errp, "Parallels images with snapshots are not supported"); + goto done; + } + + path_combine(image_path, sizeof(image_path), bs->file->filename, data); + /* the caller (top layer bdrv_open) will close file for us if bs->file + is changed. */ + bs->file = NULL; + + ret = bdrv_open(&bs->file, image_path, NULL, NULL, flags | BDRV_O_PROTOCOL, + NULL, &local_err); + if (ret < 0) { + error_setg_errno(errp, -ret, "Could not open '%s': %s", + image_path, error_get_pretty(local_err)); + error_free(local_err); + } else { + ret = parallels_open_image(bs, errp); + } + +done: + if (doc != NULL) { + xmlFreeDoc(doc); + } + if (xml != NULL) { + g_free(xml); + } + return ret; + +fail: + error_setg(errp, "Failed to parse Parallels disk descriptor XML"); + goto done; +} +#endif + +static int parallels_probe_xml(const uint8_t *buf, int buf_size) +{ + int score = 0; +#if CONFIG_LIBXML2 + xmlDoc *doc; + xmlNode *root; + + doc = xmlReadMemory((const char *)buf, buf_size, NULL, NULL, + XML_PARSE_NOERROR | XML_PARSE_NOWARNING); + if (doc == NULL) { + return 0; /* This is not an XML, we don't care */ + } + + root = xmlDocGetRootElement(doc); + if (root == NULL) { + goto done; + } + if (!xmlStrcmp(root->name, (const xmlChar *)"Parallels_disk_image")) { + score = PARALLELS_XML; + } + +done: + xmlFreeDoc(doc); +#endif + return score; +} + + static int parallels_probe(const uint8_t *buf, int buf_size, const char *filename) { const struct parallels_header *ph = (const void *)buf; @@ -69,13 +266,12 @@ static int parallels_probe(const uint8_t *buf, int buf_size, const char *filenam if ((!memcmp(ph->magic, HEADER_MAGIC, 16) || !memcmp(ph->magic, HEADER_MAGIC2, 16)) && (le32_to_cpu(ph->version) == HEADER_VERSION)) - return 100; + return PARALLELS_IMAGE; - return 0; + return parallels_probe_xml(buf, buf_size); } -static int parallels_open(BlockDriverState *bs, QDict *options, int flags, - Error **errp) +static int parallels_open_image(BlockDriverState *bs, Error **errp) { BDRVParallelsState *s = bs->opaque; int i; @@ -139,13 +335,38 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags, return 0; fail_format: - error_setg(errp, "Image not in Parallels format"); + error_setg(errp, "Image is not in Parallels format"); ret = -EINVAL; fail: g_free(s->catalog_bitmap); return ret; } +static int parallels_open(BlockDriverState *bs, QDict *options, int flags, + Error **errp) +{ + uint8_t buf[1024]; + int score; + + score = bdrv_pread(bs->file, 0, buf, sizeof(buf)); + if (score < 0) { + return score; + } + + score = parallels_probe(buf, (unsigned)score, NULL); + if (score == PARALLELS_XML) { +#if CONFIG_LIBXML2 + return parallels_open_xml(bs, flags, errp); +#endif + } else if (score == PARALLELS_IMAGE) { + return parallels_open_image(bs, errp); + } + + error_setg(errp, "Image is not in Parallels format"); + return -EINVAL; +} + + static int64_t seek_to_sector(BlockDriverState *bs, int64_t sector_num) { BDRVParallelsState *s = bs->opaque;