From: "Denis V. Lunev" <den@openvz.org>
To: Jeff Cody <jcody@redhat.com>
Cc: Kevin Wolf <kwolf@redhat.com>,
qemu-devel@nongnu.org, Stefan Hajnoczi <stefanha@redhat.com>
Subject: Re: [Qemu-devel] [PATCH 2/6] block/parallels: allow to specify DiskDescriptor.xml instead of image file
Date: Wed, 5 Nov 2014 13:51:34 +0300 [thread overview]
Message-ID: <545A0136.2080208@openvz.org> (raw)
In-Reply-To: <20141105021207.GI26767@localhost.localdomain>
On 05/11/14 05:12, Jeff Cody wrote:
> On Wed, Oct 29, 2014 at 04:38:07PM +0300, Denis V. Lunev wrote:
>> 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 <den@openvz.org>
>> Acked-by: Roman Kagan <rkagan@parallels.com>
>> CC: Jeff Cody <jcody@redhat.com>
>> CC: Kevin Wolf <kwolf@redhat.com>
>> CC: Stefan Hajnoczi <stefanha@redhat.com>
>> ---
>> 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 <libxml/parser.h>
>> +#include <libxml/tree.h>
>> +#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);
> You shouldn't need this forward declaration, if you put your new
> function parallels_open_xml() after parallels_open_image().
ok, fair enough. This is a rudiment from my previous internal version
which has been implemented in the other way.
>> +
>> +#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;
> It looks like "elem" is always controlled by us, and not passed by the
> user - will this always be the case?
>
> If not, this doesn't seem safe, with nodename being a local array of
> 128 bytes. How about using g_strdup() or g_strndup() here?
yes, this constraint is used now and will be used in the future.
XML structure is known and it is fixed. Here we just shortcut
the navigation in the tree in a convenient way to pass entire
path is one call.
I think that we can switch to interface like
static xmlNodePtr xml_seek(xmlNode *root, ...)
and call it like
xml_seek(root, "StorageData", "Storage", "Image", NULL);
in this case we will be able to drop a lot of processing and
parsing inside including strchr, memcpy, strlen etc
>> + 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 */
> s/resonable/reasonable
ok
>> + 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 */
> What gcc warning are you trying to suppress here?
use of uninitialized variable in the line below.
path_combine(image_path, sizeof(image_path), bs->file->filename, data);
which can not happen due to check size == 1 a line above.
>
>> + 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);
> error_get_pretty() is not NULL friendly - but I don't think it is an
> issue, because if (ret < 0) is true, then (local_err) should be set as
> well. Just an observation, I don't think you need to do anything here.
thank you for pointing this out
>> + } 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);
>> }
>>
> Hmm. So PARALLELS_XML is 100, and PARALLELS_IMAGE is 101 - and, this
> is used later in this patch, to differentiate between XML and non-XML
> images. This is somewhat abusing the .bdrv_probe(), I think - the
> intention of .bdrv_probe() is to return a confidence score between
> 0-100.
>
> I think you'd be better off just checking the magic in
> parallels_open() rather than overloading the .bdrv_probe() function.
>
you are right. I have missed this thing. Need some thinking on this,
but, anyway, this should be fixed.
>> -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;
>> --
>> 1.9.1
>>
next prev parent reply other threads:[~2014-11-05 10:51 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-10-29 13:38 [Qemu-devel] [PATCH v2 0/6] parallels format support improvements Denis V. Lunev
2014-10-29 13:38 ` [Qemu-devel] [PATCH 1/6] configure: add dependency from libxml2 Denis V. Lunev
2014-11-05 7:32 ` Michael Tokarev
2014-11-05 8:19 ` Denis V. Lunev
2014-10-29 13:38 ` [Qemu-devel] [PATCH 2/6] block/parallels: allow to specify DiskDescriptor.xml instead of image file Denis V. Lunev
2014-11-05 2:12 ` Jeff Cody
2014-11-05 10:51 ` Denis V. Lunev [this message]
2014-10-29 13:38 ` [Qemu-devel] [PATCH 3/6] iotests: simple parallels XML disk descriptor file test added Denis V. Lunev
2014-11-05 2:24 ` Jeff Cody
2014-10-29 13:38 ` [Qemu-devel] [PATCH 4/6] block/parallels: support padded Parallels images Denis V. Lunev
2014-11-05 2:49 ` Jeff Cody
2014-10-29 13:38 ` [Qemu-devel] [PATCH 5/6] iotests: padded parallels image test Denis V. Lunev
2014-11-05 2:55 ` Jeff Cody
2014-10-29 13:38 ` [Qemu-devel] [PATCH 6/6] parallels: change copyright information in the image header Denis V. Lunev
2014-11-05 2:56 ` Jeff Cody
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=545A0136.2080208@openvz.org \
--to=den@openvz.org \
--cc=jcody@redhat.com \
--cc=kwolf@redhat.com \
--cc=qemu-devel@nongnu.org \
--cc=stefanha@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).